Skip to content

Commit

Permalink
introduce marhsaller options and ability to include secrets content
Browse files Browse the repository at this point in the history
Signed-off-by: Nicolas De Loof <[email protected]>
  • Loading branch information
ndeloof committed Nov 25, 2024
1 parent 58f8cad commit df253f2
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 37 deletions.
20 changes: 12 additions & 8 deletions loader/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -912,7 +912,7 @@ networks:
},
}

assert.DeepEqual(t, expected, config)
assertEqual(t, expected, config)
}

func TestUnsupportedProperties(t *testing.T) {
Expand Down Expand Up @@ -1158,8 +1158,8 @@ func TestFullExample(t *testing.T) {
assert.Check(t, is.DeepEqual(expectedConfig.Services, config.Services))
assert.Check(t, is.DeepEqual(expectedConfig.Networks, config.Networks))
assert.Check(t, is.DeepEqual(expectedConfig.Volumes, config.Volumes))
assert.Check(t, is.DeepEqual(expectedConfig.Secrets, config.Secrets))
assert.Check(t, is.DeepEqual(expectedConfig.Configs, config.Configs))
assert.Check(t, is.DeepEqual(expectedConfig.Secrets, config.Secrets, cmpopts.IgnoreUnexported(types.SecretConfig{})))
assert.Check(t, is.DeepEqual(expectedConfig.Configs, config.Configs, cmpopts.IgnoreUnexported(types.ConfigObjConfig{})))
assert.Check(t, is.DeepEqual(expectedConfig.Extensions, config.Extensions))
}

Expand Down Expand Up @@ -1592,7 +1592,7 @@ secrets:
External: true,
},
}
assert.Check(t, is.DeepEqual(expected, project.Secrets))
assert.Check(t, is.DeepEqual(expected, project.Secrets, cmpopts.IgnoreUnexported(types.SecretConfig{})))
assert.Check(t, is.Contains(buf.String(), "secrets.foo: external.name is deprecated. Please set name and external: true"))
}

Expand Down Expand Up @@ -1940,7 +1940,7 @@ secrets:
"COMPOSE_PROJECT_NAME": "load-template-driver",
},
}
assert.DeepEqual(t, config, expected, cmpopts.EquateEmpty())
assertEqual(t, expected, config)
}

func TestLoadSecretDriver(t *testing.T) {
Expand Down Expand Up @@ -2012,7 +2012,11 @@ secrets:
"COMPOSE_PROJECT_NAME": "load-secret-driver",
},
}
assert.DeepEqual(t, config, expected, cmpopts.EquateEmpty())
assertEqual(t, config, expected)
}

func assertEqual(t *testing.T, config *types.Project, expected *types.Project) {
assert.DeepEqual(t, config, expected, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(types.SecretConfig{}), cmpopts.IgnoreUnexported(types.ConfigObjConfig{}))
}

func TestComposeFileWithVersion(t *testing.T) {
Expand Down Expand Up @@ -3399,12 +3403,12 @@ secrets:
"config": {
Environment: "GA",
Content: "BU",
}})
}}, cmpopts.IgnoreUnexported(types.ConfigObjConfig{}))
assert.DeepEqual(t, config.Secrets, types.Secrets{
"secret": {
Environment: "MEU",
Content: "Shadoks",
}})
}}, cmpopts.IgnoreUnexported(types.SecretConfig{}))
}

