Skip to content

Commit

Permalink
improve Project API consistency
Browse files Browse the repository at this point in the history
Signed-off-by: Guillaume Lours <[email protected]>
  • Loading branch information
glours authored and ndeloof committed Jan 10, 2024
1 parent ef965fe commit 3d88bd1
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 85 deletions.
4 changes: 2 additions & 2 deletions loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,12 +451,12 @@ func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options,
}
}

if project, err = project.ApplyProfiles(opts.Profiles); err != nil {
if project, err = project.WithProfiles(opts.Profiles); err != nil {
return nil, err
}

if !opts.SkipResolveEnvironment {
project, err = project.ResolveServicesEnvironment(opts.discardEnvFiles)
project, err = project.WithServicesEnvironmentResolved(opts.discardEnvFiles)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion loader/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2131,7 +2131,7 @@ func TestLoadServiceWithEnvFile(t *testing.T) {
},
},
}
p, err = p.ResolveServicesEnvironment(false)
p, err = p.WithServicesEnvironmentResolved(false)
assert.NilError(t, err)
service, err := p.GetService("test")
assert.NilError(t, err)
Expand Down
4 changes: 2 additions & 2 deletions types/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ func Test_WithServices(t *testing.T) {
},
}
order := []string{}
fn := func(name string, _ ServiceConfig) error {
fn := func(name string, _ *ServiceConfig) error {
order = append(order, name)
return nil
}

p, err := p.WithServices(nil, fn)
err := p.ForEachService(nil, fn)
assert.NilError(t, err)
assert.DeepEqual(t, order, []string{"service_2", "service_3", "service_1"})
}
Expand Down
115 changes: 50 additions & 65 deletions types/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,21 +186,15 @@ func (p *Project) AllServices() Services {
return all
}

type ServiceFunc func(name string, service ServiceConfig) error
type ServiceFunc func(name string, service *ServiceConfig) error

