Skip to content
This repository has been archived by the owner on Nov 5, 2021. It is now read-only.

Commit

Permalink
Unit tests for service spec creation
Browse files Browse the repository at this point in the history
Signed-off-by: Marcelo Pereira <[email protected]>
  • Loading branch information
MarcPer committed Jul 14, 2018
1 parent 4d19113 commit f91ab73
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 49 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
sudo: required

services:
- docker
- docker
before_install:
- docker pull golang:1.9.2

script:
- make test
- make docker
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.PHONY: test

linux:
CGO_ENABLED=0 GOOS=linux go build -a -ldflags "-s -w" -installsuffix cgo -o ./jaas
Expand All @@ -7,3 +8,6 @@ darwin:

docker:
docker build -t alexellis2/jaas:latest .

test:
go test ./...
117 changes: 69 additions & 48 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ var (
verbose bool
)

type clientInterface interface {
client.CommonAPIClient
}

func init() {
rootCmd.AddCommand(runCmd)

Expand Down Expand Up @@ -78,6 +82,7 @@ func runTask(taskRequest TaskRequest) error {
fmt.Printf("Connected to.. OK %s\n", taskRequest.Networks)
fmt.Printf("Constraints: %s\n", taskRequest.Constraints)
fmt.Printf("envVars: %s\n", taskRequest.EnvVars)
fmt.Printf("envFiles: %s\n", taskRequest.EnvFiles)
fmt.Printf("Secrets: %s\n", taskRequest.Secrets)
}

Expand All @@ -94,7 +99,6 @@ func runTask(taskRequest TaskRequest) error {
var err error
c, err = client.NewEnvClient()
if err != nil {

return fmt.Errorf("is the Docker Daemon running? Error: %s", err.Error())
}

Expand All @@ -116,13 +120,7 @@ func runTask(taskRequest TaskRequest) error {
}
}

spec := makeSpec(taskRequest.Image, taskRequest.EnvVars)
if len(taskRequest.Networks) > 0 {
nets := []swarm.NetworkAttachmentConfig{
swarm.NetworkAttachmentConfig{Target: taskRequest.Networks[0]},
}
spec.Networks = nets
}
spec := makeServiceSpec(taskRequest, c)

createOptions := types.ServiceCreateOptions{}

Expand All @@ -131,18 +129,52 @@ func runTask(taskRequest TaskRequest) error {
fmt.Println("Using RegistryAuth")
}

placement := &swarm.Placement{}
if len(taskRequest.Constraints) > 0 {
placement.Constraints = taskRequest.Constraints
spec.TaskTemplate.Placement = placement
createResponse, _ := c.ServiceCreate(context.Background(), spec, createOptions)
opts := types.ServiceInspectOptions{InsertDefaults: true}

service, _, _ := c.ServiceInspectWithRaw(context.Background(), createResponse.ID, opts)
fmt.Printf("Service created: %s (%s)\n", service.Spec.Name, createResponse.ID)

taskExitCode := pollTask(c, createResponse.ID, timeoutVal, taskRequest.ShowLogs, taskRequest.RemoveService)
os.Exit(taskExitCode)
return nil
}

func makeServiceSpec(tr TaskRequest, c clientInterface) swarm.ServiceSpec {
max := uint64(1)
spec := swarm.ServiceSpec{
TaskTemplate: swarm.TaskSpec{
RestartPolicy: &swarm.RestartPolicy{
MaxAttempts: &max,
Condition: swarm.RestartPolicyConditionNone,
},
ContainerSpec: &swarm.ContainerSpec{
Image: tr.Image,
Env: tr.EnvVars,
},
},
}
attachNetworks(&spec, tr.Networks)
readEnvFiles(&spec, tr.EnvFiles)
setConstraints(&spec, tr.Constraints)
setCommand(&spec, tr.Command)
attachMounts(&spec, tr.Mounts)
attachSecrets(&spec, tr.Secrets, c)
return spec
}

if len(taskRequest.Command) > 0 {
spec.TaskTemplate.ContainerSpec.Command = strings.Split(taskRequest.Command, " ")
func attachNetworks(spec *swarm.ServiceSpec, networks []string) {
nets := []swarm.NetworkAttachmentConfig{}
for _, n := range networks {
nets = append(nets, swarm.NetworkAttachmentConfig{Target: n})
}

if len(taskRequest.EnvFiles) > 0 {
for _, file := range taskRequest.EnvFiles {
spec.Networks = nets
}

func readEnvFiles(spec *swarm.ServiceSpec, files []string) {
if len(files) > 0 {
for _, file := range files {
envs, err := readEnvs(file)
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
Expand All @@ -154,11 +186,26 @@ func runTask(taskRequest TaskRequest) error {
}
}
}
}

func setConstraints(spec *swarm.ServiceSpec, constraints []string) {
if len(constraints) > 0 {
placement := &swarm.Placement{Constraints: constraints}
spec.TaskTemplate.Placement = placement
}
}

func setCommand(spec *swarm.ServiceSpec, command string) {
if len(command) > 0 {
spec.TaskTemplate.ContainerSpec.Command = strings.Split(command, " ")
}
}

func attachMounts(spec *swarm.ServiceSpec, mounts []string) {
spec.TaskTemplate.ContainerSpec.Mounts = []mount.Mount{}
for _, bindMount := range taskRequest.Mounts {
for _, bindMount := range mounts {
parts := strings.Split(bindMount, "=")
if len(parts) < 2 || len(parts) > 2 {
if len(parts) != 2 {
fmt.Fprintf(os.Stderr, "Bind-mounts must be specified as: src=dest, i.e. --mount /home/alex/tmp/=/tmp/\n")
os.Exit(1)
}
Expand All @@ -172,11 +219,13 @@ func runTask(taskRequest TaskRequest) error {
spec.TaskTemplate.ContainerSpec.Mounts = append(spec.TaskTemplate.ContainerSpec.Mounts, mountVal)
}
}
}

secretList, err := c.SecretList(context.Background(), types.SecretListOptions{})
func attachSecrets(spec *swarm.ServiceSpec, secrets []string, c clientInterface) {
secretList, _ := c.SecretList(context.Background(), types.SecretListOptions{})

spec.TaskTemplate.ContainerSpec.Secrets = []*swarm.SecretReference{}
for _, serviceSecret := range taskRequest.Secrets {
for _, serviceSecret := range secrets {
var secretID string
for _, s := range secretList {
if serviceSecret == s.Spec.Annotations.Name {
Expand All @@ -202,34 +251,6 @@ func runTask(taskRequest TaskRequest) error {

spec.TaskTemplate.ContainerSpec.Secrets = append(spec.TaskTemplate.ContainerSpec.Secrets, &secretVal)
}

createResponse, _ := c.ServiceCreate(context.Background(), spec, createOptions)
opts := types.ServiceInspectOptions{InsertDefaults: true}

service, _, _ := c.ServiceInspectWithRaw(context.Background(), createResponse.ID, opts)
fmt.Printf("Service created: %s (%s)\n", service.Spec.Name, createResponse.ID)

taskExitCode := pollTask(c, createResponse.ID, timeoutVal, taskRequest.ShowLogs, taskRequest.RemoveService)
os.Exit(taskExitCode)
return nil
}

func makeSpec(image string, envVars []string) swarm.ServiceSpec {
max := uint64(1)

spec := swarm.ServiceSpec{
TaskTemplate: swarm.TaskSpec{
RestartPolicy: &swarm.RestartPolicy{
MaxAttempts: &max,
Condition: swarm.RestartPolicyConditionNone,
},
ContainerSpec: &swarm.ContainerSpec{
Image: image,
Env: envVars,
},
},
}
return spec
}

func readEnvs(file string) ([]string, error) {
Expand Down
139 changes: 139 additions & 0 deletions cmd/run_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright (c) Alex Ellis 2017-2018. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

package cmd

import (
"context"
"io/ioutil"
"os"
"reflect"
"strconv"
"testing"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
)

var validRequest = TaskRequest{
Image: "input-image",
Networks: []string{"net1", "net2"},
Constraints: []string{"node.id=2ivku8v2gvtg4", "engine.labels.operatingsystem==ubuntu 14.04"},
EnvVars: []string{"ev1=val1", "ev2=val2"},
Mounts: []string{"hostVol1=taskVol1", "hostVol2=taskVol2"},
Secrets: []string{"secret1", "secret2"},
ShowLogs: true,
Timeout: "12",
RemoveService: true,
RegistryAuth: "true",
Command: "echo 'some output'",
}

type fakeClient struct {
client.CommonAPIClient
secrets []string
}

func (fk fakeClient) SecretList(ctx context.Context, sopt types.SecretListOptions) ([]swarm.Secret, error) {
slist := []swarm.Secret{}
for i, secret := range fk.secrets {
a := swarm.Annotations{Name: secret}
sspec := swarm.SecretSpec{Annotations: a}
s := swarm.Secret{
ID: strconv.Itoa(i),
Meta: swarm.Meta{},
Spec: sspec,
}

slist = append(slist, s)
}
return slist, nil
}

func newClient(secrets []string) fakeClient {
return fakeClient{secrets: secrets}
}

func contains(el string, array []string) bool {
for _, e := range array {
if el == e {
return true
}
}
return false
}

func TestMakeServiceSpecValid(t *testing.T) {
c := newClient([]string{"secret1", "secret2", "secret3"})

f1, _ := ioutil.TempFile("", "jaas_env")
f1body := []byte("f1var1=val11\nf1var2=val12\n")
f1.Write(f1body)
f1.Sync()
f2, _ := ioutil.TempFile("", "jaas_env")
f2body := []byte("f2var1=val21\nf2var2=val22\n")
f2.Write(f2body)
f2.Sync()
defer os.Remove(f1.Name())
defer os.Remove(f2.Name())
validRequest.EnvFiles = []string{f1.Name(), f2.Name()}

spec := makeServiceSpec(validRequest, c)
if spec.TaskTemplate.ContainerSpec.Image != "input-image" {
t.Errorf("Container spec image should be %s, was %s", validRequest.Image, spec.TaskTemplate.ContainerSpec.Image)
}

// Test networks
networkTargets := []string{}
for _, n := range spec.Networks {
networkTargets = append(networkTargets, n.Target)
}
if !reflect.DeepEqual(networkTargets, []string{"net1", "net2"}) {
t.Errorf("Container spec networks should be %s, was %s", validRequest.Networks, networkTargets)
}

// Test env vars from input and env files
inputEnv := []string{"ev1=val1", "ev2=val2", "f1var1=val11", "f1var2=val12", "f2var1=val21", "f2var2=val22"}
for _, ev := range inputEnv {
if !contains(ev, spec.TaskTemplate.ContainerSpec.Env) {
t.Errorf("Container spec env should contain %s, but only has %s", ev, spec.TaskTemplate.ContainerSpec.Env)
}
}

// Test constraints
if !reflect.DeepEqual(spec.TaskTemplate.Placement.Constraints, []string{"node.id=2ivku8v2gvtg4", "engine.labels.operatingsystem==ubuntu 14.04"}) {
t.Errorf("Container spec constraints should be %s, was %s", validRequest.Constraints, spec.TaskTemplate.Placement.Constraints)
}

// Test mounts
expectedMounts := []mount.Mount{
{Source: "hostVol1", Target: "taskVol1"},
{Source: "hostVol2", Target: "taskVol2"},
}
if !reflect.DeepEqual(spec.TaskTemplate.ContainerSpec.Mounts, expectedMounts) {
t.Error("Container spec mounts should include:")
for _, m := range expectedMounts {
t.Errorf("{Source: %s, Target: %s}", m.Source, m.Target)
}
t.Error("But contained instead:")
for _, m := range spec.TaskTemplate.ContainerSpec.Mounts {
t.Errorf("{Source: %s, Target: %s}", m.Source, m.Target)
}
}

// Test secrets
secretNames := []string{}
for _, s := range spec.TaskTemplate.ContainerSpec.Secrets {
secretNames = append(secretNames, s.SecretName)
}
if !reflect.DeepEqual(secretNames, []string{"secret1", "secret2"}) {
t.Errorf("Container spec secrets should be %s, was %s", validRequest.Secrets, secretNames)
}

// Test command
if !reflect.DeepEqual(spec.TaskTemplate.ContainerSpec.Command, []string{"echo", "'some", "output'"}) {
t.Errorf("Container spec command should be %s, was %s", []string{"echo", "'some", "output'"}, spec.TaskTemplate.ContainerSpec.Command)
}
}

0 comments on commit f91ab73

Please sign in to comment.