func TestLoadDeviceMapping(t *testing.T) {
Expand Down
3 changes: 1 addition & 2 deletions loader/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package loader

import (
"encoding/json"
"os"
"testing"

Expand Down Expand Up @@ -54,7 +53,7 @@ func TestJSONMarshalProject(t *testing.T) {
project := fullExampleProject(workingDir, homeDir)
expected := fullExampleJSON(workingDir, homeDir)

actual, err := json.MarshalIndent(project, "", " ")
actual, err := project.MarshalJSON()
assert.NilError(t, err)
assert.Check(t, is.Equal(expected, string(actual)))

Expand Down
60 changes: 45 additions & 15 deletions types/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,39 +560,69 @@ func (p *Project) WithImagesResolved(resolver func(named reference.Named) (godig
})
}

type marshallOptions struct {
secretsContent bool
}

func WithSecretContent(o *marshallOptions) {
o.secretsContent = true
}

func (opt *marshallOptions) apply(p *Project) *Project {
if opt.secretsContent {
p = p.deepCopy()
for name, config := range p.Secrets {
config.marshallContent = true
p.Secrets[name] = config
}
}
return p
}

func applyMarshallOptions(p *Project, options ...func(*marshallOptions)) *Project {
opts := &marshallOptions{}
for _, option := range options {
option(opts)
}
p = opts.apply(p)
return p
}

// MarshalYAML marshal Project into a yaml tree
func (p *Project) MarshalYAML() ([]byte, error) {
func (p *Project) MarshalYAML(options ...func(*marshallOptions)) ([]byte, error) {
buf := bytes.NewBuffer([]byte{})
encoder := yaml.NewEncoder(buf)
encoder.SetIndent(2)
// encoder.CompactSeqIndent() FIXME https://github.com/go-yaml/yaml/pull/753
err := encoder.Encode(p)
src := applyMarshallOptions(p, options...)
err := encoder.Encode(src)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

// MarshalJSON makes Config implement json.Marshaler
func (p *Project) MarshalJSON() ([]byte, error) {
// MarshalJSON marshal Project into a json document
func (p *Project) MarshalJSON(options ...func(*marshallOptions)) ([]byte, error) {
src := applyMarshallOptions(p, options...)
m := map[string]interface{}{
"name": p.Name,
"services": p.Services,
"name": src.Name,
"services": src.Services,
}

if len(p.Networks) > 0 {
m["networks"] = p.Networks
if len(src.Networks) > 0 {
m["networks"] = src.Networks
}
if len(p.Volumes) > 0 {
m["volumes"] = p.Volumes
if len(src.Volumes) > 0 {
m["volumes"] = src.Volumes
}
if len(p.Secrets) > 0 {
m["secrets"] = p.Secrets
if len(src.Secrets) > 0 {
m["secrets"] = src.Secrets
}
if len(p.Configs) > 0 {
m["configs"] = p.Configs
if len(src.Configs) > 0 {
m["configs"] = src.Configs
}
for k, v := range p.Extensions {
for k, v := range src.Extensions {
m[k] = v
}
return json.MarshalIndent(m, "", " ")
Expand Down
23 changes: 23 additions & 0 deletions types/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
_ "crypto/sha256"
"errors"
"fmt"
"strings"
"testing"

"github.com/compose-spec/compose-go/v2/utils"
Expand Down Expand Up @@ -410,3 +411,25 @@ func TestServicesWithCapabilities(t *testing.T) {
assert.DeepEqual(t, []string{"service_1"}, gpu)
assert.DeepEqual(t, []string{"service_1", "service_2"}, tpu)
}

func TestMarshallOptions(t *testing.T) {
p := &Project{
Secrets: map[string]SecretConfig{
"test": {
Name: "test",
Content: "SECRET",
File: "~/.secret",
},
},
}
yaml, err := p.MarshalYAML(WithSecretContent)
assert.NilError(t, err)
expected := `
services: {}
secrets:
test:
name: test
file: ~/.secret
content: SECRET`
assert.Equal(t, strings.TrimSpace(string(yaml)), strings.TrimSpace(expected))
}
30 changes: 18 additions & 12 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -732,16 +732,18 @@ type CredentialSpecConfig struct {

// FileObjectConfig is a config type for a file used by a service
type FileObjectConfig struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
File string `yaml:"file,omitempty" json:"file,omitempty"`
Environment string `yaml:"environment,omitempty" json:"environment,omitempty"`
Content string `yaml:"content,omitempty" json:"content,omitempty"`
External External `yaml:"external,omitempty" json:"external,omitempty"`
Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"`
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
DriverOpts map[string]string `yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
TemplateDriver string `yaml:"template_driver,omitempty" json:"template_driver,omitempty"`
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
File string `yaml:"file,omitempty" json:"file,omitempty"`
Environment string `yaml:"environment,omitempty" json:"environment,omitempty"`
Content string `yaml:"content,omitempty" json:"content,omitempty"`
// configure marshalling to include Content - excluded by default to prevent sensitive data leaks
marshallContent bool
External External `yaml:"external,omitempty" json:"external,omitempty"`
Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"`
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
DriverOpts map[string]string `yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
TemplateDriver string `yaml:"template_driver,omitempty" json:"template_driver,omitempty"`
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
}

const (
Expand Down Expand Up @@ -775,14 +777,18 @@ type SecretConfig FileObjectConfig
// MarshalYAML makes SecretConfig implement yaml.Marshaller
func (s SecretConfig) MarshalYAML() (interface{}, error) {
// secret content is set while loading model. Never marshall it
s.Content = ""
if !s.marshallContent {
s.Content = ""
}
return FileObjectConfig(s), nil
}

// MarshalJSON makes SecretConfig implement json.Marshaller
func (s SecretConfig) MarshalJSON() ([]byte, error) {
// secret content is set while loading model. Never marshall it
s.Content = ""
if !s.marshallContent {
s.Content = ""
}
return json.Marshal(FileObjectConfig(s))
}

Expand Down

0 comments on commit df253f2

Please sign in to comment.