// WithServices runs ServiceFunc on each service and dependencies according to DependencyPolicy
// It returns a new Project instance with the changes and keep the original Project unchanged
func (p *Project) WithServices(names []string, fn ServiceFunc, options ...DependencyOption) (*Project, error) {
newProject, err := p.deepCopy()
if err != nil {
return nil, err
}
// ForEachService runs ServiceFunc on each service and dependencies according to DependencyPolicy
func (p *Project) ForEachService(names []string, fn ServiceFunc, options ...DependencyOption) error {
if len(options) == 0 {
// backward compatibility
options = []DependencyOption{IncludeDependencies}
}
err = newProject.withServices(names, fn, map[string]bool{}, options, map[string]ServiceDependency{})
return newProject, err
return p.withServices(names, fn, map[string]bool{}, options, map[string]ServiceDependency{})
}

type withServicesOptions struct {
Expand Down Expand Up @@ -249,7 +243,7 @@ func (p *Project) withServices(names []string, fn ServiceFunc, seen map[string]b
return err
}
}
if err := fn(name, service); err != nil {
if err := fn(name, service.deepCopy()); err != nil {
return err
}
}
Expand Down Expand Up @@ -299,13 +293,10 @@ func (s ServiceConfig) HasProfile(profiles []string) bool {
return false
}

// ApplyProfiles disables service which don't match selected profiles
// WithProfiles disables services which don't match selected profiles
// It returns a new Project instance with the changes and keep the original Project unchanged
func (p *Project) ApplyProfiles(profiles []string) (*Project, error) {
newProject, err := p.deepCopy()
if err != nil {
return nil, err
}
func (p *Project) WithProfiles(profiles []string) (*Project, error) {
newProject := p.deepCopy()
for _, p := range profiles {
if p == "*" {
return newProject, nil
Expand All @@ -326,13 +317,10 @@ func (p *Project) ApplyProfiles(profiles []string) (*Project, error) {
return newProject, nil
}

// EnableServices ensures services are enabled and activate profiles accordingly
// WithServicesEnabled ensures services are enabled and activate profiles accordingly
// It returns a new Project instance with the changes and keep the original Project unchanged
func (p *Project) EnableServices(names ...string) (*Project, error) {
newProject, err := p.deepCopy()
if err != nil {
return nil, err
}
func (p *Project) WithServicesEnabled(names ...string) (*Project, error) {
newProject := p.deepCopy()
if len(names) == 0 {
return newProject, nil
}
Expand All @@ -346,18 +334,18 @@ func (p *Project) EnableServices(names ...string) (*Project, error) {
service := p.DisabledServices[name]
profiles = append(profiles, service.Profiles...)
}
newProject, err = newProject.ApplyProfiles(profiles)
newProject, err := newProject.WithProfiles(profiles)
if err != nil {
return newProject, err
}

return newProject.ResolveServicesEnvironment(true)
return newProject.WithServicesEnvironmentResolved(true)
}

// WithoutUnnecessaryResources drops networks/volumes/secrets/configs that are not referenced by active services
// It returns a new Project instance with the changes and keep the original Project unchanged
func (p *Project) WithoutUnnecessaryResources() (*Project, error) {
newProject, err := p.deepCopy()
func (p *Project) WithoutUnnecessaryResources() *Project {
newProject := p.deepCopy()
requiredNetworks := map[string]struct{}{}
requiredVolumes := map[string]struct{}{}
requiredSecrets := map[string]struct{}{}
Expand Down Expand Up @@ -416,7 +404,7 @@ func (p *Project) WithoutUnnecessaryResources() (*Project, error) {
}
}
newProject.Configs = configs
return newProject, err
return newProject
}

type DependencyOption func(options *withServicesOptions)
Expand All @@ -433,16 +421,17 @@ func IgnoreDependencies(options *withServicesOptions) {
options.dependencyPolicy = ignoreDependencies
}

// ForServices restricts the project model to selected services and dependencies
// WithSelectedServices restricts the project model to selected services and dependencies
// It returns a new Project instance with the changes and keep the original Project unchanged
func (p *Project) ForServices(names []string, options ...DependencyOption) (*Project, error) {
func (p *Project) WithSelectedServices(names []string, options ...DependencyOption) (*Project, error) {
newProject := p.deepCopy()
if len(names) == 0 {
// All services
return p.deepCopy()
return newProject, nil
}

set := utils.NewSet[string]()
newProject, err := p.WithServices(names, func(name string, service ServiceConfig) error {
err := p.ForEachService(names, func(name string, service *ServiceConfig) error {
set.Add(name)
return nil
}, options...)
Expand All @@ -464,44 +453,43 @@ func (p *Project) ForServices(names []string, options ...DependencyOption) (*Pro
s.DependsOn = dependencies
enabled[name] = s
} else {
if newProject, err = newProject.DisableService(s); err != nil {
return nil, err
}
newProject = newProject.WithServicesDisabled(name)
}
}
newProject.Services = enabled
return newProject, nil
}

// DisableService removes from the project model the given service and its references in all dependencies
// WithServicesDisabled removes from the project model the given services and their references in all dependencies
// It returns a new Project instance with the changes and keep the original Project unchanged
func (p *Project) DisableService(service ServiceConfig) (*Project, error) {
newProject, err := p.deepCopy()
if err != nil {
return nil, err
}
// We should remove all dependencies which reference the disabled service
for i, s := range newProject.Services {
if _, ok := s.DependsOn[service.Name]; ok {
delete(s.DependsOn, service.Name)
newProject.Services[i] = s
}
func (p *Project) WithServicesDisabled(names ...string) *Project {
newProject := p.deepCopy()
if len(names) == 0 {
return newProject
}
delete(p.Services, service.Name)
if newProject.DisabledServices == nil {
newProject.DisabledServices = Services{}
}
newProject.DisabledServices[service.Name] = service
return newProject, err
for _, name := range names {
// We should remove all dependencies which reference the disabled service
for i, s := range newProject.Services {
if _, ok := s.DependsOn[name]; ok {
delete(s.DependsOn, name)
newProject.Services[i] = s
}
}
if service, ok := newProject.Services[name]; ok {
newProject.DisabledServices[name] = service
delete(newProject.Services, name)
}
}
return newProject
}

// ResolveImages updates services images to include digest computed by a resolver function
// WithImagesResolved updates services images to include digest computed by a resolver function
// It returns a new Project instance with the changes and keep the original Project unchanged
func (p *Project) ResolveImages(resolver func(named reference.Named) (godigest.Digest, error)) (*Project, error) {
newProject, err := p.deepCopy()
if err != nil {
return nil, err
}
func (p *Project) WithImagesResolved(resolver func(named reference.Named) (godigest.Digest, error)) (*Project, error) {
newProject := p.deepCopy()
eg := errgroup.Group{}
for i, s := range newProject.Services {
idx := i
Expand Down Expand Up @@ -574,13 +562,10 @@ func (p *Project) MarshalJSON() ([]byte, error) {
return json.Marshal(m)
}

// ResolveServicesEnvironment parses env_files set for services to resolve the actual environment map for services
// WithServicesEnvironmentResolved parses env_files set for services to resolve the actual environment map for services
// It returns a new Project instance with the changes and keep the original Project unchanged
func (p Project) ResolveServicesEnvironment(discardEnvFiles bool) (*Project, error) {
newProject, err := p.deepCopy()
if err != nil {
return nil, err
}
func (p Project) WithServicesEnvironmentResolved(discardEnvFiles bool) (*Project, error) {
newProject := p.deepCopy()
for i, service := range newProject.Services {
service.Environment = service.Environment.Resolve(newProject.Environment.Resolve)

Expand Down Expand Up @@ -623,10 +608,10 @@ func (p Project) ResolveServicesEnvironment(discardEnvFiles bool) (*Project, err
return newProject, nil
}

func (p *Project) deepCopy() (*Project, error) {
func (p *Project) deepCopy() *Project {
instance, err := copystructure.Copy(p)
if err != nil {
return nil, err
panic(err)
}
return instance.(*Project), nil
return instance.(*Project)
}
29 changes: 14 additions & 15 deletions types/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ import (

func Test_ApplyProfiles(t *testing.T) {
p := makeProject()
p, err := p.ApplyProfiles([]string{"foo"})
p, err := p.WithProfiles([]string{"foo"})
assert.NilError(t, err)
assert.DeepEqual(t, p.ServiceNames(), []string{"service_1", "service_2", "service_6"})
assert.DeepEqual(t, p.DisabledServiceNames(), []string{"service_3", "service_4", "service_5"})

p, err = p.EnableServices("service_4")
p, err = p.WithServicesEnabled("service_4")
assert.NilError(t, err)

assert.DeepEqual(t, p.ServiceNames(), []string{"service_1", "service_2", "service_4", "service_5", "service_6"})
Expand All @@ -47,8 +47,7 @@ func Test_WithoutUnnecessaryResources(t *testing.T) {
p.Volumes["unused"] = VolumeConfig{}
p.Secrets["unused"] = SecretConfig{}
p.Configs["unused"] = ConfigObjConfig{}
p, err := p.WithoutUnnecessaryResources()
assert.NilError(t, err)
p = p.WithoutUnnecessaryResources()
if _, ok := p.Networks["unused"]; ok {
t.Fail()
}
Expand All @@ -65,7 +64,7 @@ func Test_WithoutUnnecessaryResources(t *testing.T) {

func Test_NoProfiles(t *testing.T) {
p := makeProject()
p, err := p.ApplyProfiles(nil)
p, err := p.WithProfiles(nil)
assert.NilError(t, err)
assert.Equal(t, len(p.Services), 2)
assert.Equal(t, len(p.DisabledServices), 4)
Expand All @@ -84,21 +83,21 @@ func Test_ServiceProfiles(t *testing.T) {

func Test_ForServices(t *testing.T) {
p := makeProject()
p, err := p.ForServices([]string{"service_2"})
p, err := p.WithSelectedServices([]string{"service_2"})
assert.NilError(t, err)

assert.DeepEqual(t, p.DisabledServiceNames(), []string{"service_3", "service_4", "service_5", "service_6"})

// Should not load the dependency service_1 when explicitly loading service_6
p = makeProject()
p, err = p.ForServices([]string{"service_6"})
p, err = p.WithSelectedServices([]string{"service_6"})
assert.NilError(t, err)
assert.DeepEqual(t, p.DisabledServiceNames(), []string{"service_1", "service_2", "service_3", "service_4", "service_5"})
}

func Test_ForServicesIgnoreDependencies(t *testing.T) {
p := makeProject()
p, err := p.ForServices([]string{"service_2"}, IgnoreDependencies)
p, err := p.WithSelectedServices([]string{"service_2"}, IgnoreDependencies)
assert.NilError(t, err)

assert.Equal(t, len(p.DisabledServices), 5)
Expand All @@ -107,7 +106,7 @@ func Test_ForServicesIgnoreDependencies(t *testing.T) {
assert.Equal(t, len(service.DependsOn), 0)

p = makeProject()
p, err = p.ForServices([]string{"service_2", "service_3"}, IgnoreDependencies)
p, err = p.WithSelectedServices([]string{"service_2", "service_3"}, IgnoreDependencies)
assert.NilError(t, err)

assert.Equal(t, len(p.DisabledServices), 4)
Expand All @@ -123,7 +122,7 @@ func Test_ForServicesCycle(t *testing.T) {
service := p.Services["service_1"]
service.Links = []string{"service_2"}
p.Services["service_1"] = service
p, err := p.ForServices([]string{"service_2"})
p, err := p.WithSelectedServices([]string{"service_2"})
assert.NilError(t, err)
}

Expand Down Expand Up @@ -200,7 +199,7 @@ func Test_ResolveImages(t *testing.T) {
service := p.Services["service_1"]
service.Image = test.image
p.Services["service_1"] = service
p, err := p.ResolveImages(resolver)
p, err := p.WithImagesResolved(resolver)
assert.NilError(t, err)
assert.Equal(t, p.Services["service_1"].Image, test.resolved)
}
Expand All @@ -209,15 +208,15 @@ func Test_ResolveImages(t *testing.T) {
func TestWithServices(t *testing.T) {
p := makeProject()
var seen []string
p, err := p.WithServices([]string{"service_3"}, func(name string, _ ServiceConfig) error {
err := p.ForEachService([]string{"service_3"}, func(name string, _ *ServiceConfig) error {
seen = append(seen, name)
return nil
}, IncludeDependencies)
assert.NilError(t, err)
assert.DeepEqual(t, seen, []string{"service_1", "service_2", "service_3"})

seen = []string{}
p, err = p.WithServices([]string{"service_1"}, func(name string, _ ServiceConfig) error {
err = p.ForEachService([]string{"service_1"}, func(name string, _ *ServiceConfig) error {
seen = append(seen, name)
return nil
}, IncludeDependents)
Expand All @@ -226,15 +225,15 @@ func TestWithServices(t *testing.T) {
assert.Check(t, utils.ArrayContains(seen, []string{"service_3", "service_4", "service_2", "service_1"}))

seen = []string{}
p, err = p.WithServices([]string{"service_1"}, func(name string, _ ServiceConfig) error {
err = p.ForEachService([]string{"service_1"}, func(name string, _ *ServiceConfig) error {
seen = append(seen, name)
return nil
}, IgnoreDependencies)
assert.NilError(t, err)
assert.DeepEqual(t, seen, []string{"service_1"})

seen = []string{}
p, err = p.WithServices([]string{"service_4"}, func(name string, _ ServiceConfig) error {
err = p.ForEachService([]string{"service_4"}, func(name string, _ *ServiceConfig) error {
seen = append(seen, name)
return nil
}, IncludeDependencies)
Expand Down
Loading

0 comments on commit 3d88bd1

Please sign in to comment.