From e88ac643daf470a589072cec50b4305eea845d06 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Fri, 13 Sep 2024 09:37:05 -0700 Subject: [PATCH] feat: adds global and optional secrets (#39) --- blueprint.cue | 8 ++++++++ blueprint/schema/_embed/schema.cue | 20 ++++++++++++++------ blueprint/schema/schema.go | 20 ++++++++++++++------ blueprint/schema/schema_go_gen.cue | 20 ++++++++++++++------ forge/cli/cmd/cmds/util.go | 4 ++++ forge/cli/pkg/earthly/earthly.go | 26 +++++++++++++++++++------- forge/cli/pkg/earthly/earthly_test.go | 19 +++++++++++++++++++ forge/cli/pkg/earthly/options.go | 2 +- 8 files changed, 93 insertions(+), 26 deletions(-) diff --git a/blueprint.cue b/blueprint.cue index 01be3eb..053be8f 100644 --- a/blueprint.cue +++ b/blueprint.cue @@ -28,6 +28,14 @@ global: { github: registry: "ghcr.io" } + secrets: [ + { + name: "GITHUB_TOKEN" + optional: true + provider: "env" + path: "GITHUB_TOKEN" + }, + ] tagging: { aliases: { forge: "forge/cli" diff --git a/blueprint/schema/_embed/schema.cue b/blueprint/schema/_embed/schema.cue index 9cc37b6..d7a4187 100644 --- a/blueprint/schema/_embed/schema.cue +++ b/blueprint/schema/_embed/schema.cue @@ -52,6 +52,10 @@ package schema // +optional registries?: [...string] @go(Registries,[]string) + // Secrets contains global secrets that will be passed to all targets. + // +optional + secrets?: [...#Secret] @go(Secrets,[]Secret) + // Tagging contains the tagging configuration for the CI system. // +optional tagging?: #Tagging @go(Tagging) @@ -134,12 +138,6 @@ package schema // Secret contains the secret provider and a list of mappings #Secret: { - // Path contains the path to the secret. - path?: null | string @go(Path,*string) - - // Provider contains the provider to use for the secret. - provider?: null | string @go(Provider,*string) - // Maps contains mappings for Earthly secret names to JSON keys in the secret. // Mutually exclusive with Name. // +optional @@ -151,6 +149,16 @@ package schema // Mutually exclusive with Maps. // +optional name?: null | string @go(Name,*string) + + // Optional determines if the secret is optional. + // +optional + optional?: null | bool @go(Optional,*bool) + + // Path contains the path to the secret. + path?: null | string @go(Path,*string) + + // Provider contains the provider to use for the secret. + provider?: null | string @go(Provider,*string) } version: "1.0" #Tagging: { diff --git a/blueprint/schema/schema.go b/blueprint/schema/schema.go index df26a42..ad6b1f2 100644 --- a/blueprint/schema/schema.go +++ b/blueprint/schema/schema.go @@ -60,6 +60,10 @@ type GlobalCI struct { // +optional Registries []string `json:"registries"` + // Secrets contains global secrets that will be passed to all targets. + // +optional + Secrets []Secret `json:"secrets"` + // Tagging contains the tagging configuration for the CI system. // +optional Tagging Tagging `json:"tagging"` @@ -141,12 +145,6 @@ type ProviderGithub struct { // Secret contains the secret provider and a list of mappings type Secret struct { - // Path contains the path to the secret. - Path *string `json:"path"` - - // Provider contains the provider to use for the secret. - Provider *string `json:"provider"` - // Maps contains mappings for Earthly secret names to JSON keys in the secret. // Mutually exclusive with Name. // +optional @@ -156,6 +154,16 @@ type Secret struct { // Mutually exclusive with Maps. // +optional Name *string `json:"name"` + + // Optional determines if the secret is optional. + // +optional + Optional *bool `json:"optional"` + + // Path contains the path to the secret. + Path *string `json:"path"` + + // Provider contains the provider to use for the secret. + Provider *string `json:"provider"` } type Tagging struct { diff --git a/blueprint/schema/schema_go_gen.cue b/blueprint/schema/schema_go_gen.cue index aac0207..68cf089 100644 --- a/blueprint/schema/schema_go_gen.cue +++ b/blueprint/schema/schema_go_gen.cue @@ -55,6 +55,10 @@ package schema // +optional registries?: [...string] @go(Registries,[]string) + // Secrets contains global secrets that will be passed to all targets. + // +optional + secrets?: [...#Secret] @go(Secrets,[]Secret) + // Tagging contains the tagging configuration for the CI system. // +optional tagging?: #Tagging @go(Tagging) @@ -136,12 +140,6 @@ package schema // Secret contains the secret provider and a list of mappings #Secret: { - // Path contains the path to the secret. - path?: null | string @go(Path,*string) - - // Provider contains the provider to use for the secret. - provider?: null | string @go(Provider,*string) - // Maps contains mappings for Earthly secret names to JSON keys in the secret. // Mutually exclusive with Name. // +optional @@ -151,6 +149,16 @@ package schema // Mutually exclusive with Maps. // +optional name?: null | string @go(Name,*string) + + // Optional determines if the secret is optional. + // +optional + optional?: null | bool @go(Optional,*bool) + + // Path contains the path to the secret. + path?: null | string @go(Path,*string) + + // Provider contains the provider to use for the secret. + provider?: null | string @go(Provider,*string) } #Tagging: { diff --git a/forge/cli/cmd/cmds/util.go b/forge/cli/cmd/cmds/util.go index 6067447..38b1a87 100644 --- a/forge/cli/cmd/cmds/util.go +++ b/forge/cli/cmd/cmds/util.go @@ -60,6 +60,10 @@ func generateOpts(target string, flags *RunCmd, config *schema.Blueprint) []eart if config.Global.CI.Providers.Earthly.Satellite != nil && !flags.Local { opts = append(opts, earthly.WithSatellite(*config.Global.CI.Providers.Earthly.Satellite)) } + + if len(config.Global.CI.Secrets) > 0 { + opts = append(opts, earthly.WithSecrets(config.Global.CI.Secrets)) + } } if flags != nil { diff --git a/forge/cli/pkg/earthly/earthly.go b/forge/cli/pkg/earthly/earthly.go index 3b80ec8..a381e69 100644 --- a/forge/cli/pkg/earthly/earthly.go +++ b/forge/cli/pkg/earthly/earthly.go @@ -67,14 +67,16 @@ func (e EarthlyExecutor) Run() (map[string]EarthlyExecutionResult, error) { return nil, err } - var secretString []string - for _, secret := range secrets { - e.logger.Info("Adding Earthly secret", "earthly_id", secret.Id, "value", secret.Value) - secretString = append(secretString, fmt.Sprintf("%s=%s", secret.Id, secret.Value)) - } + if len(secrets) > 0 { + var secretString []string + for _, secret := range secrets { + e.logger.Info("Adding Earthly secret", "earthly_id", secret.Id, "value", secret.Value) + secretString = append(secretString, fmt.Sprintf("%s=%s", secret.Id, secret.Value)) + } - if err := os.Setenv("EARTHLY_SECRETS", strings.Join(secretString, ",")); err != nil { - e.logger.Error("Failed to set secret environment varibles", "envvar", "EARTHLY_SECRETS") + if err := os.Setenv("EARTHLY_SECRETS", strings.Join(secretString, ",")); err != nil { + e.logger.Error("Failed to set secret environment varibles", "envvar", "EARTHLY_SECRETS") + } } } @@ -149,11 +151,21 @@ func (e *EarthlyExecutor) buildSecrets() ([]EarthlySecret, error) { s, err := secretClient.Get(*secret.Path) if err != nil { + if secret.Optional != nil && *secret.Optional { + e.logger.Warn("Secret is optional and not found", "provider", *secret.Provider, "path", *secret.Path) + continue + } + e.logger.Error("Unable to get secret", "provider", secret.Provider, "path", secret.Path, "error", err) return secrets, fmt.Errorf("unable to get secret %s from provider: %s", *secret.Path, *secret.Provider) } if len(secret.Maps) == 0 { + if secret.Name == nil { + e.logger.Error("Secret does not contain name or maps", "provider", secret.Provider, "path", secret.Path) + return nil, fmt.Errorf("secret does not contain name or maps: %s", *secret.Path) + } + secrets = append(secrets, EarthlySecret{ Id: *secret.Name, Value: s, diff --git a/forge/cli/pkg/earthly/earthly_test.go b/forge/cli/pkg/earthly/earthly_test.go index f5d48a9..8a88da3 100644 --- a/forge/cli/pkg/earthly/earthly_test.go +++ b/forge/cli/pkg/earthly/earthly_test.go @@ -253,6 +253,25 @@ func TestEarthlyExecutor_buildSecrets(t *testing.T) { expectErr: false, expectedErr: "", }, + { + name: "optional secret", + provider: &smocks.SecretProviderMock{ + GetFunc: func(path string) (string, error) { + return "", fmt.Errorf("not found") + }, + }, + secrets: []schema.Secret{ + { + Name: utils.StringPtr("name"), + Optional: utils.BoolPtr(true), + Path: utils.StringPtr("path"), + Provider: utils.StringPtr("mock"), + Maps: map[string]string{}, + }, + }, + expect: nil, + expectErr: false, + }, { name: "name and maps defined", provider: &smocks.SecretProviderMock{ diff --git a/forge/cli/pkg/earthly/options.go b/forge/cli/pkg/earthly/options.go index f3f8d87..d3436a7 100644 --- a/forge/cli/pkg/earthly/options.go +++ b/forge/cli/pkg/earthly/options.go @@ -60,7 +60,7 @@ func WithSatellite(s string) EarthlyExecutorOption { // be passed to the Earthly target. func WithSecrets(secrets []schema.Secret) EarthlyExecutorOption { return func(e *EarthlyExecutor) { - e.secrets = secrets + e.secrets = append(e.secrets, secrets...) } }