Commit 8876c7e5 authored by Adam Reese's avatar Adam Reese

Merge remote-tracking branch 'helm/master' into helm-merge

parents 40171283 a88506e5
*.pyc
.project
vendor/*
/bin
/vendor/*
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"
......@@ -12,9 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
ifndef GOPATH
$(error No GOPATH set)
endif
include include.mk
GO_PKGS := $(shell glide nv)
GO_DIRS ?= $(shell glide nv -x )
GO_PKGS ?= $(shell glide nv)
.PHONY: build
build:
......@@ -26,9 +31,10 @@ all: build
.PHONY: clean
clean:
go clean -v $(GO_PKGS)
rm -rf bin
.PHONY: clean
test: build lint vet test-unit
.PHONY: test
test: build test-style test-unit
.PHONY: push
push: container
......@@ -39,12 +45,18 @@ container: .project .docker
.PHONY: test-unit
test-unit:
@echo Running tests...
go test -v $(shell glide nv)
go test -v $(GO_PKGS)
.PHONY: .test-style
test-style: lint vet
@if [ $(shell gofmt -e -l -s $(GO_DIRS)) ]; then \
echo "gofmt check failed:"; gofmt -e -d -s $(GO_DIRS); exit 1; \
fi
.PHONY: lint
lint:
@echo Running golint...
@for i in $(shell glide nv); do \
@for i in $(GO_PKGS); do \
golint $$i; \
done
@echo -----------------
......@@ -52,17 +64,18 @@ lint:
.PHONY: vet
vet:
@echo Running go vet...
@for i in $(shell glide nv -x); do \
@for i in $(GO_DIRS); do \
go tool vet $$i; \
done
@echo -----------------
.PHONY: setup-gotools
setup-gotools:
@echo Installing golint
.PHONY: bootstrap
bootstrap:
@echo Installing deps
go get -u github.com/golang/lint/golint
@echo Installing vet
go get -u -v golang.org/x/tools/cmd/vet
go get -u golang.org/x/tools/cmd/vet
go get -u github.com/mitchellh/gox
glide install
.PHONY: .project
.project:
......@@ -71,3 +84,4 @@ setup-gotools:
.PHONY: .docker
.docker:
@if [[ -z `which docker` ]] || ! docker version &> /dev/null; then echo "docker is not installed correctly"; exit 1; fi
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",
},
},
}
}
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
}
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
}
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",
},
},
}
}
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)
}
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)
}
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",
},
},
}
}
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{}
}
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
}
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)
}
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)
}
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...]",
}
}
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)
}
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
}
package main
import (
"errors"
"strconv"
"strings"
)
// TODO: The concept of property here is really simple. We could definitely get
// better about the values we allow. Also, we need some validation on the names.
var errInvalidProperty = errors.New("property is not in name=value format")
// parseProperties is a utility for parsing a comma-separated key=value string.
func parseProperties(kvstr string) (map[string]interface{}, error) {
properties := map[string]interface{}{}
if len(kvstr) == 0 {
return properties, nil
}
pairs := strings.Split(kvstr, ",")
for _, p := range pairs {
// Allow for "k=v, k=v"
p = strings.TrimSpace(p)
pair := strings.Split(p, "=")
if len(pair) == 1 {
return properties, errInvalidProperty
}
// If the value looks int-like, convert it.
if i, err := strconv.Atoi(pair[1]); err == nil {
properties[pair[0]] = pair[1]
} else {
properties[pair[0]] = i
}
}
return properties, nil
}
package 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.",
},
},
}
}
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.",
},
},
}
}
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",
},
},
}
}
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",
}
}
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
}
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: []
package: github.com/kubernetes/deployment-manager
import:
- package: github.com/Masterminds/semver
- package: github.com/aokoli/goutils
- package: github.com/codegangsta/cli
- package: github.com/emicklei/go-restful
- package: github.com/ghodss/yaml
- package: github.com/google/go-github
......@@ -9,3 +12,5 @@ import:
- package: github.com/gorilla/mux
- package: gopkg.in/yaml.v2
- package: github.com/Masterminds/semver
ignore:
- google.golang.com/appengine
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)
}
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)
}
}
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
`
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))
}
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)
}
}
}
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
}
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
}
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
}
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)
}
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
}
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)
}
}
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
}
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
}
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)
}
}
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{}
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
}
......@@ -2,7 +2,7 @@
set -o errexit
set -o pipefail
readonly ALL_TARGETS=(cmd/dm cmd/expandybird cmd/manager cmd/resourcifier)
readonly ALL_TARGETS=(cmd/dm cmd/expandybird cmd/helm cmd/manager cmd/resourcifier)
error_exit() {
# Display error message and exit
......
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.
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
THIS IS PLACEHOLDER TEXT.
# Frobnitz
This is an example chart.
## Usage
This is an example. It has no usage.
## Development
For developer info, see the top-level repository.
This is a placeholder for documentation.
<?xml version="1.0"?>
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.0" width="256" height="256" id="test">
<desc>Example icon</desc>
<rect id="first" x="2" y="2" width="40" height="60" fill="navy"/>
<rect id="second" x="15" y="4" width="40" height="60" fill="red"/>
</svg>
# 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
#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
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.
imports:
- path: wordpress.jinja
resources:
- name: wordpress
type: wordpress.jinja
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).
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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment