Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds support for single secrets #37

Merged
merged 2 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion blueprint/schema/_embed/schema.cue
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,17 @@ package schema
// Provider contains the provider to use for the secret.
provider?: null | string @go(Provider,*string)

// Maps contains the mappings for the secret.
// Maps contains mappings for Earthly secret names to JSON keys in the secret.
// Mutually exclusive with Name.
// +optional
maps?: {
[string]: string
} @go(Maps,map[string]string)

// Name contains the name of the Earthly secret to use.
// Mutually exclusive with Maps.
// +optional
name?: null | string @go(Name,*string)
}
version: "1.0"
#Tagging: {
Expand Down
8 changes: 7 additions & 1 deletion blueprint/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,15 @@ type Secret struct {
// Provider contains the provider to use for the secret.
Provider *string `json:"provider"`

// Maps contains the mappings for the secret.
// Maps contains mappings for Earthly secret names to JSON keys in the secret.
// Mutually exclusive with Name.
// +optional
Maps map[string]string `json:"maps"`

// Name contains the name of the Earthly secret to use.
// Mutually exclusive with Maps.
// +optional
Name *string `json:"name"`
}

type Tagging struct {
Expand Down
8 changes: 7 additions & 1 deletion blueprint/schema/schema_go_gen.cue
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,15 @@ package schema
// Provider contains the provider to use for the secret.
provider?: null | string @go(Provider,*string)

// Maps contains the mappings for the secret.
// Maps contains mappings for Earthly secret names to JSON keys in the secret.
// Mutually exclusive with Name.
// +optional
maps?: {[string]: string} @go(Maps,map[string]string)

// Name contains the name of the Earthly secret to use.
// Mutually exclusive with Maps.
// +optional
name?: null | string @go(Name,*string)
}

#Tagging: {
Expand Down
59 changes: 35 additions & 24 deletions forge/cli/pkg/earthly/earthly.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ func (e *EarthlyExecutor) buildSecrets() ([]EarthlySecret, error) {
var secrets []EarthlySecret

for _, secret := range e.secrets {
if secret.Name != nil && len(secret.Maps) > 0 {
e.logger.Error("Secret contains both name and maps", "name", *secret.Name, "maps", secret.Maps)
return nil, fmt.Errorf("secret contains both name and maps: %s", *secret.Name)
}

secretClient, err := e.secretsStore.NewClient(e.logger, secretstore.Provider(*secret.Provider))
if err != nil {
e.logger.Error("Unable to create new secret client", "provider", secret.Provider, "error", err)
Expand All @@ -148,33 +153,39 @@ func (e *EarthlyExecutor) buildSecrets() ([]EarthlySecret, error) {
return secrets, fmt.Errorf("unable to get secret %s from provider: %s", *secret.Path, *secret.Provider)
}

var secretValues map[string]interface{}

if err := json.Unmarshal([]byte(s), &secretValues); err != nil {
e.logger.Error("Unable to unmarshal secret value", "provider", secret.Provider, "path", secret.Path, "error", err)
return secrets, fmt.Errorf("unable to unmarshal secret value: %w", err)
}

for sk, eid := range secret.Maps {
if _, ok := secretValues[sk]; !ok {
e.logger.Error("Secret key not found in secret values", "key", sk, "provider", secret.Provider, "path", secret.Path)
return nil, fmt.Errorf("secret key not found in secret values: %s", sk)
}

s := EarthlySecret{
Id: eid,
if len(secret.Maps) == 0 {
secrets = append(secrets, EarthlySecret{
Id: *secret.Name,
Value: s,
})
} else {
var secretValues map[string]interface{}
if err := json.Unmarshal([]byte(s), &secretValues); err != nil {
e.logger.Error("Failed to unmarshal secret values", "provider", secret.Provider, "path", secret.Path, "error", err)
return nil, fmt.Errorf("failed to unmarshal secret values from provider %s: %w", *secret.Provider, err)
}

switch t := secretValues[sk].(type) {
case bool:
s.Value = strconv.FormatBool(t)
case int:
s.Value = strconv.FormatInt(int64(t), 10)
default:
s.Value = t.(string)
for sk, eid := range secret.Maps {
if _, ok := secretValues[sk]; !ok {
e.logger.Error("Secret key not found in secret values", "key", sk, "provider", secret.Provider, "path", secret.Path)
return nil, fmt.Errorf("secret key not found in secret values: %s", sk)
}

s := EarthlySecret{
Id: eid,
}

switch t := secretValues[sk].(type) {
case bool:
s.Value = strconv.FormatBool(t)
case int:
s.Value = strconv.FormatInt(int64(t), 10)
default:
s.Value = t.(string)
}

secrets = append(secrets, s)
}

secrets = append(secrets, s)
}
}

Expand Down
51 changes: 49 additions & 2 deletions forge/cli/pkg/earthly/earthly_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,51 @@ func TestEarthlyExecutor_buildSecrets(t *testing.T) {
expectErr: false,
expectedErr: "",
},
{
name: "no JSON",
provider: &smocks.SecretProviderMock{
GetFunc: func(path string) (string, error) {
return "secret", nil
},
},
secrets: []schema.Secret{
{
Name: utils.StringPtr("name"),
Path: utils.StringPtr("path"),
Provider: utils.StringPtr("mock"),
Maps: map[string]string{},
},
},
expect: []EarthlySecret{
{
Id: "name",
Value: "secret",
},
},
expectErr: false,
expectedErr: "",
},
{
name: "name and maps defined",
provider: &smocks.SecretProviderMock{
GetFunc: func(path string) (string, error) {
return "", nil
},
},
secrets: []schema.Secret{
{
Name: utils.StringPtr("name"),
Path: utils.StringPtr("path"),
Provider: utils.StringPtr("mock"),
Maps: map[string]string{
"key": "id",
},
},
},
expect: nil,
expectErr: true,
expectedErr: "secret contains both name and maps: name",
},
{
name: "key does not exist",
provider: &smocks.SecretProviderMock{
Expand Down Expand Up @@ -260,12 +305,14 @@ func TestEarthlyExecutor_buildSecrets(t *testing.T) {
{
Path: utils.StringPtr("path"),
Provider: utils.StringPtr("mock"),
Maps: map[string]string{},
Maps: map[string]string{
"key1": "id1",
},
},
},
expect: nil,
expectErr: true,
expectedErr: "unable to unmarshal secret value: invalid character 'i' looking for beginning of value",
expectedErr: "failed to unmarshal secret values from provider mock: invalid character 'i' looking for beginning of value",
},
{
name: "secret provider does not exist",
Expand Down
5 changes: 3 additions & 2 deletions forge/cli/pkg/scan/earthfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/input-output-hk/catalyst-forge/tools/pkg/walker"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"golang.org/x/exp/maps"
)

type MockFileSeeker struct {
Expand Down Expand Up @@ -76,7 +75,9 @@ func TestScanEarthfiles(t *testing.T) {
return
}

assert.Equal(t, maps.Keys(got), tt.expectedKeys)
for k := range got {
assert.Contains(t, tt.expectedKeys, k)
}
})
}
}