Skip to content

Commit df253f2

Browse files
committed
introduce marhsaller options and ability to include secrets content
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 58f8cad commit df253f2

File tree

5 files changed

+99
-37
lines changed

5 files changed

+99
-37
lines changed

loader/loader_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,7 @@ networks:
912912
},
913913
}
914914

915-
assert.DeepEqual(t, expected, config)
915+
assertEqual(t, expected, config)
916916
}
917917

918918
func TestUnsupportedProperties(t *testing.T) {
@@ -1158,8 +1158,8 @@ func TestFullExample(t *testing.T) {
11581158
assert.Check(t, is.DeepEqual(expectedConfig.Services, config.Services))
11591159
assert.Check(t, is.DeepEqual(expectedConfig.Networks, config.Networks))
11601160
assert.Check(t, is.DeepEqual(expectedConfig.Volumes, config.Volumes))
1161-
assert.Check(t, is.DeepEqual(expectedConfig.Secrets, config.Secrets))
1162-
assert.Check(t, is.DeepEqual(expectedConfig.Configs, config.Configs))
1161+
assert.Check(t, is.DeepEqual(expectedConfig.Secrets, config.Secrets, cmpopts.IgnoreUnexported(types.SecretConfig{})))
1162+
assert.Check(t, is.DeepEqual(expectedConfig.Configs, config.Configs, cmpopts.IgnoreUnexported(types.ConfigObjConfig{})))
11631163
assert.Check(t, is.DeepEqual(expectedConfig.Extensions, config.Extensions))
11641164
}
11651165

@@ -1592,7 +1592,7 @@ secrets:
15921592
External: true,
15931593
},
15941594
}
1595-
assert.Check(t, is.DeepEqual(expected, project.Secrets))
1595+
assert.Check(t, is.DeepEqual(expected, project.Secrets, cmpopts.IgnoreUnexported(types.SecretConfig{})))
15961596
assert.Check(t, is.Contains(buf.String(), "secrets.foo: external.name is deprecated. Please set name and external: true"))
15971597
}
15981598

@@ -1940,7 +1940,7 @@ secrets:
19401940
"COMPOSE_PROJECT_NAME": "load-template-driver",
19411941
},
19421942
}
1943-
assert.DeepEqual(t, config, expected, cmpopts.EquateEmpty())
1943+
assertEqual(t, expected, config)
19441944
}
19451945

19461946
func TestLoadSecretDriver(t *testing.T) {
@@ -2012,7 +2012,11 @@ secrets:
20122012
"COMPOSE_PROJECT_NAME": "load-secret-driver",
20132013
},
20142014
}
2015-
assert.DeepEqual(t, config, expected, cmpopts.EquateEmpty())
2015+
assertEqual(t, config, expected)
2016+
}
2017+
2018+
func assertEqual(t *testing.T, config *types.Project, expected *types.Project) {
2019+
assert.DeepEqual(t, config, expected, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(types.SecretConfig{}), cmpopts.IgnoreUnexported(types.ConfigObjConfig{}))
20162020
}
20172021

20182022
func TestComposeFileWithVersion(t *testing.T) {
@@ -3399,12 +3403,12 @@ secrets:
33993403
"config": {
34003404
Environment: "GA",
34013405
Content: "BU",
3402-
}})
3406+
}}, cmpopts.IgnoreUnexported(types.ConfigObjConfig{}))
34033407
assert.DeepEqual(t, config.Secrets, types.Secrets{
34043408
"secret": {
34053409
Environment: "MEU",
34063410
Content: "Shadoks",
3407-
}})
3411+
}}, cmpopts.IgnoreUnexported(types.SecretConfig{}))
34083412
}
34093413

