Skip to content

Commit

Permalink
Add listeners to trace file metadata
Browse files Browse the repository at this point in the history
Signed-off-by: jhrotko <[email protected]>
  • Loading branch information
jhrotko authored and ndeloof committed Feb 14, 2024
1 parent 2b6b594 commit 26a5dd3
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 1 deletion.
20 changes: 19 additions & 1 deletion cli/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ type ProjectOptions struct {
EnvFiles []string

loadOptions []func(*loader.Options)

// Callbacks to retrieve metadata information during parse defined before
// creating the project
Listeners []loader.Listener
}

type ProjectOptionsFn func(*ProjectOptions) error
Expand All @@ -89,6 +93,7 @@ func NewProjectOptions(configs []string, opts ...ProjectOptionsFn) (*ProjectOpti
options := &ProjectOptions{
ConfigPaths: configs,
Environment: map[string]string{},
Listeners: []loader.Listener{},
}
for _, o := range opts {
err := o(options)
Expand Down Expand Up @@ -365,6 +370,11 @@ func WithExtension(name string, typ any) ProjectOptionsFn {
}
}

// Append listener to event
func (o *ProjectOptions) WithListeners(listeners ...loader.Listener) {
o.Listeners = append(o.Listeners, listeners...)
}

// WithoutEnvironmentResolution disable environment resolution
func WithoutEnvironmentResolution(o *ProjectOptions) error {
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
Expand Down Expand Up @@ -437,7 +447,8 @@ func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) {

options.loadOptions = append(options.loadOptions,
withNamePrecedenceLoad(absWorkingDir, options),
withConvertWindowsPaths(options))
withConvertWindowsPaths(options),
withListener(options))

ctx := options.ctx
if ctx == nil {
Expand Down Expand Up @@ -480,6 +491,13 @@ func withConvertWindowsPaths(options *ProjectOptions) func(*loader.Options) {
}
}

// save listeners from ProjectOptions (compose) to loader.Options
func withListener(options *ProjectOptions) func(*loader.Options) {
return func(opts *loader.Options) {
opts.Listeners = options.Listeners
}
}

// getConfigPathsFromOptions retrieves the config files for project based on project options
func getConfigPathsFromOptions(options *ProjectOptions) ([]string, error) {
if len(options.ConfigPaths) != 0 {
Expand Down
3 changes: 3 additions & 0 deletions loader/extends.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
case map[string]any:
ref = v["service"].(string)
file = v["file"]
opts.ProcessEvent("extends", v)
case string:
ref = v
opts.ProcessEvent("extends", map[string]any{"service": ref})
}

var base any
Expand Down Expand Up @@ -121,6 +123,7 @@ func applyServiceExtends(ctx context.Context, name string, services map[string]a
return nil, err
}
delete(merged, "extends")
services[name] = merged
return merged, nil
}

Expand Down
102 changes: 102 additions & 0 deletions loader/extends_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ services:
abs, err := filepath.Abs(".")
assert.NilError(t, err)

extendsCount := 0
p, err := LoadWithContext(context.Background(), types.ConfigDetails{
ConfigFiles: []types.ConfigFile{
{
Expand All @@ -60,11 +61,21 @@ services:
},
},
WorkingDir: abs,
}, func(options *Options) {
options.ResolvePaths = false
options.Listeners = []Listener{
func(event string, metadata map[string]any) {
if event == "extends" {
extendsCount++
}
},
}
})
assert.NilError(t, err)
assert.DeepEqual(t, p.Services["test1"].Hostname, "test1")
assert.Equal(t, p.Services["test2"].Hostname, "test2")
assert.Equal(t, p.Services["test3"].Hostname, "test3")
assert.Equal(t, extendsCount, 4)
}

