mirror of https://github.com/helm/helm
commit
8876c7e54b
@ -1,4 +1,4 @@
|
||||
*.pyc
|
||||
.project
|
||||
vendor/*
|
||||
/bin
|
||||
/vendor/*
|
||||
|
@ -1,12 +1,26 @@
|
||||
sudo: true
|
||||
|
||||
env:
|
||||
- GO15VENDOREXPERIMENT=1 GLIDE_VERSION="0.9.1"
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.4
|
||||
- 1.6
|
||||
|
||||
install:
|
||||
- sudo pip install -r expandybird/requirements.txt
|
||||
|
||||
script:
|
||||
- make setup-gotools test
|
||||
- make bootstrap test
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^v?(?:[0-9]+\.){2}[0-9]+.*$/
|
||||
|
||||
install:
|
||||
- wget "https://github.com/Masterminds/glide/releases/download/$GLIDE_VERSION/glide-$GLIDE_VERSION-linux-amd64.tar.gz"
|
||||
- mkdir -p $HOME/bin
|
||||
- tar -vxz -C $HOME/bin --strip=1 -f glide-$GLIDE_VERSION-linux-amd64.tar.gz
|
||||
- export PATH="$HOME/bin:$PATH" GLIDE_HOME="$HOME/.glide"
|
||||
|
@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(chartCommands())
|
||||
}
|
||||
|
||||
func chartCommands() cli.Command {
|
||||
return cli.Command{
|
||||
// Names following form prescribed here: http://is.gd/QUSEOF
|
||||
Name: "chart",
|
||||
Usage: "Perform chart-centered operations.",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "config",
|
||||
Usage: "Create a configuration parameters file for this chart.",
|
||||
ArgsUsage: "CHART",
|
||||
},
|
||||
{
|
||||
Name: "show",
|
||||
Aliases: []string{"info"},
|
||||
Usage: "Provide details about this package.",
|
||||
ArgsUsage: "CHART",
|
||||
},
|
||||
{
|
||||
Name: "scaffold",
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "list all deployed charts, optionally constraining by pattern.",
|
||||
ArgsUsage: "[PATTERN]",
|
||||
},
|
||||
{
|
||||
Name: "deployments",
|
||||
Usage: "given a chart, show all the deployments that reference it.",
|
||||
ArgsUsage: "CHART",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/aokoli/goutils"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/kubernetes/deployment-manager/pkg/chart"
|
||||
"github.com/kubernetes/deployment-manager/pkg/format"
|
||||
)
|
||||
|
||||
func uploadChart(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if len(args) < 1 {
|
||||
format.Err("First argument, filename, is required. Try 'helm deploy --help'")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cname := c.String("name")
|
||||
fname := args[0]
|
||||
|
||||
if fname == "" {
|
||||
return errors.New("A filename must be specified. For a tar archive, this is the name of the root template in the archive.")
|
||||
}
|
||||
|
||||
_, err := doUpload(fname, cname, c)
|
||||
return err
|
||||
}
|
||||
func doUpload(filename, cname string, cxt *cli.Context) (string, error) {
|
||||
|
||||
fi, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
format.Info("Chart is directory")
|
||||
c, err := chart.LoadDir(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if cname == "" {
|
||||
cname = genName(c.Chartfile().Name)
|
||||
}
|
||||
|
||||
// TODO: Is it better to generate the file in temp dir like this, or
|
||||
// just put it in the CWD?
|
||||
//tdir, err := ioutil.TempDir("", "helm-")
|
||||
//if err != nil {
|
||||
//format.Warn("Could not create temporary directory. Using .")
|
||||
//tdir = "."
|
||||
//} else {
|
||||
//defer os.RemoveAll(tdir)
|
||||
//}
|
||||
tdir := "."
|
||||
tfile, err := chart.Save(c, tdir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
filename = tfile
|
||||
} else if cname == "" {
|
||||
n, _, e := parseTarName(filename)
|
||||
if e != nil {
|
||||
return "", e
|
||||
}
|
||||
cname = n
|
||||
}
|
||||
|
||||
// TODO: Add a version build metadata on the chart.
|
||||
|
||||
if cxt.Bool("dry-run") {
|
||||
format.Info("Prepared deploy %q using file %q", cname, filename)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
c := client(cxt)
|
||||
return c.PostChart(filename, cname)
|
||||
}
|
||||
|
||||
func genName(pname string) string {
|
||||
s, _ := goutils.RandomAlphaNumeric(8)
|
||||
return fmt.Sprintf("%s-%s", pname, s)
|
||||
}
|
||||
|
||||
func parseTarName(name string) (string, string, error) {
|
||||
tnregexp := regexp.MustCompile(chart.TarNameRegex)
|
||||
if strings.HasSuffix(name, ".tgz") {
|
||||
name = strings.TrimSuffix(name, ".tgz")
|
||||
}
|
||||
v := tnregexp.FindStringSubmatch(name)
|
||||
if v == nil {
|
||||
return name, "", fmt.Errorf("invalid name %s", name)
|
||||
}
|
||||
return v[1], v[2], nil
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/kubernetes/deployment-manager/pkg/chart"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(createCmd())
|
||||
}
|
||||
|
||||
func createCmd() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create a new local chart for editing.",
|
||||
Action: func(c *cli.Context) { run(c, create) },
|
||||
ArgsUsage: "NAME",
|
||||
}
|
||||
}
|
||||
|
||||
func create(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if len(args) < 1 {
|
||||
return errors.New("'helm create' requires a chart name as an argument")
|
||||
}
|
||||
|
||||
dir, name := filepath.Split(args[0])
|
||||
|
||||
cf := &chart.Chartfile{
|
||||
Name: name,
|
||||
Description: "Created by Helm",
|
||||
Version: "0.1.0",
|
||||
}
|
||||
|
||||
_, err := chart.Create(cf, dir)
|
||||
return err
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(credCommands())
|
||||
}
|
||||
|
||||
func credCommands() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "credential",
|
||||
Aliases: []string{"cred"},
|
||||
Usage: "Perform repository credential operations.",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "add",
|
||||
Usage: "Add a credential to the remote manager.",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "file,f",
|
||||
Usage: "A JSON file with credential information.",
|
||||
},
|
||||
},
|
||||
ArgsUsage: "CREDENTIAL",
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "List the credentials on the remote manager.",
|
||||
ArgsUsage: "",
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Aliases: []string{"rm"},
|
||||
Usage: "Remove a credential from the remote manager.",
|
||||
ArgsUsage: "CREDENTIAL",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/kubernetes/deployment-manager/pkg/format"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(deleteCmd())
|
||||
}
|
||||
|
||||
func deleteCmd() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Deletes the supplied deployment",
|
||||
ArgsUsage: "DEPLOYMENT",
|
||||
Action: func(c *cli.Context) { run(c, deleteDeployment) },
|
||||
}
|
||||
}
|
||||
|
||||
func deleteDeployment(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if len(args) < 1 {
|
||||
return errors.New("First argument, deployment name, is required. Try 'helm get --help'")
|
||||
}
|
||||
name := args[0]
|
||||
deployment, err := client(c).DeleteDeployment(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return format.YAML(deployment)
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/kubernetes/deployment-manager/pkg/common"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(deployCmd())
|
||||
}
|
||||
|
||||
func deployCmd() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "deploy",
|
||||
Usage: "Deploy a chart into the cluster.",
|
||||
ArgsUsage: "[CHART]",
|
||||
Action: func(c *cli.Context) { run(c, deploy) },
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "config,c",
|
||||
Usage: "The configuration YAML file for this deployment.",
|
||||
},
|
||||
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'.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func deploy(c *cli.Context) error {
|
||||
|
||||
// If there is a configuration file, use it.
|
||||
cfg := &common.Configuration{}
|
||||
if c.String("config") != "" {
|
||||
if err := loadConfig(cfg, c.String("config")); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
cfg.Resources = []*common.Resource{
|
||||
{
|
||||
Properties: map[string]interface{}{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a chart specified on the commandline, override the config
|
||||
// file with it.
|
||||
args := c.Args()
|
||||
if len(args) > 0 {
|
||||
cname := args[0]
|
||||
if isLocalChart(cname) {
|
||||
// If we get here, we need to first package then upload the chart.
|
||||
loc, err := doUpload(cname, "", c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.Resources[0].Name = loc
|
||||
} else {
|
||||
cfg.Resources[0].Type = cname
|
||||
}
|
||||
}
|
||||
|
||||
// Override the name if one is passed in.
|
||||
if name := c.String("name"); len(name) > 0 {
|
||||
cfg.Resources[0].Name = name
|
||||
}
|
||||
|
||||
if props, err := parseProperties(c.String("properties")); err != nil {
|
||||
return err
|
||||
} else if len(props) > 0 {
|
||||
// Coalesce the properties into the first props. We have no way of
|
||||
// knowing which resource the properties are supposed to be part
|
||||
// of.
|
||||
for n, v := range props {
|
||||
cfg.Resources[0].Properties[n] = v
|
||||
}
|
||||
}
|
||||
|
||||
return client(c).PostDeployment(cfg)
|
||||
}
|
||||
|
||||
// isLocalChart returns true if the given path can be statted.
|
||||
func isLocalChart(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// loadConfig loads a file into a common.Configuration.
|
||||
func loadConfig(c *common.Configuration, filename string) error {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return yaml.Unmarshal(data, c)
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(deploymentCommands())
|
||||
}
|
||||
|
||||
func deploymentCommands() cli.Command {
|
||||
return cli.Command{
|
||||
// Names following form prescribed here: http://is.gd/QUSEOF
|
||||
Name: "deployment",
|
||||
Usage: "Perform deployment-centered operations.",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "config",
|
||||
Usage: "Dump the configuration file for this deployment.",
|
||||
ArgsUsage: "DEPLOYMENT",
|
||||
},
|
||||
{
|
||||
Name: "manifest",
|
||||
Usage: "Dump the Kubernetes manifest file for this deployment.",
|
||||
ArgsUsage: "DEPLOYMENT",
|
||||
},
|
||||
{
|
||||
Name: "show",
|
||||
Aliases: []string{"info"},
|
||||
Usage: "Provide details about this deployment.",
|
||||
ArgsUsage: "",
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "list all deployments, or filter by an optional pattern",
|
||||
ArgsUsage: "PATTERN",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/kubernetes/deployment-manager/pkg/dm"
|
||||
"github.com/kubernetes/deployment-manager/pkg/format"
|
||||
"github.com/kubernetes/deployment-manager/pkg/kubectl"
|
||||
)
|
||||
|
||||
// ErrAlreadyInstalled indicates that DM is already installed.
|
||||
var ErrAlreadyInstalled = errors.New("Already Installed")
|
||||
|
||||
func init() {
|
||||
addCommands(dmCmd())
|
||||
}
|
||||
|
||||
func dmCmd() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "dm",
|
||||
Usage: "Manage DM on Kubernetes",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "install",
|
||||
Usage: "Install DM on Kubernetes.",
|
||||
ArgsUsage: "",
|
||||
Description: ``,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "dry-run",
|
||||
Usage: "Show what would be installed, but don't install anything.",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) {
|
||||
if err := install(c.Bool("dry-run")); err != nil {
|
||||
format.Err("%s (Run 'helm doctor' for more information)", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "uninstall",
|
||||
Usage: "Uninstall the DM from Kubernetes.",
|
||||
ArgsUsage: "",
|
||||
Description: ``,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "dry-run",
|
||||
Usage: "Show what would be installed, but don't install anything.",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) {
|
||||
if err := uninstall(c.Bool("dry-run")); err != nil {
|
||||
format.Err("%s (Run 'helm doctor' for more information)", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "status",
|
||||
Usage: "Show status of DM.",
|
||||
ArgsUsage: "",
|
||||
Action: func(c *cli.Context) {
|
||||
format.Err("Not yet implemented")
|
||||
os.Exit(1)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "target",
|
||||
Usage: "Displays information about cluster.",
|
||||
ArgsUsage: "",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := target(c.Bool("dry-run")); err != nil {
|
||||
format.Err("%s (Is the cluster running?)", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "dry-run",
|
||||
Usage: "Only display the underlying kubectl commands.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func install(dryRun bool) error {
|
||||
runner := getKubectlRunner(dryRun)
|
||||
|
||||
out, err := dm.Install(runner)
|
||||
if err != nil {
|
||||
format.Err("Error installing: %s %s", out, err)
|
||||
}
|
||||
format.Msg(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
func uninstall(dryRun bool) error {
|
||||
runner := getKubectlRunner(dryRun)
|
||||
|
||||
out, err := dm.Uninstall(runner)
|
||||
if err != nil {
|
||||
format.Err("Error uninstalling: %s %s", out, err)
|
||||
}
|
||||
format.Msg(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getKubectlRunner(dryRun bool) kubectl.Runner {
|
||||
if dryRun {
|
||||
return &kubectl.PrintRunner{}
|
||||
}
|
||||
return &kubectl.RealRunner{}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/kubernetes/deployment-manager/pkg/dm"
|
||||
"github.com/kubernetes/deployment-manager/pkg/format"
|
||||
"github.com/kubernetes/deployment-manager/pkg/kubectl"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(doctorCmd())
|
||||
}
|
||||
|
||||
func doctorCmd() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "doctor",
|
||||
Usage: "Run a series of checks for necessary prerequisites.",
|
||||
ArgsUsage: "",
|
||||
Action: func(c *cli.Context) { run(c, doctor) },
|
||||
}
|
||||
}
|
||||
|
||||
func doctor(c *cli.Context) error {
|
||||
var runner kubectl.Runner
|
||||
runner = &kubectl.RealRunner{}
|
||||
if dm.IsInstalled(runner) {
|
||||
format.Success("You have everything you need. Go forth my friend!")
|
||||
} else {
|
||||
format.Warning("Looks like you don't have DM installed.\nRun: `helm install`")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/kubernetes/deployment-manager/pkg/format"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(getCmd())
|
||||
}
|
||||
|
||||
func getCmd() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "get",
|
||||
ArgsUsage: "DEPLOYMENT",
|
||||
Usage: "Retrieves the supplied deployment",
|
||||
Action: func(c *cli.Context) { run(c, get) },
|
||||
}
|
||||
}
|
||||
|
||||
func get(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
if len(args) < 1 {
|
||||
return errors.New("First argument, deployment name, is required. Try 'helm get --help'")
|
||||
}
|
||||
name := args[0]
|
||||
deployment, err := client(c).GetDeployment(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return format.YAML(deployment)
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/kubernetes/deployment-manager/pkg/dm"
|
||||
"github.com/kubernetes/deployment-manager/pkg/format"
|
||||
)
|
||||
|
||||
var version = "0.0.1"
|
||||
|
||||
var commands []cli.Command
|
||||
|
||||
func init() {
|
||||
addCommands(cmds()...)
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "helm"
|
||||
app.Version = version
|
||||
app.Usage = `Deploy and manage packages.`
|
||||
app.Commands = commands
|
||||
|
||||
// TODO: make better
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "host,u",
|
||||
Usage: "The URL of the DM server.",
|
||||
EnvVar: "HELM_HOST",
|
||||
Value: "https://localhost:8000/",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "timeout",
|
||||
Usage: "Time in seconds to wait for response",
|
||||
Value: 10,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Usage: "Enable verbose debugging output",
|
||||
},
|
||||
}
|
||||
app.Run(os.Args)
|
||||
}
|
||||
|
||||
func cmds() []cli.Command {
|
||||
return []cli.Command{
|
||||
{
|
||||
Name: "init",
|
||||
Usage: "Initialize the client and install DM on Kubernetes.",
|
||||
Description: ``,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "dry-run",
|
||||
Usage: "Show what would be installed, but don't install anything.",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) {
|
||||
if err := install(c.Bool("dry-run")); err != nil {
|
||||
format.Err("%s (Run 'helm doctor' for more information)", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "search",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func addCommands(cmds ...cli.Command) {
|
||||
commands = append(commands, cmds...)
|
||||
}
|
||||
|
||||
func run(c *cli.Context, f func(c *cli.Context) error) {
|
||||
if err := f(c); err != nil {
|
||||
os.Stderr.Write([]byte(err.Error()))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func client(c *cli.Context) *dm.Client {
|
||||
host := c.GlobalString("host")
|
||||
debug := c.GlobalBool("debug")
|
||||
timeout := c.GlobalInt("timeout")
|
||||
return dm.NewClient(host).SetDebug(debug).SetTimeout(timeout)
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(lintCmd())
|
||||
}
|
||||
|
||||
func lintCmd() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "lint",
|
||||
Usage: "Evaluate a chart's conformance to the specification.",
|
||||
ArgsUsage: "PATH [PATH...]",
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/kubernetes/deployment-manager/pkg/format"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(listCmd())
|
||||
}
|
||||
|
||||
func listCmd() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "list",
|
||||
Usage: "Lists the deployments in the cluster",
|
||||
Action: func(c *cli.Context) { run(c, list) },
|
||||
}
|
||||
}
|
||||
|
||||
func list(c *cli.Context) error {
|
||||
list, err := client(c).ListDeployments()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return format.YAML(list)
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/kubernetes/deployment-manager/pkg/chart"
|
||||
"github.com/kubernetes/deployment-manager/pkg/format"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(packageCmd())
|
||||
}
|
||||
|
||||
func packageCmd() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "package",
|
||||
Aliases: []string{"pack"},
|
||||
Usage: "Given a chart directory, package it into a release.",
|
||||
ArgsUsage: "PATH",
|
||||
Action: func(c *cli.Context) { run(c, pack) },
|
||||
}
|
||||
}
|
||||
|
||||
func pack(cxt *cli.Context) error {
|
||||
args := cxt.Args()
|
||||
if len(args) < 1 {
|
||||
return errors.New("'helm package' requires a path to a chart directory as an argument")
|
||||
}
|
||||
|
||||
dir := args[0]
|
||||
if fi, err := os.Stat(dir); err != nil {
|
||||
return fmt.Errorf("Could not find directory %s: %s", dir, err)
|
||||
} else if !fi.IsDir() {
|
||||
return fmt.Errorf("Not a directory: %s", dir)
|
||||
}
|
||||
|
||||
c, err := chart.LoadDir(dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to load %s: %s", dir, err)
|
||||
}
|
||||
|
||||
fname, err := chart.Save(c, ".")
|
||||
format.Msg(fname)
|
||||
return nil
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
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
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(redeployCommand())
|
||||
}
|
||||
|
||||
func redeployCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "redeploy",
|
||||
Usage: "update an existing deployment with a new configuration.",
|
||||
ArgsUsage: "DEPLOYMENT",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "config,f",
|
||||
Usage: "Configuration values file.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(releaseCmd())
|
||||
}
|
||||
|
||||
func releaseCmd() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "release",
|
||||
Usage: "Release a chart to a remote chart repository.",
|
||||
ArgsUsage: "PATH",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "destination,u",
|
||||
Usage: "Destination URL to which this will be POSTed.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(repoCommands())
|
||||
}
|
||||
|
||||
func repoCommands() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "repository",
|
||||
Aliases: []string{"repo"},
|
||||
Usage: "Perform repository operations.",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "add",
|
||||
Usage: "Add a repository to the remote manager.",
|
||||
ArgsUsage: "REPOSITORY",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "cred",
|
||||
Usage: "The name of the credential.",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "show",
|
||||
Usage: "Show the repository details for a given repository.",
|
||||
ArgsUsage: "REPOSITORY",
|
||||
},
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "List the repositories on the remote manager.",
|
||||
ArgsUsage: "",
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Aliases: []string{"rm"},
|
||||
Usage: "Remove a repository from the remote manager.",
|
||||
ArgsUsage: "REPOSITORY",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func init() {
|
||||
addCommands(statusCommand())
|
||||
}
|
||||
|
||||
func statusCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "status",
|
||||
Usage: "Provide status on a named deployment.",
|
||||
ArgsUsage: "DEPLOYMENT",
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kubernetes/deployment-manager/pkg/format"
|
||||
"github.com/kubernetes/deployment-manager/pkg/kubectl"
|
||||
)
|
||||
|
||||
func target(dryRun bool) error {
|
||||
client := kubectl.Client
|
||||
if dryRun {
|
||||
client = kubectl.PrintRunner{}
|
||||
}
|
||||
out, err := client.ClusterInfo()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s (%s)", out, err)
|
||||
}
|
||||
format.Msg(string(out))
|
||||
return nil
|
||||
}
|
@ -1,51 +1,78 @@
|
||||
hash: db55a031aaa2f352fa5e9e4fda871039afb80e383a57fc77e4b35114d47cca8a
|
||||
updated: 2016-01-26T17:30:54.243252416-07:00
|
||||
hash: 2d8e32786782b7979a79850cfc489866a74c068e865f433a73ed4f50ef2644e9
|
||||
updated: 2016-02-29T11:21:24.093936684-08:00
|
||||
imports:
|
||||
- name: github.com/aokoli/goutils
|
||||
version: 9c37978a95bd5c709a15883b6242714ea6709e64
|
||||
- name: github.com/codegangsta/cli
|
||||
version: a2943485b110df8842045ae0600047f88a3a56a1
|
||||
- name: github.com/emicklei/go-restful
|
||||
version: b86acf97a74ed7603ac78d012f5535b4d587b156
|
||||
subpackages:
|
||||
- log
|
||||
- name: github.com/ghodss/yaml
|
||||
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
|
||||
- name: github.com/golang/glog
|
||||
version: 23def4e6c14b4da8ac2ed8007337bc5eb5007998
|
||||
- name: github.com/golang/protobuf
|
||||
version: 6aaa8d47701fa6cf07e914ec01fde3d4a1fe79c3
|
||||
subpackages:
|
||||
- proto
|
||||
- name: github.com/google/go-github
|
||||
version: b8b4ac742977310ff6e75140a403a38dab109977
|
||||
subpackages:
|
||||
- /github
|
||||
- github
|
||||
- name: github.com/google/go-querystring
|
||||
version: 2a60fc2ba6c19de80291203597d752e9ba58e4c0
|
||||
subpackages:
|
||||
- query
|
||||
- name: github.com/gorilla/context
|
||||
version: 1c83b3eabd45b6d76072b66b746c20815fb2872d
|
||||
- name: github.com/gorilla/handlers
|
||||
version: 8f2758070a82adb7a3ad6b223a0b91878f32d400
|
||||
- name: github.com/gorilla/mux
|
||||
version: 26a6070f849969ba72b72256e9f14cf519751690
|
||||
- name: github.com/gorilla/schema
|
||||
version: 14c555599c2a4f493c1e13fd1ea6fdf721739028
|
||||
- name: github.com/Masterminds/semver
|
||||
version: c4f7ef0702f269161a60489ccbbc9f1241ad1265
|
||||
- name: github.com/mjibson/appstats
|
||||
version: 0542d5f0e87ea3a8fa4174322b9532f5d04f9fa8
|
||||
- name: golang.org/x/crypto
|
||||
version: 1f22c0103821b9390939b6776727195525381532
|
||||
- name: golang.org/x/net
|
||||
version: 04b9de9b512f58addf28c9853d50ebef61c3953e
|
||||
subpackages:
|
||||
- context
|
||||
- context/ctxhttp
|
||||
- name: golang.org/x/oauth2
|
||||
version: 8a57ed94ffd43444c0879fe75701732a38afc985
|
||||
- name: golang.org/x/text
|
||||
version: 6d3c22c4525a4da167968fa2479be5524d2e8bd0
|
||||
- name: google.golang.com/appengine
|
||||
version: ""
|
||||
repo: https://google.golang.com/appengine
|
||||
subpackages:
|
||||
- google
|
||||
- internal
|
||||
- jws
|
||||
- jwt
|
||||
- name: google.golang.org/api
|
||||
version: 0caa37974a5f5ae67172acf68b4970f7864f994c
|
||||
subpackages:
|
||||
- storage/v1
|
||||
- gensupport
|
||||
- googleapi
|
||||
- googleapi/internal/uritemplates
|
||||
- name: google.golang.org/appengine
|
||||
version: 6bde959377a90acb53366051d7d587bfd7171354
|
||||
subpackages:
|
||||
- urlfetch
|
||||
- internal
|
||||
- internal/urlfetch
|
||||
- internal/app_identity
|
||||
- internal/modules
|
||||
- internal/base
|
||||
- internal/datastore
|
||||
- internal/log
|
||||
- internal/remote_api
|
||||
- name: google.golang.org/cloud
|
||||
version: fb10e8da373d97f6ba5e648299a10b3b91f14cd5
|
||||
- name: google.golang.org/grpc
|
||||
version: e29d659177655e589850ba7d3d83f7ce12ef23dd
|
||||
subpackages:
|
||||
- compute/metadata
|
||||
- internal
|
||||
- name: gopkg.in/mgo.v2
|
||||
version: d90005c5262a3463800497ea5a89aed5fe22c886
|
||||
subpackages:
|
||||
- bson
|
||||
- internal/sasl
|
||||
- internal/scram
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: f7716cbe52baa25d2e9b0d0da546fcf909fc16b4
|
||||
devImports: []
|
||||
|
@ -0,0 +1,270 @@
|
||||
package dm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
fancypath "path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kubernetes/deployment-manager/pkg/common"
|
||||
)
|
||||
|
||||
// The default HTTP timeout
|
||||
var DefaultHTTPTimeout = time.Second * 10
|
||||
|
||||
// The default HTTP Protocol
|
||||
var DefaultHTTPProtocol = "http"
|
||||
|
||||
// Client is a DM client.
|
||||
type Client struct {
|
||||
// Timeout on HTTP connections.
|
||||
HTTPTimeout time.Duration
|
||||
// The remote host
|
||||
Host string
|
||||
// The protocol. Currently only http and https are supported.
|
||||
Protocol string
|
||||
// Transport
|
||||
Transport http.RoundTripper
|
||||
// Debug enables http logging
|
||||
Debug bool
|
||||
|
||||
// Base URL for remote service
|
||||
baseURL *url.URL
|
||||
}
|
||||
|
||||
// NewClient creates a new DM client. Host name is required.
|
||||
func NewClient(host string) *Client {
|
||||
url, _ := DefaultServerURL(host)
|
||||
|
||||
return &Client{
|
||||
HTTPTimeout: DefaultHTTPTimeout,
|
||||
baseURL: url,
|
||||
Transport: http.DefaultTransport,
|
||||
}
|
||||
}
|
||||
|
||||
// SetDebug enables debug mode which logs http
|
||||
func (c *Client) SetDebug(enable bool) *Client {
|
||||
c.Debug = enable
|
||||
return c
|
||||
}
|
||||
|
||||
// transport wraps client transport if debug is enabled
|
||||
func (c *Client) transport() http.RoundTripper {
|
||||
if c.Debug {
|
||||
return NewDebugTransport(c.Transport)
|
||||
}
|
||||
return c.Transport
|
||||
}
|
||||
|
||||
// SetTransport sets a custom Transport. Defaults to http.DefaultTransport
|
||||
func (c *Client) SetTransport(tr http.RoundTripper) *Client {
|
||||
c.Transport = tr
|
||||
return c
|
||||
}
|
||||
|
||||
// SetTimeout sets a timeout for http connections
|
||||
func (c *Client) SetTimeout(seconds int) *Client {
|
||||
c.HTTPTimeout = time.Duration(time.Duration(seconds) * time.Second)
|
||||
return c
|
||||
}
|
||||
|
||||
// url constructs the URL.
|
||||
func (c *Client) url(rawurl string) (string, error) {
|
||||
u, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return c.baseURL.ResolveReference(u).String(), nil
|
||||
}
|
||||
|
||||
func (c *Client) agent() string {
|
||||
return fmt.Sprintf("helm/%s", "0.0.1")
|
||||
}
|
||||
|
||||
// CallService is a low-level function for making an API call.
|
||||
//
|
||||
// This calls the service and then unmarshals the returned data into dest.
|
||||
func (c *Client) CallService(path, method, action string, dest interface{}, reader io.ReadCloser) error {
|
||||
u, err := c.url(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := c.callHTTP(u, method, action, reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(resp), dest); err != nil {
|
||||
return fmt.Errorf("Failed to parse JSON response from service: %s", resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// callHTTP is a low-level primitive for executing HTTP operations.
|
||||
func (c *Client) callHTTP(path, method, action string, reader io.ReadCloser) (string, error) {
|
||||
request, err := http.NewRequest(method, path, reader)
|
||||
|
||||
// TODO: dynamically set version
|
||||
request.Header.Set("User-Agent", c.agent())
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: c.HTTPTimeout,
|
||||
Transport: c.transport(),
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s := response.StatusCode
|
||||
if s < http.StatusOK || s >= http.StatusMultipleChoices {
|
||||
return "", &HTTPError{StatusCode: s, Message: string(body), URL: request.URL}
|
||||
}
|
||||
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
// DefaultServerURL converts a host, host:port, or URL string to the default base server API path
|
||||
// to use with a Client
|
||||
func DefaultServerURL(host string) (*url.URL, error) {
|
||||
if host == "" {
|
||||
return nil, fmt.Errorf("host must be a URL or a host:port pair")
|
||||
}
|
||||
base := host
|
||||
hostURL, err := url.Parse(base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hostURL.Scheme == "" {
|
||||
hostURL, err = url.Parse(DefaultHTTPProtocol + "://" + base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(hostURL.Path) > 0 && !strings.HasSuffix(hostURL.Path, "/") {
|
||||
hostURL.Path = hostURL.Path + "/"
|
||||
}
|
||||
|
||||
return hostURL, nil
|
||||
}
|
||||
|
||||
// ListDeployments lists the deployments in DM.
|
||||
func (c *Client) ListDeployments() ([]string, error) {
|
||||
var l []string
|
||||
if err := c.CallService("deployments", "GET", "list deployments", &l, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// PostChart sends a chart to DM for deploying.
|
||||
//
|
||||
// This returns the location for the new chart, typically of the form
|
||||
// `helm:repo/bucket/name-version.tgz`.
|
||||
func (c *Client) PostChart(filename, deployname string) (string, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
u, err := c.url("/v2/charts")
|
||||
request, err := http.NewRequest("POST", u, f)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return "", err
|
||||
}
|
||||
|
||||
// There is an argument to be made for using the legacy x-octet-stream for
|
||||
// this. But since we control both sides, we should use the standard one.
|
||||
// Also, gzip (x-compress) is usually treated as a content encoding. In this
|
||||
// case it probably is not, but it makes more sense to follow the standard,
|
||||
// even though we don't assume the remote server will strip it off.
|
||||
request.Header.Add("Content-Type", "application/x-tar")
|
||||
request.Header.Add("Content-Encoding", "gzip")
|
||||
request.Header.Add("X-Deployment-Name", deployname)
|
||||
request.Header.Add("X-Chart-Name", filepath.Base(filename))
|
||||
request.Header.Set("User-Agent", c.agent())
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: c.HTTPTimeout,
|
||||
Transport: c.transport(),
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// We only want 201 CREATED. Admittedly, we could accept 200 and 202.
|
||||
if response.StatusCode != http.StatusCreated {
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
response.Body.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", &HTTPError{StatusCode: response.StatusCode, Message: string(body), URL: request.URL}
|
||||
}
|
||||
|
||||
loc := response.Header.Get("Location")
|
||||
return loc, nil
|
||||
}
|
||||
|
||||
// HTTPError is an error caused by an unexpected HTTP status code.
|
||||
//
|
||||
// The StatusCode will not necessarily be a 4xx or 5xx. Any unexpected code
|
||||
// may be returned.
|
||||
type HTTPError struct {
|
||||
StatusCode int
|
||||
Message string
|
||||
URL *url.URL
|
||||
}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e *HTTPError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// String implmenets the io.Stringer interface.
|
||||
func (e *HTTPError) String() string {
|
||||
return e.Error()
|
||||
}
|
||||
|
||||
// GetDeployment retrieves the supplied deployment
|
||||
func (c *Client) GetDeployment(name string) (*common.Deployment, error) {
|
||||
var deployment *common.Deployment
|
||||
if err := c.CallService(fancypath.Join("deployments", name), "GET", "get deployment", &deployment, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return deployment, nil
|
||||
}
|
||||
|
||||
// DeleteDeployment deletes the supplied deployment
|
||||
func (c *Client) DeleteDeployment(name string) (*common.Deployment, error) {
|
||||
var deployment *common.Deployment
|
||||
if err := c.CallService(filepath.Join("deployments", name), "DELETE", "delete deployment", &deployment, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return deployment, nil
|
||||
}
|
||||
|
||||
// PostDeployment posts a deployment objec to the manager service.
|
||||
func (c *Client) PostDeployment(cfg *common.Configuration) error {
|
||||
return c.CallService("/deployments", "POST", "post deployment", cfg, nil)
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
package dm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kubernetes/deployment-manager/pkg/common"
|
||||
)
|
||||
|
||||
func TestDefaultServerURL(t *testing.T) {
|
||||
tt := []struct {
|
||||
host string
|
||||
url string
|
||||
}{
|
||||
{"127.0.0.1", "http://127.0.0.1"},
|
||||
{"127.0.0.1:8080", "http://127.0.0.1:8080"},
|
||||
{"foo.bar.com", "http://foo.bar.com"},
|
||||
{"foo.bar.com/prefix", "http://foo.bar.com/prefix/"},
|
||||
{"http://host/prefix", "http://host/prefix/"},
|
||||
{"https://host/prefix", "https://host/prefix/"},
|
||||
{"http://host", "http://host"},
|
||||
{"http://host/other", "http://host/other/"},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
u, err := DefaultServerURL(tc.host)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if tc.url != u.String() {
|
||||
t.Errorf("%s, expected host %s, got %s", tc.host, tc.url, u.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestURL(t *testing.T) {
|
||||
tt := []struct {
|
||||
host string
|
||||
path string
|
||||
url string
|
||||
}{
|
||||
{"127.0.0.1", "foo", "http://127.0.0.1/foo"},
|
||||
{"127.0.0.1:8080", "foo", "http://127.0.0.1:8080/foo"},
|
||||
{"foo.bar.com", "foo", "http://foo.bar.com/foo"},
|
||||
{"foo.bar.com/prefix", "foo", "http://foo.bar.com/prefix/foo"},
|
||||
{"http://host/prefix", "foo", "http://host/prefix/foo"},
|
||||
{"http://host", "foo", "http://host/foo"},
|
||||
{"http://host/other", "/foo", "http://host/foo"},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
c := NewClient(tc.host)
|
||||
p, err := c.url(tc.path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if tc.url != p {
|
||||
t.Errorf("expected %s, got %s", tc.url, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type fakeClient struct {
|
||||
*Client
|
||||
server *httptest.Server
|
||||
handler http.HandlerFunc
|
||||
}
|
||||
|
||||
func (c *fakeClient) setup() *fakeClient {
|
||||
c.server = httptest.NewServer(c.handler)
|
||||
c.Client = NewClient(c.server.URL)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *fakeClient) teardown() {
|
||||
c.server.Close()
|
||||
}
|
||||
|
||||
func TestUserAgent(t *testing.T) {
|
||||
fc := &fakeClient{
|
||||
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.HasPrefix(r.UserAgent(), "helm") {
|
||||
t.Error("user agent is not set")
|
||||
}
|
||||
}),
|
||||
}
|
||||
fc.setup().ListDeployments()
|
||||
}
|
||||
|
||||
func TestListDeployments(t *testing.T) {
|
||||
fc := &fakeClient{
|
||||
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`["guestbook.yaml"]`))
|
||||
}),
|
||||
}
|
||||
defer fc.teardown()
|
||||
|
||||
l, err := fc.setup().ListDeployments()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(l) != 1 {
|
||||
t.Fatal("expected a single deployment")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDeployment(t *testing.T) {
|
||||
fc := &fakeClient{
|
||||
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`{"name":"guestbook.yaml","id":0,"createdAt":"2016-02-08T12:17:49.251658308-08:00","deployedAt":"2016-02-08T12:17:49.251658589-08:00","modifiedAt":"2016-02-08T12:17:51.177518098-08:00","deletedAt":"0001-01-01T00:00:00Z","state":{"status":"Deployed"},"latestManifest":"manifest-1454962670728402229"}`))
|
||||
}),
|
||||
}
|
||||
defer fc.teardown()
|
||||
|
||||
d, err := fc.setup().GetDeployment("guestbook.yaml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if d.Name != "guestbook.yaml" {
|
||||
t.Fatalf("expected deployment name 'guestbook.yaml', got '%s'", d.Name)
|
||||
}
|
||||
|
||||
if d.State.Status != common.DeployedStatus {
|
||||
t.Fatalf("expected deployment status 'Deployed', got '%s'", d.State.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostDeployment(t *testing.T) {
|
||||
cfg := &common.Configuration{
|
||||
Resources: []*common.Resource{
|
||||
{
|
||||
Name: "foo",
|
||||
Type: "helm:example.com/foo/bar",
|
||||
Properties: map[string]interface{}{
|
||||
"port": ":8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fc := &fakeClient{
|
||||
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
fmt.Fprintln(w, "{}")
|
||||
}),
|
||||
}
|
||||
defer fc.teardown()
|
||||
|
||||
if err := fc.setup().PostDeployment(cfg); err != nil {
|
||||
t.Fatalf("failed to post deployment: %s", err)
|
||||
}
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
package dm
|
||||
|
||||
import (
|
||||
"github.com/kubernetes/deployment-manager/pkg/format"
|
||||
"github.com/kubernetes/deployment-manager/pkg/kubectl"
|
||||
)
|
||||
|
||||
// Install uses kubectl to install the base DM.
|
||||
//
|
||||
// Returns the string output received from the operation, and an error if the
|
||||
// command failed.
|
||||
func Install(runner kubectl.Runner) (string, error) {
|
||||
o, err := runner.Create([]byte(InstallYAML))
|
||||
return string(o), err
|
||||
}
|
||||
|
||||
// IsInstalled checks whether DM has been installed.
|
||||
func IsInstalled(runner kubectl.Runner) bool {
|
||||
// Basically, we test "all-or-nothing" here: if this returns without error
|
||||
// we know that we have both the namespace and the manager API server.
|
||||
out, err := runner.GetByKind("rc", "manager-rc", "dm")
|
||||
if err != nil {
|
||||
format.Err("Installation not found: %s %s", out, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// InstallYAML is the installation YAML for DM.
|
||||
const InstallYAML = `
|
||||
######################################################################
|
||||
# Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
######################################################################
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
labels:
|
||||
app: dm
|
||||
name: dm-namespace
|
||||
name: dm
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: dm
|
||||
name: expandybird-service
|
||||
name: expandybird-service
|
||||
namespace: dm
|
||||
spec:
|
||||
ports:
|
||||
- name: expandybird
|
||||
port: 8081
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: dm
|
||||
name: expandybird
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
labels:
|
||||
app: dm
|
||||
name: expandybird-rc
|
||||
name: expandybird-rc
|
||||
namespace: dm
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
app: dm
|
||||
name: expandybird
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: dm
|
||||
name: expandybird
|
||||
spec:
|
||||
containers:
|
||||
- env: []
|
||||
image: gcr.io/dm-k8s-testing/expandybird:latest
|
||||
name: expandybird
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: expandybird
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: dm
|
||||
name: resourcifier-service
|
||||
name: resourcifier-service
|
||||
namespace: dm
|
||||
spec:
|
||||
ports:
|
||||
- name: resourcifier
|
||||
port: 8082
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: dm
|
||||
name: resourcifier
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
labels:
|
||||
app: dm
|
||||
name: resourcifier-rc
|
||||
name: resourcifier-rc
|
||||
namespace: dm
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
app: dm
|
||||
name: resourcifier
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: dm
|
||||
name: resourcifier
|
||||
spec:
|
||||
containers:
|
||||
- env: []
|
||||
image: gcr.io/dm-k8s-testing/resourcifier:latest
|
||||
name: resourcifier
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: resourcifier
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
app: dm
|
||||
name: manager-service
|
||||
name: manager-service
|
||||
namespace: dm
|
||||
spec:
|
||||
ports:
|
||||
- name: manager
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: dm
|
||||
name: manager
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ReplicationController
|
||||
metadata:
|
||||
labels:
|
||||
app: dm
|
||||
name: manager-rc
|
||||
name: manager-rc
|
||||
namespace: dm
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
app: dm
|
||||
name: manager
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: dm
|
||||
name: manager
|
||||
spec:
|
||||
containers:
|
||||
- env: []
|
||||
image: gcr.io/dm-k8s-testing/manager:latest
|
||||
name: manager
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: manager
|
||||
`
|
@ -0,0 +1,66 @@
|
||||
package dm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
)
|
||||
|
||||
type debugTransport struct {
|
||||
// Writer is the logging destination
|
||||
Writer io.Writer
|
||||
|
||||
http.RoundTripper
|
||||
}
|
||||
|
||||
// NewDebugTransport returns a debugging implementation of a RoundTripper.
|
||||
func NewDebugTransport(rt http.RoundTripper) http.RoundTripper {
|
||||
return debugTransport{
|
||||
RoundTripper: rt,
|
||||
Writer: os.Stderr,
|
||||
}
|
||||
}
|
||||
|
||||
func (tr debugTransport) CancelRequest(req *http.Request) {
|
||||
type canceler interface {
|
||||
CancelRequest(*http.Request)
|
||||
}
|
||||
if cr, ok := tr.transport().(canceler); ok {
|
||||
cr.CancelRequest(req)
|
||||
}
|
||||
}
|
||||
|
||||
func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
tr.logRequest(req)
|
||||
resp, err := tr.transport().RoundTrip(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr.logResponse(resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (tr debugTransport) transport() http.RoundTripper {
|
||||
if tr.RoundTripper != nil {
|
||||
return tr.RoundTripper
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
func (tr debugTransport) logRequest(req *http.Request) {
|
||||
dump, err := httputil.DumpRequestOut(req, true)
|
||||
if err != nil {
|
||||
fmt.Fprintf(tr.Writer, "%s: %s\n", "could not dump request", err)
|
||||
}
|
||||
fmt.Fprint(tr.Writer, string(dump))
|
||||
}
|
||||
|
||||
func (tr debugTransport) logResponse(resp *http.Response) {
|
||||
dump, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
fmt.Fprintf(tr.Writer, "%s: %s\n", "could not dump response", err)
|
||||
}
|
||||
fmt.Fprint(tr.Writer, string(dump))
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package dm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDebugTransport(t *testing.T) {
|
||||
handler := func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"status":"awesome"}`))
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||
defer server.Close()
|
||||
|
||||
var output bytes.Buffer
|
||||
|
||||
client := &http.Client{
|
||||
Transport: debugTransport{
|
||||
Writer: &output,
|
||||
},
|
||||
}
|
||||
|
||||
_, err := client.Get(server.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"GET / HTTP/1.1",
|
||||
"Accept-Encoding: gzip",
|
||||
"HTTP/1.1 200 OK",
|
||||
"Content-Length: 20",
|
||||
"Content-Type: application/json",
|
||||
`{"status":"awesome"}`,
|
||||
}
|
||||
actual := output.String()
|
||||
|
||||
for _, match := range expected {
|
||||
if !strings.Contains(actual, match) {
|
||||
t.Errorf("Expected %s to contain %s", actual, match)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package dm
|
||||
|
||||
import (
|
||||
"github.com/kubernetes/deployment-manager/pkg/kubectl"
|
||||
)
|
||||
|
||||
// Uninstall uses kubectl to uninstall the base DM.
|
||||
//
|
||||
// Returns the string output received from the operation, and an error if the
|
||||
// command failed.
|
||||
func Uninstall(runner kubectl.Runner) (string, error) {
|
||||
o, err := runner.Delete("dm", "Namespace")
|
||||
return string(o), err
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
// This is all just placeholder.
|
||||
|
||||
// Err prints an error message to Stderr.
|
||||
func Err(msg string, v ...interface{}) {
|
||||
msg = "[ERROR] " + msg + "\n"
|
||||
fmt.Fprintf(os.Stderr, msg, v...)
|
||||
}
|
||||
|
||||
// Info prints an informational message to Stdout.
|
||||
func Info(msg string, v ...interface{}) {
|
||||
msg = "[INFO] " + msg + "\n"
|
||||
fmt.Fprintf(os.Stdout, msg, v...)
|
||||
}
|
||||
|
||||
// Msg prints a raw message to Stdout.
|
||||
func Msg(msg string, v ...interface{}) {
|
||||
fmt.Fprintf(os.Stdout, msg, v...)
|
||||
}
|
||||
|
||||
// Success is an achievement marked by pretty output.
|
||||
func Success(msg string, v ...interface{}) {
|
||||
msg = "[Success] " + msg + "\n"
|
||||
fmt.Fprintf(os.Stdout, msg, v...)
|
||||
}
|
||||
|
||||
// Warning emits a warning message.
|
||||
func Warning(msg string, v ...interface{}) {
|
||||
msg = "[Warning] " + msg + "\n"
|
||||
fmt.Fprintf(os.Stdout, msg, v...)
|
||||
}
|
||||
|
||||
// YAML prints an object in YAML format.
|
||||
func YAML(v interface{}) error {
|
||||
y, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to serialize to yaml: %s", v.(string))
|
||||
}
|
||||
|
||||
Msg(string(y))
|
||||
return nil
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package kubectl
|
||||
|
||||
// ClusterInfo returns Kubernetes cluster info
|
||||
func (r RealRunner) ClusterInfo() ([]byte, error) {
|
||||
return command("cluster-info").CombinedOutput()
|
||||
}
|
||||
|
||||
// ClusterInfo returns the commands to kubectl
|
||||
func (r PrintRunner) ClusterInfo() ([]byte, error) {
|
||||
cmd := command("cluster-info")
|
||||
return []byte(cmd.String()), nil
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package kubectl
|
||||
|
||||
// Delete removes a chart from Kubernetes.
|
||||
func (r RealRunner) Delete(name, ktype string) ([]byte, error) {
|
||||
|
||||
args := []string{"delete", ktype, name}
|
||||
|
||||
return command(args...).CombinedOutput()
|
||||
}
|
||||
|
||||
// Delete returns the commands to kubectl
|
||||
func (r PrintRunner) Delete(name, ktype string) ([]byte, error) {
|
||||
|
||||
args := []string{"delete", ktype, name}
|
||||
|
||||
cmd := command(args...)
|
||||
return []byte(cmd.String()), nil
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package kubectl
|
||||
|
||||
// Get returns Kubernetes resources
|
||||
func (r RealRunner) Get(stdin []byte, ns string) ([]byte, error) {
|
||||
args := []string{"get", "-f", "-"}
|
||||
|
||||
if ns != "" {
|
||||
args = append([]string{"--namespace=" + ns}, args...)
|
||||
}
|
||||
cmd := command(args...)
|
||||
assignStdin(cmd, stdin)
|
||||
|
||||
return cmd.CombinedOutput()
|
||||
}
|
||||
|
||||
// GetByKind gets a named thing by kind.
|
||||
func (r RealRunner) GetByKind(kind, name, ns string) (string, error) {
|
||||
args := []string{"get", kind, name}
|
||||
|
||||
if ns != "" {
|
||||
args = append([]string{"--namespace=" + ns}, args...)
|
||||
}
|
||||
cmd := command(args...)
|
||||
o, err := cmd.CombinedOutput()
|
||||
return string(o), err
|
||||
}
|
||||
|
||||
// Get returns the commands to kubectl
|
||||
func (r PrintRunner) Get(stdin []byte, ns string) ([]byte, error) {
|
||||
args := []string{"get", "-f", "-"}
|
||||
|
||||
if ns != "" {
|
||||
args = append([]string{"--namespace=" + ns}, args...)
|
||||
}
|
||||
cmd := command(args...)
|
||||
assignStdin(cmd, stdin)
|
||||
|
||||
return []byte(cmd.String()), nil
|
||||
}
|
||||
|
||||
// GetByKind gets a named thing by kind.
|
||||
func (r PrintRunner) GetByKind(kind, name, ns string) (string, error) {
|
||||
args := []string{"get", kind, name}
|
||||
|
||||
if ns != "" {
|
||||
args = append([]string{"--namespace=" + ns}, args...)
|
||||
}
|
||||
cmd := command(args...)
|
||||
return cmd.String(), nil
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package kubectl
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
Client = TestRunner{
|
||||
out: []byte("running the get command"),
|
||||
}
|
||||
|
||||
expects := "running the get command"
|
||||
out, _ := Client.Get([]byte{}, "")
|
||||
if string(out) != expects {
|
||||
t.Errorf("%s != %s", string(out), expects)
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
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 {
|
||||
// ClusterInfo returns Kubernetes cluster info
|
||||
ClusterInfo() ([]byte, error)
|
||||
// Create uploads a chart to Kubernetes
|
||||
Create(stdin []byte) ([]byte, error)
|
||||
// Delete removes a chart from Kubernetes.
|
||||
Delete(name string, ktype string) ([]byte, error)
|
||||
// Get returns Kubernetes resources
|
||||
Get(stdin []byte, ns string) ([]byte, error)
|
||||
|
||||
// GetByKind gets an entry by kind, name, and namespace.
|
||||
//
|
||||
// If name is omitted, all entries of that kind are returned.
|
||||
//
|
||||
// If NS is omitted, the default NS is assumed.
|
||||
GetByKind(kind, name, ns string) (string, 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{}
|
@ -0,0 +1,12 @@
|
||||
package kubectl
|
||||
|
||||
type TestRunner struct {
|
||||
Runner
|
||||
|
||||
out []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func (r TestRunner) Get(stdin []byte, ns string) ([]byte, error) {
|
||||
return r.out, r.err
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
The testdata directory here holds charts that match the specification.
|
||||
|
||||
The `fromnitz/` directory contains a chart that matches the chart
|
||||
specification.
|
||||
|
||||
The `frobnitz-0.0.1.tgz` file is an archive of the `frobnitz` directory.
|
||||
|
||||
The `ill` chart and directory is a chart that is not 100% compatible,
|
||||
but which should still be parseable.
|
Binary file not shown.
@ -0,0 +1,27 @@
|
||||
name: frobnitz
|
||||
description: This is a frobniz.
|
||||
version: 1.2.3-alpha.1+12345
|
||||
keywords:
|
||||
- frobnitz
|
||||
- sprocket
|
||||
- dodad
|
||||
maintainers:
|
||||
- name: The Helm Team
|
||||
email: helm@example.com
|
||||
- name: Someone Else
|
||||
email: nobody@example.com
|
||||
source:
|
||||
- https://example.com/foo/bar
|
||||
home: http://example.com
|
||||
dependencies:
|
||||
- name: thingerbob
|
||||
version: ^3
|
||||
location: https://example.com/charts/thingerbob-3.2.1.tgz
|
||||
environment:
|
||||
- name: Kubernetes
|
||||
version: ~1.1
|
||||
extensions:
|
||||
- extensions/v1beta1
|
||||
- extensions/v1beta1/daemonset
|
||||
apiGroups:
|
||||
- 3rdParty
|
@ -0,0 +1 @@
|
||||
THIS IS PLACEHOLDER TEXT.
|
@ -0,0 +1,11 @@
|
||||
# Frobnitz
|
||||
|
||||
This is an example chart.
|
||||
|
||||
## Usage
|
||||
|
||||
This is an example. It has no usage.
|
||||
|
||||
## Development
|
||||
|
||||
For developer info, see the top-level repository.
|
@ -0,0 +1 @@
|
||||
This is a placeholder for documentation.
|
@ -0,0 +1 @@
|
||||
# Placeholder.
|
After Width: | Height: | Size: 374 B |
@ -0,0 +1,12 @@
|
||||
# Google Cloud Deployment Manager template
|
||||
resources:
|
||||
- name: nfs-disk
|
||||
type: compute.v1.disk
|
||||
properties:
|
||||
zone: us-central1-b
|
||||
sizeGb: 200
|
||||
- name: mysql-disk
|
||||
type: compute.v1.disk
|
||||
properties:
|
||||
zone: us-central1-b
|
||||
sizeGb: 200
|
@ -0,0 +1,72 @@
|
||||
#helm:generate dm_template
|
||||
{% set PROPERTIES = properties or {} %}
|
||||
{% set PROJECT = PROPERTIES['project'] or 'dm-k8s-testing' %}
|
||||
{% set NFS_SERVER = PROPERTIES['nfs-server'] or {} %}
|
||||
{% set NFS_SERVER_IP = NFS_SERVER['ip'] or '10.0.253.247' %}
|
||||
{% set NFS_SERVER_PORT = NFS_SERVER['port'] or 2049 %}
|
||||
{% set NFS_SERVER_DISK = NFS_SERVER['disk'] or 'nfs-disk' %}
|
||||
{% set NFS_SERVER_DISK_FSTYPE = NFS_SERVER['fstype'] or 'ext4' %}
|
||||
{% set NGINX = PROPERTIES['nginx'] or {} %}
|
||||
{% set NGINX_PORT = 80 %}
|
||||
{% set NGINX_REPLICAS = NGINX['replicas'] or 2 %}
|
||||
{% set WORDPRESS_PHP = PROPERTIES['wordpress-php'] or {} %}
|
||||
{% set WORDPRESS_PHP_REPLICAS = WORDPRESS_PHP['replicas'] or 2 %}
|
||||
{% set WORDPRESS_PHP_PORT = WORDPRESS_PHP['port'] or 9000 %}
|
||||
{% set MYSQL = PROPERTIES['mysql'] or {} %}
|
||||
{% set MYSQL_PORT = MYSQL['port'] or 3306 %}
|
||||
{% set MYSQL_PASSWORD = MYSQL['password'] or 'mysql-password' %}
|
||||
{% set MYSQL_DISK = MYSQL['disk'] or 'mysql-disk' %}
|
||||
{% set MYSQL_DISK_FSTYPE = MYSQL['fstype'] or 'ext4' %}
|
||||
|
||||
resources:
|
||||
- name: nfs
|
||||
type: github.com/kubernetes/application-dm-templates/storage/nfs:v1
|
||||
properties:
|
||||
ip: {{ NFS_SERVER_IP }}
|
||||
port: {{ NFS_SERVER_PORT }}
|
||||
disk: {{ NFS_SERVER_DISK }}
|
||||
fstype: {{NFS_SERVER_DISK_FSTYPE }}
|
||||
- name: nginx
|
||||
type: github.com/kubernetes/application-dm-templates/common/replicatedservice:v2
|
||||
properties:
|
||||
service_port: {{ NGINX_PORT }}
|
||||
container_port: {{ NGINX_PORT }}
|
||||
replicas: {{ NGINX_REPLICAS }}
|
||||
external_service: true
|
||||
image: gcr.io/{{ PROJECT }}/nginx:latest
|
||||
volumes:
|
||||
- mount_path: /var/www/html
|
||||
persistentVolumeClaim:
|
||||
claimName: nfs
|
||||
- name: mysql
|
||||
type: github.com/kubernetes/application-dm-templates/common/replicatedservice:v2
|
||||
properties:
|
||||
service_port: {{ MYSQL_PORT }}
|
||||
container_port: {{ MYSQL_PORT }}
|
||||
replicas: 1
|
||||
image: mysql:5.6
|
||||
env:
|
||||
- name: MYSQL_ROOT_PASSWORD
|
||||
value: {{ MYSQL_PASSWORD }}
|
||||
volumes:
|
||||
- mount_path: /var/lib/mysql
|
||||
gcePersistentDisk:
|
||||
pdName: {{ MYSQL_DISK }}
|
||||
fsType: {{ MYSQL_DISK_FSTYPE }}
|
||||
- name: wordpress-php
|
||||
type: github.com/kubernetes/application-dm-templates/common/replicatedservice:v2
|
||||
properties:
|
||||
service_name: wordpress-php
|
||||
service_port: {{ WORDPRESS_PHP_PORT }}
|
||||
container_port: {{ WORDPRESS_PHP_PORT }}
|
||||
replicas: 2
|
||||
image: wordpress:fpm
|
||||
env:
|
||||
- name: WORDPRESS_DB_PASSWORD
|
||||
value: {{ MYSQL_PASSWORD }}
|
||||
- name: WORDPRESS_DB_HOST
|
||||
value: mysql-service
|
||||
volumes:
|
||||
- mount_path: /var/www/html
|
||||
persistentVolumeClaim:
|
||||
claimName: nfs
|
@ -0,0 +1,69 @@
|
||||
info:
|
||||
title: Wordpress
|
||||
description: |
|
||||
Defines a Wordpress website by defining four replicated services: an NFS service, an nginx service, a wordpress-php service, and a MySQL service.
|
||||
|
||||
The nginx service and the Wordpress-php service both use NFS to share files.
|
||||
|
||||
properties:
|
||||
project:
|
||||
type: string
|
||||
default: dm-k8s-testing
|
||||
description: Project location to load the images from.
|
||||
nfs-service:
|
||||
type: object
|
||||
properties:
|
||||
ip:
|
||||
type: string
|
||||
default: 10.0.253.247
|
||||
description: The IP of the NFS service.
|
||||
port:
|
||||
type: int
|
||||
default: 2049
|
||||
description: The port of the NFS service.
|
||||
disk:
|
||||
type: string
|
||||
default: nfs-disk
|
||||
description: The name of the persistent disk the NFS service uses.
|
||||
fstype:
|
||||
type: string
|
||||
default: ext4
|
||||
description: The filesystem the disk of the NFS service uses.
|
||||
nginx:
|
||||
type: object
|
||||
properties:
|
||||
replicas:
|
||||
type: int
|
||||
default: 2
|
||||
description: The number of replicas for the nginx service.
|
||||
wordpress-php:
|
||||
type: object
|
||||
properties:
|
||||
replicas:
|
||||
type: int
|
||||
default: 2
|
||||
description: The number of replicas for the wordpress-php service.
|
||||
port:
|
||||
type: int
|
||||
default: 9000
|
||||
description: The port the wordpress-php service runs on.
|
||||
mysql:
|
||||
type: object
|
||||
properties:
|
||||
port:
|
||||
type: int
|
||||
default: 3306
|
||||
description: The port the MySQL service runs on.
|
||||
password:
|
||||
type: string
|
||||
default: mysql-password
|
||||
description: The root password of the MySQL service.
|
||||
disk:
|
||||
type: string
|
||||
default: mysql-disk
|
||||
description: The name of the persistent disk the MySQL service uses.
|
||||
fstype:
|
||||
type: string
|
||||
default: ext4
|
||||
description: The filesystem the disk of the MySQL service uses.
|
||||
|
@ -0,0 +1,6 @@
|
||||
imports:
|
||||
- path: wordpress.jinja
|
||||
|
||||
resources:
|
||||
- name: wordpress
|
||||
type: wordpress.jinja
|
@ -0,0 +1,127 @@
|
||||
T**Testing fork of the guestbook example.**
|
||||
|
||||
# Guestbook Example
|
||||
|
||||
Welcome to the Guestbook example. It shows you how to build and reuse
|
||||
parameterized templates.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
First, make sure DM is installed in your Kubernetes cluster and that the
|
||||
Guestbook example is deployed by following the instructions in the top level
|
||||
[README.md](../../README.md).
|
||||
|
||||
## Understanding the Guestbook example
|
||||
|
||||
Let's take a closer look at the configuration used by the Guestbook example.
|
||||
|
||||
### Replicated services
|
||||
|
||||
The typical design pattern for microservices in Kubernetes is to create a
|
||||
replication controller and a service with the same selector, so that the service
|
||||
exposes ports from the pods managed by the replication controller.
|
||||
|
||||
We have created a parameterized template for this kind of replicated service
|
||||
called [Replicated Service](../../templates/replicatedservice/v1), and we use it
|
||||
three times in the Guestbook example.
|
||||
|
||||
The template is defined by a
|
||||
[Python script](../../templates/replicatedservice/v1/replicatedservice.py). It
|
||||
also has a [schema](../../templates/replicatedservice/v1/replicatedservice.py.schema).
|
||||
Schemas are optional. If provided, they are used to validate template invocations
|
||||
that appear in configurations.
|
||||
|
||||
For more information about templates and schemas, see the
|
||||
[design document](../../docs/design/design.md#templates).
|
||||
|
||||
### The Guestbook application
|
||||
The Guestbook application consists of 2 microservices: a front end and a Redis
|
||||
cluster.
|
||||
|
||||
#### The front end
|
||||
|
||||
The front end is a replicated service with 3 replicas:
|
||||
|
||||
```
|
||||
- name: frontend
|
||||
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
|
||||
properties:
|
||||
service_port: 80
|
||||
container_port: 80
|
||||
external_service: true
|
||||
replicas: 3
|
||||
image: gcr.io/google_containers/example-guestbook-php-redis:v3
|
||||
```
|
||||
|
||||
(Note that we use the URL for a specific version of the template replicatedservice.py,
|
||||
not just the template name.)
|
||||
|
||||
#### The Redis cluster
|
||||
|
||||
The Redis cluster consists of two replicated services: a master with a single replica
|
||||
and the slaves with 2 replicas. It's defined by [this template](../../templates/redis/v1/redis.jinja),
|
||||
which is a [Jinja](http://jinja.pocoo.org/) file with a [schema](../../templates/redis/v1/redis.jinja.schema).
|
||||
|
||||
```
|
||||
{% set REDIS_PORT = 6379 %}
|
||||
{% set WORKERS = properties['workers'] or 2 %}
|
||||
|
||||
resources:
|
||||
- name: redis-master
|
||||
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
|
||||
properties:
|
||||
# This has to be overwritten since service names are hard coded in the code
|
||||
service_name: redis-master
|
||||
service_port: {{ REDIS_PORT }}
|
||||
target_port: {{ REDIS_PORT }}
|
||||
container_port: {{ REDIS_PORT }}
|
||||
replicas: 1
|
||||
container_name: master
|
||||
image: redis
|
||||
|
||||
- name: redis-slave
|
||||
type: https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py
|
||||
properties:
|
||||
# This has to be overwritten since service names are hard coded in the code
|
||||
service_name: redis-slave
|
||||
service_port: {{ REDIS_PORT }}
|
||||
container_port: {{ REDIS_PORT }}
|
||||
replicas: {{ WORKERS }}
|
||||
container_name: worker
|
||||
image: kubernetes/redis-slave:v2
|
||||
# An example of how to specify env variables.
|
||||
env:
|
||||
- name: GET_HOSTS_FROM
|
||||
value: env
|
||||
- name: REDIS_MASTER_SERVICE_HOST
|
||||
value: redis-master
|
||||
```
|
||||
|
||||
### Displaying types
|
||||
|
||||
You can see both the both primitive types and the templates you've deployed to the
|
||||
cluster using the `deployed-types` command:
|
||||
|
||||
```
|
||||
dm deployed-types
|
||||
|
||||
["Service","ReplicationController","redis.jinja","https://raw.githubusercontent.com/kubernetes/deployment-manager/master/templates/replicatedservice/v1/replicatedservice.py"]
|
||||
```
|
||||
|
||||
This output shows 2 primitive types (Service and ReplicationController), and 2
|
||||
templates (redis.jinja and one imported from github named replicatedservice.py).
|
||||
|
||||
You can also see where a specific type is being used with the `deployed-instances` command:
|
||||
|
||||
```
|
||||
dm deployed-instances Service
|
||||
[{"name":"frontend-service","type":"Service","deployment":"guestbook4","manifest":"manifest-1446682551242763329","path":"$.resources[0].resources[0]"},{"name":"redis-master","type":"Service","deployment":"guestbook4","manifest":"manifest-1446682551242763329","path":"$.resources[1].resources[0].resources[0]"},{"name":"redis-slave","type":"Service","deployment":"guestbook4","manifest":"manifest-1446682551242763329","path":"$.resources[1].resources[1].resources[0]"}]
|
||||
```
|
||||
|
||||
This output describes the deployment and manifest, as well as the JSON paths to
|
||||
the instances of the type within the layout.
|
||||
|
||||
For more information about deployments, manifests and layouts, see the
|
||||
[design document](../../docs/design/design.md#api-model).
|
||||
|
||||
|
@ -0,0 +1,12 @@
|
||||
resources:
|
||||
- name: frontend
|
||||
type: github.com/kubernetes/application-dm-templates/common/replicatedservice:v1
|
||||
properties:
|
||||
service_port: 80
|
||||
container_port: 80
|
||||
external_service: true
|
||||
replicas: 3
|
||||
image: gcr.io/google_containers/example-guestbook-php-redis:v3
|
||||
- name: redis
|
||||
type: github.com/kubernetes/application-dm-templates/storage/redis:v1
|
||||
properties: null
|
Loading…
Reference in new issue