diff --git a/Makefile b/Makefile index 308737dcb..e80d68faa 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -DOCKER_REGISTRY := -IMAGE_PREFIX ?= helm +DOCKER_REGISTRY ?= gcr.io +IMAGE_PREFIX ?= deis-sandbox SHORT_NAME ?= tiller # go option diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 5679de8e2..062f6d6b4 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -1,22 +1,57 @@ package main import ( + "errors" "fmt" + "github.com/deis/tiller/pkg/client" + "github.com/deis/tiller/pkg/kubectl" "github.com/spf13/cobra" ) +const longDesc = ` +This command installs Tiller (the helm server side component) onto your +Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.helm/) +` + +var tillerImg string + func init() { + initCmd.Flags().StringVarP(&tillerImg, "tiller-image", "i", "", "override tiller image") RootCommand.AddCommand(initCmd) } var initCmd = &cobra.Command{ Use: "init", Short: "Initialize Helm on both client and server.", - Long: `Add long help here`, - Run: runInit, + Long: longDesc, + RunE: RunInit, +} + +// RunInit initializes local config and installs tiller to Kubernetes Cluster +func RunInit(cmd *cobra.Command, args []string) error { + if len(args) != 0 { + return errors.New("This command does not accept arguments. \n") + } + + // TODO: take value of global flag kubectl and pass that in + runner := buildKubectlRunner("") + + i := client.NewInstaller() + i.Tiller["Image"] = tillerImg + + out, err := i.Install(runner) + if err != nil { + return fmt.Errorf("error installing %s %s", string(out), err) + } + + fmt.Printf("Tiller (the helm server side component) has been installed into your Kubernetes Cluster.\n") + return nil } -func runInit(cmd *cobra.Command, args []string) { - fmt.Fprintln(stdout, "Init was called.") +func buildKubectlRunner(kubectlPath string) kubectl.Runner { + if kubectlPath != "" { + kubectl.Path = kubectlPath + } + return &kubectl.RealRunner{} } diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go new file mode 100644 index 000000000..59a45c6ab --- /dev/null +++ b/cmd/helm/init_test.go @@ -0,0 +1,10 @@ +package main + +import ( + "testing" +) + +func TestInit(t *testing.T) { + //TODO: call command and make sure no error is returned + //TODO: check local config +} diff --git a/pkg/client/install.go b/pkg/client/install.go new file mode 100644 index 000000000..1861727e2 --- /dev/null +++ b/pkg/client/install.go @@ -0,0 +1,91 @@ +package client + +import ( + "bytes" + "text/template" + + "github.com/Masterminds/sprig" + "github.com/deis/tiller/pkg/kubectl" +) + +// Installer installs tiller into Kubernetes +// +// See InstallYAML. +type Installer struct { + + // Metadata holds any global metadata attributes for the resources + Metadata map[string]interface{} + + // Tiller specific metadata + Tiller map[string]interface{} +} + +// New Installer creates a new Installer +func NewInstaller() *Installer { + return &Installer{ + Metadata: map[string]interface{}{}, + Tiller: map[string]interface{}{}, + } +} + +// Install uses kubectl to install tiller +// +// Returns the string output received from the operation, and an error if the +// command failed. +func (i *Installer) Install(runner kubectl.Runner) (string, error) { + b, err := i.expand() + if err != nil { + return "", err + } + + o, err := runner.Create(b) + return string(o), err +} + +func (i *Installer) expand() ([]byte, error) { + var b bytes.Buffer + t := template.Must(template.New("manifest").Funcs(sprig.TxtFuncMap()).Parse(InstallYAML)) + err := t.Execute(&b, i) + return b.Bytes(), err +} + +// InstallYAML is the installation YAML for DM. +const InstallYAML = ` +--- +apiVersion: v1 +kind: Namespace +metadata: + labels: + app: helm + name: helm-namespace + name: helm +--- +apiVersion: v1 +kind: ReplicationController +metadata: + labels: + app: helm + name: tiller + name: tiller-rc + namespace: helm +spec: + replicas: 1 + selector: + app: helm + name: tiller + template: + metadata: + labels: + app: helm + name: tiller + spec: + containers: + - env: [] + image: {{default "gcr.io/deis-sandbox/tiller:canary" .Tiller.Image}} + name: tiller + ports: + - containerPort: 8080 + name: tiller + imagePullPolicy: Always +--- +` diff --git a/pkg/kubectl/command.go b/pkg/kubectl/command.go new file mode 100644 index 000000000..b36e0ad33 --- /dev/null +++ b/pkg/kubectl/command.go @@ -0,0 +1,32 @@ +package kubectl + +import ( + "bytes" + "fmt" + "io/ioutil" + "os/exec" + "strings" +) + +type cmd struct { + *exec.Cmd +} + +func command(args ...string) *cmd { + return &cmd{exec.Command(Path, args...)} +} + +func assignStdin(cmd *cmd, in []byte) { + cmd.Stdin = bytes.NewBuffer(in) +} + +func (c *cmd) String() string { + var stdin string + + if c.Stdin != nil { + b, _ := ioutil.ReadAll(c.Stdin) + stdin = fmt.Sprintf("< %s", string(b)) + } + + return fmt.Sprintf("[CMD] %s %s", strings.Join(c.Args, " "), stdin) +} diff --git a/pkg/kubectl/create.go b/pkg/kubectl/create.go new file mode 100644 index 000000000..af9297aa9 --- /dev/null +++ b/pkg/kubectl/create.go @@ -0,0 +1,21 @@ +package kubectl + +// Create uploads a chart to Kubernetes +func (r RealRunner) Create(stdin []byte) ([]byte, error) { + args := []string{"create", "-f", "-"} + + cmd := command(args...) + assignStdin(cmd, stdin) + + return cmd.CombinedOutput() +} + +// Create returns the commands to kubectl +func (r PrintRunner) Create(stdin []byte) ([]byte, error) { + args := []string{"create", "-f", "-"} + + cmd := command(args...) + assignStdin(cmd, stdin) + + return []byte(cmd.String()), nil +} diff --git a/pkg/kubectl/create_test.go b/pkg/kubectl/create_test.go new file mode 100644 index 000000000..bca94f6e5 --- /dev/null +++ b/pkg/kubectl/create_test.go @@ -0,0 +1,22 @@ +package kubectl + +import ( + "testing" +) + +func TestPrintCreate(t *testing.T) { + var client Runner = PrintRunner{} + + expected := `[CMD] kubectl create -f - < some stdin data` + + out, err := client.Create([]byte("some stdin data")) + if err != nil { + t.Error(err) + } + + actual := string(out) + + if expected != actual { + t.Fatalf("actual %s != expected %s", actual, expected) + } +} diff --git a/pkg/kubectl/kubectl.go b/pkg/kubectl/kubectl.go new file mode 100644 index 000000000..e527b71fe --- /dev/null +++ b/pkg/kubectl/kubectl.go @@ -0,0 +1,19 @@ +package kubectl + +// Path is the path of the kubectl binary +var Path = "kubectl" + +// Runner is an interface to wrap kubectl convenience methods +type Runner interface { + // Create uploads a chart to Kubernetes + Create(stdin []byte) ([]byte, error) +} + +// RealRunner implements Runner to execute kubectl commands +type RealRunner struct{} + +// PrintRunner implements Runner to return a []byte of the command to be executed +type PrintRunner struct{} + +// Client stores the instance of Runner +var Client Runner = RealRunner{} diff --git a/pkg/kubectl/kubectl_test.go b/pkg/kubectl/kubectl_test.go new file mode 100644 index 000000000..c3348bba5 --- /dev/null +++ b/pkg/kubectl/kubectl_test.go @@ -0,0 +1,12 @@ +package kubectl + +type TestRunner struct { + Runner + + out []byte + err error +} + +func (r TestRunner) Create(stdin []byte, ns string) ([]byte, error) { + return r.out, r.err +} diff --git a/versioning.mk b/versioning.mk index 2faa2ec6f..8d96c8629 100644 --- a/versioning.mk +++ b/versioning.mk @@ -1,8 +1,8 @@ MUTABLE_VERSION ?= canary VERSION ?= git-$(shell git rev-parse --short HEAD) -IMAGE := ${DOCKER_REGISTRY}${IMAGE_PREFIX}/${SHORT_NAME}:${VERSION} -MUTABLE_IMAGE := ${DOCKER_REGISTRY}${IMAGE_PREFIX}/${SHORT_NAME}:${MUTABLE_VERSION} +IMAGE := ${DOCKER_REGISTRY}/${IMAGE_PREFIX}/${SHORT_NAME}:${VERSION} +MUTABLE_IMAGE := ${DOCKER_REGISTRY}/${IMAGE_PREFIX}/${SHORT_NAME}:${MUTABLE_VERSION} info: @echo "Build tag: ${VERSION}"