github.com/thiagoyeds/go-cloud@v0.26.0/runtimevar/gcpsecretmanager/gcpsecretmanager_test.go (about)

     1  // Copyright 2020 The Go Cloud Development Kit Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package gcpsecretmanager
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"testing"
    22  
    23  	secretmanager "cloud.google.com/go/secretmanager/apiv1"
    24  	"gocloud.dev/internal/gcerr"
    25  	"gocloud.dev/internal/testing/setup"
    26  	"gocloud.dev/runtimevar"
    27  	"gocloud.dev/runtimevar/driver"
    28  	"gocloud.dev/runtimevar/drivertest"
    29  	"google.golang.org/api/option"
    30  	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
    31  	"google.golang.org/grpc/codes"
    32  	"google.golang.org/grpc/status"
    33  )
    34  
    35  // This constant records the project used for the last --record.
    36  // If you want to use --record mode,
    37  // 1. Update this constant to your GCP project ID.
    38  // 2. Ensure that the "Secret Manager API" is enabled for your project.
    39  // TODO(issue #300): Use Terraform to get this.
    40  const projectID = "go-cloud-test-216917"
    41  
    42  func secretKey(secretID string) string {
    43  	return "projects/" + projectID + "/secrets/" + secretID
    44  }
    45  
    46  type harness struct {
    47  	client *secretmanager.Client
    48  	closer func()
    49  }
    50  
    51  func newHarness(t *testing.T) (drivertest.Harness, error) {
    52  	ctx := context.Background()
    53  	conn, done := setup.NewGCPgRPCConn(ctx, t, "secretmanager.googleapis.com:443", "runtimevar")
    54  
    55  	client, err := secretmanager.NewClient(ctx, option.WithGRPCConn(conn))
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	return &harness{
    61  		client: client,
    62  		closer: func() {
    63  			_ = client.Close()
    64  			done()
    65  		},
    66  	}, nil
    67  }
    68  
    69  func (h *harness) MakeWatcher(_ context.Context, name string, decoder *runtimevar.Decoder) (driver.Watcher, error) {
    70  	return newWatcher(h.client, secretKey(name), decoder, nil)
    71  }
    72  
    73  func (h *harness) CreateVariable(ctx context.Context, name string, val []byte) error {
    74  	_, err := h.client.CreateSecret(ctx, &secretmanagerpb.CreateSecretRequest{
    75  		Parent:   "projects/" + projectID,
    76  		SecretId: name,
    77  		Secret: &secretmanagerpb.Secret{
    78  			Replication: &secretmanagerpb.Replication{
    79  				Replication: &secretmanagerpb.Replication_Automatic_{
    80  					Automatic: &secretmanagerpb.Replication_Automatic{},
    81  				},
    82  			},
    83  			Labels: map[string]string{
    84  				"project": "runtimevar",
    85  			},
    86  		},
    87  	})
    88  	if err != nil {
    89  		return err
    90  	}
    91  
    92  	// Add initial secret version.
    93  	_, err = h.client.AddSecretVersion(ctx, &secretmanagerpb.AddSecretVersionRequest{
    94  		Parent:  secretKey(name),
    95  		Payload: &secretmanagerpb.SecretPayload{Data: val},
    96  	})
    97  
    98  	return err
    99  }
   100  
   101  func (h *harness) UpdateVariable(ctx context.Context, name string, val []byte) error {
   102  	_, err := h.client.AddSecretVersion(ctx, &secretmanagerpb.AddSecretVersionRequest{
   103  		Parent:  secretKey(name),
   104  		Payload: &secretmanagerpb.SecretPayload{Data: val},
   105  	})
   106  
   107  	return err
   108  }
   109  
   110  func (h *harness) DeleteVariable(ctx context.Context, name string) error {
   111  	return h.client.DeleteSecret(ctx, &secretmanagerpb.DeleteSecretRequest{Name: secretKey(name)})
   112  }
   113  
   114  func (h *harness) Close() {
   115  	h.closer()
   116  }
   117  
   118  func (h *harness) Mutable() bool { return true }
   119  
   120  func TestConformance(t *testing.T) {
   121  	drivertest.RunConformanceTests(t, newHarness, []drivertest.AsTest{verifyAs{}})
   122  }
   123  
   124  type verifyAs struct{}
   125  
   126  func (verifyAs) Name() string {
   127  	return "verify As"
   128  }
   129  
   130  func (verifyAs) SnapshotCheck(s *runtimevar.Snapshot) error {
   131  	var v *secretmanagerpb.AccessSecretVersionResponse
   132  	if !s.As(&v) {
   133  		return errors.New("Snapshot.As failed")
   134  	}
   135  	return nil
   136  }
   137  
   138  func (verifyAs) ErrorCheck(v *runtimevar.Variable, err error) error {
   139  	var s *status.Status
   140  	if !v.ErrorAs(err, &s) {
   141  		return errors.New("runtimevar.ErrorAs failed")
   142  	}
   143  	return nil
   144  }
   145  
   146  // Secretmanager-specific tests.
   147  
   148  func TestEquivalentError(t *testing.T) {
   149  	tests := []struct {
   150  		Err1, Err2 error
   151  		Want       bool
   152  	}{
   153  		{Err1: errors.New("not grpc"), Err2: errors.New("not grpc"), Want: true},
   154  		{Err1: errors.New("not grpc"), Err2: errors.New("not grpc but different")},
   155  		{Err1: errors.New("not grpc"), Err2: status.Errorf(codes.Internal, "fail")},
   156  		{Err1: status.Errorf(codes.Internal, "fail"), Err2: status.Errorf(codes.InvalidArgument, "fail")},
   157  		{Err1: status.Errorf(codes.Internal, "fail"), Err2: status.Errorf(codes.Internal, "fail"), Want: true},
   158  	}
   159  
   160  	for _, test := range tests {
   161  		got := equivalentError(test.Err1, test.Err2)
   162  		if got != test.Want {
   163  			t.Errorf("%v vs %v: got %v want %v", test.Err1, test.Err2, got, test.Want)
   164  		}
   165  	}
   166  }
   167  
   168  func TestNoConnectionError(t *testing.T) {
   169  	ctx := context.Background()
   170  	creds, err := setup.FakeGCPCredentials(ctx)
   171  	if err != nil {
   172  		t.Fatal(err)
   173  	}
   174  
   175  	// Connect to the Secret Manager service.
   176  	client, cleanup, err := Dial(ctx, creds.TokenSource)
   177  	if err != nil {
   178  		t.Fatal(err)
   179  	}
   180  	defer cleanup()
   181  
   182  	key := SecretKey("gcp-project-id", "secret-name")
   183  	v, err := OpenVariable(client, key, nil, nil)
   184  	if err != nil {
   185  		t.Fatal(err)
   186  	}
   187  	defer func() {
   188  		if err := v.Close(); err != nil {
   189  			t.Error(err)
   190  		}
   191  	}()
   192  
   193  	_, err = v.Watch(ctx)
   194  	if err == nil {
   195  		t.Error("got nil want error")
   196  	}
   197  }
   198  
   199  func TestOpenVariable(t *testing.T) {
   200  	cleanup := setup.FakeGCPDefaultCredentials(t)
   201  	defer cleanup()
   202  
   203  	tests := []struct {
   204  		URL     string
   205  		WantErr bool
   206  	}{
   207  		// OK.
   208  		{"gcpsecretmanager://projects/myproject/secrets/mysecret", false},
   209  		// OK, hierarchical key name.
   210  		{"gcpsecretmanager://projects/myproject/secrets/mysecret2", false},
   211  		// OK, setting decoder.
   212  		{"gcpsecretmanager://projects/myproject/secrets/mysecret?decoder=string", false},
   213  		// Missing projects prefix.
   214  		{"gcpsecretmanager://project/myproject/secrets/mysecret", true},
   215  		// Missing project.
   216  		{"gcpsecretmanager://projects//secrets/mysecret", true},
   217  		// Missing configs.
   218  		{"gcpsecretmanager://projects/myproject/mysecret", true},
   219  		// Missing secretID with trailing slash.
   220  		{"gcpsecretmanager://projects/myproject/secrets/", true},
   221  		// Missing secretID.
   222  		{"gcpsecretmanager://projects/myproject/secrets", true},
   223  		// Invalid decoder.
   224  		{"gcpsecretmanager://projects/myproject/secrets/mysecret?decoder=notadecoder", true},
   225  		// Invalid param.
   226  		{"gcpsecretmanager://projects/myproject/secrets/mysecret?param=value", true},
   227  	}
   228  
   229  	ctx := context.Background()
   230  	for _, test := range tests {
   231  		if err := openVariable(ctx, test.URL); (err != nil) != test.WantErr {
   232  			t.Errorf("%s: got error %v, want error %v", test.URL, err, test.WantErr)
   233  		}
   234  	}
   235  }
   236  
   237  func openVariable(ctx context.Context, URL string) (err error) {
   238  	var v *runtimevar.Variable
   239  	v, err = runtimevar.OpenVariable(ctx, URL)
   240  	defer func() {
   241  		if v == nil {
   242  			return
   243  		}
   244  
   245  		if closeErr := v.Close(); closeErr != nil {
   246  			if grpcErr, ok := closeErr.(*gcerr.Error); ok && grpcErr.Code != gcerr.Canceled {
   247  				err = fmt.Errorf("close failed: %v. prev error: %v", closeErr, err)
   248  			}
   249  		}
   250  	}()
   251  
   252  	return err
   253  }