diff --git a/src/bin/install b/src/bin/install index e05aa288d..9f6c47aeb 100755 --- a/src/bin/install +++ b/src/bin/install @@ -286,9 +286,11 @@ _install_completion_script() { esac } -_install_completion_script "$SHELL" || true -CURRENT_SHELL=${0#-} -[ "$CURRENT_SHELL" != "$SHELL" ] && _install_completion_script "$CURRENT_SHELL" || true +if [ "$CI" != "1" ]; then + _install_completion_script "$SHELL" || true + CURRENT_SHELL=${0#-} + [ "$CURRENT_SHELL" != "$SHELL" ] && _install_completion_script "$CURRENT_SHELL" || true +fi # Cleanup: Remove the temporary directory echo "Cleaning up..." diff --git a/src/pkg/cli/client/byoc/aws/byoc_test.go b/src/pkg/cli/client/byoc/aws/byoc_test.go index cb97f63b1..11a3c7e6e 100644 --- a/src/pkg/cli/client/byoc/aws/byoc_test.go +++ b/src/pkg/cli/client/byoc/aws/byoc_test.go @@ -94,18 +94,6 @@ func TestDomainMultipleProjectSupport(t *testing.T) { } } -type FakeLoader struct { - ProjectName string -} - -func (f FakeLoader) LoadProject(ctx context.Context) (*composeTypes.Project, error) { - return &composeTypes.Project{Name: f.ProjectName}, nil -} - -func (f FakeLoader) LoadProjectName(ctx context.Context) (string, bool, error) { - return f.ProjectName, false, nil -} - //go:embed testdata/*.json var testDir embed.FS diff --git a/src/pkg/cli/client/byoc/gcp/byoc.go b/src/pkg/cli/client/byoc/gcp/byoc.go index a894b279f..47445ff96 100644 --- a/src/pkg/cli/client/byoc/gcp/byoc.go +++ b/src/pkg/cli/client/byoc/gcp/byoc.go @@ -282,7 +282,6 @@ func (b *ByocGcp) SetUpCD(ctx context.Context, force bool) error { } } - // 5. Setup Cloud Run Job term.Debugf("Using CD image: %q", b.CDImage) b.SetupDone = true @@ -847,10 +846,12 @@ func (b *ByocGcp) GetProjectUpdate(ctx context.Context, projectName string) (*de pbBytes, err := b.driver.GetBucketObjectWithServiceAccount(ctx, bucketName, path, uploadSA) if err != nil { term.Debugf("Failed to get project bucket object from bucket %q at path %q with service account %q: %v", bucketName, path, uploadSA, err) - if errors.Is(err, gcp.ErrObjectNotExist) { - return nil, client.ErrNotExist // no services yet + // Handle the case where the object does not exist, or where we do not have permission to view the object, ie. + // "Permission 'iam.serviceAccounts.getAccessToken' denied on resource (or it may not exist)." #2051 + if errors.Is(err, gcp.ErrObjectNotExist) || strings.Contains(err.Error(), "(or it may not exist)") { + return nil, client.ErrNotExist // first deployment, no services yet } - return nil, err + return nil, annotateGcpError(err) } var projUpdate defangv1.ProjectUpdate diff --git a/src/pkg/cli/client/byoc/gcp/byoc_test.go b/src/pkg/cli/client/byoc/gcp/byoc_test.go index 3fe6c7348..7b5132416 100644 --- a/src/pkg/cli/client/byoc/gcp/byoc_test.go +++ b/src/pkg/cli/client/byoc/gcp/byoc_test.go @@ -17,6 +17,7 @@ import ( defangv1 "github.com/DefangLabs/defang/src/protos/io/defang/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/api/googleapi" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -368,4 +369,19 @@ func TestGetServices(t *testing.T) { require.NoError(t, err) assert.Empty(t, res.Services) }) + + t.Run("first deployment 403 = no services", func(t *testing.T) { + b.driver = &mockGcpDriver{ + bucketName: "bucket-a", + getBucketObjectWithServiceAccountError: &googleapi.Error{ + Code: 403, + Message: "Permission 'iam.serviceAccounts.getAccessToken' denied on resource (or it may not exist).", + }, + } + res, err := b.GetServices(t.Context(), &defangv1.GetServicesRequest{ + Project: "project1", + }) + require.NoError(t, err) + assert.Empty(t, res.Services) + }) } diff --git a/src/pkg/cli/client/byoc/gcp/errors.go b/src/pkg/cli/client/byoc/gcp/errors.go index ca3a9d6bc..2a8eba153 100644 --- a/src/pkg/cli/client/byoc/gcp/errors.go +++ b/src/pkg/cli/client/byoc/gcp/errors.go @@ -3,7 +3,6 @@ package gcp import ( "errors" "fmt" - "strings" "github.com/DefangLabs/defang/src/pkg/http" "google.golang.org/api/googleapi" @@ -41,20 +40,3 @@ func annotateGcpError(err error) error { } return err } - -// Used to get nested values from the detail of a googleapi.Error -func GetGoogleAPIErrorDetail(detail any, path string) string { - if path == "" { - value, ok := detail.(string) - if ok { - return value - } - return "" - } - dm, ok := detail.(map[string]any) - if !ok { - return "" - } - key, rest, _ := strings.Cut(path, ".") - return GetGoogleAPIErrorDetail(dm[key], rest) -} diff --git a/src/pkg/cli/client/byoc/gcp/errors_test.go b/src/pkg/cli/client/byoc/gcp/errors_test.go index 3a78488e1..939743188 100644 --- a/src/pkg/cli/client/byoc/gcp/errors_test.go +++ b/src/pkg/cli/client/byoc/gcp/errors_test.go @@ -65,7 +65,7 @@ func Test_BadGCPprojectnameErrorWrap(t *testing.T) { wrappedErr := annotateGcpError(gcpErr) assert.Equal(t, `double check the GCP project ID and make sure your Application Default Credentials have permission to access the project: `+gcpErr.Message, wrappedErr.Error()) - // Test the error is wrapper correctly + // Test the error is wrapped correctly var unwrappedErr *googleapi.Error assert.True(t, errors.As(wrappedErr, &unwrappedErr)) assert.Equal(t, gcpErr, unwrappedErr)