Commit 063e3752 authored by Matt Butcher's avatar Matt Butcher

feat(*): add install/deploy command

parent b5ce6939
package main
import (
"errors"
dep "github.com/deis/helm-dm/deploy"
"github.com/deis/helm-dm/format"
)
func deploy(cfg *dep.Deployment, dry bool) error {
if dry {
format.Error("Not implemented: --dry-run")
}
if cfg.Filename == "" {
return errors.New("A filename must be specified. For a tar archive, this is the name of the root template in the archive.")
}
if err := cfg.Commit(); err != nil {
format.Error("Failed to commit deployment: %s", err)
return err
}
return nil
}
......@@ -4,6 +4,7 @@ import (
"os"
"github.com/codegangsta/cli"
dep "github.com/deis/helm-dm/deploy"
"github.com/deis/helm-dm/format"
)
......@@ -22,7 +23,7 @@ func main() {
func commands() []cli.Command {
return []cli.Command{
{
Name: "install",
Name: "init",
Usage: "Initialize the client and install DM on Kubernetes.",
Description: ``,
Flags: []cli.Flag{
......@@ -33,7 +34,7 @@ func commands() []cli.Command {
},
Action: func(c *cli.Context) {
if err := install(c.Bool("dry-run")); err != nil {
format.Error(err.Error())
format.Error("%s (Run 'helm doctor' for more information)", err)
os.Exit(1)
}
},
......@@ -44,7 +45,7 @@ func commands() []cli.Command {
ArgsUsage: "",
Action: func(c *cli.Context) {
if err := target(c.Bool("dry-run")); err != nil {
format.Error(err.Error())
format.Error("%s (Is the cluster running?)", err)
os.Exit(1)
}
},
......@@ -60,6 +61,65 @@ func commands() []cli.Command {
},
{
Name: "deploy",
Aliases: []string{"install"},
Usage: "Deploy a chart into the cluster.",
Action: func(c *cli.Context) {
args := c.Args()
if len(args) < 1 {
format.Error("First argument, filename, is required. Try 'helm deploy --help'")
os.Exit(1)
}
props, err := parseProperties(c.String("properties"))
if err != nil {
format.Error("Failed to parse properties: %s", err)
os.Exit(1)
}
d := &dep.Deployment{
Name: c.String("Name"),
Properties: props,
Filename: args[0],
Imports: args[1:],
Repository: c.String("repository"),
}
if c.Bool("stdin") {
d.Input = os.Stdin
}
if err := deploy(d, c.Bool("dry-run")); err != nil {
format.Error("%s (Try running 'helm doctor')", err)
os.Exit(1)
}
},
Flags: []cli.Flag{
cli.BoolFlag{
Name: "dry-run",
Usage: "Only display the underlying kubectl commands.",
},
cli.BoolFlag{
Name: "stdin,i",
Usage: "Read a configuration from STDIN.",
},
cli.StringFlag{
Name: "name",
Usage: "Name of deployment, used for deploy and update commands (defaults to template name)",
},
// TODO: I think there is a Generic flag type that we can implement parsing with.
cli.StringFlag{
Name: "properties,p",
Usage: "A comma-separated list of key=value pairs: 'foo=bar,foo2=baz'.",
},
cli.StringFlag{
// FIXME: This is not right. It's sort of a half-baked forward
// port of dm.go.
Name: "repository",
Usage: "The default repository",
Value: "kubernetes/application-dm-templates",
},
},
},
{
Name: "search",
......
package main
import (
"errors"
"strconv"
"strings"
)
// TODO: The concept of property here is really simple. We could definitely get
// better about the values we allow. Also, we need some validation on the names.
var errInvalidProperty = errors.New("property is not in name=value format")
// parseProperties is a utility for parsing a comma-separated key=value string.
func parseProperties(kvstr string) (map[string]interface{}, error) {
properties := map[string]interface{}{}
if len(kvstr) == 0 {
return properties, nil
}
pairs := strings.Split(kvstr, ",")
for _, p := range pairs {
// Allow for "k=v, k=v"
p = strings.TrimSpace(p)
pair := strings.Split(p, "=")
if len(pair) == 1 {
return properties, errInvalidProperty
}
// If the value looks int-like, convert it.
if i, err := strconv.Atoi(pair[1]); err == nil {
properties[pair[0]] = pair[1]
} else {
properties[pair[0]] = i
}
}
return properties, nil
}
package deploy
import (
"archive/tar"
"errors"
"fmt"
"os"
"strings"
"github.com/ghodss/yaml"
"github.com/kubernetes/deployment-manager/common"
"github.com/kubernetes/deployment-manager/expandybird/expander"
"github.com/kubernetes/deployment-manager/registry"
)
type Deployer interface {
Commit() error
}
// Deployment describes a deployment of a package.
type Deployment struct {
// Name is the Deployment name. Autogenerated if empty.
Name string
// Filename is the filename for the base deployment.
Filename string
// Imports is a list of imported files.
Imports []string
// Properties to pass into the template.
Properties map[string]interface{}
// Input is a file containing templates. It may be os.Stdin.
Input *os.File
// Repository is the location of the templates.
Repository string
}
// Commit prepares the Deployment and then commits it to the remote processor.
func (d *Deployment) Commit() error {
tpl, err := d.resolveTemplate()
if err != nil {
return err
}
// If a deployment Name is specified, set that explicitly.
if d.Name != "" {
tpl.Name = d.Name
}
return nil
}
// resolveTemplate resolves what kind of template is being loaded, and then returns the template.
func (d *Deployment) resolveTemplate() (*common.Template, error) {
// If some input has been specified, read it.
if d.Input != nil {
// Assume this is a tar archive.
tpl, err := expander.NewTemplateFromArchive(d.Filename, d.Input, d.Imports)
if err == nil {
return tpl, err
} else if err != tar.ErrHeader {
return nil, err
}
// If we get here, the file is not a tar archive.
if _, err := os.Stdin.Seek(0, 0); err != nil {
return nil, err
}
return expander.NewTemplateFromReader(d.Filename, d.Input, d.Imports)
}
// Non-Stdin case
if len(d.Imports) > 0 {
if t, err := registryType(d.Filename); err != nil {
return expander.NewTemplateFromRootTemplate(d.Filename)
} else {
return buildTemplateFromType(t, d.Repository, d.Properties)
}
}
return expander.NewTemplateFromFileNames(d.Filename, d.Imports)
}
// registryType is a placeholder until registry.ParseType() is merged.
func registryType(name string) (*registry.Type, error) {
tList := strings.Split(name, ":")
if len(tList) != 2 {
return nil, errors.New("No version")
}
tt := registry.Type{Version: tList[1]}
cList := strings.Split(tList[0], "/")
if len(cList) == 1 {
tt.Name = tList[0]
} else {
tt.Collection = cList[0]
tt.Name = cList[1]
}
return &tt, nil
}
// buildTemplateFromType is a straight lift-n-shift from dm.go.
func buildTemplateFromType(t *registry.Type, reg string, props map[string]interface{}) (*common.Template, error) {
// Name the deployment after the type name.
name := fmt.Sprintf("%s:%s", t.Name, t.Version)
git, err := getGitRegistry(reg)
if err != nil {
return nil, err
}
gurls, err := git.GetURLs(*t)
if err != nil {
return nil, err
}
config := common.Configuration{Resources: []*common.Resource{&common.Resource{
Name: name,
Type: gurls[0],
Properties: props,
}}}
y, err := yaml.Marshal(config)
if err != nil {
return nil, fmt.Errorf("error: %s\ncannot create configuration for deployment: %v\n", err, config)
}
return &common.Template{
Name: name,
Content: string(y),
// No imports, as this is a single type from repository.
}, nil
}
func getGitRegistry(reg string) (registry.Registry, error) {
s := strings.SplitN(reg, "/", 3)
if len(s) < 2 {
return nil, fmt.Errorf("invalid template registry: %s", reg)
}
path := ""
if len(s) > 2 {
path = s[3]
}
if s[0] == "helm" {
return registry.NewGithubPackageRegistry(s[0], s[1]), nil
} else {
return registry.NewGithubRegistry(s[0], s[1], path), nil
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment