Skip to content

Commit 7edcbbb

Browse files
committed
chore: support gcp in cloud-image-uploader
Add support for uploading images to GCP in cloud image uploader. GCP is not enabled by default since it's going to be used for e2e-tests for now. Signed-off-by: Noel Georgi <git@frezbo.dev>
1 parent 0a87020 commit 7edcbbb

File tree

7 files changed

+495
-3
lines changed

7 files changed

+495
-3
lines changed

.secrets.yaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ secrets:
88
AZURE_TENANT_ID: ENC[AES256_GCM,data:dZapmCqJeTx9C0us38mxDpPbdxBn39fJOmIc+5MgnAI6esT5,iv:s/GuStsQKgdc/6jpq2YMAE9GggLH/xGfrDzzgk/4kmQ=,tag:+dVM3/Joq3OA/opmSU6TSA==,type:str]
99
EM_PROJECT_ID: ENC[AES256_GCM,data:nPVZ+Uoul/W7UpxIoeMP1n3YhuEjq3fNKD+zoso4FBP2Obd0,iv:SSF8KZBczWvCJjZpvDo60mnoM21CrzdmmKs2reLi8w0=,tag:VKjsQSHqiQY+IzkIXO70MA==,type:str]
1010
EM_API_TOKEN: ENC[AES256_GCM,data:PnNDZTRDTubebmtAuH1sAuEp5ZwzVie5WA0AhCUk26M=,iv:5MdcOwY+QrIdkFgCXcs2rBGCXQBnhi/EDxTPWr/vCMs=,tag:mcQ9qrWPYMaPalzr/GV7pQ==,type:str]
11+
GOOGLE_PROJECT_ID: ENC[AES256_GCM,data:egcG5hIa5aq6tSRjhA==,iv:g/6pkcSJIQNNgoon6X+6DH2JaQgLKfTDpPUNFjlJ6Xg=,tag:ygF0I8bLRRbj2RdogQqxmw==,type:str]
12+
GOOGLE_CREDENTIALS_JSON: ENC[AES256_GCM,data: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,iv:R8UR53WDK3kFRbLwOvWXt0UhaaPgxBLkiSJaPSRvTVA=,tag:RdUgGICSm8P2Dh0tzSbFvw==,type:str]
1113
sops:
1214
kms: []
1315
gcp_kms: []
@@ -23,8 +25,8 @@ sops:
2325
ZE0zRWwxVzBLL3Q1WW1FNmVvc0txZm8K+GkjAq/WSduuDrsbeyqVi29Pj2IL25mA
2426
a11K/HVqTCU834uHQXjpN3keJS23v5BJGZCpOwVXyZX8f1yAm/ZQAA==
2527
-----END AGE ENCRYPTED FILE-----
26-
lastmodified: "2024-05-06T06:16:37Z"
27-
mac: ENC[AES256_GCM,data:q3NlR1Yi/4J/aCZUbatqL50gW7FPCMrYhYXSZWaZz4f+MLqzV+ymk4dO2QvS7ssgIX7TUVXjR2ClXgl+U3p31rqeVm8o8+LQPimJJnaQ0JrbO8tRZP3sQrQ4tghOKM1hFO/sz/52NTvoxl9OS9qIsq38fM+LUor4gEFekBQEyow=,iv:tZu7y6uezwvUFeHq4DdgNI0izg7DWspDIbzUxKTIBDs=,tag:PvYXbzD4HcWOP1Jw+zmHmA==,type:str]
28+
lastmodified: "2024-09-04T04:28:03Z"
29+
mac: ENC[AES256_GCM,data:PHRJmUueHiv84Pt8fHAze4LWl0RiuNuKozWC/G1ixhqL055Zfe3X5Iv//0qFXDMWBy9b09IiXbd4WwsexaoLhgRE1ZBul1AObK8gsbBHf1DyjxgCek/AJNlIS3WY6NPZ9L5lwxuD0BhqbgzGw6It3rcIyx0q++Zo2UoDQyMciWA=,iv:0R2VsKSFfIzapJsqQELM7LMXvEhDmDWr1f9Amg5i4hs=,tag:ig8uLTnupsmPlgV8GEZTgA==,type:str]
2830
pgp:
2931
- created_at: "2024-04-29T17:03:17Z"
3032
enc: |-
@@ -86,4 +88,4 @@ sops:
8688
-----END PGP MESSAGE-----
8789
fp: AA5213AF261C1977AF38B03A94B473337258BFD5
8890
unencrypted_suffix: _unencrypted
89-
version: 3.8.1
91+
version: 3.9.0

hack/cloud-image-uploader/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,8 @@ The App registration only needs permissions to the Compute Gallery and the Stora
8989
- Select Access control (IAM)
9090
- Select Add role assignment
9191
- Select the **Storage Blob Data Contributor** role
92+
93+
## Google Cloud Pre-requisites
94+
95+
- `GOOGLE_PROJECT_ID` - Google Cloud Project ID
96+
- `GOOGLE_CREDENTIALS_JSON` - Google Cloud Service Account JSON

hack/cloud-image-uploader/gcp.go

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
package main
6+
7+
import (
8+
"context"
9+
"errors"
10+
"fmt"
11+
"io"
12+
"log"
13+
"net/http"
14+
"os"
15+
"path/filepath"
16+
"strings"
17+
"time"
18+
19+
"cloud.google.com/go/storage"
20+
"github.com/google/uuid"
21+
"github.com/siderolabs/go-retry/retry"
22+
"golang.org/x/sync/errgroup"
23+
"google.golang.org/api/compute/v1"
24+
"google.golang.org/api/googleapi"
25+
"google.golang.org/api/iterator"
26+
"google.golang.org/api/option"
27+
)
28+
29+
// GCPUploder registers the image in GCP.
30+
type GCPUploder struct {
31+
Options Options
32+
33+
storageClient *storage.Client
34+
computeService *compute.Service
35+
projectID string
36+
37+
imagePath string
38+
}
39+
40+
// NewGCPUploder creates a new GCPUploder.
41+
func NewGCPUploder(options Options) (*GCPUploder, error) {
42+
projectID := os.Getenv("GOOGLE_PROJECT_ID")
43+
credentials := os.Getenv("GOOGLE_CREDENTIALS_JSON")
44+
45+
if projectID == "" {
46+
return nil, fmt.Errorf("gcp: GOOGLE_PROJECT_ID is not set")
47+
}
48+
49+
if credentials == "" {
50+
return nil, fmt.Errorf("gcp: GOOGLE_CREDENTIALS_JSON is not set")
51+
}
52+
53+
gcpUploader := &GCPUploder{
54+
Options: options,
55+
}
56+
57+
gcpUploader.projectID = projectID
58+
59+
var err error
60+
61+
gcpUploader.storageClient, err = storage.NewClient(context.Background(), option.WithCredentialsJSON([]byte(credentials)))
62+
if err != nil {
63+
return nil, fmt.Errorf("gcp: failed to create google storage client: %w", err)
64+
}
65+
66+
gcpUploader.computeService, err = compute.NewService(context.Background(), option.WithCredentialsJSON([]byte(credentials)))
67+
if err != nil {
68+
return nil, fmt.Errorf("gcp: failed to create google compute service: %w", err)
69+
}
70+
71+
return gcpUploader, nil
72+
}
73+
74+
// Upload uploads the image to GCP.
75+
func (u *GCPUploder) Upload(ctx context.Context) error {
76+
bucketName := fmt.Sprintf("talos-image-upload-%s", uuid.New())
77+
78+
bucketHandle := u.storageClient.Bucket(bucketName)
79+
80+
if err := bucketHandle.Create(ctx, u.projectID, &storage.BucketAttrs{
81+
PublicAccessPrevention: storage.PublicAccessPreventionEnforced,
82+
}); err != nil {
83+
return fmt.Errorf("gcp: failed to create bucket %s: %w", bucketName, err)
84+
}
85+
86+
log.Println("gcp: created bucket", bucketName)
87+
88+
defer func() {
89+
objects := bucketHandle.Objects(ctx, nil)
90+
91+
for {
92+
objAttr, err := objects.Next()
93+
if errors.Is(err, iterator.Done) {
94+
break
95+
}
96+
97+
if err != nil {
98+
log.Printf("gcp: failed to list objects: %v", err)
99+
}
100+
101+
if err := bucketHandle.Object(objAttr.Name).Delete(ctx); err != nil {
102+
log.Printf("gcp: failed to delete object %s: %v", objAttr.Name, err)
103+
}
104+
}
105+
106+
if err := bucketHandle.Delete(ctx); err != nil {
107+
log.Printf("gcp: failed to delete bucket %s: %v", bucketName, err)
108+
}
109+
}()
110+
111+
var g errgroup.Group
112+
113+
for _, arch := range u.Options.Architectures {
114+
g.Go(func() error {
115+
return u.uploadImage(ctx, arch, bucketName)
116+
})
117+
}
118+
119+
if err := g.Wait(); err != nil {
120+
return fmt.Errorf("gcp: failed to upload images: %w", err)
121+
}
122+
123+
return nil
124+
}
125+
126+
func (u *GCPUploder) uploadImage(ctx context.Context, arch, bucketName string) error {
127+
objectPath := u.Options.GCPImage(arch)
128+
129+
objectName := filepath.Base(objectPath)
130+
131+
objectReader, err := os.Open(objectPath)
132+
if err != nil {
133+
return fmt.Errorf("gcp: failed to open object data file %s: %w", objectPath, err)
134+
}
135+
136+
objectHandle := u.storageClient.Bucket(bucketName).Object(objectName)
137+
138+
objectWriter := objectHandle.NewWriter(ctx)
139+
140+
defer objectWriter.Close() //nolint:errcheck
141+
142+
if _, err := io.Copy(objectWriter, objectReader); err != nil {
143+
return fmt.Errorf("gcp: failed to write object data: %w", err)
144+
}
145+
146+
if err := objectWriter.Close(); err != nil {
147+
return fmt.Errorf("gcp: failed to close object writer: %w", err)
148+
}
149+
150+
u.imagePath = fmt.Sprintf("https://storage.googleapis.com/%s/%s", bucketName, objectName)
151+
152+
log.Println("gcp: uploaded image", u.imagePath)
153+
154+
return u.registerImage(arch)
155+
}
156+
157+
//nolint:gocyclo
158+
func (u *GCPUploder) registerImage(arch string) error {
159+
imageName := fmt.Sprintf("talos-%s-%s", strings.ReplaceAll(u.Options.Tag, ".", "-"), arch)
160+
161+
if u.Options.NamePrefix != "" {
162+
imageName = fmt.Sprintf("%s-talos-%s-%s", u.Options.NamePrefix, strings.ReplaceAll(u.Options.Tag, ".", "-"), arch)
163+
}
164+
165+
exists, err := u.checkImageExists(imageName)
166+
if err != nil {
167+
return err
168+
}
169+
170+
if exists {
171+
log.Printf("gcp: image %s already exists, deleting", imageName)
172+
173+
if deleteErr := u.deleteImage(imageName); deleteErr != nil {
174+
return deleteErr
175+
}
176+
}
177+
178+
operationID, link, err := u.insertImage(imageName, arch)
179+
if err != nil {
180+
return err
181+
}
182+
183+
if err := retry.Constant(15*time.Minute, retry.WithUnits(30*time.Second)).Retry(func() error {
184+
op, err := u.computeService.GlobalOperations.Get(u.projectID, operationID).Do()
185+
if err != nil {
186+
return fmt.Errorf("gcp: failed to get operation: %w", err)
187+
}
188+
189+
if op.HTTPStatusCode != http.StatusOK {
190+
return fmt.Errorf("gcp: operation failed with http error message: %s", op.HttpErrorMessage)
191+
}
192+
193+
if op.Error != nil {
194+
return fmt.Errorf("gcp: operation faild with error message: %s", op.Error.Errors[0].Message)
195+
}
196+
197+
if op.Status == "DONE" {
198+
return nil
199+
}
200+
201+
log.Printf("gcp: image creation progress: %d", op.Progress)
202+
203+
return retry.ExpectedError(fmt.Errorf("gcp: image status is %s", op.Status))
204+
}); err != nil {
205+
return fmt.Errorf("gcp: image creation is taking longer than expected: %w", err)
206+
}
207+
208+
pushResult(CloudImage{
209+
Cloud: "gcp",
210+
Tag: u.Options.Tag,
211+
Region: "us",
212+
Arch: arch,
213+
Type: "compute#image",
214+
ID: link,
215+
})
216+
217+
return nil
218+
}
219+
220+
func (u *GCPUploder) checkImageExists(imageName string) (bool, error) {
221+
_, err := u.computeService.Images.Get(u.projectID, imageName).Do()
222+
if err != nil {
223+
var googleErr *googleapi.Error
224+
225+
if errors.As(err, &googleErr) {
226+
if googleErr.Code == http.StatusNotFound {
227+
return false, nil
228+
}
229+
}
230+
231+
return false, fmt.Errorf("gcp: failed to get image %s: %w", imageName, err)
232+
}
233+
234+
return true, nil
235+
}
236+
237+
func (u *GCPUploder) insertImage(imageName, arch string) (operationID, imageLink string, err error) {
238+
var archImage string
239+
240+
switch arch {
241+
case "amd64":
242+
archImage = "x86_64"
243+
case "arm64":
244+
archImage = "ARM64"
245+
default:
246+
return "", "", fmt.Errorf("gcp: unknown architecture %s", arch)
247+
}
248+
249+
op, err := u.computeService.Images.Insert(u.projectID, &compute.Image{
250+
Architecture: archImage,
251+
Description: fmt.Sprintf("Talos %s %s", u.Options.Tag, arch),
252+
GuestOsFeatures: []*compute.GuestOsFeature{
253+
{
254+
Type: "VIRTIO_SCSI_MULTIQUEUE",
255+
},
256+
},
257+
Name: imageName,
258+
RawDisk: &compute.ImageRawDisk{
259+
Source: u.imagePath,
260+
},
261+
}).Do()
262+
if err != nil {
263+
return "", "", fmt.Errorf("gcp: failed to insert image: %w", err)
264+
}
265+
266+
if op.HTTPStatusCode != http.StatusOK {
267+
return "", "", fmt.Errorf("gcp: insert image failed with http error message: %s", op.HttpErrorMessage)
268+
}
269+
270+
if op.Error != nil {
271+
return "", "", fmt.Errorf("gcp: insert image failed with error message: %s", op.Error.Errors[0].Message)
272+
}
273+
274+
log.Printf("gcp: image %s is being created with operation %s", imageName, op.Name)
275+
276+
return op.Name, op.TargetLink, nil
277+
}
278+
279+
func (u *GCPUploder) deleteImage(imageName string) error {
280+
if _, err := u.computeService.Images.Delete(u.projectID, imageName).Do(); err != nil {
281+
return fmt.Errorf("gcp: failed to delete image %s: %w", imageName, err)
282+
}
283+
284+
if err := retry.Constant(5*time.Minute, retry.WithUnits(30*time.Second)).Retry(func() error {
285+
_, err := u.computeService.Images.Get(u.projectID, imageName).Do()
286+
if err != nil {
287+
var googleErr *googleapi.Error
288+
289+
if errors.As(err, &googleErr) {
290+
if googleErr.Code == http.StatusNotFound {
291+
return nil
292+
}
293+
}
294+
295+
return err
296+
}
297+
298+
return retry.ExpectedError(fmt.Errorf("gcp: image %s still exists", imageName))
299+
}); err != nil {
300+
return fmt.Errorf("gcp: failed to delete image %s: %w", imageName, err)
301+
}
302+
303+
return nil
304+
}

hack/cloud-image-uploader/go.mod

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/siderolabs/cloud-image-uploader
33
go 1.22.4
44

55
require (
6+
cloud.google.com/go/storage v1.43.0
67
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
78
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
89
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0
@@ -19,9 +20,15 @@ require (
1920
github.com/siderolabs/go-retry v0.3.3
2021
github.com/spf13/pflag v1.0.5
2122
golang.org/x/sync v0.8.0
23+
google.golang.org/api v0.187.0
2224
)
2325

2426
require (
27+
cloud.google.com/go v0.115.0 // indirect
28+
cloud.google.com/go/auth v0.6.1 // indirect
29+
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
30+
cloud.google.com/go/compute/metadata v0.3.0 // indirect
31+
cloud.google.com/go/iam v1.1.8 // indirect
2532
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
2633
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
2734
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
@@ -33,14 +40,35 @@ require (
3340
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
3441
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
3542
github.com/dimchansky/utfbom v1.1.1 // indirect
43+
github.com/felixge/httpsnoop v1.0.4 // indirect
44+
github.com/go-logr/logr v1.4.1 // indirect
45+
github.com/go-logr/stdr v1.2.2 // indirect
3646
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
3747
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
48+
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
49+
github.com/golang/protobuf v1.5.4 // indirect
50+
github.com/google/s2a-go v0.1.7 // indirect
51+
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
52+
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
3853
github.com/jmespath/go-jmespath v0.4.0 // indirect
3954
github.com/kylelemons/godebug v1.1.0 // indirect
4055
github.com/mitchellh/go-homedir v1.1.0 // indirect
4156
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
57+
go.opencensus.io v0.24.0 // indirect
58+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
59+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
60+
go.opentelemetry.io/otel v1.24.0 // indirect
61+
go.opentelemetry.io/otel/metric v1.24.0 // indirect
62+
go.opentelemetry.io/otel/trace v1.24.0 // indirect
4263
golang.org/x/crypto v0.26.0 // indirect
4364
golang.org/x/net v0.28.0 // indirect
65+
golang.org/x/oauth2 v0.21.0 // indirect
4466
golang.org/x/sys v0.24.0 // indirect
4567
golang.org/x/text v0.17.0 // indirect
68+
golang.org/x/time v0.5.0 // indirect
69+
google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect
70+
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect
71+
google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect
72+
google.golang.org/grpc v1.64.0 // indirect
73+
google.golang.org/protobuf v1.34.2 // indirect
4674
)

0 commit comments

Comments
 (0)