Skip to content

Commit

Permalink
feat: add command line support
Browse files Browse the repository at this point in the history
- multikind add $name: to add one machine
- multikind delete $name: to remove one machine
- multikind export $name: to export $name 's kubeconfig
- multikind list: to list machines and their resources
  • Loading branch information
hsinhoyeh committed Jan 18, 2022
1 parent 0e9b5c8 commit 8e8c467
Show file tree
Hide file tree
Showing 25 changed files with 1,543 additions and 32 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
vendor
18 changes: 18 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM golang:1.17.2-stretch AS build
WORKDIR /src
COPY . .

WORKDIR /src
# Use go mod vendor to download imported package before building Docker image so no need to download here
#RUN go mod download

ARG GitCommitId
ARG BuildTime

# use go tool $binary | grep $variable
# to find out actual path

RUN make build

FROM alpine:3.15 AS bin
COPY --from=build /out /out
26 changes: 26 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
BUILDTIME=$(shell date --rfc-3339=seconds)
GITCOMMITID=$(shell git rev-parse HEAD)

windows: ## Build for Windows
env GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \
go build -ldflags '-X "github.com/footprintai/multikind/pkg/version.BuildTime='"${BUILDTIME}"'" -X "github.com/footprintai/multikind/pkg/version.GitCommitId='"${GITCOMMITID}"'" -extldflags "-static"' -o /out/multikind.windows.exe main.go

linux: ## Build for Linux
env GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
go build -ldflags '-X "github.com/footprintai/multikind/pkg/version.BuildTime='"${BUILDTIME}"'" -X "github.com/footprintai/multikind/pkg/version.GitCommitId='"${GITCOMMITID}"'" -extldflags "-static"' -o /out/multikind.linux main.go

darwin: ## Build for Darwin (macOS)
env GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 \
go build -ldflags '-X "github.com/footprintai/multikind/pkg/version.BuildTime='"${BUILDTIME}"'" -X "github.com/footprintai/multikind/pkg/version.GitCommitId='"${GITCOMMITID}"'" -extldflags "-static"' -o /out/multikind.darwin main.go

darwinSilicon: ## Build for Darwin Silicon (macOS M1)
env GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 \
go build -ldflags '-X "github.com/footprintai/multikind/pkg/version.BuildTime='"${BUILDTIME}"'" -X "github.com/footprintai/multikind/pkg/version.GitCommitId='"${GITCOMMITID}"'" -extldflags "-static"' -o /out/multikind.darwin-arm64 main.go


build: windows linux darwin darwinSilicon ## Build binaries
@echo commitid: $(GITCOMMITID)
@echo buildtime: $(BUILDTIME)

help: ## Display available commands
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
4 changes: 2 additions & 2 deletions assets/bootstrap/provision-cluster.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

kind create cluster --image=kindest/node:v1.20.7 --config /tmp/kind-config.yaml

cp /root/.kube/config /home/vagrant/kubeconfig
chown vagrant:vagrant /home/vagrant/kubeconfig
cp /root/.kube /home/vagrant/.kube
chown -R vagrant:vagrant /home/vagrant/.kube
9 changes: 9 additions & 0 deletions buildandpush.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash

# make sure the import packages are downloaded
#go mod vendor
./gomodtidy.sh

docker build -t footprintai/multikind:v1 \
--no-cache -f Dockerfile .
docker push footprintai/multikind:v1
169 changes: 169 additions & 0 deletions cmd/multikind/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package multikind

import (
goflag "flag"
"os"
"path/filepath"

"github.com/footprintai/multikind/pkg/runtime"
"github.com/footprintai/multikind/pkg/version"
log "github.com/golang/glog"
"github.com/spf13/cobra"
)

var (
cpus int // number of cpus allocated to the vagrant
memoryInG int // number of Gigabytes allocated to the vagrant
vagrantRootDir string // vagrant root dir which containing multiple vagrant folders, each folder(i.e. $machinename) represents a single virtual machine configuration (default: ./.vagrant)
forceDelete bool // force to deleted the instance (default: false)
forceCreate bool // force to create the instance regardless the instance's status (default: false)
forceOverwrite bool // force to overwrite the existing kubeconf file
verbose bool // verbose (default: true)
kubeconfigPath string // kubeconfig path of a vagrant machine (default: ./.vagrant/$machine/kubeconfig)

rootCmd = &cobra.Command{
Use: "multikind",
Short: "a multikind cli tool",
Long: `multikind is a command-line tool which use vagrant and kind to provision k8s single-node cluster.`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// For cobra + glog flags. Available to all subcommands.
goflag.Parse()
},
}

versionCmd = &cobra.Command{
Use: "version",
Short: "version of vagrant machine",
RunE: func(cmd *cobra.Command, args []string) error {
version.Print()
return nil
},
}

exportCmd = &cobra.Command{
Use: "export",
Short: "export kubeconfig from a vagrant machine",
RunE: func(cmd *cobra.Command, args []string) error {
run := mustNewRunCmd()
return run.Export(args[0], kubeconfigPath)
},
}

addCmd = &cobra.Command{
Use: "add",
Short: "add a vagrant machine",
RunE: func(cmd *cobra.Command, args []string) error {
run := mustNewRunCmd()
return run.Add(args[0], cpus, memoryInG)
},
}
deleteCmd = &cobra.Command{
Use: "delete",
Short: "delete a vagrant machine",
RunE: func(cmd *cobra.Command, args []string) error {
run := mustNewRunCmd()
return run.Delete(args[0])
},
}
listCmd = &cobra.Command{
Use: "list",
Short: "list vagrant machines",
RunE: func(cmd *cobra.Command, args []string) error {
run := mustNewRunCmd()
return run.List()
},
}
)

func mustNewRunCmd() *runCmd {
cmd, err := newRunCmd()
if err != nil {
panic(err)
}
return cmd
}

func newRunCmd() (*runCmd, error) {
//cfg := &runtime.VagrantMachineConfig{
// CPUs: cpus,
// Memory: memoryInG * 1024, // in M egabytes
//}
vag := runtime.NewVagrantMachines(vagrantRootDir, verbose)
return &runCmd{vag: vag}, nil
}

type runCmd struct {
vag *runtime.VagrantMachines
}

func (r *runCmd) Add(name string, cpus, memoryInG int) error {
m := r.vag.NewMachine(name)
m.AddConfig(&runtime.VagrantMachineConfig{
CPUs: cpus,
Memory: memoryInG * 1024, // in M egabytes
})
if err := m.Up(forceCreate); err != nil {
log.Errorf("runcmd: add vagrant node (%s) failed, err:%+v\n", name, err)
return err
}
return nil
}

func (r *runCmd) Export(name string, path string) error {
if path == "" {
path = filepath.Join(vagrantRootDir, name, "kubeconfig")
}
m := r.vag.NewMachine(name)
if err := m.ExportKubeConfig(path, forceOverwrite); err != nil {
log.Errorf("runcmd: export vagrant node (%s) failed, err:%+v\n", name, err)
return err
}
return nil
}

func (r *runCmd) Delete(name string) error {
m := r.vag.NewMachine(name)
if err := m.Destroy(forceDelete); err != nil {
log.Errorf("runcmd: delete vagrant node (%s) failed, err:%+v\n", name, err)
return err
}
return nil
}

var dummyRow = &runtime.OutputVagrantMachine{}

func (r *runCmd) List() error {
w := NewFormatWriter(os.Stdout, Table)
var listvalues [][]string
respList, err := r.vag.ListMachines()
if err != nil {
return err
}
for _, resp := range respList {
listvalues = append(listvalues, resp.Values())
}
return w.WriteAndClose(dummyRow.Headers(), listvalues)
}

func Main() {
defer log.Flush()

rootCmd.Execute()
}

func init() {
rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(addCmd)
rootCmd.AddCommand(deleteCmd)
rootCmd.AddCommand(listCmd)
rootCmd.AddCommand(exportCmd)

rootCmd.PersistentFlags().StringVar(&vagrantRootDir, "dir", ".vagrant", "vagrant root dir")
rootCmd.PersistentFlags().BoolVar(&verbose, "verbose", true, "verbose (default: true)")
addCmd.Flags().IntVar(&cpus, "cpus", 1, "number of cpus allocated to the vagrant")
addCmd.Flags().IntVar(&memoryInG, "memoryg", 1, "number of memory in gigabytes allocated to the vagrant")
addCmd.Flags().BoolVar(&forceCreate, "f", false, "force to create instance regardless the machine status")
deleteCmd.Flags().BoolVar(&forceDelete, "f", false, "force remove vagrant instance")
exportCmd.Flags().StringVar(&kubeconfigPath, "kubeconfig_path", "", "force remove vagrant instance")
exportCmd.Flags().BoolVar(&forceOverwrite, "f", false, "force to overwrite the exiting file")
}
62 changes: 62 additions & 0 deletions cmd/multikind/output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package multikind

import (
"encoding/csv"
"errors"
"io"

"github.com/olekukonko/tablewriter"
)

type Format string

const (
UnknownFormat Format = "unknown"
Table Format = "table"
CSV Format = "csv"
)

func MustParseFormat(s string) Format {
switch s {
case string(Table):
return Table
case string(CSV):
return CSV
default:
return UnknownFormat
}
}

func NewFormatWriter(w io.Writer, format Format) *FormatWriter {
return &FormatWriter{
w: w,
format: format,
}
}

type FormatWriter struct {
w io.Writer
format Format
}

func (f *FormatWriter) WriteAndClose(headers []string, items [][]string) error {
if f.format == Table {
table := tablewriter.NewWriter(f.w)
table.SetHeader(headers)
for _, item := range items {
table.Append(item)
}
table.Render()
return nil
} else if f.format == CSV {
ww := csv.NewWriter(f.w)
ww.Write(headers)
for _, item := range items {
ww.Write(item)
}
ww.Flush()
return ww.Error()
} else {
return errors.New("not implemented")
}
}
17 changes: 14 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,24 @@ module github.com/footprintai/multikind
go 1.17

require (
github.com/bmatcuk/go-vagrant v1.6.0
github.com/bramvdbogaerde/go-scp v1.2.0
github.com/golang/glog v1.0.0
github.com/hashicorp/go-version v1.4.0
github.com/olekukonko/tablewriter v0.0.5
github.com/spf13/afero v1.8.0
github.com/spf13/cobra v1.3.0
github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce
)

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/text v0.3.4 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
Loading

0 comments on commit 8e8c467

Please sign in to comment.