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

Unit tests for service spec creation #34

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
sudo: required

language: go

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

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)
}
}