34103414
func TestLoadDeviceMapping(t *testing.T) {

loader/types_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package loader
1818

1919
import (
20-
"encoding/json"
2120
"os"
2221
"testing"
2322

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

57-
actual, err := json.MarshalIndent(project, "", " ")
56+
actual, err := project.MarshalJSON()
5857
assert.NilError(t, err)
5958
assert.Check(t, is.Equal(expected, string(actual)))
6059

types/project.go

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -560,39 +560,69 @@ func (p *Project) WithImagesResolved(resolver func(named reference.Named) (godig
560560
})
561561
}
562562

563+
type marshallOptions struct {
564+
secretsContent bool
565+
}
566+
567+
func WithSecretContent(o *marshallOptions) {
568+
o.secretsContent = true
569+
}
570+
571+
func (opt *marshallOptions) apply(p *Project) *Project {
572+
if opt.secretsContent {
573+
p = p.deepCopy()
574+
for name, config := range p.Secrets {
575+
config.marshallContent = true
576+
p.Secrets[name] = config
577+
}
578+
}
579+
return p
580+
}
581+
582+
func applyMarshallOptions(p *Project, options ...func(*marshallOptions)) *Project {
583+
opts := &marshallOptions{}
584+
for _, option := range options {
585+
option(opts)
586+
}
587+
p = opts.apply(p)
588+
return p
589+
}
590+
563591
// MarshalYAML marshal Project into a yaml tree
564-
func (p *Project) MarshalYAML() ([]byte, error) {
592+
func (p *Project) MarshalYAML(options ...func(*marshallOptions)) ([]byte, error) {
565593
buf := bytes.NewBuffer([]byte{})
566594
encoder := yaml.NewEncoder(buf)
567595
encoder.SetIndent(2)
568596
// encoder.CompactSeqIndent() FIXME https://github.com/go-yaml/yaml/pull/753
569-
err := encoder.Encode(p)
597+
src := applyMarshallOptions(p, options...)
598+
err := encoder.Encode(src)
570599
if err != nil {
571600
return nil, err
572601
}
573602
return buf.Bytes(), nil
574603
}
575604

576-
// MarshalJSON makes Config implement json.Marshaler
577-
func (p *Project) MarshalJSON() ([]byte, error) {
605+
// MarshalJSON marshal Project into a json document
606+
func (p *Project) MarshalJSON(options ...func(*marshallOptions)) ([]byte, error) {
607+
src := applyMarshallOptions(p, options...)
578608
m := map[string]interface{}{
579-
"name": p.Name,
580-
"services": p.Services,
609+
"name": src.Name,
610+
"services": src.Services,
581611
}
582612

583-
if len(p.Networks) > 0 {
584-
m["networks"] = p.Networks
613+
if len(src.Networks) > 0 {
614+
m["networks"] = src.Networks
585615
}
586-
if len(p.Volumes) > 0 {
587-
m["volumes"] = p.Volumes
616+
if len(src.Volumes) > 0 {
617+
m["volumes"] = src.Volumes
588618
}
589-
if len(p.Secrets) > 0 {
590-
m["secrets"] = p.Secrets
619+
if len(src.Secrets) > 0 {
620+
m["secrets"] = src.Secrets
591621
}
592-
if len(p.Configs) > 0 {
593-
m["configs"] = p.Configs
622+
if len(src.Configs) > 0 {
623+
m["configs"] = src.Configs
594624
}
595-
for k, v := range p.Extensions {
625+
for k, v := range src.Extensions {
596626
m[k] = v
597627
}
598628
return json.MarshalIndent(m, "", " ")

types/project_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
_ "crypto/sha256"
2121
"errors"
2222
"fmt"
23+
"strings"
2324
"testing"
2425

2526
"github.com/compose-spec/compose-go/v2/utils"
@@ -410,3 +411,25 @@ func TestServicesWithCapabilities(t *testing.T) {
410411
assert.DeepEqual(t, []string{"service_1"}, gpu)
411412
assert.DeepEqual(t, []string{"service_1", "service_2"}, tpu)
412413
}
414+
415+
func TestMarshallOptions(t *testing.T) {
416+
p := &Project{
417+
Secrets: map[string]SecretConfig{
418+
"test": {
419+
Name: "test",
420+
Content: "SECRET",
421+
File: "~/.secret",
422+
},
423+
},
424+
}
425+
yaml, err := p.MarshalYAML(WithSecretContent)
426+
assert.NilError(t, err)
427+
expected := `
428+
services: {}
429+
secrets:
430+
test:
431+
name: test
432+
file: ~/.secret
433+
content: SECRET`
434+
assert.Equal(t, strings.TrimSpace(string(yaml)), strings.TrimSpace(expected))
435+
}

types/types.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -732,16 +732,18 @@ type CredentialSpecConfig struct {
732732

733733
// FileObjectConfig is a config type for a file used by a service
734734
type FileObjectConfig struct {
735-
Name string `yaml:"name,omitempty" json:"name,omitempty"`
736-
File string `yaml:"file,omitempty" json:"file,omitempty"`
737-
Environment string `yaml:"environment,omitempty" json:"environment,omitempty"`
738-
Content string `yaml:"content,omitempty" json:"content,omitempty"`
739-
External External `yaml:"external,omitempty" json:"external,omitempty"`
740-
Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"`
741-
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
742-
DriverOpts map[string]string `yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
743-
TemplateDriver string `yaml:"template_driver,omitempty" json:"template_driver,omitempty"`
744-
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
735+
Name string `yaml:"name,omitempty" json:"name,omitempty"`
736+
File string `yaml:"file,omitempty" json:"file,omitempty"`
737+
Environment string `yaml:"environment,omitempty" json:"environment,omitempty"`
738+
Content string `yaml:"content,omitempty" json:"content,omitempty"`
739+
// configure marshalling to include Content - excluded by default to prevent sensitive data leaks
740+
marshallContent bool
741+
External External `yaml:"external,omitempty" json:"external,omitempty"`
742+
Labels Labels `yaml:"labels,omitempty" json:"labels,omitempty"`
743+
Driver string `yaml:"driver,omitempty" json:"driver,omitempty"`
744+
DriverOpts map[string]string `yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"`
745+
TemplateDriver string `yaml:"template_driver,omitempty" json:"template_driver,omitempty"`
746+
Extensions Extensions `yaml:"#extensions,inline,omitempty" json:"-"`
745747
}
746748

747749
const (
@@ -775,14 +777,18 @@ type SecretConfig FileObjectConfig
775777
// MarshalYAML makes SecretConfig implement yaml.Marshaller
776778
func (s SecretConfig) MarshalYAML() (interface{}, error) {
777779
// secret content is set while loading model. Never marshall it
778-
s.Content = ""
780+
if !s.marshallContent {
781+
s.Content = ""
782+
}
779783
return FileObjectConfig(s), nil
780784
}
781785

782786
// MarshalJSON makes SecretConfig implement json.Marshaller
783787
func (s SecretConfig) MarshalJSON() ([]byte, error) {
784788
// secret content is set while loading model. Never marshall it
785-
s.Content = ""
789+
if !s.marshallContent {
790+
s.Content = ""
791+
}
786792
return json.Marshal(FileObjectConfig(s))
787793
}
788794

0 commit comments

Comments
 (0)