mirror of https://github.com/helm/helm
commit
8876c7e54b
@ -1,4 +1,4 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
.project
|
.project
|
||||||
vendor/*
|
|
||||||
/bin
|
/bin
|
||||||
|
/vendor/*
|
||||||
|
@ -1,12 +1,26 @@
|
|||||||
sudo: true
|
sudo: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
- GO15VENDOREXPERIMENT=1 GLIDE_VERSION="0.9.1"
|
||||||
|
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.4
|
- 1.6
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- sudo pip install -r expandybird/requirements.txt
|
- sudo pip install -r expandybird/requirements.txt
|
||||||
|
|
||||||
script:
|
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
|
hash: 2d8e32786782b7979a79850cfc489866a74c068e865f433a73ed4f50ef2644e9
|
||||||
updated: 2016-01-26T17:30:54.243252416-07:00
|
updated: 2016-02-29T11:21:24.093936684-08:00
|
||||||
imports:
|
imports:
|
||||||
|
- name: github.com/aokoli/goutils
|
||||||
|
version: 9c37978a95bd5c709a15883b6242714ea6709e64
|
||||||
|
- name: github.com/codegangsta/cli
|
||||||
|
version: a2943485b110df8842045ae0600047f88a3a56a1
|
||||||
- name: github.com/emicklei/go-restful
|
- name: github.com/emicklei/go-restful
|
||||||
version: b86acf97a74ed7603ac78d012f5535b4d587b156
|
version: b86acf97a74ed7603ac78d012f5535b4d587b156
|
||||||
|
subpackages:
|
||||||
|
- log
|
||||||
- name: github.com/ghodss/yaml
|
- name: github.com/ghodss/yaml
|
||||||
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
|
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
|
||||||
- name: github.com/golang/glog
|
|
||||||
version: 23def4e6c14b4da8ac2ed8007337bc5eb5007998
|
|
||||||
- name: github.com/golang/protobuf
|
- name: github.com/golang/protobuf
|
||||||
version: 6aaa8d47701fa6cf07e914ec01fde3d4a1fe79c3
|
version: 6aaa8d47701fa6cf07e914ec01fde3d4a1fe79c3
|
||||||
|
subpackages:
|
||||||
|
- proto
|
||||||
- name: github.com/google/go-github
|
- name: github.com/google/go-github
|
||||||
version: b8b4ac742977310ff6e75140a403a38dab109977
|
version: b8b4ac742977310ff6e75140a403a38dab109977
|
||||||
subpackages:
|
subpackages:
|
||||||
- /github
|
- github
|
||||||
- name: github.com/google/go-querystring
|
- name: github.com/google/go-querystring
|
||||||
version: 2a60fc2ba6c19de80291203597d752e9ba58e4c0
|
version: 2a60fc2ba6c19de80291203597d752e9ba58e4c0
|
||||||
|
subpackages:
|
||||||
|
- query
|
||||||
- name: github.com/gorilla/context
|
- name: github.com/gorilla/context
|
||||||
version: 1c83b3eabd45b6d76072b66b746c20815fb2872d
|
version: 1c83b3eabd45b6d76072b66b746c20815fb2872d
|
||||||
- name: github.com/gorilla/handlers
|
- name: github.com/gorilla/handlers
|
||||||
version: 8f2758070a82adb7a3ad6b223a0b91878f32d400
|
version: 8f2758070a82adb7a3ad6b223a0b91878f32d400
|
||||||
- name: github.com/gorilla/mux
|
- name: github.com/gorilla/mux
|
||||||
version: 26a6070f849969ba72b72256e9f14cf519751690
|
version: 26a6070f849969ba72b72256e9f14cf519751690
|
||||||
- name: github.com/gorilla/schema
|
|
||||||
version: 14c555599c2a4f493c1e13fd1ea6fdf721739028
|
|
||||||
- name: github.com/Masterminds/semver
|
- name: github.com/Masterminds/semver
|
||||||
version: c4f7ef0702f269161a60489ccbbc9f1241ad1265
|
version: c4f7ef0702f269161a60489ccbbc9f1241ad1265
|
||||||
- name: github.com/mjibson/appstats
|
|
||||||
version: 0542d5f0e87ea3a8fa4174322b9532f5d04f9fa8
|
|
||||||
- name: golang.org/x/crypto
|
|
||||||
version: 1f22c0103821b9390939b6776727195525381532
|
|
||||||
- name: golang.org/x/net
|
- name: golang.org/x/net
|
||||||
version: 04b9de9b512f58addf28c9853d50ebef61c3953e
|
version: 04b9de9b512f58addf28c9853d50ebef61c3953e
|
||||||
|
subpackages:
|
||||||
|
- context
|
||||||
|
- context/ctxhttp
|
||||||
- name: golang.org/x/oauth2
|
- name: golang.org/x/oauth2
|
||||||
version: 8a57ed94ffd43444c0879fe75701732a38afc985
|
version: 8a57ed94ffd43444c0879fe75701732a38afc985
|
||||||
- name: golang.org/x/text
|
subpackages:
|
||||||
version: 6d3c22c4525a4da167968fa2479be5524d2e8bd0
|
- google
|
||||||
- name: google.golang.com/appengine
|
- internal
|
||||||
version: ""
|
- jws
|
||||||
repo: https://google.golang.com/appengine
|
- jwt
|
||||||
- name: google.golang.org/api
|
- name: google.golang.org/api
|
||||||
version: 0caa37974a5f5ae67172acf68b4970f7864f994c
|
version: 0caa37974a5f5ae67172acf68b4970f7864f994c
|
||||||
|
subpackages:
|
||||||
|
- storage/v1
|
||||||
|
- gensupport
|
||||||
|
- googleapi
|
||||||
|
- googleapi/internal/uritemplates
|
||||||
- name: google.golang.org/appengine
|
- name: google.golang.org/appengine
|
||||||
version: 6bde959377a90acb53366051d7d587bfd7171354
|
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
|
- name: google.golang.org/cloud
|
||||||
version: fb10e8da373d97f6ba5e648299a10b3b91f14cd5
|
version: fb10e8da373d97f6ba5e648299a10b3b91f14cd5
|
||||||
- name: google.golang.org/grpc
|
subpackages:
|
||||||
version: e29d659177655e589850ba7d3d83f7ce12ef23dd
|
- compute/metadata
|
||||||
|
- internal
|
||||||
|
- name: gopkg.in/mgo.v2
|
||||||
|
version: d90005c5262a3463800497ea5a89aed5fe22c886
|
||||||
|
subpackages:
|
||||||
|
- bson
|
||||||
|
- internal/sasl
|
||||||
|
- internal/scram
|
||||||
- name: gopkg.in/yaml.v2
|
- name: gopkg.in/yaml.v2
|
||||||
version: f7716cbe52baa25d2e9b0d0da546fcf909fc16b4
|
version: f7716cbe52baa25d2e9b0d0da546fcf909fc16b4
|
||||||
devImports: []
|
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