func TestExtendsPort(t *testing.T) {
Expand Down Expand Up @@ -262,6 +273,7 @@ services:

assert.NilError(t, os.WriteFile(filepath.Join(tmpdir, "compose.yaml"), []byte(rootYAML), 0o600))

extendsCount := 0
actual, err := Load(types.ConfigDetails{
WorkingDir: tmpdir,
ConfigFiles: []types.ConfigFile{{
Expand All @@ -272,6 +284,13 @@ services:
options.SkipNormalization = true
options.SkipConsistencyCheck = true
options.SetProjectName("project", true)
options.Listeners = []Listener{
func(event string, metadata map[string]any) {
if event == "extends" {
extendsCount++
}
},
}
})
assert.NilError(t, err)
assert.Assert(t, is.Len(actual.Services, 2))
Expand All @@ -283,6 +302,8 @@ services:
svcB, err := actual.GetService("out-service")
assert.NilError(t, err)
assert.Equal(t, svcB.Build.Context, tmpdir)

assert.Equal(t, extendsCount, 3)
}

func TestRejectExtendsWithServiceRef(t *testing.T) {
Expand Down Expand Up @@ -358,3 +379,84 @@ services:
})
}
}

func TestLoadExtendsListener(t *testing.T) {
yaml := `
name: listener-extends
services:
foo:
image: busybox
extends: bar
bar:
image: alpine
command: echo
extends: wee
wee:
extends: last
command: echo
last:
image: python`
extendsCount := 0
_, err := Load(buildConfigDetails(yaml, nil), func(options *Options) {
options.SkipConsistencyCheck = true
options.SkipNormalization = true
options.ResolvePaths = true
options.Listeners = []Listener{
func(event string, metadata map[string]any) {
if event == "extends" {
extendsCount++
}
},
}
})

assert.NilError(t, err)
assert.Equal(t, extendsCount, 3)
}

func TestLoadExtendsListenerMultipleFiles(t *testing.T) {
tmpdir := t.TempDir()
subDir := filepath.Join(tmpdir, "sub")
assert.NilError(t, os.Mkdir(subDir, 0o700))
subYAML := `
services:
b:
extends: c
build:
target: fake
c:
command: echo
`
assert.NilError(t, os.WriteFile(filepath.Join(tmpdir, "sub", "compose.yaml"), []byte(subYAML), 0o600))

rootYAML := `
services:
a:
extends:
file: ./sub/compose.yaml
service: b
`
assert.NilError(t, os.WriteFile(filepath.Join(tmpdir, "compose.yaml"), []byte(rootYAML), 0o600))

extendsCount := 0
_, err := Load(types.ConfigDetails{
WorkingDir: tmpdir,
ConfigFiles: []types.ConfigFile{{
Filename: filepath.Join(tmpdir, "compose.yaml"),
}},
Environment: nil,
}, func(options *Options) {
options.SkipNormalization = true
options.SkipConsistencyCheck = true
options.SetProjectName("project", true)
options.Listeners = []Listener{
func(event string, metadata map[string]any) {
if event == "extends" {
extendsCount++
}
},
}
})
assert.NilError(t, err)
assert.Equal(t, extendsCount, 2)
}
12 changes: 12 additions & 0 deletions loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ type Options struct {
ResourceLoaders []ResourceLoader
// KnownExtensions manages x-* attribute we know and the corresponding go structs
KnownExtensions map[string]any
// Metada for telemetry
Listeners []Listener
}

type Listener = func(event string, metadata map[string]any)

// Invoke all listeners for an event
func (o *Options) ProcessEvent(event string, metadata map[string]any) {
for _, l := range o.Listeners {
l(event, metadata)
}
}

// ResourceLoader is a plugable remote resource resolver
Expand Down Expand Up @@ -153,6 +164,7 @@ func (o *Options) clone() *Options {
Profiles: o.Profiles,
ResourceLoaders: o.ResourceLoaders,
KnownExtensions: o.KnownExtensions,
Listeners: o.Listeners,
}
}

Expand Down

0 comments on commit 26a5dd3

Please sign in to comment.