Skip to content

Commit

Permalink
Initial implementation.
Browse files Browse the repository at this point in the history
This implementation allows to start, stop and manipulate a swarm cluster
from a go program or a CLI.

Add a first integration test.

And make sure all the join cmd are done before returning.

Master => Manager

cli is a basic example

sind -> go-sind + README

let the docker daemon decide which port to bind for exposing the remote docker daemon port

manage host properly

update readme

wait for the remote daemon to be ready

add an empty cobra entrypoint

dumb implementation of create

use ping instead of info

got a working implementation for a store file

use docker labels instead of storing ids.

make --cluster a global flag

implement delete

implement the env command

implement list-clusters

add ls as an alias of list

add a test suite for store

first attempt to implement deploy

still does not work

broken implemntation of port bindings

working-ish implementation of deploy

deploy => push

more agressive delete

ports mapping works as expected

add a note about how to use it as a CLI

better UX

go mod tidy

wait for the image to be pulled

add a basic makefile

add test targets to the makefile

setup goreleaster

add a goreleaser target
  • Loading branch information
jlevesy committed Jan 23, 2019
0 parents commit 0e24fde
Show file tree
Hide file tree
Showing 22 changed files with 1,875 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist/
28 changes: 28 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
project_name: sind
before:
hooks:
- go mod download
builds:
- main: ./cmd/sind/main.go
binary: sind
ldflags:
- "-s -w"
env:
- CGO_ENABLED=0
archive:
replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
48 changes: 48 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
DIST_DIR=dist

all: build

#
# Release targets
#

.PHONY: release
release: clean
goreleaser

.PHONY: dry_release
dry_release: clean
goreleaser --skip-publish

#
# Test targets
#

test: unit_test integration_test

.PHONY: integration_test
integration_test:
go test ./integration

.PHONY: unit_test
unit_test:
go test -race -v -cover -timeout=5s -run=$(T) $(shell go list ./... | grep -v integration)

#
# Build targets
#

install: build
mv ${DIST_DIR}/sind $${GOPATH}/bin/sind

build: clean dist binary

.PHONY: binary
binary:
CGO_ENABLED=0 go build -ldflags="-s -w" -o ${DIST_DIR}/sind ./cmd/sind

dist:
mkdir -p ${DIST_DIR}

clean:
rm -rf ${DIST_DIR}
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# go-sind

go-SIND enables you to create swarm clusters on a docker host using SIND (swarm in docker).

Not yet ready to use, this is a PoC at the moment.

## Requirements

- A reachable docker daemon.
- go 1.11.x

## Using it as a go package

Head to the [base example](./example/base/main.go) or to the [integration test suite](./integration/sind_test.go) to get started.

## Using it as a CLI

```
go install github.com/jlevesy/go-sind/cmd/sind
# Will create a new cluster with 3 managers, 3 workers and the port 8080 of the host bound
# to the port 8080 of the ingress network of the cluster.
sind create --managers=3 --workers=3 -p 8080:8080
# Setup the docker cli configuration to communicate with the new cluster.
eval $(sind env)
# Deploy an app
docker stack deploy -c my-stack.yml app
# Enjoy your app :)
docker service ls
# Once your're done, clear your docker CLI configuration then delete your cluster
unset DOCKER_HOST
sind delete
```

## Why ?

Mostly for automated testing.

## TODO list

- [ ] CI
- [ ] Release process
71 changes: 71 additions & 0 deletions cli/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cli

import (
"context"
"fmt"
"github.com/spf13/cobra"
"os"

"github.com/jlevesy/go-sind/sind"
)

var (
managers = 0
workers = 0
networkName = ""
portsMapping = []string{}

createCmd = &cobra.Command{
Use: "create",
Short: "Create a new swarm cluster.",
Run: runCreate,
}
)

func init() {
rootCmd.AddCommand(createCmd)

createCmd.Flags().IntVarP(&managers, "managers", "m", 1, "Amount of managers in the created cluster.")
createCmd.Flags().IntVarP(&workers, "workers", "w", 0, "Amount of workers in the created cluster.")
createCmd.Flags().StringVarP(&networkName, "network_name", "n", "sind_default", "Name of the network to create.")
createCmd.Flags().StringSliceVarP(&portsMapping, "ports", "p", []string{}, "Ingress network port binding.")
}

