Skip to content

Commit

Permalink
Service labels (#249)
Browse files Browse the repository at this point in the history
* adding more sample files

* added label support for service create, updated stack parser tests
  • Loading branch information
subfuzion authored Sep 26, 2016
1 parent b61350f commit 34425b0
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 81 deletions.
9 changes: 2 additions & 7 deletions api/rpc/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package service

import (
"fmt"
"log"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
Expand Down Expand Up @@ -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
Expand All @@ -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{
Expand Down Expand Up @@ -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)
}
Expand Down
117 changes: 53 additions & 64 deletions api/rpc/stack/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -154,98 +175,66 @@ 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)
}
}
}

// 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
}
7 changes: 7 additions & 0 deletions api/rpc/stack/test_samples/sample-05-labels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pinger:
image: appcelerator/pinger
labels:
- "foo=bar"
public:
- publish_port: 3000
internal_port: 3000
7 changes: 7 additions & 0 deletions api/rpc/stack/test_samples/sample-06-labels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pinger:
image: appcelerator/pinger
labels:
foo: bar
public:
- publish_port: 3000
internal_port: 3000
7 changes: 7 additions & 0 deletions api/rpc/stack/test_samples/sample-07-env.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pinger:
image: appcelerator/pinger
env:
- "foo=bar"
public:
- publish_port: 3000
internal_port: 3000
7 changes: 7 additions & 0 deletions api/rpc/stack/test_samples/sample-08-env.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pinger:
image: appcelerator/pinger
env:
foo: bar
public:
- publish_port: 3000
internal_port: 3000
15 changes: 5 additions & 10 deletions cmd/amp/service-create.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ var (
// environment variables
env []string

// service labels
labels []string

// ports
publishSpecs []string
)
Expand All @@ -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)
Expand All @@ -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 {
Expand All @@ -69,15 +71,14 @@ func create(amp *client.AMP, cmd *cobra.Command, args []string) error {
Name: name,
Replicas: replicas,
Env: env,
Labels: stringmap(labels),
PublishSpecs: parsedSpecs,
}

request := &service.ServiceCreateRequest{
ServiceSpec: spec,
}

fmt.Println(request)

client := service.NewServiceClient(amp.Conn)
reply, err := client.Create(context.Background(), request)
if err != nil {
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down

0 comments on commit 34425b0

Please sign in to comment.