diff --git a/api/rpc/service/service.go b/api/rpc/service/service.go index 77ca3e4b9..06f7c24f0 100644 --- a/api/rpc/service/service.go +++ b/api/rpc/service/service.go @@ -2,7 +2,6 @@ package service import ( "fmt" - "log" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" @@ -30,15 +29,12 @@ func init() { docker, err = client.NewClient(dockerSock, defaultVersion, nil, defaultHeaders) if err != nil { // fail fast - log.Println("new client ....") panic(err) } } // Create implements ServiceServer func (s *Service) Create(ctx context.Context, req *ServiceCreateRequest) (*ServiceCreateResponse, error) { - log.Println(req) - // TODO: pass-through right now, but will be refactored into a helper library response, err := CreateService(docker, ctx, req) return response, err @@ -48,11 +44,10 @@ func (s *Service) Create(ctx context.Context, req *ServiceCreateRequest) (*Servi func CreateService(docker *client.Client, ctx context.Context, req *ServiceCreateRequest) (*ServiceCreateResponse, error) { serv := req.ServiceSpec - //prepare swarm.ServiceSpec full instance service := swarm.ServiceSpec{ Annotations: swarm.Annotations{ Name: serv.Name, - Labels: make(map[string]string), + Labels: serv.Labels, }, TaskTemplate: swarm.TaskSpec{ ContainerSpec: swarm.ContainerSpec{ @@ -96,7 +91,7 @@ func CreateService(docker *client.Client, ctx context.Context, req *ServiceCreat // add environment service.TaskTemplate.ContainerSpec.Env = serv.Env - //add common labels + // ensure supplied service label map is not nil, then add custom amp labels if service.Annotations.Labels == nil { service.Annotations.Labels = make(map[string]string) } diff --git a/api/rpc/stack/parse_test.go b/api/rpc/stack/parse_test.go index db3f72295..e3c2648f4 100644 --- a/api/rpc/stack/parse_test.go +++ b/api/rpc/stack/parse_test.go @@ -100,19 +100,40 @@ var ( }, } + sample5 = map[string]serviceSpec{ + "pinger": { + Image: "appcelerator/pinger", + Labels: map[string]string{ + "foo": "bar", + }, + Public: []publishSpec{ + { + PublishPort: 3000, + InternalPort: 3000, + }, + }, + }, + } + // map of filenames to a map of serviceSpec elements (each file has one or more) - compareStructs = map[string]map[string]serviceSpec{ - "sample-01.yml": sample1, - "sample-02.yml": sample2, - "sample-03.yml": sample3, - "sample-03.json": sample3, - "sample-04.yml": sample4, + compareSpecs = map[string]map[string]serviceSpec{ + "sample-01.yml": sample1, + "sample-02.yml": sample2, + "sample-03.yml": sample3, + "sample-03.json": sample3, + "sample-04.yml": sample4, + "sample-05-labels.yml": sample5, + "sample-06-labels.yml": sample5, } ) func TestSamples(t *testing.T) { tests := loadFiles(t) for _, test := range tests { + if compareSpecs[test.fileName] == nil { + t.Logf("WARNING: skipping '%s' because the comparison sample is missing", test.fileName) + continue + } parse(t, test) } } @@ -154,7 +175,7 @@ func parse(t *testing.T, test *TestSpec) { } for name, spec := range serviceMap { - if !spec.compare(t, compareStructs[test.fileName][name]) { + if !spec.compare(t, compareSpecs[test.fileName][name]) { t.Logf("name: %s, valid: %t, contents:\n%s", test.fileName, test.valid, string(test.contents)) t.Log(serviceMap) t.Errorf("FAIL: %s (%s)", name, test.fileName) @@ -162,90 +183,58 @@ func parse(t *testing.T, test *TestSpec) { } } +// compares actual (parsed) service spec to expected func (a serviceSpec) compare(t *testing.T, b serviceSpec) bool { if a.Image != b.Image { - t.Logf("Images don't match: %v != %v\n", a.Image, b.Image) + t.Logf("actual != expected (image): '%v' != '%v'\n", a.Image, b.Image) return false } if a.Replicas != b.Replicas { - t.Logf("Replicas don't match: %v != %v\n", a.Replicas, b.Replicas) + t.Logf("actual != expected (replicas): %v != %v\n", a.Replicas, b.Replicas) return false } - if len(a.Public) != len(b.Public) { - t.Logf("Public don't match: %v != %v\n", a.Public, b.Public) + if !reflect.DeepEqual(a.Public, b.Public) { + t.Logf("actual != expected (public): %v != %v\n", a.Public, b.Public) return false } - for _, publishSpec := range a.Public { - if !contains(b.Public, publishSpec) { - t.Logf("Public doesn' contains: %v => %v\n", a.Public, b.Public) - return false - } - } - if !compareEnvironment(a, b) { - t.Logf("Env don't match: %v != %v\n", a, b) + if !reflect.DeepEqual(toMap(a.Environment), toMap(b.Environment)) { + t.Logf("actual != expected (env): %v != %v\n", a.Environment, b.Environment) return false } - return true -} - -// contains checks to see if a publishSpec is contained in a publishSpec slice -func contains(specs []publishSpec, spec publishSpec) bool { - for _, cmp := range specs { - if spec.compare(cmp) { - return true - } - } - return false -} - -// compare returns true if and only if the members of both publishSpecs are equal -func (a publishSpec) compare(b publishSpec) bool { - if a.Name != b.Name { - return false - } - if a.PublishPort != b.PublishPort { - return false - } - if a.InternalPort != b.InternalPort { - return false - } - if a.Protocol != b.Protocol { + if !reflect.DeepEqual(toMap(a.Labels), toMap(b.Labels)) { + t.Logf("actual != expected (label): %v != %v\n", a.Labels, b.Labels) return false } return true } // compareEnvironment returns true if and only if both serviceSpec maps are equal (performs deep equal check) +// first, ensure both environments are converted to maps (because an environment might be a string array or a map) func compareEnvironment(a serviceSpec, b serviceSpec) bool { - ae := environmentToMap(a.Environment) - be := environmentToMap(b.Environment) + ae := toMap(a.Environment) + be := toMap(b.Environment) return reflect.DeepEqual(ae, be) } -// environmentToMap -func environmentToMap(env interface{}) map[string]string { - es, ok := env.(map[string]string) +// toMap attempts to cast an interface to either an array of strings or a map of strings to strings and then returns a typed map +func toMap(arrayOrMap interface{}) map[string]string { + stringmap, ok := arrayOrMap.(map[string]string) if ok { - return es + return stringmap } - envmap := make(map[string]string) + stringmap = make(map[string]string) - em, ok := env.(map[interface{}]interface{}) - if ok { - for k, v := range em { - envmap[k.(string)] = v.(string) + if m, ok := arrayOrMap.(map[interface{}]interface{}); ok { + for k, v := range m { + stringmap[k.(string)] = v.(string) } - } - ea, ok := env.([]interface{}) - if ok { - for _, s := range ea { - a := strings.Split(s.(string), "=") - k := a[0] - v := a[1] - envmap[k] = v + } else if a, ok := arrayOrMap.([]interface{}); ok { + for _, s := range a { + parts := strings.Split(s.(string), "=") + stringmap[parts[0]] = parts[1] } } - return envmap + return stringmap } diff --git a/api/rpc/stack/test_samples/sample-05-labels.yml b/api/rpc/stack/test_samples/sample-05-labels.yml new file mode 100644 index 000000000..62f944f40 --- /dev/null +++ b/api/rpc/stack/test_samples/sample-05-labels.yml @@ -0,0 +1,7 @@ +pinger: + image: appcelerator/pinger + labels: + - "foo=bar" + public: + - publish_port: 3000 + internal_port: 3000 diff --git a/api/rpc/stack/test_samples/sample-06-labels.yml b/api/rpc/stack/test_samples/sample-06-labels.yml new file mode 100644 index 000000000..5274d092d --- /dev/null +++ b/api/rpc/stack/test_samples/sample-06-labels.yml @@ -0,0 +1,7 @@ +pinger: + image: appcelerator/pinger + labels: + foo: bar + public: + - publish_port: 3000 + internal_port: 3000 diff --git a/api/rpc/stack/test_samples/sample-07-env.yml b/api/rpc/stack/test_samples/sample-07-env.yml new file mode 100644 index 000000000..45e6bc2d1 --- /dev/null +++ b/api/rpc/stack/test_samples/sample-07-env.yml @@ -0,0 +1,7 @@ +pinger: + image: appcelerator/pinger + env: + - "foo=bar" + public: + - publish_port: 3000 + internal_port: 3000 diff --git a/api/rpc/stack/test_samples/sample-08-env.yml b/api/rpc/stack/test_samples/sample-08-env.yml new file mode 100644 index 000000000..7238a20eb --- /dev/null +++ b/api/rpc/stack/test_samples/sample-08-env.yml @@ -0,0 +1,7 @@ +pinger: + image: appcelerator/pinger + env: + foo: bar + public: + - publish_port: 3000 + internal_port: 3000 diff --git a/cmd/amp/service-create.go b/cmd/amp/service-create.go index 1d3db91b3..995d976e4 100644 --- a/cmd/amp/service-create.go +++ b/cmd/amp/service-create.go @@ -35,6 +35,9 @@ var ( // environment variables env []string + // service labels + labels []string + // ports publishSpecs []string ) @@ -44,6 +47,7 @@ func init() { flags.StringVar(&name, "name", name, "Service name") flags.Uint64Var(&replicas, "replicas", replicas, "Number of tasks (default none)") flags.StringSliceVarP(&env, "env", "e", env, "Set environment variables (default [])") + flags.StringSliceVarP(&labels, "label", "l", labels, "Set service labels (default [])") flags.StringSliceVarP(&publishSpecs, "publish", "p", publishSpecs, "Publish a service externally. Format: [published-name|published-port:]internal-service-port[/protocol], i.e. '80:3000/tcp' or 'admin:3000'") ServiceCmd.AddCommand(createCmd) @@ -56,8 +60,6 @@ func create(amp *client.AMP, cmd *cobra.Command, args []string) error { } image = args[0] - fmt.Println(args) - fmt.Println(stringify(cmd)) parsedSpecs, err := parsePublishSpecs(publishSpecs) if err != nil { @@ -69,6 +71,7 @@ func create(amp *client.AMP, cmd *cobra.Command, args []string) error { Name: name, Replicas: replicas, Env: env, + Labels: stringmap(labels), PublishSpecs: parsedSpecs, } @@ -76,8 +79,6 @@ func create(amp *client.AMP, cmd *cobra.Command, args []string) error { ServiceSpec: spec, } - fmt.Println(request) - client := service.NewServiceClient(amp.Conn) reply, err := client.Create(context.Background(), request) if err != nil { @@ -89,7 +90,6 @@ func create(amp *client.AMP, cmd *cobra.Command, args []string) error { return nil } -// TODO: use this when adding label support func stringmap(a []string) map[string]string { m := make(map[string]string) for _, e := range a { @@ -99,11 +99,6 @@ func stringmap(a []string) map[string]string { return m } -func stringify(cmd *cobra.Command) string { - return fmt.Sprintf("{ name: %s, replicas: %d, env: %v }", - name, replicas, env) -} - func parsePublishSpecs(specs []string) ([]*service.PublishSpec, error) { publishSpecs := []*service.PublishSpec{} for _, input := range specs {