|
1 | | -//go:build gcp |
2 | | -// +build gcp |
3 | | - |
4 | | -// NOTE: We use build tags to differentiate GCP testing for better isolation and parallelism when executing our tests. |
5 | | - |
6 | 1 | package gcp_test |
7 | 2 |
|
8 | 3 | import ( |
9 | | - "archive/tar" |
10 | | - "bytes" |
11 | | - "compress/gzip" |
12 | | - "fmt" |
13 | | - "strings" |
| 4 | + "context" |
| 5 | + "net" |
14 | 6 | "testing" |
15 | 7 |
|
| 8 | + cloudbuild "cloud.google.com/go/cloudbuild/apiv1/v2" |
16 | 9 | cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" |
17 | 10 | "github.com/gruntwork-io/terratest/modules/gcp" |
18 | | - "github.com/gruntwork-io/terratest/modules/logger" |
19 | | - "github.com/gruntwork-io/terratest/modules/random" |
| 11 | + "github.com/stretchr/testify/assert" |
20 | 12 | "github.com/stretchr/testify/require" |
| 13 | + "google.golang.org/api/option" |
| 14 | + "google.golang.org/grpc" |
| 15 | + "google.golang.org/grpc/credentials/insecure" |
| 16 | + "google.golang.org/grpc/test/bufconn" |
21 | 17 | ) |
22 | 18 |
|
23 | | -func TestCreateBuild(t *testing.T) { |
24 | | - t.Parallel() |
25 | | - // This test performs the following steps: |
26 | | - // |
27 | | - // 1. Creates a tarball with a single Dockerfile |
28 | | - // 2. Creates a GCS bucket |
29 | | - // 3. Uploads the tarball to the GCS Bucket |
30 | | - // 4. Triggers a build using the Cloud Build API |
31 | | - // 5. Attempts to untag and delete all pushed Build images (best-effort cleanup) |
32 | | - // 6. Deletes the GCS bucket |
33 | | - |
34 | | - // Create and add some files to the archive. |
35 | | - tarball := createSampleAppTarball(t) |
36 | | - |
37 | | - // Create GCS bucket |
38 | | - projectID := gcp.GetGoogleProjectIDFromEnvVar(t) |
39 | | - id := random.UniqueID() |
40 | | - gsBucketName := "cloud-build-terratest-" + strings.ToLower(id) |
41 | | - sampleAppPath := "docker-example.tar.gz" |
42 | | - imagePath := fmt.Sprintf("gcr.io/%s/test-image-%s", projectID, strings.ToLower(id)) |
43 | | - |
44 | | - logger.Default.Logf(t, "Random values selected Bucket Name = %s\n", gsBucketName) |
45 | | - |
46 | | - gcp.CreateStorageBucket(t, projectID, gsBucketName, nil) |
47 | | - defer gcp.DeleteStorageBucket(t, gsBucketName) |
48 | | - |
49 | | - // Write the compressed archive to the storage bucket |
50 | | - objectURL := gcp.WriteBucketObject(t, gsBucketName, sampleAppPath, tarball, "application/gzip") |
51 | | - logger.Default.Logf(t, "Got URL: %s", objectURL) |
52 | | - |
53 | | - // Create a new build |
54 | | - build := &cloudbuildpb.Build{ |
55 | | - Source: &cloudbuildpb.Source{ |
56 | | - Source: &cloudbuildpb.Source_StorageSource{ |
57 | | - StorageSource: &cloudbuildpb.StorageSource{ |
58 | | - Bucket: gsBucketName, |
59 | | - Object: sampleAppPath, |
60 | | - }, |
61 | | - }, |
62 | | - }, |
63 | | - Steps: []*cloudbuildpb.BuildStep{{ |
64 | | - Name: "gcr.io/cloud-builders/docker", |
65 | | - Args: []string{"build", "-t", imagePath, "."}, |
66 | | - }}, |
67 | | - Images: []string{imagePath}, |
68 | | - } |
69 | | - |
70 | | - // CreateBuild blocks until the build is complete |
71 | | - b := gcp.CreateBuild(t, projectID, build) |
72 | | - |
73 | | - // Attempt to delete the pushed build images (best-effort cleanup). |
74 | | - // Note: GCR (gcr.io) has been deprecated in favor of Artifact Registry. |
75 | | - // The cleanup may fail due to permission changes, but this doesn't affect |
76 | | - // the validity of the Cloud Build test itself. |
77 | | - // We could just use the `b` struct above, but we want to explicitly test |
78 | | - // the `GetBuild` method. |
79 | | - b2 := gcp.GetBuild(t, projectID, b.GetId()) |
80 | | - for _, image := range b2.GetImages() { |
81 | | - if err := gcp.DeleteGCRRepoE(t, image); err != nil { |
82 | | - logger.Default.Logf(t, "Warning: Failed to delete image %s (this may be expected due to GCR deprecation): %v", image, err) |
83 | | - } |
84 | | - } |
85 | | - |
86 | | - // Empty the storage bucket so we can delete it |
87 | | - defer gcp.EmptyStorageBucket(t, gsBucketName) |
| 19 | +// fakeCloudBuildServer only implements the methods the terratest *WithClient helpers actually |
| 20 | +// call. CreateBuild (long-running op) is left to the build-tagged integration test. |
| 21 | +type fakeCloudBuildServer struct { |
| 22 | + cloudbuildpb.UnimplementedCloudBuildServer |
| 23 | + |
| 24 | + getBuild func(*cloudbuildpb.GetBuildRequest) *cloudbuildpb.Build |
| 25 | + listBuilds func(*cloudbuildpb.ListBuildsRequest) *cloudbuildpb.ListBuildsResponse |
| 26 | +} |
| 27 | + |
| 28 | +func (f *fakeCloudBuildServer) GetBuild(_ context.Context, req *cloudbuildpb.GetBuildRequest) (*cloudbuildpb.Build, error) { |
| 29 | + return f.getBuild(req), nil |
88 | 30 | } |
89 | 31 |
|
90 | | -func createSampleAppTarball(t *testing.T) *bytes.Reader { |
| 32 | +func (f *fakeCloudBuildServer) ListBuilds(_ context.Context, req *cloudbuildpb.ListBuildsRequest) (*cloudbuildpb.ListBuildsResponse, error) { |
| 33 | + return f.listBuilds(req), nil |
| 34 | +} |
| 35 | + |
| 36 | +// newFakeCloudBuildClient runs `srv` on an in-memory bufconn gRPC server and returns a client |
| 37 | +// pointed at it. No credentials, no network. |
| 38 | +func newFakeCloudBuildClient(t *testing.T, srv *fakeCloudBuildServer) *cloudbuild.Client { |
91 | 39 | t.Helper() |
92 | 40 |
|
93 | | - var buf bytes.Buffer |
| 41 | + lis := bufconn.Listen(1024 * 1024) |
| 42 | + grpcServer := grpc.NewServer() |
| 43 | + cloudbuildpb.RegisterCloudBuildServer(grpcServer, srv) |
94 | 44 |
|
95 | | - tw := tar.NewWriter(&buf) |
| 45 | + go func() { _ = grpcServer.Serve(lis) }() |
96 | 46 |
|
97 | | - file := `FROM busybox:latest |
98 | | -MAINTAINER Rob Morgan (rob@gruntwork.io) |
99 | | - ` |
| 47 | + t.Cleanup(func() { grpcServer.Stop(); _ = lis.Close() }) |
100 | 48 |
|
101 | | - hdr := &tar.Header{ |
102 | | - Name: "Dockerfile", |
103 | | - Mode: 0600, |
104 | | - Size: int64(len(file)), |
105 | | - } |
| 49 | + conn, err := grpc.NewClient("passthrough:///bufnet", |
| 50 | + grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { return lis.DialContext(ctx) }), |
| 51 | + grpc.WithTransportCredentials(insecure.NewCredentials()), |
| 52 | + ) |
| 53 | + require.NoError(t, err) |
| 54 | + t.Cleanup(func() { _ = conn.Close() }) |
106 | 55 |
|
107 | | - err := tw.WriteHeader(hdr) |
| 56 | + client, err := cloudbuild.NewClient(context.Background(), option.WithGRPCConn(conn)) |
108 | 57 | require.NoError(t, err) |
| 58 | + t.Cleanup(func() { _ = client.Close() }) |
109 | 59 |
|
110 | | - _, werr := tw.Write([]byte(file)) |
111 | | - require.NoError(t, werr) |
| 60 | + return client |
| 61 | +} |
112 | 62 |
|
113 | | - cerr := tw.Close() |
114 | | - require.NoError(t, cerr) |
| 63 | +func TestGetBuildWithClient(t *testing.T) { |
| 64 | + t.Parallel() |
115 | 65 |
|
116 | | - // gzip the tar archive |
117 | | - var zbuf bytes.Buffer |
| 66 | + client := newFakeCloudBuildClient(t, &fakeCloudBuildServer{ |
| 67 | + getBuild: func(req *cloudbuildpb.GetBuildRequest) *cloudbuildpb.Build { |
| 68 | + return &cloudbuildpb.Build{Id: req.GetId(), ProjectId: req.GetProjectId(), Status: cloudbuildpb.Build_SUCCESS} |
| 69 | + }, |
| 70 | + }) |
118 | 71 |
|
119 | | - gzw := gzip.NewWriter(&zbuf) |
120 | | - _, gwerr := gzw.Write(buf.Bytes()) |
121 | | - require.NoError(t, gwerr) |
| 72 | + got, err := gcp.GetBuildWithClient(context.Background(), client, "p", "b1") |
| 73 | + require.NoError(t, err) |
| 74 | + assert.Equal(t, "b1", got.GetId()) |
| 75 | + assert.Equal(t, cloudbuildpb.Build_SUCCESS, got.GetStatus()) |
| 76 | +} |
| 77 | + |
| 78 | +func TestGetBuildsWithClient(t *testing.T) { |
| 79 | + t.Parallel() |
122 | 80 |
|
123 | | - gcerr := gzw.Close() |
124 | | - require.NoError(t, gcerr) |
| 81 | + client := newFakeCloudBuildClient(t, &fakeCloudBuildServer{ |
| 82 | + listBuilds: func(req *cloudbuildpb.ListBuildsRequest) *cloudbuildpb.ListBuildsResponse { |
| 83 | + assert.Equal(t, "p", req.GetProjectId()) |
125 | 84 |
|
126 | | - // return the compressed buffer |
127 | | - return bytes.NewReader(zbuf.Bytes()) |
| 85 | + return &cloudbuildpb.ListBuildsResponse{Builds: []*cloudbuildpb.Build{{Id: "a"}, {Id: "b"}}} |
| 86 | + }, |
| 87 | + }) |
| 88 | + |
| 89 | + got, err := gcp.GetBuildsWithClient(context.Background(), client, "p") |
| 90 | + require.NoError(t, err) |
| 91 | + require.Len(t, got, 2) |
| 92 | +} |
| 93 | + |
| 94 | +func TestGetBuildsForTriggerWithClient(t *testing.T) { |
| 95 | + t.Parallel() |
| 96 | + |
| 97 | + client := newFakeCloudBuildClient(t, &fakeCloudBuildServer{ |
| 98 | + listBuilds: func(_ *cloudbuildpb.ListBuildsRequest) *cloudbuildpb.ListBuildsResponse { |
| 99 | + return &cloudbuildpb.ListBuildsResponse{Builds: []*cloudbuildpb.Build{ |
| 100 | + {Id: "a", BuildTriggerId: "match"}, |
| 101 | + {Id: "b", BuildTriggerId: "other"}, |
| 102 | + {Id: "c", BuildTriggerId: "match"}, |
| 103 | + }} |
| 104 | + }, |
| 105 | + }) |
| 106 | + |
| 107 | + got, err := gcp.GetBuildsForTriggerWithClient(context.Background(), client, "p", "match") |
| 108 | + require.NoError(t, err) |
| 109 | + require.Len(t, got, 2) |
| 110 | + assert.ElementsMatch(t, []string{"a", "c"}, []string{got[0].GetId(), got[1].GetId()}) |
128 | 111 | } |
0 commit comments