From 3759c8da54922e17e5f5cda7cbb5048c1a1669dd Mon Sep 17 00:00:00 2001 From: Josh Curl Date: Thu, 7 Apr 2016 17:45:49 -0700 Subject: [PATCH] Split project package into project, config, and yaml packages Signed-off-by: Josh Curl --- {project => config}/hash.go | 16 +-- {project => config}/interpolation.go | 2 +- {project => config}/interpolation_test.go | 2 +- config/marshal_config_test.go | 88 +++++++++++++++ {project => config}/merge.go | 17 +-- {project => config}/merge_test.go | 38 ++----- {project => config}/schema.go | 2 +- {project => config}/schema_helpers.go | 2 +- config/types.go | 128 ++++++++++++++++++++++ {project => config}/validation.go | 2 +- {project => config}/validation_test.go | 2 +- docker/container.go | 9 +- docker/convert.go | 11 +- docker/convert_test.go | 11 +- docker/service.go | 12 +- docker/service_factory.go | 7 +- docker/service_test.go | 10 +- lookup/simple_env.go | 4 +- lookup/simple_env_test.go | 9 +- project/context.go | 5 +- project/project.go | 12 +- project/project_test.go | 36 +++--- project/types.go | 124 +-------------------- script/inline_schema.go | 2 +- script/schema_template.go | 2 +- {project => yaml}/types_yaml.go | 13 ++- {project => yaml}/types_yaml_test.go | 87 +-------------- {project => yaml}/yaml_test.go | 2 +- 28 files changed, 329 insertions(+), 326 deletions(-) rename {project => config}/hash.go (92%) rename {project => config}/interpolation.go (99%) rename {project => config}/interpolation_test.go (99%) create mode 100644 config/marshal_config_test.go rename {project => config}/merge.go (92%) rename {project => config}/merge_test.go (78%) rename {project => config}/schema.go (99%) rename {project => config}/schema_helpers.go (99%) create mode 100644 config/types.go rename {project => config}/validation.go (99%) rename {project => config}/validation_test.go (99%) rename {project => yaml}/types_yaml.go (97%) rename {project => yaml}/types_yaml_test.go (73%) rename {project => yaml}/yaml_test.go (98%) diff --git a/project/hash.go b/config/hash.go similarity index 92% rename from project/hash.go rename to config/hash.go index f30182a09..652e5681c 100644 --- a/project/hash.go +++ b/config/hash.go @@ -1,4 +1,4 @@ -package project +package config import ( "crypto/sha1" @@ -7,6 +7,8 @@ import ( "io" "reflect" "sort" + + "github.com/docker/libcompose/yaml" ) // GetServiceHash computes and returns a hash that will identify a service. @@ -45,7 +47,7 @@ func GetServiceHash(name string, config *ServiceConfig) string { io.WriteString(hash, fmt.Sprintf("\n %v: ", serviceKey)) switch s := serviceValue.(type) { - case SliceorMap: + case yaml.SliceorMap: sliceKeys := []string{} for lkey := range s.MapParts() { sliceKeys = append(sliceKeys, lkey) @@ -55,35 +57,35 @@ func GetServiceHash(name string, config *ServiceConfig) string { for _, sliceKey := range sliceKeys { io.WriteString(hash, fmt.Sprintf("%s=%v, ", sliceKey, s.MapParts()[sliceKey])) } - case MaporEqualSlice: + case yaml.MaporEqualSlice: sliceKeys := s.Slice() // do not sort keys as the order matters for _, sliceKey := range sliceKeys { io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey)) } - case MaporColonSlice: + case yaml.MaporColonSlice: sliceKeys := s.Slice() // do not sort keys as the order matters for _, sliceKey := range sliceKeys { io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey)) } - case MaporSpaceSlice: + case yaml.MaporSpaceSlice: sliceKeys := s.Slice() // do not sort keys as the order matters for _, sliceKey := range sliceKeys { io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey)) } - case Command: + case yaml.Command: sliceKeys := s.Slice() // do not sort keys as the order matters for _, sliceKey := range sliceKeys { io.WriteString(hash, fmt.Sprintf("%s, ", sliceKey)) } - case Stringorslice: + case yaml.Stringorslice: sliceKeys := s.Slice() sort.Strings(sliceKeys) diff --git a/project/interpolation.go b/config/interpolation.go similarity index 99% rename from project/interpolation.go rename to config/interpolation.go index e1cf883b2..fc420de9a 100644 --- a/project/interpolation.go +++ b/config/interpolation.go @@ -1,4 +1,4 @@ -package project +package config import ( "bytes" diff --git a/project/interpolation_test.go b/config/interpolation_test.go similarity index 99% rename from project/interpolation_test.go rename to config/interpolation_test.go index 7906840bd..93d7ca5ae 100644 --- a/project/interpolation_test.go +++ b/config/interpolation_test.go @@ -1,4 +1,4 @@ -package project +package config import ( "fmt" diff --git a/config/marshal_config_test.go b/config/marshal_config_test.go new file mode 100644 index 000000000..d59359c87 --- /dev/null +++ b/config/marshal_config_test.go @@ -0,0 +1,88 @@ +package config + +import ( + "testing" + + yaml "github.com/cloudfoundry-incubator/candiedyaml" + yamlTypes "github.com/docker/libcompose/yaml" + "github.com/stretchr/testify/assert" +) + +type TestConfig struct { + SystemContainers map[string]*ServiceConfig +} + +func newTestConfig() TestConfig { + return TestConfig{ + SystemContainers: map[string]*ServiceConfig{ + "udev": { + Image: "udev", + Restart: "always", + Net: "host", + Privileged: true, + DNS: yamlTypes.NewStringorslice("8.8.8.8", "8.8.4.4"), + Environment: yamlTypes.NewMaporEqualSlice([]string{ + "DAEMON=true", + }), + Labels: yamlTypes.NewSliceorMap(map[string]string{ + "io.rancher.os.detach": "true", + "io.rancher.os.scope": "system", + }), + VolumesFrom: []string{ + "system-volumes", + }, + Ulimits: yamlTypes.Ulimits{ + Elements: []yamlTypes.Ulimit{ + yamlTypes.NewUlimit("nproc", 65557, 65557), + }, + }, + }, + "system-volumes": { + Image: "state", + Net: "none", + ReadOnly: true, + Privileged: true, + Labels: yamlTypes.NewSliceorMap(map[string]string{ + "io.rancher.os.createonly": "true", + "io.rancher.os.scope": "system", + }), + Volumes: []string{ + "/dev:/host/dev", + "/var/lib/rancher/conf:/var/lib/rancher/conf", + "/etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt.rancher", + "/lib/modules:/lib/modules", + "/lib/firmware:/lib/firmware", + "/var/run:/var/run", + "/var/log:/var/log", + }, + LogDriver: "json-file", + }, + }, + } +} + +func TestMarshalConfig(t *testing.T) { + config := newTestConfig() + bytes, err := yaml.Marshal(config) + assert.Nil(t, err) + + config2 := TestConfig{} + + err = yaml.Unmarshal(bytes, &config2) + assert.Nil(t, err) + + assert.Equal(t, config, config2) +} + +func TestMarshalServiceConfig(t *testing.T) { + configPtr := newTestConfig().SystemContainers["udev"] + bytes, err := yaml.Marshal(configPtr) + assert.Nil(t, err) + + configPtr2 := &ServiceConfig{} + + err = yaml.Unmarshal(bytes, configPtr2) + assert.Nil(t, err) + + assert.Equal(t, configPtr, configPtr2) +} diff --git a/project/merge.go b/config/merge.go similarity index 92% rename from project/merge.go rename to config/merge.go index 26d888766..749a1076e 100644 --- a/project/merge.go +++ b/config/merge.go @@ -1,4 +1,4 @@ -package project +package config import ( "bufio" @@ -28,13 +28,8 @@ var ( } ) -// RawService is represent a Service in map form unparsed -type RawService map[string]interface{} - -// RawServiceMap is a collection of RawServices -type RawServiceMap map[string]RawService - -func mergeProject(p *Project, file string, bytes []byte) (map[string]*ServiceConfig, error) { +// MergeServices merges a compose file into an existing set of service configs +func MergeServices(existingServices *Configs, environmentLookup EnvironmentLookup, resourceLookup ResourceLookup, file string, bytes []byte) (map[string]*ServiceConfig, error) { configs := make(map[string]*ServiceConfig) datas := make(RawServiceMap) @@ -42,7 +37,7 @@ func mergeProject(p *Project, file string, bytes []byte) (map[string]*ServiceCon return nil, err } - if err := Interpolate(p.context.EnvironmentLookup, &datas); err != nil { + if err := Interpolate(environmentLookup, &datas); err != nil { return nil, err } @@ -51,13 +46,13 @@ func mergeProject(p *Project, file string, bytes []byte) (map[string]*ServiceCon } for name, data := range datas { - data, err := parse(p.context.ResourceLookup, p.context.EnvironmentLookup, file, data, datas) + data, err := parse(resourceLookup, environmentLookup, file, data, datas) if err != nil { logrus.Errorf("Failed to parse service %s: %v", name, err) return nil, err } - if serviceConfig, ok := p.Configs.Get(name); ok { + if serviceConfig, ok := existingServices.Get(name); ok { var rawExistingService RawService if err := utils.Convert(serviceConfig, &rawExistingService); err != nil { return nil, err diff --git a/project/merge_test.go b/config/merge_test.go similarity index 78% rename from project/merge_test.go rename to config/merge_test.go index 78dfec49e..1172c5cb5 100644 --- a/project/merge_test.go +++ b/config/merge_test.go @@ -1,4 +1,4 @@ -package project +package config import "testing" @@ -14,11 +14,7 @@ func (n *NullLookup) ResolvePath(path, inFile string) string { } func TestExtendsInheritImage(t *testing.T) { - p := NewProject(&Context{ - ResourceLookup: &NullLookup{}, - }) - - config, err := mergeProject(p, "", []byte(` + config, err := MergeServices(NewConfigs(), nil, &NullLookup{}, "", []byte(` parent: image: foo child: @@ -47,11 +43,7 @@ child: } func TestExtendsInheritBuild(t *testing.T) { - p := NewProject(&Context{ - ResourceLookup: &NullLookup{}, - }) - - config, err := mergeProject(p, "", []byte(` + config, err := MergeServices(NewConfigs(), nil, &NullLookup{}, "", []byte(` parent: build: . child: @@ -80,11 +72,7 @@ child: } func TestExtendBuildOverImage(t *testing.T) { - p := NewProject(&Context{ - ResourceLookup: &NullLookup{}, - }) - - config, err := mergeProject(p, "", []byte(` + config, err := MergeServices(NewConfigs(), nil, &NullLookup{}, "", []byte(` parent: image: foo child: @@ -114,11 +102,7 @@ child: } func TestExtendImageOverBuild(t *testing.T) { - p := NewProject(&Context{ - ResourceLookup: &NullLookup{}, - }) - - config, err := mergeProject(p, "", []byte(` + config, err := MergeServices(NewConfigs(), nil, &NullLookup{}, "", []byte(` parent: build: . child: @@ -152,11 +136,7 @@ child: } func TestRestartNo(t *testing.T) { - p := NewProject(&Context{ - ResourceLookup: &NullLookup{}, - }) - - config, err := mergeProject(p, "", []byte(` + config, err := MergeServices(NewConfigs(), nil, &NullLookup{}, "", []byte(` test: restart: "no" image: foo @@ -174,11 +154,7 @@ test: } func TestRestartAlways(t *testing.T) { - p := NewProject(&Context{ - ResourceLookup: &NullLookup{}, - }) - - config, err := mergeProject(p, "", []byte(` + config, err := MergeServices(NewConfigs(), nil, &NullLookup{}, "", []byte(` test: restart: always image: foo diff --git a/project/schema.go b/config/schema.go similarity index 99% rename from project/schema.go rename to config/schema.go index edad70bca..88fc6fa26 100644 --- a/project/schema.go +++ b/config/schema.go @@ -1,4 +1,4 @@ -package project +package config var schemaString = `{ "$schema": "http://json-schema.org/draft-04/schema#", diff --git a/project/schema_helpers.go b/config/schema_helpers.go similarity index 99% rename from project/schema_helpers.go rename to config/schema_helpers.go index 102e5e449..84dd0ab35 100644 --- a/project/schema_helpers.go +++ b/config/schema_helpers.go @@ -1,4 +1,4 @@ -package project +package config import ( "encoding/json" diff --git a/config/types.go b/config/types.go new file mode 100644 index 000000000..6d2a57f9e --- /dev/null +++ b/config/types.go @@ -0,0 +1,128 @@ +package config + +import ( + "github.com/docker/libcompose/yaml" + "sync" +) + +// EnvironmentLookup defines methods to provides environment variable loading. +type EnvironmentLookup interface { + Lookup(key, serviceName string, config *ServiceConfig) []string +} + +// ResourceLookup defines methods to provides file loading. +type ResourceLookup interface { + Lookup(file, relativeTo string) ([]byte, string, error) + ResolvePath(path, inFile string) string +} + +// ServiceConfig holds libcompose service configuration +type ServiceConfig struct { + Build string `yaml:"build,omitempty"` + CapAdd []string `yaml:"cap_add,omitempty"` + CapDrop []string `yaml:"cap_drop,omitempty"` + CgroupParent string `yaml:"cgroup_parent,omitempty"` + CPUQuota int64 `yaml:"cpu_quota,omitempty"` + CPUSet string `yaml:"cpuset,omitempty"` + CPUShares int64 `yaml:"cpu_shares,omitempty"` + Command yaml.Command `yaml:"command,flow,omitempty"` + ContainerName string `yaml:"container_name,omitempty"` + Devices []string `yaml:"devices,omitempty"` + DNS yaml.Stringorslice `yaml:"dns,omitempty"` + DNSSearch yaml.Stringorslice `yaml:"dns_search,omitempty"` + Dockerfile string `yaml:"dockerfile,omitempty"` + DomainName string `yaml:"domainname,omitempty"` + Entrypoint yaml.Command `yaml:"entrypoint,flow,omitempty"` + EnvFile yaml.Stringorslice `yaml:"env_file,omitempty"` + Environment yaml.MaporEqualSlice `yaml:"environment,omitempty"` + Hostname string `yaml:"hostname,omitempty"` + Image string `yaml:"image,omitempty"` + Labels yaml.SliceorMap `yaml:"labels,omitempty"` + Links yaml.MaporColonSlice `yaml:"links,omitempty"` + LogDriver string `yaml:"log_driver,omitempty"` + MacAddress string `yaml:"mac_address,omitempty"` + MemLimit int64 `yaml:"mem_limit,omitempty"` + MemSwapLimit int64 `yaml:"memswap_limit,omitempty"` + Name string `yaml:"name,omitempty"` + Net string `yaml:"net,omitempty"` + Pid string `yaml:"pid,omitempty"` + Uts string `yaml:"uts,omitempty"` + Ipc string `yaml:"ipc,omitempty"` + Ports []string `yaml:"ports,omitempty"` + Privileged bool `yaml:"privileged,omitempty"` + Restart string `yaml:"restart,omitempty"` + ReadOnly bool `yaml:"read_only,omitempty"` + StdinOpen bool `yaml:"stdin_open,omitempty"` + SecurityOpt []string `yaml:"security_opt,omitempty"` + Tty bool `yaml:"tty,omitempty"` + User string `yaml:"user,omitempty"` + VolumeDriver string `yaml:"volume_driver,omitempty"` + Volumes []string `yaml:"volumes,omitempty"` + VolumesFrom []string `yaml:"volumes_from,omitempty"` + WorkingDir string `yaml:"working_dir,omitempty"` + Expose []string `yaml:"expose,omitempty"` + ExternalLinks []string `yaml:"external_links,omitempty"` + LogOpt map[string]string `yaml:"log_opt,omitempty"` + ExtraHosts []string `yaml:"extra_hosts,omitempty"` + Ulimits yaml.Ulimits `yaml:"ulimits,omitempty"` +} + +// NewConfigs initializes a new Configs struct +func NewConfigs() *Configs { + return &Configs{ + m: make(map[string]*ServiceConfig), + } +} + +// Configs holds a concurrent safe map of ServiceConfig +type Configs struct { + m map[string]*ServiceConfig + mu sync.RWMutex +} + +// Has checks if the config map has the specified name +func (c *Configs) Has(name string) bool { + c.mu.RLock() + defer c.mu.RUnlock() + _, ok := c.m[name] + return ok +} + +// Get returns the config and the presence of the specified name +func (c *Configs) Get(name string) (*ServiceConfig, bool) { + c.mu.RLock() + defer c.mu.RUnlock() + service, ok := c.m[name] + return service, ok +} + +// Add add the specifed config with the specified name +func (c *Configs) Add(name string, service *ServiceConfig) { + c.mu.Lock() + c.m[name] = service + c.mu.Unlock() +} + +// Len returns the len of the configs +func (c *Configs) Len() int { + c.mu.RLock() + defer c.mu.RUnlock() + return len(c.m) +} + +// Keys returns the names of the config +func (c *Configs) Keys() []string { + keys := []string{} + c.mu.RLock() + defer c.mu.RUnlock() + for name := range c.m { + keys = append(keys, name) + } + return keys +} + +// RawService is represent a Service in map form unparsed +type RawService map[string]interface{} + +// RawServiceMap is a collection of RawServices +type RawServiceMap map[string]RawService diff --git a/project/validation.go b/config/validation.go similarity index 99% rename from project/validation.go rename to config/validation.go index 96d4352e3..177a64951 100644 --- a/project/validation.go +++ b/config/validation.go @@ -1,4 +1,4 @@ -package project +package config import ( "fmt" diff --git a/project/validation_test.go b/config/validation_test.go similarity index 99% rename from project/validation_test.go rename to config/validation_test.go index ca320734e..3105387e8 100644 --- a/project/validation_test.go +++ b/config/validation_test.go @@ -1,4 +1,4 @@ -package project +package config import ( "bytes" diff --git a/docker/container.go b/docker/container.go index 22708ce9f..ffd677401 100644 --- a/docker/container.go +++ b/docker/container.go @@ -23,6 +23,7 @@ import ( "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/container" "github.com/docker/go-connections/nat" + "github.com/docker/libcompose/config" "github.com/docker/libcompose/logger" "github.com/docker/libcompose/project" util "github.com/docker/libcompose/utils" @@ -175,7 +176,7 @@ func (c *Container) Create(imageName string) (*types.Container, error) { // CreateWithOverride create container and override parts of the config to // allow special situations to override the config generated from the compose // file -func (c *Container) CreateWithOverride(imageName string, configOverride *project.ServiceConfig) (*types.Container, error) { +func (c *Container) CreateWithOverride(imageName string, configOverride *config.ServiceConfig) (*types.Container, error) { container, err := c.findExisting() if err != nil { return nil, err @@ -285,7 +286,7 @@ func (c *Container) IsRunning() (bool, error) { // Run creates, start and attach to the container based on the image name, // the specified configuration. // It will always create a new container. -func (c *Container) Run(imageName string, configOverride *project.ServiceConfig) (int, error) { +func (c *Container) Run(imageName string, configOverride *config.ServiceConfig) (int, error) { var ( errCh chan error out, stderr io.Writer @@ -476,7 +477,7 @@ func (c *Container) OutOfSync(imageName string) (bool, error) { } func (c *Container) getHash() string { - return project.GetServiceHash(c.service.Name(), c.service.Config()) + return config.GetServiceHash(c.service.Name(), c.service.Config()) } func volumeBinds(volumes map[string]struct{}, container *types.ContainerJSON) []string { @@ -489,7 +490,7 @@ func volumeBinds(volumes map[string]struct{}, container *types.ContainerJSON) [] return result } -func (c *Container) createContainer(imageName, oldContainer string, configOverride *project.ServiceConfig) (*types.Container, error) { +func (c *Container) createContainer(imageName, oldContainer string, configOverride *config.ServiceConfig) (*types.Container, error) { serviceConfig := c.service.serviceConfig if configOverride != nil { serviceConfig.Command = configOverride.Command diff --git a/docker/convert.go b/docker/convert.go index 77b87f1e7..00aa1da91 100644 --- a/docker/convert.go +++ b/docker/convert.go @@ -10,6 +10,7 @@ import ( "github.com/docker/engine-api/types/strslice" "github.com/docker/go-connections/nat" "github.com/docker/go-units" + "github.com/docker/libcompose/config" "github.com/docker/libcompose/project" "github.com/docker/libcompose/utils" ) @@ -54,7 +55,7 @@ func ConvertToAPI(s *Service) (*ConfigWrapper, error) { return &result, nil } -func volumes(c *project.ServiceConfig, ctx project.Context) map[string]struct{} { +func volumes(c *config.ServiceConfig, ctx project.Context) map[string]struct{} { volumes := make(map[string]struct{}, len(c.Volumes)) for k, v := range c.Volumes { vol := ctx.ResourceLookup.ResolvePath(v, ctx.ComposeFiles[0]) @@ -67,7 +68,7 @@ func volumes(c *project.ServiceConfig, ctx project.Context) map[string]struct{} return volumes } -func restartPolicy(c *project.ServiceConfig) (*container.RestartPolicy, error) { +func restartPolicy(c *config.ServiceConfig) (*container.RestartPolicy, error) { restart, err := opts.ParseRestartPolicy(c.Restart) if err != nil { return nil, err @@ -75,7 +76,7 @@ func restartPolicy(c *project.ServiceConfig) (*container.RestartPolicy, error) { return &container.RestartPolicy{Name: restart.Name, MaximumRetryCount: restart.MaximumRetryCount}, nil } -func ports(c *project.ServiceConfig) (map[nat.Port]struct{}, nat.PortMap, error) { +func ports(c *config.ServiceConfig) (map[nat.Port]struct{}, nat.PortMap, error) { ports, binding, err := nat.ParsePortSpecs(c.Ports) if err != nil { return nil, nil, err @@ -107,7 +108,7 @@ func ports(c *project.ServiceConfig) (map[nat.Port]struct{}, nat.PortMap, error) } // Convert converts a service configuration to an docker API structures (Config and HostConfig) -func Convert(c *project.ServiceConfig, ctx project.Context) (*container.Config, *container.HostConfig, error) { +func Convert(c *config.ServiceConfig, ctx project.Context) (*container.Config, *container.HostConfig, error) { restartPolicy, err := restartPolicy(c) if err != nil { return nil, nil, err @@ -198,7 +199,7 @@ func Convert(c *project.ServiceConfig, ctx project.Context) (*container.Config, return config, hostConfig, nil } -func getVolumesFrom(volumesFrom []string, serviceConfigs *project.Configs, projectName string) ([]string, error) { +func getVolumesFrom(volumesFrom []string, serviceConfigs *config.Configs, projectName string) ([]string, error) { volumes := []string{} for _, volumeFrom := range volumesFrom { if serviceConfigs.Has(volumeFrom) { diff --git a/docker/convert_test.go b/docker/convert_test.go index d8fa29794..d4537940d 100644 --- a/docker/convert_test.go +++ b/docker/convert_test.go @@ -4,8 +4,9 @@ import ( "path/filepath" "testing" + "github.com/docker/libcompose/config" "github.com/docker/libcompose/lookup" - "github.com/docker/libcompose/project" + "github.com/docker/libcompose/yaml" shlex "github.com/flynn/go-shlex" "github.com/stretchr/testify/assert" ) @@ -24,7 +25,7 @@ func TestParseBindsAndVolumes(t *testing.T) { abs, err := filepath.Abs(".") assert.Nil(t, err) - cfg, hostCfg, err := Convert(&project.ServiceConfig{ + cfg, hostCfg, err := Convert(&config.ServiceConfig{ Volumes: []string{"/foo", "/home:/home", "/bar/baz", ".:/home", "/usr/lib:/usr/lib:ro"}, }, ctx.Context) assert.Nil(t, err) @@ -39,9 +40,9 @@ func TestParseLabels(t *testing.T) { bashCmd := "bash" fooLabel := "foo.label" fooLabelValue := "service.config.value" - sc := &project.ServiceConfig{ - Entrypoint: project.NewCommand(bashCmd), - Labels: project.NewSliceorMap(map[string]string{fooLabel: "service.config.value"}), + sc := &config.ServiceConfig{ + Entrypoint: yaml.NewCommand(bashCmd), + Labels: yaml.NewSliceorMap(map[string]string{fooLabel: "service.config.value"}), } cfg, _, err := Convert(sc, ctx.Context) assert.Nil(t, err) diff --git a/docker/service.go b/docker/service.go index 33231d806..3d6e2d9ba 100644 --- a/docker/service.go +++ b/docker/service.go @@ -9,19 +9,21 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/engine-api/client" "github.com/docker/go-connections/nat" + "github.com/docker/libcompose/config" "github.com/docker/libcompose/project" "github.com/docker/libcompose/utils" + "github.com/docker/libcompose/yaml" ) // Service is a project.Service implementations. type Service struct { name string - serviceConfig *project.ServiceConfig + serviceConfig *config.ServiceConfig context *Context } // NewService creates a service -func NewService(name string, serviceConfig *project.ServiceConfig, context *Context) *Service { +func NewService(name string, serviceConfig *config.ServiceConfig, context *Context) *Service { return &Service{ name: name, serviceConfig: serviceConfig, @@ -34,8 +36,8 @@ func (s *Service) Name() string { return s.name } -// Config returns the configuration of the service (project.ServiceConfig). -func (s *Service) Config() *project.ServiceConfig { +// Config returns the configuration of the service (config.ServiceConfig). +func (s *Service) Config() *config.ServiceConfig { return s.serviceConfig } @@ -221,7 +223,7 @@ func (s *Service) Run(commandParts []string) (int, error) { c := NewContainer(client, containerName, s) - return c.Run(imageName, &project.ServiceConfig{Command: project.NewCommand(commandParts...), Tty: true, StdinOpen: true}) + return c.Run(imageName, &config.ServiceConfig{Command: yaml.NewCommand(commandParts...), Tty: true, StdinOpen: true}) } // Info implements Service.Info. It returns an project.InfoSet with the containers diff --git a/docker/service_factory.go b/docker/service_factory.go index d5b3b7272..3b455ee49 100644 --- a/docker/service_factory.go +++ b/docker/service_factory.go @@ -1,6 +1,9 @@ package docker -import "github.com/docker/libcompose/project" +import ( + "github.com/docker/libcompose/config" + "github.com/docker/libcompose/project" +) // ServiceFactory is an implementation of project.ServiceFactory. type ServiceFactory struct { @@ -8,6 +11,6 @@ type ServiceFactory struct { } // Create creates a Service based on the specified project, name and service configuration. -func (s *ServiceFactory) Create(project *project.Project, name string, serviceConfig *project.ServiceConfig) (project.Service, error) { +func (s *ServiceFactory) Create(project *project.Project, name string, serviceConfig *config.ServiceConfig) (project.Service, error) { return NewService(name, serviceConfig, s.context), nil } diff --git a/docker/service_test.go b/docker/service_test.go index 69f6251b7..1db704dfe 100644 --- a/docker/service_test.go +++ b/docker/service_test.go @@ -1,15 +1,15 @@ package docker import ( - "github.com/docker/libcompose/project" + "github.com/docker/libcompose/config" "github.com/stretchr/testify/assert" "testing" ) func TestSpecifiesHostPort(t *testing.T) { servicesWithHostPort := []Service{ - {serviceConfig: &project.ServiceConfig{Ports: []string{"8000:8000"}}}, - {serviceConfig: &project.ServiceConfig{Ports: []string{"127.0.0.1:8000:8000"}}}, + {serviceConfig: &config.ServiceConfig{Ports: []string{"8000:8000"}}}, + {serviceConfig: &config.ServiceConfig{Ports: []string{"127.0.0.1:8000:8000"}}}, } for _, service := range servicesWithHostPort { @@ -17,8 +17,8 @@ func TestSpecifiesHostPort(t *testing.T) { } servicesWithoutHostPort := []Service{ - {serviceConfig: &project.ServiceConfig{Ports: []string{"8000"}}}, - {serviceConfig: &project.ServiceConfig{Ports: []string{"127.0.0.1::8000"}}}, + {serviceConfig: &config.ServiceConfig{Ports: []string{"8000"}}}, + {serviceConfig: &config.ServiceConfig{Ports: []string{"127.0.0.1::8000"}}}, } for _, service := range servicesWithoutHostPort { diff --git a/lookup/simple_env.go b/lookup/simple_env.go index 7b8aacd90..6f8c25bf2 100644 --- a/lookup/simple_env.go +++ b/lookup/simple_env.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/docker/libcompose/project" + "github.com/docker/libcompose/config" ) // OsEnvLookup is a "bare" structure that implements the project.EnvironmentLookup interface @@ -15,7 +15,7 @@ type OsEnvLookup struct { // in the form of 'key=value'. It gets environment values using os.Getenv. // If the os environment variable does not exists, the slice is empty. serviceName and config // are not used at all in this implementation. -func (o *OsEnvLookup) Lookup(key, serviceName string, config *project.ServiceConfig) []string { +func (o *OsEnvLookup) Lookup(key, serviceName string, config *config.ServiceConfig) []string { ret := os.Getenv(key) if ret == "" { return []string{} diff --git a/lookup/simple_env_test.go b/lookup/simple_env_test.go index 4d3e4e1f4..c42624e64 100644 --- a/lookup/simple_env_test.go +++ b/lookup/simple_env_test.go @@ -2,29 +2,26 @@ package lookup import ( "testing" - - "github.com/docker/libcompose/project" ) func TestOsEnvLookup(t *testing.T) { // Putting bare minimun value for serviceName and config as there are // not important on this test. serviceName := "anything" - config := &project.ServiceConfig{} osEnvLookup := &OsEnvLookup{} - envs := osEnvLookup.Lookup("PATH", serviceName, config) + envs := osEnvLookup.Lookup("PATH", serviceName, nil) if len(envs) != 1 { t.Fatalf("Expected envs to contains one element, but was %v", envs) } - envs = osEnvLookup.Lookup("path", serviceName, config) + envs = osEnvLookup.Lookup("path", serviceName, nil) if len(envs) != 0 { t.Fatalf("Expected envs to be empty, but was %v", envs) } - envs = osEnvLookup.Lookup("DOES_NOT_EXIST", serviceName, config) + envs = osEnvLookup.Lookup("DOES_NOT_EXIST", serviceName, nil) if len(envs) != 0 { t.Fatalf("Expected envs to be empty, but was %v", envs) } diff --git a/project/context.go b/project/context.go index 5c767d3a1..05f5950dc 100644 --- a/project/context.go +++ b/project/context.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/Sirupsen/logrus" + "github.com/docker/libcompose/config" "github.com/docker/libcompose/logger" ) @@ -31,8 +32,8 @@ type Context struct { ProjectName string isOpen bool ServiceFactory ServiceFactory - EnvironmentLookup EnvironmentLookup - ResourceLookup ResourceLookup + EnvironmentLookup config.EnvironmentLookup + ResourceLookup config.ResourceLookup LoggerFactory logger.Factory IgnoreMissingConfig bool Project *Project diff --git a/project/project.go b/project/project.go index dcf440bac..4d8c8b6ac 100644 --- a/project/project.go +++ b/project/project.go @@ -6,8 +6,10 @@ import ( "strings" log "github.com/Sirupsen/logrus" + "github.com/docker/libcompose/config" "github.com/docker/libcompose/logger" "github.com/docker/libcompose/utils" + "github.com/docker/libcompose/yaml" ) // ServiceState holds the state of a service. @@ -39,7 +41,7 @@ type serviceAction func(service Service) error func NewProject(context *Context) *Project { p := &Project{ context: context, - Configs: NewConfigs(), + Configs: config.NewConfigs(), } if context.LoggerFactory == nil { @@ -112,14 +114,14 @@ func (p *Project) CreateService(name string) (Service, error) { } } - config.Environment = NewMaporEqualSlice(parsedEnv) + config.Environment = yaml.NewMaporEqualSlice(parsedEnv) } return p.context.ServiceFactory.Create(p, name, &config) } // AddConfig adds the specified service config for the specified name. -func (p *Project) AddConfig(name string, config *ServiceConfig) error { +func (p *Project) AddConfig(name string, config *config.ServiceConfig) error { p.Notify(EventServiceAdd, name, nil) p.Configs.Add(name, config) @@ -136,8 +138,8 @@ func (p *Project) Load(bytes []byte) error { } func (p *Project) load(file string, bytes []byte) error { - configs := make(map[string]*ServiceConfig) - configs, err := mergeProject(p, file, bytes) + configs := make(map[string]*config.ServiceConfig) + configs, err := config.MergeServices(p.Configs, p.context.EnvironmentLookup, p.context.ResourceLookup, file, bytes) if err != nil { log.Errorf("Could not parse config for project %s : %v", p.Name, err) return err diff --git a/project/project_test.go b/project/project_test.go index 82943b370..fafef51b7 100644 --- a/project/project_test.go +++ b/project/project_test.go @@ -6,6 +6,8 @@ import ( "strings" "testing" + "github.com/docker/libcompose/config" + "github.com/docker/libcompose/yaml" "github.com/stretchr/testify/assert" ) @@ -16,12 +18,12 @@ type TestServiceFactory struct { type TestService struct { factory *TestServiceFactory name string - config *ServiceConfig + config *config.ServiceConfig EmptyService Count int } -func (t *TestService) Config() *ServiceConfig { +func (t *TestService) Config() *config.ServiceConfig { return t.config } @@ -43,7 +45,7 @@ func (t *TestService) DependentServices() []ServiceRelationship { return nil } -func (t *TestServiceFactory) Create(project *Project, name string, serviceConfig *ServiceConfig) (Service, error) { +func (t *TestServiceFactory) Create(project *Project, name string, serviceConfig *config.ServiceConfig) (Service, error) { return &TestService{ factory: t, config: serviceConfig, @@ -59,11 +61,8 @@ func TestTwoCall(t *testing.T) { p := NewProject(&Context{ ServiceFactory: factory, }) - p.Configs = &Configs{ - m: map[string]*ServiceConfig{ - "foo": {}, - }, - } + p.Configs = config.NewConfigs() + p.Configs.Add("foo", &config.ServiceConfig{}) if err := p.Create("foo"); err != nil { t.Fatal(err) @@ -126,7 +125,7 @@ func TestParseWithGoodContent(t *testing.T) { type TestEnvironmentLookup struct { } -func (t *TestEnvironmentLookup) Lookup(key, serviceName string, config *ServiceConfig) []string { +func (t *TestEnvironmentLookup) Lookup(key, serviceName string, config *config.ServiceConfig) []string { return []string{fmt.Sprintf("%s=X", key)} } @@ -139,17 +138,14 @@ func TestEnvironmentResolve(t *testing.T) { ServiceFactory: factory, EnvironmentLookup: &TestEnvironmentLookup{}, }) - p.Configs = &Configs{ - m: map[string]*ServiceConfig{ - "foo": { - Environment: NewMaporEqualSlice([]string{ - "A", - "A=", - "A=B", - }), - }, - }, - } + p.Configs = config.NewConfigs() + p.Configs.Add("foo", &config.ServiceConfig{ + Environment: yaml.NewMaporEqualSlice([]string{ + "A", + "A=", + "A=B", + }), + }) service, err := p.CreateService("foo") if err != nil { diff --git a/project/types.go b/project/types.go index d8b8daf7c..be28bdb69 100644 --- a/project/types.go +++ b/project/types.go @@ -2,7 +2,7 @@ package project import ( "fmt" - "sync" + "github.com/docker/libcompose/config" ) // EventType defines a type of libcompose event. @@ -187,126 +187,10 @@ type InfoSet []Info // Info holds a list of InfoPart. type Info []InfoPart -// ServiceConfig holds libcompose service configuration -type ServiceConfig struct { - Build string `yaml:"build,omitempty"` - CapAdd []string `yaml:"cap_add,omitempty"` - CapDrop []string `yaml:"cap_drop,omitempty"` - CgroupParent string `yaml:"cgroup_parent,omitempty"` - CPUQuota int64 `yaml:"cpu_quota,omitempty"` - CPUSet string `yaml:"cpuset,omitempty"` - CPUShares int64 `yaml:"cpu_shares,omitempty"` - Command Command `yaml:"command,flow,omitempty"` - ContainerName string `yaml:"container_name,omitempty"` - Devices []string `yaml:"devices,omitempty"` - DNS Stringorslice `yaml:"dns,omitempty"` - DNSSearch Stringorslice `yaml:"dns_search,omitempty"` - Dockerfile string `yaml:"dockerfile,omitempty"` - DomainName string `yaml:"domainname,omitempty"` - Entrypoint Command `yaml:"entrypoint,flow,omitempty"` - EnvFile Stringorslice `yaml:"env_file,omitempty"` - Environment MaporEqualSlice `yaml:"environment,omitempty"` - Hostname string `yaml:"hostname,omitempty"` - Image string `yaml:"image,omitempty"` - Labels SliceorMap `yaml:"labels,omitempty"` - Links MaporColonSlice `yaml:"links,omitempty"` - LogDriver string `yaml:"log_driver,omitempty"` - MacAddress string `yaml:"mac_address,omitempty"` - MemLimit int64 `yaml:"mem_limit,omitempty"` - MemSwapLimit int64 `yaml:"memswap_limit,omitempty"` - Name string `yaml:"name,omitempty"` - Net string `yaml:"net,omitempty"` - Pid string `yaml:"pid,omitempty"` - Uts string `yaml:"uts,omitempty"` - Ipc string `yaml:"ipc,omitempty"` - Ports []string `yaml:"ports,omitempty"` - Privileged bool `yaml:"privileged,omitempty"` - Restart string `yaml:"restart,omitempty"` - ReadOnly bool `yaml:"read_only,omitempty"` - StdinOpen bool `yaml:"stdin_open,omitempty"` - SecurityOpt []string `yaml:"security_opt,omitempty"` - Tty bool `yaml:"tty,omitempty"` - User string `yaml:"user,omitempty"` - VolumeDriver string `yaml:"volume_driver,omitempty"` - Volumes []string `yaml:"volumes,omitempty"` - VolumesFrom []string `yaml:"volumes_from,omitempty"` - WorkingDir string `yaml:"working_dir,omitempty"` - Expose []string `yaml:"expose,omitempty"` - ExternalLinks []string `yaml:"external_links,omitempty"` - LogOpt map[string]string `yaml:"log_opt,omitempty"` - ExtraHosts []string `yaml:"extra_hosts,omitempty"` - Ulimits Ulimits `yaml:"ulimits,omitempty"` -} - -// EnvironmentLookup defines methods to provides environment variable loading. -type EnvironmentLookup interface { - Lookup(key, serviceName string, config *ServiceConfig) []string -} - -// ResourceLookup defines methods to provides file loading. -type ResourceLookup interface { - Lookup(file, relativeTo string) ([]byte, string, error) - ResolvePath(path, inFile string) string -} - -// NewConfigs initializes a new Configs struct -func NewConfigs() *Configs { - return &Configs{ - m: make(map[string]*ServiceConfig), - } -} - -// Configs holds a concurrent safe map of ServiceConfig -type Configs struct { - m map[string]*ServiceConfig - mu sync.RWMutex -} - -// Has checks if the config map has the specified name -func (c *Configs) Has(name string) bool { - c.mu.RLock() - defer c.mu.RUnlock() - _, ok := c.m[name] - return ok -} - -// Get returns the config and the presence of the specified name -func (c *Configs) Get(name string) (*ServiceConfig, bool) { - c.mu.RLock() - defer c.mu.RUnlock() - service, ok := c.m[name] - return service, ok -} - -// Add add the specifed config with the specified name -func (c *Configs) Add(name string, service *ServiceConfig) { - c.mu.Lock() - c.m[name] = service - c.mu.Unlock() -} - -// Len returns the len of the configs -func (c *Configs) Len() int { - c.mu.RLock() - defer c.mu.RUnlock() - return len(c.m) -} - -// Keys returns the names of the config -func (c *Configs) Keys() []string { - keys := []string{} - c.mu.RLock() - defer c.mu.RUnlock() - for name := range c.m { - keys = append(keys, name) - } - return keys -} - // Project holds libcompose project information. type Project struct { Name string - Configs *Configs + Configs *config.Configs Files []string ReloadCallback func() error context *Context @@ -331,7 +215,7 @@ type Service interface { Log() error Pull() error Kill() error - Config() *ServiceConfig + Config() *config.ServiceConfig DependentServices() []ServiceRelationship Containers() ([]Container, error) Scale(count int) error @@ -351,7 +235,7 @@ type Container interface { // ServiceFactory is an interface factory to create Service object for the specified // project, with the specified name and service configuration. type ServiceFactory interface { - Create(project *Project, name string, serviceConfig *ServiceConfig) (Service, error) + Create(project *Project, name string, serviceConfig *config.ServiceConfig) (Service, error) } // ServiceRelationshipType defines the type of service relationship. diff --git a/script/inline_schema.go b/script/inline_schema.go index 94d19a095..1370703a5 100644 --- a/script/inline_schema.go +++ b/script/inline_schema.go @@ -15,7 +15,7 @@ func main() { schema, err := ioutil.ReadFile("./script/config_schema_v1.json") - inlinedFile, err := os.Create("project/schema.go") + inlinedFile, err := os.Create("config/schema.go") if err != nil { panic(err) diff --git a/script/schema_template.go b/script/schema_template.go index 1c2a75c05..a28eaa64d 100644 --- a/script/schema_template.go +++ b/script/schema_template.go @@ -1,3 +1,3 @@ -package project +package config var schemaString = `{{.schemaString}}` diff --git a/project/types_yaml.go b/yaml/types_yaml.go similarity index 97% rename from project/types_yaml.go rename to yaml/types_yaml.go index 4f65a3180..ad3b1eec8 100644 --- a/project/types_yaml.go +++ b/yaml/types_yaml.go @@ -1,4 +1,4 @@ -package project +package yaml import ( "fmt" @@ -160,6 +160,17 @@ func (u Ulimit) MarshalYAML() (tag string, value interface{}, err error) { return "", u.ulimitValues, err } +// NewUlimit creates a Ulimit based on the specified parts. +func NewUlimit(name string, soft int64, hard int64) Ulimit { + return Ulimit{ + Name: name, + ulimitValues: ulimitValues{ + Soft: soft, + Hard: hard, + }, + } +} + // Command represents a docker command, can be a string or an array of strings. // FIXME why not use Stringorslice (type Command struct { Stringorslice } type Command struct { diff --git a/project/types_yaml_test.go b/yaml/types_yaml_test.go similarity index 73% rename from project/types_yaml_test.go rename to yaml/types_yaml_test.go index 6320f1a41..67372bae7 100644 --- a/project/types_yaml_test.go +++ b/yaml/types_yaml_test.go @@ -1,4 +1,4 @@ -package project +package yaml import ( "fmt" @@ -14,91 +14,6 @@ type StructStringorslice struct { Foo Stringorslice } -type TestConfig struct { - SystemContainers map[string]*ServiceConfig -} - -func newTestConfig() TestConfig { - return TestConfig{ - SystemContainers: map[string]*ServiceConfig{ - "udev": { - Image: "udev", - Restart: "always", - Net: "host", - Privileged: true, - DNS: Stringorslice{[]string{"8.8.8.8", "8.8.4.4"}}, - Environment: MaporEqualSlice{[]string{ - "DAEMON=true", - }}, - Labels: SliceorMap{map[string]string{ - "io.rancher.os.detach": "true", - "io.rancher.os.scope": "system", - }}, - VolumesFrom: []string{ - "system-volumes", - }, - Ulimits: Ulimits{ - Elements: []Ulimit{ - { - Name: "nproc", - ulimitValues: ulimitValues{ - Soft: 65557, - Hard: 65557, - }, - }, - }, - }, - }, - "system-volumes": { - Image: "state", - Net: "none", - ReadOnly: true, - Privileged: true, - Labels: SliceorMap{map[string]string{ - "io.rancher.os.createonly": "true", - "io.rancher.os.scope": "system", - }}, - Volumes: []string{ - "/dev:/host/dev", - "/var/lib/rancher/conf:/var/lib/rancher/conf", - "/etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt.rancher", - "/lib/modules:/lib/modules", - "/lib/firmware:/lib/firmware", - "/var/run:/var/run", - "/var/log:/var/log", - }, - LogDriver: "json-file", - }, - }, - } -} - -func TestMarshalConfig(t *testing.T) { - config := newTestConfig() - bytes, err := yaml.Marshal(config) - assert.Nil(t, err) - - config2 := TestConfig{} - - err = yaml.Unmarshal(bytes, &config2) - assert.Nil(t, err) - - assert.Equal(t, config, config2) -} - -func TestMarshalServiceConfig(t *testing.T) { - configPtr := newTestConfig().SystemContainers["udev"] - bytes, err := yaml.Marshal(configPtr) - assert.Nil(t, err) - - configPtr2 := &ServiceConfig{} - - err = yaml.Unmarshal(bytes, configPtr2) - assert.Nil(t, err) - - assert.Equal(t, configPtr, configPtr2) -} - func TestStringorsliceYaml(t *testing.T) { str := `{foo: [bar, baz]}` diff --git a/project/yaml_test.go b/yaml/yaml_test.go similarity index 98% rename from project/yaml_test.go rename to yaml/yaml_test.go index 1368f7e05..6fb92ed52 100644 --- a/project/yaml_test.go +++ b/yaml/yaml_test.go @@ -1,4 +1,4 @@ -package project +package yaml import ( "testing"