func runCreate(cmd *cobra.Command, args []string) {
fmt.Printf("Creating a new cluster %q with %d managers and %d, workers...\n", clusterName, managers, workers)

ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

store, err := NewStore()
if err != nil {
fmt.Printf("unable to create store: %v\n", err)
os.Exit(1)
}

if err := store.ValidateName(clusterName); err != nil {
fmt.Printf("invalid cluster name: %v\n", err)
os.Exit(1)
}

clusterParams := sind.CreateClusterParams{
Managers: managers,
Workers: workers,
NetworkName: networkName,
ClusterName: clusterName,
PortBindings: portsMapping,
}

cluster, err := sind.CreateCluster(ctx, clusterParams)
if err != nil {
fmt.Printf("unable to setup a swarm cluster: %v\n", err)
os.Exit(1)
}

if err = store.Save(*cluster); err != nil {
fmt.Printf("unable to save cluster: %v\n", err)
os.Exit(1)
}

fmt.Printf("Cluster %s successfuly created !\n", clusterName)
}
46 changes: 46 additions & 0 deletions cli/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cli

import (
"context"
"fmt"

"github.com/spf13/cobra"
)

var (
deleteCmd = &cobra.Command{
Use: "delete",
Short: "Delete a swarm cluster.",
Run: runDelete,
}
)

func init() {
rootCmd.AddCommand(deleteCmd)
}

func runDelete(cmd *cobra.Command, args []string) {
fmt.Printf("Deleting cluster %q...\n", clusterName)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

store, err := NewStore()
if err != nil {
fail("unable to create store: %v\n", err)
}

cluster, err := store.Load(clusterName)
if err != nil {
fail("unable to load cluster: %v\n", err)
}

if err = cluster.Delete(ctx); err != nil {
fail("unable to tear down cluster: %v", err)
}

if err = store.Delete(clusterName); err != nil {
fail("unable to delete cluster from storage: %v", err)
}

fmt.Printf("Cluster %s successfuly deleted !\n", clusterName)
}
33 changes: 33 additions & 0 deletions cli/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cli

import (
"fmt"

"github.com/spf13/cobra"
)

var (
envCmd = &cobra.Command{
Use: "env",
Short: "Sets up docker env variables.",
Run: runEnv,
}
)

func init() {
rootCmd.AddCommand(envCmd)
}

func runEnv(cmd *cobra.Command, args []string) {
store, err := NewStore()
if err != nil {
fail("unable to create store: %v\n", err)
}

cluster, err := store.Load(clusterName)
if err != nil {
fail("unable to load cluster: %v\n", err)
}

fmt.Printf("export DOCKER_HOST=%s", cluster.Cluster.DockerHost())
}
42 changes: 42 additions & 0 deletions cli/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cli

import (
"fmt"
"os"
"text/tabwriter"

"github.com/spf13/cobra"
)

var (
listCmd = &cobra.Command{
Use: "list",
Short: "List existing clusters.",
Aliases: []string{"ls"},
Run: runList,
}
)

func init() {
rootCmd.AddCommand(listCmd)
}

func runList(cmd *cobra.Command, args []string) {
store, err := NewStore()
if err != nil {
fail("unable to create store: %v\n", err)
}

clusters, err := store.List()
if err != nil {
fail("unable to list existing clusters: %v\n", err)
}

wr := tabwriter.NewWriter(os.Stdout, 4, 8, 0, '\t', 0)
fmt.Fprintf(wr, "NAME\tSWARM DOCKER HOST\tDOCKER HOST\t\n")
for _, cluster := range clusters {
fmt.Fprintf(wr, "%s\t%s\t%s\t\n", cluster.Name, cluster.Cluster.DockerHost(), cluster.Host.Host)
}
wr.Flush()
fmt.Printf("\nTotal: %d\n", len(clusters))
}
42 changes: 42 additions & 0 deletions cli/push.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cli

import (
"context"
"fmt"

"github.com/spf13/cobra"
)

var (
pushCmd = &cobra.Command{
Use: "push",
Short: "Push an image to the swarm cluster.",
Run: runPush,
}
)

func init() {
rootCmd.AddCommand(pushCmd)
}

func runPush(cmd *cobra.Command, args []string) {
fmt.Printf("Pushing images %v to the cluster %s...\n", args, clusterName)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

store, err := NewStore()
if err != nil {
fail("unable to create store: %v\n", err)
}

cluster, err := store.Load(clusterName)
if err != nil {
fail("unable to load cluster: %v\n", err)
}

if err = cluster.PushImage(ctx, args); err != nil {
fail("unable to push %v to the cluster: %v", args, err)
}

fmt.Printf("Images %v successfuly pushed to %s !\n", args, clusterName)
}
Loading

0 comments on commit 0e24fde

Please sign in to comment.