From 336386b6211ed63cbc9bd0e54fd6e4339bd383f0 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Thu, 7 Apr 2016 14:36:30 -0600 Subject: [PATCH 01/86] Initial commit --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..3aab6ef08 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# tiller +helmd From 0868355d3ec4ef5b4784b732056e7dc148b27784 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 8 Apr 2016 16:41:21 -0600 Subject: [PATCH 02/86] feat(cmd): add grpc client and server So far, they just have basic readiness checks. --- .gitignore | 1 + _proto/helm.proto | 22 +++++++ cmd/helmet/helmet.go | 40 ++++++++++++ cmd/tiller/server.go | 30 +++++++++ cmd/tiller/tiller.go | 24 ++++++++ docs/developers.md | 21 +++++++ glide.lock | 49 +++++++++++++++ glide.yaml | 6 ++ pkg/hapi/doc.go | 10 +++ pkg/hapi/helm.pb.go | 141 +++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 344 insertions(+) create mode 100644 .gitignore create mode 100644 _proto/helm.proto create mode 100644 cmd/helmet/helmet.go create mode 100644 cmd/tiller/server.go create mode 100644 cmd/tiller/tiller.go create mode 100644 docs/developers.md create mode 100644 glide.lock create mode 100644 glide.yaml create mode 100644 pkg/hapi/doc.go create mode 100644 pkg/hapi/helm.pb.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..48b8bf907 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +vendor/ diff --git a/_proto/helm.proto b/_proto/helm.proto new file mode 100644 index 000000000..c14fc00b1 --- /dev/null +++ b/_proto/helm.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +option java_package = "sh.helm"; + +// hapi: The Helm API +package hapi; + +// Probe is used to check liveness and readiness. +service Probe { + // Run a readiness test. + rpc Ready (PingRequest) returns (PingResponse) {} +} + +// The readiness test request. +message PingRequest { + string name = 1; +} + +// The readiness test response. +message PingResponse { + string status = 1; +} diff --git a/cmd/helmet/helmet.go b/cmd/helmet/helmet.go new file mode 100644 index 000000000..02fe1e450 --- /dev/null +++ b/cmd/helmet/helmet.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + + "github.com/codegangsta/cli" + "github.com/deis/tiller/pkg/hapi" + ctx "golang.org/x/net/context" + "google.golang.org/grpc" +) + +func main() { + app := cli.NewApp() + app.Name = "helmet" + app.Usage = "The Helm Easy Tester (HelmET)" + app.Action = run + + app.Run(os.Args) +} + +func run(c *cli.Context) { + conn, err := grpc.Dial("localhost:44134", grpc.WithInsecure()) + if err != nil { + fmt.Fprintf(os.Stderr, "Could not connect to server: %s\n", err) + os.Exit(1) + } + defer conn.Close() + + pc := hapi.NewProbeClient(conn) + + req := &hapi.PingRequest{Name: "helmet"} + res, err := pc.Ready(ctx.Background(), req) + if err != nil { + fmt.Fprintf(os.Stderr, "Error pinging server: %s\n", err) + os.Exit(1) + } + + fmt.Printf("Server is %s\n", res.Status) +} diff --git a/cmd/tiller/server.go b/cmd/tiller/server.go new file mode 100644 index 000000000..1ccb3abfc --- /dev/null +++ b/cmd/tiller/server.go @@ -0,0 +1,30 @@ +package main + +import ( + "net" + + "github.com/deis/tiller/pkg/hapi" + ctx "golang.org/x/net/context" + "google.golang.org/grpc" +) + +type server struct{} + +func (s *server) Ready(c ctx.Context, req *hapi.PingRequest) (*hapi.PingResponse, error) { + return &hapi.PingResponse{Status: "OK"}, nil +} + +func startServer(addr string) error { + lstn, err := net.Listen("tcp", addr) + if err != nil { + return nil + } + + hserver := &server{} + + srv := grpc.NewServer() + hapi.RegisterProbeServer(srv, hserver) + srv.Serve(lstn) + + return nil +} diff --git a/cmd/tiller/tiller.go b/cmd/tiller/tiller.go new file mode 100644 index 000000000..5fb3b524b --- /dev/null +++ b/cmd/tiller/tiller.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "os" + + "github.com/codegangsta/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "tiller" + app.Usage = `The Helm server.` + app.Action = start + + app.Run(os.Args) +} + +func start(c *cli.Context) { + if err := startServer(":44134"); err != nil { + fmt.Fprintf(os.Stderr, "Server died: %s\n", err) + os.Exit(1) + } +} diff --git a/docs/developers.md b/docs/developers.md new file mode 100644 index 000000000..c6975f6b8 --- /dev/null +++ b/docs/developers.md @@ -0,0 +1,21 @@ +# Developers Guide + +This guide explains how to set up your environment for developing on +Helm and Tiller. + +## Prerequisites + +Tiller uses gRPC. To get started with gRPC, you will need to... + +- Install `protoc` for compiling protobuf files. Releases are + [here](https://github.com/google/protobuf/releases) +- Install the protoc Go plugin: `go get -u github.com/golang/protobuf/protoc-gen-go` + +Note that you need to be on protobuf 3.x (`protoc --version`) and use the latest Go plugin. + +## The Helm API (HAPI) + +We use gRPC as an API layer. See `pkg/hapi` for the generated Go code, +and `_proto` for the protocol buffer definitions. + +To regenerate `hapi`, use `go generate pkg/hapi`. diff --git a/glide.lock b/glide.lock new file mode 100644 index 000000000..414732a28 --- /dev/null +++ b/glide.lock @@ -0,0 +1,49 @@ +hash: 9bf35c0cd5fd9a87324b8b410499579fd983072338d8b6522987b630353bf986 +updated: 2016-04-08T15:50:43.442411072-06:00 +imports: +- name: github.com/aokoli/goutils + version: 45307ec16e3cd47cd841506c081f7afd8237d210 +- name: github.com/cloudfoundry-incubator/candiedyaml + version: 5cef21e2e4f0fd147973b558d4db7395176bcd95 +- name: github.com/codegangsta/cli + version: 71f57d300dd6a780ac1856c005c4b518cfd498ec +- name: github.com/ghodss/yaml + version: 1a6f069841556a7bcaff4a397ca6e8328d266c2f +- name: github.com/golang/protobuf + version: dda510ac0fd43b39770f22ac6260eb91d377bce3 + subpackages: + - proto +- name: github.com/kubernetes/helm + version: d11dd488257d4ed3f5c861b9ca8d35cd3b49fa75 + subpackages: + - pkg/log + - pkg/common + - pkg/httputil + - pkg/kubectl + - pkg/version +- name: github.com/Masterminds/semver + version: 7e56b5cd00f9666fc899aa9c11c9a7d51f2a9995 +- name: github.com/Masterminds/sprig + version: 679bb747f11c6ffc3373965988fea8877c40b47b +- name: golang.org/x/net + version: e45385e9b226f570b1f086bf287b25d3d4117776 + subpackages: + - context + - http2 + - trace + - http2/hpack + - internal/timeseries +- name: google.golang.org/grpc + version: 7834b974e55fbf85a5b01afb5821391c71084efd + subpackages: + - codes + - credentials + - grpclog + - internal + - metadata + - naming + - transport + - peer +- name: gopkg.in/yaml.v2 + version: a83829b6f1293c91addabc89d0571c246397bbf4 +devImports: [] diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 000000000..29d5af970 --- /dev/null +++ b/glide.yaml @@ -0,0 +1,6 @@ +package: github.com/deis/tiller +import: +- package: google.golang.org/grpc +- package: golang.org/x/net + subpackages: + - context diff --git a/pkg/hapi/doc.go b/pkg/hapi/doc.go new file mode 100644 index 000000000..9f0ff7828 --- /dev/null +++ b/pkg/hapi/doc.go @@ -0,0 +1,10 @@ +//go:generate protoc -I ../../_proto ../../_proto/helm.proto --go_out=plugins=grpc:. +/*Package hapi contains the Helm API (HAPI). + +Helm uses gRPC to handle communication between client and server. This package +contains the definitions of the API objeccts. + +The files in this package that end with the extension *.pb.go are automatically +generated by Protobuf for use with gRPC. +*/ +package hapi diff --git a/pkg/hapi/helm.pb.go b/pkg/hapi/helm.pb.go new file mode 100644 index 000000000..e0f45463c --- /dev/null +++ b/pkg/hapi/helm.pb.go @@ -0,0 +1,141 @@ +// Code generated by protoc-gen-go. +// source: helm.proto +// DO NOT EDIT! + +/* +Package hapi is a generated protocol buffer package. + +hapi: The Helm API + +It is generated from these files: + helm.proto + +It has these top-level messages: + PingRequest + PingResponse +*/ +package hapi + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +// The readiness test request. +type PingRequest struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (m *PingRequest) Reset() { *m = PingRequest{} } +func (m *PingRequest) String() string { return proto.CompactTextString(m) } +func (*PingRequest) ProtoMessage() {} +func (*PingRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +// The readiness test response. +type PingResponse struct { + Status string `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` +} + +func (m *PingResponse) Reset() { *m = PingResponse{} } +func (m *PingResponse) String() string { return proto.CompactTextString(m) } +func (*PingResponse) ProtoMessage() {} +func (*PingResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func init() { + proto.RegisterType((*PingRequest)(nil), "hapi.PingRequest") + proto.RegisterType((*PingResponse)(nil), "hapi.PingResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion1 + +// Client API for Probe service + +type ProbeClient interface { + // Run a readiness test. + Ready(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) +} + +type probeClient struct { + cc *grpc.ClientConn +} + +func NewProbeClient(cc *grpc.ClientConn) ProbeClient { + return &probeClient{cc} +} + +func (c *probeClient) Ready(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) { + out := new(PingResponse) + err := grpc.Invoke(ctx, "/hapi.Probe/Ready", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Probe service + +type ProbeServer interface { + // Run a readiness test. + Ready(context.Context, *PingRequest) (*PingResponse, error) +} + +func RegisterProbeServer(s *grpc.Server, srv ProbeServer) { + s.RegisterService(&_Probe_serviceDesc, srv) +} + +func _Probe_Ready_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(PingRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(ProbeServer).Ready(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _Probe_serviceDesc = grpc.ServiceDesc{ + ServiceName: "hapi.Probe", + HandlerType: (*ProbeServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Ready", + Handler: _Probe_Ready_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +var fileDescriptor0 = []byte{ + // 149 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xca, 0x48, 0xcd, 0xc9, + 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9, 0x48, 0x2c, 0xc8, 0x54, 0x52, 0xe4, 0xe2, + 0x0e, 0xc8, 0xcc, 0x4b, 0x0f, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x12, 0xe2, 0x62, 0xc9, + 0x4b, 0xcc, 0x4d, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x95, 0xd4, 0xb8, 0x78, + 0x20, 0x4a, 0x8a, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x85, 0xc4, 0xb8, 0xd8, 0x8a, 0x4b, 0x12, 0x4b, + 0x4a, 0x8b, 0xa1, 0xaa, 0xa0, 0x3c, 0x23, 0x4b, 0x2e, 0xd6, 0x80, 0xa2, 0xfc, 0xa4, 0x54, 0x21, + 0x03, 0x2e, 0xd6, 0xa0, 0xd4, 0xc4, 0x94, 0x4a, 0x21, 0x41, 0x3d, 0x90, 0x1d, 0x7a, 0x48, 0x16, + 0x48, 0x09, 0x21, 0x0b, 0x41, 0x0c, 0x54, 0x62, 0x70, 0xe2, 0xe4, 0x62, 0x2f, 0xce, 0xd0, 0x03, + 0x39, 0x2e, 0x89, 0x0d, 0xec, 0x3a, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x04, 0x87, 0x54, + 0xa0, 0xab, 0x00, 0x00, 0x00, +} From 7a0413344e18c182ac1438ded7df4104a2890003 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Mon, 11 Apr 2016 13:26:33 -0600 Subject: [PATCH 03/86] feat(tiller): stub out environment --- README.md | 4 ++ _proto/helm.proto | 10 ++++ cmd/tiller/environment/environment.go | 56 ++++++++++++++++++++++ cmd/tiller/environment/environment_test.go | 32 +++++++++++++ docs/developers.md | 5 ++ pkg/hapi/helm.pb.go | 47 +++++++++++++++--- 6 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 cmd/tiller/environment/environment.go create mode 100644 cmd/tiller/environment/environment_test.go diff --git a/README.md b/README.md index 3aab6ef08..1ceae5fc6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # tiller helmd + +# Runtime Requirements + +- kubectl 1.2 or later diff --git a/_proto/helm.proto b/_proto/helm.proto index c14fc00b1..bf6f4bbeb 100644 --- a/_proto/helm.proto +++ b/_proto/helm.proto @@ -20,3 +20,13 @@ message PingRequest { message PingResponse { string status = 1; } + +message Chart { + string name = 1; +} +message Values { + string name = 1; +} +message Release { + string name = 1; +} diff --git a/cmd/tiller/environment/environment.go b/cmd/tiller/environment/environment.go new file mode 100644 index 000000000..e92eb77cc --- /dev/null +++ b/cmd/tiller/environment/environment.go @@ -0,0 +1,56 @@ +package environment + +import ( + "github.com/deis/tiller/pkg/hapi" +) + +// EngineYard maps engine names to engine implementations. +type EngineYard map[string]Engine + +func (y EngineYard) Get(k string) (Engine, bool) { + e, ok := y[k] + return e, ok +} + +// Engine represents a template engine that can render templates. +// +// For some engines, "rendering" includes both compiling and executing. (Other +// engines do not distinguish between phases.) +// +// An Engine must be capable of executing multiple concurrent requests, but +// without tainting one request's environment with data from another request. +type Engine interface { + Render(*hapi.Chart, *hapi.Values) ([]byte, error) +} + +// ReleaseStorage represents a storage engine for a Release. +// +// Release storage must be concurrency safe. +type ReleaseStorage interface { + Get(key string) (*hapi.Release, error) + Set(key string, val *hapi.Release) error +} + +// KubeClient represents a client capable of communicating with the Kubernetes API. +// +// A KubeClient must be concurrency safe. +type KubeClient interface { + Install(manifest []byte) error +} + +// Environment provides the context for executing a client request. +// +// All services in a context are concurrency safe. +type Environment struct { + // EngineYard provides access to the known template engines. + EngineYard EngineYard + // Releases stores records of releases. + Releases ReleaseStorage + // KubeClient is a Kubernetes API client. + KubeClient KubeClient +} + +// New returns an environment initialized with the defaults. +func New() *Environment { + return &Environment{} +} diff --git a/cmd/tiller/environment/environment_test.go b/cmd/tiller/environment/environment_test.go new file mode 100644 index 000000000..d265fd26a --- /dev/null +++ b/cmd/tiller/environment/environment_test.go @@ -0,0 +1,32 @@ +package environment + +import ( + "testing" + + "github.com/deis/tiller/pkg/hapi" +) + +type mockEngine struct { + out []byte +} + +func (e *mockEngine) Render(chrt *hapi.Chart, v *hapi.Values) ([]byte, error) { + return e.out, nil +} + +var _ Engine = &mockEngine{} + +func TestEngine(t *testing.T) { + eng := &mockEngine{out: []byte("test")} + + env := New() + env.EngineYard = EngineYard(map[string]Engine{"test": eng}) + + if engine, ok := env.EngineYard.Get("test"); !ok { + t.Errorf("failed to get engine from EngineYard") + } else if out, err := engine.Render(&hapi.Chart{}, &hapi.Values{}); err != nil { + t.Errorf("unexpected template error: %s", err) + } else if string(out) != "test" { + t.Errorf("expected 'test', got %q", string(out)) + } +} diff --git a/docs/developers.md b/docs/developers.md index c6975f6b8..73edcfa52 100644 --- a/docs/developers.md +++ b/docs/developers.md @@ -5,6 +5,11 @@ Helm and Tiller. ## Prerequisites +- Go 1.6.0 or later +- kubectl 1.2 or later +- A Kubernetes cluster (optional) +- The gRPC toolchain + Tiller uses gRPC. To get started with gRPC, you will need to... - Install `protoc` for compiling protobuf files. Releases are diff --git a/pkg/hapi/helm.pb.go b/pkg/hapi/helm.pb.go index e0f45463c..f8dd39fff 100644 --- a/pkg/hapi/helm.pb.go +++ b/pkg/hapi/helm.pb.go @@ -13,6 +13,9 @@ It is generated from these files: It has these top-level messages: PingRequest PingResponse + Chart + Values + Release */ package hapi @@ -54,9 +57,39 @@ func (m *PingResponse) String() string { return proto.CompactTextStri func (*PingResponse) ProtoMessage() {} func (*PingResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } +type Chart struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (m *Chart) Reset() { *m = Chart{} } +func (m *Chart) String() string { return proto.CompactTextString(m) } +func (*Chart) ProtoMessage() {} +func (*Chart) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } + +type Values struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (m *Values) Reset() { *m = Values{} } +func (m *Values) String() string { return proto.CompactTextString(m) } +func (*Values) ProtoMessage() {} +func (*Values) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +type Release struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` +} + +func (m *Release) Reset() { *m = Release{} } +func (m *Release) String() string { return proto.CompactTextString(m) } +func (*Release) ProtoMessage() {} +func (*Release) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } + func init() { proto.RegisterType((*PingRequest)(nil), "hapi.PingRequest") proto.RegisterType((*PingResponse)(nil), "hapi.PingResponse") + proto.RegisterType((*Chart)(nil), "hapi.Chart") + proto.RegisterType((*Values)(nil), "hapi.Values") + proto.RegisterType((*Release)(nil), "hapi.Release") } // Reference imports to suppress errors if they are not otherwise used. @@ -127,15 +160,17 @@ var _Probe_serviceDesc = grpc.ServiceDesc{ } var fileDescriptor0 = []byte{ - // 149 bytes of a gzipped FileDescriptorProto + // 182 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xca, 0x48, 0xcd, 0xc9, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9, 0x48, 0x2c, 0xc8, 0x54, 0x52, 0xe4, 0xe2, 0x0e, 0xc8, 0xcc, 0x4b, 0x0f, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x12, 0xe2, 0x62, 0xc9, 0x4b, 0xcc, 0x4d, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x95, 0xd4, 0xb8, 0x78, 0x20, 0x4a, 0x8a, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x85, 0xc4, 0xb8, 0xd8, 0x8a, 0x4b, 0x12, 0x4b, - 0x4a, 0x8b, 0xa1, 0xaa, 0xa0, 0x3c, 0x23, 0x4b, 0x2e, 0xd6, 0x80, 0xa2, 0xfc, 0xa4, 0x54, 0x21, - 0x03, 0x2e, 0xd6, 0xa0, 0xd4, 0xc4, 0x94, 0x4a, 0x21, 0x41, 0x3d, 0x90, 0x1d, 0x7a, 0x48, 0x16, - 0x48, 0x09, 0x21, 0x0b, 0x41, 0x0c, 0x54, 0x62, 0x70, 0xe2, 0xe4, 0x62, 0x2f, 0xce, 0xd0, 0x03, - 0x39, 0x2e, 0x89, 0x0d, 0xec, 0x3a, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x04, 0x87, 0x54, - 0xa0, 0xab, 0x00, 0x00, 0x00, + 0x4a, 0x8b, 0xa1, 0xaa, 0xa0, 0x3c, 0x25, 0x69, 0x2e, 0x56, 0xe7, 0x8c, 0xc4, 0x22, 0xec, 0x86, + 0xc8, 0x70, 0xb1, 0x85, 0x25, 0xe6, 0x00, 0xed, 0xc0, 0x2a, 0x2b, 0xcb, 0xc5, 0x1e, 0x94, 0x9a, + 0x93, 0x9a, 0x08, 0x34, 0x1d, 0x8b, 0xb4, 0x91, 0x25, 0x17, 0x6b, 0x40, 0x51, 0x7e, 0x52, 0xaa, + 0x90, 0x01, 0x17, 0x6b, 0x50, 0x6a, 0x62, 0x4a, 0xa5, 0x90, 0xa0, 0x1e, 0xc8, 0xf5, 0x7a, 0x48, + 0x4e, 0x97, 0x12, 0x42, 0x16, 0x82, 0x38, 0x55, 0x89, 0xc1, 0x89, 0x93, 0x8b, 0xbd, 0x38, 0x43, + 0x0f, 0xe4, 0xed, 0x24, 0x36, 0xb0, 0xbf, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x1e, 0x2f, + 0xf3, 0xed, 0x05, 0x01, 0x00, 0x00, } From 456ddeaa73e624dadf4bec9e65756e98b867e820 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Mon, 11 Apr 2016 15:22:18 -0600 Subject: [PATCH 04/86] fix(glide): update to non-broken glide.lock. --- glide.lock | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/glide.lock b/glide.lock index 414732a28..915c93e3b 100644 --- a/glide.lock +++ b/glide.lock @@ -1,32 +1,14 @@ hash: 9bf35c0cd5fd9a87324b8b410499579fd983072338d8b6522987b630353bf986 -updated: 2016-04-08T15:50:43.442411072-06:00 +updated: 2016-04-11T15:22:12.013812164-06:00 imports: -- name: github.com/aokoli/goutils - version: 45307ec16e3cd47cd841506c081f7afd8237d210 -- name: github.com/cloudfoundry-incubator/candiedyaml - version: 5cef21e2e4f0fd147973b558d4db7395176bcd95 - name: github.com/codegangsta/cli version: 71f57d300dd6a780ac1856c005c4b518cfd498ec -- name: github.com/ghodss/yaml - version: 1a6f069841556a7bcaff4a397ca6e8328d266c2f - name: github.com/golang/protobuf version: dda510ac0fd43b39770f22ac6260eb91d377bce3 subpackages: - proto -- name: github.com/kubernetes/helm - version: d11dd488257d4ed3f5c861b9ca8d35cd3b49fa75 - subpackages: - - pkg/log - - pkg/common - - pkg/httputil - - pkg/kubectl - - pkg/version -- name: github.com/Masterminds/semver - version: 7e56b5cd00f9666fc899aa9c11c9a7d51f2a9995 -- name: github.com/Masterminds/sprig - version: 679bb747f11c6ffc3373965988fea8877c40b47b - name: golang.org/x/net - version: e45385e9b226f570b1f086bf287b25d3d4117776 + version: 589fda73dd0faec3dc59e7d7dab5b069e3fce0f9 subpackages: - context - http2 @@ -34,7 +16,7 @@ imports: - http2/hpack - internal/timeseries - name: google.golang.org/grpc - version: 7834b974e55fbf85a5b01afb5821391c71084efd + version: d07d0562ffca36dd7ee333b5d236209f98fe9ba0 subpackages: - codes - credentials @@ -44,6 +26,4 @@ imports: - naming - transport - peer -- name: gopkg.in/yaml.v2 - version: a83829b6f1293c91addabc89d0571c246397bbf4 devImports: [] From ce1185faa8375eadb429882d57c5d400e180e01c Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Mon, 11 Apr 2016 16:25:39 -0600 Subject: [PATCH 05/86] feat(helm): add initial helm client --- cmd/helm/helm.go | 19 +++++++++++++++++++ cmd/helm/init.go | 22 ++++++++++++++++++++++ glide.lock | 10 ++++++++-- glide.yaml | 3 +++ 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 cmd/helm/helm.go create mode 100644 cmd/helm/init.go diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go new file mode 100644 index 000000000..785dac967 --- /dev/null +++ b/cmd/helm/helm.go @@ -0,0 +1,19 @@ +package main + +import ( + "os" + + "github.com/spf13/cobra" +) + +var stdout = os.Stdout + +var RootCommand = &cobra.Command{ + Use: "helm", + Short: "The Helm package manager for Kubernetes.", + Long: `Do long help here.`, +} + +func main() { + RootCommand.Execute() +} diff --git a/cmd/helm/init.go b/cmd/helm/init.go new file mode 100644 index 000000000..5679de8e2 --- /dev/null +++ b/cmd/helm/init.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func init() { + RootCommand.AddCommand(initCmd) +} + +var initCmd = &cobra.Command{ + Use: "init", + Short: "Initialize Helm on both client and server.", + Long: `Add long help here`, + Run: runInit, +} + +func runInit(cmd *cobra.Command, args []string) { + fmt.Fprintln(stdout, "Init was called.") +} diff --git a/glide.lock b/glide.lock index 915c93e3b..2a820a40d 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 9bf35c0cd5fd9a87324b8b410499579fd983072338d8b6522987b630353bf986 -updated: 2016-04-11T15:22:12.013812164-06:00 +hash: 692bf17990973cf424c2dbfda6afee8b7002bbc07aaaa78372eac447fed4c8b0 +updated: 2016-04-11T16:13:14.192550144-06:00 imports: - name: github.com/codegangsta/cli version: 71f57d300dd6a780ac1856c005c4b518cfd498ec @@ -7,6 +7,12 @@ imports: version: dda510ac0fd43b39770f22ac6260eb91d377bce3 subpackages: - proto +- name: github.com/spf13/cobra + version: 4c05eb1145f16d0e6bb4a3e1b6d769f4713cb41f + subpackages: + - cobra +- name: github.com/spf13/pflag + version: 1f296710f879815ad9e6d39d947c828c3e4b4c3d - name: golang.org/x/net version: 589fda73dd0faec3dc59e7d7dab5b069e3fce0f9 subpackages: diff --git a/glide.yaml b/glide.yaml index 29d5af970..bb9ca005c 100644 --- a/glide.yaml +++ b/glide.yaml @@ -4,3 +4,6 @@ import: - package: golang.org/x/net subpackages: - context +- package: github.com/spf13/cobra + subpackages: + - cobra From 548dc31fc558e2c89277e4b3c08f86035b9c0138 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Mon, 11 Apr 2016 17:43:14 -0600 Subject: [PATCH 06/86] feat(proto): first protos for helm --- _proto/aaa.proto | 53 ++++++++++++++++++++++++++++++++++++++++++ _proto/chartfile.proto | 3 +++ _proto/get.proto | 20 ++++++++++++++++ _proto/helm.proto | 38 +++++++++++++++++++++--------- _proto/install.proto | 20 ++++++++++++++++ _proto/list.proto | 20 ++++++++++++++++ _proto/status.proto | 17 ++++++++++++++ _proto/uninstall.proto | 17 ++++++++++++++ 8 files changed, 177 insertions(+), 11 deletions(-) create mode 100644 _proto/aaa.proto create mode 100644 _proto/chartfile.proto create mode 100644 _proto/get.proto create mode 100644 _proto/install.proto create mode 100644 _proto/list.proto create mode 100644 _proto/status.proto create mode 100644 _proto/uninstall.proto diff --git a/_proto/aaa.proto b/_proto/aaa.proto new file mode 100644 index 000000000..162b94b77 --- /dev/null +++ b/_proto/aaa.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; +package hapi; + +message Chartfile { + string name = 1; + string version = 2; +} + +message Chart { + // Option 1: Chart is raw []byte data + // Option 2: List of files as []byte data, with special treatment for Chart.yaml + // Option 3: Completely parsed out (probably very inflexible, ultimately) + + // Option 2: + Chartfile chartfile = 1; + Values defaultValues = 2; + map templates = 3; // filename => []bytes + repeated Chart charts = 4; +} + +// Values represents a set of values that will be passed into the template. +message Values { + // Option 1: "values" is unparsed TOML data (raw []byte) + // Option 2: Model TOML in Protobuf (not _too_ bad) + // Option 3: Force everything into a map[string]string model +} + +message Release { + string name = 1; +} + +message Status { + StatusCode code = 1; + string msg = 2; +} + +message Error { + ErrorCode errror_code = 1; + string error_msg = 2; +} + +enum ErrorCode { + ERROR_CODE_UNSET = 0; + BAD_REQUEST = 1; +} + +enum StatusCode { + STATUS_CODE_UNSET = 0; + UNKNOWN = 1; + DEPLOYED = 2; + DELETED = 3; + SUPERSEDED = 4; +} diff --git a/_proto/chartfile.proto b/_proto/chartfile.proto new file mode 100644 index 000000000..df84dd387 --- /dev/null +++ b/_proto/chartfile.proto @@ -0,0 +1,3 @@ +syntax = "proto3"; +package hapi; + diff --git a/_proto/get.proto b/_proto/get.proto new file mode 100644 index 000000000..e66fcc03e --- /dev/null +++ b/_proto/get.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package hapi; + +import "aaa.proto"; + +message GetRequest { + string name = 1; +} + +message GetResponseError { + oneof response { + Error err = 1; + GetResponse get_response = 2; + } +} + +message GetResponse { + Chart chart = 1; + Values values = 2; +} diff --git a/_proto/helm.proto b/_proto/helm.proto index bf6f4bbeb..7abfe35e3 100644 --- a/_proto/helm.proto +++ b/_proto/helm.proto @@ -1,10 +1,36 @@ syntax = "proto3"; -option java_package = "sh.helm"; +/* +// No server +helm init +helm create +helm fetch // Fetch chart from repo +helm search +helm package + +// Server + +// Releases +helm install CHART +helm list +helm uninstall RELEASE +helm status RELEASE +helm get RELEASE +helm update RELEASE +*/ // hapi: The Helm API package hapi; +service ReleaseService { + rpc Install (InstallRequest) returns (InstallResponseError) {} + rpc List (ListRequest) returns (ListResponseError) {} + rpc Uninstall (UninstallRequest) returns (UninstallResponseError) {} + rpc Status (StatusRequest) returns (StatusResponseError) {} + rpc Get (GetRequest) returns (GetResponseError) {} + // rpc Update (UpdateRequest) returns (UpdateResponseError) {} +} + // Probe is used to check liveness and readiness. service Probe { // Run a readiness test. @@ -20,13 +46,3 @@ message PingRequest { message PingResponse { string status = 1; } - -message Chart { - string name = 1; -} -message Values { - string name = 1; -} -message Release { - string name = 1; -} diff --git a/_proto/install.proto b/_proto/install.proto new file mode 100644 index 000000000..1d6459671 --- /dev/null +++ b/_proto/install.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package hapi; + +message InstallRequest{ + string name = 1 + Chart chart = 2 + Values values = 3 +} + +message InstallResponseError { + oneof response { + Error = 1 + InstallResponse = 2 + } +} + +message InstallResponse{ + string name = 1 + Status status = 2 +} diff --git a/_proto/list.proto b/_proto/list.proto new file mode 100644 index 000000000..6dc0b9f5d --- /dev/null +++ b/_proto/list.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package hapi; + +message ListRequest { + int64 limit = 1; + int64 offset = 2; +} + +message ListResponseError { + oneof response { + Error = 1 + ListResponse = 2 + } +} +message ListResponse { + repeated Release releases = 1; + int64 count = 2; + int64 offset = 3; + int64 total = 4; +} diff --git a/_proto/status.proto b/_proto/status.proto new file mode 100644 index 000000000..87ae4d5f9 --- /dev/null +++ b/_proto/status.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; +package hapi; + +message StatusRequest { + string name = 1 +} + +message StatusResponseError { + oneof response { + Error = 1 + StatusResponse = 2 + } +} +message StatusResponse { + Release release = 1; + Status status = 2 +} diff --git a/_proto/uninstall.proto b/_proto/uninstall.proto new file mode 100644 index 000000000..60a348f3d --- /dev/null +++ b/_proto/uninstall.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; +package hapi; + +message UninstallRequest{ + string name = 1 +} + +message UninstallResponseError { + oneof response { + Error = 1 + UninstallResponse = 2 + } +} + +message UninstallResponse{ + Status status = 1 +} From 610c6ced093552ce710bf7cc4226f59fddd07c3e Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 12 Apr 2016 11:53:42 -0600 Subject: [PATCH 07/86] fix(environment): add tests for Environment --- cmd/tiller/environment/environment_test.go | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/cmd/tiller/environment/environment_test.go b/cmd/tiller/environment/environment_test.go index d265fd26a..df240f209 100644 --- a/cmd/tiller/environment/environment_test.go +++ b/cmd/tiller/environment/environment_test.go @@ -14,7 +14,29 @@ func (e *mockEngine) Render(chrt *hapi.Chart, v *hapi.Values) ([]byte, error) { return e.out, nil } +type mockReleaseStorage struct { + rel *hapi.Release +} + +func (r *mockReleaseStorage) Get(k string) (*hapi.Release, error) { + return r.rel, nil +} + +func (r *mockReleaseStorage) Set(k string, v *hapi.Release) error { + r.rel = v + return nil +} + +type mockKubeClient struct { +} + +func (k *mockKubeClient) Install(manifest []byte) error { + return nil +} + var _ Engine = &mockEngine{} +var _ ReleaseStorage = &mockReleaseStorage{} +var _ KubeClient = &mockKubeClient{} func TestEngine(t *testing.T) { eng := &mockEngine{out: []byte("test")} @@ -30,3 +52,31 @@ func TestEngine(t *testing.T) { t.Errorf("expected 'test', got %q", string(out)) } } + +func TestReleaseStorage(t *testing.T) { + rs := &mockReleaseStorage{} + env := New() + env.Releases = rs + + release := &hapi.Release{Name: "mariner"} + + if err := env.Releases.Set("albatross", release); err != nil { + t.Fatalf("failed to store release: %s", err) + } + + if v, err := env.Releases.Get("albatross"); err != nil { + t.Errorf("Error fetching release: %s", err) + } else if v.Name != "mariner" { + t.Errorf("Expected mariner, got %q", v.Name) + } +} + +func TestKubeClient(t *testing.T) { + kc := &mockKubeClient{} + env := New() + env.KubeClient = kc + + if err := env.KubeClient.Install([]byte("apiVersion: v1\n")); err != nil { + t.Errorf("Kubeclient failed: %s", err) + } +} From b86a1cb94eaa9af356041c814a63e4503f10d52f Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 12 Apr 2016 12:23:44 -0600 Subject: [PATCH 08/86] ifeat(tiller): add environment to server struct --- cmd/tiller/server.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/cmd/tiller/server.go b/cmd/tiller/server.go index 1ccb3abfc..5a98244db 100644 --- a/cmd/tiller/server.go +++ b/cmd/tiller/server.go @@ -3,24 +3,40 @@ package main import ( "net" + "github.com/deis/tiller/cmd/tiller/environment" "github.com/deis/tiller/pkg/hapi" ctx "golang.org/x/net/context" "google.golang.org/grpc" ) -type server struct{} +type server struct { + Environment *environment.Environment +} + +// newServer creates a new server with the default environment. +// +// TODO: This can take a configuration object of some sort so that we can +// initialize the environment with the correct stuff. +func newServer() *server { + return &server{ + Environment: environment.New(), + } +} func (s *server) Ready(c ctx.Context, req *hapi.PingRequest) (*hapi.PingResponse, error) { return &hapi.PingResponse{Status: "OK"}, nil } +// startServer starts a new gRPC server listening on the given address. +// +// addr must conform to the requirements of "net.Listen". func startServer(addr string) error { lstn, err := net.Listen("tcp", addr) if err != nil { return nil } - hserver := &server{} + hserver := newServer() srv := grpc.NewServer() hapi.RegisterProbeServer(srv, hserver) From b7b48ff72b01760507e60ae7c54d574b0f855860 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 12 Apr 2016 12:41:54 -0600 Subject: [PATCH 09/86] feat(proto): stub out a Chartfile message --- .gitignore | 1 + _proto/aaa.proto | 5 ----- _proto/chartfile.proto | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 48b8bf907..d872985e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ vendor/ +_proto/*.pb.go diff --git a/_proto/aaa.proto b/_proto/aaa.proto index 162b94b77..4db9d8f94 100644 --- a/_proto/aaa.proto +++ b/_proto/aaa.proto @@ -1,11 +1,6 @@ syntax = "proto3"; package hapi; -message Chartfile { - string name = 1; - string version = 2; -} - message Chart { // Option 1: Chart is raw []byte data // Option 2: List of files as []byte data, with special treatment for Chart.yaml diff --git a/_proto/chartfile.proto b/_proto/chartfile.proto index df84dd387..07921f624 100644 --- a/_proto/chartfile.proto +++ b/_proto/chartfile.proto @@ -1,3 +1,36 @@ syntax = "proto3"; package hapi; +// Maintainer is a chart maintainer +message Maintainer { + // Name is a user name or organization name + string name = 1; + // Email is an optional email address to contact the named maintainer + string email = 2; +} + +// Chartfile represents the structure of a Chart.yaml file. +// +// Spec: https://github.com/kubernetes/helm/blob/master/docs/design/chart_format.md#the-chart-file +// +// Fields: +// - name: The name of the chart +// - verison: The SemVer 2 conformant version of the chart +// - description: A once-sentence description of the chart +// - keywords: A list of string keywords +message Chartfile { + // Name is the name of the chart + string name = 1; + // Version is the SemVer 2 version of the chart + string version = 2; + // Description is a sentence describing the chart + string description = 3; + // Keywords is a list of keywords describing the chart + repeated string keywords = 4; + // Maintainers is the set of maintainers of this chart + repeated Maintainer maintainers = 5; + // Source is the URL to the source code of this chart + string source = 6; + // Home is the URL to the chart's home page + string home = 7; +} From 6092f01fac0770009cd2cdad9541dd475c254d38 Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Tue, 12 Apr 2016 14:03:25 -0700 Subject: [PATCH 10/86] feat(Makefile): add Makefile --- .gitignore | 2 ++ Makefile | 44 ++++++++++++++++++++++++++++++++++++++++++ scripts/coverage.sh | 22 +++++++++++++++++++++ scripts/validate-go.sh | 41 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+) create mode 100644 Makefile create mode 100755 scripts/coverage.sh create mode 100755 scripts/validate-go.sh diff --git a/.gitignore b/.gitignore index 48b8bf907..46d7bc398 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ +.coverage/ +bin/ vendor/ diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..13fb3a4bc --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +GO ?= go + +PKG := $(shell glide novendor) +TAGS := +TESTS := . +TESTFLAGS := +LDFLAGS := + +BINARIES := helm tiller + +.PHONY: all +all: build + +.PHONY: build +build: + @for i in $(BINARIES); do \ + $(GO) build -o ./bin/$$i -v $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/$$i || exit 1; \ + done + +.PHONY: test +test: TESTFLAGS += -race -v +test: test-style +test: test-unit + +.PHONY: test-unit +test-unit: + $(GO) test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS) + +.PHONY: test-style +test-style: + @scripts/validate-go.sh + +.PHONY: clean +clean: + rm -rf ./bin + +.PHONY: coverage +coverage: + @scripts/coverage.sh + +.PHONY: bootstrap +bootstrap: + glide install + diff --git a/scripts/coverage.sh b/scripts/coverage.sh new file mode 100755 index 000000000..9d6763a1e --- /dev/null +++ b/scripts/coverage.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +COVERDIR=${COVERDIR:-.coverage} +COVERMODE=${COVERMODE:-atomic} +PACKAGES=($(go list $(glide novendor))) + +if [[ ! -d "$COVERDIR" ]]; then + mkdir -p "$COVERDIR" +fi + +echo "mode: ${COVERMODE}" > "${COVERDIR}/coverage.out" + +for d in "${PACKAGES[@]}"; do + go test -coverprofile=profile.out -covermode="$COVERMODE" "$d" + if [ -f profile.out ]; then + sed "/mode: $COVERMODE/d" profile.out >> "${COVERDIR}/coverage.out" + rm profile.out + fi +done + +go tool cover -html "${COVERDIR}/coverage.out" -o "${COVERDIR}/coverage.html" diff --git a/scripts/validate-go.sh b/scripts/validate-go.sh new file mode 100755 index 000000000..3c3dd2ae8 --- /dev/null +++ b/scripts/validate-go.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +readonly reset=$(tput sgr0) +readonly red=$(tput bold; tput setaf 1) +readonly green=$(tput bold; tput setaf 2) +readonly yellow=$(tput bold; tput setaf 3) + +exit_code=0 + +find_go_files() { + find . -type f -name "*.go" | grep -v vendor +} + +hash golint 2>/dev/null || go get -u github.com/golang/lint/golint +hash go vet 2>/dev/null || go get -u golang.org/x/tools/cmd/vet + +echo "==> Running golint..." +for pkg in $(glide nv); do + if golint_out=$(golint "$pkg" 2>&1); then + echo "${yellow}${golint_out}${reset}" + fi +done + +echo "==> Running go vet..." +echo -n "$red" +go vet $(glide nv) 2>&1 | grep -v "^exit status " || exit_code=${PIPESTATUS[0]} +echo -n "$reset" + +echo "==> Running gofmt..." +failed_fmt=$(find_go_files | xargs gofmt -s -l) +if [[ -n "${failed_fmt}" ]]; then + echo -n "${red}" + echo "gofmt check failed:" + echo "$failed_fmt" + gofmt -s -d "${failed_fmt}" + echo -n "${reset}" + exit_code=1 +fi + +exit ${exit_code} From 17c0ed972caba0e0da48d6cf41157b6bbbfe9fbc Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Tue, 12 Apr 2016 14:13:56 -0700 Subject: [PATCH 11/86] feat(ci): add circleci build configuration --- circle.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 circle.yml diff --git a/circle.yml b/circle.yml new file mode 100644 index 000000000..2d7c7a450 --- /dev/null +++ b/circle.yml @@ -0,0 +1,31 @@ +machine: + environment: + GLIDE_VERSION: "0.10.1" + GO15VENDOREXPERIMENT: 1 + GOPATH: /usr/local/go_workspace + HOME: /home/ubuntu + IMPORT_PATH: "github.com/deis/tiller" + PATH: $HOME/go/bin:$PATH + GOROOT: $HOME/go + +dependencies: + override: + - mkdir -p $HOME/go + - wget "https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz" + - tar -C $HOME -xzf go1.6.linux-amd64.tar.gz + - go version + - go env + - sudo chown -R $(whoami):staff /usr/local + - cd $GOPATH + - mkdir -p $GOPATH/src/$IMPORT_PATH + - cd $HOME/tiller + - rsync -az --delete ./ "$GOPATH/src/$IMPORT_PATH/" + - 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" + - cd $GOPATH/src/$IMPORT_PATH + +test: + override: + - cd $GOPATH/src/$IMPORT_PATH && make bootstrap test From 5207186ee96dc843b46f3d7a025d8345d9d08ce4 Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Tue, 12 Apr 2016 15:30:38 -0700 Subject: [PATCH 12/86] fix(scripts): go vet is built into go --- scripts/validate-go.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/validate-go.sh b/scripts/validate-go.sh index 3c3dd2ae8..a99af956e 100755 --- a/scripts/validate-go.sh +++ b/scripts/validate-go.sh @@ -13,7 +13,6 @@ find_go_files() { } hash golint 2>/dev/null || go get -u github.com/golang/lint/golint -hash go vet 2>/dev/null || go get -u golang.org/x/tools/cmd/vet echo "==> Running golint..." for pkg in $(glide nv); do From fa387494fb5d454501944c5b7f563cc8ba91b291 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 12 Apr 2016 16:18:42 -0600 Subject: [PATCH 13/86] feat(engine): add template engine --- glide.lock | 8 +++- glide.yaml | 1 + pkg/engine/doc.go | 7 ++++ pkg/engine/engine.go | 73 ++++++++++++++++++++++++++++++++++ pkg/engine/engine_test.go | 82 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 169 insertions(+), 2 deletions(-) create mode 100644 pkg/engine/doc.go create mode 100644 pkg/engine/engine.go create mode 100644 pkg/engine/engine_test.go diff --git a/glide.lock b/glide.lock index 2a820a40d..0aaa8649f 100644 --- a/glide.lock +++ b/glide.lock @@ -1,12 +1,16 @@ -hash: 692bf17990973cf424c2dbfda6afee8b7002bbc07aaaa78372eac447fed4c8b0 -updated: 2016-04-11T16:13:14.192550144-06:00 +hash: 1dcbbc192182125021b40497fcf9d52bc643455a3bb03d6cd3458819fcb03dbb +updated: 2016-04-12T13:27:50.987288211-06:00 imports: +- name: github.com/aokoli/goutils + version: 9c37978a95bd5c709a15883b6242714ea6709e64 - name: github.com/codegangsta/cli version: 71f57d300dd6a780ac1856c005c4b518cfd498ec - name: github.com/golang/protobuf version: dda510ac0fd43b39770f22ac6260eb91d377bce3 subpackages: - proto +- name: github.com/Masterminds/sprig + version: 679bb747f11c6ffc3373965988fea8877c40b47b - name: github.com/spf13/cobra version: 4c05eb1145f16d0e6bb4a3e1b6d769f4713cb41f subpackages: diff --git a/glide.yaml b/glide.yaml index bb9ca005c..dab2d29c8 100644 --- a/glide.yaml +++ b/glide.yaml @@ -7,3 +7,4 @@ import: - package: github.com/spf13/cobra subpackages: - cobra +- package: github.com/Masterminds/sprig diff --git a/pkg/engine/doc.go b/pkg/engine/doc.go new file mode 100644 index 000000000..e657b1734 --- /dev/null +++ b/pkg/engine/doc.go @@ -0,0 +1,7 @@ +/*Package engine implements the Go template engine as a Tiller Engine. + +Tiller provides a simple interface for taking a Chart and rendering its templates. +The 'engine' package implements this interface using Go's built-in 'text/template' +package. +*/ +package engine diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go new file mode 100644 index 000000000..067d6b98a --- /dev/null +++ b/pkg/engine/engine.go @@ -0,0 +1,73 @@ +package engine + +import ( + "bytes" + "fmt" + "text/template" + + "github.com/Masterminds/sprig" + "github.com/deis/tiller/pkg/hapi" +) + +type Engine struct { + FuncMap template.FuncMap +} + +// New creates a new Go template Engine instance. +// +// The FuncMap is initialized here. You may modify the FuncMap _prior to_ the +// first invocation of Render. +// +// The FuncMap sets all of the Sprig functions except for those that provide +// access to the underlying OS (env, expandenv). +func New() *Engine { + f := sprig.TxtFuncMap() + delete(f, "env") + delete(f, "expandenv") + return &Engine{ + FuncMap: f, + } +} + +// Render takes a chart, optional values, and attempts to render the Go templates. +// +// Render can be called repeatedly on the same engine. +// +// This will look in the chart's 'templates' data (e.g. the 'templates/' directory) +// and attempt to render the templates there using the values passed in. +func (e *Engine) Render(chart *hapi.Chart, vals *hapi.Values) (map[string]string, error) { + // Uncomment this once the proto files compile. + //return render(chart.Chartfile.Name, chart.Templates, vals) + return map[string]string{}, nil +} + +func (e *Engine) render(name string, tpls map[string]string, v interface{}) (map[string]string, error) { + // Basically, what we do here is start with an empty parent template and then + // build up a list of templates -- one for each file. Once all of the templates + // have been parsed, we loop through again and execute every template. + // + // The idea with this process is to make it possible for more complex templates + // to share common blocks, but to make the entire thing feel like a file-based + // template engine. + t := template.New(name) + files := []string{} + for fname, tpl := range tpls { + t = t.New(fname).Funcs(e.FuncMap) + if _, err := t.Parse(tpl); err != nil { + return map[string]string{}, fmt.Errorf("parse error in %q: %s", fname, err) + } + files = append(files, fname) + } + + rendered := make(map[string]string, len(files)) + var buf bytes.Buffer + for _, file := range files { + if err := t.ExecuteTemplate(&buf, file, v); err != nil { + return map[string]string{}, fmt.Errorf("render error in %q: %s", file, err) + } + rendered[file] = buf.String() + buf.Reset() + } + + return rendered, nil +} diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go new file mode 100644 index 000000000..b0a00794b --- /dev/null +++ b/pkg/engine/engine_test.go @@ -0,0 +1,82 @@ +package engine + +import ( + "fmt" + "sync" + "testing" +) + +func TestEngine(t *testing.T) { + e := New() + + // Forbidden because they allow access to the host OS. + forbidden := []string{"env", "expandenv"} + for _, f := range forbidden { + if _, ok := e.FuncMap[f]; ok { + t.Errorf("Forbidden function %s exists in FuncMap.", f) + } + } +} + +func TestRender(t *testing.T) { + t.Skip() +} + +func TestRenderInternals(t *testing.T) { + // Test the internals of the rendering tool. + e := New() + + tpls := map[string]string{ + "one": `Hello {{title .Name}}`, + "two": `Goodbye {{upper .Value}}`, + // Test whether a template can reliably reference another template + // without regard for ordering. + "three": `{{template "two" dict "Value" "three"}}`, + } + vals := map[string]string{"Name": "one", "Value": "two"} + + out, err := e.render("irrelevant", tpls, vals) + if err != nil { + t.Fatalf("Failed template rendering: %s", err) + } + + if len(out) != 3 { + t.Fatalf("Expected 3 templates, got %d", len(out)) + } + + if out["one"] != "Hello One" { + t.Errorf("Expected 'Hello One', got %q", out["one"]) + } + + if out["two"] != "Goodbye TWO" { + t.Errorf("Expected 'Goodbye TWO'. got %q", out["two"]) + } + + if out["three"] != "Goodbye THREE" { + t.Errorf("Expected 'Goodbye THREE'. got %q", out["two"]) + } +} + +func TestParallelRenderInternals(t *testing.T) { + // Make sure that we can use one Engine to run parallel template renders. + e := New() + var wg sync.WaitGroup + for i := 0; i < 20; i++ { + wg.Add(1) + go func(i int) { + fname := "my/file/name" + tt := fmt.Sprintf("expect-%d", i) + tpls := map[string]string{fname: `{{.val}}`} + v := map[string]string{"val": tt} + out, err := e.render("intentionally_duplicated", tpls, v) + if err != nil { + t.Errorf("Failed to render %s: %s", tt, err) + } + if out[fname] != tt { + t.Errorf("Expected %q, got %q", tt, out[fname]) + } + wg.Done() + }(i) + } + wg.Wait() +} From 913905a54fd27e9bc388f50b4fd2e5951ea27aea Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 12 Apr 2016 16:58:53 -0600 Subject: [PATCH 14/86] fix(tiller): change environment.Engine signature --- cmd/tiller/environment/environment.go | 5 ++++- cmd/tiller/environment/environment_test.go | 10 +++++----- pkg/engine/engine_test.go | 4 ++++ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/cmd/tiller/environment/environment.go b/cmd/tiller/environment/environment.go index e92eb77cc..f7d863b6d 100644 --- a/cmd/tiller/environment/environment.go +++ b/cmd/tiller/environment/environment.go @@ -17,10 +17,13 @@ func (y EngineYard) Get(k string) (Engine, bool) { // For some engines, "rendering" includes both compiling and executing. (Other // engines do not distinguish between phases.) // +// The engine returns a map where the key is the named output entity (usually +// a file name) and the value is the rendered content of the template. +// // An Engine must be capable of executing multiple concurrent requests, but // without tainting one request's environment with data from another request. type Engine interface { - Render(*hapi.Chart, *hapi.Values) ([]byte, error) + Render(*hapi.Chart, *hapi.Values) (map[string]string, error) } // ReleaseStorage represents a storage engine for a Release. diff --git a/cmd/tiller/environment/environment_test.go b/cmd/tiller/environment/environment_test.go index df240f209..e42376f6e 100644 --- a/cmd/tiller/environment/environment_test.go +++ b/cmd/tiller/environment/environment_test.go @@ -7,10 +7,10 @@ import ( ) type mockEngine struct { - out []byte + out map[string]string } -func (e *mockEngine) Render(chrt *hapi.Chart, v *hapi.Values) ([]byte, error) { +func (e *mockEngine) Render(chrt *hapi.Chart, v *hapi.Values) (map[string]string, error) { return e.out, nil } @@ -39,7 +39,7 @@ var _ ReleaseStorage = &mockReleaseStorage{} var _ KubeClient = &mockKubeClient{} func TestEngine(t *testing.T) { - eng := &mockEngine{out: []byte("test")} + eng := &mockEngine{out: map[string]string{"albatross": "test"}} env := New() env.EngineYard = EngineYard(map[string]Engine{"test": eng}) @@ -48,8 +48,8 @@ func TestEngine(t *testing.T) { t.Errorf("failed to get engine from EngineYard") } else if out, err := engine.Render(&hapi.Chart{}, &hapi.Values{}); err != nil { t.Errorf("unexpected template error: %s", err) - } else if string(out) != "test" { - t.Errorf("expected 'test', got %q", string(out)) + } else if out["albatross"] != "test" { + t.Errorf("expected 'test', got %q", out["albatross"]) } } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index b0a00794b..613804ef3 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -4,8 +4,12 @@ import ( "fmt" "sync" "testing" + + "github.com/deis/tiller/cmd/tiller/environment" ) +var _ environment.Engine = &Engine{} + func TestEngine(t *testing.T) { e := New() From b04a1cc7d41e5f5b2209ab28f3d369e4317ca94f Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 13 Apr 2016 11:55:27 -0600 Subject: [PATCH 15/86] fix(tiller): set up Tiller environment correctly And structure the tests better. --- cmd/tiller/environment/environment.go | 25 ++++++++++++++++++++++++- pkg/engine/engine_test.go | 4 ---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/cmd/tiller/environment/environment.go b/cmd/tiller/environment/environment.go index f7d863b6d..f651800c0 100644 --- a/cmd/tiller/environment/environment.go +++ b/cmd/tiller/environment/environment.go @@ -1,9 +1,14 @@ package environment import ( + "github.com/deis/tiller/pkg/engine" "github.com/deis/tiller/pkg/hapi" ) +const GoTplEngine = "gotpl" + +var DefaultEngine = GoTplEngine + // EngineYard maps engine names to engine implementations. type EngineYard map[string]Engine @@ -12,6 +17,20 @@ func (y EngineYard) Get(k string) (Engine, bool) { return e, ok } +// Default returns the default template engine. +// +// The default is specified by DefaultEngine. +// +// If the default template engine cannot be found, this panics. +func (y EngineYard) Default() Engine { + d, ok := y[DefaultEngine] + if !ok { + // This is a developer error! + panic("Default template engine does not exist") + } + return d +} + // Engine represents a template engine that can render templates. // // For some engines, "rendering" includes both compiling and executing. (Other @@ -55,5 +74,9 @@ type Environment struct { // New returns an environment initialized with the defaults. func New() *Environment { - return &Environment{} + e := engine.New() + var ey EngineYard = map[string]Engine{GoTplEngine: e} + return &Environment{ + EngineYard: ey, + } } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 613804ef3..b0a00794b 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -4,12 +4,8 @@ import ( "fmt" "sync" "testing" - - "github.com/deis/tiller/cmd/tiller/environment" ) -var _ environment.Engine = &Engine{} - func TestEngine(t *testing.T) { e := New() From b647e9a94d3a2d36aa22506e0f9b8208e097ff08 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 13 Apr 2016 11:58:24 -0600 Subject: [PATCH 16/86] fix(tiller): add server test --- cmd/tiller/server_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 cmd/tiller/server_test.go diff --git a/cmd/tiller/server_test.go b/cmd/tiller/server_test.go new file mode 100644 index 000000000..e018530ad --- /dev/null +++ b/cmd/tiller/server_test.go @@ -0,0 +1,32 @@ +package main + +import ( + "testing" + + "github.com/deis/tiller/cmd/tiller/environment" + "github.com/deis/tiller/pkg/engine" +) + +// These are canary tests to make sure that the default server actually +// fulfills its requirements. +var _ environment.Engine = &engine.Engine{} + +func TestNewServer(t *testing.T) { + defer func() { + if recover() != nil { + t.Fatalf("Panic trapped. Check EngineYard.Default()") + } + }() + s := newServer() + + // This will panic if it is not correct. + s.Environment.EngineYard.Default() + + e, ok := s.Environment.EngineYard.Get(environment.GoTplEngine) + if !ok { + t.Fatalf("Could not find GoTplEngine") + } + if e == nil { + t.Fatalf("Template engine GoTplEngine returned nil.") + } +} From b8bf4db3479da545a7ea151e48ee6708e8c06d2f Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 13 Apr 2016 12:06:11 -0600 Subject: [PATCH 17/86] fix(environment): make KubeClient take multiple files Rather than just taking a []byte for Install, take a map of filenames->contents. --- cmd/tiller/environment/environment.go | 8 +++++++- cmd/tiller/environment/environment_test.go | 6 ++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/cmd/tiller/environment/environment.go b/cmd/tiller/environment/environment.go index f7d863b6d..3a9487305 100644 --- a/cmd/tiller/environment/environment.go +++ b/cmd/tiller/environment/environment.go @@ -38,7 +38,13 @@ type ReleaseStorage interface { // // A KubeClient must be concurrency safe. type KubeClient interface { - Install(manifest []byte) error + // Install takes a map where the key is a "file name" (read: unique relational + // id) and the value is a Kubernetes manifest containing one or more resource + // definitions. + // + // TODO: Can these be in YAML or JSON, or must they be in one particular + // format? + Install(manifests map[string]string) error } // Environment provides the context for executing a client request. diff --git a/cmd/tiller/environment/environment_test.go b/cmd/tiller/environment/environment_test.go index e42376f6e..e13f4def1 100644 --- a/cmd/tiller/environment/environment_test.go +++ b/cmd/tiller/environment/environment_test.go @@ -30,7 +30,7 @@ func (r *mockReleaseStorage) Set(k string, v *hapi.Release) error { type mockKubeClient struct { } -func (k *mockKubeClient) Install(manifest []byte) error { +func (k *mockKubeClient) Install(manifests map[string]string) error { return nil } @@ -76,7 +76,9 @@ func TestKubeClient(t *testing.T) { env := New() env.KubeClient = kc - if err := env.KubeClient.Install([]byte("apiVersion: v1\n")); err != nil { + manifests := map[string]string{} + + if err := env.KubeClient.Install(manifests); err != nil { t.Errorf("Kubeclient failed: %s", err) } } From efdf5cd4ec018d6383ba7365638b6d89b63d6c34 Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Wed, 13 Apr 2016 12:12:54 -0700 Subject: [PATCH 18/86] feat(Makefile): add docker build tooling Still needs a dockerfile /cc @michelleN --- .gitignore | 2 ++ Makefile | 36 ++++++++++++++++++++++++++++++++---- rootfs/README.md | 27 +++++++++++++++++++++++++++ versioning.mk | 22 ++++++++++++++++++++++ 4 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 rootfs/README.md create mode 100644 versioning.mk diff --git a/.gitignore b/.gitignore index de8371fc3..c61a717ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .coverage/ bin/ +rootfs/helm +rootfs/tiller vendor/ _proto/*.pb.go diff --git a/Makefile b/Makefile index 13fb3a4bc..308737dcb 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,50 @@ -GO ?= go +DOCKER_REGISTRY := +IMAGE_PREFIX ?= helm +SHORT_NAME ?= tiller +# go option +GO ?= go +GOARCH ?= $(shell go env GOARCH) +GOOS ?= $(shell go env GOOS) PKG := $(shell glide novendor) TAGS := TESTS := . TESTFLAGS := LDFLAGS := +GOFLAGS := +BINDIR := ./bin +BINARIES := helm tiller -BINARIES := helm tiller +include versioning.mk .PHONY: all all: build .PHONY: build +build: GOFLAGS += -v -a -installsuffix cgo build: @for i in $(BINARIES); do \ - $(GO) build -o ./bin/$$i -v $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/$$i || exit 1; \ + CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GO) build -o $(BINDIR)/$$i $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/$$i || exit 1; \ done +.PHONY: check-docker +check-docker: + @if [ -z $$(which docker) ]; then \ + echo "Missing \`docker\` client which is required for development"; \ + exit 2; \ + fi + +.PHONY: docker-binary +docker-binary: GOOS = linux +docker-binary: GOARCH = amd64 +docker-binary: BINDIR = ./rootfs +docker-binary: build + +.PHONY: docker-build +docker-build: check-docker docker-binary + docker build --rm -t ${IMAGE} rootfs + docker tag -f ${IMAGE} ${MUTABLE_IMAGE} + .PHONY: test test: TESTFLAGS += -race -v test: test-style @@ -32,7 +60,7 @@ test-style: .PHONY: clean clean: - rm -rf ./bin + @rm -rf $(BINDIR) .PHONY: coverage coverage: diff --git a/rootfs/README.md b/rootfs/README.md new file mode 100644 index 000000000..cf8b2e61e --- /dev/null +++ b/rootfs/README.md @@ -0,0 +1,27 @@ +# RootFS + +This directory stores all files that should be copied to the rootfs of a +Docker container. The files should be stored according to the correct +directory structure of the destination container. For example: + +``` +rootfs/bin -> /bin +rootfs/usr/local/share -> /usr/local/share +``` + +## Dockerfile + +A Dockerfile in the rootfs is used to build the image. Where possible, +compilation should not be done in this Dockerfile, since we are +interested in deploying the smallest possible images. + +Example: + +```Dockerfile +FROM alpine:3.2 + +COPY . / + +ENTRYPOINT ["/usr/local/bin/boot"] +``` + diff --git a/versioning.mk b/versioning.mk new file mode 100644 index 000000000..2faa2ec6f --- /dev/null +++ b/versioning.mk @@ -0,0 +1,22 @@ +MUTABLE_VERSION ?= canary +VERSION ?= git-$(shell git rev-parse --short HEAD) + +IMAGE := ${DOCKER_REGISTRY}${IMAGE_PREFIX}/${SHORT_NAME}:${VERSION} +MUTABLE_IMAGE := ${DOCKER_REGISTRY}${IMAGE_PREFIX}/${SHORT_NAME}:${MUTABLE_VERSION} + +info: + @echo "Build tag: ${VERSION}" + @echo "Registry: ${DOCKER_REGISTRY}" + @echo "Immutable tag: ${IMAGE}" + @echo "Mutable tag: ${MUTABLE_IMAGE}" + +.PHONY: docker-push +docker-push: docker-mutable-push docker-immutable-push + +.PHONY: docker-immutable-push +docker-immutable-push: + docker push ${IMAGE} + +.PHONY: docker-mutable-push +docker-mutable-push: + docker push ${MUTABLE_IMAGE} From df78765bf94a217976b4d19ba9d7370138c6d8d4 Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Wed, 13 Apr 2016 14:42:20 -0700 Subject: [PATCH 19/86] feat(Makefile): add a placeholder Dockerfile --- rootfs/Dockerfile | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 rootfs/Dockerfile diff --git a/rootfs/Dockerfile b/rootfs/Dockerfile new file mode 100644 index 000000000..54760d87e --- /dev/null +++ b/rootfs/Dockerfile @@ -0,0 +1,8 @@ +FROM alpine:3.3 + +COPY . / + +EXPOSE 44134 + +CMD ["/tiller"] + From fa6a33c349ac7ec30d629ff0ad54f64386b871f5 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 13 Apr 2016 12:54:57 -0600 Subject: [PATCH 20/86] feat(storage): add basic implementation of storage This is an in-memory storage layer for storing releases. This will be superseded by the Kubernetes ConfigMap implementtion. --- cmd/tiller/environment/environment.go | 12 ++++ cmd/tiller/environment/environment_test.go | 8 +++ pkg/engine/engine.go | 3 + pkg/storage/doc.go | 7 +++ pkg/storage/memory.go | 53 ++++++++++++++++ pkg/storage/memory_test.go | 70 ++++++++++++++++++++++ 6 files changed, 153 insertions(+) create mode 100644 pkg/storage/doc.go create mode 100644 pkg/storage/memory.go create mode 100644 pkg/storage/memory_test.go diff --git a/cmd/tiller/environment/environment.go b/cmd/tiller/environment/environment.go index 61305e6f7..1845d83c7 100644 --- a/cmd/tiller/environment/environment.go +++ b/cmd/tiller/environment/environment.go @@ -12,6 +12,10 @@ var DefaultEngine = GoTplEngine // EngineYard maps engine names to engine implementations. type EngineYard map[string]Engine +// Get retrieves a template engine by name. +// +// If no matching template engine is found, the second return value will +// be false. func (y EngineYard) Get(k string) (Engine, bool) { e, ok := y[k] return e, ok @@ -49,8 +53,16 @@ type Engine interface { // // Release storage must be concurrency safe. type ReleaseStorage interface { + // Get takes a name and returns the accompanying release. Get(key string) (*hapi.Release, error) + // Set saves the release with the given name. Set(key string, val *hapi.Release) error + // List lists all active (non-deleted, non-superseded) releases. + // + // To get deleted or superseded releases, use Query. + List() ([]*hapi.Release, error) + // Query takes a map of labels and returns any releases that match. + Query(map[string]string) ([]*hapi.Release, error) } // KubeClient represents a client capable of communicating with the Kubernetes API. diff --git a/cmd/tiller/environment/environment_test.go b/cmd/tiller/environment/environment_test.go index e13f4def1..c1d037423 100644 --- a/cmd/tiller/environment/environment_test.go +++ b/cmd/tiller/environment/environment_test.go @@ -27,6 +27,14 @@ func (r *mockReleaseStorage) Set(k string, v *hapi.Release) error { return nil } +func (r *mockReleaseStorage) List() ([]*hapi.Release, error) { + return []*hapi.Release{}, nil +} + +func (r *mockReleaseStorage) Query(labels map[string]string) ([]*hapi.Release, error) { + return []*hapi.Release{}, nil +} + type mockKubeClient struct { } diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 067d6b98a..59a42f96f 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -9,7 +9,10 @@ import ( "github.com/deis/tiller/pkg/hapi" ) +// Engine is an implementation of 'cmd/tiller/environment'.Engine that uses Go templates. type Engine struct { + // FuncMap contains the template functions that will be passed to each + // render call. This may only be modified before the first call to Render. FuncMap template.FuncMap } diff --git a/pkg/storage/doc.go b/pkg/storage/doc.go new file mode 100644 index 000000000..21e11ade4 --- /dev/null +++ b/pkg/storage/doc.go @@ -0,0 +1,7 @@ +/*Package storage implements storage for Tiller objects. + +Tiller stores releases (see 'cmd/tiller/environment'.Environment). The backend +storage mechanism may be implemented with different backends. This package +and its subpackages provide storage layers for Tiller objects. +*/ +package storage diff --git a/pkg/storage/memory.go b/pkg/storage/memory.go new file mode 100644 index 000000000..a3670426a --- /dev/null +++ b/pkg/storage/memory.go @@ -0,0 +1,53 @@ +package storage + +import ( + "errors" + + "github.com/deis/tiller/pkg/hapi" +) + +// Memory is an in-memory ReleaseStorage implementation. +type Memory struct { + releases map[string]*hapi.Release +} + +func NewMemory() *Memory { + return &Memory{ + releases: map[string]*hapi.Release{}, + } +} + +var ErrNotFound = errors.New("release not found") + +// Get returns the named Release. +// +// If the release is not found, an ErrNotFound error is returned. +func (m *Memory) Get(k string) (*hapi.Release, error) { + v, ok := m.releases[k] + if !ok { + return v, ErrNotFound + } + return v, nil +} + +// Set sets a release. +// +// TODO: Is there any reason why Set doesn't just use the release name? +func (m *Memory) Set(k string, rel *hapi.Release) error { + m.releases[k] = rel + return nil +} + +// List returns all releases +func (m *Memory) List() ([]*hapi.Release, error) { + buf := make([]*hapi.Release, len(m.releases)) + i := 0 + for _, v := range m.releases { + buf[i] = v + i++ + } + return buf, nil +} +func (m *Memory) Query(labels map[string]string) ([]*hapi.Release, error) { + return []*hapi.Release{}, errors.New("Cannot implement until hapi.Release is defined.") +} diff --git a/pkg/storage/memory_test.go b/pkg/storage/memory_test.go new file mode 100644 index 000000000..7b619b5b0 --- /dev/null +++ b/pkg/storage/memory_test.go @@ -0,0 +1,70 @@ +package storage + +import ( + "testing" + + "github.com/deis/tiller/pkg/hapi" +) + +func TestSet(t *testing.T) { + k := "test-1" + r := &hapi.Release{Name: k} + + ms := NewMemory() + if err := ms.Set(k, r); err != nil { + t.Fatalf("Failed set: %s", err) + } + + if ms.releases[k].Name != k { + t.Errorf("Unexpected release name: %s", ms.releases[k].Name) + } +} + +func TestGet(t *testing.T) { + k := "test-1" + r := &hapi.Release{Name: k} + + ms := NewMemory() + ms.Set(k, r) + + if out, err := ms.Get(k); err != nil { + t.Errorf("Could not get %s: %s", k, err) + } else if out.Name != k { + t.Errorf("Expected %s, got %s", k, out.Name) + } +} + +func TestList(t *testing.T) { + ms := NewMemory() + rels := []string{"a", "b", "c"} + + for _, k := range rels { + ms.Set(k, &hapi.Release{Name: k}) + } + + l, err := ms.List() + if err != nil { + t.Error(err) + } + + if len(l) != 3 { + t.Errorf("Expected 3, got %d", len(l)) + } + + for _, n := range rels { + foundN := false + for _, rr := range l { + if rr.Name == n { + foundN = true + break + } + } + if !foundN { + t.Errorf("Did not find %s in list.", n) + } + } +} + +func TestQuery(t *testing.T) { + t.Skip("Not Implemented") +} From 9281012d3cf9e8ea1b92815738f1f0a03d152e87 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Tue, 12 Apr 2016 13:42:27 -0700 Subject: [PATCH 21/86] feat(helm): add init cmd and test --- cmd/helm/init.go | 7 +++++-- cmd/helm/init_test.go | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 cmd/helm/init_test.go diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 5679de8e2..2565f34dd 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "github.com/spf13/cobra" @@ -14,9 +15,11 @@ var initCmd = &cobra.Command{ Use: "init", Short: "Initialize Helm on both client and server.", Long: `Add long help here`, - Run: runInit, + RunE: RunInit, } -func runInit(cmd *cobra.Command, args []string) { +// RunInit initializes local config and installs tiller to Kubernetes Cluster +func RunInit(cmd *cobra.Command, args []string) error { fmt.Fprintln(stdout, "Init was called.") + return errors.New("NotImplemented") } diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go new file mode 100644 index 000000000..333714ad0 --- /dev/null +++ b/cmd/helm/init_test.go @@ -0,0 +1,14 @@ +package main + +import ( + "testing" +) + +func TestRunInit(t *testing.T) { + + //TODO: call command and make sure no error is recevied + err := RunInit(initCmd, nil) + if err != nil { + t.Errorf("Expected no error but got one: %s", err) + } +} From 338dc6d4feeded97669a1a7e3e9233e07641a793 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Tue, 12 Apr 2016 16:08:22 -0700 Subject: [PATCH 22/86] feat(init): add Dockerfile for tiller --- cmd/tiller/.dockerignore | 3 +++ cmd/tiller/Dockerfile | 5 +++++ 2 files changed, 8 insertions(+) create mode 100644 cmd/tiller/.dockerignore create mode 100644 cmd/tiller/Dockerfile diff --git a/cmd/tiller/.dockerignore b/cmd/tiller/.dockerignore new file mode 100644 index 000000000..f6fb65317 --- /dev/null +++ b/cmd/tiller/.dockerignore @@ -0,0 +1,3 @@ +.dockerignore +Dockerfile +Makefile diff --git a/cmd/tiller/Dockerfile b/cmd/tiller/Dockerfile new file mode 100644 index 000000000..eba2c76af --- /dev/null +++ b/cmd/tiller/Dockerfile @@ -0,0 +1,5 @@ +FROM ubuntu:14.04 + +ADD . / + +CMD ["/tiller"] From 44dc0de71fbb6d414f810d51a1642b86960c461f Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 13 Apr 2016 17:12:45 -0600 Subject: [PATCH 23/86] fix(server): add tests for in-memory storage This adds a higher level set of tests for storage. --- cmd/tiller/environment/environment.go | 12 +++++++++++- cmd/tiller/server_test.go | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/cmd/tiller/environment/environment.go b/cmd/tiller/environment/environment.go index 1845d83c7..a0e2c2990 100644 --- a/cmd/tiller/environment/environment.go +++ b/cmd/tiller/environment/environment.go @@ -3,10 +3,15 @@ package environment import ( "github.com/deis/tiller/pkg/engine" "github.com/deis/tiller/pkg/hapi" + "github.com/deis/tiller/pkg/storage" ) +// GoTplEngine is the name of the Go template engine, as registered in the EngineYard. const GoTplEngine = "gotpl" +// DefaultEngine points to the engine that the EngineYard should treat as the +// default. A chart that does not specify an engine may be run through the +// default engine. var DefaultEngine = GoTplEngine // EngineYard maps engine names to engine implementations. @@ -93,8 +98,13 @@ type Environment struct { // New returns an environment initialized with the defaults. func New() *Environment { e := engine.New() - var ey EngineYard = map[string]Engine{GoTplEngine: e} + var ey EngineYard = map[string]Engine{ + // Currently, the only template engine we support is the GoTpl one. But + // we can easily add some here. + GoTplEngine: e, + } return &Environment{ EngineYard: ey, + Releases: storage.NewMemory(), } } diff --git a/cmd/tiller/server_test.go b/cmd/tiller/server_test.go index e018530ad..82efbfe6a 100644 --- a/cmd/tiller/server_test.go +++ b/cmd/tiller/server_test.go @@ -5,11 +5,13 @@ import ( "github.com/deis/tiller/cmd/tiller/environment" "github.com/deis/tiller/pkg/engine" + "github.com/deis/tiller/pkg/storage" ) // These are canary tests to make sure that the default server actually // fulfills its requirements. var _ environment.Engine = &engine.Engine{} +var _ environment.ReleaseStorage = storage.NewMemory() func TestNewServer(t *testing.T) { defer func() { From 5b78c77f3f539efff69e15f4dbcbd5dbd6b2cae4 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 12 Apr 2016 17:18:33 -0600 Subject: [PATCH 24/86] feat(chart): add chart package This is the chart package from k8s/helm, modified for usage here. --- glide.lock | 16 +- glide.yaml | 4 + pkg/chart/chart.go | 444 ++++++++++++++++++ pkg/chart/chart_test.go | 279 +++++++++++ pkg/chart/chartfile.go | 110 +++++ pkg/chart/chartfile_test.go | 91 ++++ pkg/chart/doc.go | 23 + pkg/chart/save.go | 117 +++++ pkg/chart/save_test.go | 123 +++++ pkg/chart/testdata/README.md | 9 + pkg/chart/testdata/frobnitz-0.0.1.tgz | Bin 0 -> 2363 bytes pkg/chart/testdata/frobnitz/Chart.yaml | 33 ++ pkg/chart/testdata/frobnitz/LICENSE | 1 + pkg/chart/testdata/frobnitz/README.md | 11 + pkg/chart/testdata/frobnitz/docs/README.md | 1 + .../testdata/frobnitz/hooks/pre-install.py | 1 + pkg/chart/testdata/frobnitz/icon.svg | 8 + .../templates/wordpress-resources.yaml | 12 + .../frobnitz/templates/wordpress.jinja | 72 +++ .../frobnitz/templates/wordpress.jinja.schema | 69 +++ .../frobnitz/templates/wordpress.yaml | 6 + pkg/chart/testdata/ill-1.2.3.tgz | Bin 0 -> 2305 bytes pkg/chart/testdata/ill/Chart.yaml | 28 ++ pkg/chart/testdata/ill/LICENSE | 1 + pkg/chart/testdata/ill/README.md | 11 + pkg/chart/testdata/ill/docs/README.md | 1 + pkg/chart/testdata/ill/hooks/pre-install.py | 1 + .../ill/templates/wordpress-resources.yaml | 12 + .../testdata/ill/templates/wordpress.jinja | 72 +++ .../ill/templates/wordpress.jinja.schema | 69 +++ .../testdata/ill/templates/wordpress.yaml | 6 + pkg/chart/testdata/nochart.tgz | Bin 0 -> 325 bytes pkg/chart/testdata/sprocket/Chart.yaml | 4 + pkg/chart/testdata/sprocket/LICENSE | 1 + pkg/chart/testdata/sprocket/README.md | 3 + pkg/chart/testdata/sprocket/docs/README.md | 1 + .../testdata/sprocket/hooks/pre-install.py | 1 + pkg/chart/testdata/sprocket/icon.svg | 8 + .../sprocket/templates/placeholder.txt | 1 + 39 files changed, 1644 insertions(+), 6 deletions(-) create mode 100644 pkg/chart/chart.go create mode 100644 pkg/chart/chart_test.go create mode 100644 pkg/chart/chartfile.go create mode 100644 pkg/chart/chartfile_test.go create mode 100644 pkg/chart/doc.go create mode 100644 pkg/chart/save.go create mode 100644 pkg/chart/save_test.go create mode 100644 pkg/chart/testdata/README.md create mode 100644 pkg/chart/testdata/frobnitz-0.0.1.tgz create mode 100644 pkg/chart/testdata/frobnitz/Chart.yaml create mode 100644 pkg/chart/testdata/frobnitz/LICENSE create mode 100644 pkg/chart/testdata/frobnitz/README.md create mode 100644 pkg/chart/testdata/frobnitz/docs/README.md create mode 100644 pkg/chart/testdata/frobnitz/hooks/pre-install.py create mode 100644 pkg/chart/testdata/frobnitz/icon.svg create mode 100644 pkg/chart/testdata/frobnitz/templates/wordpress-resources.yaml create mode 100644 pkg/chart/testdata/frobnitz/templates/wordpress.jinja create mode 100644 pkg/chart/testdata/frobnitz/templates/wordpress.jinja.schema create mode 100644 pkg/chart/testdata/frobnitz/templates/wordpress.yaml create mode 100644 pkg/chart/testdata/ill-1.2.3.tgz create mode 100644 pkg/chart/testdata/ill/Chart.yaml create mode 100644 pkg/chart/testdata/ill/LICENSE create mode 100644 pkg/chart/testdata/ill/README.md create mode 100644 pkg/chart/testdata/ill/docs/README.md create mode 100644 pkg/chart/testdata/ill/hooks/pre-install.py create mode 100644 pkg/chart/testdata/ill/templates/wordpress-resources.yaml create mode 100644 pkg/chart/testdata/ill/templates/wordpress.jinja create mode 100644 pkg/chart/testdata/ill/templates/wordpress.jinja.schema create mode 100644 pkg/chart/testdata/ill/templates/wordpress.yaml create mode 100644 pkg/chart/testdata/nochart.tgz create mode 100644 pkg/chart/testdata/sprocket/Chart.yaml create mode 100644 pkg/chart/testdata/sprocket/LICENSE create mode 100644 pkg/chart/testdata/sprocket/README.md create mode 100644 pkg/chart/testdata/sprocket/docs/README.md create mode 100644 pkg/chart/testdata/sprocket/hooks/pre-install.py create mode 100644 pkg/chart/testdata/sprocket/icon.svg create mode 100644 pkg/chart/testdata/sprocket/templates/placeholder.txt diff --git a/glide.lock b/glide.lock index 0aaa8649f..795c132ac 100644 --- a/glide.lock +++ b/glide.lock @@ -1,14 +1,16 @@ -hash: 1dcbbc192182125021b40497fcf9d52bc643455a3bb03d6cd3458819fcb03dbb -updated: 2016-04-12T13:27:50.987288211-06:00 +hash: 7f9a27ad54a10edaa7c57521246676477d0f84ef4246524bae75f8df9d049983 +updated: 2016-04-14T12:24:49.130995956-06:00 imports: - name: github.com/aokoli/goutils version: 9c37978a95bd5c709a15883b6242714ea6709e64 - name: github.com/codegangsta/cli version: 71f57d300dd6a780ac1856c005c4b518cfd498ec - name: github.com/golang/protobuf - version: dda510ac0fd43b39770f22ac6260eb91d377bce3 + version: f0a097ddac24fb00e07d2ac17f8671423f3ea47c subpackages: - proto +- name: github.com/Masterminds/semver + version: 808ed7761c233af2de3f9729a041d68c62527f3a - name: github.com/Masterminds/sprig version: 679bb747f11c6ffc3373965988fea8877c40b47b - name: github.com/spf13/cobra @@ -16,9 +18,9 @@ imports: subpackages: - cobra - name: github.com/spf13/pflag - version: 1f296710f879815ad9e6d39d947c828c3e4b4c3d + version: 8f6a28b0916586e7f22fe931ae2fcfc380b1c0e6 - name: golang.org/x/net - version: 589fda73dd0faec3dc59e7d7dab5b069e3fce0f9 + version: fb93926129b8ec0056f2f458b1f519654814edf0 subpackages: - context - http2 @@ -26,7 +28,7 @@ imports: - http2/hpack - internal/timeseries - name: google.golang.org/grpc - version: d07d0562ffca36dd7ee333b5d236209f98fe9ba0 + version: 9ac074585f926c8506b6351bfdc396d2b19b1cb1 subpackages: - codes - credentials @@ -36,4 +38,6 @@ imports: - naming - transport - peer +- name: gopkg.in/yaml.v2 + version: a83829b6f1293c91addabc89d0571c246397bbf4 devImports: [] diff --git a/glide.yaml b/glide.yaml index dab2d29c8..a47f5a981 100644 --- a/glide.yaml +++ b/glide.yaml @@ -8,3 +8,7 @@ import: subpackages: - cobra - package: github.com/Masterminds/sprig + version: ^2.1 +- package: gopkg.in/yaml.v2 +- package: github.com/Masterminds/semver + version: 1.1.0 diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go new file mode 100644 index 000000000..6edad5bc8 --- /dev/null +++ b/pkg/chart/chart.go @@ -0,0 +1,444 @@ +/* +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. +*/ + +package chart + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +// ChartfileName is the default Chart file name. +const ChartfileName string = "Chart.yaml" + +const ( + preTemplates string = "templates/" + preHooks string = "hooks/" + preDocs string = "docs/" + preIcon string = "icon.svg" +) + +var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") + +// Chart represents a complete chart. +// +// A chart consists of the following parts: +// +// - Chart.yaml: In code, we refer to this as the Chartfile +// - templates/*: The template directory +// - README.md: Optional README file +// - LICENSE: Optional license file +// - hooks/: Optional hooks registry +// - docs/: Optional docs directory +// +// Packed charts are stored in gzipped tar archives (.tgz). Unpackaged charts +// are directories where the directory name is the Chartfile.Name. +// +// Optionally, a chart might also locate a provenance (.prov) file that it +// can use for cryptographic signing. +type Chart struct { + loader chartLoader +} + +// Close the chart. +// +// Charts should always be closed when no longer needed. +func (c *Chart) Close() error { + return c.loader.close() +} + +// Chartfile gets the Chartfile (Chart.yaml) for this chart. +func (c *Chart) Chartfile() *Chartfile { + return c.loader.chartfile() +} + +// Dir returns the directory where the charts are located. +func (c *Chart) Dir() string { + return c.loader.dir() +} + +// DocsDir returns the directory where the chart's documentation is stored. +func (c *Chart) DocsDir() string { + return filepath.Join(c.loader.dir(), preDocs) +} + +// HooksDir returns the directory where the hooks are stored. +func (c *Chart) HooksDir() string { + return filepath.Join(c.loader.dir(), preHooks) +} + +// TemplatesDir returns the directory where the templates are stored. +func (c *Chart) TemplatesDir() string { + return filepath.Join(c.loader.dir(), preTemplates) +} + +// Icon returns the path to the icon.svg file. +// +// If an icon is not found in the chart, this will return an error. +func (c *Chart) Icon() (string, error) { + i := filepath.Join(c.Dir(), preIcon) + _, err := os.Stat(i) + return i, err +} + +// chartLoader provides load, close, and save implementations for a chart. +type chartLoader interface { + // Chartfile resturns a *Chartfile for this chart. + chartfile() *Chartfile + // Dir returns a directory where the chart can be accessed. + dir() string + + // Close cleans up a chart. + close() error +} + +type dirChart struct { + chartyaml *Chartfile + chartdir string +} + +func (d *dirChart) chartfile() *Chartfile { + return d.chartyaml +} + +func (d *dirChart) dir() string { + return d.chartdir +} + +func (d *dirChart) close() error { + return nil +} + +type tarChart struct { + chartyaml *Chartfile + tmpDir string +} + +func (t *tarChart) chartfile() *Chartfile { + return t.chartyaml +} + +func (t *tarChart) dir() string { + return t.tmpDir +} + +func (t *tarChart) close() error { + // Remove the temp directory. + return os.RemoveAll(t.tmpDir) +} + +// Create creates a new chart in a directory. +// +// Inside of dir, this will create a directory based on the name of +// chartfile.Name. It will then write the Chart.yaml into this directory and +// create the (empty) appropriate directories. +// +// The returned *Chart will point to the newly created directory. +// +// If dir does not exist, this will return an error. +// If Chart.yaml or any directories cannot be created, this will return an +// error. In such a case, this will attempt to clean up by removing the +// new chart directory. +func Create(chartfile *Chartfile, dir string) (*Chart, error) { + path, err := filepath.Abs(dir) + if err != nil { + return nil, err + } + + if fi, err := os.Stat(path); err != nil { + return nil, err + } else if !fi.IsDir() { + return nil, fmt.Errorf("no such directory %s", path) + } + + n := fname(chartfile.Name) + cdir := filepath.Join(path, n) + if _, err := os.Stat(cdir); err == nil { + return nil, fmt.Errorf("directory already exists: %s", cdir) + } + if err := os.MkdirAll(cdir, 0755); err != nil { + return nil, err + } + + rollback := func() { + // TODO: Should we log failures here? + os.RemoveAll(cdir) + } + + if err := chartfile.Save(filepath.Join(cdir, ChartfileName)); err != nil { + rollback() + return nil, err + } + + for _, d := range []string{preHooks, preDocs, preTemplates} { + if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil { + rollback() + return nil, err + } + } + + return &Chart{ + loader: &dirChart{chartyaml: chartfile, chartdir: cdir}, + }, nil +} + +// fname prepares names for the filesystem +func fname(name string) string { + // Right now, we don't do anything. Do we need to encode any particular + // characters? What characters are legal in a chart name, but not in file + // names on Windows, Linux, or OSX. + return name +} + +// LoadDir loads an entire chart from a directory. +// +// This includes the Chart.yaml (*Chartfile) and all of the manifests. +// +// If you are just reading the Chart.yaml file, it is substantially more +// performant to use LoadChartfile. +func LoadDir(chart string) (*Chart, error) { + dir, err := filepath.Abs(chart) + if err != nil { + return nil, fmt.Errorf("%s is not a valid path", chart) + } + + if fi, err := os.Stat(dir); err != nil { + return nil, err + } else if !fi.IsDir() { + return nil, fmt.Errorf("%s is not a directory", chart) + } + + cf, err := LoadChartfile(filepath.Join(dir, "Chart.yaml")) + if err != nil { + return nil, err + } + + cl := &dirChart{ + chartyaml: cf, + chartdir: dir, + } + + return &Chart{ + loader: cl, + }, nil +} + +// LoadData loads a chart from data, where data is a []byte containing a gzipped tar file. +func LoadData(data []byte) (*Chart, error) { + return LoadDataFromReader(bytes.NewBuffer(data)) +} + +// Load loads a chart from a chart archive. +// +// A chart archive is a gzipped tar archive that follows the Chart format +// specification. +func Load(archive string) (*Chart, error) { + if fi, err := os.Stat(archive); err != nil { + return nil, err + } else if fi.IsDir() { + return nil, errors.New("cannot load a directory with chart.Load()") + } + + raw, err := os.Open(archive) + if err != nil { + return nil, err + } + defer raw.Close() + + return LoadDataFromReader(raw) +} + +// LoadDataFromReader loads a chart from a reader +func LoadDataFromReader(r io.Reader) (*Chart, error) { + unzipped, err := gzip.NewReader(r) + if err != nil { + return nil, err + } + defer unzipped.Close() + + untarred := tar.NewReader(unzipped) + c, err := loadTar(untarred) + if err != nil { + return nil, err + } + + cf, err := LoadChartfile(filepath.Join(c.tmpDir, ChartfileName)) + if err != nil { + return nil, err + } + c.chartyaml = cf + return &Chart{loader: c}, nil +} + +func loadTar(r *tar.Reader) (*tarChart, error) { + td, err := ioutil.TempDir("", "chart-") + if err != nil { + return nil, err + } + + // ioutil.TempDir uses Getenv("TMPDIR"), so there are no guarantees + dir, err := filepath.Abs(td) + if err != nil { + return nil, fmt.Errorf("%s is not a valid path", td) + } + + c := &tarChart{ + chartyaml: &Chartfile{}, + tmpDir: dir, + } + + firstDir := "" + + hdr, err := r.Next() + for err == nil { + // This is to prevent malformed tar attacks. + hdr.Name = filepath.Clean(hdr.Name) + + if firstDir == "" { + fi := hdr.FileInfo() + if fi.IsDir() { + firstDir = hdr.Name + } + } else if strings.HasPrefix(hdr.Name, firstDir) { + // We know this has the prefix, so we know there won't be an error. + rel, _ := filepath.Rel(firstDir, hdr.Name) + + // If tar record is a directory, create one in the tmpdir and return. + if hdr.FileInfo().IsDir() { + os.MkdirAll(filepath.Join(c.tmpDir, rel), 0755) + hdr, err = r.Next() + continue + } + + //dest := filepath.Join(c.tmpDir, rel) + f, err := os.Create(filepath.Join(c.tmpDir, rel)) + if err != nil { + hdr, err = r.Next() + continue + } + if _, err := io.Copy(f, r); err != nil { + } + f.Close() + } + hdr, err = r.Next() + } + + if err != nil && err != io.EOF { + c.close() + return c, err + } + return c, nil +} + +// Member is a file in a chart. +type Member struct { + Path string `json:"path"` // Path from the root of the chart. + Content []byte `json:"content"` // Base64 encoded content. +} + +// LoadTemplates loads the members of TemplatesDir(). +func (c *Chart) LoadTemplates() ([]*Member, error) { + dir := c.TemplatesDir() + return c.loadDirectory(dir) +} + +// loadDirectory loads the members of a directory. +func (c *Chart) loadDirectory(dir string) ([]*Member, error) { + files, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + members := []*Member{} + for _, file := range files { + filename := filepath.Join(dir, file.Name()) + if !file.IsDir() { + addition, err := c.loadMember(filename) + if err != nil { + return nil, err + } + + members = append(members, addition) + } else { + additions, err := c.loadDirectory(filename) + if err != nil { + return nil, err + } + + members = append(members, additions...) + } + } + + return members, nil +} + +// LoadMember loads a chart member from a given path where path is the root of the chart. +func (c *Chart) LoadMember(path string) (*Member, error) { + filename := filepath.Join(c.loader.dir(), path) + return c.loadMember(filename) +} + +// loadMember loads and base 64 encodes a file. +func (c *Chart) loadMember(filename string) (*Member, error) { + dir := c.Dir() + if !strings.HasPrefix(filename, dir) { + err := fmt.Errorf("File %s is outside chart directory %s", filename, dir) + return nil, err + } + + content, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + + path := strings.TrimPrefix(filename, dir) + path = strings.TrimLeft(path, "/") + result := &Member{ + Path: path, + Content: content, + } + + return result, nil +} + +// Content is abstraction for the contents of a chart. +type Content struct { + Chartfile *Chartfile `json:"chartfile"` + Members []*Member `json:"members"` +} + +// LoadContent loads contents of a chart directory into Content +func (c *Chart) LoadContent() (*Content, error) { + ms, err := c.loadDirectory(c.Dir()) + if err != nil { + return nil, err + } + + cc := &Content{ + Chartfile: c.Chartfile(), + Members: ms, + } + + return cc, nil +} diff --git a/pkg/chart/chart_test.go b/pkg/chart/chart_test.go new file mode 100644 index 000000000..a6f0870a1 --- /dev/null +++ b/pkg/chart/chart_test.go @@ -0,0 +1,279 @@ +/* +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. +*/ + +package chart + +import ( + "fmt" + "io/ioutil" + "path/filepath" + "reflect" + "testing" +) + +const ( + testfile = "testdata/frobnitz/Chart.yaml" + testdir = "testdata/frobnitz/" + testarchive = "testdata/frobnitz-0.0.1.tgz" + testill = "testdata/ill-1.2.3.tgz" + testnochart = "testdata/nochart.tgz" + testmember = "templates/wordpress.jinja" +) + +// Type canaries. If these fail, they will fail at compile time. +var _ chartLoader = &dirChart{} +var _ chartLoader = &tarChart{} + +func TestLoadDir(t *testing.T) { + + c, err := LoadDir(testdir) + if err != nil { + t.Errorf("Failed to load chart: %s", err) + } + + if c.Chartfile().Name != "frobnitz" { + t.Errorf("Expected chart name to be 'frobnitz'. Got '%s'.", c.Chartfile().Name) + } + + if c.Chartfile().Dependencies[0].Version != "^3" { + d := c.Chartfile().Dependencies[0].Version + t.Errorf("Expected dependency 0 to have version '^3'. Got '%s'.", d) + } +} + +func TestLoad(t *testing.T) { + c, err := Load(testarchive) + if err != nil { + t.Errorf("Failed to load chart: %s", err) + return + } + defer c.Close() + + if c.Chartfile() == nil { + t.Error("No chartfile was loaded.") + return + } + + if c.Chartfile().Name != "frobnitz" { + t.Errorf("Expected name to be frobnitz, got %q", c.Chartfile().Name) + } +} + +func TestLoadData(t *testing.T) { + data, err := ioutil.ReadFile(testarchive) + if err != nil { + t.Errorf("Failed to read testarchive file: %s", err) + return + } + c, err := LoadData(data) + if err != nil { + t.Errorf("Failed to load chart: %s", err) + return + } + if c.Chartfile() == nil { + t.Error("No chartfile was loaded.") + return + } + + if c.Chartfile().Name != "frobnitz" { + t.Errorf("Expected name to be frobnitz, got %q", c.Chartfile().Name) + } +} + +func TestLoadIll(t *testing.T) { + c, err := Load(testill) + if err != nil { + t.Errorf("Failed to load chart: %s", err) + return + } + defer c.Close() + + if c.Chartfile() == nil { + t.Error("No chartfile was loaded.") + return + } + + // Ill does not have an icon. + if i, err := c.Icon(); err == nil { + t.Errorf("Expected icon to be missing. Got %s", i) + } +} + +func TestLoadNochart(t *testing.T) { + _, err := Load(testnochart) + if err == nil { + t.Error("Nochart should not have loaded at all.") + } +} + +func TestChart(t *testing.T) { + c, err := LoadDir(testdir) + if err != nil { + t.Errorf("Failed to load chart: %s", err) + } + defer c.Close() + + if c.Dir() != c.loader.dir() { + t.Errorf("Unexpected location for directory: %s", c.Dir()) + } + + if c.Chartfile().Name != c.loader.chartfile().Name { + t.Errorf("Unexpected chart file name: %s", c.Chartfile().Name) + } + + dir := c.Dir() + d := c.DocsDir() + if d != filepath.Join(dir, preDocs) { + t.Errorf("Unexpectedly, docs are in %s", d) + } + + d = c.TemplatesDir() + if d != filepath.Join(dir, preTemplates) { + t.Errorf("Unexpectedly, templates are in %s", d) + } + + d = c.HooksDir() + if d != filepath.Join(dir, preHooks) { + t.Errorf("Unexpectedly, hooks are in %s", d) + } + + i, err := c.Icon() + if err != nil { + t.Errorf("No icon found in test chart: %s", err) + } + if i != filepath.Join(dir, preIcon) { + t.Errorf("Unexpectedly, icon is in %s", i) + } +} + +func TestLoadTemplates(t *testing.T) { + c, err := LoadDir(testdir) + if err != nil { + t.Errorf("Failed to load chart: %s", err) + } + + members, err := c.LoadTemplates() + if members == nil { + t.Fatalf("Cannot load templates: unknown error") + } + + if err != nil { + t.Fatalf("Cannot load templates: %s", err) + } + + dir := c.TemplatesDir() + files, err := ioutil.ReadDir(dir) + if err != nil { + t.Fatalf("Cannot read template directory: %s", err) + } + + if len(members) != len(files) { + t.Fatalf("Expected %d templates, got %d", len(files), len(members)) + } + + root := c.loader.dir() + for _, file := range files { + path := filepath.Join(preTemplates, file.Name()) + if err := findMember(root, path, members); err != nil { + t.Fatal(err) + } + } +} + +func findMember(root, path string, members []*Member) error { + for _, member := range members { + if member.Path == path { + filename := filepath.Join(root, path) + if err := compareContent(filename, member.Content); err != nil { + return err + } + + return nil + } + } + + return fmt.Errorf("Template not found: %s", path) +} + +func TestLoadMember(t *testing.T) { + c, err := LoadDir(testdir) + if err != nil { + t.Errorf("Failed to load chart: %s", err) + } + + member, err := c.LoadMember(testmember) + if member == nil { + t.Fatalf("Cannot load member %s: unknown error", testmember) + } + + if err != nil { + t.Fatalf("Cannot load member %s: %s", testmember, err) + } + + if member.Path != testmember { + t.Errorf("Expected member path %s, got %s", testmember, member.Path) + } + + filename := filepath.Join(c.loader.dir(), testmember) + if err := compareContent(filename, member.Content); err != nil { + t.Fatal(err) + } +} + +func TestLoadContent(t *testing.T) { + c, err := LoadDir(testdir) + if err != nil { + t.Errorf("Failed to load chart: %s", err) + } + + content, err := c.LoadContent() + if err != nil { + t.Errorf("Failed to load chart content: %s", err) + } + + want := c.Chartfile() + have := content.Chartfile + if !reflect.DeepEqual(want, have) { + t.Errorf("Unexpected chart file\nwant:\n%v\nhave:\n%v\n", want, have) + } + + for _, member := range content.Members { + have := member.Content + wantMember, err := c.LoadMember(member.Path) + if err != nil { + t.Errorf("Failed to load chart member: %s", err) + } + + t.Logf("%s:\n%s\n\n", member.Path, member.Content) + want := wantMember.Content + if !reflect.DeepEqual(want, have) { + t.Errorf("Unexpected chart member %s\nwant:\n%v\nhave:\n%v\n", member.Path, want, have) + } + } +} + +func compareContent(filename string, content []byte) error { + compare, err := ioutil.ReadFile(filename) + if err != nil { + return fmt.Errorf("Cannot read test file %s: %s", filename, err) + } + + if !reflect.DeepEqual(compare, content) { + return fmt.Errorf("Expected member content\n%v\ngot\n%v", compare, content) + } + + return nil +} diff --git a/pkg/chart/chartfile.go b/pkg/chart/chartfile.go new file mode 100644 index 000000000..1332ef805 --- /dev/null +++ b/pkg/chart/chartfile.go @@ -0,0 +1,110 @@ +/* +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. +*/ + +package chart + +import ( + "io/ioutil" + + "github.com/Masterminds/semver" + "gopkg.in/yaml.v2" +) + +// Chartfile describes a Helm Chart (e.g. Chart.yaml) +type Chartfile struct { + Name string `yaml:"name"` + Description string `yaml:"description"` + Version string `yaml:"version"` + Keywords []string `yaml:"keywords,omitempty"` + Maintainers []*Maintainer `yaml:"maintainers,omitempty"` + Source []string `yaml:"source,omitempty"` + Home string `yaml:"home"` + Dependencies []*Dependency `yaml:"dependencies,omitempty"` + Environment []*EnvConstraint `yaml:"environment,omitempty"` + Expander *Expander `yaml:"expander,omitempty"` + Schema string `yaml:"schema,omitempty"` +} + +// Maintainer describes a chart maintainer. +type Maintainer struct { + Name string `yaml:"name"` + Email string `yaml:"email,omitempty"` +} + +// Dependency describes a specific dependency. +type Dependency struct { + Name string `yaml:"name,omitempty"` + Version string `yaml:"version"` + Location string `yaml:"location"` +} + +// EnvConstraint specifies environmental constraints. +type EnvConstraint struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + Extensions []string `yaml:"extensions,omitempty"` + APIGroups []string `yaml:"apiGroups,omitempty"` +} + +// Expander controls how template/ is evaluated. +type Expander struct { + // Currently just Expandybird or GoTemplate + Name string `json:"name"` + // During evaluation, which file to start from. + Entrypoint string `json:"entrypoint"` +} + +// LoadChartfile loads a Chart.yaml file into a *Chart. +func LoadChartfile(filename string) (*Chartfile, error) { + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + var y Chartfile + return &y, yaml.Unmarshal(b, &y) +} + +// Save saves a Chart.yaml file +func (c *Chartfile) Save(filename string) error { + b, err := c.Marshal() + if err != nil { + return err + } + + return ioutil.WriteFile(filename, b, 0644) +} + +// Marshal encodes the chart file into YAML. +func (c *Chartfile) Marshal() ([]byte, error) { + return yaml.Marshal(c) +} + +// VersionOK returns true if the given version meets the constraints. +// +// It returns false if the version string or constraint is unparsable or if the +// version does not meet the constraint. +func (d *Dependency) VersionOK(version string) bool { + c, err := semver.NewConstraint(d.Version) + if err != nil { + return false + } + v, err := semver.NewVersion(version) + if err != nil { + return false + } + + return c.Check(v) +} diff --git a/pkg/chart/chartfile_test.go b/pkg/chart/chartfile_test.go new file mode 100644 index 000000000..3c4042db7 --- /dev/null +++ b/pkg/chart/chartfile_test.go @@ -0,0 +1,91 @@ +/* +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. +*/ + +package chart + +import ( + "testing" +) + +func TestLoadChartfile(t *testing.T) { + f, err := LoadChartfile(testfile) + if err != nil { + t.Errorf("Failed to open %s: %s", testfile, err) + return + } + + if len(f.Environment[0].Extensions) != 2 { + t.Errorf("Expected two extensions, got %d", len(f.Environment[0].Extensions)) + } + + if f.Name != "frobnitz" { + t.Errorf("Expected frobnitz, got %s", f.Name) + } + + if len(f.Maintainers) != 2 { + t.Errorf("Expected 2 maintainers, got %d", len(f.Maintainers)) + } + + if len(f.Dependencies) != 1 { + t.Errorf("Expected 2 dependencies, got %d", len(f.Dependencies)) + } + + if f.Dependencies[0].Name != "thingerbob" { + t.Errorf("Expected second dependency to be thingerbob: %q", f.Dependencies[0].Name) + } + + if f.Source[0] != "https://example.com/foo/bar" { + t.Errorf("Expected https://example.com/foo/bar, got %s", f.Source) + } + + expander := f.Expander + if expander == nil { + t.Errorf("No expander found in %s", testfile) + } else { + if expander.Name != "Expandybird" { + t.Errorf("Expected expander name Expandybird, got %s", expander.Name) + } + + if expander.Entrypoint != "templates/wordpress.jinja" { + t.Errorf("Expected expander entrypoint templates/wordpress.jinja, got %s", expander.Entrypoint) + } + } + + if f.Schema != "wordpress.jinja.schema" { + t.Errorf("Expected schema wordpress.jinja.schema, got %s", f.Schema) + } +} + +func TestVersionOK(t *testing.T) { + f, err := LoadChartfile(testfile) + if err != nil { + t.Errorf("Error loading %s: %s", testfile, err) + } + + // These are canaries. The SemVer package exhuastively tests the + // various permutations. This will alert us if we wired it up + // incorrectly. + + d := f.Dependencies[0] + if d.VersionOK("1.0.0") { + t.Errorf("1.0.0 should have been marked out of range") + } + + if !d.VersionOK("3.2.3") { + t.Errorf("Version 3.2.3 should have been marked in-range") + } + +} diff --git a/pkg/chart/doc.go b/pkg/chart/doc.go new file mode 100644 index 000000000..ec0627506 --- /dev/null +++ b/pkg/chart/doc.go @@ -0,0 +1,23 @@ +/* +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. +*/ + +/* +Package chart implements the Chart format. + +This package provides tools for working with the Chart format, including the +Chartfile (chart.yaml) and compressed chart archives. +*/ +package chart diff --git a/pkg/chart/save.go b/pkg/chart/save.go new file mode 100644 index 000000000..d6fc5600e --- /dev/null +++ b/pkg/chart/save.go @@ -0,0 +1,117 @@ +/* +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. +*/ + +package chart + +import ( + "archive/tar" + "compress/gzip" + "fmt" + "io" + "os" + "path/filepath" +) + +// Save creates an archived chart to the given directory. +// +// This takes an existing chart and a destination directory. +// +// If the directory is /foo, and the chart is named bar, with version 1.0.0, this +// will generate /foo/bar-1.0.0.tgz. +// +// This returns the absolute path to the chart archive file. +func Save(c *Chart, outDir string) (string, error) { + // Create archive + if fi, err := os.Stat(outDir); err != nil { + return "", err + } else if !fi.IsDir() { + return "", fmt.Errorf("location %s is not a directory", outDir) + } + + cfile := c.Chartfile() + dir := c.Dir() + pdir := filepath.Dir(dir) + filename := fmt.Sprintf("%s-%s.tgz", fname(cfile.Name), cfile.Version) + filename = filepath.Join(outDir, filename) + + // Fail early if the YAML is borked. + if err := cfile.Save(filepath.Join(dir, ChartfileName)); err != nil { + return "", err + } + + // Create file. + f, err := os.Create(filename) + if err != nil { + return "", err + } + + // Wrap in gzip writer + zipper := gzip.NewWriter(f) + zipper.Header.Extra = headerBytes + zipper.Header.Comment = "Helm" + + // Wrap in tar writer + twriter := tar.NewWriter(zipper) + rollback := false + defer func() { + twriter.Close() + zipper.Close() + f.Close() + if rollback { + os.Remove(filename) + } + }() + + err = filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + hdr, err := tar.FileInfoHeader(fi, ".") + if err != nil { + return err + } + + relpath, err := filepath.Rel(pdir, path) + if err != nil { + return err + } + hdr.Name = relpath + + twriter.WriteHeader(hdr) + + // Skip directories. + if fi.IsDir() { + return nil + } + + in, err := os.Open(path) + if err != nil { + return err + } + _, err = io.Copy(twriter, in) + in.Close() + if err != nil { + return err + } + + return nil + }) + if err != nil { + rollback = true + return filename, err + } + return filename, nil +} diff --git a/pkg/chart/save_test.go b/pkg/chart/save_test.go new file mode 100644 index 000000000..56979c6a1 --- /dev/null +++ b/pkg/chart/save_test.go @@ -0,0 +1,123 @@ +/* +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. +*/ + +package chart + +import ( + "archive/tar" + "compress/gzip" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +const sprocketdir = "testdata/sprocket" + +func TestSave(t *testing.T) { + + tmpdir, err := ioutil.TempDir("", "helm-") + if err != nil { + t.Fatal("Could not create temp directory") + } + t.Logf("Temp: %s", tmpdir) + // Because of the defer, don't call t.Fatal in the remainder of this + // function. + defer os.RemoveAll(tmpdir) + + c, err := LoadDir(sprocketdir) + if err != nil { + t.Errorf("Failed to load %s: %s", sprocketdir, err) + return + } + + tfile, err := Save(c, tmpdir) + if err != nil { + t.Errorf("Failed to save %s to %s: %s", c.Chartfile().Name, tmpdir, err) + return + } + + b := filepath.Base(tfile) + expectname := "sprocket-1.2.3-alpha.1+12345.tgz" + if b != expectname { + t.Errorf("Expected %q, got %q", expectname, b) + } + + files, err := getAllFiles(tfile) + if err != nil { + t.Errorf("Could not extract files: %s", err) + } + + // Files should come back in order. + expect := []string{ + "sprocket", + "sprocket/Chart.yaml", + "sprocket/LICENSE", + "sprocket/README.md", + "sprocket/docs", + "sprocket/docs/README.md", + "sprocket/hooks", + "sprocket/hooks/pre-install.py", + "sprocket/icon.svg", + "sprocket/templates", + "sprocket/templates/placeholder.txt", + } + if len(expect) != len(files) { + t.Errorf("Expected %d files, found %d", len(expect), len(files)) + return + } + for i := 0; i < len(expect); i++ { + if expect[i] != files[i] { + t.Errorf("Expected file %q, got %q", expect[i], files[i]) + } + } +} + +func getAllFiles(tfile string) ([]string, error) { + f1, err := os.Open(tfile) + if err != nil { + return []string{}, err + } + f2, err := gzip.NewReader(f1) + if err != nil { + f1.Close() + return []string{}, err + } + + if f2.Header.Comment != "Helm" { + return []string{}, fmt.Errorf("Expected header Helm. Got %s", f2.Header.Comment) + } + if string(f2.Header.Extra) != string(headerBytes) { + return []string{}, fmt.Errorf("Expected header signature. Got %v", f2.Header.Extra) + } + + f3 := tar.NewReader(f2) + + files := []string{} + var e error + var hdr *tar.Header + for e == nil { + hdr, e = f3.Next() + if e == nil { + files = append(files, hdr.Name) + } + } + + f2.Close() + f1.Close() + return files, nil +} diff --git a/pkg/chart/testdata/README.md b/pkg/chart/testdata/README.md new file mode 100644 index 000000000..6ddc704f5 --- /dev/null +++ b/pkg/chart/testdata/README.md @@ -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. diff --git a/pkg/chart/testdata/frobnitz-0.0.1.tgz b/pkg/chart/testdata/frobnitz-0.0.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..fae67e01141c91d380b367764a5b634ea7731792 GIT binary patch literal 2363 zcmV-B3B>jviwFQl_3>5!1MM4YbK5pDpYtJGN{B`vMlbSS+DAC9}}u*Xpy!15MKgy&ift zFpYr;e>Gk2>lza6DIneG4@_O_nMNOJy3sYXXQ=mxI%TkA+@he$2g|-#(9h?Sj3fK| zjlev$|4Y}RTv=Oz|KKuUKKiD)(f)c@Gb;8s4O4%HvP@W)Z!S; z32B8^fQKku7%c}gn|cxVNH|1iuE!AkSV{l2(pq84WM5lX45iz#{K&Nw{d;g?v)682 z;PoY;4jZ-*>f~I-1B)oJFEE!64sk4}6exVG*c2_5@Iy+87`89WJa8IiHMOUwQ(%aR32{XK6Kr ztjK#!$ucUi>r!V716jB5Rb;`WP|+2uH03%Dugan0|u!=Upq#-6Gf&5F}Ew<3oq)B^@D>0 zRra=vsY|R8y7V0G9<_~LzYQs$w{Us#;5kQa$QpS2IZz#nRQ35NPN|Ss(1)sQ5`7fg zT%ylC3i#*>IJD7PekSZqE$7~c|L30XAGJekwQj3r9t;C>M>5fSl8d>83&oCLawsO` zdF!dIob>Utw+pDTe^zPhz=y1ivM+ltur2;~tLHyMH_Uz$|91g< z=rth=i1aUgvUDJ5M?P7Ln2p|AA;fZ;m1bMnY8bZCdUH5u9miuAa5&=Y2pVh>L`#m9 zm98Z15fveyCxvl@u1N@ZOV+VP{nzsKPFx#j-Zg$b8zMM0X6%DC`=9?90-k!-zaF^D z|Km#gm&@4V|2?f&J^u~5{igo26R5oZbAt2C-#>l_DH=!P$?14BIXf9m(NV$K5M*>8 zZ_sx)1-yTZUY-F`PVs&p2tHt&@Bc^O?>j-~;z!mI6&o*H>=&rty_%j+N0VPilL`Y7 zVi=|?Ok*A;nK{+@$ruRp=lAEx_J9ub;oZD{5}J=yWe z^ydnh2ug|EG&pXMDTvQsP0xNGk19OoOvJi`3%=rJiNouYcmJ-=(ZUO_>ZV8Fri1%Y zyVWxpjp6+NLKs#yyx)fm-1lsYB_l6T{B}Ay9#2No>G}B0xFSE#7$P^S(W69Llv?Vz zxAb&G73$n#2f_n$;cZphawgwFUt}+kRV$Ll5bvuy;J5ky zz~F!enSlx{hAX{h2lk>N`uMTpoHsYJPt0YtSI&0QF9uufm-CbK$0HON8(azSvM~y` z_}ozfUO+-MS&|$(A_#dl92DB_(w4K4pX zRS0pqjBhx20oVmbYE#7{DsV_+^39DZCu1nb7Qeb*5q}xr!i)QWEJJ=CS==2Wb!Aae zo>yHS_z4D#S`_6OhrH@nN&M2cykMAh!x%-wyKI4#^0+55M8Tmq8{#%nZV0_NwQ?lG zp3;v^5w7y-i-#I>nw*}VWpN>!pOxh=aZz3xI1-uLE#iAKHJ!{D7B(JdRyh{VQ!q#5 z#J0#J7CkA3IXg@IsV1%ze5n-TcWcUe1!_!Pvq@|=`DWzY^hVM2nF7Ok6l^ko&h_~3 zpS}P#@Og7OJ*)DFw}xa2cID4covb$S;IRGtm)x}a0vTJ*e_ioVuK#HL=KbGpK$PNz z$8(SSBJQNNho;iS&p%{TIL32`qT>2zhKMfljCpW$oUMTZy5V4l=8$fpJj|z|8bYcS zip(x;_(43&%%sp#4)sM;2Rh%bMKYf>DlMs&xE@;KB5}pLh2QE}p&8*WS~4tE;RG?) zqFB7B#|$`@_o3tf8%M=>*JGT*Amelu&#k2oJ4*RvoAedtZydA9u7C=$KCxmg z%+p0)iQNh5C%Ad(g}|m&vBlBL)A0nZUZ=p5rPg=me-HF+pl$E}bYoDh{~O&V|Jw<8 zfrt~bFc_Z{tKYw;001X`puzwE literal 0 HcmV?d00001 diff --git a/pkg/chart/testdata/frobnitz/Chart.yaml b/pkg/chart/testdata/frobnitz/Chart.yaml new file mode 100644 index 000000000..b1e67a038 --- /dev/null +++ b/pkg/chart/testdata/frobnitz/Chart.yaml @@ -0,0 +1,33 @@ +#helm:generate foo +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 + location: https://example.com/charts/thingerbob-3.2.1.tgz + version: ^3 +environment: + - name: Kubernetes + version: ~1.1 + extensions: + - extensions/v1beta1 + - extensions/v1beta1/daemonset + apiGroups: + - 3rdParty +expander: + name: Expandybird + entrypoint: templates/wordpress.jinja +schema: wordpress.jinja.schema + \ No newline at end of file diff --git a/pkg/chart/testdata/frobnitz/LICENSE b/pkg/chart/testdata/frobnitz/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/pkg/chart/testdata/frobnitz/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/pkg/chart/testdata/frobnitz/README.md b/pkg/chart/testdata/frobnitz/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/pkg/chart/testdata/frobnitz/README.md @@ -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. diff --git a/pkg/chart/testdata/frobnitz/docs/README.md b/pkg/chart/testdata/frobnitz/docs/README.md new file mode 100644 index 000000000..d40747caf --- /dev/null +++ b/pkg/chart/testdata/frobnitz/docs/README.md @@ -0,0 +1 @@ +This is a placeholder for documentation. diff --git a/pkg/chart/testdata/frobnitz/hooks/pre-install.py b/pkg/chart/testdata/frobnitz/hooks/pre-install.py new file mode 100644 index 000000000..c9b0d0a92 --- /dev/null +++ b/pkg/chart/testdata/frobnitz/hooks/pre-install.py @@ -0,0 +1 @@ +# Placeholder. diff --git a/pkg/chart/testdata/frobnitz/icon.svg b/pkg/chart/testdata/frobnitz/icon.svg new file mode 100644 index 000000000..892130606 --- /dev/null +++ b/pkg/chart/testdata/frobnitz/icon.svg @@ -0,0 +1,8 @@ + + + Example icon + + + diff --git a/pkg/chart/testdata/frobnitz/templates/wordpress-resources.yaml b/pkg/chart/testdata/frobnitz/templates/wordpress-resources.yaml new file mode 100644 index 000000000..00f709de0 --- /dev/null +++ b/pkg/chart/testdata/frobnitz/templates/wordpress-resources.yaml @@ -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 diff --git a/pkg/chart/testdata/frobnitz/templates/wordpress.jinja b/pkg/chart/testdata/frobnitz/templates/wordpress.jinja new file mode 100644 index 000000000..f34e4fec9 --- /dev/null +++ b/pkg/chart/testdata/frobnitz/templates/wordpress.jinja @@ -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 diff --git a/pkg/chart/testdata/frobnitz/templates/wordpress.jinja.schema b/pkg/chart/testdata/frobnitz/templates/wordpress.jinja.schema new file mode 100644 index 000000000..215b47e1e --- /dev/null +++ b/pkg/chart/testdata/frobnitz/templates/wordpress.jinja.schema @@ -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. + diff --git a/pkg/chart/testdata/frobnitz/templates/wordpress.yaml b/pkg/chart/testdata/frobnitz/templates/wordpress.yaml new file mode 100644 index 000000000..b401897ab --- /dev/null +++ b/pkg/chart/testdata/frobnitz/templates/wordpress.yaml @@ -0,0 +1,6 @@ +imports: +- path: wordpress.jinja + +resources: +- name: wordpress + type: wordpress.jinja diff --git a/pkg/chart/testdata/ill-1.2.3.tgz b/pkg/chart/testdata/ill-1.2.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..198cd2e877f732eaf3068390d3022319df2819ec GIT binary patch literal 2305 zcmV+c3I6sUiwFSO*Rxgt1MM4YbK5pDpZO~=^<@0vB8m@DZZ&f=m$Q|`Z4>JxU3;C( zodc1OiI^mK04PUJ-QRuy_@*h#&RKFZ;eLoiU|+yu7rOv79ltzzJJv(*+3vtFjNxDa z4WA?J&PyFQ|=cmi#? zyn|0V6j1#AN?`6Iz!BpfHk;=c&67`b?f zUC1$sbWM|dJ}fB-Bh0V+A1bU|5^;zl24Kpn!U-|OOEqhoI4&f|o}nAMsq>5L4`7J{ z1hC*20Z3gRuMi+h#i&59$j>?9Ao!3e03)xWA8u$Sz9YhcX&BFi?qMwSSp?{XCKd;{ z^062{^GOnTfN{YSED(qxWe5r+U3+SCJ{ZIh`n#4P{oZjl`@i&;y8qj@ee?`CyH-rX zH}3z^{>O6h>uVSWyOFUa{+p&{Htg?M&QTZt_W=h!3c}F^iV%eyfh8eY1Vc0eVqs_= zVlKt849Fue_eEq>!7`1q*SfYsl&QY&Ox@D$9t>h1>gHce%N{u2X_shyMX1L{8UQ`T zQ2u2xCGI8S>cb-*^t4dy4|0zX>{8rR1sS*B2=%}m!BDgVfCOeR0#fI{p-(W3Ma&g3 zxDj|FJa`tuQ4~h$3E<_fxV! zG(utSj6zpE!zSn^IFkuhQ0EnG3|Bc}G{@gD4c=%4z_>7{8_@i7V`E>P^Hz z)?eQa_-%uX{~mGKE|Y+V@Bag9*zNy&0cn4qkjq^zfG@EBU}$yr-v>zh|9pBpem5KM zOvbkQ->{s*{zKcA_O}Oim;dbpGTVbVfG&~+KcZh@72Cm=g7p9Cj?VzM`M+g1>%YO! zv^xLa2ONM`*_oL7G+Q9+%CxaKL4M_*59QaTkn}I|krT8akg#r@G zVnx$l5emGt0a1XXC3yiDLh96xleibiK0r}S7*0>^o_b=w5s?1RkuamE{&$rFK0N<( zY}@JbzkNX6{_?mcriihg_(vU&utzu-+!p`s;jnIh%d&>UF8=QW4!~sU0%3zLK+eP!xddrW1>=h+b% z0M{fEcnRydLI{O{*-KB!7`{fY7b9R9M!`N@v;PLW5b)Hq{^x-W{;z+;(MR}5GPd}? zV>nL3|BZpso&WCz8t4CBcwYGX=jS3t<6ts9n~bOP)A0=awc>0*WOScz!1J35-jCzs zxj-r@-X98rkI3bR|CacNUf8?*p7rE~Gmb6}E7b2^&CX}z=`Z7HgMkb&4AB*$DUX`W zlIr|)A_&Xx_lG#ngqrg1*m7*$8XUEYJfECR=MD0h&`fKw8h>afJ2{>G)F6{VDU+KA z#|<(i@%gLS{I|)t!DGo}tjoBdPkd10@cQ)KznXJ&fum1t(<5>7!F_Ms>Y0uwqW*s= z4XYa7ABqe-z%FFj$SV}To=s0C)A4L}K6x`~$S>1|*pFNEsL@uX);jJjJs(kny0q92 z(gRE4x4+H)@pD6EwY9XUs&I1w>Z8iowvFL!Ne`KxJv57SQ$g!T=j(CmYW0<1)9hV`hvx1 zWwz|VI39t|pBv73bEEp?TsC{vY-jy)u+4roKUse|LW!}#l>}ZfM(Gw`I!b{TlBl08 zNsc@jgs_W7E30M5S?75%^h3 zix{a(bxit#LmrcFZu)96Mrv&7CEkhzNr);}cp*t5ejY>ak3fF~Y5(f#s_*kK$S}mH z<;^1FBCq;I5g!K-hohofj8QhcD;8KSk9#6RR2+JXjM*M zI@FZY^z3Y2#D!vhRxn6VRbC!AGMU>g65vHYpUf04Tr?@Haw46lVvg8LZBa<9dQu8Y zHqZR2C9YI_sTSh*Ysz*7T1?%tNo+RxX5`ZJR@L-{0;6RdZZdz#_2lmlUjQ5Uyg8fA zn>^C3A)A7I`SVjJs~tQ#+*$wWOq4_6|6K-dssHSu**O2R%whNbcRwJH;-$xP%mW#B z@&zE8YOlY3S5e^vEkzWSZ@>yfaD^5Oi_&qi78Ige6n0=K(oIl?`8-sMkP0K2+2su{ zlAYEnWOIiUua?c+J*dh-gDiovBi?=3`RBny3DmG;Iwb4_?0V9AV4y z-6#yuBw|4Rk^y8@!+odc-s;KIh$mm z?&aq<48`w1cK8T%tN%N6f72n01Vqt@wm$;)B;}4*eVw*s_wBI!D z*V{2&h*58gF}}~Glze%43p{K$clX=u_Jgs4|5IWZ+0_BvX1x%A`w8ai^8-t&(iL%d_m5)AYfQ&hp{5nMEn@SXviJo}y++ zI!;;B?d-e6_Lz7ha*-D~KOGwC?V!n~JV!DpW1ltoehNB|&z}F0WQU=7FCbkv>t{bT zOMB;;3L&mi_i>l{w1d9R($7IJ#-{T} + + Example icon + + + diff --git a/pkg/chart/testdata/sprocket/templates/placeholder.txt b/pkg/chart/testdata/sprocket/templates/placeholder.txt new file mode 100644 index 000000000..ef9fb20a7 --- /dev/null +++ b/pkg/chart/testdata/sprocket/templates/placeholder.txt @@ -0,0 +1 @@ +This is a placeholder. From 47991c9c7c71bf7aa24db7a71a4bcdc832268ce1 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Thu, 14 Apr 2016 13:55:57 -0600 Subject: [PATCH 25/86] fix(chart): update chart testdata for newer format --- pkg/chart/chart_test.go | 48 +----------- pkg/chart/chartfile.go | 59 ++------------ pkg/chart/chartfile_test.go | 50 ------------ pkg/chart/save_test.go | 13 ++-- pkg/chart/testdata/README.md | 10 +-- pkg/chart/testdata/frobnitz-0.0.1.tgz | Bin 2363 -> 1152 bytes pkg/chart/testdata/frobnitz/Chart.toml | 15 ++++ pkg/chart/testdata/frobnitz/Chart.yaml | 18 ----- pkg/chart/testdata/frobnitz/INSTALL.txt | 1 + .../testdata/frobnitz/hooks/pre-install.py | 1 - .../testdata/frobnitz/templates/template.tpl | 1 + .../templates/wordpress-resources.yaml | 12 --- .../frobnitz/templates/wordpress.jinja | 72 ------------------ .../frobnitz/templates/wordpress.jinja.schema | 69 ----------------- .../frobnitz/templates/wordpress.yaml | 6 -- pkg/chart/testdata/frobnitz/values.toml | 6 ++ pkg/chart/testdata/ill-1.2.3.tgz | Bin 2305 -> 0 bytes pkg/chart/testdata/ill/Chart.yaml | 28 ------- pkg/chart/testdata/ill/LICENSE | 1 - pkg/chart/testdata/ill/README.md | 11 --- pkg/chart/testdata/ill/docs/README.md | 1 - pkg/chart/testdata/ill/hooks/pre-install.py | 1 - .../ill/templates/wordpress-resources.yaml | 12 --- .../testdata/ill/templates/wordpress.jinja | 72 ------------------ .../ill/templates/wordpress.jinja.schema | 69 ----------------- .../testdata/ill/templates/wordpress.yaml | 6 -- pkg/chart/testdata/nochart.tgz | Bin 325 -> 0 bytes pkg/chart/testdata/sprocket/Chart.yaml | 15 +++- pkg/chart/testdata/sprocket/LICENSE | 1 - pkg/chart/testdata/sprocket/README.md | 3 - pkg/chart/testdata/sprocket/docs/README.md | 1 - .../testdata/sprocket/hooks/pre-install.py | 1 - pkg/chart/testdata/sprocket/icon.svg | 8 -- .../sprocket/templates/placeholder.txt | 1 - .../testdata/sprocket/templates/template.tpl | 1 + pkg/chart/testdata/sprocket/values.toml | 6 ++ 36 files changed, 57 insertions(+), 562 deletions(-) create mode 100644 pkg/chart/testdata/frobnitz/Chart.toml create mode 100644 pkg/chart/testdata/frobnitz/INSTALL.txt delete mode 100644 pkg/chart/testdata/frobnitz/hooks/pre-install.py create mode 100644 pkg/chart/testdata/frobnitz/templates/template.tpl delete mode 100644 pkg/chart/testdata/frobnitz/templates/wordpress-resources.yaml delete mode 100644 pkg/chart/testdata/frobnitz/templates/wordpress.jinja delete mode 100644 pkg/chart/testdata/frobnitz/templates/wordpress.jinja.schema delete mode 100644 pkg/chart/testdata/frobnitz/templates/wordpress.yaml create mode 100644 pkg/chart/testdata/frobnitz/values.toml delete mode 100644 pkg/chart/testdata/ill-1.2.3.tgz delete mode 100644 pkg/chart/testdata/ill/Chart.yaml delete mode 100644 pkg/chart/testdata/ill/LICENSE delete mode 100644 pkg/chart/testdata/ill/README.md delete mode 100644 pkg/chart/testdata/ill/docs/README.md delete mode 100644 pkg/chart/testdata/ill/hooks/pre-install.py delete mode 100644 pkg/chart/testdata/ill/templates/wordpress-resources.yaml delete mode 100644 pkg/chart/testdata/ill/templates/wordpress.jinja delete mode 100644 pkg/chart/testdata/ill/templates/wordpress.jinja.schema delete mode 100644 pkg/chart/testdata/ill/templates/wordpress.yaml delete mode 100644 pkg/chart/testdata/nochart.tgz delete mode 100644 pkg/chart/testdata/sprocket/LICENSE delete mode 100644 pkg/chart/testdata/sprocket/README.md delete mode 100644 pkg/chart/testdata/sprocket/docs/README.md delete mode 100644 pkg/chart/testdata/sprocket/hooks/pre-install.py delete mode 100644 pkg/chart/testdata/sprocket/icon.svg delete mode 100644 pkg/chart/testdata/sprocket/templates/placeholder.txt create mode 100644 pkg/chart/testdata/sprocket/templates/template.tpl create mode 100644 pkg/chart/testdata/sprocket/values.toml diff --git a/pkg/chart/chart_test.go b/pkg/chart/chart_test.go index a6f0870a1..dd114fe67 100644 --- a/pkg/chart/chart_test.go +++ b/pkg/chart/chart_test.go @@ -28,9 +28,7 @@ const ( testfile = "testdata/frobnitz/Chart.yaml" testdir = "testdata/frobnitz/" testarchive = "testdata/frobnitz-0.0.1.tgz" - testill = "testdata/ill-1.2.3.tgz" - testnochart = "testdata/nochart.tgz" - testmember = "templates/wordpress.jinja" + testmember = "templates/template.tpl" ) // Type canaries. If these fail, they will fail at compile time. @@ -47,11 +45,6 @@ func TestLoadDir(t *testing.T) { if c.Chartfile().Name != "frobnitz" { t.Errorf("Expected chart name to be 'frobnitz'. Got '%s'.", c.Chartfile().Name) } - - if c.Chartfile().Dependencies[0].Version != "^3" { - d := c.Chartfile().Dependencies[0].Version - t.Errorf("Expected dependency 0 to have version '^3'. Got '%s'.", d) - } } func TestLoad(t *testing.T) { @@ -93,32 +86,6 @@ func TestLoadData(t *testing.T) { } } -func TestLoadIll(t *testing.T) { - c, err := Load(testill) - if err != nil { - t.Errorf("Failed to load chart: %s", err) - return - } - defer c.Close() - - if c.Chartfile() == nil { - t.Error("No chartfile was loaded.") - return - } - - // Ill does not have an icon. - if i, err := c.Icon(); err == nil { - t.Errorf("Expected icon to be missing. Got %s", i) - } -} - -func TestLoadNochart(t *testing.T) { - _, err := Load(testnochart) - if err == nil { - t.Error("Nochart should not have loaded at all.") - } -} - func TestChart(t *testing.T) { c, err := LoadDir(testdir) if err != nil { @@ -144,19 +111,6 @@ func TestChart(t *testing.T) { if d != filepath.Join(dir, preTemplates) { t.Errorf("Unexpectedly, templates are in %s", d) } - - d = c.HooksDir() - if d != filepath.Join(dir, preHooks) { - t.Errorf("Unexpectedly, hooks are in %s", d) - } - - i, err := c.Icon() - if err != nil { - t.Errorf("No icon found in test chart: %s", err) - } - if i != filepath.Join(dir, preIcon) { - t.Errorf("Unexpectedly, icon is in %s", i) - } } func TestLoadTemplates(t *testing.T) { diff --git a/pkg/chart/chartfile.go b/pkg/chart/chartfile.go index 1332ef805..23d74c035 100644 --- a/pkg/chart/chartfile.go +++ b/pkg/chart/chartfile.go @@ -19,23 +19,18 @@ package chart import ( "io/ioutil" - "github.com/Masterminds/semver" "gopkg.in/yaml.v2" ) // Chartfile describes a Helm Chart (e.g. Chart.yaml) type Chartfile struct { - Name string `yaml:"name"` - Description string `yaml:"description"` - Version string `yaml:"version"` - Keywords []string `yaml:"keywords,omitempty"` - Maintainers []*Maintainer `yaml:"maintainers,omitempty"` - Source []string `yaml:"source,omitempty"` - Home string `yaml:"home"` - Dependencies []*Dependency `yaml:"dependencies,omitempty"` - Environment []*EnvConstraint `yaml:"environment,omitempty"` - Expander *Expander `yaml:"expander,omitempty"` - Schema string `yaml:"schema,omitempty"` + Name string `yaml:"name"` + Description string `yaml:"description"` + Version string `yaml:"version"` + Keywords []string `yaml:"keywords,omitempty"` + Maintainers []*Maintainer `yaml:"maintainers,omitempty"` + Source []string `yaml:"source,omitempty"` + Home string `yaml:"home"` } // Maintainer describes a chart maintainer. @@ -44,29 +39,6 @@ type Maintainer struct { Email string `yaml:"email,omitempty"` } -// Dependency describes a specific dependency. -type Dependency struct { - Name string `yaml:"name,omitempty"` - Version string `yaml:"version"` - Location string `yaml:"location"` -} - -// EnvConstraint specifies environmental constraints. -type EnvConstraint struct { - Name string `yaml:"name"` - Version string `yaml:"version"` - Extensions []string `yaml:"extensions,omitempty"` - APIGroups []string `yaml:"apiGroups,omitempty"` -} - -// Expander controls how template/ is evaluated. -type Expander struct { - // Currently just Expandybird or GoTemplate - Name string `json:"name"` - // During evaluation, which file to start from. - Entrypoint string `json:"entrypoint"` -} - // LoadChartfile loads a Chart.yaml file into a *Chart. func LoadChartfile(filename string) (*Chartfile, error) { b, err := ioutil.ReadFile(filename) @@ -91,20 +63,3 @@ func (c *Chartfile) Save(filename string) error { func (c *Chartfile) Marshal() ([]byte, error) { return yaml.Marshal(c) } - -// VersionOK returns true if the given version meets the constraints. -// -// It returns false if the version string or constraint is unparsable or if the -// version does not meet the constraint. -func (d *Dependency) VersionOK(version string) bool { - c, err := semver.NewConstraint(d.Version) - if err != nil { - return false - } - v, err := semver.NewVersion(version) - if err != nil { - return false - } - - return c.Check(v) -} diff --git a/pkg/chart/chartfile_test.go b/pkg/chart/chartfile_test.go index 3c4042db7..975871d03 100644 --- a/pkg/chart/chartfile_test.go +++ b/pkg/chart/chartfile_test.go @@ -27,10 +27,6 @@ func TestLoadChartfile(t *testing.T) { return } - if len(f.Environment[0].Extensions) != 2 { - t.Errorf("Expected two extensions, got %d", len(f.Environment[0].Extensions)) - } - if f.Name != "frobnitz" { t.Errorf("Expected frobnitz, got %s", f.Name) } @@ -39,53 +35,7 @@ func TestLoadChartfile(t *testing.T) { t.Errorf("Expected 2 maintainers, got %d", len(f.Maintainers)) } - if len(f.Dependencies) != 1 { - t.Errorf("Expected 2 dependencies, got %d", len(f.Dependencies)) - } - - if f.Dependencies[0].Name != "thingerbob" { - t.Errorf("Expected second dependency to be thingerbob: %q", f.Dependencies[0].Name) - } - if f.Source[0] != "https://example.com/foo/bar" { t.Errorf("Expected https://example.com/foo/bar, got %s", f.Source) } - - expander := f.Expander - if expander == nil { - t.Errorf("No expander found in %s", testfile) - } else { - if expander.Name != "Expandybird" { - t.Errorf("Expected expander name Expandybird, got %s", expander.Name) - } - - if expander.Entrypoint != "templates/wordpress.jinja" { - t.Errorf("Expected expander entrypoint templates/wordpress.jinja, got %s", expander.Entrypoint) - } - } - - if f.Schema != "wordpress.jinja.schema" { - t.Errorf("Expected schema wordpress.jinja.schema, got %s", f.Schema) - } -} - -func TestVersionOK(t *testing.T) { - f, err := LoadChartfile(testfile) - if err != nil { - t.Errorf("Error loading %s: %s", testfile, err) - } - - // These are canaries. The SemVer package exhuastively tests the - // various permutations. This will alert us if we wired it up - // incorrectly. - - d := f.Dependencies[0] - if d.VersionOK("1.0.0") { - t.Errorf("1.0.0 should have been marked out of range") - } - - if !d.VersionOK("3.2.3") { - t.Errorf("Version 3.2.3 should have been marked in-range") - } - } diff --git a/pkg/chart/save_test.go b/pkg/chart/save_test.go index 56979c6a1..5722e987f 100644 --- a/pkg/chart/save_test.go +++ b/pkg/chart/save_test.go @@ -23,6 +23,7 @@ import ( "io/ioutil" "os" "path/filepath" + "sort" "testing" ) @@ -66,20 +67,16 @@ func TestSave(t *testing.T) { expect := []string{ "sprocket", "sprocket/Chart.yaml", - "sprocket/LICENSE", - "sprocket/README.md", - "sprocket/docs", - "sprocket/docs/README.md", - "sprocket/hooks", - "sprocket/hooks/pre-install.py", - "sprocket/icon.svg", + "sprocket/values.toml", "sprocket/templates", - "sprocket/templates/placeholder.txt", + "sprocket/templates/template.tpl", } if len(expect) != len(files) { t.Errorf("Expected %d files, found %d", len(expect), len(files)) return } + sort.Strings(files) + sort.Strings(expect) for i := 0; i < len(expect); i++ { if expect[i] != files[i] { t.Errorf("Expected file %q, got %q", expect[i], files[i]) diff --git a/pkg/chart/testdata/README.md b/pkg/chart/testdata/README.md index 6ddc704f5..a3aa71f14 100644 --- a/pkg/chart/testdata/README.md +++ b/pkg/chart/testdata/README.md @@ -1,9 +1 @@ -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. +This directory houses charts used in testing. diff --git a/pkg/chart/testdata/frobnitz-0.0.1.tgz b/pkg/chart/testdata/frobnitz-0.0.1.tgz index fae67e01141c91d380b367764a5b634ea7731792..41f3197a591e8fda92ca561bda445a18a0286fe1 100644 GIT binary patch literal 1152 zcmV-`1b_PFeh@%)4C|xq z`t`8^9JgBjlDmL0Fvm|-NPxBn@@jpVc z=>Lx?5z>-8XFIP${T`w3|G58yzT5V{9lEOjL(dNnz}|UFHt>u0e?f8zLogTPz%*i7 zMk1TYj2F7#Ce2Dve*|iiua;>%Qc*U=j^$e3F=3OGSk7;b>-m9cJka@!i?~zlUO^P6oXmeIogUQ7huPX_R~_B3*iGKy6X3oAeUSd-p_` zN2>+}`FSE2R2Rad)TgSv>zuO5qD-Iowd9Dh8m+kX7wcSASn? z)eoIqt4HAlg%K<1nva6t^LW1Q<*j?Z=9&KIO$W7edot~WXwz;h+8+_S2@)mvT) zuZbyjg|9@EDlQG+WmI}6!rc+A{$n1MdrAU-KK~1KFi`prJTJifuM67#&mvw}<>Pn{ zb!^T5!th!DhjtiX{O^L(4^KIRCae!tRJY9!XU3@-2%rjza-hzJi`;BBn^`l@;$qx$ z)kF4FnOU2?YElK{+S?P$iib_c&dJG1uPUw0*IER#ESBlebc4`TZ-H!_%H~TJ4^2r+ zX`ZROPW9XCY*dG8&;$CkS6$L&f=043PcotG;YoQgVP5@j-23*@d#KKnjIp6vkjJ^% zTlY~?vb-{M8kSUWk@KgiiTdWE$5zqq=-IlsKLXtViC*KIpkyxaaK;4I!bkq^^V3c^6zQ&e|hn7 zbag%2M;%-B?*=RS_jLZ}J2?OAgr?g;2|J=GXE7Bh?O%vi|9_6oKm9SX@_6@kY}3E% zInU>RA-?~+;SfGAR%witT%cL+fT&s(G>pST_^Tviy2;3b3kfMHq2MspMz#GX`bZg{ z=uE;eK63$aeSr#WEhVlB&KWYA&_=MWCNw z|LHrn)7SaGZ+p1@(*jqGlM!NW}E!71oyaR!z=)LT7A)DC{{}*SLoa`nP zY@PqL`fuXDYxg}5_1^_wC}SMHeX}a%{;iU}>Rb;5Fwe`N&-5jviwFQl_3>5!1MM4YbK5pDpYtJGN{B`vMlbSS+DAC9}}u*Xpy!15MKgy&ift zFpYr;e>Gk2>lza6DIneG4@_O_nMNOJy3sYXXQ=mxI%TkA+@he$2g|-#(9h?Sj3fK| zjlev$|4Y}RTv=Oz|KKuUKKiD)(f)c@Gb;8s4O4%HvP@W)Z!S; z32B8^fQKku7%c}gn|cxVNH|1iuE!AkSV{l2(pq84WM5lX45iz#{K&Nw{d;g?v)682 z;PoY;4jZ-*>f~I-1B)oJFEE!64sk4}6exVG*c2_5@Iy+87`89WJa8IiHMOUwQ(%aR32{XK6Kr ztjK#!$ucUi>r!V716jB5Rb;`WP|+2uH03%Dugan0|u!=Upq#-6Gf&5F}Ew<3oq)B^@D>0 zRra=vsY|R8y7V0G9<_~LzYQs$w{Us#;5kQa$QpS2IZz#nRQ35NPN|Ss(1)sQ5`7fg zT%ylC3i#*>IJD7PekSZqE$7~c|L30XAGJekwQj3r9t;C>M>5fSl8d>83&oCLawsO` zdF!dIob>Utw+pDTe^zPhz=y1ivM+ltur2;~tLHyMH_Uz$|91g< z=rth=i1aUgvUDJ5M?P7Ln2p|AA;fZ;m1bMnY8bZCdUH5u9miuAa5&=Y2pVh>L`#m9 zm98Z15fveyCxvl@u1N@ZOV+VP{nzsKPFx#j-Zg$b8zMM0X6%DC`=9?90-k!-zaF^D z|Km#gm&@4V|2?f&J^u~5{igo26R5oZbAt2C-#>l_DH=!P$?14BIXf9m(NV$K5M*>8 zZ_sx)1-yTZUY-F`PVs&p2tHt&@Bc^O?>j-~;z!mI6&o*H>=&rty_%j+N0VPilL`Y7 zVi=|?Ok*A;nK{+@$ruRp=lAEx_J9ub;oZD{5}J=yWe z^ydnh2ug|EG&pXMDTvQsP0xNGk19OoOvJi`3%=rJiNouYcmJ-=(ZUO_>ZV8Fri1%Y zyVWxpjp6+NLKs#yyx)fm-1lsYB_l6T{B}Ay9#2No>G}B0xFSE#7$P^S(W69Llv?Vz zxAb&G73$n#2f_n$;cZphawgwFUt}+kRV$Ll5bvuy;J5ky zz~F!enSlx{hAX{h2lk>N`uMTpoHsYJPt0YtSI&0QF9uufm-CbK$0HON8(azSvM~y` z_}ozfUO+-MS&|$(A_#dl92DB_(w4K4pX zRS0pqjBhx20oVmbYE#7{DsV_+^39DZCu1nb7Qeb*5q}xr!i)QWEJJ=CS==2Wb!Aae zo>yHS_z4D#S`_6OhrH@nN&M2cykMAh!x%-wyKI4#^0+55M8Tmq8{#%nZV0_NwQ?lG zp3;v^5w7y-i-#I>nw*}VWpN>!pOxh=aZz3xI1-uLE#iAKHJ!{D7B(JdRyh{VQ!q#5 z#J0#J7CkA3IXg@IsV1%ze5n-TcWcUe1!_!Pvq@|=`DWzY^hVM2nF7Ok6l^ko&h_~3 zpS}P#@Og7OJ*)DFw}xa2cID4covb$S;IRGtm)x}a0vTJ*e_ioVuK#HL=KbGpK$PNz z$8(SSBJQNNho;iS&p%{TIL32`qT>2zhKMfljCpW$oUMTZy5V4l=8$fpJj|z|8bYcS zip(x;_(43&%%sp#4)sM;2Rh%bMKYf>DlMs&xE@;KB5}pLh2QE}p&8*WS~4tE;RG?) zqFB7B#|$`@_o3tf8%M=>*JGT*Amelu&#k2oJ4*RvoAedtZydA9u7C=$KCxmg z%+p0)iQNh5C%Ad(g}|m&vBlBL)A0nZUZ=p5rPg=me-HF+pl$E}bYoDh{~O&V|Jw<8 zfrt~bFc_Z{tKYw;001X`puzwE diff --git a/pkg/chart/testdata/frobnitz/Chart.toml b/pkg/chart/testdata/frobnitz/Chart.toml new file mode 100644 index 000000000..0f4c51d57 --- /dev/null +++ b/pkg/chart/testdata/frobnitz/Chart.toml @@ -0,0 +1,15 @@ +name = "frobnitz" +description = "This is a frobniz." +version = "1.2.3-alpha.1+12345" +keywords = ["frobnitz", "sprocket", "dodad"] +home = "http://example.com" +source = [ + "https://example.com/foo/bar", + "https://github.com/example/foo" +] +[[maintainer]] + name = "The Helm Team" + email = "helm@example.com" +[[maintainer]] + name = "Someone Else" + email = "nobody@example.com" diff --git a/pkg/chart/testdata/frobnitz/Chart.yaml b/pkg/chart/testdata/frobnitz/Chart.yaml index b1e67a038..2fa9ac025 100644 --- a/pkg/chart/testdata/frobnitz/Chart.yaml +++ b/pkg/chart/testdata/frobnitz/Chart.yaml @@ -1,4 +1,3 @@ -#helm:generate foo name: frobnitz description: This is a frobniz. version: "1.2.3-alpha.1+12345" @@ -14,20 +13,3 @@ maintainers: source: - https://example.com/foo/bar home: http://example.com -dependencies: - - name: thingerbob - location: https://example.com/charts/thingerbob-3.2.1.tgz - version: ^3 -environment: - - name: Kubernetes - version: ~1.1 - extensions: - - extensions/v1beta1 - - extensions/v1beta1/daemonset - apiGroups: - - 3rdParty -expander: - name: Expandybird - entrypoint: templates/wordpress.jinja -schema: wordpress.jinja.schema - \ No newline at end of file diff --git a/pkg/chart/testdata/frobnitz/INSTALL.txt b/pkg/chart/testdata/frobnitz/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/pkg/chart/testdata/frobnitz/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/pkg/chart/testdata/frobnitz/hooks/pre-install.py b/pkg/chart/testdata/frobnitz/hooks/pre-install.py deleted file mode 100644 index c9b0d0a92..000000000 --- a/pkg/chart/testdata/frobnitz/hooks/pre-install.py +++ /dev/null @@ -1 +0,0 @@ -# Placeholder. diff --git a/pkg/chart/testdata/frobnitz/templates/template.tpl b/pkg/chart/testdata/frobnitz/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/pkg/chart/testdata/frobnitz/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/pkg/chart/testdata/frobnitz/templates/wordpress-resources.yaml b/pkg/chart/testdata/frobnitz/templates/wordpress-resources.yaml deleted file mode 100644 index 00f709de0..000000000 --- a/pkg/chart/testdata/frobnitz/templates/wordpress-resources.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# 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 diff --git a/pkg/chart/testdata/frobnitz/templates/wordpress.jinja b/pkg/chart/testdata/frobnitz/templates/wordpress.jinja deleted file mode 100644 index f34e4fec9..000000000 --- a/pkg/chart/testdata/frobnitz/templates/wordpress.jinja +++ /dev/null @@ -1,72 +0,0 @@ -#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 diff --git a/pkg/chart/testdata/frobnitz/templates/wordpress.jinja.schema b/pkg/chart/testdata/frobnitz/templates/wordpress.jinja.schema deleted file mode 100644 index 215b47e1e..000000000 --- a/pkg/chart/testdata/frobnitz/templates/wordpress.jinja.schema +++ /dev/null @@ -1,69 +0,0 @@ -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. - diff --git a/pkg/chart/testdata/frobnitz/templates/wordpress.yaml b/pkg/chart/testdata/frobnitz/templates/wordpress.yaml deleted file mode 100644 index b401897ab..000000000 --- a/pkg/chart/testdata/frobnitz/templates/wordpress.yaml +++ /dev/null @@ -1,6 +0,0 @@ -imports: -- path: wordpress.jinja - -resources: -- name: wordpress - type: wordpress.jinja diff --git a/pkg/chart/testdata/frobnitz/values.toml b/pkg/chart/testdata/frobnitz/values.toml new file mode 100644 index 000000000..6fc24051f --- /dev/null +++ b/pkg/chart/testdata/frobnitz/values.toml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name = "Some Name" + +[section] +name = "Name in a section" diff --git a/pkg/chart/testdata/ill-1.2.3.tgz b/pkg/chart/testdata/ill-1.2.3.tgz deleted file mode 100644 index 198cd2e877f732eaf3068390d3022319df2819ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2305 zcmV+c3I6sUiwFSO*Rxgt1MM4YbK5pDpZO~=^<@0vB8m@DZZ&f=m$Q|`Z4>JxU3;C( zodc1OiI^mK04PUJ-QRuy_@*h#&RKFZ;eLoiU|+yu7rOv79ltzzJJv(*+3vtFjNxDa z4WA?J&PyFQ|=cmi#? zyn|0V6j1#AN?`6Iz!BpfHk;=c&67`b?f zUC1$sbWM|dJ}fB-Bh0V+A1bU|5^;zl24Kpn!U-|OOEqhoI4&f|o}nAMsq>5L4`7J{ z1hC*20Z3gRuMi+h#i&59$j>?9Ao!3e03)xWA8u$Sz9YhcX&BFi?qMwSSp?{XCKd;{ z^062{^GOnTfN{YSED(qxWe5r+U3+SCJ{ZIh`n#4P{oZjl`@i&;y8qj@ee?`CyH-rX zH}3z^{>O6h>uVSWyOFUa{+p&{Htg?M&QTZt_W=h!3c}F^iV%eyfh8eY1Vc0eVqs_= zVlKt849Fue_eEq>!7`1q*SfYsl&QY&Ox@D$9t>h1>gHce%N{u2X_shyMX1L{8UQ`T zQ2u2xCGI8S>cb-*^t4dy4|0zX>{8rR1sS*B2=%}m!BDgVfCOeR0#fI{p-(W3Ma&g3 zxDj|FJa`tuQ4~h$3E<_fxV! zG(utSj6zpE!zSn^IFkuhQ0EnG3|Bc}G{@gD4c=%4z_>7{8_@i7V`E>P^Hz z)?eQa_-%uX{~mGKE|Y+V@Bag9*zNy&0cn4qkjq^zfG@EBU}$yr-v>zh|9pBpem5KM zOvbkQ->{s*{zKcA_O}Oim;dbpGTVbVfG&~+KcZh@72Cm=g7p9Cj?VzM`M+g1>%YO! zv^xLa2ONM`*_oL7G+Q9+%CxaKL4M_*59QaTkn}I|krT8akg#r@G zVnx$l5emGt0a1XXC3yiDLh96xleibiK0r}S7*0>^o_b=w5s?1RkuamE{&$rFK0N<( zY}@JbzkNX6{_?mcriihg_(vU&utzu-+!p`s;jnIh%d&>UF8=QW4!~sU0%3zLK+eP!xddrW1>=h+b% z0M{fEcnRydLI{O{*-KB!7`{fY7b9R9M!`N@v;PLW5b)Hq{^x-W{;z+;(MR}5GPd}? zV>nL3|BZpso&WCz8t4CBcwYGX=jS3t<6ts9n~bOP)A0=awc>0*WOScz!1J35-jCzs zxj-r@-X98rkI3bR|CacNUf8?*p7rE~Gmb6}E7b2^&CX}z=`Z7HgMkb&4AB*$DUX`W zlIr|)A_&Xx_lG#ngqrg1*m7*$8XUEYJfECR=MD0h&`fKw8h>afJ2{>G)F6{VDU+KA z#|<(i@%gLS{I|)t!DGo}tjoBdPkd10@cQ)KznXJ&fum1t(<5>7!F_Ms>Y0uwqW*s= z4XYa7ABqe-z%FFj$SV}To=s0C)A4L}K6x`~$S>1|*pFNEsL@uX);jJjJs(kny0q92 z(gRE4x4+H)@pD6EwY9XUs&I1w>Z8iowvFL!Ne`KxJv57SQ$g!T=j(CmYW0<1)9hV`hvx1 zWwz|VI39t|pBv73bEEp?TsC{vY-jy)u+4roKUse|LW!}#l>}ZfM(Gw`I!b{TlBl08 zNsc@jgs_W7E30M5S?75%^h3 zix{a(bxit#LmrcFZu)96Mrv&7CEkhzNr);}cp*t5ejY>ak3fF~Y5(f#s_*kK$S}mH z<;^1FBCq;I5g!K-hohofj8QhcD;8KSk9#6RR2+JXjM*M zI@FZY^z3Y2#D!vhRxn6VRbC!AGMU>g65vHYpUf04Tr?@Haw46lVvg8LZBa<9dQu8Y zHqZR2C9YI_sTSh*Ysz*7T1?%tNo+RxX5`ZJR@L-{0;6RdZZdz#_2lmlUjQ5Uyg8fA zn>^C3A)A7I`SVjJs~tQ#+*$wWOq4_6|6K-dssHSu**O2R%whNbcRwJH;-$xP%mW#B z@&zE8YOlY3S5e^vEkzWSZ@>yfaD^5Oi_&qi78Ige6n0=K(oIl?`8-sMkP0K2+2su{ zlAYEnWOIiUua?c+J*dh-gDiovBi?=3`RBny3DmG;Iwb4_?0V9AV4y z-6#yuBw|4Rk^y8@!+odc-s;KIh$mm z?&aq<48`w1cK8T%tN%N6f72n01Vqt@wm$;)B;}4*eVw*s_wBI!D z*V{2&h*58gF}}~Glze%43p{K$clX=u_Jgs4|5IWZ+0_BvX1x%A`w8ai^8-t&(iL%d_m5)AYfQ&hp{5nMEn@SXviJo}y++ zI!;;B?d-e6_Lz7ha*-D~KOGwC?V!n~JV!DpW1ltoehNB|&z}F0WQU=7FCbkv>t{bT zOMB;;3L&mi_i>l{w1d9R($7IJ#-{T} - - Example icon - - - diff --git a/pkg/chart/testdata/sprocket/templates/placeholder.txt b/pkg/chart/testdata/sprocket/templates/placeholder.txt deleted file mode 100644 index ef9fb20a7..000000000 --- a/pkg/chart/testdata/sprocket/templates/placeholder.txt +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder. diff --git a/pkg/chart/testdata/sprocket/templates/template.tpl b/pkg/chart/testdata/sprocket/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/pkg/chart/testdata/sprocket/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/pkg/chart/testdata/sprocket/values.toml b/pkg/chart/testdata/sprocket/values.toml new file mode 100644 index 000000000..6fc24051f --- /dev/null +++ b/pkg/chart/testdata/sprocket/values.toml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name = "Some Name" + +[section] +name = "Name in a section" From 274067a2b4c13e711707a9b5538c644319dfa65c Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Thu, 14 Apr 2016 14:38:00 -0600 Subject: [PATCH 26/86] ref(Makefile): add default registry + "/"s --- Makefile | 4 ++-- versioning.mk | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 308737dcb..e80d68faa 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -DOCKER_REGISTRY := -IMAGE_PREFIX ?= helm +DOCKER_REGISTRY ?= gcr.io +IMAGE_PREFIX ?= deis-sandbox SHORT_NAME ?= tiller # go option diff --git a/versioning.mk b/versioning.mk index 2faa2ec6f..8d96c8629 100644 --- a/versioning.mk +++ b/versioning.mk @@ -1,8 +1,8 @@ MUTABLE_VERSION ?= canary VERSION ?= git-$(shell git rev-parse --short HEAD) -IMAGE := ${DOCKER_REGISTRY}${IMAGE_PREFIX}/${SHORT_NAME}:${VERSION} -MUTABLE_IMAGE := ${DOCKER_REGISTRY}${IMAGE_PREFIX}/${SHORT_NAME}:${MUTABLE_VERSION} +IMAGE := ${DOCKER_REGISTRY}/${IMAGE_PREFIX}/${SHORT_NAME}:${VERSION} +MUTABLE_IMAGE := ${DOCKER_REGISTRY}/${IMAGE_PREFIX}/${SHORT_NAME}:${MUTABLE_VERSION} info: @echo "Build tag: ${VERSION}" From 72acb82e5002dbb7c8e72d75551b3e9d72e862b5 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Thu, 14 Apr 2016 14:39:32 -0600 Subject: [PATCH 27/86] feat(init): add tiller installer logic --- pkg/client/install.go | 91 +++++++++++++++++++++++++++++++++++++ pkg/kubectl/command.go | 32 +++++++++++++ pkg/kubectl/create.go | 21 +++++++++ pkg/kubectl/create_test.go | 22 +++++++++ pkg/kubectl/kubectl.go | 19 ++++++++ pkg/kubectl/kubectl_test.go | 12 +++++ 6 files changed, 197 insertions(+) create mode 100644 pkg/client/install.go create mode 100644 pkg/kubectl/command.go create mode 100644 pkg/kubectl/create.go create mode 100644 pkg/kubectl/create_test.go create mode 100644 pkg/kubectl/kubectl.go create mode 100644 pkg/kubectl/kubectl_test.go diff --git a/pkg/client/install.go b/pkg/client/install.go new file mode 100644 index 000000000..1861727e2 --- /dev/null +++ b/pkg/client/install.go @@ -0,0 +1,91 @@ +package client + +import ( + "bytes" + "text/template" + + "github.com/Masterminds/sprig" + "github.com/deis/tiller/pkg/kubectl" +) + +// Installer installs tiller into Kubernetes +// +// See InstallYAML. +type Installer struct { + + // Metadata holds any global metadata attributes for the resources + Metadata map[string]interface{} + + // Tiller specific metadata + Tiller map[string]interface{} +} + +// New Installer creates a new Installer +func NewInstaller() *Installer { + return &Installer{ + Metadata: map[string]interface{}{}, + Tiller: map[string]interface{}{}, + } +} + +// Install uses kubectl to install tiller +// +// Returns the string output received from the operation, and an error if the +// command failed. +func (i *Installer) Install(runner kubectl.Runner) (string, error) { + b, err := i.expand() + if err != nil { + return "", err + } + + o, err := runner.Create(b) + return string(o), err +} + +func (i *Installer) expand() ([]byte, error) { + var b bytes.Buffer + t := template.Must(template.New("manifest").Funcs(sprig.TxtFuncMap()).Parse(InstallYAML)) + err := t.Execute(&b, i) + return b.Bytes(), err +} + +// InstallYAML is the installation YAML for DM. +const InstallYAML = ` +--- +apiVersion: v1 +kind: Namespace +metadata: + labels: + app: helm + name: helm-namespace + name: helm +--- +apiVersion: v1 +kind: ReplicationController +metadata: + labels: + app: helm + name: tiller + name: tiller-rc + namespace: helm +spec: + replicas: 1 + selector: + app: helm + name: tiller + template: + metadata: + labels: + app: helm + name: tiller + spec: + containers: + - env: [] + image: {{default "gcr.io/deis-sandbox/tiller:canary" .Tiller.Image}} + name: tiller + ports: + - containerPort: 8080 + name: tiller + imagePullPolicy: Always +--- +` diff --git a/pkg/kubectl/command.go b/pkg/kubectl/command.go new file mode 100644 index 000000000..b36e0ad33 --- /dev/null +++ b/pkg/kubectl/command.go @@ -0,0 +1,32 @@ +package kubectl + +import ( + "bytes" + "fmt" + "io/ioutil" + "os/exec" + "strings" +) + +type cmd struct { + *exec.Cmd +} + +func command(args ...string) *cmd { + return &cmd{exec.Command(Path, args...)} +} + +func assignStdin(cmd *cmd, in []byte) { + cmd.Stdin = bytes.NewBuffer(in) +} + +func (c *cmd) String() string { + var stdin string + + if c.Stdin != nil { + b, _ := ioutil.ReadAll(c.Stdin) + stdin = fmt.Sprintf("< %s", string(b)) + } + + return fmt.Sprintf("[CMD] %s %s", strings.Join(c.Args, " "), stdin) +} diff --git a/pkg/kubectl/create.go b/pkg/kubectl/create.go new file mode 100644 index 000000000..af9297aa9 --- /dev/null +++ b/pkg/kubectl/create.go @@ -0,0 +1,21 @@ +package kubectl + +// Create uploads a chart to Kubernetes +func (r RealRunner) Create(stdin []byte) ([]byte, error) { + args := []string{"create", "-f", "-"} + + cmd := command(args...) + assignStdin(cmd, stdin) + + return cmd.CombinedOutput() +} + +// Create returns the commands to kubectl +func (r PrintRunner) Create(stdin []byte) ([]byte, error) { + args := []string{"create", "-f", "-"} + + cmd := command(args...) + assignStdin(cmd, stdin) + + return []byte(cmd.String()), nil +} diff --git a/pkg/kubectl/create_test.go b/pkg/kubectl/create_test.go new file mode 100644 index 000000000..bca94f6e5 --- /dev/null +++ b/pkg/kubectl/create_test.go @@ -0,0 +1,22 @@ +package kubectl + +import ( + "testing" +) + +func TestPrintCreate(t *testing.T) { + var client Runner = PrintRunner{} + + expected := `[CMD] kubectl create -f - < some stdin data` + + out, err := client.Create([]byte("some stdin data")) + if err != nil { + t.Error(err) + } + + actual := string(out) + + if expected != actual { + t.Fatalf("actual %s != expected %s", actual, expected) + } +} diff --git a/pkg/kubectl/kubectl.go b/pkg/kubectl/kubectl.go new file mode 100644 index 000000000..e527b71fe --- /dev/null +++ b/pkg/kubectl/kubectl.go @@ -0,0 +1,19 @@ +package kubectl + +// Path is the path of the kubectl binary +var Path = "kubectl" + +// Runner is an interface to wrap kubectl convenience methods +type Runner interface { + // Create uploads a chart to Kubernetes + Create(stdin []byte) ([]byte, error) +} + +// RealRunner implements Runner to execute kubectl commands +type RealRunner struct{} + +// PrintRunner implements Runner to return a []byte of the command to be executed +type PrintRunner struct{} + +// Client stores the instance of Runner +var Client Runner = RealRunner{} diff --git a/pkg/kubectl/kubectl_test.go b/pkg/kubectl/kubectl_test.go new file mode 100644 index 000000000..c3348bba5 --- /dev/null +++ b/pkg/kubectl/kubectl_test.go @@ -0,0 +1,12 @@ +package kubectl + +type TestRunner struct { + Runner + + out []byte + err error +} + +func (r TestRunner) Create(stdin []byte, ns string) ([]byte, error) { + return r.out, r.err +} From 306aca6aec4961c77ce12ab00ee414919dffeb33 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Thu, 14 Apr 2016 14:40:10 -0600 Subject: [PATCH 28/86] feat(init): add init logic + -i flag to override tiller image --- cmd/helm/init.go | 31 +++++++++++++++++++++++++++++-- cmd/helm/init_test.go | 10 +++------- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 2565f34dd..1074afc82 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -4,10 +4,15 @@ import ( "errors" "fmt" + "github.com/deis/tiller/pkg/client" + "github.com/deis/tiller/pkg/kubectl" "github.com/spf13/cobra" ) +var tillerImg string + func init() { + initCmd.Flags().StringVarP(&tillerImg, "tiller-image", "i", "", "override tiller image") RootCommand.AddCommand(initCmd) } @@ -20,6 +25,28 @@ var initCmd = &cobra.Command{ // RunInit initializes local config and installs tiller to Kubernetes Cluster func RunInit(cmd *cobra.Command, args []string) error { - fmt.Fprintln(stdout, "Init was called.") - return errors.New("NotImplemented") + if len(args) != 0 { + return errors.New("This command does not accept arguments. \n") + } + + // TODO: take value of global flag kubectl and pass that in + runner := buildKubectlRunner("") + + i := client.NewInstaller() + i.Tiller["Image"] = tillerImg + + out, err := i.Install(runner) + if err != nil { + return fmt.Errorf("error installing %s %s", string(out), err) + } + + fmt.Printf("Tiller (the helm server side component) has been installed into your Kubernetes Cluster.\n") + return nil +} + +func buildKubectlRunner(kubectlPath string) kubectl.Runner { + if kubectlPath != "" { + kubectl.Path = kubectlPath + } + return &kubectl.RealRunner{} } diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index 333714ad0..59a45c6ab 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -4,11 +4,7 @@ import ( "testing" ) -func TestRunInit(t *testing.T) { - - //TODO: call command and make sure no error is recevied - err := RunInit(initCmd, nil) - if err != nil { - t.Errorf("Expected no error but got one: %s", err) - } +func TestInit(t *testing.T) { + //TODO: call command and make sure no error is returned + //TODO: check local config } From 40cbc9007c0e5f68a0058e13ada3f6c7382432c9 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Thu, 14 Apr 2016 14:55:05 -0600 Subject: [PATCH 29/86] ref(init): add desc, rm cmd/tiller/Dockerfile --- cmd/helm/init.go | 7 ++++++- cmd/tiller/.dockerignore | 3 --- cmd/tiller/Dockerfile | 5 ----- 3 files changed, 6 insertions(+), 9 deletions(-) delete mode 100644 cmd/tiller/.dockerignore delete mode 100644 cmd/tiller/Dockerfile diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 1074afc82..062f6d6b4 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -9,6 +9,11 @@ import ( "github.com/spf13/cobra" ) +const longDesc = ` +This command installs Tiller (the helm server side component) onto your +Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.helm/) +` + var tillerImg string func init() { @@ -19,7 +24,7 @@ func init() { var initCmd = &cobra.Command{ Use: "init", Short: "Initialize Helm on both client and server.", - Long: `Add long help here`, + Long: longDesc, RunE: RunInit, } diff --git a/cmd/tiller/.dockerignore b/cmd/tiller/.dockerignore deleted file mode 100644 index f6fb65317..000000000 --- a/cmd/tiller/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -.dockerignore -Dockerfile -Makefile diff --git a/cmd/tiller/Dockerfile b/cmd/tiller/Dockerfile deleted file mode 100644 index eba2c76af..000000000 --- a/cmd/tiller/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ubuntu:14.04 - -ADD . / - -CMD ["/tiller"] From af88c5863f908c72164d5985ec230261699288b6 Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Thu, 14 Apr 2016 15:19:22 -0700 Subject: [PATCH 30/86] fix(Makefile): disable verbose on build --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 308737dcb..89a5ea3db 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ include versioning.mk all: build .PHONY: build -build: GOFLAGS += -v -a -installsuffix cgo +build: GOFLAGS += -a -installsuffix cgo build: @for i in $(BINARIES); do \ CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GO) build -o $(BINDIR)/$$i $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./cmd/$$i || exit 1; \ From 68e3cc1cc996ac128102dffaa4e4cc596d94626b Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Thu, 14 Apr 2016 17:08:05 -0600 Subject: [PATCH 31/86] feat(helm): add 'helm package' command --- cmd/helm/package.go | 59 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 cmd/helm/package.go diff --git a/cmd/helm/package.go b/cmd/helm/package.go new file mode 100644 index 000000000..3e8e82978 --- /dev/null +++ b/cmd/helm/package.go @@ -0,0 +1,59 @@ +package main + +import ( + "os" + "path/filepath" + + "github.com/deis/tiller/pkg/chart" + "github.com/spf13/cobra" +) + +const packageDesc = ` +This command packages a chart into a versioned chart archive file. If a path +is given, this will look at that path for a chart (which must contain a +Chart.yaml file) and then package that directory. + +If no path is given, this will look in the present working directory for a +Chart.yaml file, and (if found) build the current directory into a chart. + +Versioned chart archives are used by Helm package repositories. +` + +func init() { + RootCommand.AddCommand(packageCmd) +} + +var packageCmd = &cobra.Command{ + Use: "package", + Short: "Package a chart directory into a chart archive.", + Long: packageDesc, + RunE: runPackage, +} + +func runPackage(cmd *cobra.Command, args []string) error { + path := "." + if len(args) > 0 { + path = args[0] + } + + path, err := filepath.Abs(path) + if err != nil { + return err + } + + ch, err := chart.LoadDir(path) + if err != nil { + return err + } + + // Save to the current working directory. + cwd, err := os.Getwd() + if err != nil { + return err + } + name, err := chart.Save(ch, cwd) + if err == nil { + cmd.Printf("Saved %s", name) + } + return err +} From 8afc295608b7a0d0b5170eaa4a992cc40669d87f Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Thu, 14 Apr 2016 17:25:00 -0600 Subject: [PATCH 32/86] feat(helm): add global usage description to helm --- cmd/helm/helm.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 785dac967..8ab220c20 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -8,10 +8,31 @@ import ( var stdout = os.Stdout +var globalUsage = `The Kubernetes package manager + +To begin working with Helm, run the 'helm init' command: + +$ helm init + +This will install Tiller to your running Kubernetes cluster. +It will also set up any necessary local configuration. + +Commond actions from this point on include: + +- helm search: search for charts +- helm fetch: download a chart to your local directory to view +- helm install: upload the chart to Kubernetes +- helm list: list releases of charts + +ENVIRONMENT: +$HELM_HOME: Set an alternative location for Helm files. + By default, these are stored in ~/.helm +` + var RootCommand = &cobra.Command{ Use: "helm", Short: "The Helm package manager for Kubernetes.", - Long: `Do long help here.`, + Long: globalUsage, } func main() { From 2e11f2694678532b77e5eb85e54dbb8ee1b0074b Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Thu, 14 Apr 2016 17:25:39 -0600 Subject: [PATCH 33/86] feat(home): add helm home command --- cmd/helm/home.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 cmd/helm/home.go diff --git a/cmd/helm/home.go b/cmd/helm/home.go new file mode 100644 index 000000000..3b091931b --- /dev/null +++ b/cmd/helm/home.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var longHomeHelp = ` +This command displays the location of HELM_HOME. This is where +any helm configuration files live. +` + +var homeCommand = &cobra.Command{ + Use: "home", + Short: "Displays the location of HELM_HOME", + Long: longHomeHelp, + Run: Home, +} + +func init() { + RootCommand.AddCommand(homeCommand) +} + +func Home(cmd *cobra.Command, args []string) { + fmt.Println("helm home was called") +} From 0ad4803aa32f7c85aa9e97f637e2b4ee0bf6ed2e Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 15 Apr 2016 09:56:24 -0600 Subject: [PATCH 34/86] fix(storage): Use a CRUD interface Fixes #23 --- cmd/tiller/environment/environment.go | 39 +++++++++++++++++++--- cmd/tiller/environment/environment_test.go | 25 +++++++++++--- pkg/storage/memory.go | 35 +++++++++++++++---- pkg/storage/memory_test.go | 31 +++++++++++++---- 4 files changed, 108 insertions(+), 22 deletions(-) diff --git a/cmd/tiller/environment/environment.go b/cmd/tiller/environment/environment.go index a0e2c2990..078d24b50 100644 --- a/cmd/tiller/environment/environment.go +++ b/cmd/tiller/environment/environment.go @@ -58,15 +58,46 @@ type Engine interface { // // Release storage must be concurrency safe. type ReleaseStorage interface { - // Get takes a name and returns the accompanying release. - Get(key string) (*hapi.Release, error) - // Set saves the release with the given name. - Set(key string, val *hapi.Release) error + + // Create stores a release in the storage. + // + // If a release with the same name exists, this returns an error. + // + // It may return other errors in cases where it cannot write to storage. + Create(*hapi.Release) error + // Read takes a name and returns a release that has that name. + // + // It will only return releases that are not deleted and not superseded. + // + // It will return an error if no relevant release can be found, or if storage + // is not properly functioning. + Read(name string) (*hapi.Release, error) + + // Update looks for a release with the same name and updates it with the + // present release contents. + // + // For immutable storage backends, this may result in a new release record + // being created, and the previous release being marked as superseded. + // + // It will return an error if a previous release is not found. It may also + // return an error if the storage backend encounters an error. + Update(*hapi.Release) error + + // Delete marks a Release as deleted. + // + // It returns the deleted record. If the record is not found or if the + // underlying storage encounters an error, this will return an error. + Delete(name string) (*hapi.Release, error) + // List lists all active (non-deleted, non-superseded) releases. // // To get deleted or superseded releases, use Query. List() ([]*hapi.Release, error) + // Query takes a map of labels and returns any releases that match. + // + // Query will search all releases, including deleted and superseded ones. + // The provided map will be used to filter results. Query(map[string]string) ([]*hapi.Release, error) } diff --git a/cmd/tiller/environment/environment_test.go b/cmd/tiller/environment/environment_test.go index c1d037423..ffe531490 100644 --- a/cmd/tiller/environment/environment_test.go +++ b/cmd/tiller/environment/environment_test.go @@ -18,15 +18,24 @@ type mockReleaseStorage struct { rel *hapi.Release } -func (r *mockReleaseStorage) Get(k string) (*hapi.Release, error) { +func (r *mockReleaseStorage) Create(v *hapi.Release) error { + r.rel = v + return nil +} + +func (r *mockReleaseStorage) Read(k string) (*hapi.Release, error) { return r.rel, nil } -func (r *mockReleaseStorage) Set(k string, v *hapi.Release) error { +func (r *mockReleaseStorage) Update(v *hapi.Release) error { r.rel = v return nil } +func (r *mockReleaseStorage) Delete(k string) (*hapi.Release, error) { + return r.rel, nil +} + func (r *mockReleaseStorage) List() ([]*hapi.Release, error) { return []*hapi.Release{}, nil } @@ -68,15 +77,23 @@ func TestReleaseStorage(t *testing.T) { release := &hapi.Release{Name: "mariner"} - if err := env.Releases.Set("albatross", release); err != nil { + if err := env.Releases.Create(release); err != nil { t.Fatalf("failed to store release: %s", err) } - if v, err := env.Releases.Get("albatross"); err != nil { + if err := env.Releases.Update(release); err != nil { + t.Fatalf("failed to update release: %s", err) + } + + if v, err := env.Releases.Read("albatross"); err != nil { t.Errorf("Error fetching release: %s", err) } else if v.Name != "mariner" { t.Errorf("Expected mariner, got %q", v.Name) } + + if _, err := env.Releases.Delete("albatross"); err != nil { + t.Fatalf("failed to delete release: %s", err) + } } func TestKubeClient(t *testing.T) { diff --git a/pkg/storage/memory.go b/pkg/storage/memory.go index a3670426a..16d5650db 100644 --- a/pkg/storage/memory.go +++ b/pkg/storage/memory.go @@ -19,10 +19,10 @@ func NewMemory() *Memory { var ErrNotFound = errors.New("release not found") -// Get returns the named Release. +// Read returns the named Release. // // If the release is not found, an ErrNotFound error is returned. -func (m *Memory) Get(k string) (*hapi.Release, error) { +func (m *Memory) Read(k string) (*hapi.Release, error) { v, ok := m.releases[k] if !ok { return v, ErrNotFound @@ -30,14 +30,35 @@ func (m *Memory) Get(k string) (*hapi.Release, error) { return v, nil } -// Set sets a release. -// -// TODO: Is there any reason why Set doesn't just use the release name? -func (m *Memory) Set(k string, rel *hapi.Release) error { - m.releases[k] = rel +// Create sets a release. +func (m *Memory) Create(rel *hapi.Release) error { + m.releases[rel.Name] = rel return nil } +var ErrNoRelease = errors.New("no release found") + +// Update sets a release. +func (m *Memory) Update(rel *hapi.Release) error { + if _, ok := m.releases[rel.Name]; !ok { + return ErrNoRelease + } + + // FIXME: When Release is done, we need to do this right by marking the old + // release as superseded, and creating a new release. + m.releases[rel.Name] = rel + return nil +} + +func (m *Memory) Delete(name string) (*hapi.Release, error) { + rel, ok := m.releases[name] + if !ok { + return nil, ErrNoRelease + } + delete(m.releases, name) + return rel, nil +} + // List returns all releases func (m *Memory) List() ([]*hapi.Release, error) { buf := make([]*hapi.Release, len(m.releases)) diff --git a/pkg/storage/memory_test.go b/pkg/storage/memory_test.go index 7b619b5b0..b07e664ad 100644 --- a/pkg/storage/memory_test.go +++ b/pkg/storage/memory_test.go @@ -6,13 +6,13 @@ import ( "github.com/deis/tiller/pkg/hapi" ) -func TestSet(t *testing.T) { +func TestCreate(t *testing.T) { k := "test-1" r := &hapi.Release{Name: k} ms := NewMemory() - if err := ms.Set(k, r); err != nil { - t.Fatalf("Failed set: %s", err) + if err := ms.Create(r); err != nil { + t.Fatalf("Failed create: %s", err) } if ms.releases[k].Name != k { @@ -20,26 +20,43 @@ func TestSet(t *testing.T) { } } -func TestGet(t *testing.T) { +func TestRead(t *testing.T) { k := "test-1" r := &hapi.Release{Name: k} ms := NewMemory() - ms.Set(k, r) + ms.Create(r) - if out, err := ms.Get(k); err != nil { + if out, err := ms.Read(k); err != nil { t.Errorf("Could not get %s: %s", k, err) } else if out.Name != k { t.Errorf("Expected %s, got %s", k, out.Name) } } +func TestUpdate(t *testing.T) { + k := "test-1" + r := &hapi.Release{Name: k} + + ms := NewMemory() + if err := ms.Create(r); err != nil { + t.Fatalf("Failed create: %s", err) + } + if err := ms.Update(r); err != nil { + t.Fatalf("Failed update: %s", err) + } + + if ms.releases[k].Name != k { + t.Errorf("Unexpected release name: %s", ms.releases[k].Name) + } +} + func TestList(t *testing.T) { ms := NewMemory() rels := []string{"a", "b", "c"} for _, k := range rels { - ms.Set(k, &hapi.Release{Name: k}) + ms.Create(&hapi.Release{Name: k}) } l, err := ms.List() From 619e1e2a0a03d619bceb6323f8bacf2bf4f0349a Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 15 Apr 2016 16:18:46 -0600 Subject: [PATCH 35/86] feat(chart): add values parser This adds code to parse TOML files into Values maps. These can then easily be passed into the template engine. Included in this is support for TOML "tables", subsections of TOML files. We will be using those to pass config data to dependent charts. --- glide.lock | 8 +- glide.yaml | 1 + pkg/chart/testdata/coleridge.toml | 11 +++ pkg/chart/values.go | 67 +++++++++++++++ pkg/chart/values_test.go | 134 ++++++++++++++++++++++++++++++ 5 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 pkg/chart/testdata/coleridge.toml create mode 100644 pkg/chart/values.go create mode 100644 pkg/chart/values_test.go diff --git a/glide.lock b/glide.lock index 795c132ac..71ba5d45c 100644 --- a/glide.lock +++ b/glide.lock @@ -1,8 +1,10 @@ -hash: 7f9a27ad54a10edaa7c57521246676477d0f84ef4246524bae75f8df9d049983 -updated: 2016-04-14T12:24:49.130995956-06:00 +hash: e7c99013acb06eb359cf20390579af9a4553ef0fbed3f7bbb784b4ab7c8df807 +updated: 2016-04-15T15:15:21.87772545-06:00 imports: - name: github.com/aokoli/goutils version: 9c37978a95bd5c709a15883b6242714ea6709e64 +- name: github.com/BurntSushi/toml + version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f - name: github.com/codegangsta/cli version: 71f57d300dd6a780ac1856c005c4b518cfd498ec - name: github.com/golang/protobuf @@ -28,7 +30,7 @@ imports: - http2/hpack - internal/timeseries - name: google.golang.org/grpc - version: 9ac074585f926c8506b6351bfdc396d2b19b1cb1 + version: 8eeecf2291de9d171d0b1392a27ff3975679f4f5 subpackages: - codes - credentials diff --git a/glide.yaml b/glide.yaml index a47f5a981..2b1531d12 100644 --- a/glide.yaml +++ b/glide.yaml @@ -12,3 +12,4 @@ import: - package: gopkg.in/yaml.v2 - package: github.com/Masterminds/semver version: 1.1.0 +- package: github.com/BurntSushi/toml diff --git a/pkg/chart/testdata/coleridge.toml b/pkg/chart/testdata/coleridge.toml new file mode 100644 index 000000000..bd16a8c84 --- /dev/null +++ b/pkg/chart/testdata/coleridge.toml @@ -0,0 +1,11 @@ +poet = "Coleridge" +title = "Rime of the Ancient Mariner" +stanza = ["at", "length", "did", "cross", "an", "Albatross"] + +[mariner] +with = "crossbow" +shot = "ALBATROSS" + +[water.water] +where = "everywhere" +nor = "any drop to drink" diff --git a/pkg/chart/values.go b/pkg/chart/values.go new file mode 100644 index 000000000..8c1c1200d --- /dev/null +++ b/pkg/chart/values.go @@ -0,0 +1,67 @@ +package chart + +import ( + "errors" + "io/ioutil" + "strings" + + "github.com/BurntSushi/toml" +) + +var ErrNoTable = errors.New("no table") + +type Values map[string]interface{} + +// Table gets a table (TOML subsection) from a Values object. +// +// The table is returned as a Values. +// +// Compound table names may be specified with dots: +// +// foo.bar +// +// The above will be evaluated as "The table bar inside the table +// foo". +// +// An ErrNoTable is returned if the table does not exist. +func (v Values) Table(name string) (Values, error) { + names := strings.Split(name, ".") + table := v + var err error + + for _, n := range names { + table, err = tableLookup(table, n) + if err != nil { + return table, err + } + } + return table, err +} + +func tableLookup(v Values, simple string) (Values, error) { + v2, ok := v[simple] + if !ok { + return v, ErrNoTable + } + vv, ok := v2.(map[string]interface{}) + if !ok { + return vv, ErrNoTable + } + return vv, nil +} + +// ReadValues will parse TOML byte data into a Values. +func ReadValues(data []byte) (Values, error) { + out := map[string]interface{}{} + err := toml.Unmarshal(data, out) + return out, err +} + +// ReadValuesFile will parse a TOML file into a Values. +func ReadValuesFile(filename string) (Values, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + return map[string]interface{}{}, err + } + return ReadValues(data) +} diff --git a/pkg/chart/values_test.go b/pkg/chart/values_test.go new file mode 100644 index 000000000..ecb6b3df9 --- /dev/null +++ b/pkg/chart/values_test.go @@ -0,0 +1,134 @@ +package chart + +import ( + "bytes" + "fmt" + "testing" + "text/template" +) + +func TestReadValues(t *testing.T) { + doc := `# Test TOML parse +poet = "Coleridge" +title = "Rime of the Ancient Mariner" +stanza = ["at", "length", "did", "cross", "an", "Albatross"] + +[mariner] +with = "crossbow" +shot = "ALBATROSS" + +[water.water] +where = "everywhere" +nor = "any drop to drink" +` + + data, err := ReadValues([]byte(doc)) + if err != nil { + t.Fatalf("Error parsing bytes: %s", err) + } + matchValues(t, data) +} + +func TestReadValuesFile(t *testing.T) { + data, err := ReadValuesFile("./testdata/coleridge.toml") + if err != nil { + t.Fatalf("Error reading TOML file: %s", err) + } + matchValues(t, data) +} + +func ExampleValues() { + doc := `title="Moby Dick" +[chapter.one] +title = "Loomings" + +[chapter.two] +title = "The Carpet-Bag" + +[chapter.three] +title = "The Spouter Inn" +` + d, err := ReadValues([]byte(doc)) + if err != nil { + panic(err) + } + ch1, err := d.Table("chapter.one") + if err != nil { + panic("could not find chapter one") + } + fmt.Print(ch1["title"]) + // Output: + // Loomings +} + +func TestTable(t *testing.T) { + doc := `title="Moby Dick" +[chapter.one] +title = "Loomings" + +[chapter.two] +title = "The Carpet-Bag" + +[chapter.three] +title = "The Spouter Inn" +` + d, err := ReadValues([]byte(doc)) + if err != nil { + t.Fatalf("Failed to parse the White Whale: %s", err) + } + + if _, err := d.Table("title"); err == nil { + t.Fatalf("Title is not a table.") + } + + if _, err := d.Table("chapter"); err != nil { + t.Fatalf("Failed to get the chapter table: %s\n%v", err, d) + } + + if v, err := d.Table("chapter.one"); err != nil { + t.Errorf("Failed to get chapter.one: %s", err) + } else if v["title"] != "Loomings" { + t.Errorf("Unexpected title: %s", v["title"]) + } + + if _, err := d.Table("chapter.three"); err != nil { + t.Errorf("Chapter three is missing: %s\n%v", err, d) + } + + if _, err := d.Table("chapter.OneHundredThirtySix"); err == nil { + t.Errorf("I think you mean 'Epilogue'") + } +} + +func matchValues(t *testing.T, data map[string]interface{}) { + if data["poet"] != "Coleridge" { + t.Errorf("Unexpected poet: %s", data["poet"]) + } + + if o, err := ttpl("{{len .stanza}}", data); err != nil { + t.Errorf("len stanza: %s", err) + } else if o != "6" { + t.Errorf("Expected 6, got %s", o) + } + + if o, err := ttpl("{{.mariner.shot}}", data); err != nil { + t.Errorf(".mariner.shot: %s", err) + } else if o != "ALBATROSS" { + t.Errorf("Expected that mariner shot ALBATROSS") + } + + if o, err := ttpl("{{.water.water.where}}", data); err != nil { + t.Errorf(".water.water.where: %s", err) + } else if o != "everywhere" { + t.Errorf("Expected water water everywhere") + } +} + +func ttpl(tpl string, v map[string]interface{}) (string, error) { + var b bytes.Buffer + tt := template.Must(template.New("t").Parse(tpl)) + if err := tt.Execute(&b, v); err != nil { + return "", err + } + return b.String(), nil +} From a3066bb0c00119a7ea3ffc08fe8861403bc03731 Mon Sep 17 00:00:00 2001 From: Brian Hardock Date: Thu, 14 Apr 2016 11:40:20 -0600 Subject: [PATCH 36/86] feat(hapi-pb): 1. remove preliminary hapi protocol buffer definitions 2. modify hapi Makefile 3. update hapi protobuf model in _proto/hapi/{chart,release,services} 4. generate protobuf src to pkg/proto/hapi/{chart,release,services} --- _proto/Makefile | 37 ++ _proto/aaa.proto | 48 --- _proto/chartfile.proto | 36 -- _proto/get.proto | 20 - _proto/hapi/chart/chart.proto | 24 ++ _proto/hapi/chart/config.proto | 13 + _proto/hapi/chart/metadata.proto | 49 +++ _proto/hapi/chart/template.proto | 23 ++ _proto/hapi/release/info.proto | 20 + _proto/hapi/release/release.proto | 28 ++ _proto/hapi/release/status.proto | 27 ++ _proto/hapi/services/probe.proto | 16 + _proto/hapi/services/tiller.proto | 190 ++++++++++ _proto/helm.proto | 48 --- _proto/install.proto | 20 - _proto/list.proto | 20 - _proto/status.proto | 17 - _proto/uninstall.proto | 17 - pkg/proto/hapi/chart/chart.pb.go | 94 +++++ pkg/proto/hapi/chart/config.pb.go | 40 ++ pkg/proto/hapi/chart/metadata.pb.go | 91 +++++ pkg/proto/hapi/chart/template.pb.go | 67 ++++ pkg/proto/hapi/release/info.pb.go | 89 +++++ pkg/proto/hapi/release/release.pb.go | 78 ++++ pkg/proto/hapi/release/status.pb.go | 86 +++++ pkg/proto/hapi/services/probe.pb.go | 145 +++++++ pkg/proto/hapi/services/tiller.pb.go | 543 +++++++++++++++++++++++++++ 27 files changed, 1660 insertions(+), 226 deletions(-) create mode 100644 _proto/Makefile delete mode 100644 _proto/aaa.proto delete mode 100644 _proto/chartfile.proto delete mode 100644 _proto/get.proto create mode 100644 _proto/hapi/chart/chart.proto create mode 100644 _proto/hapi/chart/config.proto create mode 100644 _proto/hapi/chart/metadata.proto create mode 100644 _proto/hapi/chart/template.proto create mode 100644 _proto/hapi/release/info.proto create mode 100644 _proto/hapi/release/release.proto create mode 100644 _proto/hapi/release/status.proto create mode 100644 _proto/hapi/services/probe.proto create mode 100644 _proto/hapi/services/tiller.proto delete mode 100644 _proto/helm.proto delete mode 100644 _proto/install.proto delete mode 100644 _proto/list.proto delete mode 100644 _proto/status.proto delete mode 100644 _proto/uninstall.proto create mode 100644 pkg/proto/hapi/chart/chart.pb.go create mode 100644 pkg/proto/hapi/chart/config.pb.go create mode 100644 pkg/proto/hapi/chart/metadata.pb.go create mode 100644 pkg/proto/hapi/chart/template.pb.go create mode 100644 pkg/proto/hapi/release/info.pb.go create mode 100644 pkg/proto/hapi/release/release.pb.go create mode 100644 pkg/proto/hapi/release/status.pb.go create mode 100644 pkg/proto/hapi/services/probe.pb.go create mode 100644 pkg/proto/hapi/services/tiller.pb.go diff --git a/_proto/Makefile b/_proto/Makefile new file mode 100644 index 000000000..baf437cc5 --- /dev/null +++ b/_proto/Makefile @@ -0,0 +1,37 @@ +space := $(empty) $(empty) +comma := , +empty := + +import_path = github.com/deis/tiller/pkg/proto/hapi + +dst = ../pkg/proto +target = go +plugins = grpc + +chart_ias = $(subst $(space),$(comma),$(addsuffix =$(import_path)/$(chart_pkg),$(addprefix M,$(chart_pbs)))) +chart_pbs = $(wildcard hapi/chart/*.proto) +chart_pkg = chart + +release_ias = $(subst $(space),$(comma),$(addsuffix =$(import_path)/$(release_pkg),$(addprefix M,$(release_pbs)))) +release_pbs = $(wildcard hapi/release/*.proto) +release_pkg = release + +services_ias = $(subst $(space),$(comma),$(addsuffix =$(import_path)/$(services_pkg),$(addprefix M,$(services_pbs)))) +services_pbs = $(wildcard hapi/services/*.proto) +services_pkg = services + +google_deps = Mgoogle/protobuf/timestamp.proto=github.com/golang/protobuf/ptypes/timestamp,Mgoogle/protobuf/any.proto=github.com/golang/protobuf/ptypes/any + +all: chart release services + +chart: + protoc --$(target)_out=plugins=$(plugins),$(google_deps),$(chart_ias):$(dst) $(chart_pbs) + +release: + protoc --$(target)_out=plugins=$(plugins),$(google_deps),$(chart_ias):$(dst) $(release_pbs) + +services: + protoc --$(target)_out=plugins=$(plugins),$(google_deps),$(chart_ias),$(release_ias):$(dst) $(services_pbs) + +clean: + @rm -rf $(dst)/hapi 2>/dev/null diff --git a/_proto/aaa.proto b/_proto/aaa.proto deleted file mode 100644 index 4db9d8f94..000000000 --- a/_proto/aaa.proto +++ /dev/null @@ -1,48 +0,0 @@ -syntax = "proto3"; -package hapi; - -message Chart { - // Option 1: Chart is raw []byte data - // Option 2: List of files as []byte data, with special treatment for Chart.yaml - // Option 3: Completely parsed out (probably very inflexible, ultimately) - - // Option 2: - Chartfile chartfile = 1; - Values defaultValues = 2; - map templates = 3; // filename => []bytes - repeated Chart charts = 4; -} - -// Values represents a set of values that will be passed into the template. -message Values { - // Option 1: "values" is unparsed TOML data (raw []byte) - // Option 2: Model TOML in Protobuf (not _too_ bad) - // Option 3: Force everything into a map[string]string model -} - -message Release { - string name = 1; -} - -message Status { - StatusCode code = 1; - string msg = 2; -} - -message Error { - ErrorCode errror_code = 1; - string error_msg = 2; -} - -enum ErrorCode { - ERROR_CODE_UNSET = 0; - BAD_REQUEST = 1; -} - -enum StatusCode { - STATUS_CODE_UNSET = 0; - UNKNOWN = 1; - DEPLOYED = 2; - DELETED = 3; - SUPERSEDED = 4; -} diff --git a/_proto/chartfile.proto b/_proto/chartfile.proto deleted file mode 100644 index 07921f624..000000000 --- a/_proto/chartfile.proto +++ /dev/null @@ -1,36 +0,0 @@ -syntax = "proto3"; -package hapi; - -// Maintainer is a chart maintainer -message Maintainer { - // Name is a user name or organization name - string name = 1; - // Email is an optional email address to contact the named maintainer - string email = 2; -} - -// Chartfile represents the structure of a Chart.yaml file. -// -// Spec: https://github.com/kubernetes/helm/blob/master/docs/design/chart_format.md#the-chart-file -// -// Fields: -// - name: The name of the chart -// - verison: The SemVer 2 conformant version of the chart -// - description: A once-sentence description of the chart -// - keywords: A list of string keywords -message Chartfile { - // Name is the name of the chart - string name = 1; - // Version is the SemVer 2 version of the chart - string version = 2; - // Description is a sentence describing the chart - string description = 3; - // Keywords is a list of keywords describing the chart - repeated string keywords = 4; - // Maintainers is the set of maintainers of this chart - repeated Maintainer maintainers = 5; - // Source is the URL to the source code of this chart - string source = 6; - // Home is the URL to the chart's home page - string home = 7; -} diff --git a/_proto/get.proto b/_proto/get.proto deleted file mode 100644 index e66fcc03e..000000000 --- a/_proto/get.proto +++ /dev/null @@ -1,20 +0,0 @@ -syntax = "proto3"; -package hapi; - -import "aaa.proto"; - -message GetRequest { - string name = 1; -} - -message GetResponseError { - oneof response { - Error err = 1; - GetResponse get_response = 2; - } -} - -message GetResponse { - Chart chart = 1; - Values values = 2; -} diff --git a/_proto/hapi/chart/chart.proto b/_proto/hapi/chart/chart.proto new file mode 100644 index 000000000..67bb79ee4 --- /dev/null +++ b/_proto/hapi/chart/chart.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package hapi.chart; + +import "hapi/chart/metadata.proto"; +import "hapi/chart/template.proto"; + +option go_package = "chart"; + +// +// Chart: +// A chart is a helm package that contains metadata, a default config, zero or more +// optionally parameterizable templates, and zero or more charts (dependencies). +// +message Chart { + // TODO + hapi.chart.Metadata metadata = 1; + + // TODO + hapi.chart.Templates templates = 2; + + // TODO + repeated Chart dependencies = 3; +} diff --git a/_proto/hapi/chart/config.proto b/_proto/hapi/chart/config.proto new file mode 100644 index 000000000..e5c51e9a7 --- /dev/null +++ b/_proto/hapi/chart/config.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package hapi.chart; + +option go_package = "chart"; + +// +// Config: +// +// A config supplies values to the parametrizable templates of a chart. +// +message Config { +} diff --git a/_proto/hapi/chart/metadata.proto b/_proto/hapi/chart/metadata.proto new file mode 100644 index 000000000..3068aa452 --- /dev/null +++ b/_proto/hapi/chart/metadata.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package hapi.chart; + +option go_package = "chart"; + +// +// Maintainer: +// +// A descriptor of the Chart maintainer(s). +// +message Maintainer { + // Name is a user name or organization name + string name = 1; + + // Email is an optional email address to contact the named maintainer + string email = 2; +} + +// +// Metadata: +// +// Metadata for a Chart file. This models the structure +// of a Chart.yaml file. +// +// Spec: https://github.com/kubernetes/helm/blob/master/docs/design/chart_format.md#the-chart-file +// +message Metadata { + // The name of the chart + string name = 1; + + // The URL to a relecant project page, git repo, or contact person + string home = 2; + + // Source is the URL to the source code of this chart + string source = 3; + + // A SemVer 2 conformant version string of the chart + string version = 4; + + // A one-sentence description of the chart + string description = 5; + + // A list of string keywords + repeated string keywords = 6; + + // A list of name and URL/email address combinations for the maintainer(s) + repeated Maintainer maintainers = 7; +} diff --git a/_proto/hapi/chart/template.proto b/_proto/hapi/chart/template.proto new file mode 100644 index 000000000..4f9968966 --- /dev/null +++ b/_proto/hapi/chart/template.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package hapi.chart; + +option go_package = "chart"; + +// +// Template: +// +// TODO +// +message Templates { + // TODO + repeated Template templates = 1; +} + +message Template { + // TODO + string template_name = 1; + + // TODO + bytes template_data = 2; +} diff --git a/_proto/hapi/release/info.proto b/_proto/hapi/release/info.proto new file mode 100644 index 000000000..09e022ecd --- /dev/null +++ b/_proto/hapi/release/info.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package hapi.release; + +import "google/protobuf/timestamp.proto"; +import "hapi/release/status.proto"; + +option go_package = "release"; + +// +// Info: +// +// +message Info { + Status status = 1; + + google.protobuf.Timestamp first_deployed = 2; + + google.protobuf.Timestamp last_deployed = 3; +} diff --git a/_proto/hapi/release/release.proto b/_proto/hapi/release/release.proto new file mode 100644 index 000000000..0caa3cf28 --- /dev/null +++ b/_proto/hapi/release/release.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package hapi.release; + +import "hapi/release/info.proto"; +import "hapi/chart/config.proto"; +import "hapi/chart/chart.proto"; + +option go_package = "release"; + +// +// Release: +// +// TODO +// +message Release { + // TODO + string name = 1; + + // TODO + hapi.release.Info info = 2; + + // TODO + hapi.chart.Chart chart = 3; + + // TODO + hapi.chart.Config config = 4; +} diff --git a/_proto/hapi/release/status.proto b/_proto/hapi/release/status.proto new file mode 100644 index 000000000..9ec021005 --- /dev/null +++ b/_proto/hapi/release/status.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package hapi.release; + +import "google/protobuf/any.proto"; + +option go_package = "release"; + +// +// Status: +// +// +message Status { + enum Code { + UNKNOWN = 0; + + DEPLOYED = 1; + + DELETED = 2; + + SUPERSEDED = 3; + } + + Code code = 1; + + google.protobuf.Any details = 2; +} diff --git a/_proto/hapi/services/probe.proto b/_proto/hapi/services/probe.proto new file mode 100644 index 000000000..062b37bdb --- /dev/null +++ b/_proto/hapi/services/probe.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package hapi.services.probe; + +option go_package = "services"; + +service ProbeService { + rpc Ready(ReadyRequest) returns (ReadyResponse) { + } +} + +message ReadyRequest { +} + +message ReadyResponse { +} diff --git a/_proto/hapi/services/tiller.proto b/_proto/hapi/services/tiller.proto new file mode 100644 index 000000000..6704477f8 --- /dev/null +++ b/_proto/hapi/services/tiller.proto @@ -0,0 +1,190 @@ +syntax = "proto3"; + +package hapi.services.tiller; + +import "hapi/release/release.proto"; +import "hapi/release/status.proto"; + +option go_package = "services"; + +// +// ReleaseService: +// +// The service that a helm application uses to mutate, +// query, and manage releases. +// +// Release: A named installation composed of a chart and +// config. At any given time a release has one +// chart and one config. +// +// Config: A config is a TOML file that supplies values +// to the parametrizable templates of a chart. +// +// Chart: A chart is a helm package that contains +// metadata, a default config, zero or more +// optionally parameterizable templates, and +// zero or more charts (dependencies). +// +// +service ReleaseService { + // + // Retrieve release history. TODO: Allow filtering the set of releases by + // release status. By default, ListAllReleases returns the releases who + // current status is "Active". + // + rpc ListReleases(ListReleasesRequest) returns (stream ListReleasesResponse) { + } + + // + // Retrieve status information for the specified release. + // + rpc GetReleaseStatus(GetReleaseStatusRequest) returns (GetReleaseStatusResponse) { + } + + // + // Retrieve the release content (chart + value) for the specifed release. + // + rpc GetReleaseContent(GetReleaseContentRequest) returns (GetReleaseContentResponse) { + } + + // + // Update release content. + // + rpc UpdateRelease(UpdateReleaseRequest) returns (UpdateReleaseResponse) { + } + + // + // Request release install. + // + rpc InstallRelease(InstallReleaseRequest) returns (InstallReleaseResponse) { + } + + // + // Request release deletion. + // + rpc UninstallRelease(UninstallReleaseRequest) returns (UninstallReleaseResponse) { + } +} + +// +// ListReleasesRequest: +// +// TODO +// +message ListReleasesRequest { + // The maximum number of releases to be returned + int64 limit = 1; + + // The zero-based offset at which the returned release list begins + int64 offset = 2; +} + +// +// ListReleasesResponse: +// +// TODO +// +message ListReleasesResponse { + // The expected total number of releases to be returned + int64 count = 1; + + // The zero-based offset at which the list is positioned + int64 offset = 2; + + // The total number of queryable releases + int64 total = 3; + + // The resulting releases + repeated hapi.release.Release releases = 4; +} + +// +// GetReleaseStatusRequest: +// +// TODO +// +message GetReleaseStatusRequest { + // The name of the release + string release_name = 1; +} + +// +// GetReleaseStatusResponse: +// +// TODO +// +message GetReleaseStatusResponse { + // The name of the release + string release_name = 1; + + // The release status + hapi.release.Status release_status = 2; +} + +// +// GetReleaseContentRequest: +// +// TODO +// +message GetReleaseContentRequest { + // The name of the release + string release_name = 1; +} + +// +// GetReleaseContentResponse: +// +// TODO +// +message GetReleaseContentResponse { + // The release content + hapi.release.Release release = 1; +} + +// +// UpdateReleaseRequest: +// +// TODO +// +message UpdateReleaseRequest { +} + +// +// UpdateReleaseResponse: +// +// TODO +// +message UpdateReleaseResponse { +} + +// +// InstallReleaseRequest: +// +// TODO +// +message InstallReleaseRequest { +} + +// +// InstallReleaseResponse: +// +// TODO +// +message InstallReleaseResponse { +} + +// +// UninstallReleaseRequest: +// +// TODO +// +message UninstallReleaseRequest { +} + +// +// UninstallReleaseResponse: +// +// TODO +// +message UninstallReleaseResponse { +} diff --git a/_proto/helm.proto b/_proto/helm.proto deleted file mode 100644 index 7abfe35e3..000000000 --- a/_proto/helm.proto +++ /dev/null @@ -1,48 +0,0 @@ -syntax = "proto3"; - -/* -// No server -helm init -helm create -helm fetch // Fetch chart from repo -helm search -helm package - -// Server - -// Releases -helm install CHART -helm list -helm uninstall RELEASE -helm status RELEASE -helm get RELEASE -helm update RELEASE -*/ - -// hapi: The Helm API -package hapi; - -service ReleaseService { - rpc Install (InstallRequest) returns (InstallResponseError) {} - rpc List (ListRequest) returns (ListResponseError) {} - rpc Uninstall (UninstallRequest) returns (UninstallResponseError) {} - rpc Status (StatusRequest) returns (StatusResponseError) {} - rpc Get (GetRequest) returns (GetResponseError) {} - // rpc Update (UpdateRequest) returns (UpdateResponseError) {} -} - -// Probe is used to check liveness and readiness. -service Probe { - // Run a readiness test. - rpc Ready (PingRequest) returns (PingResponse) {} -} - -// The readiness test request. -message PingRequest { - string name = 1; -} - -// The readiness test response. -message PingResponse { - string status = 1; -} diff --git a/_proto/install.proto b/_proto/install.proto deleted file mode 100644 index 1d6459671..000000000 --- a/_proto/install.proto +++ /dev/null @@ -1,20 +0,0 @@ -syntax = "proto3"; -package hapi; - -message InstallRequest{ - string name = 1 - Chart chart = 2 - Values values = 3 -} - -message InstallResponseError { - oneof response { - Error = 1 - InstallResponse = 2 - } -} - -message InstallResponse{ - string name = 1 - Status status = 2 -} diff --git a/_proto/list.proto b/_proto/list.proto deleted file mode 100644 index 6dc0b9f5d..000000000 --- a/_proto/list.proto +++ /dev/null @@ -1,20 +0,0 @@ -syntax = "proto3"; -package hapi; - -message ListRequest { - int64 limit = 1; - int64 offset = 2; -} - -message ListResponseError { - oneof response { - Error = 1 - ListResponse = 2 - } -} -message ListResponse { - repeated Release releases = 1; - int64 count = 2; - int64 offset = 3; - int64 total = 4; -} diff --git a/_proto/status.proto b/_proto/status.proto deleted file mode 100644 index 87ae4d5f9..000000000 --- a/_proto/status.proto +++ /dev/null @@ -1,17 +0,0 @@ -syntax = "proto3"; -package hapi; - -message StatusRequest { - string name = 1 -} - -message StatusResponseError { - oneof response { - Error = 1 - StatusResponse = 2 - } -} -message StatusResponse { - Release release = 1; - Status status = 2 -} diff --git a/_proto/uninstall.proto b/_proto/uninstall.proto deleted file mode 100644 index 60a348f3d..000000000 --- a/_proto/uninstall.proto +++ /dev/null @@ -1,17 +0,0 @@ -syntax = "proto3"; -package hapi; - -message UninstallRequest{ - string name = 1 -} - -message UninstallResponseError { - oneof response { - Error = 1 - UninstallResponse = 2 - } -} - -message UninstallResponse{ - Status status = 1 -} diff --git a/pkg/proto/hapi/chart/chart.pb.go b/pkg/proto/hapi/chart/chart.pb.go new file mode 100644 index 000000000..36570e173 --- /dev/null +++ b/pkg/proto/hapi/chart/chart.pb.go @@ -0,0 +1,94 @@ +// Code generated by protoc-gen-go. +// source: hapi/chart/chart.proto +// DO NOT EDIT! + +/* +Package chart is a generated protocol buffer package. + +It is generated from these files: + hapi/chart/chart.proto + hapi/chart/config.proto + hapi/chart/metadata.proto + hapi/chart/template.proto + +It has these top-level messages: + Chart + Config + Maintainer + Metadata + Templates + Template +*/ +package chart + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +// +// Chart: +// A chart is a helm package that contains metadata, a default config, zero or more +// optionally parameterizable templates, and zero or more charts (dependencies). +// +type Chart struct { + // TODO + Metadata *Metadata `protobuf:"bytes,1,opt,name=metadata" json:"metadata,omitempty"` + // TODO + Templates *Templates `protobuf:"bytes,2,opt,name=templates" json:"templates,omitempty"` + // TODO + Dependencies []*Chart `protobuf:"bytes,3,rep,name=dependencies" json:"dependencies,omitempty"` +} + +func (m *Chart) Reset() { *m = Chart{} } +func (m *Chart) String() string { return proto.CompactTextString(m) } +func (*Chart) ProtoMessage() {} +func (*Chart) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *Chart) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +func (m *Chart) GetTemplates() *Templates { + if m != nil { + return m.Templates + } + return nil +} + +func (m *Chart) GetDependencies() []*Chart { + if m != nil { + return m.Dependencies + } + return nil +} + +func init() { + proto.RegisterType((*Chart)(nil), "hapi.chart.Chart") +} + +var fileDescriptor0 = []byte{ + // 169 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcb, 0x48, 0x2c, 0xc8, + 0xd4, 0x4f, 0xce, 0x48, 0x2c, 0x2a, 0x81, 0x90, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x5c, + 0x20, 0x71, 0x3d, 0xb0, 0x88, 0x94, 0x24, 0x92, 0x9a, 0xdc, 0xd4, 0x92, 0xc4, 0x94, 0xc4, 0x92, + 0x44, 0x88, 0x32, 0x14, 0xa9, 0x92, 0xd4, 0xdc, 0x82, 0x9c, 0xc4, 0x92, 0x54, 0x88, 0x94, 0xd2, + 0x52, 0x46, 0x2e, 0x56, 0x67, 0x90, 0x84, 0x90, 0x01, 0x17, 0x07, 0x4c, 0x9b, 0x04, 0xa3, 0x02, + 0xa3, 0x06, 0xb7, 0x91, 0x88, 0x1e, 0xc2, 0x78, 0x3d, 0x5f, 0xa8, 0x5c, 0x10, 0x5c, 0x95, 0x90, + 0x31, 0x17, 0x27, 0xcc, 0xb4, 0x62, 0x09, 0x26, 0xb0, 0x16, 0x51, 0x64, 0x2d, 0x21, 0x30, 0xc9, + 0x20, 0x84, 0x3a, 0x21, 0x53, 0x2e, 0x9e, 0x94, 0xd4, 0x82, 0xd4, 0xbc, 0x94, 0xd4, 0xbc, 0xe4, + 0x4c, 0xa0, 0x3e, 0x66, 0x05, 0x66, 0xa0, 0x3e, 0x41, 0x64, 0x7d, 0x60, 0xf7, 0x04, 0xa1, 0x28, + 0x73, 0x62, 0x8f, 0x62, 0x05, 0x4b, 0x26, 0xb1, 0x81, 0xdd, 0x6d, 0x0c, 0x08, 0x00, 0x00, 0xff, + 0xff, 0xd3, 0xb1, 0x38, 0xe5, 0x13, 0x01, 0x00, 0x00, +} diff --git a/pkg/proto/hapi/chart/config.pb.go b/pkg/proto/hapi/chart/config.pb.go new file mode 100644 index 000000000..191a7e304 --- /dev/null +++ b/pkg/proto/hapi/chart/config.pb.go @@ -0,0 +1,40 @@ +// Code generated by protoc-gen-go. +// source: hapi/chart/config.proto +// DO NOT EDIT! + +package chart + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// +// Config: +// +// A config supplies values to the parametrizable templates of a chart. +// +type Config struct { +} + +func (m *Config) Reset() { *m = Config{} } +func (m *Config) String() string { return proto.CompactTextString(m) } +func (*Config) ProtoMessage() {} +func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} } + +func init() { + proto.RegisterType((*Config)(nil), "hapi.chart.Config") +} + +var fileDescriptor1 = []byte{ + // 74 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcf, 0x48, 0x2c, 0xc8, + 0xd4, 0x4f, 0xce, 0x48, 0x2c, 0x2a, 0xd1, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, + 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x02, 0x49, 0xe8, 0x81, 0x25, 0x94, 0x38, 0xb8, 0xd8, 0x9c, 0xc1, + 0x72, 0x4e, 0xec, 0x51, 0xac, 0x60, 0xa1, 0x24, 0x36, 0xb0, 0x2a, 0x63, 0x40, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x98, 0x3b, 0x34, 0xb8, 0x40, 0x00, 0x00, 0x00, +} diff --git a/pkg/proto/hapi/chart/metadata.pb.go b/pkg/proto/hapi/chart/metadata.pb.go new file mode 100644 index 000000000..56ee4e021 --- /dev/null +++ b/pkg/proto/hapi/chart/metadata.pb.go @@ -0,0 +1,91 @@ +// Code generated by protoc-gen-go. +// source: hapi/chart/metadata.proto +// DO NOT EDIT! + +package chart + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// +// Maintainer: +// +// A descriptor of the Chart maintainer(s). +// +type Maintainer struct { + // Name is a user name or organization name + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Email is an optional email address to contact the named maintainer + Email string `protobuf:"bytes,2,opt,name=email" json:"email,omitempty"` +} + +func (m *Maintainer) Reset() { *m = Maintainer{} } +func (m *Maintainer) String() string { return proto.CompactTextString(m) } +func (*Maintainer) ProtoMessage() {} +func (*Maintainer) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} } + +// +// Metadata: +// +// Metadata for a Chart file. This models the structure +// of a Chart.yaml file. +// +// Spec: https://github.com/kubernetes/helm/blob/master/docs/design/chart_format.md#the-chart-file +// +type Metadata struct { + // The name of the chart + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // The URL to a relecant project page, git repo, or contact person + Home string `protobuf:"bytes,2,opt,name=home" json:"home,omitempty"` + // Source is the URL to the source code of this chart + Source string `protobuf:"bytes,3,opt,name=source" json:"source,omitempty"` + // A SemVer 2 conformant version string of the chart + Version string `protobuf:"bytes,4,opt,name=version" json:"version,omitempty"` + // A one-sentence description of the chart + Description string `protobuf:"bytes,5,opt,name=description" json:"description,omitempty"` + // A list of string keywords + Keywords []string `protobuf:"bytes,6,rep,name=keywords" json:"keywords,omitempty"` + // A list of name and URL/email address combinations for the maintainer(s) + Maintainers []*Maintainer `protobuf:"bytes,7,rep,name=maintainers" json:"maintainers,omitempty"` +} + +func (m *Metadata) Reset() { *m = Metadata{} } +func (m *Metadata) String() string { return proto.CompactTextString(m) } +func (*Metadata) ProtoMessage() {} +func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{1} } + +func (m *Metadata) GetMaintainers() []*Maintainer { + if m != nil { + return m.Maintainers + } + return nil +} + +func init() { + proto.RegisterType((*Maintainer)(nil), "hapi.chart.Maintainer") + proto.RegisterType((*Metadata)(nil), "hapi.chart.Metadata") +} + +var fileDescriptor2 = []byte{ + // 224 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x90, 0xb1, 0x4e, 0xc3, 0x30, + 0x10, 0x86, 0x15, 0xda, 0x24, 0xe5, 0xb2, 0x9d, 0x50, 0x65, 0x98, 0xa2, 0x4e, 0x4c, 0xae, 0x04, + 0x12, 0x62, 0x66, 0xef, 0xd2, 0x91, 0xed, 0x48, 0x4e, 0x8a, 0x05, 0x8e, 0x23, 0xdb, 0x80, 0x78, + 0x57, 0x1e, 0x06, 0xf7, 0x1a, 0x92, 0x0c, 0x1d, 0x22, 0xdd, 0xff, 0x7d, 0x77, 0x91, 0x7e, 0xc3, + 0x6d, 0x47, 0x83, 0xd9, 0x37, 0x1d, 0xf9, 0xb8, 0xb7, 0x1c, 0xa9, 0xa5, 0x48, 0x7a, 0xf0, 0x2e, + 0x3a, 0x84, 0x93, 0xd2, 0xa2, 0x76, 0x4f, 0x00, 0x07, 0x32, 0x7d, 0x4c, 0x1f, 0x7b, 0x44, 0x58, + 0xf7, 0x64, 0x59, 0x65, 0x75, 0x76, 0x7f, 0x7d, 0x94, 0x19, 0x6f, 0x20, 0x67, 0x4b, 0xe6, 0x43, + 0x5d, 0x09, 0x3c, 0x87, 0xdd, 0x6f, 0x06, 0x9b, 0xc3, 0xf8, 0xdb, 0x8b, 0x67, 0x89, 0x75, 0x2e, + 0xb1, 0xf3, 0x95, 0xcc, 0xb8, 0x85, 0x22, 0xb8, 0x4f, 0xdf, 0xb0, 0x5a, 0x09, 0x1d, 0x13, 0x2a, + 0x28, 0xbf, 0xd8, 0x07, 0xe3, 0x7a, 0xb5, 0x16, 0xf1, 0x1f, 0xb1, 0x86, 0xaa, 0xe5, 0xd0, 0x78, + 0x33, 0xc4, 0x93, 0xcd, 0xc5, 0x2e, 0x11, 0xde, 0xc1, 0xe6, 0x9d, 0x7f, 0xbe, 0x9d, 0x6f, 0x83, + 0x2a, 0xea, 0x55, 0xd2, 0x53, 0xc6, 0x67, 0xa8, 0xec, 0x54, 0x2e, 0xa8, 0x32, 0xe9, 0xea, 0x61, + 0xab, 0xe7, 0xfa, 0x7a, 0xee, 0x7e, 0x5c, 0xae, 0xbe, 0x94, 0xaf, 0xb9, 0x2c, 0xbc, 0x15, 0xf2, + 0x64, 0x8f, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x23, 0x79, 0xfc, 0xf8, 0x4f, 0x01, 0x00, 0x00, +} diff --git a/pkg/proto/hapi/chart/template.pb.go b/pkg/proto/hapi/chart/template.pb.go new file mode 100644 index 000000000..f5b6b3704 --- /dev/null +++ b/pkg/proto/hapi/chart/template.pb.go @@ -0,0 +1,67 @@ +// Code generated by protoc-gen-go. +// source: hapi/chart/template.proto +// DO NOT EDIT! + +package chart + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// +// Template: +// +// TODO +// +type Templates struct { + // TODO + Templates []*Template `protobuf:"bytes,1,rep,name=templates" json:"templates,omitempty"` +} + +func (m *Templates) Reset() { *m = Templates{} } +func (m *Templates) String() string { return proto.CompactTextString(m) } +func (*Templates) ProtoMessage() {} +func (*Templates) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{0} } + +func (m *Templates) GetTemplates() []*Template { + if m != nil { + return m.Templates + } + return nil +} + +type Template struct { + // TODO + TemplateName string `protobuf:"bytes,1,opt,name=template_name,json=templateName" json:"template_name,omitempty"` + // TODO + TemplateData []byte `protobuf:"bytes,2,opt,name=template_data,json=templateData,proto3" json:"template_data,omitempty"` +} + +func (m *Template) Reset() { *m = Template{} } +func (m *Template) String() string { return proto.CompactTextString(m) } +func (*Template) ProtoMessage() {} +func (*Template) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{1} } + +func init() { + proto.RegisterType((*Templates)(nil), "hapi.chart.Templates") + proto.RegisterType((*Template)(nil), "hapi.chart.Template") +} + +var fileDescriptor3 = []byte{ + // 146 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x92, 0xcc, 0x48, 0x2c, 0xc8, + 0xd4, 0x4f, 0xce, 0x48, 0x2c, 0x2a, 0xd1, 0x2f, 0x49, 0xcd, 0x2d, 0xc8, 0x49, 0x2c, 0x49, 0xd5, + 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x02, 0x49, 0xe9, 0x81, 0xa5, 0x94, 0xec, 0xb9, 0x38, + 0x43, 0xa0, 0xb2, 0xc5, 0x42, 0x46, 0x5c, 0x9c, 0x30, 0xa5, 0xc5, 0x12, 0x8c, 0x0a, 0xcc, 0x1a, + 0xdc, 0x46, 0x22, 0x7a, 0x08, 0xc5, 0x7a, 0x30, 0x95, 0x41, 0x08, 0x65, 0x4a, 0x21, 0x5c, 0x1c, + 0x30, 0x61, 0x21, 0x65, 0x2e, 0x5e, 0x98, 0x44, 0x7c, 0x5e, 0x62, 0x6e, 0x2a, 0xd0, 0x0c, 0x46, + 0x0d, 0xce, 0x20, 0x1e, 0x98, 0xa0, 0x1f, 0x50, 0x0c, 0x45, 0x51, 0x4a, 0x62, 0x49, 0xa2, 0x04, + 0x13, 0x50, 0x11, 0x0f, 0x42, 0x91, 0x0b, 0x50, 0xcc, 0x89, 0x3d, 0x8a, 0x15, 0x6c, 0x65, 0x12, + 0x1b, 0xd8, 0xc9, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x48, 0xda, 0x77, 0x0e, 0xcf, 0x00, + 0x00, 0x00, +} diff --git a/pkg/proto/hapi/release/info.pb.go b/pkg/proto/hapi/release/info.pb.go new file mode 100644 index 000000000..72c3225c5 --- /dev/null +++ b/pkg/proto/hapi/release/info.pb.go @@ -0,0 +1,89 @@ +// Code generated by protoc-gen-go. +// source: hapi/release/info.proto +// DO NOT EDIT! + +/* +Package release is a generated protocol buffer package. + +It is generated from these files: + hapi/release/info.proto + hapi/release/release.proto + hapi/release/status.proto + +It has these top-level messages: + Info + Release + Status +*/ +package release + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/timestamp" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +// +// Info: +// +// +type Info struct { + Status *Status `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` + FirstDeployed *google_protobuf.Timestamp `protobuf:"bytes,2,opt,name=first_deployed,json=firstDeployed" json:"first_deployed,omitempty"` + LastDeployed *google_protobuf.Timestamp `protobuf:"bytes,3,opt,name=last_deployed,json=lastDeployed" json:"last_deployed,omitempty"` +} + +func (m *Info) Reset() { *m = Info{} } +func (m *Info) String() string { return proto.CompactTextString(m) } +func (*Info) ProtoMessage() {} +func (*Info) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *Info) GetStatus() *Status { + if m != nil { + return m.Status + } + return nil +} + +func (m *Info) GetFirstDeployed() *google_protobuf.Timestamp { + if m != nil { + return m.FirstDeployed + } + return nil +} + +func (m *Info) GetLastDeployed() *google_protobuf.Timestamp { + if m != nil { + return m.LastDeployed + } + return nil +} + +func init() { + proto.RegisterType((*Info)(nil), "hapi.release.Info") +} + +var fileDescriptor0 = []byte{ + // 194 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcf, 0x48, 0x2c, 0xc8, + 0xd4, 0x2f, 0x4a, 0xcd, 0x49, 0x4d, 0x2c, 0x4e, 0xd5, 0xcf, 0xcc, 0x4b, 0xcb, 0xd7, 0x2b, 0x28, + 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x01, 0x49, 0xe8, 0x41, 0x25, 0xa4, 0xe4, 0xd3, 0xf3, 0xf3, 0xd3, + 0x73, 0x52, 0xf5, 0xc1, 0x72, 0x49, 0xa5, 0x69, 0xfa, 0x25, 0x99, 0xb9, 0xa9, 0xc5, 0x25, 0x89, + 0xb9, 0x05, 0x10, 0xe5, 0x52, 0x92, 0x28, 0xe6, 0x00, 0x65, 0x4a, 0x4a, 0x8b, 0x21, 0x52, 0x4a, + 0x3b, 0x18, 0xb9, 0x58, 0x3c, 0x81, 0x06, 0x0b, 0xe9, 0x70, 0xb1, 0x41, 0x24, 0x24, 0x18, 0x15, + 0x18, 0x35, 0xb8, 0x8d, 0x44, 0xf4, 0x90, 0xed, 0xd0, 0x0b, 0x06, 0xcb, 0x05, 0x41, 0xd5, 0x08, + 0x39, 0x72, 0xf1, 0xa5, 0x65, 0x16, 0x15, 0x97, 0xc4, 0xa7, 0xa4, 0x16, 0xe4, 0xe4, 0x57, 0xa6, + 0xa6, 0x48, 0x30, 0x81, 0x75, 0x49, 0xe9, 0x41, 0xdc, 0xa2, 0x07, 0x73, 0x8b, 0x5e, 0x08, 0xcc, + 0x2d, 0x41, 0xbc, 0x60, 0x1d, 0x2e, 0x50, 0x0d, 0x42, 0xf6, 0x5c, 0xbc, 0x39, 0x89, 0xc8, 0x26, + 0x30, 0x13, 0x34, 0x81, 0x07, 0xa4, 0x01, 0x66, 0x80, 0x13, 0x67, 0x14, 0x3b, 0xd4, 0x75, 0x49, + 0x6c, 0x60, 0xc5, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfb, 0xae, 0xa9, 0x99, 0x31, 0x01, + 0x00, 0x00, +} diff --git a/pkg/proto/hapi/release/release.pb.go b/pkg/proto/hapi/release/release.pb.go new file mode 100644 index 000000000..5f14340d9 --- /dev/null +++ b/pkg/proto/hapi/release/release.pb.go @@ -0,0 +1,78 @@ +// Code generated by protoc-gen-go. +// source: hapi/release/release.proto +// DO NOT EDIT! + +package release + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import hapi_chart "github.com/deis/tiller/pkg/proto/hapi/chart" +import hapi_chart3 "github.com/deis/tiller/pkg/proto/hapi/chart" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// +// Release: +// +// TODO +// +type Release struct { + // TODO + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // TODO + Info *Info `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` + // TODO + Chart *hapi_chart3.Chart `protobuf:"bytes,3,opt,name=chart" json:"chart,omitempty"` + // TODO + Config *hapi_chart.Config `protobuf:"bytes,4,opt,name=config" json:"config,omitempty"` +} + +func (m *Release) Reset() { *m = Release{} } +func (m *Release) String() string { return proto.CompactTextString(m) } +func (*Release) ProtoMessage() {} +func (*Release) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} } + +func (m *Release) GetInfo() *Info { + if m != nil { + return m.Info + } + return nil +} + +func (m *Release) GetChart() *hapi_chart3.Chart { + if m != nil { + return m.Chart + } + return nil +} + +func (m *Release) GetConfig() *hapi_chart.Config { + if m != nil { + return m.Config + } + return nil +} + +func init() { + proto.RegisterType((*Release)(nil), "hapi.release.Release") +} + +var fileDescriptor1 = []byte{ + // 182 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x92, 0xca, 0x48, 0x2c, 0xc8, + 0xd4, 0x2f, 0x4a, 0xcd, 0x49, 0x4d, 0x2c, 0x4e, 0x85, 0xd1, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, + 0x42, 0x3c, 0x20, 0x39, 0x3d, 0xa8, 0x98, 0x94, 0x38, 0x8a, 0xca, 0xcc, 0xbc, 0xb4, 0x7c, 0x88, + 0x32, 0xa8, 0x44, 0x72, 0x46, 0x62, 0x51, 0x89, 0x7e, 0x72, 0x7e, 0x5e, 0x5a, 0x66, 0x3a, 0x54, + 0x42, 0x0c, 0x59, 0x02, 0x44, 0x42, 0xc4, 0x95, 0x66, 0x31, 0x72, 0xb1, 0x07, 0x41, 0xcc, 0x11, + 0x12, 0xe2, 0x62, 0xc9, 0x4b, 0xcc, 0x4d, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, + 0x85, 0xd4, 0xb8, 0x58, 0x40, 0xc6, 0x4b, 0x30, 0x01, 0xc5, 0xb8, 0x8d, 0x84, 0xf4, 0x90, 0x9d, + 0xa1, 0xe7, 0x09, 0x94, 0x09, 0x02, 0xcb, 0x0b, 0xa9, 0x73, 0xb1, 0x82, 0x8d, 0x95, 0x60, 0x06, + 0x2b, 0x14, 0x84, 0x28, 0x84, 0xd8, 0xe4, 0x0c, 0x22, 0x83, 0x20, 0xf2, 0x42, 0x5a, 0x5c, 0x6c, + 0x10, 0x87, 0x49, 0xb0, 0x20, 0x1b, 0x09, 0x55, 0x09, 0x96, 0x09, 0x82, 0xaa, 0x70, 0xe2, 0x8c, + 0x62, 0x87, 0x5a, 0x95, 0xc4, 0x06, 0x76, 0xae, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xb4, 0xa3, + 0x61, 0x9d, 0x24, 0x01, 0x00, 0x00, +} diff --git a/pkg/proto/hapi/release/status.pb.go b/pkg/proto/hapi/release/status.pb.go new file mode 100644 index 000000000..178402c8e --- /dev/null +++ b/pkg/proto/hapi/release/status.pb.go @@ -0,0 +1,86 @@ +// Code generated by protoc-gen-go. +// source: hapi/release/status.proto +// DO NOT EDIT! + +package release + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf1 "github.com/golang/protobuf/ptypes/any" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type Status_Code int32 + +const ( + Status_UNKNOWN Status_Code = 0 + Status_DEPLOYED Status_Code = 1 + Status_DELETED Status_Code = 2 + Status_SUPERSEDED Status_Code = 3 +) + +var Status_Code_name = map[int32]string{ + 0: "UNKNOWN", + 1: "DEPLOYED", + 2: "DELETED", + 3: "SUPERSEDED", +} +var Status_Code_value = map[string]int32{ + "UNKNOWN": 0, + "DEPLOYED": 1, + "DELETED": 2, + "SUPERSEDED": 3, +} + +func (x Status_Code) String() string { + return proto.EnumName(Status_Code_name, int32(x)) +} +func (Status_Code) EnumDescriptor() ([]byte, []int) { return fileDescriptor2, []int{0, 0} } + +// +// Status: +// +// +type Status struct { + Code Status_Code `protobuf:"varint,1,opt,name=code,enum=hapi.release.Status_Code" json:"code,omitempty"` + Details *google_protobuf1.Any `protobuf:"bytes,2,opt,name=details" json:"details,omitempty"` +} + +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} } + +func (m *Status) GetDetails() *google_protobuf1.Any { + if m != nil { + return m.Details + } + return nil +} + +func init() { + proto.RegisterType((*Status)(nil), "hapi.release.Status") + proto.RegisterEnum("hapi.release.Status_Code", Status_Code_name, Status_Code_value) +} + +var fileDescriptor2 = []byte{ + // 215 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x92, 0xcc, 0x48, 0x2c, 0xc8, + 0xd4, 0x2f, 0x4a, 0xcd, 0x49, 0x4d, 0x2c, 0x4e, 0xd5, 0x2f, 0x2e, 0x49, 0x2c, 0x29, 0x2d, 0xd6, + 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x01, 0x49, 0xe9, 0x41, 0xa5, 0xa4, 0x24, 0xd3, 0xf3, + 0xf3, 0xd3, 0x73, 0x52, 0xf5, 0xc1, 0x72, 0x49, 0xa5, 0x69, 0xfa, 0x89, 0x79, 0x95, 0x10, 0x85, + 0x4a, 0xcb, 0x19, 0xb9, 0xd8, 0x82, 0xc1, 0x3a, 0x85, 0x74, 0xb9, 0x58, 0x92, 0xf3, 0x53, 0x52, + 0x25, 0x18, 0x15, 0x18, 0x35, 0xf8, 0x8c, 0x24, 0xf5, 0x90, 0x8d, 0xd0, 0x83, 0xa8, 0xd1, 0x73, + 0x06, 0x2a, 0x08, 0x02, 0x2b, 0x13, 0xd2, 0xe3, 0x62, 0x4f, 0x49, 0x2d, 0x49, 0xcc, 0xcc, 0x29, + 0x96, 0x60, 0x02, 0xea, 0xe0, 0x36, 0x12, 0xd1, 0x83, 0x58, 0xa3, 0x07, 0xb3, 0x46, 0xcf, 0x31, + 0xaf, 0x32, 0x08, 0xa6, 0x48, 0xc9, 0x8e, 0x8b, 0x05, 0xa4, 0x5b, 0x88, 0x9b, 0x8b, 0x3d, 0xd4, + 0xcf, 0xdb, 0xcf, 0x3f, 0xdc, 0x4f, 0x80, 0x41, 0x88, 0x87, 0x8b, 0xc3, 0xc5, 0x35, 0xc0, 0xc7, + 0x3f, 0xd2, 0xd5, 0x45, 0x80, 0x11, 0x24, 0xe5, 0xe2, 0xea, 0xe3, 0x1a, 0x02, 0xe4, 0x30, 0x09, + 0xf1, 0x71, 0x71, 0x05, 0x87, 0x06, 0xb8, 0x06, 0x05, 0xbb, 0xba, 0x00, 0xf9, 0xcc, 0x4e, 0x9c, + 0x51, 0xec, 0x50, 0xc7, 0x24, 0xb1, 0x81, 0x6d, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x0d, + 0xcd, 0xe7, 0x6f, 0x01, 0x01, 0x00, 0x00, +} diff --git a/pkg/proto/hapi/services/probe.pb.go b/pkg/proto/hapi/services/probe.pb.go new file mode 100644 index 000000000..60b432820 --- /dev/null +++ b/pkg/proto/hapi/services/probe.pb.go @@ -0,0 +1,145 @@ +// Code generated by protoc-gen-go. +// source: hapi/services/probe.proto +// DO NOT EDIT! + +/* +Package services is a generated protocol buffer package. + +It is generated from these files: + hapi/services/probe.proto + hapi/services/tiller.proto + +It has these top-level messages: + ReadyRequest + ReadyResponse + ListReleasesRequest + ListReleasesResponse + GetReleaseStatusRequest + GetReleaseStatusResponse + GetReleaseContentRequest + GetReleaseContentResponse + UpdateReleaseRequest + UpdateReleaseResponse + InstallReleaseRequest + InstallReleaseResponse + UninstallReleaseRequest + UninstallReleaseResponse +*/ +package services + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +type ReadyRequest struct { +} + +func (m *ReadyRequest) Reset() { *m = ReadyRequest{} } +func (m *ReadyRequest) String() string { return proto.CompactTextString(m) } +func (*ReadyRequest) ProtoMessage() {} +func (*ReadyRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +type ReadyResponse struct { +} + +func (m *ReadyResponse) Reset() { *m = ReadyResponse{} } +func (m *ReadyResponse) String() string { return proto.CompactTextString(m) } +func (*ReadyResponse) ProtoMessage() {} +func (*ReadyResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func init() { + proto.RegisterType((*ReadyRequest)(nil), "hapi.services.probe.ReadyRequest") + proto.RegisterType((*ReadyResponse)(nil), "hapi.services.probe.ReadyResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion1 + +// Client API for ProbeService service + +type ProbeServiceClient interface { + Ready(ctx context.Context, in *ReadyRequest, opts ...grpc.CallOption) (*ReadyResponse, error) +} + +type probeServiceClient struct { + cc *grpc.ClientConn +} + +func NewProbeServiceClient(cc *grpc.ClientConn) ProbeServiceClient { + return &probeServiceClient{cc} +} + +func (c *probeServiceClient) Ready(ctx context.Context, in *ReadyRequest, opts ...grpc.CallOption) (*ReadyResponse, error) { + out := new(ReadyResponse) + err := grpc.Invoke(ctx, "/hapi.services.probe.ProbeService/Ready", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for ProbeService service + +type ProbeServiceServer interface { + Ready(context.Context, *ReadyRequest) (*ReadyResponse, error) +} + +func RegisterProbeServiceServer(s *grpc.Server, srv ProbeServiceServer) { + s.RegisterService(&_ProbeService_serviceDesc, srv) +} + +func _ProbeService_Ready_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(ReadyRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(ProbeServiceServer).Ready(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _ProbeService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "hapi.services.probe.ProbeService", + HandlerType: (*ProbeServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Ready", + Handler: _ProbeService_Ready_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +var fileDescriptor0 = []byte{ + // 131 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x92, 0xcc, 0x48, 0x2c, 0xc8, + 0xd4, 0x2f, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0x2d, 0xd6, 0x2f, 0x28, 0xca, 0x4f, 0x4a, 0xd5, + 0x03, 0x92, 0x25, 0xf9, 0x42, 0xc2, 0x20, 0x29, 0x3d, 0x98, 0x94, 0x1e, 0x58, 0x4a, 0x89, 0x8f, + 0x8b, 0x27, 0x28, 0x35, 0x31, 0xa5, 0x32, 0x28, 0xb5, 0xb0, 0x34, 0xb5, 0xb8, 0x44, 0x89, 0x9f, + 0x8b, 0x17, 0xca, 0x2f, 0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x35, 0x4a, 0xe0, 0xe2, 0x09, 0x00, 0xa9, + 0x0c, 0x86, 0xe8, 0x13, 0x0a, 0xe0, 0x62, 0x05, 0x2b, 0x10, 0x52, 0xd4, 0xc3, 0x62, 0x9e, 0x1e, + 0xb2, 0x61, 0x52, 0x4a, 0xf8, 0x94, 0x40, 0xcc, 0x57, 0x62, 0x70, 0xe2, 0x8a, 0xe2, 0x80, 0xa9, + 0x48, 0x62, 0x03, 0x3b, 0xd5, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x65, 0x03, 0x07, 0x01, 0xc7, + 0x00, 0x00, 0x00, +} diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go new file mode 100644 index 000000000..e13d3ea9d --- /dev/null +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -0,0 +1,543 @@ +// Code generated by protoc-gen-go. +// source: hapi/services/tiller.proto +// DO NOT EDIT! + +package services + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import hapi_release2 "github.com/deis/tiller/pkg/proto/hapi/release" +import hapi_release "github.com/deis/tiller/pkg/proto/hapi/release" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// +// ListReleasesRequest: +// +// TODO +// +type ListReleasesRequest struct { + // The maximum number of releases to be returned + Limit int64 `protobuf:"varint,1,opt,name=limit" json:"limit,omitempty"` + // The zero-based offset at which the returned release list begins + Offset int64 `protobuf:"varint,2,opt,name=offset" json:"offset,omitempty"` +} + +func (m *ListReleasesRequest) Reset() { *m = ListReleasesRequest{} } +func (m *ListReleasesRequest) String() string { return proto.CompactTextString(m) } +func (*ListReleasesRequest) ProtoMessage() {} +func (*ListReleasesRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} } + +// +// ListReleasesResponse: +// +// TODO +// +type ListReleasesResponse struct { + // The expected total number of releases to be returned + Count int64 `protobuf:"varint,1,opt,name=count" json:"count,omitempty"` + // The zero-based offset at which the list is positioned + Offset int64 `protobuf:"varint,2,opt,name=offset" json:"offset,omitempty"` + // The total number of queryable releases + Total int64 `protobuf:"varint,3,opt,name=total" json:"total,omitempty"` + // The resulting releases + Releases []*hapi_release2.Release `protobuf:"bytes,4,rep,name=releases" json:"releases,omitempty"` +} + +func (m *ListReleasesResponse) Reset() { *m = ListReleasesResponse{} } +func (m *ListReleasesResponse) String() string { return proto.CompactTextString(m) } +func (*ListReleasesResponse) ProtoMessage() {} +func (*ListReleasesResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{1} } + +func (m *ListReleasesResponse) GetReleases() []*hapi_release2.Release { + if m != nil { + return m.Releases + } + return nil +} + +// +// GetReleaseStatusRequest: +// +// TODO +// +type GetReleaseStatusRequest struct { + // The name of the release + ReleaseName string `protobuf:"bytes,1,opt,name=release_name,json=releaseName" json:"release_name,omitempty"` +} + +func (m *GetReleaseStatusRequest) Reset() { *m = GetReleaseStatusRequest{} } +func (m *GetReleaseStatusRequest) String() string { return proto.CompactTextString(m) } +func (*GetReleaseStatusRequest) ProtoMessage() {} +func (*GetReleaseStatusRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{2} } + +// +// GetReleaseStatusResponse: +// +// TODO +// +type GetReleaseStatusResponse struct { + // The name of the release + ReleaseName string `protobuf:"bytes,1,opt,name=release_name,json=releaseName" json:"release_name,omitempty"` + // The release status + ReleaseStatus *hapi_release.Status `protobuf:"bytes,2,opt,name=release_status,json=releaseStatus" json:"release_status,omitempty"` +} + +func (m *GetReleaseStatusResponse) Reset() { *m = GetReleaseStatusResponse{} } +func (m *GetReleaseStatusResponse) String() string { return proto.CompactTextString(m) } +func (*GetReleaseStatusResponse) ProtoMessage() {} +func (*GetReleaseStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{3} } + +func (m *GetReleaseStatusResponse) GetReleaseStatus() *hapi_release.Status { + if m != nil { + return m.ReleaseStatus + } + return nil +} + +// +// GetReleaseContentRequest: +// +// TODO +// +type GetReleaseContentRequest struct { + // The name of the release + ReleaseName string `protobuf:"bytes,1,opt,name=release_name,json=releaseName" json:"release_name,omitempty"` +} + +func (m *GetReleaseContentRequest) Reset() { *m = GetReleaseContentRequest{} } +func (m *GetReleaseContentRequest) String() string { return proto.CompactTextString(m) } +func (*GetReleaseContentRequest) ProtoMessage() {} +func (*GetReleaseContentRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{4} } + +// +// GetReleaseContentResponse: +// +// TODO +// +type GetReleaseContentResponse struct { + // The release content + Release *hapi_release2.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` +} + +func (m *GetReleaseContentResponse) Reset() { *m = GetReleaseContentResponse{} } +func (m *GetReleaseContentResponse) String() string { return proto.CompactTextString(m) } +func (*GetReleaseContentResponse) ProtoMessage() {} +func (*GetReleaseContentResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{5} } + +func (m *GetReleaseContentResponse) GetRelease() *hapi_release2.Release { + if m != nil { + return m.Release + } + return nil +} + +// +// UpdateReleaseRequest: +// +// TODO +// +type UpdateReleaseRequest struct { +} + +func (m *UpdateReleaseRequest) Reset() { *m = UpdateReleaseRequest{} } +func (m *UpdateReleaseRequest) String() string { return proto.CompactTextString(m) } +func (*UpdateReleaseRequest) ProtoMessage() {} +func (*UpdateReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{6} } + +// +// UpdateReleaseResponse: +// +// TODO +// +type UpdateReleaseResponse struct { +} + +func (m *UpdateReleaseResponse) Reset() { *m = UpdateReleaseResponse{} } +func (m *UpdateReleaseResponse) String() string { return proto.CompactTextString(m) } +func (*UpdateReleaseResponse) ProtoMessage() {} +func (*UpdateReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{7} } + +// +// InstallReleaseRequest: +// +// TODO +// +type InstallReleaseRequest struct { +} + +func (m *InstallReleaseRequest) Reset() { *m = InstallReleaseRequest{} } +func (m *InstallReleaseRequest) String() string { return proto.CompactTextString(m) } +func (*InstallReleaseRequest) ProtoMessage() {} +func (*InstallReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{8} } + +// +// InstallReleaseResponse: +// +// TODO +// +type InstallReleaseResponse struct { +} + +func (m *InstallReleaseResponse) Reset() { *m = InstallReleaseResponse{} } +func (m *InstallReleaseResponse) String() string { return proto.CompactTextString(m) } +func (*InstallReleaseResponse) ProtoMessage() {} +func (*InstallReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{9} } + +// +// UninstallReleaseRequest: +// +// TODO +// +type UninstallReleaseRequest struct { +} + +func (m *UninstallReleaseRequest) Reset() { *m = UninstallReleaseRequest{} } +func (m *UninstallReleaseRequest) String() string { return proto.CompactTextString(m) } +func (*UninstallReleaseRequest) ProtoMessage() {} +func (*UninstallReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{10} } + +// +// UninstallReleaseResponse: +// +// TODO +// +type UninstallReleaseResponse struct { +} + +func (m *UninstallReleaseResponse) Reset() { *m = UninstallReleaseResponse{} } +func (m *UninstallReleaseResponse) String() string { return proto.CompactTextString(m) } +func (*UninstallReleaseResponse) ProtoMessage() {} +func (*UninstallReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{11} } + +func init() { + proto.RegisterType((*ListReleasesRequest)(nil), "hapi.services.tiller.ListReleasesRequest") + proto.RegisterType((*ListReleasesResponse)(nil), "hapi.services.tiller.ListReleasesResponse") + proto.RegisterType((*GetReleaseStatusRequest)(nil), "hapi.services.tiller.GetReleaseStatusRequest") + proto.RegisterType((*GetReleaseStatusResponse)(nil), "hapi.services.tiller.GetReleaseStatusResponse") + proto.RegisterType((*GetReleaseContentRequest)(nil), "hapi.services.tiller.GetReleaseContentRequest") + proto.RegisterType((*GetReleaseContentResponse)(nil), "hapi.services.tiller.GetReleaseContentResponse") + proto.RegisterType((*UpdateReleaseRequest)(nil), "hapi.services.tiller.UpdateReleaseRequest") + proto.RegisterType((*UpdateReleaseResponse)(nil), "hapi.services.tiller.UpdateReleaseResponse") + proto.RegisterType((*InstallReleaseRequest)(nil), "hapi.services.tiller.InstallReleaseRequest") + proto.RegisterType((*InstallReleaseResponse)(nil), "hapi.services.tiller.InstallReleaseResponse") + proto.RegisterType((*UninstallReleaseRequest)(nil), "hapi.services.tiller.UninstallReleaseRequest") + proto.RegisterType((*UninstallReleaseResponse)(nil), "hapi.services.tiller.UninstallReleaseResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion1 + +// Client API for ReleaseService service + +type ReleaseServiceClient interface { + // + // Retrieve release history. TODO: Allow filtering the set of releases by + // release status. By default, ListAllReleases returns the releases who + // current status is "Active". + // + ListReleases(ctx context.Context, in *ListReleasesRequest, opts ...grpc.CallOption) (ReleaseService_ListReleasesClient, error) + // + // Retrieve status information for the specified release. + // + GetReleaseStatus(ctx context.Context, in *GetReleaseStatusRequest, opts ...grpc.CallOption) (*GetReleaseStatusResponse, error) + // + // Retrieve the release content (chart + value) for the specifed release. + // + GetReleaseContent(ctx context.Context, in *GetReleaseContentRequest, opts ...grpc.CallOption) (*GetReleaseContentResponse, error) + // + // Update release content. + // + UpdateRelease(ctx context.Context, in *UpdateReleaseRequest, opts ...grpc.CallOption) (*UpdateReleaseResponse, error) + // + // Request release install. + // + InstallRelease(ctx context.Context, in *InstallReleaseRequest, opts ...grpc.CallOption) (*InstallReleaseResponse, error) + // + // Request release deletion. + // + UninstallRelease(ctx context.Context, in *UninstallReleaseRequest, opts ...grpc.CallOption) (*UninstallReleaseResponse, error) +} + +type releaseServiceClient struct { + cc *grpc.ClientConn +} + +func NewReleaseServiceClient(cc *grpc.ClientConn) ReleaseServiceClient { + return &releaseServiceClient{cc} +} + +func (c *releaseServiceClient) ListReleases(ctx context.Context, in *ListReleasesRequest, opts ...grpc.CallOption) (ReleaseService_ListReleasesClient, error) { + stream, err := grpc.NewClientStream(ctx, &_ReleaseService_serviceDesc.Streams[0], c.cc, "/hapi.services.tiller.ReleaseService/ListReleases", opts...) + if err != nil { + return nil, err + } + x := &releaseServiceListReleasesClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type ReleaseService_ListReleasesClient interface { + Recv() (*ListReleasesResponse, error) + grpc.ClientStream +} + +type releaseServiceListReleasesClient struct { + grpc.ClientStream +} + +func (x *releaseServiceListReleasesClient) Recv() (*ListReleasesResponse, error) { + m := new(ListReleasesResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *releaseServiceClient) GetReleaseStatus(ctx context.Context, in *GetReleaseStatusRequest, opts ...grpc.CallOption) (*GetReleaseStatusResponse, error) { + out := new(GetReleaseStatusResponse) + err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/GetReleaseStatus", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseServiceClient) GetReleaseContent(ctx context.Context, in *GetReleaseContentRequest, opts ...grpc.CallOption) (*GetReleaseContentResponse, error) { + out := new(GetReleaseContentResponse) + err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/GetReleaseContent", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseServiceClient) UpdateRelease(ctx context.Context, in *UpdateReleaseRequest, opts ...grpc.CallOption) (*UpdateReleaseResponse, error) { + out := new(UpdateReleaseResponse) + err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/UpdateRelease", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseServiceClient) InstallRelease(ctx context.Context, in *InstallReleaseRequest, opts ...grpc.CallOption) (*InstallReleaseResponse, error) { + out := new(InstallReleaseResponse) + err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/InstallRelease", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseServiceClient) UninstallRelease(ctx context.Context, in *UninstallReleaseRequest, opts ...grpc.CallOption) (*UninstallReleaseResponse, error) { + out := new(UninstallReleaseResponse) + err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/UninstallRelease", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for ReleaseService service + +type ReleaseServiceServer interface { + // + // Retrieve release history. TODO: Allow filtering the set of releases by + // release status. By default, ListAllReleases returns the releases who + // current status is "Active". + // + ListReleases(*ListReleasesRequest, ReleaseService_ListReleasesServer) error + // + // Retrieve status information for the specified release. + // + GetReleaseStatus(context.Context, *GetReleaseStatusRequest) (*GetReleaseStatusResponse, error) + // + // Retrieve the release content (chart + value) for the specifed release. + // + GetReleaseContent(context.Context, *GetReleaseContentRequest) (*GetReleaseContentResponse, error) + // + // Update release content. + // + UpdateRelease(context.Context, *UpdateReleaseRequest) (*UpdateReleaseResponse, error) + // + // Request release install. + // + InstallRelease(context.Context, *InstallReleaseRequest) (*InstallReleaseResponse, error) + // + // Request release deletion. + // + UninstallRelease(context.Context, *UninstallReleaseRequest) (*UninstallReleaseResponse, error) +} + +func RegisterReleaseServiceServer(s *grpc.Server, srv ReleaseServiceServer) { + s.RegisterService(&_ReleaseService_serviceDesc, srv) +} + +func _ReleaseService_ListReleases_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(ListReleasesRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(ReleaseServiceServer).ListReleases(m, &releaseServiceListReleasesServer{stream}) +} + +type ReleaseService_ListReleasesServer interface { + Send(*ListReleasesResponse) error + grpc.ServerStream +} + +type releaseServiceListReleasesServer struct { + grpc.ServerStream +} + +func (x *releaseServiceListReleasesServer) Send(m *ListReleasesResponse) error { + return x.ServerStream.SendMsg(m) +} + +func _ReleaseService_GetReleaseStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(GetReleaseStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(ReleaseServiceServer).GetReleaseStatus(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _ReleaseService_GetReleaseContent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(GetReleaseContentRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(ReleaseServiceServer).GetReleaseContent(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _ReleaseService_UpdateRelease_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(UpdateReleaseRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(ReleaseServiceServer).UpdateRelease(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _ReleaseService_InstallRelease_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(InstallReleaseRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(ReleaseServiceServer).InstallRelease(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _ReleaseService_UninstallRelease_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(UninstallReleaseRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(ReleaseServiceServer).UninstallRelease(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _ReleaseService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "hapi.services.tiller.ReleaseService", + HandlerType: (*ReleaseServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetReleaseStatus", + Handler: _ReleaseService_GetReleaseStatus_Handler, + }, + { + MethodName: "GetReleaseContent", + Handler: _ReleaseService_GetReleaseContent_Handler, + }, + { + MethodName: "UpdateRelease", + Handler: _ReleaseService_UpdateRelease_Handler, + }, + { + MethodName: "InstallRelease", + Handler: _ReleaseService_InstallRelease_Handler, + }, + { + MethodName: "UninstallRelease", + Handler: _ReleaseService_UninstallRelease_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "ListReleases", + Handler: _ReleaseService_ListReleases_Handler, + ServerStreams: true, + }, + }, +} + +var fileDescriptor1 = []byte{ + // 469 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x54, 0xcd, 0xd2, 0xd2, 0x30, + 0x14, 0xfd, 0x2a, 0x7e, 0x88, 0x97, 0x9f, 0xd1, 0x58, 0xa0, 0x64, 0x85, 0x59, 0xe1, 0x5f, 0xab, + 0xb8, 0x54, 0x37, 0xb2, 0x70, 0x9c, 0x61, 0x5c, 0xd4, 0x61, 0xe3, 0xc6, 0xa9, 0x18, 0xc6, 0x38, + 0xa5, 0xad, 0x4d, 0x60, 0xe1, 0x3b, 0xf8, 0x3e, 0x3e, 0x9e, 0x34, 0x3f, 0x1d, 0x5a, 0x9a, 0xa1, + 0xae, 0x98, 0xe4, 0x9c, 0x73, 0xef, 0xb9, 0xb9, 0xa7, 0x00, 0xfe, 0x11, 0x65, 0x2c, 0xe0, 0x34, + 0x3f, 0xb2, 0x2d, 0xe5, 0x81, 0x60, 0x71, 0x4c, 0x73, 0x3f, 0xcb, 0x53, 0x91, 0x22, 0xb7, 0xc0, + 0x7c, 0x83, 0xf9, 0x0a, 0xc3, 0x4a, 0x91, 0xd3, 0x98, 0x46, 0x9c, 0x9a, 0x5f, 0xa5, 0xc0, 0xb3, + 0x0a, 0xc6, 0x45, 0x24, 0x0e, 0x5c, 0x41, 0x64, 0x05, 0x8f, 0xd6, 0x8c, 0x8b, 0x50, 0x61, 0x3c, + 0xa4, 0xbf, 0x0e, 0x94, 0x0b, 0xe4, 0xc2, 0x6d, 0xcc, 0xf6, 0x4c, 0x78, 0xce, 0xdc, 0x59, 0x74, + 0x42, 0x75, 0x40, 0x13, 0xe8, 0xa6, 0xbb, 0x1d, 0xa7, 0xc2, 0xbb, 0x23, 0xaf, 0xf5, 0x89, 0xfc, + 0x71, 0xc0, 0xad, 0x56, 0xe1, 0x59, 0x9a, 0x70, 0x5a, 0x94, 0xd9, 0xa6, 0x87, 0xa4, 0x2c, 0x23, + 0x0f, 0xb6, 0x32, 0x05, 0x5b, 0xa4, 0x22, 0x8a, 0xbd, 0x8e, 0x62, 0xcb, 0x03, 0x7a, 0x05, 0x3d, + 0xed, 0x9c, 0x7b, 0x77, 0xe7, 0x9d, 0x45, 0x7f, 0x39, 0xf6, 0xe5, 0x0b, 0x98, 0x19, 0x75, 0xd7, + 0xb0, 0xa4, 0x91, 0xb7, 0x30, 0xfd, 0x40, 0x8d, 0x9b, 0xcf, 0x72, 0x5c, 0x33, 0xd8, 0x63, 0x18, + 0x68, 0xda, 0xd7, 0x24, 0xda, 0x53, 0x69, 0xec, 0x7e, 0xd8, 0xd7, 0x77, 0x9f, 0x4e, 0x57, 0xe4, + 0x37, 0x78, 0x97, 0x6a, 0x3d, 0xd0, 0x75, 0x39, 0x7a, 0x03, 0x23, 0x43, 0x51, 0x2f, 0x2d, 0xa7, + 0xec, 0x2f, 0xdd, 0xaa, 0x6b, 0x5d, 0x78, 0x98, 0x9f, 0xf7, 0x21, 0xef, 0xce, 0x7b, 0xaf, 0xd2, + 0x44, 0xd0, 0x44, 0xfc, 0x87, 0xf5, 0x35, 0xcc, 0x1a, 0xe4, 0xda, 0x7b, 0x00, 0xf7, 0x34, 0x57, + 0x4a, 0xad, 0xef, 0x68, 0x58, 0x64, 0x02, 0xee, 0x26, 0xfb, 0x1e, 0x09, 0x6a, 0x10, 0x65, 0x84, + 0x4c, 0x61, 0x5c, 0xbb, 0x57, 0x1d, 0x0a, 0xe0, 0x63, 0x72, 0x1a, 0x3a, 0x8e, 0x6b, 0x0a, 0x0f, + 0x26, 0x75, 0x40, 0x4b, 0x66, 0x30, 0xdd, 0x24, 0xac, 0x51, 0x84, 0xc1, 0xbb, 0x84, 0x94, 0x6c, + 0xf9, 0xf7, 0x16, 0x46, 0x66, 0x43, 0xea, 0x43, 0x40, 0x0c, 0x06, 0xe7, 0x19, 0x44, 0x4f, 0xfc, + 0xa6, 0xef, 0xc4, 0x6f, 0x48, 0x3b, 0x7e, 0xda, 0x86, 0xaa, 0x0d, 0xdf, 0xbc, 0x74, 0x10, 0x87, + 0x07, 0xf5, 0x84, 0xa0, 0x17, 0xcd, 0x35, 0x2c, 0x39, 0xc4, 0x7e, 0x5b, 0xba, 0x69, 0x8b, 0x8e, + 0xf0, 0xf0, 0x62, 0xb7, 0xe8, 0x6a, 0x99, 0x6a, 0x86, 0x70, 0xd0, 0x9a, 0x5f, 0xf6, 0xfd, 0x09, + 0xc3, 0xca, 0xb6, 0x91, 0xe5, 0xb5, 0x9a, 0xa2, 0x82, 0x9f, 0xb5, 0xe2, 0x96, 0xbd, 0xf6, 0x30, + 0xaa, 0xe6, 0x04, 0x59, 0x0a, 0x34, 0xc6, 0x0c, 0x3f, 0x6f, 0x47, 0x2e, 0xdb, 0x9d, 0xf6, 0x58, + 0x4f, 0x98, 0x6d, 0x8f, 0x96, 0x90, 0xda, 0xf6, 0x68, 0x0b, 0x2e, 0xb9, 0x79, 0x0f, 0x5f, 0x7a, + 0x86, 0xfd, 0xad, 0x2b, 0xff, 0x84, 0x5f, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x35, 0x75, 0xc2, + 0x92, 0xef, 0x05, 0x00, 0x00, +} From a97bf8b7c026c63a39b3c119f785ef4dac125435 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Thu, 14 Apr 2016 16:54:04 -0600 Subject: [PATCH 37/86] feat(helm): implement 'helm create' --- cmd/helm/create.go | 77 +++++++++++++++++++++++++++++++++++++++++ cmd/helm/helm.go | 1 + cmd/helm/init.go | 4 +-- pkg/chart/chart.go | 41 +++++++--------------- pkg/chart/chart_test.go | 45 ++++++++++++++++++++++-- pkg/client/install.go | 2 +- 6 files changed, 135 insertions(+), 35 deletions(-) create mode 100644 cmd/helm/create.go diff --git a/cmd/helm/create.go b/cmd/helm/create.go new file mode 100644 index 000000000..c982f0a40 --- /dev/null +++ b/cmd/helm/create.go @@ -0,0 +1,77 @@ +package main + +import ( + "errors" + "os/user" + "path/filepath" + + "github.com/deis/tiller/pkg/chart" + "github.com/spf13/cobra" +) + +const createDesc = ` +This command creates a chart directory along with the common files and +directories used in a chart. + +For example, 'helm create foo' will create a directory structure that looks +something like this: + + foo/ + |- Chart.yaml + | + |- values.toml + | + |- templates/ + +'helm create' takes a path for an argument. If directories in the given path +do not exist, Helm will attempt to create them as it goes. If the given +destination exists and there are files in that directory, conflicting files +will be overwritten, but other files will be left alone. +` + +func init() { + RootCommand.AddCommand(createCmd) +} + +var createCmd = &cobra.Command{ + Use: "create [PATH]", + Short: "Create a new chart at the location specified.", + Long: createDesc, + RunE: runCreate, +} + +func runCreate(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("the name of the new chart is required") + } + cname := args[0] + cmd.Printf("Creating %s\n", cname) + + chartname := filepath.Base(cname) + cfile := chart.Chartfile{ + Name: chartname, + Description: "A Helm chart for Kubernetes", + Version: "0.1.0", + Maintainers: []*chart.Maintainer{ + {Name: username()}, + }, + } + + if _, err := chart.Create(&cfile, filepath.Dir(cname)); err != nil { + return err + } + + return nil +} + +func username() string { + uname := "Unknown" + u, err := user.Current() + if err == nil { + uname = u.Name + if uname == "" { + uname = u.Username + } + } + return uname +} diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 785dac967..def0dd1da 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -8,6 +8,7 @@ import ( var stdout = os.Stdout +// RootCommand is the top-level command for Helm. var RootCommand = &cobra.Command{ Use: "helm", Short: "The Helm package manager for Kubernetes.", diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 062f6d6b4..7a7f53b31 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/cobra" ) -const longDesc = ` +const installDesc = ` This command installs Tiller (the helm server side component) onto your Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.helm/) ` @@ -24,7 +24,7 @@ func init() { var initCmd = &cobra.Command{ Use: "init", Short: "Initialize Helm on both client and server.", - Long: longDesc, + Long: installDesc, RunE: RunInit, } diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go index 6edad5bc8..55a08fe61 100644 --- a/pkg/chart/chart.go +++ b/pkg/chart/chart.go @@ -34,9 +34,8 @@ const ChartfileName string = "Chart.yaml" const ( preTemplates string = "templates/" - preHooks string = "hooks/" - preDocs string = "docs/" - preIcon string = "icon.svg" + preValues string = "values.toml" + preCharts string = "charts/" ) var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") @@ -78,28 +77,14 @@ func (c *Chart) Dir() string { return c.loader.dir() } -// DocsDir returns the directory where the chart's documentation is stored. -func (c *Chart) DocsDir() string { - return filepath.Join(c.loader.dir(), preDocs) -} - -// HooksDir returns the directory where the hooks are stored. -func (c *Chart) HooksDir() string { - return filepath.Join(c.loader.dir(), preHooks) -} - // TemplatesDir returns the directory where the templates are stored. func (c *Chart) TemplatesDir() string { return filepath.Join(c.loader.dir(), preTemplates) } -// Icon returns the path to the icon.svg file. -// -// If an icon is not found in the chart, this will return an error. -func (c *Chart) Icon() (string, error) { - i := filepath.Join(c.Dir(), preIcon) - _, err := os.Stat(i) - return i, err +// ChartsDir returns teh directory where dependency charts are stored. +func (c *Chart) ChartsDir() string { + return filepath.Join(c.loader.dir(), preCharts) } // chartLoader provides load, close, and save implementations for a chart. @@ -174,26 +159,24 @@ func Create(chartfile *Chartfile, dir string) (*Chart, error) { n := fname(chartfile.Name) cdir := filepath.Join(path, n) - if _, err := os.Stat(cdir); err == nil { - return nil, fmt.Errorf("directory already exists: %s", cdir) + if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() { + return nil, fmt.Errorf("file %s already exists and is not a directory", cdir) } if err := os.MkdirAll(cdir, 0755); err != nil { return nil, err } - rollback := func() { - // TODO: Should we log failures here? - os.RemoveAll(cdir) + if err := chartfile.Save(filepath.Join(cdir, ChartfileName)); err != nil { + return nil, err } - if err := chartfile.Save(filepath.Join(cdir, ChartfileName)); err != nil { - rollback() + val := []byte(fmt.Sprintf("# Default Values for %s\n", chartfile.Name)) + if err := ioutil.WriteFile(filepath.Join(cdir, preValues), val, 0644); err != nil { return nil, err } - for _, d := range []string{preHooks, preDocs, preTemplates} { + for _, d := range []string{preTemplates, preCharts} { if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil { - rollback() return nil, err } } diff --git a/pkg/chart/chart_test.go b/pkg/chart/chart_test.go index dd114fe67..e14ffe396 100644 --- a/pkg/chart/chart_test.go +++ b/pkg/chart/chart_test.go @@ -19,6 +19,7 @@ package chart import ( "fmt" "io/ioutil" + "os" "path/filepath" "reflect" "testing" @@ -47,6 +48,44 @@ func TestLoadDir(t *testing.T) { } } +func TestCreate(t *testing.T) { + tdir, err := ioutil.TempDir("", "helm-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tdir) + + cf := &Chartfile{Name: "foo"} + + c, err := Create(cf, tdir) + if err != nil { + t.Fatal(err) + } + + dir := filepath.Join(tdir, "foo") + + if c.Chartfile().Name != "foo" { + t.Errorf("Expected name to be 'foo', got %q", c.Chartfile().Name) + } + + for _, d := range []string{preTemplates, preCharts} { + if fi, err := os.Stat(filepath.Join(dir, d)); err != nil { + t.Errorf("Expected %s dir: %s", d, err) + } else if !fi.IsDir() { + t.Errorf("Expected %s to be a directory.", d) + } + } + + for _, f := range []string{ChartfileName, preValues} { + if fi, err := os.Stat(filepath.Join(dir, f)); err != nil { + t.Errorf("Expected %s file: %s", f, err) + } else if fi.IsDir() { + t.Errorf("Expected %s to be a fle.", f) + } + } + +} + func TestLoad(t *testing.T) { c, err := Load(testarchive) if err != nil { @@ -102,9 +141,9 @@ func TestChart(t *testing.T) { } dir := c.Dir() - d := c.DocsDir() - if d != filepath.Join(dir, preDocs) { - t.Errorf("Unexpectedly, docs are in %s", d) + d := c.ChartsDir() + if d != filepath.Join(dir, preCharts) { + t.Errorf("Unexpectedly, charts are in %s", d) } d = c.TemplatesDir() diff --git a/pkg/client/install.go b/pkg/client/install.go index 1861727e2..adf34f8d0 100644 --- a/pkg/client/install.go +++ b/pkg/client/install.go @@ -20,7 +20,7 @@ type Installer struct { Tiller map[string]interface{} } -// New Installer creates a new Installer +// NewInstaller creates a new Installer func NewInstaller() *Installer { return &Installer{ Metadata: map[string]interface{}{}, From 1dd6c01f5dde35b527d0a44cd35012609775d382 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 15 Apr 2016 17:52:33 -0600 Subject: [PATCH 38/86] fix(helm): improve create help --- cmd/helm/create.go | 8 +++++--- pkg/chart/chart.go | 8 +++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cmd/helm/create.go b/cmd/helm/create.go index c982f0a40..be8cd40a9 100644 --- a/cmd/helm/create.go +++ b/cmd/helm/create.go @@ -17,11 +17,13 @@ For example, 'helm create foo' will create a directory structure that looks something like this: foo/ - |- Chart.yaml + |- Chart.yaml # Information about your chart | - |- values.toml + |- values.toml # The default values for your templates | - |- templates/ + |- charts/ # Charts that this chart depends on + | + |- templates/ # The template files 'helm create' takes a path for an argument. If directories in the given path do not exist, Helm will attempt to create them as it goes. If the given diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go index 55a08fe61..964f8490b 100644 --- a/pkg/chart/chart.go +++ b/pkg/chart/chart.go @@ -38,6 +38,12 @@ const ( preCharts string = "charts/" ) +const defaultValues = `# Default values for %s. +# This is a TOML-formatted file. https://github.com/toml-lang/toml +# Declare name/value pairs to be passed into your templates. +# name = "value" +` + var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") // Chart represents a complete chart. @@ -170,7 +176,7 @@ func Create(chartfile *Chartfile, dir string) (*Chart, error) { return nil, err } - val := []byte(fmt.Sprintf("# Default Values for %s\n", chartfile.Name)) + val := []byte(fmt.Sprintf(defaultValues, chartfile.Name)) if err := ioutil.WriteFile(filepath.Join(cdir, preValues), val, 0644); err != nil { return nil, err } From 8b36967a7e55f0e755d82bebed9c02d3cb30d010 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Mon, 18 Apr 2016 11:04:55 -0600 Subject: [PATCH 39/86] fix(client): remove auto-generating maintainer --- cmd/helm/create.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/cmd/helm/create.go b/cmd/helm/create.go index be8cd40a9..7f52d04b2 100644 --- a/cmd/helm/create.go +++ b/cmd/helm/create.go @@ -2,7 +2,6 @@ package main import ( "errors" - "os/user" "path/filepath" "github.com/deis/tiller/pkg/chart" @@ -54,9 +53,6 @@ func runCreate(cmd *cobra.Command, args []string) error { Name: chartname, Description: "A Helm chart for Kubernetes", Version: "0.1.0", - Maintainers: []*chart.Maintainer{ - {Name: username()}, - }, } if _, err := chart.Create(&cfile, filepath.Dir(cname)); err != nil { @@ -65,15 +61,3 @@ func runCreate(cmd *cobra.Command, args []string) error { return nil } - -func username() string { - uname := "Unknown" - u, err := user.Current() - if err == nil { - uname = u.Name - if uname == "" { - uname = u.Username - } - } - return uname -} From 0b9683e193c0ac10f58cfb96ff22e3425cace372 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Mon, 18 Apr 2016 12:33:25 -0600 Subject: [PATCH 40/86] feat(tiller): add release server stub --- _proto/hapi/chart/chart.proto | 15 +++-- _proto/hapi/chart/config.proto | 1 + _proto/hapi/services/tiller.proto | 7 +++ cmd/tiller/releases_server.go | 47 ++++++++++++++ cmd/tiller/server.go | 46 -------------- cmd/tiller/server_test.go | 7 +-- cmd/tiller/tiller.go | 20 +++++- pkg/proto/hapi/chart/chart.pb.go | 37 +++++++---- pkg/proto/hapi/chart/config.pb.go | 10 +-- pkg/proto/hapi/services/tiller.pb.go | 94 +++++++++++++++++++--------- 10 files changed, 180 insertions(+), 104 deletions(-) create mode 100644 cmd/tiller/releases_server.go delete mode 100644 cmd/tiller/server.go diff --git a/_proto/hapi/chart/chart.proto b/_proto/hapi/chart/chart.proto index 67bb79ee4..150bd6a14 100644 --- a/_proto/hapi/chart/chart.proto +++ b/_proto/hapi/chart/chart.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package hapi.chart; +import "hapi/chart/config.proto"; import "hapi/chart/metadata.proto"; import "hapi/chart/template.proto"; @@ -13,12 +14,16 @@ option go_package = "chart"; // optionally parameterizable templates, and zero or more charts (dependencies). // message Chart { - // TODO + // Contents of the Chartfile. hapi.chart.Metadata metadata = 1; - - // TODO + + // Templates for this chart. hapi.chart.Templates templates = 2; - - // TODO + + // Charts that this chart depends on. repeated Chart dependencies = 3; + + // Default config for this template. + hapi.chart.Config values = 4; + } diff --git a/_proto/hapi/chart/config.proto b/_proto/hapi/chart/config.proto index e5c51e9a7..541564268 100644 --- a/_proto/hapi/chart/config.proto +++ b/_proto/hapi/chart/config.proto @@ -10,4 +10,5 @@ option go_package = "chart"; // A config supplies values to the parametrizable templates of a chart. // message Config { + string raw = 1; } diff --git a/_proto/hapi/services/tiller.proto b/_proto/hapi/services/tiller.proto index 6704477f8..34fd9687a 100644 --- a/_proto/hapi/services/tiller.proto +++ b/_proto/hapi/services/tiller.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package hapi.services.tiller; +import "hapi/chart/chart.proto"; +import "hapi/chart/config.proto"; import "hapi/release/release.proto"; import "hapi/release/status.proto"; @@ -163,6 +165,10 @@ message UpdateReleaseResponse { // TODO // message InstallReleaseRequest { + // Chart is the protobuf representation of a chart. + hapi.chart.Chart chart = 1; + // Values is a string containing (unparsed) TOML values. + hapi.chart.Config values = 2; } // @@ -171,6 +177,7 @@ message InstallReleaseRequest { // TODO // message InstallReleaseResponse { + hapi.release.Release release = 1; } // diff --git a/cmd/tiller/releases_server.go b/cmd/tiller/releases_server.go new file mode 100644 index 000000000..a5b0da9ae --- /dev/null +++ b/cmd/tiller/releases_server.go @@ -0,0 +1,47 @@ +package main + +import ( + "errors" + + "github.com/deis/tiller/cmd/tiller/environment" + "github.com/deis/tiller/pkg/proto/hapi/services" + ctx "golang.org/x/net/context" +) + +func init() { + srv := &releaseServer{ + env: env, + } + services.RegisterReleaseServiceServer(rootServer, srv) +} + +type releaseServer struct { + env *environment.Environment +} + +// errNotImplemented is a temporary error for uninmplemented callbacks. +var errNotImplemented = errors.New("not implemented") + +func (s *releaseServer) ListReleases(req *services.ListReleasesRequest, stream services.ReleaseService_ListReleasesServer) error { + return errNotImplemented +} + +func (s *releaseServer) GetReleaseStatus(c ctx.Context, req *services.GetReleaseStatusRequest) (*services.GetReleaseStatusResponse, error) { + return nil, errNotImplemented +} + +func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleaseContentRequest) (*services.GetReleaseContentResponse, error) { + return nil, errNotImplemented +} + +func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { + return nil, errNotImplemented +} + +func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { + return &services.InstallReleaseResponse{}, errNotImplemented +} + +func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) { + return nil, errNotImplemented +} diff --git a/cmd/tiller/server.go b/cmd/tiller/server.go deleted file mode 100644 index 5a98244db..000000000 --- a/cmd/tiller/server.go +++ /dev/null @@ -1,46 +0,0 @@ -package main - -import ( - "net" - - "github.com/deis/tiller/cmd/tiller/environment" - "github.com/deis/tiller/pkg/hapi" - ctx "golang.org/x/net/context" - "google.golang.org/grpc" -) - -type server struct { - Environment *environment.Environment -} - -// newServer creates a new server with the default environment. -// -// TODO: This can take a configuration object of some sort so that we can -// initialize the environment with the correct stuff. -func newServer() *server { - return &server{ - Environment: environment.New(), - } -} - -func (s *server) Ready(c ctx.Context, req *hapi.PingRequest) (*hapi.PingResponse, error) { - return &hapi.PingResponse{Status: "OK"}, nil -} - -// startServer starts a new gRPC server listening on the given address. -// -// addr must conform to the requirements of "net.Listen". -func startServer(addr string) error { - lstn, err := net.Listen("tcp", addr) - if err != nil { - return nil - } - - hserver := newServer() - - srv := grpc.NewServer() - hapi.RegisterProbeServer(srv, hserver) - srv.Serve(lstn) - - return nil -} diff --git a/cmd/tiller/server_test.go b/cmd/tiller/server_test.go index 82efbfe6a..58a79744e 100644 --- a/cmd/tiller/server_test.go +++ b/cmd/tiller/server_test.go @@ -13,18 +13,17 @@ import ( var _ environment.Engine = &engine.Engine{} var _ environment.ReleaseStorage = storage.NewMemory() -func TestNewServer(t *testing.T) { +func TestInit(t *testing.T) { defer func() { if recover() != nil { t.Fatalf("Panic trapped. Check EngineYard.Default()") } }() - s := newServer() // This will panic if it is not correct. - s.Environment.EngineYard.Default() + env.EngineYard.Default() - e, ok := s.Environment.EngineYard.Get(environment.GoTplEngine) + e, ok := env.EngineYard.Get(environment.GoTplEngine) if !ok { t.Fatalf("Could not find GoTplEngine") } diff --git a/cmd/tiller/tiller.go b/cmd/tiller/tiller.go index 5fb3b524b..98ccec73a 100644 --- a/cmd/tiller/tiller.go +++ b/cmd/tiller/tiller.go @@ -2,11 +2,20 @@ package main import ( "fmt" + "net" "os" "github.com/codegangsta/cli" + "github.com/deis/tiller/cmd/tiller/environment" + "google.golang.org/grpc" ) +// rootServer is the root gRPC server. +// +// Each gRPC service registers itself to this server during init(). +var rootServer *grpc.Server = grpc.NewServer() +var env = environment.New() + func main() { app := cli.NewApp() app.Name = "tiller" @@ -17,7 +26,16 @@ func main() { } func start(c *cli.Context) { - if err := startServer(":44134"); err != nil { + addr := ":44134" + lstn, err := net.Listen("tcp", addr) + if err != nil { + fmt.Fprintf(os.Stderr, "Server died: %s\n", err) + os.Exit(1) + } + + fmt.Printf("Tiller is running on %s\n", addr) + + if err := rootServer.Serve(lstn); err != nil { fmt.Fprintf(os.Stderr, "Server died: %s\n", err) os.Exit(1) } diff --git a/pkg/proto/hapi/chart/chart.pb.go b/pkg/proto/hapi/chart/chart.pb.go index 36570e173..86c246e83 100644 --- a/pkg/proto/hapi/chart/chart.pb.go +++ b/pkg/proto/hapi/chart/chart.pb.go @@ -40,12 +40,14 @@ const _ = proto.ProtoPackageIsVersion1 // optionally parameterizable templates, and zero or more charts (dependencies). // type Chart struct { - // TODO + // Contents of the Chartfile. Metadata *Metadata `protobuf:"bytes,1,opt,name=metadata" json:"metadata,omitempty"` - // TODO + // Templates for this chart. Templates *Templates `protobuf:"bytes,2,opt,name=templates" json:"templates,omitempty"` - // TODO + // Charts that this chart depends on. Dependencies []*Chart `protobuf:"bytes,3,rep,name=dependencies" json:"dependencies,omitempty"` + // Default config for this template. + Values *Config `protobuf:"bytes,4,opt,name=values" json:"values,omitempty"` } func (m *Chart) Reset() { *m = Chart{} } @@ -74,21 +76,30 @@ func (m *Chart) GetDependencies() []*Chart { return nil } +func (m *Chart) GetValues() *Config { + if m != nil { + return m.Values + } + return nil +} + func init() { proto.RegisterType((*Chart)(nil), "hapi.chart.Chart") } var fileDescriptor0 = []byte{ - // 169 bytes of a gzipped FileDescriptorProto + // 200 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcb, 0x48, 0x2c, 0xc8, 0xd4, 0x4f, 0xce, 0x48, 0x2c, 0x2a, 0x81, 0x90, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x5c, - 0x20, 0x71, 0x3d, 0xb0, 0x88, 0x94, 0x24, 0x92, 0x9a, 0xdc, 0xd4, 0x92, 0xc4, 0x94, 0xc4, 0x92, - 0x44, 0x88, 0x32, 0x14, 0xa9, 0x92, 0xd4, 0xdc, 0x82, 0x9c, 0xc4, 0x92, 0x54, 0x88, 0x94, 0xd2, - 0x52, 0x46, 0x2e, 0x56, 0x67, 0x90, 0x84, 0x90, 0x01, 0x17, 0x07, 0x4c, 0x9b, 0x04, 0xa3, 0x02, - 0xa3, 0x06, 0xb7, 0x91, 0x88, 0x1e, 0xc2, 0x78, 0x3d, 0x5f, 0xa8, 0x5c, 0x10, 0x5c, 0x95, 0x90, - 0x31, 0x17, 0x27, 0xcc, 0xb4, 0x62, 0x09, 0x26, 0xb0, 0x16, 0x51, 0x64, 0x2d, 0x21, 0x30, 0xc9, - 0x20, 0x84, 0x3a, 0x21, 0x53, 0x2e, 0x9e, 0x94, 0xd4, 0x82, 0xd4, 0xbc, 0x94, 0xd4, 0xbc, 0xe4, - 0x4c, 0xa0, 0x3e, 0x66, 0x05, 0x66, 0xa0, 0x3e, 0x41, 0x64, 0x7d, 0x60, 0xf7, 0x04, 0xa1, 0x28, - 0x73, 0x62, 0x8f, 0x62, 0x05, 0x4b, 0x26, 0xb1, 0x81, 0xdd, 0x6d, 0x0c, 0x08, 0x00, 0x00, 0xff, - 0xff, 0xd3, 0xb1, 0x38, 0xe5, 0x13, 0x01, 0x00, 0x00, + 0x20, 0x71, 0x3d, 0xb0, 0x88, 0x94, 0x38, 0xb2, 0x9a, 0xfc, 0xbc, 0xb4, 0xcc, 0x74, 0x88, 0x22, + 0x29, 0x49, 0x24, 0x89, 0xdc, 0xd4, 0x92, 0xc4, 0x94, 0xc4, 0x92, 0x44, 0x2c, 0x52, 0x25, 0xa9, + 0xb9, 0x05, 0x39, 0x89, 0x25, 0xa9, 0x10, 0x29, 0xa5, 0x8b, 0x8c, 0x5c, 0xac, 0xce, 0x20, 0x09, + 0x21, 0x03, 0x2e, 0x0e, 0x98, 0x36, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x11, 0x3d, 0x84, + 0xbd, 0x7a, 0xbe, 0x50, 0xb9, 0x20, 0xb8, 0x2a, 0x21, 0x63, 0x2e, 0x4e, 0x98, 0x69, 0xc5, 0x12, + 0x4c, 0x60, 0x2d, 0xa2, 0xc8, 0x5a, 0x42, 0x60, 0x92, 0x41, 0x08, 0x75, 0x42, 0xa6, 0x5c, 0x3c, + 0x29, 0xa9, 0x05, 0xa9, 0x79, 0x29, 0xa9, 0x79, 0xc9, 0x99, 0x40, 0x7d, 0xcc, 0x0a, 0xcc, 0x40, + 0x7d, 0x82, 0xc8, 0xfa, 0xc0, 0xee, 0x09, 0x42, 0x51, 0x26, 0xa4, 0xc5, 0xc5, 0x56, 0x96, 0x98, + 0x53, 0x0a, 0xd4, 0xc0, 0x02, 0xb6, 0x48, 0x08, 0x45, 0x03, 0x38, 0x1c, 0x82, 0xa0, 0x2a, 0x9c, + 0xd8, 0xa3, 0x58, 0xc1, 0xe2, 0x49, 0x6c, 0x60, 0x3f, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, + 0x12, 0xa6, 0x6a, 0xa8, 0x58, 0x01, 0x00, 0x00, } diff --git a/pkg/proto/hapi/chart/config.pb.go b/pkg/proto/hapi/chart/config.pb.go index 191a7e304..decab3377 100644 --- a/pkg/proto/hapi/chart/config.pb.go +++ b/pkg/proto/hapi/chart/config.pb.go @@ -19,6 +19,7 @@ var _ = math.Inf // A config supplies values to the parametrizable templates of a chart. // type Config struct { + Raw string `protobuf:"bytes,1,opt,name=raw" json:"raw,omitempty"` } func (m *Config) Reset() { *m = Config{} } @@ -31,10 +32,11 @@ func init() { } var fileDescriptor1 = []byte{ - // 74 bytes of a gzipped FileDescriptorProto + // 89 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcf, 0x48, 0x2c, 0xc8, 0xd4, 0x4f, 0xce, 0x48, 0x2c, 0x2a, 0xd1, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, - 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x02, 0x49, 0xe8, 0x81, 0x25, 0x94, 0x38, 0xb8, 0xd8, 0x9c, 0xc1, - 0x72, 0x4e, 0xec, 0x51, 0xac, 0x60, 0xa1, 0x24, 0x36, 0xb0, 0x2a, 0x63, 0x40, 0x00, 0x00, 0x00, - 0xff, 0xff, 0x98, 0x3b, 0x34, 0xb8, 0x40, 0x00, 0x00, 0x00, + 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x02, 0x49, 0xe8, 0x81, 0x25, 0x94, 0xa4, 0xb8, 0xd8, 0x9c, 0xc1, + 0x72, 0x42, 0x02, 0x5c, 0xcc, 0x45, 0x89, 0xe5, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x20, + 0xa6, 0x13, 0x7b, 0x14, 0x2b, 0x58, 0x51, 0x12, 0x1b, 0x58, 0x9f, 0x31, 0x20, 0x00, 0x00, 0xff, + 0xff, 0xfe, 0xa0, 0x78, 0x2a, 0x52, 0x00, 0x00, 0x00, } diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go index e13d3ea9d..b12567750 100644 --- a/pkg/proto/hapi/services/tiller.pb.go +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -7,6 +7,8 @@ package services import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" +import hapi_chart3 "github.com/deis/tiller/pkg/proto/hapi/chart" +import hapi_chart "github.com/deis/tiller/pkg/proto/hapi/chart" import hapi_release2 "github.com/deis/tiller/pkg/proto/hapi/release" import hapi_release "github.com/deis/tiller/pkg/proto/hapi/release" @@ -173,6 +175,10 @@ func (*UpdateReleaseResponse) Descriptor() ([]byte, []int) { return fileDescript // TODO // type InstallReleaseRequest struct { + // Chart is the protobuf representation of a chart. + Chart *hapi_chart3.Chart `protobuf:"bytes,1,opt,name=chart" json:"chart,omitempty"` + // Values is a string containing (unparsed) TOML values. + Values *hapi_chart.Config `protobuf:"bytes,2,opt,name=values" json:"values,omitempty"` } func (m *InstallReleaseRequest) Reset() { *m = InstallReleaseRequest{} } @@ -180,12 +186,27 @@ func (m *InstallReleaseRequest) String() string { return proto.Compac func (*InstallReleaseRequest) ProtoMessage() {} func (*InstallReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{8} } +func (m *InstallReleaseRequest) GetChart() *hapi_chart3.Chart { + if m != nil { + return m.Chart + } + return nil +} + +func (m *InstallReleaseRequest) GetValues() *hapi_chart.Config { + if m != nil { + return m.Values + } + return nil +} + // // InstallReleaseResponse: // // TODO // type InstallReleaseResponse struct { + Release *hapi_release2.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` } func (m *InstallReleaseResponse) Reset() { *m = InstallReleaseResponse{} } @@ -193,6 +214,13 @@ func (m *InstallReleaseResponse) String() string { return proto.Compa func (*InstallReleaseResponse) ProtoMessage() {} func (*InstallReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{9} } +func (m *InstallReleaseResponse) GetRelease() *hapi_release2.Release { + if m != nil { + return m.Release + } + return nil +} + // // UninstallReleaseRequest: // @@ -509,35 +537,39 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ } var fileDescriptor1 = []byte{ - // 469 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x54, 0xcd, 0xd2, 0xd2, 0x30, - 0x14, 0xfd, 0x2a, 0x7e, 0x88, 0x97, 0x9f, 0xd1, 0x58, 0xa0, 0x64, 0x85, 0x59, 0xe1, 0x5f, 0xab, - 0xb8, 0x54, 0x37, 0xb2, 0x70, 0x9c, 0x61, 0x5c, 0xd4, 0x61, 0xe3, 0xc6, 0xa9, 0x18, 0xc6, 0x38, - 0xa5, 0xad, 0x4d, 0x60, 0xe1, 0x3b, 0xf8, 0x3e, 0x3e, 0x9e, 0x34, 0x3f, 0x1d, 0x5a, 0x9a, 0xa1, - 0xae, 0x98, 0xe4, 0x9c, 0x73, 0xef, 0xb9, 0xb9, 0xa7, 0x00, 0xfe, 0x11, 0x65, 0x2c, 0xe0, 0x34, - 0x3f, 0xb2, 0x2d, 0xe5, 0x81, 0x60, 0x71, 0x4c, 0x73, 0x3f, 0xcb, 0x53, 0x91, 0x22, 0xb7, 0xc0, - 0x7c, 0x83, 0xf9, 0x0a, 0xc3, 0x4a, 0x91, 0xd3, 0x98, 0x46, 0x9c, 0x9a, 0x5f, 0xa5, 0xc0, 0xb3, - 0x0a, 0xc6, 0x45, 0x24, 0x0e, 0x5c, 0x41, 0x64, 0x05, 0x8f, 0xd6, 0x8c, 0x8b, 0x50, 0x61, 0x3c, - 0xa4, 0xbf, 0x0e, 0x94, 0x0b, 0xe4, 0xc2, 0x6d, 0xcc, 0xf6, 0x4c, 0x78, 0xce, 0xdc, 0x59, 0x74, - 0x42, 0x75, 0x40, 0x13, 0xe8, 0xa6, 0xbb, 0x1d, 0xa7, 0xc2, 0xbb, 0x23, 0xaf, 0xf5, 0x89, 0xfc, - 0x71, 0xc0, 0xad, 0x56, 0xe1, 0x59, 0x9a, 0x70, 0x5a, 0x94, 0xd9, 0xa6, 0x87, 0xa4, 0x2c, 0x23, - 0x0f, 0xb6, 0x32, 0x05, 0x5b, 0xa4, 0x22, 0x8a, 0xbd, 0x8e, 0x62, 0xcb, 0x03, 0x7a, 0x05, 0x3d, - 0xed, 0x9c, 0x7b, 0x77, 0xe7, 0x9d, 0x45, 0x7f, 0x39, 0xf6, 0xe5, 0x0b, 0x98, 0x19, 0x75, 0xd7, - 0xb0, 0xa4, 0x91, 0xb7, 0x30, 0xfd, 0x40, 0x8d, 0x9b, 0xcf, 0x72, 0x5c, 0x33, 0xd8, 0x63, 0x18, - 0x68, 0xda, 0xd7, 0x24, 0xda, 0x53, 0x69, 0xec, 0x7e, 0xd8, 0xd7, 0x77, 0x9f, 0x4e, 0x57, 0xe4, - 0x37, 0x78, 0x97, 0x6a, 0x3d, 0xd0, 0x75, 0x39, 0x7a, 0x03, 0x23, 0x43, 0x51, 0x2f, 0x2d, 0xa7, - 0xec, 0x2f, 0xdd, 0xaa, 0x6b, 0x5d, 0x78, 0x98, 0x9f, 0xf7, 0x21, 0xef, 0xce, 0x7b, 0xaf, 0xd2, - 0x44, 0xd0, 0x44, 0xfc, 0x87, 0xf5, 0x35, 0xcc, 0x1a, 0xe4, 0xda, 0x7b, 0x00, 0xf7, 0x34, 0x57, - 0x4a, 0xad, 0xef, 0x68, 0x58, 0x64, 0x02, 0xee, 0x26, 0xfb, 0x1e, 0x09, 0x6a, 0x10, 0x65, 0x84, - 0x4c, 0x61, 0x5c, 0xbb, 0x57, 0x1d, 0x0a, 0xe0, 0x63, 0x72, 0x1a, 0x3a, 0x8e, 0x6b, 0x0a, 0x0f, - 0x26, 0x75, 0x40, 0x4b, 0x66, 0x30, 0xdd, 0x24, 0xac, 0x51, 0x84, 0xc1, 0xbb, 0x84, 0x94, 0x6c, - 0xf9, 0xf7, 0x16, 0x46, 0x66, 0x43, 0xea, 0x43, 0x40, 0x0c, 0x06, 0xe7, 0x19, 0x44, 0x4f, 0xfc, - 0xa6, 0xef, 0xc4, 0x6f, 0x48, 0x3b, 0x7e, 0xda, 0x86, 0xaa, 0x0d, 0xdf, 0xbc, 0x74, 0x10, 0x87, - 0x07, 0xf5, 0x84, 0xa0, 0x17, 0xcd, 0x35, 0x2c, 0x39, 0xc4, 0x7e, 0x5b, 0xba, 0x69, 0x8b, 0x8e, - 0xf0, 0xf0, 0x62, 0xb7, 0xe8, 0x6a, 0x99, 0x6a, 0x86, 0x70, 0xd0, 0x9a, 0x5f, 0xf6, 0xfd, 0x09, - 0xc3, 0xca, 0xb6, 0x91, 0xe5, 0xb5, 0x9a, 0xa2, 0x82, 0x9f, 0xb5, 0xe2, 0x96, 0xbd, 0xf6, 0x30, - 0xaa, 0xe6, 0x04, 0x59, 0x0a, 0x34, 0xc6, 0x0c, 0x3f, 0x6f, 0x47, 0x2e, 0xdb, 0x9d, 0xf6, 0x58, - 0x4f, 0x98, 0x6d, 0x8f, 0x96, 0x90, 0xda, 0xf6, 0x68, 0x0b, 0x2e, 0xb9, 0x79, 0x0f, 0x5f, 0x7a, - 0x86, 0xfd, 0xad, 0x2b, 0xff, 0x84, 0x5f, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x35, 0x75, 0xc2, - 0x92, 0xef, 0x05, 0x00, 0x00, + // 529 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0x4d, 0x73, 0xd3, 0x30, + 0x10, 0x6d, 0x08, 0x0d, 0x65, 0xd3, 0x66, 0xa8, 0x70, 0x12, 0x47, 0xa7, 0xa2, 0x0b, 0xa5, 0x80, + 0x03, 0xe1, 0x08, 0x5c, 0xc8, 0x81, 0xe9, 0x4c, 0x87, 0x83, 0x99, 0x5e, 0xb8, 0x30, 0x26, 0x28, + 0x54, 0x8c, 0x62, 0x07, 0x4b, 0xc9, 0x81, 0xff, 0xc0, 0xff, 0xe1, 0xe7, 0x61, 0xeb, 0xc3, 0x13, + 0x39, 0xd6, 0xd4, 0xf4, 0xe2, 0x8c, 0xf4, 0xde, 0xee, 0xdb, 0x5d, 0xbd, 0x9d, 0x00, 0xbe, 0x49, + 0xd6, 0x6c, 0x2a, 0x68, 0xbe, 0x65, 0x0b, 0x2a, 0xa6, 0x92, 0x71, 0x4e, 0xf3, 0x68, 0x9d, 0x67, + 0x32, 0x43, 0x41, 0x89, 0x45, 0x16, 0x8b, 0x34, 0x86, 0x47, 0x2a, 0x62, 0x71, 0x93, 0xe4, 0x52, + 0x7f, 0x35, 0x1b, 0x8f, 0x77, 0xef, 0xb3, 0x74, 0xc9, 0x7e, 0x18, 0x40, 0x4b, 0xe4, 0x94, 0xd3, + 0x44, 0x50, 0xfb, 0x6b, 0xb0, 0x89, 0x83, 0x09, 0x99, 0xc8, 0x8d, 0xd0, 0x10, 0x99, 0xc3, 0xe3, + 0x2b, 0x26, 0x64, 0xac, 0x31, 0x11, 0xd3, 0x5f, 0x1b, 0x2a, 0x24, 0x0a, 0xe0, 0x90, 0xb3, 0x15, + 0x93, 0x61, 0xe7, 0xac, 0x73, 0xde, 0x8d, 0xf5, 0x01, 0x8d, 0xa0, 0x97, 0x2d, 0x97, 0x82, 0xca, + 0xf0, 0x9e, 0xba, 0x36, 0x27, 0xf2, 0xa7, 0x03, 0x81, 0x9b, 0x45, 0xac, 0xb3, 0x54, 0xd0, 0x32, + 0xcd, 0x22, 0xdb, 0xa4, 0x55, 0x1a, 0x75, 0xf0, 0xa5, 0x29, 0xd9, 0x32, 0x93, 0x09, 0x0f, 0xbb, + 0x9a, 0xad, 0x0e, 0xe8, 0x35, 0x1c, 0x99, 0xca, 0x45, 0x78, 0xff, 0xac, 0x7b, 0xde, 0x9f, 0x0d, + 0x23, 0x35, 0x32, 0xdb, 0xa3, 0x51, 0x8d, 0x2b, 0x1a, 0x79, 0x07, 0xe3, 0x8f, 0xd4, 0x56, 0xf3, + 0x59, 0xb5, 0x6b, 0x1b, 0x7b, 0x02, 0xc7, 0x86, 0xf6, 0x35, 0x4d, 0x56, 0x54, 0x15, 0xf6, 0x30, + 0xee, 0x9b, 0xbb, 0x4f, 0xc5, 0x15, 0xf9, 0x0d, 0xe1, 0x7e, 0xb4, 0x69, 0xe8, 0xf6, 0x70, 0xf4, + 0x16, 0x06, 0x96, 0xa2, 0x27, 0xad, 0xba, 0xec, 0xcf, 0x02, 0xb7, 0x6a, 0x93, 0xf8, 0x24, 0xdf, + 0xd5, 0x21, 0xef, 0x77, 0xb5, 0xe7, 0x59, 0x2a, 0x69, 0x2a, 0xff, 0xa3, 0xf4, 0x2b, 0x98, 0x34, + 0x84, 0x9b, 0xda, 0xa7, 0xf0, 0xc0, 0x70, 0x55, 0xa8, 0x77, 0x8e, 0x96, 0x45, 0x46, 0x10, 0x5c, + 0xaf, 0xbf, 0x27, 0x92, 0x5a, 0x44, 0x17, 0x42, 0xc6, 0x30, 0xac, 0xdd, 0x6b, 0x05, 0xc2, 0x61, + 0x78, 0x99, 0x16, 0x4d, 0x73, 0xee, 0x46, 0xa0, 0xa7, 0x85, 0x0f, 0x4a, 0xcb, 0x1a, 0xe1, 0x53, + 0x2d, 0xac, 0x7d, 0x3d, 0x2f, 0xbf, 0xb1, 0xc6, 0xd1, 0x05, 0xf4, 0xb6, 0x09, 0x2f, 0x62, 0xcc, + 0xd0, 0x90, 0xc3, 0x54, 0x7e, 0x8f, 0x0d, 0x83, 0x5c, 0xc2, 0xa8, 0xae, 0x76, 0xd7, 0x4e, 0x27, + 0x30, 0xbe, 0x4e, 0x59, 0x53, 0xe9, 0x04, 0x43, 0xb8, 0x0f, 0x69, 0x9d, 0xd9, 0xdf, 0x43, 0x18, + 0x58, 0x9f, 0xe8, 0xfd, 0x45, 0x0c, 0x8e, 0x77, 0x37, 0x01, 0x3d, 0x8b, 0x9a, 0xd6, 0x3b, 0x6a, + 0xd8, 0x39, 0x7c, 0xd1, 0x86, 0x6a, 0x26, 0x7d, 0xf0, 0xaa, 0x83, 0x04, 0x3c, 0xaa, 0xfb, 0x14, + 0xbd, 0x6c, 0xce, 0xe1, 0xd9, 0x06, 0x1c, 0xb5, 0xa5, 0x5b, 0x59, 0xb4, 0x85, 0xd3, 0x3d, 0x87, + 0xa1, 0x5b, 0xd3, 0xb8, 0x4e, 0xc6, 0xd3, 0xd6, 0xfc, 0x4a, 0xf7, 0x27, 0x9c, 0x38, 0x9e, 0x43, + 0x9e, 0x69, 0x35, 0x19, 0x16, 0x3f, 0x6f, 0xc5, 0xad, 0xb4, 0x56, 0x30, 0x70, 0x8d, 0x85, 0x3c, + 0x09, 0x1a, 0xcd, 0x8e, 0x5f, 0xb4, 0x23, 0x57, 0x72, 0xc5, 0x3b, 0xd6, 0x1d, 0xe6, 0x7b, 0x47, + 0x8f, 0x49, 0x7d, 0xef, 0xe8, 0x33, 0x2e, 0x39, 0xf8, 0x00, 0x5f, 0x8e, 0x2c, 0xfb, 0x5b, 0x4f, + 0xfd, 0x15, 0xbc, 0xf9, 0x17, 0x00, 0x00, 0xff, 0xff, 0x75, 0xe1, 0x2e, 0x4f, 0xa6, 0x06, 0x00, + 0x00, } From 7d207d9a7134fd5f79f32e9a321960d576964755 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Mon, 18 Apr 2016 14:28:26 -0600 Subject: [PATCH 41/86] feat(init): add local config step to init This is the second half of issue #3. --- cmd/helm/helm.go | 5 ++++ cmd/helm/init.go | 60 +++++++++++++++++++++++++++++++++++++++++-- cmd/helm/init_test.go | 32 ++++++++++++++++++++--- 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 8ab220c20..89f293c82 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -7,6 +7,7 @@ import ( ) var stdout = os.Stdout +var helmHome string var globalUsage = `The Kubernetes package manager @@ -35,6 +36,10 @@ var RootCommand = &cobra.Command{ Long: globalUsage, } +func init() { + RootCommand.PersistentFlags().StringVar(&helmHome, "home", "$HOME/.helm", "location of you Helm files [$HELM_HOME]") +} + func main() { RootCommand.Execute() } diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 062f6d6b4..c942b4ffe 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -3,6 +3,9 @@ package main import ( "errors" "fmt" + "io/ioutil" + "os" + "path/filepath" "github.com/deis/tiller/pkg/client" "github.com/deis/tiller/pkg/kubectl" @@ -14,6 +17,10 @@ This command installs Tiller (the helm server side component) onto your Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.helm/) ` +const repositoriesPath = ".repositories" +const cachePath = "cache" + +var defaultRepo = map[string]string{"default-name": "default-url"} var tillerImg string func init() { @@ -34,18 +41,30 @@ func RunInit(cmd *cobra.Command, args []string) error { return errors.New("This command does not accept arguments. \n") } + if err := EnsureHome(os.ExpandEnv(helmHome)); err != nil { + return err + } + + if err := installTiller(); err != nil { + return err + } + + fmt.Printf("Tiller (the helm server side component) has been installed into your Kubernetes Cluster.\n$HELM_HOME has also been configured at %s.\nHappy Helming!\n", helmHome) + return nil +} + +func installTiller() error { // TODO: take value of global flag kubectl and pass that in runner := buildKubectlRunner("") i := client.NewInstaller() i.Tiller["Image"] = tillerImg - out, err := i.Install(runner) + if err != nil { return fmt.Errorf("error installing %s %s", string(out), err) } - fmt.Printf("Tiller (the helm server side component) has been installed into your Kubernetes Cluster.\n") return nil } @@ -55,3 +74,40 @@ func buildKubectlRunner(kubectlPath string) kubectl.Runner { } return &kubectl.RealRunner{} } + +// EnsureHome checks to see if $HELM_HOME exists +// +// If $HELM_HOME does not exist, this function will create it. +func EnsureHome(home string) error { + configDirectories := []string{home, CacheDirectory(home)} + + for _, p := range configDirectories { + if fi, err := os.Stat(p); err != nil { + fmt.Printf("Creating %s \n", p) + if err := os.MkdirAll(p, 0755); err != nil { + return fmt.Errorf("Could not create %s: %s", p, err) + } + } else if !fi.IsDir() { + return fmt.Errorf("%s must be a directory.", p) + } + } + + repoPath := RepositoriesFile(home) + if fi, err := os.Stat(repoPath); err != nil { + fmt.Printf("Creating %s \n", repoPath) + if err := ioutil.WriteFile(repoPath, []byte("test-charts: https://www.googleapis.com/storage/v1/b/test-charts/o\n"), 0644); err != nil { + return err + } + } else if fi.IsDir() { + return fmt.Errorf("%s must be a file, not a directory.", repoPath) + } + return nil +} + +func CacheDirectory(home string) string { + return filepath.Join(home, cachePath) +} + +func RepositoriesFile(home string) string { + return filepath.Join(home, repositoriesPath) +} diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index 59a45c6ab..31d75bc8e 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -1,10 +1,36 @@ package main import ( + "io/ioutil" + "os" "testing" ) -func TestInit(t *testing.T) { - //TODO: call command and make sure no error is returned - //TODO: check local config +func TestEnsureHome(t *testing.T) { + home := CreateTmpHome() + if err := EnsureHome(home); err != nil { + t.Errorf("%s", err) + } + + dirs := []string{home, CacheDirectory(home)} + for _, dir := range dirs { + if fi, err := os.Stat(dir); err != nil { + t.Errorf("%s", err) + } else if !fi.IsDir() { + t.Errorf("%s is not a directory", fi) + } + } + + if fi, err := os.Stat(RepositoriesFile(home)); err != nil { + t.Errorf("%s", err) + } else if fi.IsDir() { + t.Errorf("%s should not be a directory", fi) + } + +} + +func CreateTmpHome() string { + tmpHome, _ := ioutil.TempDir("", "helm_home") + defer os.Remove(tmpHome) + return tmpHome } From 50f434951261ea3c8132e49ecabf6df649f6f9cd Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Mon, 18 Apr 2016 17:28:04 -0600 Subject: [PATCH 42/86] feat(tiller): add template and release to install --- _proto/hapi/chart/chart.proto | 2 +- _proto/hapi/chart/template.proto | 22 ++++------ cmd/tiller/environment/environment.go | 3 +- cmd/tiller/environment/environment_test.go | 5 ++- cmd/tiller/releases_server.go | 36 ++++++++++++++-- glide.lock | 10 +++-- glide.yaml | 1 + pkg/engine/engine.go | 43 ++++++++++++++++--- pkg/engine/engine_test.go | 4 +- pkg/proto/hapi/chart/chart.pb.go | 23 +++++----- pkg/proto/hapi/chart/template.pb.go | 50 ++++++---------------- 11 files changed, 118 insertions(+), 81 deletions(-) diff --git a/_proto/hapi/chart/chart.proto b/_proto/hapi/chart/chart.proto index 150bd6a14..4529b440f 100644 --- a/_proto/hapi/chart/chart.proto +++ b/_proto/hapi/chart/chart.proto @@ -18,7 +18,7 @@ message Chart { hapi.chart.Metadata metadata = 1; // Templates for this chart. - hapi.chart.Templates templates = 2; + repeated hapi.chart.Template templates = 2; // Charts that this chart depends on. repeated Chart dependencies = 3; diff --git a/_proto/hapi/chart/template.proto b/_proto/hapi/chart/template.proto index 4f9968966..3e68113c2 100644 --- a/_proto/hapi/chart/template.proto +++ b/_proto/hapi/chart/template.proto @@ -4,20 +4,14 @@ package hapi.chart; option go_package = "chart"; +// Template represents a template as a name/value pair. // -// Template: -// -// TODO -// -message Templates { - // TODO - repeated Template templates = 1; -} - +// By convention, name is a relative path within the scope of the chart's +// base directory. message Template { - // TODO - string template_name = 1; - - // TODO - bytes template_data = 2; + // Name is the path-like name of the template. + string name = 1; + + // Data is the template as byte data. + bytes data = 2; } diff --git a/cmd/tiller/environment/environment.go b/cmd/tiller/environment/environment.go index 078d24b50..b8a38c0ad 100644 --- a/cmd/tiller/environment/environment.go +++ b/cmd/tiller/environment/environment.go @@ -3,6 +3,7 @@ package environment import ( "github.com/deis/tiller/pkg/engine" "github.com/deis/tiller/pkg/hapi" + "github.com/deis/tiller/pkg/proto/hapi/chart" "github.com/deis/tiller/pkg/storage" ) @@ -51,7 +52,7 @@ func (y EngineYard) Default() Engine { // An Engine must be capable of executing multiple concurrent requests, but // without tainting one request's environment with data from another request. type Engine interface { - Render(*hapi.Chart, *hapi.Values) (map[string]string, error) + Render(*chart.Chart, *chart.Config) (map[string]string, error) } // ReleaseStorage represents a storage engine for a Release. diff --git a/cmd/tiller/environment/environment_test.go b/cmd/tiller/environment/environment_test.go index ffe531490..2ab7d7f2d 100644 --- a/cmd/tiller/environment/environment_test.go +++ b/cmd/tiller/environment/environment_test.go @@ -4,13 +4,14 @@ import ( "testing" "github.com/deis/tiller/pkg/hapi" + "github.com/deis/tiller/pkg/proto/hapi/chart" ) type mockEngine struct { out map[string]string } -func (e *mockEngine) Render(chrt *hapi.Chart, v *hapi.Values) (map[string]string, error) { +func (e *mockEngine) Render(chrt *chart.Chart, v *chart.Config) (map[string]string, error) { return e.out, nil } @@ -63,7 +64,7 @@ func TestEngine(t *testing.T) { if engine, ok := env.EngineYard.Get("test"); !ok { t.Errorf("failed to get engine from EngineYard") - } else if out, err := engine.Render(&hapi.Chart{}, &hapi.Values{}); err != nil { + } else if out, err := engine.Render(&chart.Chart{}, &chart.Config{}); err != nil { t.Errorf("unexpected template error: %s", err) } else if out["albatross"] != "test" { t.Errorf("expected 'test', got %q", out["albatross"]) diff --git a/cmd/tiller/releases_server.go b/cmd/tiller/releases_server.go index a5b0da9ae..6dab276cb 100644 --- a/cmd/tiller/releases_server.go +++ b/cmd/tiller/releases_server.go @@ -4,7 +4,9 @@ import ( "errors" "github.com/deis/tiller/cmd/tiller/environment" + "github.com/deis/tiller/pkg/proto/hapi/release" "github.com/deis/tiller/pkg/proto/hapi/services" + "github.com/technosophos/moniker" ctx "golang.org/x/net/context" ) @@ -19,8 +21,11 @@ type releaseServer struct { env *environment.Environment } -// errNotImplemented is a temporary error for uninmplemented callbacks. -var errNotImplemented = errors.New("not implemented") +var ( + // errNotImplemented is a temporary error for uninmplemented callbacks. + errNotImplemented = errors.New("not implemented") + errMissingChart = errors.New("no chart provided") +) func (s *releaseServer) ListReleases(req *services.ListReleasesRequest, stream services.ReleaseService_ListReleasesServer) error { return errNotImplemented @@ -39,7 +44,32 @@ func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateRelease } func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { - return &services.InstallReleaseResponse{}, errNotImplemented + if req.Chart == nil { + return nil, errMissingChart + } + + // We should probably make a name generator part of the Environment. + namer := moniker.New() + // TODO: Make sure this is unique. + name := namer.Name() + + // Render the templates + _, err := s.env.EngineYard.Default().Render(req.Chart, req.Values) + if err != nil { + return nil, err + } + + // Store a release. + r := &release.Release{ + Name: name, + Chart: req.Chart, + Config: req.Values, + Info: &release.Info{ + Status: &release.Status{Code: release.Status_UNKNOWN}, + }, + } + + return &services.InstallReleaseResponse{Release: r}, errNotImplemented } func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) { diff --git a/glide.lock b/glide.lock index 71ba5d45c..ed5d5e980 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: e7c99013acb06eb359cf20390579af9a4553ef0fbed3f7bbb784b4ab7c8df807 -updated: 2016-04-15T15:15:21.87772545-06:00 +hash: 264d156a2a07d53efbf5f608ead3eb31c261de5124e0235139b3f99c6ead4dba +updated: 2016-04-18T17:25:07.662942088-06:00 imports: - name: github.com/aokoli/goutils version: 9c37978a95bd5c709a15883b6242714ea6709e64 @@ -11,6 +11,8 @@ imports: version: f0a097ddac24fb00e07d2ac17f8671423f3ea47c subpackages: - proto + - ptypes/any + - ptypes/timestamp - name: github.com/Masterminds/semver version: 808ed7761c233af2de3f9729a041d68c62527f3a - name: github.com/Masterminds/sprig @@ -21,6 +23,8 @@ imports: - cobra - name: github.com/spf13/pflag version: 8f6a28b0916586e7f22fe931ae2fcfc380b1c0e6 +- name: github.com/technosophos/moniker + version: 9f956786b91d9786ca11aa5be6104542fa911546 - name: golang.org/x/net version: fb93926129b8ec0056f2f458b1f519654814edf0 subpackages: @@ -30,7 +34,7 @@ imports: - http2/hpack - internal/timeseries - name: google.golang.org/grpc - version: 8eeecf2291de9d171d0b1392a27ff3975679f4f5 + version: dec33edc378cf4971a2741cfd86ed70a644d6ba3 subpackages: - codes - credentials diff --git a/glide.yaml b/glide.yaml index 2b1531d12..7560443da 100644 --- a/glide.yaml +++ b/glide.yaml @@ -13,3 +13,4 @@ import: - package: github.com/Masterminds/semver version: 1.1.0 - package: github.com/BurntSushi/toml +- package: github.com/technosophos/moniker diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 59a42f96f..c19caab58 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -6,7 +6,8 @@ import ( "text/template" "github.com/Masterminds/sprig" - "github.com/deis/tiller/pkg/hapi" + chartutil "github.com/deis/tiller/pkg/chart" + "github.com/deis/tiller/pkg/proto/hapi/chart" ) // Engine is an implementation of 'cmd/tiller/environment'.Engine that uses Go templates. @@ -38,13 +39,41 @@ func New() *Engine { // // This will look in the chart's 'templates' data (e.g. the 'templates/' directory) // and attempt to render the templates there using the values passed in. -func (e *Engine) Render(chart *hapi.Chart, vals *hapi.Values) (map[string]string, error) { - // Uncomment this once the proto files compile. - //return render(chart.Chartfile.Name, chart.Templates, vals) - return map[string]string{}, nil +func (e *Engine) Render(chrt *chart.Chart, vals *chart.Config) (map[string]string, error) { + var cvals chartutil.Values + if chrt.Values == nil { + cvals = map[string]interface{}{} + } else { + var err error + cvals, err = chartutil.ReadValues([]byte(chrt.Values.Raw)) + if err != nil { + return map[string]string{}, err + } + } + + // Parse values if not nil + if vals != nil { + evals, err := chartutil.ReadValues([]byte(vals.Raw)) + if err != nil { + return map[string]string{}, err + } + // Coalesce chart default values and values + for k, v := range evals { + // FIXME: This needs to merge tables. Ideally, this feature should + // be part of the Values type. + cvals[k] = v + } + } + + // Render the charts + tmap := make(map[string]string, len(chrt.Templates)) + for _, tpl := range chrt.Templates { + tmap[tpl.Name] = string(tpl.Data) + } + return e.render(tmap, cvals) } -func (e *Engine) render(name string, tpls map[string]string, v interface{}) (map[string]string, error) { +func (e *Engine) render(tpls map[string]string, v interface{}) (map[string]string, error) { // Basically, what we do here is start with an empty parent template and then // build up a list of templates -- one for each file. Once all of the templates // have been parsed, we loop through again and execute every template. @@ -52,7 +81,7 @@ func (e *Engine) render(name string, tpls map[string]string, v interface{}) (map // The idea with this process is to make it possible for more complex templates // to share common blocks, but to make the entire thing feel like a file-based // template engine. - t := template.New(name) + t := template.New("gotpl") files := []string{} for fname, tpl := range tpls { t = t.New(fname).Funcs(e.FuncMap) diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index b0a00794b..dba3b2fe3 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -35,7 +35,7 @@ func TestRenderInternals(t *testing.T) { } vals := map[string]string{"Name": "one", "Value": "two"} - out, err := e.render("irrelevant", tpls, vals) + out, err := e.render(tpls, vals) if err != nil { t.Fatalf("Failed template rendering: %s", err) } @@ -68,7 +68,7 @@ func TestParallelRenderInternals(t *testing.T) { tt := fmt.Sprintf("expect-%d", i) tpls := map[string]string{fname: `{{.val}}`} v := map[string]string{"val": tt} - out, err := e.render("intentionally_duplicated", tpls, v) + out, err := e.render(tpls, v) if err != nil { t.Errorf("Failed to render %s: %s", tt, err) } diff --git a/pkg/proto/hapi/chart/chart.pb.go b/pkg/proto/hapi/chart/chart.pb.go index 86c246e83..81937a65e 100644 --- a/pkg/proto/hapi/chart/chart.pb.go +++ b/pkg/proto/hapi/chart/chart.pb.go @@ -16,7 +16,6 @@ It has these top-level messages: Config Maintainer Metadata - Templates Template */ package chart @@ -43,7 +42,7 @@ type Chart struct { // Contents of the Chartfile. Metadata *Metadata `protobuf:"bytes,1,opt,name=metadata" json:"metadata,omitempty"` // Templates for this chart. - Templates *Templates `protobuf:"bytes,2,opt,name=templates" json:"templates,omitempty"` + Templates []*Template `protobuf:"bytes,2,rep,name=templates" json:"templates,omitempty"` // Charts that this chart depends on. Dependencies []*Chart `protobuf:"bytes,3,rep,name=dependencies" json:"dependencies,omitempty"` // Default config for this template. @@ -62,7 +61,7 @@ func (m *Chart) GetMetadata() *Metadata { return nil } -func (m *Chart) GetTemplates() *Templates { +func (m *Chart) GetTemplates() []*Template { if m != nil { return m.Templates } @@ -88,18 +87,18 @@ func init() { } var fileDescriptor0 = []byte{ - // 200 bytes of a gzipped FileDescriptorProto + // 197 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcb, 0x48, 0x2c, 0xc8, 0xd4, 0x4f, 0xce, 0x48, 0x2c, 0x2a, 0x81, 0x90, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x5c, 0x20, 0x71, 0x3d, 0xb0, 0x88, 0x94, 0x38, 0xb2, 0x9a, 0xfc, 0xbc, 0xb4, 0xcc, 0x74, 0x88, 0x22, 0x29, 0x49, 0x24, 0x89, 0xdc, 0xd4, 0x92, 0xc4, 0x94, 0xc4, 0x92, 0x44, 0x2c, 0x52, 0x25, 0xa9, - 0xb9, 0x05, 0x39, 0x89, 0x25, 0xa9, 0x10, 0x29, 0xa5, 0x8b, 0x8c, 0x5c, 0xac, 0xce, 0x20, 0x09, + 0xb9, 0x05, 0x39, 0x89, 0x25, 0xa9, 0x10, 0x29, 0xa5, 0x0b, 0x8c, 0x5c, 0xac, 0xce, 0x20, 0x09, 0x21, 0x03, 0x2e, 0x0e, 0x98, 0x36, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x11, 0x3d, 0x84, - 0xbd, 0x7a, 0xbe, 0x50, 0xb9, 0x20, 0xb8, 0x2a, 0x21, 0x63, 0x2e, 0x4e, 0x98, 0x69, 0xc5, 0x12, - 0x4c, 0x60, 0x2d, 0xa2, 0xc8, 0x5a, 0x42, 0x60, 0x92, 0x41, 0x08, 0x75, 0x42, 0xa6, 0x5c, 0x3c, - 0x29, 0xa9, 0x05, 0xa9, 0x79, 0x29, 0xa9, 0x79, 0xc9, 0x99, 0x40, 0x7d, 0xcc, 0x0a, 0xcc, 0x40, - 0x7d, 0x82, 0xc8, 0xfa, 0xc0, 0xee, 0x09, 0x42, 0x51, 0x26, 0xa4, 0xc5, 0xc5, 0x56, 0x96, 0x98, - 0x53, 0x0a, 0xd4, 0xc0, 0x02, 0xb6, 0x48, 0x08, 0x45, 0x03, 0x38, 0x1c, 0x82, 0xa0, 0x2a, 0x9c, - 0xd8, 0xa3, 0x58, 0xc1, 0xe2, 0x49, 0x6c, 0x60, 0x3f, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, - 0x12, 0xa6, 0x6a, 0xa8, 0x58, 0x01, 0x00, 0x00, + 0xbd, 0x7a, 0xbe, 0x50, 0xb9, 0x20, 0xb8, 0x2a, 0x21, 0x23, 0x2e, 0x4e, 0x98, 0x69, 0xc5, 0x12, + 0x4c, 0x0a, 0xcc, 0xe8, 0x5a, 0x42, 0xa0, 0x92, 0x41, 0x08, 0x65, 0x42, 0xa6, 0x5c, 0x3c, 0x29, + 0xa9, 0x05, 0xa9, 0x79, 0x29, 0xa9, 0x79, 0xc9, 0x99, 0x40, 0x6d, 0xcc, 0x60, 0x6d, 0x82, 0xc8, + 0xda, 0xc0, 0xce, 0x09, 0x42, 0x51, 0x26, 0xa4, 0xc5, 0xc5, 0x56, 0x96, 0x98, 0x53, 0x0a, 0xd4, + 0xc0, 0x02, 0x76, 0x9a, 0x10, 0x8a, 0x06, 0x70, 0x30, 0x04, 0x41, 0x55, 0x38, 0xb1, 0x47, 0xb1, + 0x82, 0xc5, 0x93, 0xd8, 0xc0, 0x5e, 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xb5, 0xff, 0x0f, + 0xec, 0x57, 0x01, 0x00, 0x00, } diff --git a/pkg/proto/hapi/chart/template.pb.go b/pkg/proto/hapi/chart/template.pb.go index f5b6b3704..115bc945e 100644 --- a/pkg/proto/hapi/chart/template.pb.go +++ b/pkg/proto/hapi/chart/template.pb.go @@ -13,55 +13,33 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +// Template represents a template as a name/value pair. // -// Template: -// -// TODO -// -type Templates struct { - // TODO - Templates []*Template `protobuf:"bytes,1,rep,name=templates" json:"templates,omitempty"` -} - -func (m *Templates) Reset() { *m = Templates{} } -func (m *Templates) String() string { return proto.CompactTextString(m) } -func (*Templates) ProtoMessage() {} -func (*Templates) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{0} } - -func (m *Templates) GetTemplates() []*Template { - if m != nil { - return m.Templates - } - return nil -} - +// By convention, name is a relative path within the scope of the chart's +// base directory. type Template struct { - // TODO - TemplateName string `protobuf:"bytes,1,opt,name=template_name,json=templateName" json:"template_name,omitempty"` - // TODO - TemplateData []byte `protobuf:"bytes,2,opt,name=template_data,json=templateData,proto3" json:"template_data,omitempty"` + // Name is the path-like name of the template. + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Data is the template as byte data. + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` } func (m *Template) Reset() { *m = Template{} } func (m *Template) String() string { return proto.CompactTextString(m) } func (*Template) ProtoMessage() {} -func (*Template) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{1} } +func (*Template) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{0} } func init() { - proto.RegisterType((*Templates)(nil), "hapi.chart.Templates") proto.RegisterType((*Template)(nil), "hapi.chart.Template") } var fileDescriptor3 = []byte{ - // 146 bytes of a gzipped FileDescriptorProto + // 106 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x92, 0xcc, 0x48, 0x2c, 0xc8, 0xd4, 0x4f, 0xce, 0x48, 0x2c, 0x2a, 0xd1, 0x2f, 0x49, 0xcd, 0x2d, 0xc8, 0x49, 0x2c, 0x49, 0xd5, - 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x02, 0x49, 0xe9, 0x81, 0xa5, 0x94, 0xec, 0xb9, 0x38, - 0x43, 0xa0, 0xb2, 0xc5, 0x42, 0x46, 0x5c, 0x9c, 0x30, 0xa5, 0xc5, 0x12, 0x8c, 0x0a, 0xcc, 0x1a, - 0xdc, 0x46, 0x22, 0x7a, 0x08, 0xc5, 0x7a, 0x30, 0x95, 0x41, 0x08, 0x65, 0x4a, 0x21, 0x5c, 0x1c, - 0x30, 0x61, 0x21, 0x65, 0x2e, 0x5e, 0x98, 0x44, 0x7c, 0x5e, 0x62, 0x6e, 0x2a, 0xd0, 0x0c, 0x46, - 0x0d, 0xce, 0x20, 0x1e, 0x98, 0xa0, 0x1f, 0x50, 0x0c, 0x45, 0x51, 0x4a, 0x62, 0x49, 0xa2, 0x04, - 0x13, 0x50, 0x11, 0x0f, 0x42, 0x91, 0x0b, 0x50, 0xcc, 0x89, 0x3d, 0x8a, 0x15, 0x6c, 0x65, 0x12, - 0x1b, 0xd8, 0xc9, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x48, 0xda, 0x77, 0x0e, 0xcf, 0x00, - 0x00, 0x00, + 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x02, 0x49, 0xe9, 0x81, 0xa5, 0x94, 0x8c, 0xb8, 0x38, + 0x42, 0xa0, 0xb2, 0x42, 0x42, 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, + 0x9c, 0x41, 0x60, 0x36, 0x48, 0x2c, 0x25, 0xb1, 0x24, 0x51, 0x82, 0x09, 0x28, 0xc6, 0x13, 0x04, + 0x66, 0x3b, 0xb1, 0x47, 0xb1, 0x82, 0x35, 0x27, 0xb1, 0x81, 0xcd, 0x33, 0x06, 0x04, 0x00, 0x00, + 0xff, 0xff, 0x53, 0xee, 0x0e, 0x67, 0x6c, 0x00, 0x00, 0x00, } From f741fe6953434d0816a22f11829f62c53c721d86 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Mon, 18 Apr 2016 21:39:31 -0600 Subject: [PATCH 43/86] ref(package): add error handling on args --- cmd/helm/package.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 3e8e82978..217e0c018 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "os" "path/filepath" @@ -24,7 +25,7 @@ func init() { } var packageCmd = &cobra.Command{ - Use: "package", + Use: "package [CHART_PATH]", Short: "Package a chart directory into a chart archive.", Long: packageDesc, RunE: runPackage, @@ -32,8 +33,11 @@ var packageCmd = &cobra.Command{ func runPackage(cmd *cobra.Command, args []string) error { path := "." + if len(args) > 0 { path = args[0] + } else { + return fmt.Errorf("This command needs at least one argument, the path to the chart.") } path, err := filepath.Abs(path) From 8b8000ecc495e971b43c2bab3149eac55c177b0a Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Mon, 18 Apr 2016 21:47:56 -0600 Subject: [PATCH 44/86] feat(helm): implement home command --- cmd/helm/home.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/helm/home.go b/cmd/helm/home.go index 3b091931b..fce35ab2a 100644 --- a/cmd/helm/home.go +++ b/cmd/helm/home.go @@ -1,7 +1,7 @@ package main import ( - "fmt" + "os" "github.com/spf13/cobra" ) @@ -23,5 +23,5 @@ func init() { } func Home(cmd *cobra.Command, args []string) { - fmt.Println("helm home was called") + cmd.Printf(os.ExpandEnv(helmHome) + "\n") } From 9e2428323533455fe1520853d71f58f7dc69162d Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Tue, 19 Apr 2016 09:52:29 -0700 Subject: [PATCH 45/86] feat(local-cluster): add kubernetes startup script --- scripts/cluster/kube-system.yaml | 4 + scripts/cluster/skydns.yaml | 137 +++++++++++++ scripts/local-cluster.sh | 331 +++++++++++++++++++++++++++++++ 3 files changed, 472 insertions(+) create mode 100644 scripts/cluster/kube-system.yaml create mode 100644 scripts/cluster/skydns.yaml create mode 100755 scripts/local-cluster.sh diff --git a/scripts/cluster/kube-system.yaml b/scripts/cluster/kube-system.yaml new file mode 100644 index 000000000..986f4b482 --- /dev/null +++ b/scripts/cluster/kube-system.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kube-system diff --git a/scripts/cluster/skydns.yaml b/scripts/cluster/skydns.yaml new file mode 100644 index 000000000..720877d5d --- /dev/null +++ b/scripts/cluster/skydns.yaml @@ -0,0 +1,137 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + name: kube-dns-v10 + namespace: kube-system + labels: + k8s-app: kube-dns + version: v10 + kubernetes.io/cluster-service: "true" +spec: + replicas: 1 + selector: + k8s-app: kube-dns + version: v10 + template: + metadata: + labels: + k8s-app: kube-dns + version: v10 + kubernetes.io/cluster-service: "true" + spec: + containers: + - name: etcd + image: gcr.io/google_containers/etcd-amd64:2.2.1 + resources: + # keep request = limit to keep this container in guaranteed class + limits: + cpu: 100m + memory: 50Mi + requests: + cpu: 100m + memory: 50Mi + command: + - /usr/local/bin/etcd + - -data-dir + - /var/etcd/data + - -listen-client-urls + - http://127.0.0.1:2379,http://127.0.0.1:4001 + - -advertise-client-urls + - http://127.0.0.1:2379,http://127.0.0.1:4001 + - -initial-cluster-token + - skydns-etcd + volumeMounts: + - name: etcd-storage + mountPath: /var/etcd/data + - name: kube2sky + image: gcr.io/google_containers/kube2sky:1.12 + resources: + # keep request = limit to keep this container in guaranteed class + limits: + cpu: 100m + memory: 50Mi + requests: + cpu: 100m + memory: 50Mi + args: + # command = "/kube2sky" + - --domain=cluster.local + - name: skydns + image: gcr.io/google_containers/skydns:2015-10-13-8c72f8c + resources: + # keep request = limit to keep this container in guaranteed class + limits: + cpu: 100m + memory: 50Mi + requests: + cpu: 100m + memory: 50Mi + args: + # command = "/skydns" + - -machines=http://127.0.0.1:4001 + - -addr=0.0.0.0:53 + - -ns-rotate=false + - -domain=cluster.local. + ports: + - containerPort: 53 + name: dns + protocol: UDP + - containerPort: 53 + name: dns-tcp + protocol: TCP + livenessProbe: + httpGet: + path: /healthz + port: 8080 + scheme: HTTP + initialDelaySeconds: 30 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /healthz + port: 8080 + scheme: HTTP + initialDelaySeconds: 1 + timeoutSeconds: 5 + - name: healthz + image: gcr.io/google_containers/exechealthz:1.0 + resources: + # keep request = limit to keep this container in guaranteed class + limits: + cpu: 10m + memory: 20Mi + requests: + cpu: 10m + memory: 20Mi + args: + - -cmd=nslookup kubernetes.default.svc.cluster.local 127.0.0.1 >/dev/null + - -port=8080 + ports: + - containerPort: 8080 + protocol: TCP + volumes: + - name: etcd-storage + emptyDir: {} + dnsPolicy: Default # Don't use cluster DNS. +--- +apiVersion: v1 +kind: Service +metadata: + name: kube-dns + namespace: kube-system + labels: + k8s-app: kube-dns + kubernetes.io/cluster-service: "true" + kubernetes.io/name: "KubeDNS" +spec: + selector: + k8s-app: kube-dns + clusterIP: 10.0.0.10 + ports: + - name: dns + port: 53 + protocol: UDP + - name: dns-tcp + port: 53 + protocol: TCP + diff --git a/scripts/local-cluster.sh b/scripts/local-cluster.sh new file mode 100755 index 000000000..7cc04b71b --- /dev/null +++ b/scripts/local-cluster.sh @@ -0,0 +1,331 @@ +#!/usr/bin/env bash + +# Bash 'Strict Mode' +# http://redsymbol.net/articles/unofficial-bash-strict-mode +set -euo pipefail +IFS=$'\n\t' + +HELM_ROOT="${BASH_SOURCE[0]%/*}/.." +cd "$HELM_ROOT" + +# Globals ---------------------------------------------------------------------- + +KUBE_VERSION=${KUBE_VERSION:-} +KUBE_PORT=${KUBE_PORT:-8080} +KUBE_CONTEXT=${KUBE_CONTEXT:-docker} +KUBECTL=${KUBECTL:-kubectl} +ENABLE_CLUSTER_DNS=${KUBE_ENABLE_CLUSTER_DNS:-true} +LOG_LEVEL=${LOG_LEVEL:-2} + +# Helper Functions ------------------------------------------------------------- + +# Display error message and exit +error_exit() { + echo "error: ${1:-"unknown error"}" 1>&2 + exit 1 +} + +# Checks if a command exists. Returns 1 or 0 +command_exists() { + hash "${1}" 2>/dev/null +} + +# Program Functions ------------------------------------------------------------ + +# Check host platform and docker host +verify_prereqs() { + echo "Verifying Prerequisites...." + + case "$(uname -s)" in + Darwin) + host_os=darwin + ;; + Linux) + host_os=linux + ;; + *) + error_exit "Unsupported host OS. Must be Linux or Mac OS X." + ;; + esac + + case "$(uname -m)" in + x86_64*) + host_arch=amd64 + ;; + i?86_64*) + host_arch=amd64 + ;; + amd64*) + host_arch=amd64 + ;; + arm*) + host_arch=arm + ;; + i?86*) + host_arch=x86 + ;; + s390x*) + host_arch=s390x + ;; + ppc64le*) + host_arch=ppc64le + ;; + *) + error_exit "Unsupported host arch. Must be x86_64, 386, arm, s390x or ppc64le." + ;; + esac + + + command_exists docker || error_exit "You need docker" + + if ! docker info > /dev/null 2>&1 ; then + error_exit "Can't connect to 'docker' daemon." + fi + + $KUBECTL version --client >/dev/null || download_kubectl +} + +# Get the latest stable release tag +get_latest_version_number() { + local -r latest_url="https://storage.googleapis.com/kubernetes-release/release/stable.txt" + if command_exists wget ; then + wget -qO- ${latest_url} + elif command_exists curl ; then + curl -Ss ${latest_url} + else + error_exit "Couldn't find curl or wget. Bailing out." + fi +} + +# Detect ip address od docker host +detect_docker_host_ip() { + if [ -n "${DOCKER_HOST:-}" ]; then + awk -F'[/:]' '{print $4}' <<< "$DOCKER_HOST" + else + ifconfig docker0 \ + | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' \ + | grep -Eo '([0-9]*\.){3}[0-9]*' >/dev/null 2>&1 || : + fi +} + +# Set KUBE_MASTER_IP from docker host ip. Defaults to localhost +set_master_ip() { + local docker_ip + + if [[ -z "${KUBE_MASTER_IP:-}" ]]; then + docker_ip=$(detect_docker_host_ip) + if [[ -n "${docker_ip}" ]]; then + KUBE_MASTER_IP="${docker_ip}" + else + KUBE_MASTER_IP=localhost + fi + fi +} + +# Start dockerized kubelet +start_kubernetes() { + echo "Starting kubelet" + + # Enable dns + if [[ "${ENABLE_CLUSTER_DNS}" = true ]]; then + dns_args="--cluster-dns=10.0.0.1 --cluster-domain=cluster.local" + else + # DNS server for real world hostnames. + dns_args="--cluster-dns=8.8.8.8" + fi + + local start_time=$(date +%s) + + docker run \ + --name=kubelet \ + --volume=/:/rootfs:ro \ + --volume=/sys:/sys:ro \ + --volume=/var/lib/docker/:/var/lib/docker:rw \ + --volume=/var/lib/kubelet/:/var/lib/kubelet:rw \ + --volume=/var/run:/var/run:rw \ + --net=host \ + --pid=host \ + --privileged=true \ + -d \ + gcr.io/google_containers/hyperkube-amd64:${KUBE_VERSION} \ + /hyperkube kubelet \ + --containerized \ + --hostname-override="127.0.0.1" \ + --api-servers=http://localhost:8080 \ + --config=/etc/kubernetes/manifests \ + --allow-privileged=true \ + ${dns_args} \ + --v=${LOG_LEVEL} >/dev/null + + # We expect to have at least 3 running pods - etcd, master and kube-proxy. + local attempt=1 + while (($($KUBECTL get pods --no-headers 2>/dev/null | grep -c "Running") < 3)); do + echo -n "." + sleep $(( attempt++ )) + done + echo + + local end_time=$(date +%s) + echo "Started master components in $((end_time - start_time)) seconds." +} + +# Open kubernetes master api port. +setup_firewall() { + [[ -n "${DOCKER_MACHINE_NAME}" ]] || return + + echo "Adding iptables hackery for docker-machine..." + + local machine_ip + machine_ip=$(docker-machine ip "$DOCKER_MACHINE_NAME") + local iptables_rule="PREROUTING -p tcp -d ${machine_ip} --dport ${KUBE_PORT} -j DNAT --to-destination 127.0.0.1:${KUBE_PORT}" + + if ! docker-machine ssh "${DOCKER_MACHINE_NAME}" "sudo /usr/local/sbin/iptables -t nat -C ${iptables_rule}" &> /dev/null; then + docker-machine ssh "${DOCKER_MACHINE_NAME}" "sudo /usr/local/sbin/iptables -t nat -I ${iptables_rule}" + fi +} + +# Create kube-system namespace in kubernetes +create_kube_system_namespace() { + echo "Creating kube-system namespace..." + + $KUBECTL create -f ./scripts/cluster/kube-system.yaml >/dev/null +} + +# Activate skydns in kubernetes and wait for pods to be ready. +create_kube_dns() { + [[ "${ENABLE_CLUSTER_DNS}" = true ]] || return + + local start_time=$(date +%s) + + echo "Setting up cluster dns..." + + $KUBECTL create -f ./scripts/cluster/skydns.yaml >/dev/null + + echo "Waiting for cluster DNS to become available..." + + local attempt=1 + until $KUBECTL get pods --no-headers --namespace kube-system --selector=k8s-app=kube-dns 2>/dev/null | grep "Running" &>/dev/null; do + echo -n "." + sleep $(( attempt++ )) + done + echo + local end_time=$(date +%s) + echo "Started DNS in $((end_time - start_time)) seconds." +} + +# Generate kubeconfig data for the created cluster. +generate_kubeconfig() { + local cluster_args=( + "--server=http://${KUBE_MASTER_IP}:${KUBE_PORT}" + "--insecure-skip-tls-verify=true" + ) + + $KUBECTL config set-cluster "${KUBE_CONTEXT}" "${cluster_args[@]}" >/dev/null + $KUBECTL config set-context "${KUBE_CONTEXT}" --cluster="${KUBE_CONTEXT}" >/dev/null + $KUBECTL config use-context "${KUBE_CONTEXT}" >/dev/null + + echo "Wrote config for kubeconfig using context: '${KUBE_CONTEXT}'" +} + +# Download kubectl +download_kubectl() { + echo "Downloading kubectl binary..." + + kubectl_url="https://storage.googleapis.com/kubernetes-release/release/${KUBE_VERSION}/bin/${host_os}/${host_arch}/kubectl" + if command_exists wget; then + wget -O ./bin/kubectl "${kubectl_url}" + elif command_exists curl; then + curl -sSOL ./bin/kubectl "${kubectl_url}" + else + error_exit "Couldn't find curl or wget. Bailing out." + fi + chmod a+x ./bin/kubectl + + KUBECTL=./bin/kubectl +} + +# Clean volumes that are left by kubelet +# +# https://github.com/kubernetes/kubernetes/issues/23197 +# code stolen from https://github.com/huggsboson/docker-compose-kubernetes/blob/SwitchToSharedMount/kube-up.sh +clean_volumes() { + if [[ -n "${DOCKER_MACHINE_NAME}" ]]; then + docker-machine ssh "${DOCKER_MACHINE_NAME}" "mount | grep -o 'on /var/lib/kubelet.* type' | cut -c 4- | rev | cut -c 6- | rev | sort -r | xargs --no-run-if-empty sudo umount" + docker-machine ssh "${DOCKER_MACHINE_NAME}" "sudo rm -Rf /var/lib/kubelet" + docker-machine ssh "${DOCKER_MACHINE_NAME}" "sudo mkdir -p /var/lib/kubelet" + docker-machine ssh "${DOCKER_MACHINE_NAME}" "sudo mount --bind /var/lib/kubelet /var/lib/kubelet" + docker-machine ssh "${DOCKER_MACHINE_NAME}" "sudo mount --make-shared /var/lib/kubelet" + else + mount | grep -o 'on /var/lib/kubelet.* type' | cut -c 4- | rev | cut -c 6- | rev | sort -r | xargs --no-run-if-empty sudo umount + sudo rm -Rf /var/lib/kubelet + sudo mkdir -p /var/lib/kubelet + sudo mount --bind /var/lib/kubelet /var/lib/kubelet + sudo mount --make-shared /var/lib/kubelet + fi +} + +# Helper function to properly remove containers +delete_container() { + local container=("$@") + docker stop "${container[@]}" &>/dev/null || : + docker wait "${container[@]}" &>/dev/null || : + docker rm --force --volumes "${container[@]}" &>/dev/null || : +} + +# Delete master components and resources in kubernetes. +kube_down() { + echo "Deleting all resources in kubernetes..." + $KUBECTL delete replicationcontrollers,services,pods,secrets --all >/dev/null 2>&1 || : + $KUBECTL delete replicationcontrollers,services,pods,secrets --all --namespace=kube-system >/dev/null 2>&1 || : + $KUBECTL delete namespace kube-system >/dev/null 2>&1 || : + + echo "Stopping kubelet..." + delete_container kubelet + + echo "Stopping remaining kubernetes containers..." + local kube_containers=($(docker ps -aqf "name=k8s_")) + if [[ "${#kube_containers[@]}" -gt 0 ]]; then + delete_container "${kube_containers[@]}" + fi +} + +# Start a kubernetes cluster in docker. +kube_up() { + verify_prereqs + + set_master_ip + clean_volumes + setup_firewall + + start_kubernetes + generate_kubeconfig + create_kube_system_namespace + create_kube_dns + + $KUBECTL cluster-info +} + +KUBE_VERSION=${KUBE_VERSION:-$(get_latest_version_number)} + +# Main ------------------------------------------------------------------------- + +main() { + case "$1" in + up|start) + kube_up + ;; + down|stop) + kube_down + ;; + restart) + kube_down + kube_up + ;; + *) + echo "Usage: $0 {up|down|restart}" + ;; + esac +} + +main "${@:-}" + From dcfd1a4284177ea6c36767432384512562f6d621 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 20 Apr 2016 13:02:49 -0600 Subject: [PATCH 46/86] fix(storage): use new HAPI for storage --- cmd/tiller/environment/environment.go | 14 +++++++------- cmd/tiller/environment/environment_test.go | 22 +++++++++++----------- cmd/tiller/releases_server.go | 4 ++++ pkg/storage/memory.go | 22 +++++++++++----------- pkg/storage/memory_test.go | 10 +++++----- 5 files changed, 38 insertions(+), 34 deletions(-) diff --git a/cmd/tiller/environment/environment.go b/cmd/tiller/environment/environment.go index b8a38c0ad..e8949b1cd 100644 --- a/cmd/tiller/environment/environment.go +++ b/cmd/tiller/environment/environment.go @@ -2,8 +2,8 @@ package environment import ( "github.com/deis/tiller/pkg/engine" - "github.com/deis/tiller/pkg/hapi" "github.com/deis/tiller/pkg/proto/hapi/chart" + "github.com/deis/tiller/pkg/proto/hapi/release" "github.com/deis/tiller/pkg/storage" ) @@ -65,14 +65,14 @@ type ReleaseStorage interface { // If a release with the same name exists, this returns an error. // // It may return other errors in cases where it cannot write to storage. - Create(*hapi.Release) error + Create(*release.Release) error // Read takes a name and returns a release that has that name. // // It will only return releases that are not deleted and not superseded. // // It will return an error if no relevant release can be found, or if storage // is not properly functioning. - Read(name string) (*hapi.Release, error) + Read(name string) (*release.Release, error) // Update looks for a release with the same name and updates it with the // present release contents. @@ -82,24 +82,24 @@ type ReleaseStorage interface { // // It will return an error if a previous release is not found. It may also // return an error if the storage backend encounters an error. - Update(*hapi.Release) error + Update(*release.Release) error // Delete marks a Release as deleted. // // It returns the deleted record. If the record is not found or if the // underlying storage encounters an error, this will return an error. - Delete(name string) (*hapi.Release, error) + Delete(name string) (*release.Release, error) // List lists all active (non-deleted, non-superseded) releases. // // To get deleted or superseded releases, use Query. - List() ([]*hapi.Release, error) + List() ([]*release.Release, error) // Query takes a map of labels and returns any releases that match. // // Query will search all releases, including deleted and superseded ones. // The provided map will be used to filter results. - Query(map[string]string) ([]*hapi.Release, error) + Query(map[string]string) ([]*release.Release, error) } // KubeClient represents a client capable of communicating with the Kubernetes API. diff --git a/cmd/tiller/environment/environment_test.go b/cmd/tiller/environment/environment_test.go index 2ab7d7f2d..5cfbf22ae 100644 --- a/cmd/tiller/environment/environment_test.go +++ b/cmd/tiller/environment/environment_test.go @@ -3,8 +3,8 @@ package environment import ( "testing" - "github.com/deis/tiller/pkg/hapi" "github.com/deis/tiller/pkg/proto/hapi/chart" + "github.com/deis/tiller/pkg/proto/hapi/release" ) type mockEngine struct { @@ -16,33 +16,33 @@ func (e *mockEngine) Render(chrt *chart.Chart, v *chart.Config) (map[string]stri } type mockReleaseStorage struct { - rel *hapi.Release + rel *release.Release } -func (r *mockReleaseStorage) Create(v *hapi.Release) error { +func (r *mockReleaseStorage) Create(v *release.Release) error { r.rel = v return nil } -func (r *mockReleaseStorage) Read(k string) (*hapi.Release, error) { +func (r *mockReleaseStorage) Read(k string) (*release.Release, error) { return r.rel, nil } -func (r *mockReleaseStorage) Update(v *hapi.Release) error { +func (r *mockReleaseStorage) Update(v *release.Release) error { r.rel = v return nil } -func (r *mockReleaseStorage) Delete(k string) (*hapi.Release, error) { +func (r *mockReleaseStorage) Delete(k string) (*release.Release, error) { return r.rel, nil } -func (r *mockReleaseStorage) List() ([]*hapi.Release, error) { - return []*hapi.Release{}, nil +func (r *mockReleaseStorage) List() ([]*release.Release, error) { + return []*release.Release{}, nil } -func (r *mockReleaseStorage) Query(labels map[string]string) ([]*hapi.Release, error) { - return []*hapi.Release{}, nil +func (r *mockReleaseStorage) Query(labels map[string]string) ([]*release.Release, error) { + return []*release.Release{}, nil } type mockKubeClient struct { @@ -76,7 +76,7 @@ func TestReleaseStorage(t *testing.T) { env := New() env.Releases = rs - release := &hapi.Release{Name: "mariner"} + release := &release.Release{Name: "mariner"} if err := env.Releases.Create(release); err != nil { t.Fatalf("failed to store release: %s", err) diff --git a/cmd/tiller/releases_server.go b/cmd/tiller/releases_server.go index 6dab276cb..25d0e97ea 100644 --- a/cmd/tiller/releases_server.go +++ b/cmd/tiller/releases_server.go @@ -69,6 +69,10 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea }, } + if err := s.env.Releases.Create(r); err != nil { + return nil, err + } + return &services.InstallReleaseResponse{Release: r}, errNotImplemented } diff --git a/pkg/storage/memory.go b/pkg/storage/memory.go index 16d5650db..55ce53366 100644 --- a/pkg/storage/memory.go +++ b/pkg/storage/memory.go @@ -3,17 +3,17 @@ package storage import ( "errors" - "github.com/deis/tiller/pkg/hapi" + "github.com/deis/tiller/pkg/proto/hapi/release" ) // Memory is an in-memory ReleaseStorage implementation. type Memory struct { - releases map[string]*hapi.Release + releases map[string]*release.Release } func NewMemory() *Memory { return &Memory{ - releases: map[string]*hapi.Release{}, + releases: map[string]*release.Release{}, } } @@ -22,7 +22,7 @@ var ErrNotFound = errors.New("release not found") // Read returns the named Release. // // If the release is not found, an ErrNotFound error is returned. -func (m *Memory) Read(k string) (*hapi.Release, error) { +func (m *Memory) Read(k string) (*release.Release, error) { v, ok := m.releases[k] if !ok { return v, ErrNotFound @@ -31,7 +31,7 @@ func (m *Memory) Read(k string) (*hapi.Release, error) { } // Create sets a release. -func (m *Memory) Create(rel *hapi.Release) error { +func (m *Memory) Create(rel *release.Release) error { m.releases[rel.Name] = rel return nil } @@ -39,7 +39,7 @@ func (m *Memory) Create(rel *hapi.Release) error { var ErrNoRelease = errors.New("no release found") // Update sets a release. -func (m *Memory) Update(rel *hapi.Release) error { +func (m *Memory) Update(rel *release.Release) error { if _, ok := m.releases[rel.Name]; !ok { return ErrNoRelease } @@ -50,7 +50,7 @@ func (m *Memory) Update(rel *hapi.Release) error { return nil } -func (m *Memory) Delete(name string) (*hapi.Release, error) { +func (m *Memory) Delete(name string) (*release.Release, error) { rel, ok := m.releases[name] if !ok { return nil, ErrNoRelease @@ -60,8 +60,8 @@ func (m *Memory) Delete(name string) (*hapi.Release, error) { } // List returns all releases -func (m *Memory) List() ([]*hapi.Release, error) { - buf := make([]*hapi.Release, len(m.releases)) +func (m *Memory) List() ([]*release.Release, error) { + buf := make([]*release.Release, len(m.releases)) i := 0 for _, v := range m.releases { buf[i] = v @@ -69,6 +69,6 @@ func (m *Memory) List() ([]*hapi.Release, error) { } return buf, nil } -func (m *Memory) Query(labels map[string]string) ([]*hapi.Release, error) { - return []*hapi.Release{}, errors.New("Cannot implement until hapi.Release is defined.") +func (m *Memory) Query(labels map[string]string) ([]*release.Release, error) { + return []*release.Release{}, errors.New("Cannot implement until release.Release is defined.") } diff --git a/pkg/storage/memory_test.go b/pkg/storage/memory_test.go index b07e664ad..d2105b928 100644 --- a/pkg/storage/memory_test.go +++ b/pkg/storage/memory_test.go @@ -3,12 +3,12 @@ package storage import ( "testing" - "github.com/deis/tiller/pkg/hapi" + "github.com/deis/tiller/pkg/proto/hapi/release" ) func TestCreate(t *testing.T) { k := "test-1" - r := &hapi.Release{Name: k} + r := &release.Release{Name: k} ms := NewMemory() if err := ms.Create(r); err != nil { @@ -22,7 +22,7 @@ func TestCreate(t *testing.T) { func TestRead(t *testing.T) { k := "test-1" - r := &hapi.Release{Name: k} + r := &release.Release{Name: k} ms := NewMemory() ms.Create(r) @@ -36,7 +36,7 @@ func TestRead(t *testing.T) { func TestUpdate(t *testing.T) { k := "test-1" - r := &hapi.Release{Name: k} + r := &release.Release{Name: k} ms := NewMemory() if err := ms.Create(r); err != nil { @@ -56,7 +56,7 @@ func TestList(t *testing.T) { rels := []string{"a", "b", "c"} for _, k := range rels { - ms.Create(&hapi.Release{Name: k}) + ms.Create(&release.Release{Name: k}) } l, err := ms.List() From 00aaf653da8026c395e070c487562099efac359f Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 20 Apr 2016 16:23:51 -0600 Subject: [PATCH 47/86] fix(tiller): add tests --- .../{releases_server.go => release_server.go} | 2 +- cmd/tiller/release_server_test.go | 38 +++++++++++++++++++ cmd/tiller/{server_test.go => tiller_test.go} | 0 3 files changed, 39 insertions(+), 1 deletion(-) rename cmd/tiller/{releases_server.go => release_server.go} (96%) create mode 100644 cmd/tiller/release_server_test.go rename cmd/tiller/{server_test.go => tiller_test.go} (100%) diff --git a/cmd/tiller/releases_server.go b/cmd/tiller/release_server.go similarity index 96% rename from cmd/tiller/releases_server.go rename to cmd/tiller/release_server.go index 25d0e97ea..212b63d3c 100644 --- a/cmd/tiller/releases_server.go +++ b/cmd/tiller/release_server.go @@ -73,7 +73,7 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea return nil, err } - return &services.InstallReleaseResponse{Release: r}, errNotImplemented + return &services.InstallReleaseResponse{Release: r}, nil } func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) { diff --git a/cmd/tiller/release_server_test.go b/cmd/tiller/release_server_test.go new file mode 100644 index 000000000..a25bd9378 --- /dev/null +++ b/cmd/tiller/release_server_test.go @@ -0,0 +1,38 @@ +package main + +import ( + "github.com/deis/tiller/cmd/tiller/environment" + "github.com/deis/tiller/pkg/proto/hapi/chart" + "github.com/deis/tiller/pkg/proto/hapi/services" + "github.com/deis/tiller/pkg/storage" + "golang.org/x/net/context" + "testing" +) + +func rsFixture() *releaseServer { + return &releaseServer{ + env: mockEnvironment(), + } +} + +func TestInstallRelease(t *testing.T) { + c := context.Background() + rs := rsFixture() + + req := &services.InstallReleaseRequest{ + Chart: &chart.Chart{}, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Errorf("Failed install: %s", err) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } +} + +func mockEnvironment() *environment.Environment { + e := environment.New() + e.Releases = storage.NewMemory() + return e +} diff --git a/cmd/tiller/server_test.go b/cmd/tiller/tiller_test.go similarity index 100% rename from cmd/tiller/server_test.go rename to cmd/tiller/tiller_test.go From 00966bb4d1b673fd87fbd0779b6b83dc2cbaf5fb Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 20 Apr 2016 16:35:08 -0600 Subject: [PATCH 48/86] fix(helmet): remove helmet --- cmd/helmet/helmet.go | 40 ---------------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 cmd/helmet/helmet.go diff --git a/cmd/helmet/helmet.go b/cmd/helmet/helmet.go deleted file mode 100644 index 02fe1e450..000000000 --- a/cmd/helmet/helmet.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/codegangsta/cli" - "github.com/deis/tiller/pkg/hapi" - ctx "golang.org/x/net/context" - "google.golang.org/grpc" -) - -func main() { - app := cli.NewApp() - app.Name = "helmet" - app.Usage = "The Helm Easy Tester (HelmET)" - app.Action = run - - app.Run(os.Args) -} - -func run(c *cli.Context) { - conn, err := grpc.Dial("localhost:44134", grpc.WithInsecure()) - if err != nil { - fmt.Fprintf(os.Stderr, "Could not connect to server: %s\n", err) - os.Exit(1) - } - defer conn.Close() - - pc := hapi.NewProbeClient(conn) - - req := &hapi.PingRequest{Name: "helmet"} - res, err := pc.Ready(ctx.Background(), req) - if err != nil { - fmt.Fprintf(os.Stderr, "Error pinging server: %s\n", err) - os.Exit(1) - } - - fmt.Printf("Server is %s\n", res.Status) -} From 33a543c920f9a270ce18dc9f0c430250e7e6ede7 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 20 Apr 2016 17:16:33 -0600 Subject: [PATCH 49/86] fix(tiller): replace codegangsta with cobra --- cmd/tiller/tiller.go | 25 +++++++++++++++++-------- glide.lock | 6 ++---- glide.yaml | 5 ++++- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/cmd/tiller/tiller.go b/cmd/tiller/tiller.go index 98ccec73a..6e420d1ef 100644 --- a/cmd/tiller/tiller.go +++ b/cmd/tiller/tiller.go @@ -5,8 +5,8 @@ import ( "net" "os" - "github.com/codegangsta/cli" "github.com/deis/tiller/cmd/tiller/environment" + "github.com/spf13/cobra" "google.golang.org/grpc" ) @@ -16,16 +16,25 @@ import ( var rootServer *grpc.Server = grpc.NewServer() var env = environment.New() -func main() { - app := cli.NewApp() - app.Name = "tiller" - app.Usage = `The Helm server.` - app.Action = start +const globalUsage = `The Kubernetes Helm server. + +Tiller is the server for Helm. It provides in-cluster resource management. + +By default, Tiller listens for gRPC connections on port 44134. +` - app.Run(os.Args) +var rootCommand = &cobra.Command{ + Use: "tiller", + Short: "The Kubernetes Helm server.", + Long: globalUsage, + Run: start, +} + +func main() { + rootCommand.Execute() } -func start(c *cli.Context) { +func start(c *cobra.Command, args []string) { addr := ":44134" lstn, err := net.Listen("tcp", addr) if err != nil { diff --git a/glide.lock b/glide.lock index ed5d5e980..f73ce1e65 100644 --- a/glide.lock +++ b/glide.lock @@ -1,12 +1,10 @@ -hash: 264d156a2a07d53efbf5f608ead3eb31c261de5124e0235139b3f99c6ead4dba -updated: 2016-04-18T17:25:07.662942088-06:00 +hash: 23af9670ae8c1600f0f3c70bd9191e0df9211fed1e57430160aed95d5c71c2d1 +updated: 2016-04-20T17:16:04.782839628-06:00 imports: - name: github.com/aokoli/goutils version: 9c37978a95bd5c709a15883b6242714ea6709e64 - name: github.com/BurntSushi/toml version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f -- name: github.com/codegangsta/cli - version: 71f57d300dd6a780ac1856c005c4b518cfd498ec - name: github.com/golang/protobuf version: f0a097ddac24fb00e07d2ac17f8671423f3ea47c subpackages: diff --git a/glide.yaml b/glide.yaml index 7560443da..962fb5aee 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,6 +1,5 @@ package: github.com/deis/tiller import: -- package: google.golang.org/grpc - package: golang.org/x/net subpackages: - context @@ -14,3 +13,7 @@ import: version: 1.1.0 - package: github.com/BurntSushi/toml - package: github.com/technosophos/moniker +- package: github.com/golang/protobuf + version: f0a097ddac24fb00e07d2ac17f8671423f3ea47c +- package: google.golang.org/grpc + version: dec33edc378cf4971a2741cfd86ed70a644d6ba3 From b5aebe41e95a0e7c7d117748d4b3f6dc32009a10 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 20 Apr 2016 17:39:02 -0600 Subject: [PATCH 50/86] fix(Makefile): skip gofmt on proto gen code --- scripts/validate-go.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/validate-go.sh b/scripts/validate-go.sh index a99af956e..cf4e4a8f6 100755 --- a/scripts/validate-go.sh +++ b/scripts/validate-go.sh @@ -13,9 +13,10 @@ find_go_files() { } hash golint 2>/dev/null || go get -u github.com/golang/lint/golint +hash godir 2>/dev/null || go get -u github.com/Masterminds/godir echo "==> Running golint..." -for pkg in $(glide nv); do +for pkg in $(godir pkgs | grep -v proto); do if golint_out=$(golint "$pkg" 2>&1); then echo "${yellow}${golint_out}${reset}" fi @@ -23,7 +24,7 @@ done echo "==> Running go vet..." echo -n "$red" -go vet $(glide nv) 2>&1 | grep -v "^exit status " || exit_code=${PIPESTATUS[0]} +go vet $(godir pkgs) 2>&1 | grep -v "^exit status " || exit_code=${PIPESTATUS[0]} echo -n "$reset" echo "==> Running gofmt..." From c4b2926ce673ccb07eb487f72952fbb45b9babd1 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 20 Apr 2016 16:58:12 -0600 Subject: [PATCH 51/86] fix(*): fix misc style issues --- cmd/helm/home.go | 4 ++-- cmd/helm/init.go | 24 ++++++++++++------------ cmd/tiller/tiller.go | 2 +- pkg/chart/values.go | 2 ++ pkg/storage/memory.go | 13 ++++++++----- 5 files changed, 25 insertions(+), 20 deletions(-) diff --git a/cmd/helm/home.go b/cmd/helm/home.go index fce35ab2a..78d3473f1 100644 --- a/cmd/helm/home.go +++ b/cmd/helm/home.go @@ -15,13 +15,13 @@ var homeCommand = &cobra.Command{ Use: "home", Short: "Displays the location of HELM_HOME", Long: longHomeHelp, - Run: Home, + Run: home, } func init() { RootCommand.AddCommand(homeCommand) } -func Home(cmd *cobra.Command, args []string) { +func home(cmd *cobra.Command, args []string) { cmd.Printf(os.ExpandEnv(helmHome) + "\n") } diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 5155c6724..0d08dcc19 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -32,16 +32,16 @@ var initCmd = &cobra.Command{ Use: "init", Short: "Initialize Helm on both client and server.", Long: installDesc, - RunE: RunInit, + RunE: runInit, } -// RunInit initializes local config and installs tiller to Kubernetes Cluster -func RunInit(cmd *cobra.Command, args []string) error { +// runInit initializes local config and installs tiller to Kubernetes Cluster +func runInit(cmd *cobra.Command, args []string) error { if len(args) != 0 { return errors.New("This command does not accept arguments. \n") } - if err := EnsureHome(os.ExpandEnv(helmHome)); err != nil { + if err := ensureHome(os.ExpandEnv(helmHome)); err != nil { return err } @@ -75,11 +75,11 @@ func buildKubectlRunner(kubectlPath string) kubectl.Runner { return &kubectl.RealRunner{} } -// EnsureHome checks to see if $HELM_HOME exists +// ensureHome checks to see if $HELM_HOME exists // // If $HELM_HOME does not exist, this function will create it. -func EnsureHome(home string) error { - configDirectories := []string{home, CacheDirectory(home)} +func ensureHome(home string) error { + configDirectories := []string{home, cacheDirectory(home)} for _, p := range configDirectories { if fi, err := os.Stat(p); err != nil { @@ -88,26 +88,26 @@ func EnsureHome(home string) error { return fmt.Errorf("Could not create %s: %s", p, err) } } else if !fi.IsDir() { - return fmt.Errorf("%s must be a directory.", p) + return fmt.Errorf("%s must be a directory", p) } } - repoPath := RepositoriesFile(home) + repoPath := repositoriesFile(home) if fi, err := os.Stat(repoPath); err != nil { fmt.Printf("Creating %s \n", repoPath) if err := ioutil.WriteFile(repoPath, []byte("test-charts: https://www.googleapis.com/storage/v1/b/test-charts/o\n"), 0644); err != nil { return err } } else if fi.IsDir() { - return fmt.Errorf("%s must be a file, not a directory.", repoPath) + return fmt.Errorf("%s must be a file, not a directory", repoPath) } return nil } -func CacheDirectory(home string) string { +func cacheDirectory(home string) string { return filepath.Join(home, cachePath) } -func RepositoriesFile(home string) string { +func repositoriesFile(home string) string { return filepath.Join(home, repositoriesPath) } diff --git a/cmd/tiller/tiller.go b/cmd/tiller/tiller.go index 6e420d1ef..d53d220ba 100644 --- a/cmd/tiller/tiller.go +++ b/cmd/tiller/tiller.go @@ -13,7 +13,7 @@ import ( // rootServer is the root gRPC server. // // Each gRPC service registers itself to this server during init(). -var rootServer *grpc.Server = grpc.NewServer() +var rootServer = grpc.NewServer() var env = environment.New() const globalUsage = `The Kubernetes Helm server. diff --git a/pkg/chart/values.go b/pkg/chart/values.go index 8c1c1200d..20a712dc3 100644 --- a/pkg/chart/values.go +++ b/pkg/chart/values.go @@ -8,8 +8,10 @@ import ( "github.com/BurntSushi/toml" ) +// ErrNoTable indicates that a chart does not have a matching table. var ErrNoTable = errors.New("no table") +// Values represents a collection of chart values. type Values map[string]interface{} // Table gets a table (TOML subsection) from a Values object. diff --git a/pkg/storage/memory.go b/pkg/storage/memory.go index 55ce53366..28bf62495 100644 --- a/pkg/storage/memory.go +++ b/pkg/storage/memory.go @@ -11,12 +11,14 @@ type Memory struct { releases map[string]*release.Release } +// NewMemory creates a new in-memory storage. func NewMemory() *Memory { return &Memory{ releases: map[string]*release.Release{}, } } +// ErrNotFound indicates that a release is not found. var ErrNotFound = errors.New("release not found") // Read returns the named Release. @@ -36,12 +38,10 @@ func (m *Memory) Create(rel *release.Release) error { return nil } -var ErrNoRelease = errors.New("no release found") - // Update sets a release. func (m *Memory) Update(rel *release.Release) error { if _, ok := m.releases[rel.Name]; !ok { - return ErrNoRelease + return ErrNotFound } // FIXME: When Release is done, we need to do this right by marking the old @@ -50,16 +50,17 @@ func (m *Memory) Update(rel *release.Release) error { return nil } +// Delete removes a release. func (m *Memory) Delete(name string) (*release.Release, error) { rel, ok := m.releases[name] if !ok { - return nil, ErrNoRelease + return nil, ErrNotFound } delete(m.releases, name) return rel, nil } -// List returns all releases +// List returns all releases. func (m *Memory) List() ([]*release.Release, error) { buf := make([]*release.Release, len(m.releases)) i := 0 @@ -69,6 +70,8 @@ func (m *Memory) List() ([]*release.Release, error) { } return buf, nil } + +// Query searches all releases for matches. func (m *Memory) Query(labels map[string]string) ([]*release.Release, error) { return []*release.Release{}, errors.New("Cannot implement until release.Release is defined.") } From b708bde7299dfbf71c39db787410acf76b1ae101 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 20 Apr 2016 17:03:09 -0600 Subject: [PATCH 52/86] fix(*): remove unusted packages --- cmd/helm/init_test.go | 10 +-- pkg/hapi/doc.go | 10 --- pkg/hapi/helm.pb.go | 176 ------------------------------------------ 3 files changed, 5 insertions(+), 191 deletions(-) delete mode 100644 pkg/hapi/doc.go delete mode 100644 pkg/hapi/helm.pb.go diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index 31d75bc8e..f7935de3d 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -7,12 +7,12 @@ import ( ) func TestEnsureHome(t *testing.T) { - home := CreateTmpHome() - if err := EnsureHome(home); err != nil { + home := createTmpHome() + if err := ensureHome(home); err != nil { t.Errorf("%s", err) } - dirs := []string{home, CacheDirectory(home)} + dirs := []string{home, cacheDirectory(home)} for _, dir := range dirs { if fi, err := os.Stat(dir); err != nil { t.Errorf("%s", err) @@ -21,7 +21,7 @@ func TestEnsureHome(t *testing.T) { } } - if fi, err := os.Stat(RepositoriesFile(home)); err != nil { + if fi, err := os.Stat(repositoriesFile(home)); err != nil { t.Errorf("%s", err) } else if fi.IsDir() { t.Errorf("%s should not be a directory", fi) @@ -29,7 +29,7 @@ func TestEnsureHome(t *testing.T) { } -func CreateTmpHome() string { +func createTmpHome() string { tmpHome, _ := ioutil.TempDir("", "helm_home") defer os.Remove(tmpHome) return tmpHome diff --git a/pkg/hapi/doc.go b/pkg/hapi/doc.go deleted file mode 100644 index 9f0ff7828..000000000 --- a/pkg/hapi/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:generate protoc -I ../../_proto ../../_proto/helm.proto --go_out=plugins=grpc:. -/*Package hapi contains the Helm API (HAPI). - -Helm uses gRPC to handle communication between client and server. This package -contains the definitions of the API objeccts. - -The files in this package that end with the extension *.pb.go are automatically -generated by Protobuf for use with gRPC. -*/ -package hapi diff --git a/pkg/hapi/helm.pb.go b/pkg/hapi/helm.pb.go deleted file mode 100644 index f8dd39fff..000000000 --- a/pkg/hapi/helm.pb.go +++ /dev/null @@ -1,176 +0,0 @@ -// Code generated by protoc-gen-go. -// source: helm.proto -// DO NOT EDIT! - -/* -Package hapi is a generated protocol buffer package. - -hapi: The Helm API - -It is generated from these files: - helm.proto - -It has these top-level messages: - PingRequest - PingResponse - Chart - Values - Release -*/ -package hapi - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -import ( - context "golang.org/x/net/context" - grpc "google.golang.org/grpc" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 - -// The readiness test request. -type PingRequest struct { - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` -} - -func (m *PingRequest) Reset() { *m = PingRequest{} } -func (m *PingRequest) String() string { return proto.CompactTextString(m) } -func (*PingRequest) ProtoMessage() {} -func (*PingRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } - -// The readiness test response. -type PingResponse struct { - Status string `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` -} - -func (m *PingResponse) Reset() { *m = PingResponse{} } -func (m *PingResponse) String() string { return proto.CompactTextString(m) } -func (*PingResponse) ProtoMessage() {} -func (*PingResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } - -type Chart struct { - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` -} - -func (m *Chart) Reset() { *m = Chart{} } -func (m *Chart) String() string { return proto.CompactTextString(m) } -func (*Chart) ProtoMessage() {} -func (*Chart) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } - -type Values struct { - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` -} - -func (m *Values) Reset() { *m = Values{} } -func (m *Values) String() string { return proto.CompactTextString(m) } -func (*Values) ProtoMessage() {} -func (*Values) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } - -type Release struct { - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` -} - -func (m *Release) Reset() { *m = Release{} } -func (m *Release) String() string { return proto.CompactTextString(m) } -func (*Release) ProtoMessage() {} -func (*Release) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } - -func init() { - proto.RegisterType((*PingRequest)(nil), "hapi.PingRequest") - proto.RegisterType((*PingResponse)(nil), "hapi.PingResponse") - proto.RegisterType((*Chart)(nil), "hapi.Chart") - proto.RegisterType((*Values)(nil), "hapi.Values") - proto.RegisterType((*Release)(nil), "hapi.Release") -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion1 - -// Client API for Probe service - -type ProbeClient interface { - // Run a readiness test. - Ready(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) -} - -type probeClient struct { - cc *grpc.ClientConn -} - -func NewProbeClient(cc *grpc.ClientConn) ProbeClient { - return &probeClient{cc} -} - -func (c *probeClient) Ready(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) { - out := new(PingResponse) - err := grpc.Invoke(ctx, "/hapi.Probe/Ready", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// Server API for Probe service - -type ProbeServer interface { - // Run a readiness test. - Ready(context.Context, *PingRequest) (*PingResponse, error) -} - -func RegisterProbeServer(s *grpc.Server, srv ProbeServer) { - s.RegisterService(&_Probe_serviceDesc, srv) -} - -func _Probe_Ready_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(PingRequest) - if err := dec(in); err != nil { - return nil, err - } - out, err := srv.(ProbeServer).Ready(ctx, in) - if err != nil { - return nil, err - } - return out, nil -} - -var _Probe_serviceDesc = grpc.ServiceDesc{ - ServiceName: "hapi.Probe", - HandlerType: (*ProbeServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Ready", - Handler: _Probe_Ready_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, -} - -var fileDescriptor0 = []byte{ - // 182 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xca, 0x48, 0xcd, 0xc9, - 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9, 0x48, 0x2c, 0xc8, 0x54, 0x52, 0xe4, 0xe2, - 0x0e, 0xc8, 0xcc, 0x4b, 0x0f, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x12, 0xe2, 0x62, 0xc9, - 0x4b, 0xcc, 0x4d, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x95, 0xd4, 0xb8, 0x78, - 0x20, 0x4a, 0x8a, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x85, 0xc4, 0xb8, 0xd8, 0x8a, 0x4b, 0x12, 0x4b, - 0x4a, 0x8b, 0xa1, 0xaa, 0xa0, 0x3c, 0x25, 0x69, 0x2e, 0x56, 0xe7, 0x8c, 0xc4, 0x22, 0xec, 0x86, - 0xc8, 0x70, 0xb1, 0x85, 0x25, 0xe6, 0x00, 0xed, 0xc0, 0x2a, 0x2b, 0xcb, 0xc5, 0x1e, 0x94, 0x9a, - 0x93, 0x9a, 0x08, 0x34, 0x1d, 0x8b, 0xb4, 0x91, 0x25, 0x17, 0x6b, 0x40, 0x51, 0x7e, 0x52, 0xaa, - 0x90, 0x01, 0x17, 0x6b, 0x50, 0x6a, 0x62, 0x4a, 0xa5, 0x90, 0xa0, 0x1e, 0xc8, 0xf5, 0x7a, 0x48, - 0x4e, 0x97, 0x12, 0x42, 0x16, 0x82, 0x38, 0x55, 0x89, 0xc1, 0x89, 0x93, 0x8b, 0xbd, 0x38, 0x43, - 0x0f, 0xe4, 0xed, 0x24, 0x36, 0xb0, 0xbf, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x1e, 0x2f, - 0xf3, 0xed, 0x05, 0x01, 0x00, 0x00, -} From 6077968341b10a8929c243a8ae6f604ea5b5fbf0 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Thu, 21 Apr 2016 14:50:16 -0600 Subject: [PATCH 53/86] feat(engine): add recursive template resolution --- pkg/engine/engine.go | 19 +++++++++++--- pkg/engine/engine_test.go | 54 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index c19caab58..77b25140d 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -66,10 +66,7 @@ func (e *Engine) Render(chrt *chart.Chart, vals *chart.Config) (map[string]strin } // Render the charts - tmap := make(map[string]string, len(chrt.Templates)) - for _, tpl := range chrt.Templates { - tmap[tpl.Name] = string(tpl.Data) - } + tmap := allTemplates(chrt) return e.render(tmap, cvals) } @@ -103,3 +100,17 @@ func (e *Engine) render(tpls map[string]string, v interface{}) (map[string]strin return rendered, nil } + +// allTemplates returns all templates for a chart and its dependencies. +func allTemplates(c *chart.Chart) map[string]string { + templates := map[string]string{} + for _, child := range c.Dependencies { + for _, t := range child.Templates { + templates[t.Name] = string(t.Data) + } + } + for _, t := range c.Templates { + templates[t.Name] = string(t.Data) + } + return templates +} diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index dba3b2fe3..006a91bcd 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -4,6 +4,8 @@ import ( "fmt" "sync" "testing" + + "github.com/deis/tiller/pkg/proto/hapi/chart" ) func TestEngine(t *testing.T) { @@ -80,3 +82,55 @@ func TestParallelRenderInternals(t *testing.T) { } wg.Wait() } + +func TestAllTemplates(t *testing.T) { + ch1 := &chart.Chart{ + Templates: []*chart.Template{ + {Name: "foo", Data: []byte("foo")}, + {Name: "bar", Data: []byte("bar")}, + }, + Dependencies: []*chart.Chart{ + {Templates: []*chart.Template{ + {Name: "pinky", Data: []byte("pinky")}, + {Name: "brain", Data: []byte("brain")}, + }}, + }, + } + + tpls := allTemplates(ch1) + if len(tpls) != 4 { + t.Errorf("Expected 4 charts, got %d", len(tpls)) + } +} + +func TestRenderDependency(t *testing.T) { + e := New() + deptpl := `{{define "myblock"}}World{{end}}` + toptpl := `Hello {{template "myblock"}}` + ch := &chart.Chart{ + Templates: []*chart.Template{ + {Name: "outer", Data: []byte(toptpl)}, + }, + Dependencies: []*chart.Chart{ + {Templates: []*chart.Template{ + {Name: "inner", Data: []byte(deptpl)}, + }}, + }, + } + + out, err := e.Render(ch, nil) + + if err != nil { + t.Fatalf("failed to render chart: %s", err) + } + + if len(out) != 2 { + t.Errorf("Expected 2, got %d", len(out)) + } + + expect := "Hello World" + if out["outer"] != expect { + t.Errorf("Expected %q, got %q", expect, out["outer"]) + } + +} From 07312c68e917854ce2f767c06c8b502ea7861c36 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Thu, 21 Apr 2016 15:52:16 -0600 Subject: [PATCH 54/86] fix(engine): support chart template recursion --- pkg/engine/engine.go | 11 +++++++---- pkg/engine/engine_test.go | 27 ++++++++++++++++++--------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 77b25140d..4d3c8b315 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -104,13 +104,16 @@ func (e *Engine) render(tpls map[string]string, v interface{}) (map[string]strin // allTemplates returns all templates for a chart and its dependencies. func allTemplates(c *chart.Chart) map[string]string { templates := map[string]string{} + recAllTpls(c, templates) + return templates +} + +func recAllTpls(c *chart.Chart, templates map[string]string) { for _, child := range c.Dependencies { - for _, t := range child.Templates { - templates[t.Name] = string(t.Data) - } + recAllTpls(child, templates) } for _, t := range c.Templates { templates[t.Name] = string(t.Data) } - return templates + } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 006a91bcd..36992a1f2 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -90,16 +90,23 @@ func TestAllTemplates(t *testing.T) { {Name: "bar", Data: []byte("bar")}, }, Dependencies: []*chart.Chart{ - {Templates: []*chart.Template{ - {Name: "pinky", Data: []byte("pinky")}, - {Name: "brain", Data: []byte("brain")}, - }}, + { + Templates: []*chart.Template{ + {Name: "pinky", Data: []byte("pinky")}, + {Name: "brain", Data: []byte("brain")}, + }, + Dependencies: []*chart.Chart{ + {Templates: []*chart.Template{ + {Name: "innermost", Data: []byte("innermost")}, + }}, + }, + }, }, } tpls := allTemplates(ch1) - if len(tpls) != 4 { - t.Errorf("Expected 4 charts, got %d", len(tpls)) + if len(tpls) != 5 { + t.Errorf("Expected 5 charts, got %d", len(tpls)) } } @@ -112,9 +119,11 @@ func TestRenderDependency(t *testing.T) { {Name: "outer", Data: []byte(toptpl)}, }, Dependencies: []*chart.Chart{ - {Templates: []*chart.Template{ - {Name: "inner", Data: []byte(deptpl)}, - }}, + { + Templates: []*chart.Template{ + {Name: "inner", Data: []byte(deptpl)}, + }, + }, }, } From 7bc50a5d7913fd063728699b02336d68137faadd Mon Sep 17 00:00:00 2001 From: Brian Date: Wed, 20 Apr 2016 19:05:12 -0600 Subject: [PATCH 55/86] feat(tiller): add initial tiller client for basic helm installs. 1. install command loads chart archive. 2. invokes helm api to transform the pkg/chart.Chart type to it's proto model. 3. the client then establishes a connection to tiller. 4. sends InstallReleaseRequest, receives InstallReleaseResponse. todo (for complete install): - walk pkg/chart.{Values,Deps,Templates} types and populate proto definitions for various apis/messages. --- _proto/hapi/chart/chart.proto | 4 +- _proto/hapi/chart/config.proto | 11 ++ _proto/hapi/chart/metadata.proto | 2 +- cmd/helm/init.go | 2 +- cmd/helm/install.go | 65 ++++++++++ pkg/chart/chart.go | 5 + pkg/hapi/doc.go | 10 -- pkg/hapi/helm.pb.go | 176 ---------------------------- pkg/helm/client.go | 34 ++++++ pkg/helm/config.go | 28 +++++ pkg/helm/error.go | 15 +++ pkg/helm/helm.go | 120 +++++++++++++++++++ pkg/proto/hapi/chart/chart.pb.go | 1 + pkg/proto/hapi/chart/config.pb.go | 41 ++++++- pkg/proto/hapi/chart/metadata.pb.go | 30 ++--- 15 files changed, 333 insertions(+), 211 deletions(-) create mode 100644 cmd/helm/install.go delete mode 100644 pkg/hapi/doc.go delete mode 100644 pkg/hapi/helm.pb.go create mode 100644 pkg/helm/client.go create mode 100644 pkg/helm/config.go create mode 100644 pkg/helm/error.go create mode 100644 pkg/helm/helm.go diff --git a/_proto/hapi/chart/chart.proto b/_proto/hapi/chart/chart.proto index 4529b440f..90e4938cc 100644 --- a/_proto/hapi/chart/chart.proto +++ b/_proto/hapi/chart/chart.proto @@ -23,7 +23,7 @@ message Chart { // Charts that this chart depends on. repeated Chart dependencies = 3; - // Default config for this template. - hapi.chart.Config values = 4; + // Default config for this template. + hapi.chart.Config values = 4; } diff --git a/_proto/hapi/chart/config.proto b/_proto/hapi/chart/config.proto index 541564268..0829632ac 100644 --- a/_proto/hapi/chart/config.proto +++ b/_proto/hapi/chart/config.proto @@ -11,4 +11,15 @@ option go_package = "chart"; // message Config { string raw = 1; + + map values = 2; +} + +// +// Value: +// +// TODO +// +message Value { + string value = 1; } diff --git a/_proto/hapi/chart/metadata.proto b/_proto/hapi/chart/metadata.proto index 3068aa452..08e03985d 100644 --- a/_proto/hapi/chart/metadata.proto +++ b/_proto/hapi/chart/metadata.proto @@ -33,7 +33,7 @@ message Metadata { string home = 2; // Source is the URL to the source code of this chart - string source = 3; + repeated string sources = 3; // A SemVer 2 conformant version string of the chart string version = 4; diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 5155c6724..46761cc90 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -12,7 +12,7 @@ import ( "github.com/spf13/cobra" ) -const installDesc = ` +const initDesc = ` This command installs Tiller (the helm server side component) onto your Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.helm/) ` diff --git a/cmd/helm/install.go b/cmd/helm/install.go new file mode 100644 index 000000000..073edfd97 --- /dev/null +++ b/cmd/helm/install.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + + "github.com/deis/tiller/pkg/chart" + "github.com/deis/tiller/pkg/helm" +) + +const installDesc = ` +This command installs a chart archive. +` + +func init() { + RootCommand.Flags() + RootCommand.AddCommand(installCmd) +} + +var installCmd = &cobra.Command{ + Use: "install [CHART]", + Short: "install a chart archive.", + Long: installDesc, + RunE: runInstall, +} + +func runInstall(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("This command needs at least one argument, the name of the chart.") + } + + ch, err := loadChart(args[0]) + if err != nil { + return err + } + + res, err := helm.InstallRelease(ch) + if err != nil { + return err + } + + fmt.Printf("release.name: %s\n", res.Release.Name) + fmt.Printf("release.chart: %s\n", res.Release.Chart.Metadata.Name) + fmt.Printf("release.status: %s\n", res.Release.Info.Status.Code) + + return nil +} + +func loadChart(path string) (*chart.Chart, error) { + path, err := filepath.Abs(path) + if err != nil { + return nil, err + } + + if fi, err := os.Stat(path); err != nil { + return nil, err + } else if fi.IsDir() { + return chart.LoadDir(path) + } + + return chart.Load(path) +} diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go index 964f8490b..73b948e26 100644 --- a/pkg/chart/chart.go +++ b/pkg/chart/chart.go @@ -93,6 +93,11 @@ func (c *Chart) ChartsDir() string { return filepath.Join(c.loader.dir(), preCharts) } +// LoadValues loads the contents of values.toml into a map +func (c *Chart) LoadValues() (Values, error) { + return ReadValuesFile(filepath.Join(c.loader.dir(), preValues)) +} + // chartLoader provides load, close, and save implementations for a chart. type chartLoader interface { // Chartfile resturns a *Chartfile for this chart. diff --git a/pkg/hapi/doc.go b/pkg/hapi/doc.go deleted file mode 100644 index 9f0ff7828..000000000 --- a/pkg/hapi/doc.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:generate protoc -I ../../_proto ../../_proto/helm.proto --go_out=plugins=grpc:. -/*Package hapi contains the Helm API (HAPI). - -Helm uses gRPC to handle communication between client and server. This package -contains the definitions of the API objeccts. - -The files in this package that end with the extension *.pb.go are automatically -generated by Protobuf for use with gRPC. -*/ -package hapi diff --git a/pkg/hapi/helm.pb.go b/pkg/hapi/helm.pb.go deleted file mode 100644 index f8dd39fff..000000000 --- a/pkg/hapi/helm.pb.go +++ /dev/null @@ -1,176 +0,0 @@ -// Code generated by protoc-gen-go. -// source: helm.proto -// DO NOT EDIT! - -/* -Package hapi is a generated protocol buffer package. - -hapi: The Helm API - -It is generated from these files: - helm.proto - -It has these top-level messages: - PingRequest - PingResponse - Chart - Values - Release -*/ -package hapi - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -import ( - context "golang.org/x/net/context" - grpc "google.golang.org/grpc" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 - -// The readiness test request. -type PingRequest struct { - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` -} - -func (m *PingRequest) Reset() { *m = PingRequest{} } -func (m *PingRequest) String() string { return proto.CompactTextString(m) } -func (*PingRequest) ProtoMessage() {} -func (*PingRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } - -// The readiness test response. -type PingResponse struct { - Status string `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` -} - -func (m *PingResponse) Reset() { *m = PingResponse{} } -func (m *PingResponse) String() string { return proto.CompactTextString(m) } -func (*PingResponse) ProtoMessage() {} -func (*PingResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } - -type Chart struct { - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` -} - -func (m *Chart) Reset() { *m = Chart{} } -func (m *Chart) String() string { return proto.CompactTextString(m) } -func (*Chart) ProtoMessage() {} -func (*Chart) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } - -type Values struct { - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` -} - -func (m *Values) Reset() { *m = Values{} } -func (m *Values) String() string { return proto.CompactTextString(m) } -func (*Values) ProtoMessage() {} -func (*Values) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } - -type Release struct { - Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` -} - -func (m *Release) Reset() { *m = Release{} } -func (m *Release) String() string { return proto.CompactTextString(m) } -func (*Release) ProtoMessage() {} -func (*Release) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } - -func init() { - proto.RegisterType((*PingRequest)(nil), "hapi.PingRequest") - proto.RegisterType((*PingResponse)(nil), "hapi.PingResponse") - proto.RegisterType((*Chart)(nil), "hapi.Chart") - proto.RegisterType((*Values)(nil), "hapi.Values") - proto.RegisterType((*Release)(nil), "hapi.Release") -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion1 - -// Client API for Probe service - -type ProbeClient interface { - // Run a readiness test. - Ready(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) -} - -type probeClient struct { - cc *grpc.ClientConn -} - -func NewProbeClient(cc *grpc.ClientConn) ProbeClient { - return &probeClient{cc} -} - -func (c *probeClient) Ready(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) { - out := new(PingResponse) - err := grpc.Invoke(ctx, "/hapi.Probe/Ready", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// Server API for Probe service - -type ProbeServer interface { - // Run a readiness test. - Ready(context.Context, *PingRequest) (*PingResponse, error) -} - -func RegisterProbeServer(s *grpc.Server, srv ProbeServer) { - s.RegisterService(&_Probe_serviceDesc, srv) -} - -func _Probe_Ready_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(PingRequest) - if err := dec(in); err != nil { - return nil, err - } - out, err := srv.(ProbeServer).Ready(ctx, in) - if err != nil { - return nil, err - } - return out, nil -} - -var _Probe_serviceDesc = grpc.ServiceDesc{ - ServiceName: "hapi.Probe", - HandlerType: (*ProbeServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Ready", - Handler: _Probe_Ready_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, -} - -var fileDescriptor0 = []byte{ - // 182 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xca, 0x48, 0xcd, 0xc9, - 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9, 0x48, 0x2c, 0xc8, 0x54, 0x52, 0xe4, 0xe2, - 0x0e, 0xc8, 0xcc, 0x4b, 0x0f, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x11, 0x12, 0xe2, 0x62, 0xc9, - 0x4b, 0xcc, 0x4d, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, 0x95, 0xd4, 0xb8, 0x78, - 0x20, 0x4a, 0x8a, 0x0b, 0xf2, 0xf3, 0x8a, 0x53, 0x85, 0xc4, 0xb8, 0xd8, 0x8a, 0x4b, 0x12, 0x4b, - 0x4a, 0x8b, 0xa1, 0xaa, 0xa0, 0x3c, 0x25, 0x69, 0x2e, 0x56, 0xe7, 0x8c, 0xc4, 0x22, 0xec, 0x86, - 0xc8, 0x70, 0xb1, 0x85, 0x25, 0xe6, 0x00, 0xed, 0xc0, 0x2a, 0x2b, 0xcb, 0xc5, 0x1e, 0x94, 0x9a, - 0x93, 0x9a, 0x08, 0x34, 0x1d, 0x8b, 0xb4, 0x91, 0x25, 0x17, 0x6b, 0x40, 0x51, 0x7e, 0x52, 0xaa, - 0x90, 0x01, 0x17, 0x6b, 0x50, 0x6a, 0x62, 0x4a, 0xa5, 0x90, 0xa0, 0x1e, 0xc8, 0xf5, 0x7a, 0x48, - 0x4e, 0x97, 0x12, 0x42, 0x16, 0x82, 0x38, 0x55, 0x89, 0xc1, 0x89, 0x93, 0x8b, 0xbd, 0x38, 0x43, - 0x0f, 0xe4, 0xed, 0x24, 0x36, 0xb0, 0xbf, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x1e, 0x2f, - 0xf3, 0xed, 0x05, 0x01, 0x00, 0x00, -} diff --git a/pkg/helm/client.go b/pkg/helm/client.go new file mode 100644 index 000000000..a68a1efb4 --- /dev/null +++ b/pkg/helm/client.go @@ -0,0 +1,34 @@ +package helm + +import ( + "golang.org/x/net/context" + "google.golang.org/grpc" + + "github.com/deis/tiller/pkg/proto/hapi/services" +) + +type client struct { + cfg *config + conn *grpc.ClientConn + impl services.ReleaseServiceClient +} + +func (c *client) dial() (err error) { + c.conn, err = grpc.Dial(c.cfg.ServAddr, c.cfg.DialOpts()...) + c.impl = services.NewReleaseServiceClient(c.conn) + return +} + +func (c *client) install(req *services.InstallReleaseRequest) (res *services.InstallReleaseResponse, err error) { + if err = c.dial(); err != nil { + return + } + + defer c.Close() + + return c.impl.InstallRelease(context.TODO(), req, c.cfg.CallOpts()...) +} + +func (c *client) Close() error { + return c.conn.Close() +} diff --git a/pkg/helm/config.go b/pkg/helm/config.go new file mode 100644 index 000000000..0e515a112 --- /dev/null +++ b/pkg/helm/config.go @@ -0,0 +1,28 @@ +package helm + +import ( + "google.golang.org/grpc" +) + +type config struct { + ServAddr string + Insecure bool +} + +func (cfg *config) DialOpts() (opts []grpc.DialOption) { + if cfg.Insecure { + opts = append(opts, grpc.WithInsecure()) + } else { + // TODO: handle transport credentials + } + + return +} + +func (cfg *config) CallOpts() (opts []grpc.CallOption) { + return +} + +func (cfg *config) client() *client { + return &client{cfg: cfg} +} diff --git a/pkg/helm/error.go b/pkg/helm/error.go new file mode 100644 index 000000000..8f5a02499 --- /dev/null +++ b/pkg/helm/error.go @@ -0,0 +1,15 @@ +package helm + +const ( + errNotImplemented = Error("helm api not implemented") + errMissingSrvAddr = Error("missing tiller address") + errMissingTpls = Error("missing chart templates") + errMissingChart = Error("missing chart metadata") + errMissingValues = Error("missing chart values") +) + +type Error string + +func (e Error) Error() string { + return string(e) +} diff --git a/pkg/helm/helm.go b/pkg/helm/helm.go new file mode 100644 index 000000000..7e535c1d2 --- /dev/null +++ b/pkg/helm/helm.go @@ -0,0 +1,120 @@ +package helm + +import ( + "github.com/deis/tiller/pkg/chart" + chartpb "github.com/deis/tiller/pkg/proto/hapi/chart" + "github.com/deis/tiller/pkg/proto/hapi/services" +) + +var Config = &config{ + ServAddr: ":44134", + Insecure: true, +} + +func ListReleases(limit, offset int) (<-chan *services.ListReleasesResponse, error) { + return nil, errNotImplemented +} + +func GetReleaseStatus(name string) (*services.GetReleaseStatusResponse, error) { + return nil, errNotImplemented +} + +func GetReleaseContent(name string) (*services.GetReleaseContentResponse, error) { + return nil, errNotImplemented +} + +func UpdateRelease(name string) (*services.UpdateReleaseResponse, error) { + return nil, errNotImplemented +} + +func UninstallRelease(name string) (*services.UninstallReleaseResponse, error) { + return nil, errNotImplemented +} + +func InstallRelease(ch *chart.Chart) (res *services.InstallReleaseResponse, err error) { + chpb := new(chartpb.Chart) + + chpb.Metadata, err = mkProtoMetadata(ch.Chartfile()) + if err != nil { + return + } + + chpb.Templates, err = mkProtoTemplates(ch) + if err != nil { + return + } + + chpb.Dependencies, err = mkProtoChartDeps(ch) + if err != nil { + return + } + + var vals *chartpb.Config + + vals, err = mkProtoConfigValues(ch) + if err != nil { + return + } + + res, err = Config.client().install(&services.InstallReleaseRequest{ + Chart: chpb, + Values: vals, + }) + + return +} + +// pkg/chart to proto/hapi/chart helpers. temporary. +func mkProtoMetadata(ch *chart.Chartfile) (*chartpb.Metadata, error) { + if ch == nil { + return nil, errMissingChart + } + + md := &chartpb.Metadata{ + Name: ch.Name, + Home: ch.Home, + Version: ch.Version, + Description: ch.Description, + } + + md.Sources = make([]string, len(ch.Source)) + copy(md.Sources, ch.Source) + + md.Keywords = make([]string, len(ch.Keywords)) + copy(md.Keywords, ch.Keywords) + + for _, maintainer := range ch.Maintainers { + md.Maintainers = append(md.Maintainers, &chartpb.Maintainer{ + Name: maintainer.Name, + Email: maintainer.Email, + }) + } + + return md, nil +} + +func mkProtoTemplates(ch *chart.Chart) ([]*chartpb.Template, error) { + tpls, err := ch.LoadTemplates() + if err != nil { + return nil, err + } + + _ = tpls + + return nil, nil +} + +func mkProtoChartDeps(ch *chart.Chart) ([]*chartpb.Chart, error) { + return nil, nil +} + +func mkProtoConfigValues(ch *chart.Chart) (*chartpb.Config, error) { + vals, err := ch.LoadValues() + if err != nil { + return nil, errMissingValues + } + + _ = vals + + return nil, nil +} diff --git a/pkg/proto/hapi/chart/chart.pb.go b/pkg/proto/hapi/chart/chart.pb.go index 81937a65e..ee27d39f5 100644 --- a/pkg/proto/hapi/chart/chart.pb.go +++ b/pkg/proto/hapi/chart/chart.pb.go @@ -14,6 +14,7 @@ It is generated from these files: It has these top-level messages: Chart Config + Value Maintainer Metadata Template diff --git a/pkg/proto/hapi/chart/config.pb.go b/pkg/proto/hapi/chart/config.pb.go index decab3377..e4cd27998 100644 --- a/pkg/proto/hapi/chart/config.pb.go +++ b/pkg/proto/hapi/chart/config.pb.go @@ -19,7 +19,8 @@ var _ = math.Inf // A config supplies values to the parametrizable templates of a chart. // type Config struct { - Raw string `protobuf:"bytes,1,opt,name=raw" json:"raw,omitempty"` + Raw string `protobuf:"bytes,1,opt,name=raw" json:"raw,omitempty"` + Values map[string]*Value `protobuf:"bytes,2,rep,name=values" json:"values,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` } func (m *Config) Reset() { *m = Config{} } @@ -27,16 +28,44 @@ func (m *Config) String() string { return proto.CompactTextString(m) func (*Config) ProtoMessage() {} func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} } +func (m *Config) GetValues() map[string]*Value { + if m != nil { + return m.Values + } + return nil +} + +// +// Value: +// +// TODO +// +type Value struct { + Value string `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"` +} + +func (m *Value) Reset() { *m = Value{} } +func (m *Value) String() string { return proto.CompactTextString(m) } +func (*Value) ProtoMessage() {} +func (*Value) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{1} } + func init() { proto.RegisterType((*Config)(nil), "hapi.chart.Config") + proto.RegisterType((*Value)(nil), "hapi.chart.Value") } var fileDescriptor1 = []byte{ - // 89 bytes of a gzipped FileDescriptorProto + // 179 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcf, 0x48, 0x2c, 0xc8, 0xd4, 0x4f, 0xce, 0x48, 0x2c, 0x2a, 0xd1, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, - 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x02, 0x49, 0xe8, 0x81, 0x25, 0x94, 0xa4, 0xb8, 0xd8, 0x9c, 0xc1, - 0x72, 0x42, 0x02, 0x5c, 0xcc, 0x45, 0x89, 0xe5, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x20, - 0xa6, 0x13, 0x7b, 0x14, 0x2b, 0x58, 0x51, 0x12, 0x1b, 0x58, 0x9f, 0x31, 0x20, 0x00, 0x00, 0xff, - 0xff, 0xfe, 0xa0, 0x78, 0x2a, 0x52, 0x00, 0x00, 0x00, + 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x02, 0x49, 0xe8, 0x81, 0x25, 0x94, 0x16, 0x30, 0x72, 0xb1, 0x39, + 0x83, 0x25, 0x85, 0x04, 0xb8, 0x98, 0x8b, 0x12, 0xcb, 0x25, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, + 0x40, 0x4c, 0x21, 0x33, 0x2e, 0xb6, 0xb2, 0xc4, 0x9c, 0xd2, 0xd4, 0x62, 0x09, 0x26, 0x05, 0x66, + 0x0d, 0x6e, 0x23, 0x39, 0x3d, 0x84, 0x4e, 0x3d, 0x88, 0x2e, 0xbd, 0x30, 0xb0, 0x02, 0xd7, 0xbc, + 0x92, 0xa2, 0xca, 0x20, 0xa8, 0x6a, 0x29, 0x1f, 0x2e, 0x6e, 0x24, 0x61, 0x90, 0xc1, 0xd9, 0xa9, + 0x95, 0x30, 0x83, 0x81, 0x4c, 0x21, 0x75, 0x2e, 0x56, 0xb0, 0x52, 0xa0, 0xb9, 0x8c, 0x40, 0x73, + 0x05, 0x91, 0xcd, 0x05, 0xeb, 0x0c, 0x82, 0xc8, 0x5b, 0x31, 0x59, 0x30, 0x2a, 0xc9, 0x72, 0xb1, + 0x82, 0xc5, 0x84, 0x44, 0x60, 0xba, 0x20, 0x26, 0x41, 0x38, 0x4e, 0xec, 0x51, 0xac, 0x60, 0x8d, + 0x49, 0x6c, 0x60, 0xdf, 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xe1, 0x12, 0x60, 0xda, 0xf8, + 0x00, 0x00, 0x00, } diff --git a/pkg/proto/hapi/chart/metadata.pb.go b/pkg/proto/hapi/chart/metadata.pb.go index 56ee4e021..42f16e125 100644 --- a/pkg/proto/hapi/chart/metadata.pb.go +++ b/pkg/proto/hapi/chart/metadata.pb.go @@ -44,7 +44,7 @@ type Metadata struct { // The URL to a relecant project page, git repo, or contact person Home string `protobuf:"bytes,2,opt,name=home" json:"home,omitempty"` // Source is the URL to the source code of this chart - Source string `protobuf:"bytes,3,opt,name=source" json:"source,omitempty"` + Sources []string `protobuf:"bytes,3,rep,name=sources" json:"sources,omitempty"` // A SemVer 2 conformant version string of the chart Version string `protobuf:"bytes,4,opt,name=version" json:"version,omitempty"` // A one-sentence description of the chart @@ -74,18 +74,18 @@ func init() { var fileDescriptor2 = []byte{ // 224 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x90, 0xb1, 0x4e, 0xc3, 0x30, - 0x10, 0x86, 0x15, 0xda, 0x24, 0xe5, 0xb2, 0x9d, 0x50, 0x65, 0x98, 0xa2, 0x4e, 0x4c, 0xae, 0x04, - 0x12, 0x62, 0x66, 0xef, 0xd2, 0x91, 0xed, 0x48, 0x4e, 0x8a, 0x05, 0x8e, 0x23, 0xdb, 0x80, 0x78, - 0x57, 0x1e, 0x06, 0xf7, 0x1a, 0x92, 0x0c, 0x1d, 0x22, 0xdd, 0xff, 0x7d, 0x77, 0x91, 0x7e, 0xc3, - 0x6d, 0x47, 0x83, 0xd9, 0x37, 0x1d, 0xf9, 0xb8, 0xb7, 0x1c, 0xa9, 0xa5, 0x48, 0x7a, 0xf0, 0x2e, - 0x3a, 0x84, 0x93, 0xd2, 0xa2, 0x76, 0x4f, 0x00, 0x07, 0x32, 0x7d, 0x4c, 0x1f, 0x7b, 0x44, 0x58, - 0xf7, 0x64, 0x59, 0x65, 0x75, 0x76, 0x7f, 0x7d, 0x94, 0x19, 0x6f, 0x20, 0x67, 0x4b, 0xe6, 0x43, - 0x5d, 0x09, 0x3c, 0x87, 0xdd, 0x6f, 0x06, 0x9b, 0xc3, 0xf8, 0xdb, 0x8b, 0x67, 0x89, 0x75, 0x2e, - 0xb1, 0xf3, 0x95, 0xcc, 0xb8, 0x85, 0x22, 0xb8, 0x4f, 0xdf, 0xb0, 0x5a, 0x09, 0x1d, 0x13, 0x2a, - 0x28, 0xbf, 0xd8, 0x07, 0xe3, 0x7a, 0xb5, 0x16, 0xf1, 0x1f, 0xb1, 0x86, 0xaa, 0xe5, 0xd0, 0x78, - 0x33, 0xc4, 0x93, 0xcd, 0xc5, 0x2e, 0x11, 0xde, 0xc1, 0xe6, 0x9d, 0x7f, 0xbe, 0x9d, 0x6f, 0x83, - 0x2a, 0xea, 0x55, 0xd2, 0x53, 0xc6, 0x67, 0xa8, 0xec, 0x54, 0x2e, 0xa8, 0x32, 0xe9, 0xea, 0x61, - 0xab, 0xe7, 0xfa, 0x7a, 0xee, 0x7e, 0x5c, 0xae, 0xbe, 0x94, 0xaf, 0xb9, 0x2c, 0xbc, 0x15, 0xf2, - 0x64, 0x8f, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x23, 0x79, 0xfc, 0xf8, 0x4f, 0x01, 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x90, 0x3f, 0x4f, 0xc4, 0x30, + 0x0c, 0xc5, 0x55, 0xee, 0x7a, 0x3d, 0xdc, 0xcd, 0x42, 0x28, 0x30, 0x55, 0x37, 0x31, 0xe5, 0x24, + 0x90, 0x10, 0x33, 0xfb, 0x2d, 0x37, 0xb2, 0x99, 0xd6, 0x52, 0x23, 0x48, 0x53, 0x25, 0x01, 0xc4, + 0x97, 0xe5, 0xb3, 0x90, 0xba, 0xf4, 0xcf, 0xc0, 0x60, 0xc9, 0xef, 0xfd, 0xfc, 0x2c, 0xd9, 0x70, + 0xd3, 0x52, 0x6f, 0x8e, 0x75, 0x4b, 0x3e, 0x1e, 0x2d, 0x47, 0x6a, 0x28, 0x92, 0xee, 0xbd, 0x8b, + 0x0e, 0x61, 0x40, 0x5a, 0xd0, 0xe1, 0x11, 0xe0, 0x44, 0xa6, 0x8b, 0xa9, 0xd8, 0x23, 0xc2, 0xb6, + 0x23, 0xcb, 0x2a, 0xab, 0xb2, 0xbb, 0xcb, 0xb3, 0xf4, 0x78, 0x05, 0x39, 0x5b, 0x32, 0xef, 0xea, + 0x42, 0xcc, 0x51, 0x1c, 0x7e, 0x32, 0xd8, 0x9f, 0xfe, 0xd6, 0xfe, 0x1b, 0x4b, 0x5e, 0xeb, 0x92, + 0x37, 0xa6, 0xa4, 0x47, 0x05, 0x45, 0x70, 0x1f, 0xbe, 0xe6, 0xa0, 0x36, 0xd5, 0x26, 0xd9, 0x93, + 0x1c, 0xc8, 0x27, 0xfb, 0x60, 0x5c, 0xa7, 0xb6, 0x12, 0x98, 0x24, 0x56, 0x50, 0x36, 0x1c, 0x6a, + 0x6f, 0xfa, 0x38, 0xd0, 0x5c, 0xe8, 0xda, 0xc2, 0x5b, 0xd8, 0xbf, 0xf1, 0xf7, 0x97, 0xf3, 0x4d, + 0x50, 0x3b, 0x59, 0x3b, 0x6b, 0x7c, 0x82, 0xd2, 0xce, 0xe7, 0x05, 0x55, 0x24, 0x5c, 0xde, 0x5f, + 0xeb, 0xe5, 0x01, 0x7a, 0xb9, 0xfe, 0xbc, 0x1e, 0x7d, 0x2e, 0x5e, 0x72, 0x19, 0x78, 0xdd, 0xc9, + 0xd3, 0x1e, 0x7e, 0x03, 0x00, 0x00, 0xff, 0xff, 0xb9, 0xaf, 0x44, 0xa7, 0x51, 0x01, 0x00, 0x00, } From 00b9356a95e8342645ecc22c541e586cc5644059 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Mon, 18 Apr 2016 20:52:21 -0600 Subject: [PATCH 56/86] feat(helm): add serve command to start local repo --- cmd/helm/serve.go | 29 +++++++++++++++++++++++++++++ pkg/repo/localRepo.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 cmd/helm/serve.go create mode 100644 pkg/repo/localRepo.go diff --git a/cmd/helm/serve.go b/cmd/helm/serve.go new file mode 100644 index 000000000..f26acdfe6 --- /dev/null +++ b/cmd/helm/serve.go @@ -0,0 +1,29 @@ +package main + +import ( + "os" + + "github.com/deis/tiller/pkg/repo" + "github.com/spf13/cobra" +) + +var serveDesc = `This command starts a local chart repository server that serves the charts saved in your $HELM_HOME/local/ directory.` + +//TODO: add repoPath flag to be passed in in case you want +// to serve charts from a different local dir + +func init() { + RootCommand.AddCommand(serveCmd) +} + +var serveCmd = &cobra.Command{ + Use: "serve", + Short: "Start a local http web server", + Long: serveDesc, + Run: ServeLocal, +} + +func ServeLocal(cmd *cobra.Command, args []string) { + localRepoPath := os.ExpandEnv(helmHome) + "/local/" + repo.StartLocalRepo(localRepoPath) +} diff --git a/pkg/repo/localRepo.go b/pkg/repo/localRepo.go new file mode 100644 index 000000000..1014d4673 --- /dev/null +++ b/pkg/repo/localRepo.go @@ -0,0 +1,36 @@ +package repo + +import ( + "fmt" + "net/http" + "strings" +) + +var localRepoPath string + +func StartLocalRepo(path string) { + fmt.Println("Now serving you on localhost:8879...") + localRepoPath = path + http.HandleFunc("/", homeHandler) + http.HandleFunc("/charts/", indexHandler) + http.ListenAndServe(":8879", nil) +} +func homeHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Welcome to the Kubernetes Package manager!\nBrowse charts on localhost:8879/charts!") +} +func indexHandler(w http.ResponseWriter, r *http.Request) { + file := r.URL.Path[len("/charts/"):] + if len(strings.Split(file, ".")) > 1 { + serveFile(w, r, file) + } else if file == "" { + fmt.Fprintf(w, "list of charts should be here at some point") + } else if file == "cache" { + fmt.Fprintf(w, "cache file data should be here at some point") + } else { + fmt.Fprintf(w, "Ummm... Nothing to see here folks") + } +} + +func serveFile(w http.ResponseWriter, r *http.Request, file string) { + http.ServeFile(w, r, localRepoPath+file) +} From fbd6ffa4b9895691677183594513fc35fa6f036c Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Mon, 18 Apr 2016 21:06:49 -0600 Subject: [PATCH 57/86] feat(helm): add local dir in home to store charts --- cmd/helm/init.go | 7 ++++++- cmd/helm/init_test.go | 2 +- cmd/helm/serve.go | 3 +-- pkg/repo/localRepo.go | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 0d08dcc19..167497962 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -19,6 +19,7 @@ Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.he const repositoriesPath = ".repositories" const cachePath = "cache" +const localPath = "local" var defaultRepo = map[string]string{"default-name": "default-url"} var tillerImg string @@ -79,7 +80,7 @@ func buildKubectlRunner(kubectlPath string) kubectl.Runner { // // If $HELM_HOME does not exist, this function will create it. func ensureHome(home string) error { - configDirectories := []string{home, cacheDirectory(home)} + configDirectories := []string{home, CacheDirectory(home), LocalDirectory(home)} for _, p := range configDirectories { if fi, err := os.Stat(p); err != nil { @@ -111,3 +112,7 @@ func cacheDirectory(home string) string { func repositoriesFile(home string) string { return filepath.Join(home, repositoriesPath) } + +func LocalDirectory(home string) string { + return filepath.Join(home, localPath) +} diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index f7935de3d..b7cefe342 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -12,7 +12,7 @@ func TestEnsureHome(t *testing.T) { t.Errorf("%s", err) } - dirs := []string{home, cacheDirectory(home)} + dirs := []string{home, CacheDirectory(home), LocalDirectory(home)} for _, dir := range dirs { if fi, err := os.Stat(dir); err != nil { t.Errorf("%s", err) diff --git a/cmd/helm/serve.go b/cmd/helm/serve.go index f26acdfe6..7e4978dcf 100644 --- a/cmd/helm/serve.go +++ b/cmd/helm/serve.go @@ -24,6 +24,5 @@ var serveCmd = &cobra.Command{ } func ServeLocal(cmd *cobra.Command, args []string) { - localRepoPath := os.ExpandEnv(helmHome) + "/local/" - repo.StartLocalRepo(localRepoPath) + repo.StartLocalRepo(LocalDirectory(os.ExpandEnv(helmHome))) } diff --git a/pkg/repo/localRepo.go b/pkg/repo/localRepo.go index 1014d4673..813ffb80e 100644 --- a/pkg/repo/localRepo.go +++ b/pkg/repo/localRepo.go @@ -3,6 +3,7 @@ package repo import ( "fmt" "net/http" + "path/filepath" "strings" ) @@ -32,5 +33,5 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { } func serveFile(w http.ResponseWriter, r *http.Request, file string) { - http.ServeFile(w, r, localRepoPath+file) + http.ServeFile(w, r, filepath.Join(localRepoPath, file)) } From a5a1ef95461add8e5bf3cd41500fc28ea43932ea Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Mon, 18 Apr 2016 21:59:42 -0600 Subject: [PATCH 58/86] feat(package): save package to $HELM_HOME/local --- cmd/helm/package.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 217e0c018..7f7037de1 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -20,7 +20,10 @@ Chart.yaml file, and (if found) build the current directory into a chart. Versioned chart archives are used by Helm package repositories. ` +var save bool + func init() { + packageCmd.Flags().BoolVar(&save, "save", true, "save packaged chart to local chart repository") RootCommand.AddCommand(packageCmd) } @@ -50,6 +53,17 @@ func runPackage(cmd *cobra.Command, args []string) error { return err } + // Save to $HELM_HOME/local directory. + if save { + dir := LocalDirectory(os.ExpandEnv(helmHome)) + name, err := chart.Save(ch, dir) + if err == nil { + cmd.Printf("Saved %s to $HELM_HOME/local/\n", name) + } else { + return err + } + } + // Save to the current working directory. cwd, err := os.Getwd() if err != nil { @@ -57,7 +71,7 @@ func runPackage(cmd *cobra.Command, args []string) error { } name, err := chart.Save(ch, cwd) if err == nil { - cmd.Printf("Saved %s", name) + cmd.Printf("Saved %s to current directory\n", name) } return err } From f9c06b1da2c864b40d1a34ef5d076cd19c0ff7c3 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Mon, 18 Apr 2016 23:36:13 -0600 Subject: [PATCH 59/86] feat(init): add cache.txt to $HELM_HOME/local --- cmd/helm/init.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 167497962..282d2aaac 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -20,6 +20,7 @@ Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.he const repositoriesPath = ".repositories" const cachePath = "cache" const localPath = "local" +const localCacheFilePath = localPath + "/cache.txt" var defaultRepo = map[string]string{"default-name": "default-url"} var tillerImg string @@ -96,12 +97,25 @@ func ensureHome(home string) error { repoPath := repositoriesFile(home) if fi, err := os.Stat(repoPath); err != nil { fmt.Printf("Creating %s \n", repoPath) - if err := ioutil.WriteFile(repoPath, []byte("test-charts: https://www.googleapis.com/storage/v1/b/test-charts/o\n"), 0644); err != nil { + if err := ioutil.WriteFile(repoPath, []byte("local: localhost:8879/charts"), 0644); err != nil { return err } } else if fi.IsDir() { return fmt.Errorf("%s must be a file, not a directory", repoPath) } + + localCacheFile := LocalDirCacheFile(home) + if fi, err := os.Stat(localCacheFile); err != nil { + fmt.Printf("Creating %s \n", localCacheFile) + _, err := os.Create(localCacheFile) + if err != nil { + return err + } + + os.Symlink(localCacheFile, CacheDirectory(home)+"/local-cache.txt") + } else if fi.IsDir() { + return fmt.Errorf("%s must be a file, not a directory.", repoPath) + } return nil } @@ -116,3 +130,7 @@ func repositoriesFile(home string) string { func LocalDirectory(home string) string { return filepath.Join(home, localPath) } + +func LocalDirCacheFile(home string) string { + return filepath.Join(home, localCacheFilePath) +} From 7842d2457b3c6530cc9f93b95c7e6242da0fdce4 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Tue, 19 Apr 2016 01:28:42 -0600 Subject: [PATCH 60/86] feat(helm): reindex cache file --- cmd/helm/init.go | 2 +- cmd/helm/package.go | 7 +-- pkg/repo/local.go | 128 ++++++++++++++++++++++++++++++++++++++++++ pkg/repo/localRepo.go | 37 ------------ 4 files changed, 131 insertions(+), 43 deletions(-) create mode 100644 pkg/repo/local.go delete mode 100644 pkg/repo/localRepo.go diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 282d2aaac..dbd84c9d0 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -20,7 +20,7 @@ Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.he const repositoriesPath = ".repositories" const cachePath = "cache" const localPath = "local" -const localCacheFilePath = localPath + "/cache.txt" +const localCacheFilePath = localPath + "/cache.yaml" var defaultRepo = map[string]string{"default-name": "default-url"} var tillerImg string diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 7f7037de1..5e49041aa 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -6,6 +6,7 @@ import ( "path/filepath" "github.com/deis/tiller/pkg/chart" + "github.com/deis/tiller/pkg/repo" "github.com/spf13/cobra" ) @@ -55,11 +56,7 @@ func runPackage(cmd *cobra.Command, args []string) error { // Save to $HELM_HOME/local directory. if save { - dir := LocalDirectory(os.ExpandEnv(helmHome)) - name, err := chart.Save(ch, dir) - if err == nil { - cmd.Printf("Saved %s to $HELM_HOME/local/\n", name) - } else { + if err := repo.AddChartToLocalRepo(ch, LocalDirectory(os.ExpandEnv(helmHome))); err != nil { return err } } diff --git a/pkg/repo/local.go b/pkg/repo/local.go new file mode 100644 index 000000000..124c8e75d --- /dev/null +++ b/pkg/repo/local.go @@ -0,0 +1,128 @@ +package repo + +import ( + "fmt" + "io/ioutil" + "net/http" + "path/filepath" + "strings" + + "github.com/deis/tiller/pkg/chart" + "gopkg.in/yaml.v2" +) + +var localRepoPath string + +type CacheFile struct { + Entries map[string]*ChartRef +} + +type ChartRef struct { + Name string `yaml:name` + Url string `yaml:url` +} + +func StartLocalRepo(path string) { + fmt.Println("Now serving you on localhost:8879...") + localRepoPath = path + http.HandleFunc("/", homeHandler) + http.HandleFunc("/charts/", indexHandler) + http.ListenAndServe(":8879", nil) +} +func homeHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Welcome to the Kubernetes Package manager!\nBrowse charts on localhost:8879/charts!") +} +func indexHandler(w http.ResponseWriter, r *http.Request) { + file := r.URL.Path[len("/charts/"):] + if len(strings.Split(file, ".")) > 1 { + serveFile(w, r, file) + } else if file == "" { + fmt.Fprintf(w, "list of charts should be here at some point") + } else if file == "cache" { + fmt.Fprintf(w, "cache file data should be here at some point") + } else { + fmt.Fprintf(w, "Ummm... Nothing to see here folks") + } +} + +func serveFile(w http.ResponseWriter, r *http.Request, file string) { + http.ServeFile(w, r, filepath.Join(localRepoPath, file)) +} + +func AddChartToLocalRepo(ch *chart.Chart, path string) error { + name, err := chart.Save(ch, path) + if err != nil { + return err + } + err = ReindexCacheFile(ch, path+"/cache.yaml") + if err != nil { + return nil + } + fmt.Printf("Saved %s to $HELM_HOME/local", name) + return nil +} + +func ReindexCacheFile(ch *chart.Chart, path string) error { + name := ch.Chartfile().Name + "-" + ch.Chartfile().Version + fmt.Println("\nname: " + name) + b, err := ioutil.ReadFile(path) + + if err != nil { + fmt.Println("read file err") + fmt.Printf("err, %s", err) + return err + } + + var y CacheFile + err = yaml.Unmarshal(b, &y) + if err != nil { + fmt.Println("error unmarshaling") + fmt.Println("err, %s", err) + return err + } + fmt.Println("%v\n", y) + found := false + for k, v := range y.Entries { + fmt.Printf("in here: %v", v) + fmt.Printf("in here: %v", k) + if k == name { + found = true + break + } + } + if !found { + url := "localhost:8879/charts/" + name + ".tgz" + + out, err := y.InsertChartEntry(name, url) + if err != nil { + return err + } + + ioutil.WriteFile(path, out, 0644) + } + return nil +} +func (c *CacheFile) UnmarshalYAML(unmarshal func(interface{}) error) error { + var refs map[string]*ChartRef + if err := unmarshal(&refs); err != nil { + if _, ok := err.(*yaml.TypeError); !ok { + return err + } + } + c.Entries = refs + return nil +} + +func (cache *CacheFile) InsertChartEntry(name string, url string) ([]byte, error) { + if cache.Entries == nil { + cache.Entries = make(map[string]*ChartRef) + } + entry := ChartRef{Name: name, Url: url} + cache.Entries[name] = &entry + out, err := yaml.Marshal(&cache.Entries) + if err != nil { + return nil, err + } + + return out, nil +} diff --git a/pkg/repo/localRepo.go b/pkg/repo/localRepo.go deleted file mode 100644 index 813ffb80e..000000000 --- a/pkg/repo/localRepo.go +++ /dev/null @@ -1,37 +0,0 @@ -package repo - -import ( - "fmt" - "net/http" - "path/filepath" - "strings" -) - -var localRepoPath string - -func StartLocalRepo(path string) { - fmt.Println("Now serving you on localhost:8879...") - localRepoPath = path - http.HandleFunc("/", homeHandler) - http.HandleFunc("/charts/", indexHandler) - http.ListenAndServe(":8879", nil) -} -func homeHandler(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Welcome to the Kubernetes Package manager!\nBrowse charts on localhost:8879/charts!") -} -func indexHandler(w http.ResponseWriter, r *http.Request) { - file := r.URL.Path[len("/charts/"):] - if len(strings.Split(file, ".")) > 1 { - serveFile(w, r, file) - } else if file == "" { - fmt.Fprintf(w, "list of charts should be here at some point") - } else if file == "cache" { - fmt.Fprintf(w, "cache file data should be here at some point") - } else { - fmt.Fprintf(w, "Ummm... Nothing to see here folks") - } -} - -func serveFile(w http.ResponseWriter, r *http.Request, file string) { - http.ServeFile(w, r, filepath.Join(localRepoPath, file)) -} From 2e1701be1153ffb30785469605f6cee669b03a8a Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Tue, 19 Apr 2016 02:34:01 -0600 Subject: [PATCH 61/86] helm(search): add search command --- cmd/helm/search.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++ pkg/repo/local.go | 20 +++++++-------- 2 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 cmd/helm/search.go diff --git a/cmd/helm/search.go b/cmd/helm/search.go new file mode 100644 index 000000000..7551b6fdc --- /dev/null +++ b/cmd/helm/search.go @@ -0,0 +1,61 @@ +package main + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/deis/tiller/pkg/repo" + "github.com/spf13/cobra" +) + +func init() { + RootCommand.AddCommand(searchCmd) +} + +var searchCmd = &cobra.Command{ + Use: "search [CHART]", + Short: "Search for charts", + Long: "", + RunE: Search, +} + +func Search(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("This command needs at least one argument") + } + + results, err := searchCacheForPattern(args[0]) + if err != nil { + return err + } + cmd.Println("Charts:") + for _, result := range results { + fmt.Println(result) + } + return nil +} + +func searchCacheForPattern(name string) ([]string, error) { + dir := CacheDirectory(os.ExpandEnv(helmHome)) + fileList := []string{} + filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { + if !f.IsDir() { + fileList = append(fileList, path) + } + return nil + }) + matches := []string{} + for _, f := range fileList { + cache, _ := repo.LoadCacheFile(f) + repoName := filepath.Base(strings.TrimRight(f, "-cache.txt")) + for k, _ := range cache.Entries { + if strings.Contains(k, name) { + matches = append(matches, repoName+"/"+k) + } + } + } + return matches, nil +} diff --git a/pkg/repo/local.go b/pkg/repo/local.go index 124c8e75d..888f87900 100644 --- a/pkg/repo/local.go +++ b/pkg/repo/local.go @@ -62,15 +62,12 @@ func AddChartToLocalRepo(ch *chart.Chart, path string) error { return nil } -func ReindexCacheFile(ch *chart.Chart, path string) error { - name := ch.Chartfile().Name + "-" + ch.Chartfile().Version - fmt.Println("\nname: " + name) +func LoadCacheFile(path string) (*CacheFile, error) { b, err := ioutil.ReadFile(path) - if err != nil { fmt.Println("read file err") fmt.Printf("err, %s", err) - return err + return nil, err } var y CacheFile @@ -78,13 +75,16 @@ func ReindexCacheFile(ch *chart.Chart, path string) error { if err != nil { fmt.Println("error unmarshaling") fmt.Println("err, %s", err) - return err + return nil, err } - fmt.Println("%v\n", y) + return &y, nil +} + +func ReindexCacheFile(ch *chart.Chart, path string) error { + name := ch.Chartfile().Name + "-" + ch.Chartfile().Version + y, _ := LoadCacheFile(path) //TODO: handle err later found := false - for k, v := range y.Entries { - fmt.Printf("in here: %v", v) - fmt.Printf("in here: %v", k) + for k, _ := range y.Entries { if k == name { found = true break From 9e05956efa2dbf1a7c6094787b18e860fe23ec25 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Tue, 19 Apr 2016 02:48:12 -0600 Subject: [PATCH 62/86] feat(fetch): stub out fetch command --- cmd/helm/fetch.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 cmd/helm/fetch.go diff --git a/cmd/helm/fetch.go b/cmd/helm/fetch.go new file mode 100644 index 000000000..2fe8dd0c5 --- /dev/null +++ b/cmd/helm/fetch.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "os" + + "github.com/spf13/cobra" +) + +func init() { + RootCommand.AddCommand(fetchCmd) +} + +var fetchCmd = &cobra.Command{ + Use: "fetch", + Short: "Download a chart from a repository and unpack it in local directory.", + Long: "", + RunE: Fetch, +} + +func Fetch(cmd *cobra.Command, args []string) error { + // parse args + // get download url + // call download url + out, err := os.Create("nginx-2.0.0.tgz") + if err != nil { + return err + } + defer out.Close() + resp, err := http.Get("http://localhost:8879/charts/nginx-2.0.0.tgz") + fmt.Println("after req") + // unpack file + if err != nil { + return err + } + + defer resp.Body.Close() + + _, err = io.Copy(out, resp.Body) + if err != nil { + return err + } + return nil +} From 39a9eab114b15740ef5186f7be16c5b016376ddb Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Wed, 20 Apr 2016 16:59:56 -0600 Subject: [PATCH 63/86] ref(helm): comment + err cleanup on helm --- cmd/helm/init.go | 5 +++-- cmd/helm/search.go | 4 ++-- pkg/repo/local.go | 22 +++++++++++----------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/cmd/helm/init.go b/cmd/helm/init.go index dbd84c9d0..dc2bd16d0 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -97,7 +97,7 @@ func ensureHome(home string) error { repoPath := repositoriesFile(home) if fi, err := os.Stat(repoPath); err != nil { fmt.Printf("Creating %s \n", repoPath) - if err := ioutil.WriteFile(repoPath, []byte("local: localhost:8879/charts"), 0644); err != nil { + if err := ioutil.WriteFile(repoPath, []byte("local: localhost:8879/charts\n"), 0644); err != nil { return err } } else if fi.IsDir() { @@ -112,7 +112,8 @@ func ensureHome(home string) error { return err } - os.Symlink(localCacheFile, CacheDirectory(home)+"/local-cache.txt") + //TODO: take this out and replace with helm update functionality + os.Symlink(localCacheFile, CacheDirectory(home)+"/local-cache.yaml") } else if fi.IsDir() { return fmt.Errorf("%s must be a file, not a directory.", repoPath) } diff --git a/cmd/helm/search.go b/cmd/helm/search.go index 7551b6fdc..07bd846aa 100644 --- a/cmd/helm/search.go +++ b/cmd/helm/search.go @@ -18,7 +18,7 @@ func init() { var searchCmd = &cobra.Command{ Use: "search [CHART]", Short: "Search for charts", - Long: "", + Long: "", //TODO: add search command description RunE: Search, } @@ -51,7 +51,7 @@ func searchCacheForPattern(name string) ([]string, error) { for _, f := range fileList { cache, _ := repo.LoadCacheFile(f) repoName := filepath.Base(strings.TrimRight(f, "-cache.txt")) - for k, _ := range cache.Entries { + for k := range cache.Entries { if strings.Contains(k, name) { matches = append(matches, repoName+"/"+k) } diff --git a/pkg/repo/local.go b/pkg/repo/local.go index 888f87900..df65bf284 100644 --- a/pkg/repo/local.go +++ b/pkg/repo/local.go @@ -18,18 +18,18 @@ type CacheFile struct { } type ChartRef struct { - Name string `yaml:name` - Url string `yaml:url` + Name string + Url string } func StartLocalRepo(path string) { fmt.Println("Now serving you on localhost:8879...") localRepoPath = path - http.HandleFunc("/", homeHandler) + http.HandleFunc("/", rootHandler) http.HandleFunc("/charts/", indexHandler) http.ListenAndServe(":8879", nil) } -func homeHandler(w http.ResponseWriter, r *http.Request) { +func rootHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Welcome to the Kubernetes Package manager!\nBrowse charts on localhost:8879/charts!") } func indexHandler(w http.ResponseWriter, r *http.Request) { @@ -56,7 +56,7 @@ func AddChartToLocalRepo(ch *chart.Chart, path string) error { } err = ReindexCacheFile(ch, path+"/cache.yaml") if err != nil { - return nil + return err } fmt.Printf("Saved %s to $HELM_HOME/local", name) return nil @@ -65,16 +65,13 @@ func AddChartToLocalRepo(ch *chart.Chart, path string) error { func LoadCacheFile(path string) (*CacheFile, error) { b, err := ioutil.ReadFile(path) if err != nil { - fmt.Println("read file err") - fmt.Printf("err, %s", err) return nil, err } + //TODO: change variable name - y is not helpful :P var y CacheFile err = yaml.Unmarshal(b, &y) if err != nil { - fmt.Println("error unmarshaling") - fmt.Println("err, %s", err) return nil, err } return &y, nil @@ -82,9 +79,12 @@ func LoadCacheFile(path string) (*CacheFile, error) { func ReindexCacheFile(ch *chart.Chart, path string) error { name := ch.Chartfile().Name + "-" + ch.Chartfile().Version - y, _ := LoadCacheFile(path) //TODO: handle err later + y, err := LoadCacheFile(path) + if err != nil { + return err + } found := false - for k, _ := range y.Entries { + for k := range y.Entries { if k == name { found = true break From ae720981d3ac34588ff020d11c2a0cfe8d5ee2bc Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Fri, 22 Apr 2016 11:59:49 -0600 Subject: [PATCH 64/86] ref(repo): fix style errors in cmd/helm & pkg/repo --- cmd/helm/fetch.go | 4 ++-- cmd/helm/init.go | 12 ++++++------ cmd/helm/init_test.go | 2 +- cmd/helm/package.go | 2 +- cmd/helm/search.go | 6 +++--- cmd/helm/serve.go | 6 +++--- pkg/repo/local.go | 24 ++++++++++++++++-------- 7 files changed, 32 insertions(+), 24 deletions(-) diff --git a/cmd/helm/fetch.go b/cmd/helm/fetch.go index 2fe8dd0c5..c14c4f103 100644 --- a/cmd/helm/fetch.go +++ b/cmd/helm/fetch.go @@ -17,10 +17,10 @@ var fetchCmd = &cobra.Command{ Use: "fetch", Short: "Download a chart from a repository and unpack it in local directory.", Long: "", - RunE: Fetch, + RunE: fetch, } -func Fetch(cmd *cobra.Command, args []string) error { +func fetch(cmd *cobra.Command, args []string) error { // parse args // get download url // call download url diff --git a/cmd/helm/init.go b/cmd/helm/init.go index dc2bd16d0..ec6f284fc 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -81,7 +81,7 @@ func buildKubectlRunner(kubectlPath string) kubectl.Runner { // // If $HELM_HOME does not exist, this function will create it. func ensureHome(home string) error { - configDirectories := []string{home, CacheDirectory(home), LocalDirectory(home)} + configDirectories := []string{home, cacheDirectory(home), localDirectory(home)} for _, p := range configDirectories { if fi, err := os.Stat(p); err != nil { @@ -104,7 +104,7 @@ func ensureHome(home string) error { return fmt.Errorf("%s must be a file, not a directory", repoPath) } - localCacheFile := LocalDirCacheFile(home) + localCacheFile := localDirCacheFile(home) if fi, err := os.Stat(localCacheFile); err != nil { fmt.Printf("Creating %s \n", localCacheFile) _, err := os.Create(localCacheFile) @@ -113,9 +113,9 @@ func ensureHome(home string) error { } //TODO: take this out and replace with helm update functionality - os.Symlink(localCacheFile, CacheDirectory(home)+"/local-cache.yaml") + os.Symlink(localCacheFile, cacheDirectory(home)+"/local-cache.yaml") } else if fi.IsDir() { - return fmt.Errorf("%s must be a file, not a directory.", repoPath) + return fmt.Errorf("%s must be a file, not a directory", repoPath) } return nil } @@ -128,10 +128,10 @@ func repositoriesFile(home string) string { return filepath.Join(home, repositoriesPath) } -func LocalDirectory(home string) string { +func localDirectory(home string) string { return filepath.Join(home, localPath) } -func LocalDirCacheFile(home string) string { +func localDirCacheFile(home string) string { return filepath.Join(home, localCacheFilePath) } diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index b7cefe342..067d6ffc2 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -12,7 +12,7 @@ func TestEnsureHome(t *testing.T) { t.Errorf("%s", err) } - dirs := []string{home, CacheDirectory(home), LocalDirectory(home)} + dirs := []string{home, cacheDirectory(home), localDirectory(home)} for _, dir := range dirs { if fi, err := os.Stat(dir); err != nil { t.Errorf("%s", err) diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 5e49041aa..6b3dc0348 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -56,7 +56,7 @@ func runPackage(cmd *cobra.Command, args []string) error { // Save to $HELM_HOME/local directory. if save { - if err := repo.AddChartToLocalRepo(ch, LocalDirectory(os.ExpandEnv(helmHome))); err != nil { + if err := repo.AddChartToLocalRepo(ch, localDirectory(os.ExpandEnv(helmHome))); err != nil { return err } } diff --git a/cmd/helm/search.go b/cmd/helm/search.go index 07bd846aa..e1276a58c 100644 --- a/cmd/helm/search.go +++ b/cmd/helm/search.go @@ -19,10 +19,10 @@ var searchCmd = &cobra.Command{ Use: "search [CHART]", Short: "Search for charts", Long: "", //TODO: add search command description - RunE: Search, + RunE: search, } -func Search(cmd *cobra.Command, args []string) error { +func search(cmd *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("This command needs at least one argument") } @@ -39,7 +39,7 @@ func Search(cmd *cobra.Command, args []string) error { } func searchCacheForPattern(name string) ([]string, error) { - dir := CacheDirectory(os.ExpandEnv(helmHome)) + dir := cacheDirectory(os.ExpandEnv(helmHome)) fileList := []string{} filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { if !f.IsDir() { diff --git a/cmd/helm/serve.go b/cmd/helm/serve.go index 7e4978dcf..b69f18f13 100644 --- a/cmd/helm/serve.go +++ b/cmd/helm/serve.go @@ -20,9 +20,9 @@ var serveCmd = &cobra.Command{ Use: "serve", Short: "Start a local http web server", Long: serveDesc, - Run: ServeLocal, + Run: serve, } -func ServeLocal(cmd *cobra.Command, args []string) { - repo.StartLocalRepo(LocalDirectory(os.ExpandEnv(helmHome))) +func serve(cmd *cobra.Command, args []string) { + repo.StartLocalRepo(localDirectory(os.ExpandEnv(helmHome))) } diff --git a/pkg/repo/local.go b/pkg/repo/local.go index df65bf284..c85e1e618 100644 --- a/pkg/repo/local.go +++ b/pkg/repo/local.go @@ -13,15 +13,18 @@ import ( var localRepoPath string +// CacheFile represents the cache file in a chart repository type CacheFile struct { Entries map[string]*ChartRef } +// ChartRef represents a chart entry in the CacheFile type ChartRef struct { Name string - Url string + URL string } +// StartLocalRepo starts a web server and serves files from the given path func StartLocalRepo(path string) { fmt.Println("Now serving you on localhost:8879...") localRepoPath = path @@ -49,6 +52,7 @@ func serveFile(w http.ResponseWriter, r *http.Request, file string) { http.ServeFile(w, r, filepath.Join(localRepoPath, file)) } +// AddChartToLocalRepo saves a chart in the given path and then reindexes the cache file func AddChartToLocalRepo(ch *chart.Chart, path string) error { name, err := chart.Save(ch, path) if err != nil { @@ -62,6 +66,7 @@ func AddChartToLocalRepo(ch *chart.Chart, path string) error { return nil } +// LoadCacheFile takes a file at the given path and returns a CacheFile object func LoadCacheFile(path string) (*CacheFile, error) { b, err := ioutil.ReadFile(path) if err != nil { @@ -77,6 +82,7 @@ func LoadCacheFile(path string) (*CacheFile, error) { return &y, nil } +// ReindexCacheFile adds an entry to the cache file at the given path func ReindexCacheFile(ch *chart.Chart, path string) error { name := ch.Chartfile().Name + "-" + ch.Chartfile().Version y, err := LoadCacheFile(path) @@ -93,7 +99,7 @@ func ReindexCacheFile(ch *chart.Chart, path string) error { if !found { url := "localhost:8879/charts/" + name + ".tgz" - out, err := y.InsertChartEntry(name, url) + out, err := y.insertChartEntry(name, url) if err != nil { return err } @@ -102,6 +108,8 @@ func ReindexCacheFile(ch *chart.Chart, path string) error { } return nil } + +// UnmarshalYAML unmarshals the cache file func (c *CacheFile) UnmarshalYAML(unmarshal func(interface{}) error) error { var refs map[string]*ChartRef if err := unmarshal(&refs); err != nil { @@ -113,13 +121,13 @@ func (c *CacheFile) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } -func (cache *CacheFile) InsertChartEntry(name string, url string) ([]byte, error) { - if cache.Entries == nil { - cache.Entries = make(map[string]*ChartRef) +func (c *CacheFile) insertChartEntry(name string, url string) ([]byte, error) { + if c.Entries == nil { + c.Entries = make(map[string]*ChartRef) } - entry := ChartRef{Name: name, Url: url} - cache.Entries[name] = &entry - out, err := yaml.Marshal(&cache.Entries) + entry := ChartRef{Name: name, URL: url} + c.Entries[name] = &entry + out, err := yaml.Marshal(&c.Entries) if err != nil { return nil, err } From ba22a18fadbe22db80bc7fd3ba794456255da5c2 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Thu, 21 Apr 2016 16:45:42 -0600 Subject: [PATCH 65/86] fix(engine): coalesce values in templates --- pkg/engine/engine.go | 163 +++++++++++++++++++++++++++++++------- pkg/engine/engine_test.go | 144 ++++++++++++++++++++++++++++++--- 2 files changed, 269 insertions(+), 38 deletions(-) diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 4d3c8b315..76af245a9 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -3,6 +3,7 @@ package engine import ( "bytes" "fmt" + "log" "text/template" "github.com/Masterminds/sprig" @@ -39,38 +40,53 @@ func New() *Engine { // // This will look in the chart's 'templates' data (e.g. the 'templates/' directory) // and attempt to render the templates there using the values passed in. +// +// Values are scoped to their templates. A dependency template will not have +// access to the values set for its parent. If chart "foo" includes chart "bar", +// "bar" will not have access to the values for "foo". +// +// Values are passed through the templates according to scope. If the top layer +// chart includes the chart foo, which includes the chart bar, the values map +// will be examined for a table called "foo". If "foo" is found in vals, +// that section of the values will be passed into the "foo" chart. And if that +// section contains a value named "bar", that value will be passed on to the +// bar chart during render time. +// +// Values are coalesced together using the fillowing rules: +// +// - Values in a higher level chart always override values in a lower-level +// dependency chart +// - Scalar values and arrays are replaced, maps are merged +// - A chart has access to all of the variables for it, as well as all of +// the values destined for its dependencies. func (e *Engine) Render(chrt *chart.Chart, vals *chart.Config) (map[string]string, error) { var cvals chartutil.Values - if chrt.Values == nil { - cvals = map[string]interface{}{} - } else { - var err error - cvals, err = chartutil.ReadValues([]byte(chrt.Values.Raw)) - if err != nil { - return map[string]string{}, err - } - } - // Parse values if not nil + // Parse values if not nil. We merge these at the top level because + // the passed-in values are in the same namespace as the parent chart. if vals != nil { evals, err := chartutil.ReadValues([]byte(vals.Raw)) if err != nil { return map[string]string{}, err } - // Coalesce chart default values and values - for k, v := range evals { - // FIXME: This needs to merge tables. Ideally, this feature should - // be part of the Values type. - cvals[k] = v - } + cvals = coalesceValues(chrt, evals) } // Render the charts - tmap := allTemplates(chrt) - return e.render(tmap, cvals) + tmap := allTemplates(chrt, cvals) + return e.render(tmap) +} + +// renderable is an object that can be rendered. +type renderable struct { + // tpl is the current template. + tpl string + // vals are the values to be supplied to the template. + vals chartutil.Values } -func (e *Engine) render(tpls map[string]string, v interface{}) (map[string]string, error) { +// render takes a map of templates/values and renders them. +func (e *Engine) render(tpls map[string]renderable) (map[string]string, error) { // Basically, what we do here is start with an empty parent template and then // build up a list of templates -- one for each file. Once all of the templates // have been parsed, we loop through again and execute every template. @@ -80,9 +96,9 @@ func (e *Engine) render(tpls map[string]string, v interface{}) (map[string]strin // template engine. t := template.New("gotpl") files := []string{} - for fname, tpl := range tpls { + for fname, r := range tpls { t = t.New(fname).Funcs(e.FuncMap) - if _, err := t.Parse(tpl); err != nil { + if _, err := t.Parse(r.tpl); err != nil { return map[string]string{}, fmt.Errorf("parse error in %q: %s", fname, err) } files = append(files, fname) @@ -91,7 +107,8 @@ func (e *Engine) render(tpls map[string]string, v interface{}) (map[string]strin rendered := make(map[string]string, len(files)) var buf bytes.Buffer for _, file := range files { - if err := t.ExecuteTemplate(&buf, file, v); err != nil { + // log.Printf("Exec %s with %v (%s)", file, tpls[file].vals, tpls[file].tpl) + if err := t.ExecuteTemplate(&buf, file, tpls[file].vals); err != nil { return map[string]string{}, fmt.Errorf("render error in %q: %s", file, err) } rendered[file] = buf.String() @@ -102,18 +119,108 @@ func (e *Engine) render(tpls map[string]string, v interface{}) (map[string]strin } // allTemplates returns all templates for a chart and its dependencies. -func allTemplates(c *chart.Chart) map[string]string { - templates := map[string]string{} - recAllTpls(c, templates) +// +// As it goes, it also prepares the values in a scope-sensitive manner. +func allTemplates(c *chart.Chart, vals chartutil.Values) map[string]renderable { + templates := map[string]renderable{} + recAllTpls(c, templates, vals, true) return templates } -func recAllTpls(c *chart.Chart, templates map[string]string) { +// recAllTpls recurses through the templates in a chart. +// +// As it recurses, it also sets the values to be appropriate for the template +// scope. +func recAllTpls(c *chart.Chart, templates map[string]renderable, parentVals chartutil.Values, top bool) { + var pvals chartutil.Values + if top { + // If this is the top of the rendering tree, assume that parentVals + // is already resolved to the authoritative values. + pvals = parentVals + } else if c.Metadata != nil && c.Metadata.Name != "" { + // An error indicates that the table doesn't exist. So we leave it as + // an empty map. + tmp, err := parentVals.Table(c.Metadata.Name) + if err == nil { + pvals = tmp + } + } + cvals := coalesceValues(c, pvals) + //log.Printf("racAllTpls values: %v", cvals) for _, child := range c.Dependencies { - recAllTpls(child, templates) + recAllTpls(child, templates, cvals, false) } for _, t := range c.Templates { - templates[t.Name] = string(t.Data) + templates[t.Name] = renderable{ + tpl: string(t.Data), + vals: cvals, + } + } +} + +// coalesceValues builds up a values map for a particular chart. +// +// Values in v will override the values in the chart. +func coalesceValues(c *chart.Chart, v chartutil.Values) chartutil.Values { + // If there are no values in the chart, we just return the given values + if c.Values == nil { + return v + } + + nv, err := chartutil.ReadValues([]byte(c.Values.Raw)) + if err != nil { + // On error, we return just the overridden values. + // FIXME: We should log this error. It indicates that the TOML data + // did not parse. + log.Printf("error reading default values: %s", err) + return v } + for k, val := range v { + // NOTE: We could block coalesce on cases where nv does not explicitly + // declare a value. But that forces the chart author to explicitly + // set a default for every template param. We want to preserve the + // possibility of "hidden" parameters. + if istable(val) { + if inmap, ok := nv[k]; ok && istable(inmap) { + coalesceTables(inmap.(map[string]interface{}), val.(map[string]interface{})) + } else if ok { + log.Printf("Cannot copy table into non-table value for %s (%v)", k, inmap) + } else { + // The parent table does not have a key entry for this item, + // so we can safely set it. This is necessary for nested charts. + log.Printf("Copying %s into map %v", k, nv) + nv[k] = val + } + } else { + nv[k] = val + } + } + return nv +} + +// coalesceTables merges a source map into a destination map. +func coalesceTables(dst, src map[string]interface{}) { + for key, val := range src { + if istable(val) { + if innerdst, ok := dst[key]; !ok { + dst[key] = val + } else if istable(innerdst) { + coalesceTables(innerdst.(map[string]interface{}), val.(map[string]interface{})) + } else { + log.Printf("Cannot overwrite table with non table for %s (%v)", key, val) + } + continue + } else if dv, ok := dst[key]; ok && istable(dv) { + log.Printf("Destination for %s is a table. Ignoring non-table value %v", key, val) + continue + } + dst[key] = val + } +} + +// istable is a special-purpose function to see if the present thing matches the definition of a TOML table. +func istable(v interface{}) bool { + _, ok := v.(map[string]interface{}) + return ok } diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 36992a1f2..6a88fe2bd 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -5,6 +5,7 @@ import ( "sync" "testing" + chartutil "github.com/deis/tiller/pkg/chart" "github.com/deis/tiller/pkg/proto/hapi/chart" ) @@ -28,16 +29,16 @@ func TestRenderInternals(t *testing.T) { // Test the internals of the rendering tool. e := New() - tpls := map[string]string{ - "one": `Hello {{title .Name}}`, - "two": `Goodbye {{upper .Value}}`, + vals := chartutil.Values{"Name": "one", "Value": "two"} + tpls := map[string]renderable{ + "one": {tpl: `Hello {{title .Name}}`, vals: vals}, + "two": {tpl: `Goodbye {{upper .Value}}`, vals: vals}, // Test whether a template can reliably reference another template // without regard for ordering. - "three": `{{template "two" dict "Value" "three"}}`, + "three": {tpl: `{{template "two" dict "Value" "three"}}`, vals: vals}, } - vals := map[string]string{"Name": "one", "Value": "two"} - out, err := e.render(tpls, vals) + out, err := e.render(tpls) if err != nil { t.Fatalf("Failed template rendering: %s", err) } @@ -68,9 +69,9 @@ func TestParallelRenderInternals(t *testing.T) { go func(i int) { fname := "my/file/name" tt := fmt.Sprintf("expect-%d", i) - tpls := map[string]string{fname: `{{.val}}`} - v := map[string]string{"val": tt} - out, err := e.render(tpls, v) + v := chartutil.Values{"val": tt} + tpls := map[string]renderable{fname: {tpl: `{{.val}}`, vals: v}} + out, err := e.render(tpls) if err != nil { t.Errorf("Failed to render %s: %s", tt, err) } @@ -104,7 +105,8 @@ func TestAllTemplates(t *testing.T) { }, } - tpls := allTemplates(ch1) + var v chartutil.Values + tpls := allTemplates(ch1, v) if len(tpls) != 5 { t.Errorf("Expected 5 charts, got %d", len(tpls)) } @@ -143,3 +145,125 @@ func TestRenderDependency(t *testing.T) { } } + +func TestRenderNestedValues(t *testing.T) { + e := New() + + innerpath := "charts/inner/templates/inner.tpl" + outerpath := "templates/outer.tpl" + deepestpath := "charts/inner/charts/deepest/templates/deepest.tpl" + + deepest := &chart.Chart{ + Metadata: &chart.Metadata{Name: "deepest"}, + Templates: []*chart.Template{ + {Name: deepestpath, Data: []byte(`And this same {{.what}} that smiles to-day`)}, + }, + Values: &chart.Config{Raw: `what = "milkshake"`}, + } + + inner := &chart.Chart{ + Metadata: &chart.Metadata{Name: "herrick"}, + Templates: []*chart.Template{ + {Name: innerpath, Data: []byte(`Old {{.who}} is still a-flyin'`)}, + }, + Values: &chart.Config{Raw: `who = "Robert"`}, + Dependencies: []*chart.Chart{deepest}, + } + + outer := &chart.Chart{ + Metadata: &chart.Metadata{Name: "top"}, + Templates: []*chart.Template{ + {Name: outerpath, Data: []byte(`Gather ye {{.what}} while ye may`)}, + }, + Values: &chart.Config{ + Raw: `what = "stinkweed" + [herrick] + who = "time" + `}, + Dependencies: []*chart.Chart{inner}, + } + + inject := chart.Config{ + Raw: ` + what = "rosebuds" + [herrick.deepest] + what = "flower"`, + } + + out, err := e.Render(outer, &inject) + if err != nil { + t.Fatalf("failed to render templates: %s", err) + } + + if out[outerpath] != "Gather ye rosebuds while ye may" { + t.Errorf("Unexpected outer: %q", out[outerpath]) + } + + if out[innerpath] != "Old time is still a-flyin'" { + t.Errorf("Unexpected inner: %q", out[innerpath]) + } + + if out[deepestpath] != "And this same flower that smiles to-day" { + t.Errorf("Unexpected deepest: %q", out[deepestpath]) + } +} + +func TestCoalesceTables(t *testing.T) { + dst := map[string]interface{}{ + "name": "Ishmael", + "address": map[string]interface{}{ + "street": "123 Spouter Inn Ct.", + "city": "Nantucket", + }, + "details": map[string]interface{}{ + "friends": []string{"Tashtego"}, + }, + "boat": "pequod", + } + src := map[string]interface{}{ + "occupation": "whaler", + "address": map[string]interface{}{ + "state": "MA", + "street": "234 Spouter Inn Ct.", + }, + "details": "empty", + "boat": map[string]interface{}{ + "mast": true, + }, + } + coalesceTables(dst, src) + + if dst["name"] != "Ishmael" { + t.Errorf("Unexpected name: %s", dst["name"]) + } + if dst["occupation"] != "whaler" { + t.Errorf("Unexpected occupation: %s", dst["occupation"]) + } + + addr, ok := dst["address"].(map[string]interface{}) + if !ok { + t.Fatal("Address went away.") + } + + if addr["street"].(string) != "234 Spouter Inn Ct." { + t.Errorf("Unexpected address: %v", addr["street"]) + } + + if addr["city"].(string) != "Nantucket" { + t.Errorf("Unexpected city: %v", addr["city"]) + } + + if addr["state"].(string) != "MA" { + t.Errorf("Unexpected state: %v", addr["state"]) + } + + if det, ok := dst["details"].(map[string]interface{}); !ok { + t.Fatalf("Details is the wrong type: %v", dst["details"]) + } else if _, ok := det["friends"]; !ok { + t.Error("Could not find your friends. Maybe you don't have any. :-(") + } + + if dst["boat"].(string) != "pequod" { + t.Errorf("Expected boat string, got %v", dst["boat"]) + } +} From 58fdaf1e529536b66c326e3a208b7eb51e32ae9b Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 22 Apr 2016 16:48:33 -0600 Subject: [PATCH 66/86] feat(tiller): add support for dry run install This adds support for dry run on install, as well as providing enough info for an install to display (verbose) manifests. While doing this, I ended up just storing the rendered manifests for simplicity. --- _proto/hapi/release/info.proto | 4 +- _proto/hapi/release/release.proto | 19 ++++--- _proto/hapi/services/tiller.proto | 4 ++ cmd/tiller/release_server.go | 20 +++++++- cmd/tiller/release_server_test.go | 70 +++++++++++++++++++++++++- pkg/proto/hapi/release/release.pb.go | 41 ++++++++------- pkg/proto/hapi/services/tiller.pb.go | 75 +++++++++++++++------------- 7 files changed, 168 insertions(+), 65 deletions(-) diff --git a/_proto/hapi/release/info.proto b/_proto/hapi/release/info.proto index 09e022ecd..fa8623ee8 100644 --- a/_proto/hapi/release/info.proto +++ b/_proto/hapi/release/info.proto @@ -13,8 +13,8 @@ option go_package = "release"; // message Info { Status status = 1; - + google.protobuf.Timestamp first_deployed = 2; - + google.protobuf.Timestamp last_deployed = 3; } diff --git a/_proto/hapi/release/release.proto b/_proto/hapi/release/release.proto index 0caa3cf28..52ba2cd44 100644 --- a/_proto/hapi/release/release.proto +++ b/_proto/hapi/release/release.proto @@ -11,18 +11,23 @@ option go_package = "release"; // // Release: // -// TODO +// A release describes a deployment of a chart, together with the chart +// and the variables used to deploy that chart. // message Release { - // TODO + // Name is the name of the release string name = 1; - // TODO + // Info provides information about a release hapi.release.Info info = 2; - - // TODO + + // Chart is the chart that was released. hapi.chart.Chart chart = 3; - - // TODO + + // Config is the set of extra Values added to the chart. + // These values override the default values inside of the chart. hapi.chart.Config config = 4; + + // Manifest is the string representation of the rendered template. + string manifest = 5; } diff --git a/_proto/hapi/services/tiller.proto b/_proto/hapi/services/tiller.proto index 34fd9687a..581fe659f 100644 --- a/_proto/hapi/services/tiller.proto +++ b/_proto/hapi/services/tiller.proto @@ -169,6 +169,10 @@ message InstallReleaseRequest { hapi.chart.Chart chart = 1; // Values is a string containing (unparsed) TOML values. hapi.chart.Config values = 2; + // DryRun, if true, will run through the release logic, but neither create + // a release object nor deploy to Kubernetes. The release object returned + // in the response will be fake. + bool dry_run = 3; } // diff --git a/cmd/tiller/release_server.go b/cmd/tiller/release_server.go index 212b63d3c..dba45f1c9 100644 --- a/cmd/tiller/release_server.go +++ b/cmd/tiller/release_server.go @@ -1,7 +1,9 @@ package main import ( + "bytes" "errors" + "log" "github.com/deis/tiller/cmd/tiller/environment" "github.com/deis/tiller/pkg/proto/hapi/release" @@ -54,11 +56,21 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea name := namer.Name() // Render the templates - _, err := s.env.EngineYard.Default().Render(req.Chart, req.Values) + files, err := s.env.EngineYard.Default().Render(req.Chart, req.Values) if err != nil { return nil, err } + b := bytes.NewBuffer(nil) + for name, file := range files { + // Ignore empty documents because the Kubernetes library can't handle + // them. + if len(file) > 0 { + b.WriteString("\n---\n# Source: " + name + "\n") + b.WriteString(file) + } + } + // Store a release. r := &release.Release{ Name: name, @@ -67,6 +79,12 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea Info: &release.Info{ Status: &release.Status{Code: release.Status_UNKNOWN}, }, + Manifest: b.String(), + } + + if req.DryRun { + log.Printf("Dry run for %s", name) + return &services.InstallReleaseResponse{Release: r}, nil } if err := s.env.Releases.Create(r); err != nil { diff --git a/cmd/tiller/release_server_test.go b/cmd/tiller/release_server_test.go index a25bd9378..c09736341 100644 --- a/cmd/tiller/release_server_test.go +++ b/cmd/tiller/release_server_test.go @@ -1,12 +1,14 @@ package main import ( + "strings" + "testing" + "github.com/deis/tiller/cmd/tiller/environment" "github.com/deis/tiller/pkg/proto/hapi/chart" "github.com/deis/tiller/pkg/proto/hapi/services" "github.com/deis/tiller/pkg/storage" "golang.org/x/net/context" - "testing" ) func rsFixture() *releaseServer { @@ -20,7 +22,12 @@ func TestInstallRelease(t *testing.T) { rs := rsFixture() req := &services.InstallReleaseRequest{ - Chart: &chart.Chart{}, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "hello", Data: []byte("hello: world")}, + }, + }, } res, err := rs.InstallRelease(c, req) if err != nil { @@ -29,6 +36,65 @@ func TestInstallRelease(t *testing.T) { if res.Release.Name == "" { t.Errorf("Expected release name.") } + + rel, err := rs.env.Releases.Read(res.Release.Name) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + t.Logf("rel: %v", rel) + + if len(res.Release.Manifest) == 0 { + t.Errorf("No manifest returned: %v", res.Release) + } + + if len(rel.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) + } + + if !strings.Contains(rel.Manifest, "---\n# Source: hello\nhello: world") { + t.Errorf("unexpected output: %s", rel.Manifest) + } +} + +func TestInstallReleaseDryRun(t *testing.T) { + c := context.Background() + rs := rsFixture() + + req := &services.InstallReleaseRequest{ + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "hello", Data: []byte("hello: world")}, + {Name: "goodbye", Data: []byte("goodbye: world")}, + {Name: "empty", Data: []byte("")}, + }, + }, + DryRun: true, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Errorf("Failed install: %s", err) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + + if !strings.Contains(res.Release.Manifest, "---\n# Source: hello\nhello: world") { + t.Errorf("unexpected output: %s", res.Release.Manifest) + } + + if !strings.Contains(res.Release.Manifest, "---\n# Source: goodbye\ngoodbye: world") { + t.Errorf("unexpected output: %s", res.Release.Manifest) + } + + if strings.Contains(res.Release.Manifest, "empty") { + t.Errorf("Should not contain template data for an empty file. %s", res.Release.Manifest) + } + + if _, err := rs.env.Releases.Read(res.Release.Name); err == nil { + t.Errorf("Expected no stored release.") + } } func mockEnvironment() *environment.Environment { diff --git a/pkg/proto/hapi/release/release.pb.go b/pkg/proto/hapi/release/release.pb.go index 5f14340d9..b97f38f15 100644 --- a/pkg/proto/hapi/release/release.pb.go +++ b/pkg/proto/hapi/release/release.pb.go @@ -18,17 +18,21 @@ var _ = math.Inf // // Release: // -// TODO +// A release describes a deployment of a chart, together with the chart +// and the variables used to deploy that chart. // type Release struct { - // TODO + // Name is the name of the release Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` - // TODO + // Info provides information about a release Info *Info `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` - // TODO + // Chart is the chart that was released. Chart *hapi_chart3.Chart `protobuf:"bytes,3,opt,name=chart" json:"chart,omitempty"` - // TODO + // Config is the set of extra Values added to the chart. + // These values override the default values inside of the chart. Config *hapi_chart.Config `protobuf:"bytes,4,opt,name=config" json:"config,omitempty"` + // Manifest is the string representation of the rendered template. + Manifest string `protobuf:"bytes,5,opt,name=manifest" json:"manifest,omitempty"` } func (m *Release) Reset() { *m = Release{} } @@ -62,17 +66,18 @@ func init() { } var fileDescriptor1 = []byte{ - // 182 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x92, 0xca, 0x48, 0x2c, 0xc8, - 0xd4, 0x2f, 0x4a, 0xcd, 0x49, 0x4d, 0x2c, 0x4e, 0x85, 0xd1, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, - 0x42, 0x3c, 0x20, 0x39, 0x3d, 0xa8, 0x98, 0x94, 0x38, 0x8a, 0xca, 0xcc, 0xbc, 0xb4, 0x7c, 0x88, - 0x32, 0xa8, 0x44, 0x72, 0x46, 0x62, 0x51, 0x89, 0x7e, 0x72, 0x7e, 0x5e, 0x5a, 0x66, 0x3a, 0x54, - 0x42, 0x0c, 0x59, 0x02, 0x44, 0x42, 0xc4, 0x95, 0x66, 0x31, 0x72, 0xb1, 0x07, 0x41, 0xcc, 0x11, - 0x12, 0xe2, 0x62, 0xc9, 0x4b, 0xcc, 0x4d, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0xb3, - 0x85, 0xd4, 0xb8, 0x58, 0x40, 0xc6, 0x4b, 0x30, 0x01, 0xc5, 0xb8, 0x8d, 0x84, 0xf4, 0x90, 0x9d, - 0xa1, 0xe7, 0x09, 0x94, 0x09, 0x02, 0xcb, 0x0b, 0xa9, 0x73, 0xb1, 0x82, 0x8d, 0x95, 0x60, 0x06, - 0x2b, 0x14, 0x84, 0x28, 0x84, 0xd8, 0xe4, 0x0c, 0x22, 0x83, 0x20, 0xf2, 0x42, 0x5a, 0x5c, 0x6c, - 0x10, 0x87, 0x49, 0xb0, 0x20, 0x1b, 0x09, 0x55, 0x09, 0x96, 0x09, 0x82, 0xaa, 0x70, 0xe2, 0x8c, - 0x62, 0x87, 0x5a, 0x95, 0xc4, 0x06, 0x76, 0xae, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xb4, 0xa3, - 0x61, 0x9d, 0x24, 0x01, 0x00, 0x00, + // 200 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x54, 0x8f, 0xbd, 0x0e, 0x82, 0x30, + 0x14, 0x85, 0x83, 0xf2, 0x23, 0xd5, 0xc5, 0x3b, 0x68, 0xc3, 0x64, 0x1c, 0xd4, 0x38, 0x94, 0x44, + 0xdf, 0x40, 0x27, 0xd7, 0x8e, 0x6e, 0x95, 0x14, 0x69, 0x22, 0x2d, 0x01, 0x9e, 0xcd, 0xe7, 0x93, + 0xf6, 0x56, 0x83, 0xcb, 0x85, 0xde, 0xef, 0xcb, 0xe9, 0x29, 0xc9, 0x2a, 0xd1, 0xa8, 0xbc, 0x95, + 0x2f, 0x29, 0x3a, 0xf9, 0xfd, 0xb2, 0xa6, 0x35, 0xbd, 0x81, 0x85, 0x65, 0xcc, 0xef, 0xb2, 0xf5, + 0x9f, 0xa9, 0x74, 0x69, 0x50, 0xf3, 0xa0, 0xa8, 0x44, 0xdb, 0xe7, 0x85, 0xd1, 0xa5, 0x7a, 0x7a, + 0xb0, 0x1a, 0x03, 0x3b, 0x71, 0xbf, 0x7d, 0x07, 0x24, 0xe1, 0x98, 0x03, 0x40, 0x42, 0x2d, 0x6a, + 0x49, 0x83, 0x4d, 0x70, 0x48, 0xb9, 0xfb, 0x87, 0x1d, 0x09, 0x6d, 0x3c, 0x9d, 0x0c, 0xbb, 0xf9, + 0x09, 0xd8, 0xb8, 0x06, 0xbb, 0x0d, 0x84, 0x3b, 0x0e, 0x7b, 0x12, 0xb9, 0x58, 0x3a, 0x75, 0xe2, + 0x12, 0x45, 0xbc, 0xe9, 0x6a, 0x27, 0x47, 0x0e, 0x47, 0x12, 0x63, 0x31, 0x1a, 0x8e, 0x23, 0xbd, + 0xe9, 0x08, 0xf7, 0x06, 0x64, 0x64, 0x56, 0x0b, 0xad, 0x4a, 0xd9, 0xf5, 0x34, 0x72, 0xa5, 0x7e, + 0xe7, 0x4b, 0x7a, 0x4f, 0x7c, 0x8d, 0x47, 0xec, 0x9e, 0x72, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, + 0xd4, 0xf3, 0x60, 0x0b, 0x40, 0x01, 0x00, 0x00, } diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go index b12567750..e8922e10a 100644 --- a/pkg/proto/hapi/services/tiller.pb.go +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -179,6 +179,10 @@ type InstallReleaseRequest struct { Chart *hapi_chart3.Chart `protobuf:"bytes,1,opt,name=chart" json:"chart,omitempty"` // Values is a string containing (unparsed) TOML values. Values *hapi_chart.Config `protobuf:"bytes,2,opt,name=values" json:"values,omitempty"` + // DryRun, if true, will run through the release logic, but neither create + // a release object nor deploy to Kubernetes. The release object returned + // in the response will be fake. + DryRun bool `protobuf:"varint,3,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"` } func (m *InstallReleaseRequest) Reset() { *m = InstallReleaseRequest{} } @@ -537,39 +541,40 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ } var fileDescriptor1 = []byte{ - // 529 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0x4d, 0x73, 0xd3, 0x30, - 0x10, 0x6d, 0x08, 0x0d, 0x65, 0xd3, 0x66, 0xa8, 0x70, 0x12, 0x47, 0xa7, 0xa2, 0x0b, 0xa5, 0x80, - 0x03, 0xe1, 0x08, 0x5c, 0xc8, 0x81, 0xe9, 0x4c, 0x87, 0x83, 0x99, 0x5e, 0xb8, 0x30, 0x26, 0x28, - 0x54, 0x8c, 0x62, 0x07, 0x4b, 0xc9, 0x81, 0xff, 0xc0, 0xff, 0xe1, 0xe7, 0x61, 0xeb, 0xc3, 0x13, - 0x39, 0xd6, 0xd4, 0xf4, 0xe2, 0x8c, 0xf4, 0xde, 0xee, 0xdb, 0x5d, 0xbd, 0x9d, 0x00, 0xbe, 0x49, - 0xd6, 0x6c, 0x2a, 0x68, 0xbe, 0x65, 0x0b, 0x2a, 0xa6, 0x92, 0x71, 0x4e, 0xf3, 0x68, 0x9d, 0x67, - 0x32, 0x43, 0x41, 0x89, 0x45, 0x16, 0x8b, 0x34, 0x86, 0x47, 0x2a, 0x62, 0x71, 0x93, 0xe4, 0x52, - 0x7f, 0x35, 0x1b, 0x8f, 0x77, 0xef, 0xb3, 0x74, 0xc9, 0x7e, 0x18, 0x40, 0x4b, 0xe4, 0x94, 0xd3, - 0x44, 0x50, 0xfb, 0x6b, 0xb0, 0x89, 0x83, 0x09, 0x99, 0xc8, 0x8d, 0xd0, 0x10, 0x99, 0xc3, 0xe3, - 0x2b, 0x26, 0x64, 0xac, 0x31, 0x11, 0xd3, 0x5f, 0x1b, 0x2a, 0x24, 0x0a, 0xe0, 0x90, 0xb3, 0x15, - 0x93, 0x61, 0xe7, 0xac, 0x73, 0xde, 0x8d, 0xf5, 0x01, 0x8d, 0xa0, 0x97, 0x2d, 0x97, 0x82, 0xca, - 0xf0, 0x9e, 0xba, 0x36, 0x27, 0xf2, 0xa7, 0x03, 0x81, 0x9b, 0x45, 0xac, 0xb3, 0x54, 0xd0, 0x32, - 0xcd, 0x22, 0xdb, 0xa4, 0x55, 0x1a, 0x75, 0xf0, 0xa5, 0x29, 0xd9, 0x32, 0x93, 0x09, 0x0f, 0xbb, - 0x9a, 0xad, 0x0e, 0xe8, 0x35, 0x1c, 0x99, 0xca, 0x45, 0x78, 0xff, 0xac, 0x7b, 0xde, 0x9f, 0x0d, - 0x23, 0x35, 0x32, 0xdb, 0xa3, 0x51, 0x8d, 0x2b, 0x1a, 0x79, 0x07, 0xe3, 0x8f, 0xd4, 0x56, 0xf3, - 0x59, 0xb5, 0x6b, 0x1b, 0x7b, 0x02, 0xc7, 0x86, 0xf6, 0x35, 0x4d, 0x56, 0x54, 0x15, 0xf6, 0x30, - 0xee, 0x9b, 0xbb, 0x4f, 0xc5, 0x15, 0xf9, 0x0d, 0xe1, 0x7e, 0xb4, 0x69, 0xe8, 0xf6, 0x70, 0xf4, - 0x16, 0x06, 0x96, 0xa2, 0x27, 0xad, 0xba, 0xec, 0xcf, 0x02, 0xb7, 0x6a, 0x93, 0xf8, 0x24, 0xdf, - 0xd5, 0x21, 0xef, 0x77, 0xb5, 0xe7, 0x59, 0x2a, 0x69, 0x2a, 0xff, 0xa3, 0xf4, 0x2b, 0x98, 0x34, - 0x84, 0x9b, 0xda, 0xa7, 0xf0, 0xc0, 0x70, 0x55, 0xa8, 0x77, 0x8e, 0x96, 0x45, 0x46, 0x10, 0x5c, - 0xaf, 0xbf, 0x27, 0x92, 0x5a, 0x44, 0x17, 0x42, 0xc6, 0x30, 0xac, 0xdd, 0x6b, 0x05, 0xc2, 0x61, - 0x78, 0x99, 0x16, 0x4d, 0x73, 0xee, 0x46, 0xa0, 0xa7, 0x85, 0x0f, 0x4a, 0xcb, 0x1a, 0xe1, 0x53, - 0x2d, 0xac, 0x7d, 0x3d, 0x2f, 0xbf, 0xb1, 0xc6, 0xd1, 0x05, 0xf4, 0xb6, 0x09, 0x2f, 0x62, 0xcc, - 0xd0, 0x90, 0xc3, 0x54, 0x7e, 0x8f, 0x0d, 0x83, 0x5c, 0xc2, 0xa8, 0xae, 0x76, 0xd7, 0x4e, 0x27, - 0x30, 0xbe, 0x4e, 0x59, 0x53, 0xe9, 0x04, 0x43, 0xb8, 0x0f, 0x69, 0x9d, 0xd9, 0xdf, 0x43, 0x18, - 0x58, 0x9f, 0xe8, 0xfd, 0x45, 0x0c, 0x8e, 0x77, 0x37, 0x01, 0x3d, 0x8b, 0x9a, 0xd6, 0x3b, 0x6a, - 0xd8, 0x39, 0x7c, 0xd1, 0x86, 0x6a, 0x26, 0x7d, 0xf0, 0xaa, 0x83, 0x04, 0x3c, 0xaa, 0xfb, 0x14, - 0xbd, 0x6c, 0xce, 0xe1, 0xd9, 0x06, 0x1c, 0xb5, 0xa5, 0x5b, 0x59, 0xb4, 0x85, 0xd3, 0x3d, 0x87, - 0xa1, 0x5b, 0xd3, 0xb8, 0x4e, 0xc6, 0xd3, 0xd6, 0xfc, 0x4a, 0xf7, 0x27, 0x9c, 0x38, 0x9e, 0x43, - 0x9e, 0x69, 0x35, 0x19, 0x16, 0x3f, 0x6f, 0xc5, 0xad, 0xb4, 0x56, 0x30, 0x70, 0x8d, 0x85, 0x3c, - 0x09, 0x1a, 0xcd, 0x8e, 0x5f, 0xb4, 0x23, 0x57, 0x72, 0xc5, 0x3b, 0xd6, 0x1d, 0xe6, 0x7b, 0x47, - 0x8f, 0x49, 0x7d, 0xef, 0xe8, 0x33, 0x2e, 0x39, 0xf8, 0x00, 0x5f, 0x8e, 0x2c, 0xfb, 0x5b, 0x4f, - 0xfd, 0x15, 0xbc, 0xf9, 0x17, 0x00, 0x00, 0xff, 0xff, 0x75, 0xe1, 0x2e, 0x4f, 0xa6, 0x06, 0x00, - 0x00, + // 550 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0x4d, 0x6f, 0xd3, 0x40, + 0x10, 0xad, 0x09, 0x4d, 0xc3, 0xa4, 0x8d, 0xe8, 0xe2, 0xc4, 0x8e, 0x4f, 0x65, 0x2f, 0x94, 0x02, + 0x0e, 0x84, 0x23, 0x70, 0x21, 0x07, 0x54, 0xa9, 0xe2, 0x60, 0xd4, 0x0b, 0x97, 0xca, 0xa4, 0x1b, + 0xba, 0xc8, 0x59, 0x07, 0xef, 0x3a, 0x12, 0xdc, 0x39, 0xf2, 0x7f, 0xf8, 0x79, 0xd8, 0xfb, 0x61, + 0xc5, 0x89, 0x57, 0x31, 0x5c, 0x1c, 0xed, 0xbe, 0x37, 0xf3, 0x66, 0x66, 0xdf, 0x28, 0x10, 0xdc, + 0xc5, 0x2b, 0x3a, 0xe1, 0x24, 0x5b, 0xd3, 0x39, 0xe1, 0x13, 0x41, 0x93, 0x84, 0x64, 0xe1, 0x2a, + 0x4b, 0x45, 0x8a, 0xdc, 0x12, 0x0b, 0x0d, 0x16, 0x2a, 0x2c, 0x18, 0xc9, 0x88, 0xf9, 0x5d, 0x9c, + 0x09, 0xf5, 0x55, 0xec, 0xc0, 0xdb, 0xbc, 0x4f, 0xd9, 0x82, 0x7e, 0xd5, 0x80, 0x92, 0xc8, 0x48, + 0x42, 0x62, 0x4e, 0xcc, 0xaf, 0xc6, 0xc6, 0x35, 0x8c, 0x8b, 0x58, 0xe4, 0x5c, 0x41, 0x78, 0x06, + 0x8f, 0xae, 0x28, 0x17, 0x91, 0xc2, 0x78, 0x44, 0xbe, 0xe7, 0x84, 0x0b, 0xe4, 0xc2, 0x61, 0x42, + 0x97, 0x54, 0xf8, 0xce, 0x99, 0x73, 0xde, 0x89, 0xd4, 0x01, 0x8d, 0xa0, 0x9b, 0x2e, 0x16, 0x9c, + 0x08, 0xff, 0x9e, 0xbc, 0xd6, 0x27, 0xfc, 0xdb, 0x01, 0xb7, 0x9e, 0x85, 0xaf, 0x52, 0xc6, 0x49, + 0x99, 0x66, 0x9e, 0xe6, 0xac, 0x4a, 0x23, 0x0f, 0xb6, 0x34, 0x25, 0x5b, 0xa4, 0x22, 0x4e, 0xfc, + 0x8e, 0x62, 0xcb, 0x03, 0x7a, 0x05, 0x3d, 0x5d, 0x39, 0xf7, 0xef, 0x9f, 0x75, 0xce, 0xfb, 0xd3, + 0x61, 0x28, 0x47, 0x66, 0x7a, 0xd4, 0xaa, 0x51, 0x45, 0xc3, 0x6f, 0xc1, 0xfb, 0x40, 0x4c, 0x35, + 0x9f, 0x64, 0xbb, 0xa6, 0xb1, 0xc7, 0x70, 0xac, 0x69, 0x37, 0x2c, 0x5e, 0x12, 0x59, 0xd8, 0x83, + 0xa8, 0xaf, 0xef, 0x3e, 0x16, 0x57, 0xf8, 0x27, 0xf8, 0xbb, 0xd1, 0xba, 0xa1, 0xfd, 0xe1, 0xe8, + 0x0d, 0x0c, 0x0c, 0x45, 0x4d, 0x5a, 0x76, 0xd9, 0x9f, 0xba, 0xf5, 0xaa, 0x75, 0xe2, 0x93, 0x6c, + 0x53, 0x07, 0xbf, 0xdb, 0xd4, 0x9e, 0xa5, 0x4c, 0x10, 0x26, 0xfe, 0xa1, 0xf4, 0x2b, 0x18, 0x37, + 0x84, 0xeb, 0xda, 0x27, 0x70, 0xa4, 0xb9, 0x32, 0xd4, 0x3a, 0x47, 0xc3, 0xc2, 0x23, 0x70, 0xaf, + 0x57, 0xb7, 0xb1, 0x20, 0x06, 0x51, 0x85, 0x60, 0x0f, 0x86, 0x5b, 0xf7, 0x4a, 0x01, 0xff, 0x72, + 0x60, 0x78, 0xc9, 0x8a, 0xae, 0x93, 0xa4, 0x1e, 0x82, 0x9e, 0x14, 0x46, 0x28, 0x3d, 0xab, 0x95, + 0x4f, 0x95, 0xb2, 0x32, 0xf6, 0xac, 0xfc, 0x46, 0x0a, 0x47, 0x17, 0xd0, 0x5d, 0xc7, 0x49, 0x11, + 0xa3, 0xa7, 0x86, 0x6a, 0x4c, 0x69, 0xf8, 0x48, 0x33, 0x90, 0x07, 0x47, 0xb7, 0xd9, 0x8f, 0x9b, + 0x2c, 0x67, 0xd2, 0x31, 0xbd, 0xa8, 0x5b, 0x1c, 0xa3, 0x9c, 0xe1, 0x4b, 0x18, 0x6d, 0x97, 0xf1, + 0xbf, 0x33, 0x18, 0x83, 0x77, 0xcd, 0x68, 0x53, 0x4f, 0x38, 0x00, 0x7f, 0x17, 0x52, 0x3a, 0xd3, + 0x3f, 0x87, 0x30, 0x30, 0x0e, 0x52, 0x9b, 0x8d, 0x28, 0x1c, 0x6f, 0xee, 0x08, 0x7a, 0x1a, 0x36, + 0x2d, 0x7e, 0xd8, 0xb0, 0x8d, 0xc1, 0x45, 0x1b, 0xaa, 0x7e, 0x83, 0x83, 0x97, 0x0e, 0xe2, 0xf0, + 0x70, 0xdb, 0xc1, 0xe8, 0x45, 0x73, 0x0e, 0xcb, 0x9e, 0x04, 0x61, 0x5b, 0xba, 0x91, 0x45, 0x6b, + 0x38, 0xdd, 0xf1, 0x1e, 0xda, 0x9b, 0xa6, 0xee, 0xf1, 0x60, 0xd2, 0x9a, 0x5f, 0xe9, 0x7e, 0x83, + 0x93, 0x9a, 0x1b, 0x91, 0x65, 0x5a, 0x4d, 0x56, 0x0e, 0x9e, 0xb5, 0xe2, 0x56, 0x5a, 0x4b, 0x18, + 0xd4, 0x8d, 0x85, 0x2c, 0x09, 0x1a, 0xb7, 0x20, 0x78, 0xde, 0x8e, 0x5c, 0xc9, 0x15, 0xef, 0xb8, + 0xed, 0x30, 0xdb, 0x3b, 0x5a, 0x4c, 0x6a, 0x7b, 0x47, 0x9b, 0x71, 0xf1, 0xc1, 0x7b, 0xf8, 0xdc, + 0x33, 0xec, 0x2f, 0x5d, 0xf9, 0x27, 0xf1, 0xfa, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa9, 0x65, + 0x97, 0x54, 0xc0, 0x06, 0x00, 0x00, } From 627a0e24e86537d966bb7a26d6453bee1d19324b Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Fri, 22 Apr 2016 16:51:51 -0600 Subject: [PATCH 67/86] fix(storage): add mutex to in-memory This adds a RW Mutex on the in-memory storage. --- pkg/storage/memory.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/storage/memory.go b/pkg/storage/memory.go index 28bf62495..bc57d7c4d 100644 --- a/pkg/storage/memory.go +++ b/pkg/storage/memory.go @@ -2,12 +2,14 @@ package storage import ( "errors" + "sync" "github.com/deis/tiller/pkg/proto/hapi/release" ) // Memory is an in-memory ReleaseStorage implementation. type Memory struct { + sync.RWMutex releases map[string]*release.Release } @@ -25,6 +27,8 @@ var ErrNotFound = errors.New("release not found") // // If the release is not found, an ErrNotFound error is returned. func (m *Memory) Read(k string) (*release.Release, error) { + m.RLock() + defer m.RUnlock() v, ok := m.releases[k] if !ok { return v, ErrNotFound @@ -34,12 +38,16 @@ func (m *Memory) Read(k string) (*release.Release, error) { // Create sets a release. func (m *Memory) Create(rel *release.Release) error { + m.Lock() + defer m.Unlock() m.releases[rel.Name] = rel return nil } // Update sets a release. func (m *Memory) Update(rel *release.Release) error { + m.Lock() + defer m.Unlock() if _, ok := m.releases[rel.Name]; !ok { return ErrNotFound } @@ -52,6 +60,8 @@ func (m *Memory) Update(rel *release.Release) error { // Delete removes a release. func (m *Memory) Delete(name string) (*release.Release, error) { + m.Lock() + defer m.Unlock() rel, ok := m.releases[name] if !ok { return nil, ErrNotFound @@ -62,6 +72,8 @@ func (m *Memory) Delete(name string) (*release.Release, error) { // List returns all releases. func (m *Memory) List() ([]*release.Release, error) { + m.RLock() + defer m.RUnlock() buf := make([]*release.Release, len(m.releases)) i := 0 for _, v := range m.releases { @@ -73,5 +85,7 @@ func (m *Memory) List() ([]*release.Release, error) { // Query searches all releases for matches. func (m *Memory) Query(labels map[string]string) ([]*release.Release, error) { + m.RLock() + defer m.RUnlock() return []*release.Release{}, errors.New("Cannot implement until release.Release is defined.") } From c9b3c362439f996d0c341237a52e293336a32ce6 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Mon, 25 Apr 2016 16:25:25 -0600 Subject: [PATCH 68/86] feat(helm): implement 'helm remove' --- _proto/hapi/release/info.proto | 9 ++- _proto/hapi/services/tiller.proto | 16 ++--- cmd/helm/remove.go | 52 +++++++++++++++ cmd/tiller/release_server.go | 48 ++++++++++++-- cmd/tiller/release_server_test.go | 36 +++++++++++ pkg/helm/client.go | 9 +++ pkg/helm/error.go | 1 + pkg/helm/helm.go | 13 +++- pkg/proto/hapi/release/info.pb.go | 19 ++++-- pkg/proto/hapi/services/tiller.pb.go | 95 ++++++++++++++-------------- 10 files changed, 229 insertions(+), 69 deletions(-) create mode 100644 cmd/helm/remove.go diff --git a/_proto/hapi/release/info.proto b/_proto/hapi/release/info.proto index fa8623ee8..382f4acfc 100644 --- a/_proto/hapi/release/info.proto +++ b/_proto/hapi/release/info.proto @@ -12,9 +12,12 @@ option go_package = "release"; // // message Info { - Status status = 1; + Status status = 1; - google.protobuf.Timestamp first_deployed = 2; + google.protobuf.Timestamp first_deployed = 2; - google.protobuf.Timestamp last_deployed = 3; + google.protobuf.Timestamp last_deployed = 3; + + // Deleted tracks when this object was deleted. + google.protobuf.Timestamp deleted = 4; } diff --git a/_proto/hapi/services/tiller.proto b/_proto/hapi/services/tiller.proto index 581fe659f..9768ca737 100644 --- a/_proto/hapi/services/tiller.proto +++ b/_proto/hapi/services/tiller.proto @@ -184,18 +184,14 @@ message InstallReleaseResponse { hapi.release.Release release = 1; } -// -// UninstallReleaseRequest: -// -// TODO -// +// UninstallReleaseRequest represents a request to uninstall a named release. message UninstallReleaseRequest { + // Name is the name of the release to delete. + string name = 1; } -// -// UninstallReleaseResponse: -// -// TODO -// +// UninstallReleaseResponse represents a successful response to an uninstall request. message UninstallReleaseResponse { + // Release is the release that was marked deleted. + hapi.release.Release release = 1; } diff --git a/cmd/helm/remove.go b/cmd/helm/remove.go new file mode 100644 index 000000000..cdaf93b13 --- /dev/null +++ b/cmd/helm/remove.go @@ -0,0 +1,52 @@ +package main + +import ( + "errors" + "fmt" + + "github.com/deis/tiller/pkg/helm" + "github.com/spf13/cobra" +) + +const removeDesc = ` +This command takes a release name, and then deletes the release from Kubernetes. +It removes all of the resources associated with the last release of the chart. + +Use the '--dry-run' flag to see which releases will be deleted without actually +deleting them. +` + +var removeDryRun bool + +var removeCommand = &cobra.Command{ + Use: "remove [flags] RELEASE_NAME", + Aliases: []string{"rm"}, + SuggestFor: []string{"delete", "del"}, + Short: "Given a release name, remove the release from Kubernetes", + Long: removeDesc, + RunE: rmRelease, +} + +func init() { + RootCommand.AddCommand(removeCommand) + removeCommand.Flags().BoolVar(&removeDryRun, "dry-run", false, "Simulate action, but don't actually do it.") +} + +func rmRelease(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("Command 'remove' requires a release name.") + } + + // TODO: Handle dry run use case. + if removeDryRun { + fmt.Printf("Deleting %s\n", args[0]) + return nil + } + + _, err := helm.UninstallRelease(args[0]) + if err != nil { + return err + } + + return nil +} diff --git a/cmd/tiller/release_server.go b/cmd/tiller/release_server.go index dba45f1c9..a9ad6edff 100644 --- a/cmd/tiller/release_server.go +++ b/cmd/tiller/release_server.go @@ -4,10 +4,12 @@ import ( "bytes" "errors" "log" + "time" "github.com/deis/tiller/cmd/tiller/environment" "github.com/deis/tiller/pkg/proto/hapi/release" "github.com/deis/tiller/pkg/proto/hapi/services" + "github.com/golang/protobuf/ptypes/timestamp" "github.com/technosophos/moniker" ctx "golang.org/x/net/context" ) @@ -26,7 +28,10 @@ type releaseServer struct { var ( // errNotImplemented is a temporary error for uninmplemented callbacks. errNotImplemented = errors.New("not implemented") - errMissingChart = errors.New("no chart provided") + // errMissingChart indicates that a chart was not provided. + errMissingChart = errors.New("no chart provided") + // errMissingRelease indicates that a release (name) was not provided. + errMissingRelease = errors.New("no release provided") ) func (s *releaseServer) ListReleases(req *services.ListReleasesRequest, stream services.ReleaseService_ListReleasesServer) error { @@ -53,7 +58,8 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea // We should probably make a name generator part of the Environment. namer := moniker.New() // TODO: Make sure this is unique. - name := namer.Name() + name := namer.NameSep("-") + ts := now() // Render the templates files, err := s.env.EngineYard.Default().Render(req.Chart, req.Values) @@ -77,7 +83,9 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea Chart: req.Chart, Config: req.Values, Info: &release.Info{ - Status: &release.Status{Code: release.Status_UNKNOWN}, + FirstDeployed: ts, + LastDeployed: ts, + Status: &release.Status{Code: release.Status_UNKNOWN}, }, Manifest: b.String(), } @@ -94,6 +102,38 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea return &services.InstallReleaseResponse{Release: r}, nil } +func now() *timestamp.Timestamp { + t := time.Now() + ts := ×tamp.Timestamp{ + Seconds: t.Unix(), + Nanos: int32(t.Nanosecond()), + } + return ts +} + func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) { - return nil, errNotImplemented + if req.Name == "" { + log.Printf("uninstall: Release not found: %s", req.Name) + return nil, errMissingRelease + } + + rel, err := s.env.Releases.Read(req.Name) + if err != nil { + log.Printf("uninstall: Release not loaded: %s", req.Name) + return nil, err + } + + log.Printf("uninstall: Deleting %s", req.Name) + rel.Info.Status.Code = release.Status_DELETED + rel.Info.Deleted = now() + + // TODO: Once KubeClient is ready, delete the resources. + log.Println("WARNING: Currently not deleting resources from k8s") + + if err := s.env.Releases.Update(rel); err != nil { + log.Printf("uninstall: Failed to store updated release: %s", err) + } + + res := services.UninstallReleaseResponse{Release: rel} + return &res, nil } diff --git a/cmd/tiller/release_server_test.go b/cmd/tiller/release_server_test.go index c09736341..878bbb9b5 100644 --- a/cmd/tiller/release_server_test.go +++ b/cmd/tiller/release_server_test.go @@ -6,6 +6,7 @@ import ( "github.com/deis/tiller/cmd/tiller/environment" "github.com/deis/tiller/pkg/proto/hapi/chart" + "github.com/deis/tiller/pkg/proto/hapi/release" "github.com/deis/tiller/pkg/proto/hapi/services" "github.com/deis/tiller/pkg/storage" "golang.org/x/net/context" @@ -97,6 +98,41 @@ func TestInstallReleaseDryRun(t *testing.T) { } } +func TestUninstallRelease(t *testing.T) { + c := context.Background() + rs := rsFixture() + rs.env.Releases.Create(&release.Release{ + Name: "angry-panda", + Info: &release.Info{ + FirstDeployed: now(), + Status: &release.Status{ + Code: release.Status_DEPLOYED, + }, + }, + }) + + req := &services.UninstallReleaseRequest{ + Name: "angry-panda", + } + + res, err := rs.UninstallRelease(c, req) + if err != nil { + t.Errorf("Failed uninstall: %s", err) + } + + if res.Release.Name != "angry-panda" { + t.Errorf("Expected angry-panda, got %q", res.Release.Name) + } + + if res.Release.Info.Status.Code != release.Status_DELETED { + t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code) + } + + if res.Release.Info.Deleted.Seconds <= 0 { + t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds) + } +} + func mockEnvironment() *environment.Environment { e := environment.New() e.Releases = storage.NewMemory() diff --git a/pkg/helm/client.go b/pkg/helm/client.go index a68a1efb4..69c6b4535 100644 --- a/pkg/helm/client.go +++ b/pkg/helm/client.go @@ -29,6 +29,15 @@ func (c *client) install(req *services.InstallReleaseRequest) (res *services.Ins return c.impl.InstallRelease(context.TODO(), req, c.cfg.CallOpts()...) } +func (c *client) uninstall(req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) { + if err := c.dial(); err != nil { + return nil, err + } + defer c.Close() + + return c.impl.UninstallRelease(context.TODO(), req, c.cfg.CallOpts()...) +} + func (c *client) Close() error { return c.conn.Close() } diff --git a/pkg/helm/error.go b/pkg/helm/error.go index 8f5a02499..b8d72a2c4 100644 --- a/pkg/helm/error.go +++ b/pkg/helm/error.go @@ -8,6 +8,7 @@ const ( errMissingValues = Error("missing chart values") ) +// Error represents a Helm client error. type Error string func (e Error) Error() string { diff --git a/pkg/helm/helm.go b/pkg/helm/helm.go index 7e535c1d2..963851064 100644 --- a/pkg/helm/helm.go +++ b/pkg/helm/helm.go @@ -6,31 +6,42 @@ import ( "github.com/deis/tiller/pkg/proto/hapi/services" ) +// Config defines a gRPC client's configuration. var Config = &config{ ServAddr: ":44134", Insecure: true, } +// ListReleases lists the current releases. func ListReleases(limit, offset int) (<-chan *services.ListReleasesResponse, error) { return nil, errNotImplemented } +// GetReleaseStatus returns the given release's status. func GetReleaseStatus(name string) (*services.GetReleaseStatusResponse, error) { return nil, errNotImplemented } +// GetReleaseContent returns the configuration for a given release. func GetReleaseContent(name string) (*services.GetReleaseContentResponse, error) { return nil, errNotImplemented } +// UpdateRelease updates a release to a new/different chart. +// TODO: This must take more than just name for an arg. func UpdateRelease(name string) (*services.UpdateReleaseResponse, error) { return nil, errNotImplemented } +// UninstallRelease uninstalls a named release and returns the response. func UninstallRelease(name string) (*services.UninstallReleaseResponse, error) { - return nil, errNotImplemented + u := &services.UninstallReleaseRequest{ + Name: name, + } + return Config.client().uninstall(u) } +// InstallRelease installs a new chart and returns the release response. func InstallRelease(ch *chart.Chart) (res *services.InstallReleaseResponse, err error) { chpb := new(chartpb.Chart) diff --git a/pkg/proto/hapi/release/info.pb.go b/pkg/proto/hapi/release/info.pb.go index 72c3225c5..4c3d8ed7c 100644 --- a/pkg/proto/hapi/release/info.pb.go +++ b/pkg/proto/hapi/release/info.pb.go @@ -39,6 +39,8 @@ type Info struct { Status *Status `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` FirstDeployed *google_protobuf.Timestamp `protobuf:"bytes,2,opt,name=first_deployed,json=firstDeployed" json:"first_deployed,omitempty"` LastDeployed *google_protobuf.Timestamp `protobuf:"bytes,3,opt,name=last_deployed,json=lastDeployed" json:"last_deployed,omitempty"` + // Deleted tracks when this object was deleted. + Deleted *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=deleted" json:"deleted,omitempty"` } func (m *Info) Reset() { *m = Info{} } @@ -67,23 +69,30 @@ func (m *Info) GetLastDeployed() *google_protobuf.Timestamp { return nil } +func (m *Info) GetDeleted() *google_protobuf.Timestamp { + if m != nil { + return m.Deleted + } + return nil +} + func init() { proto.RegisterType((*Info)(nil), "hapi.release.Info") } var fileDescriptor0 = []byte{ - // 194 bytes of a gzipped FileDescriptorProto + // 208 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcf, 0x48, 0x2c, 0xc8, 0xd4, 0x2f, 0x4a, 0xcd, 0x49, 0x4d, 0x2c, 0x4e, 0xd5, 0xcf, 0xcc, 0x4b, 0xcb, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x01, 0x49, 0xe8, 0x41, 0x25, 0xa4, 0xe4, 0xd3, 0xf3, 0xf3, 0xd3, 0x73, 0x52, 0xf5, 0xc1, 0x72, 0x49, 0xa5, 0x69, 0xfa, 0x25, 0x99, 0xb9, 0xa9, 0xc5, 0x25, 0x89, 0xb9, 0x05, 0x10, 0xe5, 0x52, 0x92, 0x28, 0xe6, 0x00, 0x65, 0x4a, 0x4a, 0x8b, 0x21, 0x52, 0x4a, - 0x3b, 0x18, 0xb9, 0x58, 0x3c, 0x81, 0x06, 0x0b, 0xe9, 0x70, 0xb1, 0x41, 0x24, 0x24, 0x18, 0x15, + 0xef, 0x18, 0xb9, 0x58, 0x3c, 0x81, 0x06, 0x0b, 0xe9, 0x70, 0xb1, 0x41, 0x24, 0x24, 0x18, 0x15, 0x18, 0x35, 0xb8, 0x8d, 0x44, 0xf4, 0x90, 0xed, 0xd0, 0x0b, 0x06, 0xcb, 0x05, 0x41, 0xd5, 0x08, 0x39, 0x72, 0xf1, 0xa5, 0x65, 0x16, 0x15, 0x97, 0xc4, 0xa7, 0xa4, 0x16, 0xe4, 0xe4, 0x57, 0xa6, 0xa6, 0x48, 0x30, 0x81, 0x75, 0x49, 0xe9, 0x41, 0xdc, 0xa2, 0x07, 0x73, 0x8b, 0x5e, 0x08, 0xcc, 0x2d, 0x41, 0xbc, 0x60, 0x1d, 0x2e, 0x50, 0x0d, 0x42, 0xf6, 0x5c, 0xbc, 0x39, 0x89, 0xc8, 0x26, - 0x30, 0x13, 0x34, 0x81, 0x07, 0xa4, 0x01, 0x66, 0x80, 0x13, 0x67, 0x14, 0x3b, 0xd4, 0x75, 0x49, - 0x6c, 0x60, 0xc5, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfb, 0xae, 0xa9, 0x99, 0x31, 0x01, - 0x00, 0x00, + 0x30, 0x13, 0x34, 0x81, 0x07, 0xa4, 0x01, 0x6e, 0x80, 0x09, 0x17, 0x7b, 0x0a, 0xd0, 0x75, 0x25, + 0x40, 0xad, 0x2c, 0x04, 0xb5, 0xc2, 0x94, 0x3a, 0x71, 0x46, 0xb1, 0x43, 0xfd, 0x94, 0xc4, 0x06, + 0x56, 0x67, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xeb, 0x9d, 0xa1, 0xf8, 0x67, 0x01, 0x00, 0x00, } diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go index e8922e10a..5c19915ce 100644 --- a/pkg/proto/hapi/services/tiller.pb.go +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -225,12 +225,10 @@ func (m *InstallReleaseResponse) GetRelease() *hapi_release2.Release { return nil } -// -// UninstallReleaseRequest: -// -// TODO -// +// UninstallReleaseRequest represents a request to uninstall a named release. type UninstallReleaseRequest struct { + // Name is the name of the release to delete. + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` } func (m *UninstallReleaseRequest) Reset() { *m = UninstallReleaseRequest{} } @@ -238,12 +236,10 @@ func (m *UninstallReleaseRequest) String() string { return proto.Comp func (*UninstallReleaseRequest) ProtoMessage() {} func (*UninstallReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{10} } -// -// UninstallReleaseResponse: -// -// TODO -// +// UninstallReleaseResponse represents a successful response to an uninstall request. type UninstallReleaseResponse struct { + // Release is the release that was marked deleted. + Release *hapi_release2.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` } func (m *UninstallReleaseResponse) Reset() { *m = UninstallReleaseResponse{} } @@ -251,6 +247,13 @@ func (m *UninstallReleaseResponse) String() string { return proto.Com func (*UninstallReleaseResponse) ProtoMessage() {} func (*UninstallReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{11} } +func (m *UninstallReleaseResponse) GetRelease() *hapi_release2.Release { + if m != nil { + return m.Release + } + return nil +} + func init() { proto.RegisterType((*ListReleasesRequest)(nil), "hapi.services.tiller.ListReleasesRequest") proto.RegisterType((*ListReleasesResponse)(nil), "hapi.services.tiller.ListReleasesResponse") @@ -541,40 +544,40 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ } var fileDescriptor1 = []byte{ - // 550 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0x4d, 0x6f, 0xd3, 0x40, - 0x10, 0xad, 0x09, 0x4d, 0xc3, 0xa4, 0x8d, 0xe8, 0xe2, 0xc4, 0x8e, 0x4f, 0x65, 0x2f, 0x94, 0x02, - 0x0e, 0x84, 0x23, 0x70, 0x21, 0x07, 0x54, 0xa9, 0xe2, 0x60, 0xd4, 0x0b, 0x97, 0xca, 0xa4, 0x1b, - 0xba, 0xc8, 0x59, 0x07, 0xef, 0x3a, 0x12, 0xdc, 0x39, 0xf2, 0x7f, 0xf8, 0x79, 0xd8, 0xfb, 0x61, - 0xc5, 0x89, 0x57, 0x31, 0x5c, 0x1c, 0xed, 0xbe, 0x37, 0xf3, 0x66, 0x66, 0xdf, 0x28, 0x10, 0xdc, - 0xc5, 0x2b, 0x3a, 0xe1, 0x24, 0x5b, 0xd3, 0x39, 0xe1, 0x13, 0x41, 0x93, 0x84, 0x64, 0xe1, 0x2a, - 0x4b, 0x45, 0x8a, 0xdc, 0x12, 0x0b, 0x0d, 0x16, 0x2a, 0x2c, 0x18, 0xc9, 0x88, 0xf9, 0x5d, 0x9c, - 0x09, 0xf5, 0x55, 0xec, 0xc0, 0xdb, 0xbc, 0x4f, 0xd9, 0x82, 0x7e, 0xd5, 0x80, 0x92, 0xc8, 0x48, - 0x42, 0x62, 0x4e, 0xcc, 0xaf, 0xc6, 0xc6, 0x35, 0x8c, 0x8b, 0x58, 0xe4, 0x5c, 0x41, 0x78, 0x06, - 0x8f, 0xae, 0x28, 0x17, 0x91, 0xc2, 0x78, 0x44, 0xbe, 0xe7, 0x84, 0x0b, 0xe4, 0xc2, 0x61, 0x42, - 0x97, 0x54, 0xf8, 0xce, 0x99, 0x73, 0xde, 0x89, 0xd4, 0x01, 0x8d, 0xa0, 0x9b, 0x2e, 0x16, 0x9c, - 0x08, 0xff, 0x9e, 0xbc, 0xd6, 0x27, 0xfc, 0xdb, 0x01, 0xb7, 0x9e, 0x85, 0xaf, 0x52, 0xc6, 0x49, - 0x99, 0x66, 0x9e, 0xe6, 0xac, 0x4a, 0x23, 0x0f, 0xb6, 0x34, 0x25, 0x5b, 0xa4, 0x22, 0x4e, 0xfc, - 0x8e, 0x62, 0xcb, 0x03, 0x7a, 0x05, 0x3d, 0x5d, 0x39, 0xf7, 0xef, 0x9f, 0x75, 0xce, 0xfb, 0xd3, - 0x61, 0x28, 0x47, 0x66, 0x7a, 0xd4, 0xaa, 0x51, 0x45, 0xc3, 0x6f, 0xc1, 0xfb, 0x40, 0x4c, 0x35, - 0x9f, 0x64, 0xbb, 0xa6, 0xb1, 0xc7, 0x70, 0xac, 0x69, 0x37, 0x2c, 0x5e, 0x12, 0x59, 0xd8, 0x83, - 0xa8, 0xaf, 0xef, 0x3e, 0x16, 0x57, 0xf8, 0x27, 0xf8, 0xbb, 0xd1, 0xba, 0xa1, 0xfd, 0xe1, 0xe8, - 0x0d, 0x0c, 0x0c, 0x45, 0x4d, 0x5a, 0x76, 0xd9, 0x9f, 0xba, 0xf5, 0xaa, 0x75, 0xe2, 0x93, 0x6c, - 0x53, 0x07, 0xbf, 0xdb, 0xd4, 0x9e, 0xa5, 0x4c, 0x10, 0x26, 0xfe, 0xa1, 0xf4, 0x2b, 0x18, 0x37, - 0x84, 0xeb, 0xda, 0x27, 0x70, 0xa4, 0xb9, 0x32, 0xd4, 0x3a, 0x47, 0xc3, 0xc2, 0x23, 0x70, 0xaf, - 0x57, 0xb7, 0xb1, 0x20, 0x06, 0x51, 0x85, 0x60, 0x0f, 0x86, 0x5b, 0xf7, 0x4a, 0x01, 0xff, 0x72, - 0x60, 0x78, 0xc9, 0x8a, 0xae, 0x93, 0xa4, 0x1e, 0x82, 0x9e, 0x14, 0x46, 0x28, 0x3d, 0xab, 0x95, - 0x4f, 0x95, 0xb2, 0x32, 0xf6, 0xac, 0xfc, 0x46, 0x0a, 0x47, 0x17, 0xd0, 0x5d, 0xc7, 0x49, 0x11, - 0xa3, 0xa7, 0x86, 0x6a, 0x4c, 0x69, 0xf8, 0x48, 0x33, 0x90, 0x07, 0x47, 0xb7, 0xd9, 0x8f, 0x9b, - 0x2c, 0x67, 0xd2, 0x31, 0xbd, 0xa8, 0x5b, 0x1c, 0xa3, 0x9c, 0xe1, 0x4b, 0x18, 0x6d, 0x97, 0xf1, - 0xbf, 0x33, 0x18, 0x83, 0x77, 0xcd, 0x68, 0x53, 0x4f, 0x38, 0x00, 0x7f, 0x17, 0x52, 0x3a, 0xd3, - 0x3f, 0x87, 0x30, 0x30, 0x0e, 0x52, 0x9b, 0x8d, 0x28, 0x1c, 0x6f, 0xee, 0x08, 0x7a, 0x1a, 0x36, - 0x2d, 0x7e, 0xd8, 0xb0, 0x8d, 0xc1, 0x45, 0x1b, 0xaa, 0x7e, 0x83, 0x83, 0x97, 0x0e, 0xe2, 0xf0, - 0x70, 0xdb, 0xc1, 0xe8, 0x45, 0x73, 0x0e, 0xcb, 0x9e, 0x04, 0x61, 0x5b, 0xba, 0x91, 0x45, 0x6b, - 0x38, 0xdd, 0xf1, 0x1e, 0xda, 0x9b, 0xa6, 0xee, 0xf1, 0x60, 0xd2, 0x9a, 0x5f, 0xe9, 0x7e, 0x83, - 0x93, 0x9a, 0x1b, 0x91, 0x65, 0x5a, 0x4d, 0x56, 0x0e, 0x9e, 0xb5, 0xe2, 0x56, 0x5a, 0x4b, 0x18, - 0xd4, 0x8d, 0x85, 0x2c, 0x09, 0x1a, 0xb7, 0x20, 0x78, 0xde, 0x8e, 0x5c, 0xc9, 0x15, 0xef, 0xb8, - 0xed, 0x30, 0xdb, 0x3b, 0x5a, 0x4c, 0x6a, 0x7b, 0x47, 0x9b, 0x71, 0xf1, 0xc1, 0x7b, 0xf8, 0xdc, - 0x33, 0xec, 0x2f, 0x5d, 0xf9, 0x27, 0xf1, 0xfa, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa9, 0x65, - 0x97, 0x54, 0xc0, 0x06, 0x00, 0x00, + // 558 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0x3d, 0x73, 0xd3, 0x40, + 0x10, 0x8d, 0x71, 0xe2, 0x98, 0x75, 0xe2, 0x21, 0x8b, 0x6c, 0x29, 0xaa, 0xc2, 0x35, 0x84, 0x40, + 0x64, 0x30, 0x25, 0xd0, 0xe0, 0x82, 0xc9, 0x90, 0xa1, 0x10, 0x93, 0x86, 0x26, 0x23, 0x9c, 0x33, + 0x11, 0x23, 0x9f, 0x8c, 0xee, 0xe4, 0x19, 0xe8, 0x29, 0xf9, 0x3f, 0xfc, 0x3c, 0xa4, 0xfb, 0xd0, + 0x48, 0xb6, 0x34, 0x11, 0x69, 0x64, 0xdf, 0xed, 0xdb, 0x7d, 0xfb, 0xf1, 0x56, 0x02, 0xf7, 0x36, + 0x58, 0x85, 0x13, 0x4e, 0x93, 0x75, 0x38, 0xa7, 0x7c, 0x22, 0xc2, 0x28, 0xa2, 0x89, 0xb7, 0x4a, + 0x62, 0x11, 0xa3, 0x95, 0xdb, 0x3c, 0x63, 0xf3, 0x94, 0xcd, 0x1d, 0x4b, 0x8f, 0xf9, 0x6d, 0x90, + 0x08, 0xf5, 0x54, 0x68, 0xd7, 0x2e, 0xdf, 0xc7, 0x6c, 0x11, 0x7e, 0xd3, 0x06, 0x45, 0x91, 0xd0, + 0x88, 0x06, 0x9c, 0x9a, 0x5f, 0x6d, 0x3b, 0xae, 0xd8, 0xb8, 0x08, 0x44, 0xca, 0x95, 0x89, 0xcc, + 0xe0, 0xf1, 0x65, 0xc8, 0x85, 0xaf, 0x6c, 0xdc, 0xa7, 0x3f, 0x52, 0xca, 0x05, 0x5a, 0xb0, 0x17, + 0x85, 0xcb, 0x50, 0x38, 0x9d, 0x93, 0xce, 0x69, 0xd7, 0x57, 0x07, 0x1c, 0x43, 0x2f, 0x5e, 0x2c, + 0x38, 0x15, 0xce, 0x03, 0x79, 0xad, 0x4f, 0xe4, 0x4f, 0x07, 0xac, 0x6a, 0x14, 0xbe, 0x8a, 0x19, + 0xa7, 0x79, 0x98, 0x79, 0x9c, 0xb2, 0x22, 0x8c, 0x3c, 0x34, 0x85, 0xc9, 0xd1, 0x22, 0x16, 0x41, + 0xe4, 0x74, 0x15, 0x5a, 0x1e, 0xf0, 0x15, 0xf4, 0x75, 0xe6, 0xdc, 0xd9, 0x3d, 0xe9, 0x9e, 0x0e, + 0xa6, 0x23, 0x4f, 0xb6, 0xcc, 0xd4, 0xa8, 0x59, 0xfd, 0x02, 0x46, 0xde, 0x82, 0xfd, 0x81, 0x9a, + 0x6c, 0x3e, 0xcb, 0x72, 0x4d, 0x61, 0x4f, 0xe0, 0x40, 0xc3, 0xae, 0x59, 0xb0, 0xa4, 0x32, 0xb1, + 0x87, 0xfe, 0x40, 0xdf, 0x7d, 0xca, 0xae, 0xc8, 0x2f, 0x70, 0xb6, 0xbd, 0x75, 0x41, 0x77, 0xbb, + 0xe3, 0x1b, 0x18, 0x1a, 0x88, 0xea, 0xb4, 0xac, 0x72, 0x30, 0xb5, 0xaa, 0x59, 0xeb, 0xc0, 0x87, + 0x49, 0x99, 0x87, 0xbc, 0x2b, 0x73, 0xcf, 0x62, 0x26, 0x28, 0x13, 0xff, 0x91, 0xfa, 0x25, 0x1c, + 0xd7, 0xb8, 0xeb, 0xdc, 0x27, 0xb0, 0xaf, 0xb1, 0xd2, 0xb5, 0xb1, 0x8f, 0x06, 0x45, 0xc6, 0x60, + 0x5d, 0xad, 0x6e, 0x02, 0x41, 0x8d, 0x45, 0x25, 0x42, 0x6c, 0x18, 0x6d, 0xdc, 0x2b, 0x06, 0xf2, + 0xbb, 0x03, 0xa3, 0x0b, 0x96, 0x55, 0x1d, 0x45, 0x55, 0x17, 0x7c, 0x9a, 0x09, 0x21, 0xd7, 0xac, + 0x66, 0x3e, 0x52, 0xcc, 0x4a, 0xd8, 0xb3, 0xfc, 0xe9, 0x2b, 0x3b, 0x9e, 0x41, 0x6f, 0x1d, 0x44, + 0x99, 0x8f, 0xee, 0x1a, 0x56, 0x90, 0x52, 0xf0, 0xbe, 0x46, 0xa0, 0x0d, 0xfb, 0x37, 0xc9, 0xcf, + 0xeb, 0x24, 0x65, 0x52, 0x31, 0x7d, 0xbf, 0x97, 0x1d, 0xfd, 0x94, 0x91, 0x0b, 0x18, 0x6f, 0xa6, + 0x71, 0xdf, 0x1e, 0x9c, 0x83, 0x7d, 0xc5, 0xc2, 0xda, 0x9a, 0x10, 0x76, 0x4b, 0x73, 0x90, 0xff, + 0xc9, 0x47, 0x70, 0xb6, 0xe1, 0xf7, 0xe4, 0x9e, 0xfe, 0xdd, 0x83, 0xa1, 0x91, 0xa1, 0x7a, 0x3d, + 0x60, 0x08, 0x07, 0xe5, 0x45, 0xc3, 0x67, 0x5e, 0xdd, 0xdb, 0xc3, 0xab, 0x59, 0x69, 0xf7, 0xac, + 0x0d, 0x54, 0x0f, 0x72, 0xe7, 0x65, 0x07, 0x39, 0x3c, 0xda, 0x5c, 0x03, 0x3c, 0xaf, 0x8f, 0xd1, + 0xb0, 0x6c, 0xae, 0xd7, 0x16, 0x6e, 0x68, 0x71, 0x0d, 0x47, 0x5b, 0x02, 0xc6, 0x3b, 0xc3, 0x54, + 0x17, 0xc5, 0x9d, 0xb4, 0xc6, 0x17, 0xbc, 0xdf, 0xe1, 0xb0, 0x22, 0x69, 0x6c, 0xe8, 0x56, 0xdd, + 0x3e, 0xb8, 0xcf, 0x5b, 0x61, 0x0b, 0xae, 0x25, 0x0c, 0xab, 0xea, 0xc4, 0x86, 0x00, 0xb5, 0xab, + 0xe4, 0xbe, 0x68, 0x07, 0x2e, 0xe8, 0xb2, 0x39, 0x6e, 0x4a, 0xb2, 0x69, 0x8e, 0x0d, 0x4a, 0x6f, + 0x9a, 0x63, 0x93, 0xd2, 0xc9, 0xce, 0x7b, 0xf8, 0xd2, 0x37, 0xe8, 0xaf, 0x3d, 0xf9, 0xa5, 0x79, + 0xfd, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x9f, 0x15, 0x68, 0xaf, 0x05, 0x07, 0x00, 0x00, } From dd2ff4f91601ef2f4cf9dd8dffdf84842a79164d Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 26 Apr 2016 12:36:08 -0600 Subject: [PATCH 69/86] feat(tiller): implement get and status --- _proto/hapi/services/tiller.proto | 48 ++++------- cmd/helm/get.go | 53 ++++++++++++ cmd/helm/status.go | 44 ++++++++++ cmd/tiller/release_server.go | 34 ++++---- cmd/tiller/release_server_test.go | 62 ++++++++++++++- pkg/helm/helm.go | 19 ++++- pkg/proto/hapi/services/tiller.pb.go | 115 ++++++++++++--------------- pkg/timeconv/doc.go | 7 ++ pkg/timeconv/timeconv.go | 32 ++++++++ pkg/timeconv/timeconv_test.go | 46 +++++++++++ 10 files changed, 344 insertions(+), 116 deletions(-) create mode 100644 cmd/helm/get.go create mode 100644 cmd/helm/status.go create mode 100644 pkg/timeconv/doc.go create mode 100644 pkg/timeconv/timeconv.go create mode 100644 pkg/timeconv/timeconv_test.go diff --git a/_proto/hapi/services/tiller.proto b/_proto/hapi/services/tiller.proto index 9768ca737..cd5adb958 100644 --- a/_proto/hapi/services/tiller.proto +++ b/_proto/hapi/services/tiller.proto @@ -5,7 +5,7 @@ package hapi.services.tiller; import "hapi/chart/chart.proto"; import "hapi/chart/config.proto"; import "hapi/release/release.proto"; -import "hapi/release/status.proto"; +import "hapi/release/info.proto"; option go_package = "services"; @@ -89,55 +89,39 @@ message ListReleasesRequest { message ListReleasesResponse { // The expected total number of releases to be returned int64 count = 1; - + // The zero-based offset at which the list is positioned int64 offset = 2; - + // The total number of queryable releases int64 total = 3; - + // The resulting releases repeated hapi.release.Release releases = 4; } -// -// GetReleaseStatusRequest: -// -// TODO -// +// GetReleaseStatusRequest is a request to get the status of a release. message GetReleaseStatusRequest { - // The name of the release - string release_name = 1; + // Name is the name of the release + string name = 1; } -// -// GetReleaseStatusResponse: -// -// TODO -// +// GetReleaseStatusResponse is the response indicating the status of the named release. message GetReleaseStatusResponse { - // The name of the release - string release_name = 1; - - // The release status - hapi.release.Status release_status = 2; + // Name is the name of the release. + string name = 1; + + // Info contains information about the release. + hapi.release.Info info = 2; } -// -// GetReleaseContentRequest: -// -// TODO -// +// GetReleaseContentRequest is a request to get the contents of a release. message GetReleaseContentRequest { // The name of the release - string release_name = 1; + string name = 1; } -// -// GetReleaseContentResponse: -// -// TODO -// +// GetReleaseContentResponse is a response containing the contents of a release. message GetReleaseContentResponse { // The release content hapi.release.Release release = 1; diff --git a/cmd/helm/get.go b/cmd/helm/get.go new file mode 100644 index 000000000..9ba031435 --- /dev/null +++ b/cmd/helm/get.go @@ -0,0 +1,53 @@ +package main + +import ( + "errors" + "fmt" + + "github.com/deis/tiller/pkg/helm" + "github.com/spf13/cobra" +) + +var getHelp = ` +This command shows the details of a named release. + +It can be used to get extended information about the release, including: + + - The values used to generate the release + - The chart used to generate the release + - The generated manifest file + +By default, this prints a human readable collection of information about the +chart, the supplied values, and the generated manifest file. +` + +var errReleaseRequired = errors.New("release name is required") + +var getCommand = &cobra.Command{ + Use: "get [flags] RELEASE_NAME", + Short: "Download a named release", + Long: getHelp, + RunE: getCmd, +} + +func init() { + RootCommand.AddCommand(getCommand) +} + +func getCmd(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errReleaseRequired + } + + res, err := helm.GetReleaseContent(args[0]) + if err != nil { + return err + } + + fmt.Printf("Chart/Version: %s %s\n", res.Release.Chart.Metadata.Name, res.Release.Chart.Metadata.Version) + fmt.Println("Config:") + fmt.Println(res.Release.Config) + fmt.Println("\nManifest:") + fmt.Println(res.Release.Manifest) + return nil +} diff --git a/cmd/helm/status.go b/cmd/helm/status.go new file mode 100644 index 000000000..2a1ec08ed --- /dev/null +++ b/cmd/helm/status.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + "time" + + "github.com/deis/tiller/pkg/helm" + "github.com/deis/tiller/pkg/timeconv" + "github.com/spf13/cobra" +) + +var statusHelp = ` +This command shows the status of a named release. +` + +var statusCommand = &cobra.Command{ + Use: "status [flags] RELEASE_NAME", + Short: "Displays the status of the named release", + Long: statusHelp, + RunE: status, +} + +func init() { + RootCommand.AddCommand(statusCommand) +} + +func status(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errReleaseRequired + } + + res, err := helm.GetReleaseStatus(args[0]) + if err != nil { + return err + } + + fmt.Printf("Last Deployed: %s\n", timeconv.Format(res.Info.LastDeployed, time.ANSIC)) + fmt.Printf("Status: %s\n", res.Info.Status.Code) + if res.Info.Status.Details != nil { + fmt.Printf("Details: %s\n", res.Info.Status.Details) + } + + return nil +} diff --git a/cmd/tiller/release_server.go b/cmd/tiller/release_server.go index a9ad6edff..887968803 100644 --- a/cmd/tiller/release_server.go +++ b/cmd/tiller/release_server.go @@ -4,12 +4,11 @@ import ( "bytes" "errors" "log" - "time" "github.com/deis/tiller/cmd/tiller/environment" "github.com/deis/tiller/pkg/proto/hapi/release" "github.com/deis/tiller/pkg/proto/hapi/services" - "github.com/golang/protobuf/ptypes/timestamp" + "github.com/deis/tiller/pkg/timeconv" "github.com/technosophos/moniker" ctx "golang.org/x/net/context" ) @@ -39,11 +38,25 @@ func (s *releaseServer) ListReleases(req *services.ListReleasesRequest, stream s } func (s *releaseServer) GetReleaseStatus(c ctx.Context, req *services.GetReleaseStatusRequest) (*services.GetReleaseStatusResponse, error) { - return nil, errNotImplemented + if req.Name == "" { + return nil, errMissingRelease + } + rel, err := s.env.Releases.Read(req.Name) + if err != nil { + return nil, err + } + if rel.Info == nil { + return nil, errors.New("release info is missing") + } + return &services.GetReleaseStatusResponse{Info: rel.Info}, nil } func (s *releaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleaseContentRequest) (*services.GetReleaseContentResponse, error) { - return nil, errNotImplemented + if req.Name == "" { + return nil, errMissingRelease + } + rel, err := s.env.Releases.Read(req.Name) + return &services.GetReleaseContentResponse{Release: rel}, err } func (s *releaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { @@ -59,7 +72,7 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea namer := moniker.New() // TODO: Make sure this is unique. name := namer.NameSep("-") - ts := now() + ts := timeconv.Now() // Render the templates files, err := s.env.EngineYard.Default().Render(req.Chart, req.Values) @@ -102,15 +115,6 @@ func (s *releaseServer) InstallRelease(c ctx.Context, req *services.InstallRelea return &services.InstallReleaseResponse{Release: r}, nil } -func now() *timestamp.Timestamp { - t := time.Now() - ts := ×tamp.Timestamp{ - Seconds: t.Unix(), - Nanos: int32(t.Nanosecond()), - } - return ts -} - func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) { if req.Name == "" { log.Printf("uninstall: Release not found: %s", req.Name) @@ -125,7 +129,7 @@ func (s *releaseServer) UninstallRelease(c ctx.Context, req *services.UninstallR log.Printf("uninstall: Deleting %s", req.Name) rel.Info.Status.Code = release.Status_DELETED - rel.Info.Deleted = now() + rel.Info.Deleted = timeconv.Now() // TODO: Once KubeClient is ready, delete the resources. log.Println("WARNING: Currently not deleting resources from k8s") diff --git a/cmd/tiller/release_server_test.go b/cmd/tiller/release_server_test.go index 878bbb9b5..cf1369a38 100644 --- a/cmd/tiller/release_server_test.go +++ b/cmd/tiller/release_server_test.go @@ -9,6 +9,8 @@ import ( "github.com/deis/tiller/pkg/proto/hapi/release" "github.com/deis/tiller/pkg/proto/hapi/services" "github.com/deis/tiller/pkg/storage" + "github.com/deis/tiller/pkg/timeconv" + "github.com/golang/protobuf/ptypes/timestamp" "golang.org/x/net/context" ) @@ -18,6 +20,28 @@ func rsFixture() *releaseServer { } } +func releaseMock() *release.Release { + date := timestamp.Timestamp{242085845, 0} + return &release.Release{ + Name: "angry-panda", + Info: &release.Info{ + FirstDeployed: &date, + LastDeployed: &date, + Status: &release.Status{Code: release.Status_DEPLOYED}, + }, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "foo", + Version: "0.1.0-beta.1", + }, + Templates: []*chart.Template{ + {Name: "foo.tpl", Data: []byte("Hello")}, + }, + }, + Config: &chart.Config{Raw: `name = "value"`}, + } +} + func TestInstallRelease(t *testing.T) { c := context.Background() rs := rsFixture() @@ -104,7 +128,7 @@ func TestUninstallRelease(t *testing.T) { rs.env.Releases.Create(&release.Release{ Name: "angry-panda", Info: &release.Info{ - FirstDeployed: now(), + FirstDeployed: timeconv.Now(), Status: &release.Status{ Code: release.Status_DEPLOYED, }, @@ -133,6 +157,42 @@ func TestUninstallRelease(t *testing.T) { } } +func TestGetReleaseContent(t *testing.T) { + c := context.Background() + rs := rsFixture() + rel := releaseMock() + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + + res, err := rs.GetReleaseContent(c, &services.GetReleaseContentRequest{Name: rel.Name}) + if err != nil { + t.Errorf("Error getting release content: %s", err) + } + + if res.Release.Chart.Metadata.Name != rel.Chart.Metadata.Name { + t.Errorf("Expected %q, got %q", rel.Chart.Metadata.Name, res.Release.Chart.Metadata.Name) + } +} + +func TestGetReleaseStatus(t *testing.T) { + c := context.Background() + rs := rsFixture() + rel := releaseMock() + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + + res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name}) + if err != nil { + t.Errorf("Error getting release content: %s", err) + } + + if res.Info.Status.Code != release.Status_DEPLOYED { + t.Errorf("Expected %d, got %d", release.Status_DEPLOYED, res.Info.Status.Code) + } +} + func mockEnvironment() *environment.Environment { e := environment.New() e.Releases = storage.NewMemory() diff --git a/pkg/helm/helm.go b/pkg/helm/helm.go index 963851064..5224d251f 100644 --- a/pkg/helm/helm.go +++ b/pkg/helm/helm.go @@ -4,6 +4,7 @@ import ( "github.com/deis/tiller/pkg/chart" chartpb "github.com/deis/tiller/pkg/proto/hapi/chart" "github.com/deis/tiller/pkg/proto/hapi/services" + "golang.org/x/net/context" ) // Config defines a gRPC client's configuration. @@ -19,12 +20,26 @@ func ListReleases(limit, offset int) (<-chan *services.ListReleasesResponse, err // GetReleaseStatus returns the given release's status. func GetReleaseStatus(name string) (*services.GetReleaseStatusResponse, error) { - return nil, errNotImplemented + c := Config.client() + if err := c.dial(); err != nil { + return nil, err + } + defer c.Close() + + req := &services.GetReleaseStatusRequest{Name: name} + return c.impl.GetReleaseStatus(context.TODO(), req, c.cfg.CallOpts()...) } // GetReleaseContent returns the configuration for a given release. func GetReleaseContent(name string) (*services.GetReleaseContentResponse, error) { - return nil, errNotImplemented + c := Config.client() + if err := c.dial(); err != nil { + return nil, err + } + defer c.Close() + + req := &services.GetReleaseContentRequest{Name: name} + return c.impl.GetReleaseContent(context.TODO(), req, c.cfg.CallOpts()...) } // UpdateRelease updates a release to a new/different chart. diff --git a/pkg/proto/hapi/services/tiller.pb.go b/pkg/proto/hapi/services/tiller.pb.go index 5c19915ce..265544d6f 100644 --- a/pkg/proto/hapi/services/tiller.pb.go +++ b/pkg/proto/hapi/services/tiller.pb.go @@ -10,7 +10,7 @@ import math "math" import hapi_chart3 "github.com/deis/tiller/pkg/proto/hapi/chart" import hapi_chart "github.com/deis/tiller/pkg/proto/hapi/chart" import hapi_release2 "github.com/deis/tiller/pkg/proto/hapi/release" -import hapi_release "github.com/deis/tiller/pkg/proto/hapi/release" +import hapi_release1 "github.com/deis/tiller/pkg/proto/hapi/release" import ( context "golang.org/x/net/context" @@ -67,14 +67,10 @@ func (m *ListReleasesResponse) GetReleases() []*hapi_release2.Release { return nil } -// -// GetReleaseStatusRequest: -// -// TODO -// +// GetReleaseStatusRequest is a request to get the status of a release. type GetReleaseStatusRequest struct { - // The name of the release - ReleaseName string `protobuf:"bytes,1,opt,name=release_name,json=releaseName" json:"release_name,omitempty"` + // Name is the name of the release + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` } func (m *GetReleaseStatusRequest) Reset() { *m = GetReleaseStatusRequest{} } @@ -82,16 +78,12 @@ func (m *GetReleaseStatusRequest) String() string { return proto.Comp func (*GetReleaseStatusRequest) ProtoMessage() {} func (*GetReleaseStatusRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{2} } -// -// GetReleaseStatusResponse: -// -// TODO -// +// GetReleaseStatusResponse is the response indicating the status of the named release. type GetReleaseStatusResponse struct { - // The name of the release - ReleaseName string `protobuf:"bytes,1,opt,name=release_name,json=releaseName" json:"release_name,omitempty"` - // The release status - ReleaseStatus *hapi_release.Status `protobuf:"bytes,2,opt,name=release_status,json=releaseStatus" json:"release_status,omitempty"` + // Name is the name of the release. + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Info contains information about the release. + Info *hapi_release1.Info `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` } func (m *GetReleaseStatusResponse) Reset() { *m = GetReleaseStatusResponse{} } @@ -99,21 +91,17 @@ func (m *GetReleaseStatusResponse) String() string { return proto.Com func (*GetReleaseStatusResponse) ProtoMessage() {} func (*GetReleaseStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{3} } -func (m *GetReleaseStatusResponse) GetReleaseStatus() *hapi_release.Status { +func (m *GetReleaseStatusResponse) GetInfo() *hapi_release1.Info { if m != nil { - return m.ReleaseStatus + return m.Info } return nil } -// -// GetReleaseContentRequest: -// -// TODO -// +// GetReleaseContentRequest is a request to get the contents of a release. type GetReleaseContentRequest struct { // The name of the release - ReleaseName string `protobuf:"bytes,1,opt,name=release_name,json=releaseName" json:"release_name,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` } func (m *GetReleaseContentRequest) Reset() { *m = GetReleaseContentRequest{} } @@ -121,11 +109,7 @@ func (m *GetReleaseContentRequest) String() string { return proto.Com func (*GetReleaseContentRequest) ProtoMessage() {} func (*GetReleaseContentRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{4} } -// -// GetReleaseContentResponse: -// -// TODO -// +// GetReleaseContentResponse is a response containing the contents of a release. type GetReleaseContentResponse struct { // The release content Release *hapi_release2.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` @@ -544,40 +528,39 @@ var _ReleaseService_serviceDesc = grpc.ServiceDesc{ } var fileDescriptor1 = []byte{ - // 558 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0x3d, 0x73, 0xd3, 0x40, - 0x10, 0x8d, 0x71, 0xe2, 0x98, 0x75, 0xe2, 0x21, 0x8b, 0x6c, 0x29, 0xaa, 0xc2, 0x35, 0x84, 0x40, - 0x64, 0x30, 0x25, 0xd0, 0xe0, 0x82, 0xc9, 0x90, 0xa1, 0x10, 0x93, 0x86, 0x26, 0x23, 0x9c, 0x33, - 0x11, 0x23, 0x9f, 0x8c, 0xee, 0xe4, 0x19, 0xe8, 0x29, 0xf9, 0x3f, 0xfc, 0x3c, 0xa4, 0xfb, 0xd0, - 0x48, 0xb6, 0x34, 0x11, 0x69, 0x64, 0xdf, 0xed, 0xdb, 0x7d, 0xfb, 0xf1, 0x56, 0x02, 0xf7, 0x36, - 0x58, 0x85, 0x13, 0x4e, 0x93, 0x75, 0x38, 0xa7, 0x7c, 0x22, 0xc2, 0x28, 0xa2, 0x89, 0xb7, 0x4a, - 0x62, 0x11, 0xa3, 0x95, 0xdb, 0x3c, 0x63, 0xf3, 0x94, 0xcd, 0x1d, 0x4b, 0x8f, 0xf9, 0x6d, 0x90, - 0x08, 0xf5, 0x54, 0x68, 0xd7, 0x2e, 0xdf, 0xc7, 0x6c, 0x11, 0x7e, 0xd3, 0x06, 0x45, 0x91, 0xd0, - 0x88, 0x06, 0x9c, 0x9a, 0x5f, 0x6d, 0x3b, 0xae, 0xd8, 0xb8, 0x08, 0x44, 0xca, 0x95, 0x89, 0xcc, - 0xe0, 0xf1, 0x65, 0xc8, 0x85, 0xaf, 0x6c, 0xdc, 0xa7, 0x3f, 0x52, 0xca, 0x05, 0x5a, 0xb0, 0x17, - 0x85, 0xcb, 0x50, 0x38, 0x9d, 0x93, 0xce, 0x69, 0xd7, 0x57, 0x07, 0x1c, 0x43, 0x2f, 0x5e, 0x2c, - 0x38, 0x15, 0xce, 0x03, 0x79, 0xad, 0x4f, 0xe4, 0x4f, 0x07, 0xac, 0x6a, 0x14, 0xbe, 0x8a, 0x19, - 0xa7, 0x79, 0x98, 0x79, 0x9c, 0xb2, 0x22, 0x8c, 0x3c, 0x34, 0x85, 0xc9, 0xd1, 0x22, 0x16, 0x41, - 0xe4, 0x74, 0x15, 0x5a, 0x1e, 0xf0, 0x15, 0xf4, 0x75, 0xe6, 0xdc, 0xd9, 0x3d, 0xe9, 0x9e, 0x0e, - 0xa6, 0x23, 0x4f, 0xb6, 0xcc, 0xd4, 0xa8, 0x59, 0xfd, 0x02, 0x46, 0xde, 0x82, 0xfd, 0x81, 0x9a, - 0x6c, 0x3e, 0xcb, 0x72, 0x4d, 0x61, 0x4f, 0xe0, 0x40, 0xc3, 0xae, 0x59, 0xb0, 0xa4, 0x32, 0xb1, - 0x87, 0xfe, 0x40, 0xdf, 0x7d, 0xca, 0xae, 0xc8, 0x2f, 0x70, 0xb6, 0xbd, 0x75, 0x41, 0x77, 0xbb, - 0xe3, 0x1b, 0x18, 0x1a, 0x88, 0xea, 0xb4, 0xac, 0x72, 0x30, 0xb5, 0xaa, 0x59, 0xeb, 0xc0, 0x87, - 0x49, 0x99, 0x87, 0xbc, 0x2b, 0x73, 0xcf, 0x62, 0x26, 0x28, 0x13, 0xff, 0x91, 0xfa, 0x25, 0x1c, - 0xd7, 0xb8, 0xeb, 0xdc, 0x27, 0xb0, 0xaf, 0xb1, 0xd2, 0xb5, 0xb1, 0x8f, 0x06, 0x45, 0xc6, 0x60, - 0x5d, 0xad, 0x6e, 0x02, 0x41, 0x8d, 0x45, 0x25, 0x42, 0x6c, 0x18, 0x6d, 0xdc, 0x2b, 0x06, 0xf2, - 0xbb, 0x03, 0xa3, 0x0b, 0x96, 0x55, 0x1d, 0x45, 0x55, 0x17, 0x7c, 0x9a, 0x09, 0x21, 0xd7, 0xac, - 0x66, 0x3e, 0x52, 0xcc, 0x4a, 0xd8, 0xb3, 0xfc, 0xe9, 0x2b, 0x3b, 0x9e, 0x41, 0x6f, 0x1d, 0x44, - 0x99, 0x8f, 0xee, 0x1a, 0x56, 0x90, 0x52, 0xf0, 0xbe, 0x46, 0xa0, 0x0d, 0xfb, 0x37, 0xc9, 0xcf, - 0xeb, 0x24, 0x65, 0x52, 0x31, 0x7d, 0xbf, 0x97, 0x1d, 0xfd, 0x94, 0x91, 0x0b, 0x18, 0x6f, 0xa6, - 0x71, 0xdf, 0x1e, 0x9c, 0x83, 0x7d, 0xc5, 0xc2, 0xda, 0x9a, 0x10, 0x76, 0x4b, 0x73, 0x90, 0xff, - 0xc9, 0x47, 0x70, 0xb6, 0xe1, 0xf7, 0xe4, 0x9e, 0xfe, 0xdd, 0x83, 0xa1, 0x91, 0xa1, 0x7a, 0x3d, - 0x60, 0x08, 0x07, 0xe5, 0x45, 0xc3, 0x67, 0x5e, 0xdd, 0xdb, 0xc3, 0xab, 0x59, 0x69, 0xf7, 0xac, - 0x0d, 0x54, 0x0f, 0x72, 0xe7, 0x65, 0x07, 0x39, 0x3c, 0xda, 0x5c, 0x03, 0x3c, 0xaf, 0x8f, 0xd1, - 0xb0, 0x6c, 0xae, 0xd7, 0x16, 0x6e, 0x68, 0x71, 0x0d, 0x47, 0x5b, 0x02, 0xc6, 0x3b, 0xc3, 0x54, - 0x17, 0xc5, 0x9d, 0xb4, 0xc6, 0x17, 0xbc, 0xdf, 0xe1, 0xb0, 0x22, 0x69, 0x6c, 0xe8, 0x56, 0xdd, - 0x3e, 0xb8, 0xcf, 0x5b, 0x61, 0x0b, 0xae, 0x25, 0x0c, 0xab, 0xea, 0xc4, 0x86, 0x00, 0xb5, 0xab, - 0xe4, 0xbe, 0x68, 0x07, 0x2e, 0xe8, 0xb2, 0x39, 0x6e, 0x4a, 0xb2, 0x69, 0x8e, 0x0d, 0x4a, 0x6f, - 0x9a, 0x63, 0x93, 0xd2, 0xc9, 0xce, 0x7b, 0xf8, 0xd2, 0x37, 0xe8, 0xaf, 0x3d, 0xf9, 0xa5, 0x79, - 0xfd, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x9f, 0x15, 0x68, 0xaf, 0x05, 0x07, 0x00, 0x00, + // 540 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0xcd, 0x6e, 0xd3, 0x40, + 0x10, 0xae, 0x49, 0x9a, 0x86, 0x29, 0x54, 0x74, 0xc8, 0x8f, 0xf1, 0xa9, 0xda, 0x03, 0x94, 0x42, + 0x1d, 0x08, 0x6f, 0x40, 0x0e, 0x28, 0xa2, 0xa7, 0x45, 0xe5, 0xc0, 0x05, 0x99, 0x74, 0x43, 0x17, + 0x39, 0xeb, 0xe0, 0x5d, 0x47, 0xe2, 0x01, 0x38, 0xf2, 0x3e, 0x3c, 0x1e, 0xf6, 0xfe, 0x58, 0x71, + 0x62, 0x53, 0xab, 0x17, 0xb7, 0xbb, 0xdf, 0x37, 0xf3, 0xcd, 0xce, 0x7c, 0xa3, 0x40, 0x70, 0x1b, + 0xad, 0xf9, 0x44, 0xb2, 0x74, 0xc3, 0x17, 0x4c, 0x4e, 0x14, 0x8f, 0x63, 0x96, 0x86, 0xeb, 0x34, + 0x51, 0x09, 0x0e, 0x0a, 0x2c, 0x74, 0x58, 0x68, 0xb0, 0x60, 0xa4, 0x23, 0x16, 0xb7, 0x51, 0xaa, + 0xcc, 0xd7, 0xb0, 0x83, 0xf1, 0xf6, 0x7d, 0x22, 0x96, 0xfc, 0xbb, 0x05, 0x8c, 0x44, 0xca, 0x62, + 0x16, 0x49, 0xe6, 0xfe, 0x56, 0x82, 0x1c, 0xc6, 0xc5, 0x32, 0x31, 0x00, 0x99, 0xc1, 0xd3, 0x2b, + 0x2e, 0x15, 0x35, 0x88, 0xa4, 0xec, 0x67, 0xc6, 0xa4, 0xc2, 0x01, 0x1c, 0xc6, 0x7c, 0xc5, 0x95, + 0xef, 0x9d, 0x79, 0xe7, 0x1d, 0x6a, 0x0e, 0x38, 0x82, 0x5e, 0xb2, 0x5c, 0x4a, 0xa6, 0xfc, 0x07, + 0xfa, 0xda, 0x9e, 0xc8, 0x1f, 0x0f, 0x06, 0xd5, 0x2c, 0x72, 0x9d, 0x08, 0xc9, 0x8a, 0x34, 0x8b, + 0x24, 0x13, 0x65, 0x1a, 0x7d, 0x68, 0x4a, 0x53, 0xb0, 0x55, 0xa2, 0xa2, 0xd8, 0xef, 0x18, 0xb6, + 0x3e, 0xe0, 0x5b, 0xe8, 0xdb, 0xba, 0xa5, 0xdf, 0x3d, 0xeb, 0x9c, 0x1f, 0x4f, 0x87, 0xa1, 0x6e, + 0x98, 0x7b, 0xa1, 0x55, 0xa5, 0x25, 0x8d, 0x5c, 0xc2, 0xf8, 0x03, 0x73, 0xd5, 0x7c, 0x52, 0x91, + 0xca, 0xca, 0x87, 0x21, 0x74, 0x45, 0xb4, 0x62, 0xba, 0xa0, 0x87, 0x54, 0xff, 0x4f, 0x3e, 0x83, + 0xbf, 0x4f, 0xb7, 0x2f, 0xa8, 0xe1, 0xe3, 0x73, 0xe8, 0x16, 0x1d, 0xd4, 0xd5, 0x1f, 0x4f, 0xb1, + 0x5a, 0xcd, 0x3c, 0x47, 0xa8, 0xc6, 0x49, 0xb8, 0x9d, 0x77, 0x96, 0x08, 0xc5, 0x84, 0xfa, 0x5f, + 0x1d, 0x57, 0xf0, 0xac, 0x86, 0x6f, 0x0b, 0x99, 0xc0, 0x91, 0x95, 0xd0, 0x31, 0x8d, 0x5d, 0x70, + 0x2c, 0x32, 0x82, 0xc1, 0xf5, 0xfa, 0x26, 0x52, 0xcc, 0x21, 0x46, 0x99, 0x8c, 0x61, 0xb8, 0x73, + 0x6f, 0x14, 0xc8, 0x6f, 0x0f, 0x86, 0x73, 0x21, 0xf3, 0x9e, 0xc7, 0xd5, 0x10, 0x7c, 0x91, 0x8f, + 0xb1, 0xf0, 0x9b, 0x55, 0x3e, 0x35, 0xca, 0xc6, 0x94, 0xb3, 0xe2, 0x4b, 0x0d, 0x8e, 0x17, 0xd0, + 0xdb, 0x44, 0x71, 0x1e, 0x53, 0xed, 0x8d, 0x65, 0x6a, 0xb3, 0x52, 0xcb, 0xc0, 0x31, 0x1c, 0xdd, + 0xa4, 0xbf, 0xbe, 0xa6, 0x99, 0xd0, 0xf3, 0xee, 0xd3, 0x5e, 0x7e, 0xa4, 0x99, 0x20, 0x73, 0x18, + 0xed, 0x96, 0x71, 0xdf, 0x1e, 0xe4, 0x46, 0xb8, 0x16, 0xbc, 0xf6, 0x4d, 0x75, 0x03, 0xf8, 0x08, + 0xfe, 0x3e, 0xfd, 0x9e, 0xda, 0xd3, 0xbf, 0x87, 0x70, 0xe2, 0x3c, 0x65, 0x56, 0x1b, 0x39, 0x3c, + 0xda, 0x5e, 0x13, 0x7c, 0x19, 0xd6, 0x6d, 0x7e, 0x58, 0xb3, 0x90, 0xc1, 0x45, 0x1b, 0xaa, 0x1d, + 0xe4, 0xc1, 0x1b, 0x0f, 0x25, 0x3c, 0xd9, 0xf5, 0x34, 0x5e, 0xd6, 0xe7, 0x68, 0x58, 0x95, 0x20, + 0x6c, 0x4b, 0x77, 0xb2, 0xb8, 0x81, 0xd3, 0x3d, 0x03, 0xe3, 0x9d, 0x69, 0xaa, 0x9b, 0x11, 0x4c, + 0x5a, 0xf3, 0x4b, 0xdd, 0x1f, 0xf0, 0xb8, 0x62, 0x69, 0x6c, 0xe8, 0x56, 0xdd, 0x3e, 0x04, 0xaf, + 0x5a, 0x71, 0x4b, 0xad, 0x15, 0x9c, 0x54, 0xdd, 0x89, 0x0d, 0x09, 0x6a, 0x57, 0x29, 0x78, 0xdd, + 0x8e, 0x5c, 0xca, 0xe5, 0x73, 0xdc, 0xb5, 0x64, 0xd3, 0x1c, 0x1b, 0x9c, 0xde, 0x34, 0xc7, 0x26, + 0xa7, 0x93, 0x83, 0xf7, 0xf0, 0xa5, 0xef, 0xd8, 0xdf, 0x7a, 0xfa, 0x77, 0xe2, 0xdd, 0xbf, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x8c, 0xd6, 0xc7, 0x2c, 0xc1, 0x06, 0x00, 0x00, } diff --git a/pkg/timeconv/doc.go b/pkg/timeconv/doc.go new file mode 100644 index 000000000..42140ff35 --- /dev/null +++ b/pkg/timeconv/doc.go @@ -0,0 +1,7 @@ +/*Package timeconv contains utilities for converting time. + +The gRPC/Protobuf libraries contain time implementations that require conversion +to and from Go times. This library provides utilities and convenience functions +for performing conversions. +*/ +package timeconv diff --git a/pkg/timeconv/timeconv.go b/pkg/timeconv/timeconv.go new file mode 100644 index 000000000..8a3fb31c4 --- /dev/null +++ b/pkg/timeconv/timeconv.go @@ -0,0 +1,32 @@ +package timeconv + +import ( + "time" + + "github.com/golang/protobuf/ptypes/timestamp" +) + +// Now creates a timestamp.Timestamp representing the current time. +func Now() *timestamp.Timestamp { + return Timestamp(time.Now()) +} + +// Timestamp converts a time.Time to a protobuf *timestamp.Timestamp. +func Timestamp(t time.Time) *timestamp.Timestamp { + return ×tamp.Timestamp{ + Seconds: t.Unix(), + Nanos: int32(t.Nanosecond()), + } +} + +// Time converts a protobuf *timestamp.Timestamp to a time.Time. +func Time(ts *timestamp.Timestamp) time.Time { + return time.Unix(ts.Seconds, int64(ts.Nanos)) +} + +// Format formats a *timestamp.Timestamp into a string. +// +// This follows the rules for time.Time.Format(). +func Format(ts *timestamp.Timestamp, layout string) string { + return Time(ts).Format(layout) +} diff --git a/pkg/timeconv/timeconv_test.go b/pkg/timeconv/timeconv_test.go new file mode 100644 index 000000000..1090155b8 --- /dev/null +++ b/pkg/timeconv/timeconv_test.go @@ -0,0 +1,46 @@ +package timeconv + +import ( + "testing" + "time" +) + +func TestNow(t *testing.T) { + now := time.Now() + ts := Now() + var drift int64 = 5 + if ts.Seconds < int64(now.Second())-drift { + t.Errorf("Unexpected time drift: %d", ts.Seconds) + } +} + +func TestTimestamp(t *testing.T) { + now := time.Now() + ts := Timestamp(now) + + if now.Unix() != ts.Seconds { + t.Errorf("Unexpected time drift: %d to %d", now.Second(), ts.Seconds) + } + + if now.Nanosecond() != int(ts.Nanos) { + t.Errorf("Unexpected nano drift: %d to %d", now.Nanosecond(), ts.Nanos) + } +} + +func TestTime(t *testing.T) { + nowts := Now() + now := Time(nowts) + + if now.Unix() != nowts.Seconds { + t.Errorf("Unexpected time drift %d", now.Unix()) + } +} + +func TestFormat(t *testing.T) { + now := time.Now() + nowts := Timestamp(now) + + if now.Format(time.ANSIC) != Format(nowts, time.ANSIC) { + t.Error("Format mismatch") + } +} From bd61e3ab86437513781cc01491533a1e8b099a3d Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Tue, 26 Apr 2016 14:14:28 -0700 Subject: [PATCH 70/86] fix(golint): fix printing blank lines --- scripts/validate-go.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/validate-go.sh b/scripts/validate-go.sh index cf4e4a8f6..1f0aac130 100755 --- a/scripts/validate-go.sh +++ b/scripts/validate-go.sh @@ -17,7 +17,8 @@ hash godir 2>/dev/null || go get -u github.com/Masterminds/godir echo "==> Running golint..." for pkg in $(godir pkgs | grep -v proto); do - if golint_out=$(golint "$pkg" 2>&1); then + golint_out=$(golint "$pkg" 2>&1) + if [[ -n "$golint_out" ]]; then echo "${yellow}${golint_out}${reset}" fi done From cf9c8ebe3cd7a9f75102d6b2ee85cdb14a3bda26 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 26 Apr 2016 17:20:37 -0600 Subject: [PATCH 71/86] feat(helm): add a very basic lint command This adds a lint command that just checks to see if the chart is parseable and if the templates are paresable. --- cmd/helm/lint.go | 39 ++++++++++++ pkg/lint/chartfile.go | 45 +++++++++++++ pkg/lint/chartfile_test.go | 22 +++++++ pkg/lint/doc.go | 6 ++ pkg/lint/lint.go | 7 +++ pkg/lint/message.go | 27 ++++++++ pkg/lint/message_test.go | 20 ++++++ pkg/lint/template.go | 63 +++++++++++++++++++ pkg/lint/template_test.go | 20 ++++++ pkg/lint/testdata/albatross/Chart.yaml | 3 + .../albatross/templates/albatross.yaml | 2 + .../testdata/albatross/templates/fail.yaml | 1 + pkg/lint/testdata/albatross/values.toml | 1 + pkg/lint/testdata/badchartfile/Chart.yaml | 3 + pkg/lint/testdata/badchartfile/values.toml | 4 ++ 15 files changed, 263 insertions(+) create mode 100644 cmd/helm/lint.go create mode 100644 pkg/lint/chartfile.go create mode 100644 pkg/lint/chartfile_test.go create mode 100644 pkg/lint/doc.go create mode 100644 pkg/lint/lint.go create mode 100644 pkg/lint/message.go create mode 100644 pkg/lint/message_test.go create mode 100644 pkg/lint/template.go create mode 100644 pkg/lint/template_test.go create mode 100644 pkg/lint/testdata/albatross/Chart.yaml create mode 100644 pkg/lint/testdata/albatross/templates/albatross.yaml create mode 100644 pkg/lint/testdata/albatross/templates/fail.yaml create mode 100644 pkg/lint/testdata/albatross/values.toml create mode 100644 pkg/lint/testdata/badchartfile/Chart.yaml create mode 100644 pkg/lint/testdata/badchartfile/values.toml diff --git a/cmd/helm/lint.go b/cmd/helm/lint.go new file mode 100644 index 000000000..1210d15d0 --- /dev/null +++ b/cmd/helm/lint.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + + "github.com/deis/tiller/pkg/lint" + "github.com/spf13/cobra" +) + +var longLintHelp = ` +This command takes a path to a chart and runs a series of tests to verify that +the chart is well-formed. + +If the linter encounters things that will cause the chart to fail installation, +it will emit [ERROR] messages. If it encounters issues that break with convention +or recommendation, it will emit [WARNING] messages. +` + +var lintCommand = &cobra.Command{ + Use: "lint [flags] PATH", + Short: "Examines a chart for possible issues", + Long: longLintHelp, + Run: lintCmd, +} + +func init() { + RootCommand.AddCommand(lintCommand) +} + +func lintCmd(cmd *cobra.Command, args []string) { + path := "." + if len(args) > 0 { + path = args[0] + } + issues := lint.All(path) + for _, i := range issues { + fmt.Printf("%s\n", i) + } +} diff --git a/pkg/lint/chartfile.go b/pkg/lint/chartfile.go new file mode 100644 index 000000000..b8a33a30b --- /dev/null +++ b/pkg/lint/chartfile.go @@ -0,0 +1,45 @@ +package lint + +import ( + "os" + "path/filepath" + + chartutil "github.com/deis/tiller/pkg/chart" +) + +func Chartfile(basepath string) (m []Message) { + m = []Message{} + + path := filepath.Join(basepath, "Chart.yaml") + if fi, err := os.Stat(path); err != nil { + m = append(m, Message{Severity: ErrorSev, Text: "No Chart.yaml file"}) + return + } else if fi.IsDir() { + m = append(m, Message{Severity: ErrorSev, Text: "Chart.yaml is a directory."}) + return + } + + cf, err := chartutil.LoadChartfile(path) + if err != nil { + m = append(m, Message{ + Severity: ErrorSev, + Text: err.Error(), + }) + return + } + + if cf.Name == "" { + m = append(m, Message{ + Severity: ErrorSev, + Text: "Chart.yaml: 'name' is required", + }) + } + + if cf.Version == "" || cf.Version == "0.0.0" { + m = append(m, Message{ + Severity: ErrorSev, + Text: "Chart.yaml: 'version' is required, and must be greater than 0.0.0", + }) + } + return +} diff --git a/pkg/lint/chartfile_test.go b/pkg/lint/chartfile_test.go new file mode 100644 index 000000000..c8c3be911 --- /dev/null +++ b/pkg/lint/chartfile_test.go @@ -0,0 +1,22 @@ +package lint + +import ( + "testing" +) + +const badchartfile = "testdata/badchartfile" + +func TestChartfile(t *testing.T) { + msgs := Chartfile(badchartfile) + if len(msgs) != 2 { + t.Errorf("Expected 2 errors, got %d", len(msgs)) + } + + if msgs[0].Text != "Chart.yaml: 'name' is required" { + t.Errorf("Unexpected message 0: %s", msgs[0].Text) + } + + if msgs[1].Text != "Chart.yaml: 'version' is required, and must be greater than 0.0.0" { + t.Errorf("Unexpected message 1: %s", msgs[1].Text) + } +} diff --git a/pkg/lint/doc.go b/pkg/lint/doc.go new file mode 100644 index 000000000..f2cc19670 --- /dev/null +++ b/pkg/lint/doc.go @@ -0,0 +1,6 @@ +/*Package lint contains tools for linting charts. + +Linting is the process of testing charts for errors or warnings regarding +formatting, compilation, or standards compliance. +*/ +package lint diff --git a/pkg/lint/lint.go b/pkg/lint/lint.go new file mode 100644 index 000000000..07a31acea --- /dev/null +++ b/pkg/lint/lint.go @@ -0,0 +1,7 @@ +package lint + +func All(basedir string) []Message { + out := Chartfile(basedir) + out = append(out, Templates(basedir)...) + return out +} diff --git a/pkg/lint/message.go b/pkg/lint/message.go new file mode 100644 index 000000000..a0acda0ca --- /dev/null +++ b/pkg/lint/message.go @@ -0,0 +1,27 @@ +package lint + +import "fmt" + +type Severity int + +const ( + UnknownSev = iota + WarningSev + ErrorSev +) + +var sev = []string{"INFO", "WARNING", "ERROR"} + +type Message struct { + // Severity is one of the *Sev constants + Severity int + // Text contains the message text + Text string +} + +// String prints a string representation of this Message. +// +// Implements fmt.Stringer. +func (m Message) String() string { + return fmt.Sprintf("[%s] %s", sev[m.Severity], m.Text) +} diff --git a/pkg/lint/message_test.go b/pkg/lint/message_test.go new file mode 100644 index 000000000..1d083b369 --- /dev/null +++ b/pkg/lint/message_test.go @@ -0,0 +1,20 @@ +package lint + +import ( + "fmt" + "testing" +) + +var _ fmt.Stringer = Message{} + +func TestMessage(t *testing.T) { + m := Message{ErrorSev, "Foo"} + if m.String() != "[ERROR] Foo" { + t.Errorf("Unexpected output: %s", m.String()) + } + + m = Message{WarningSev, "Bar"} + if m.String() != "[WARNING] Bar" { + t.Errorf("Unexpected output: %s", m.String()) + } +} diff --git a/pkg/lint/template.go b/pkg/lint/template.go new file mode 100644 index 000000000..f3bb559d0 --- /dev/null +++ b/pkg/lint/template.go @@ -0,0 +1,63 @@ +package lint + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "text/template" + + "github.com/Masterminds/sprig" +) + +func Templates(basepath string) (messages []Message) { + messages = []Message{} + path := filepath.Join(basepath, "templates") + if fi, err := os.Stat(path); err != nil { + messages = append(messages, Message{Severity: WarningSev, Text: "No templates"}) + return + } else if !fi.IsDir() { + messages = append(messages, Message{Severity: ErrorSev, Text: "'templates' is not a directory"}) + return + } + + tpl := template.New("tpl").Funcs(sprig.TxtFuncMap()) + + err := filepath.Walk(basepath, func(name string, fi os.FileInfo, e error) error { + // If an error is returned, we fail. Non-fatal errors should just be + // added directly to messages. + if e != nil { + return e + } + if fi.IsDir() { + return nil + } + + data, err := ioutil.ReadFile(name) + if err != nil { + messages = append(messages, Message{ + Severity: ErrorSev, + Text: fmt.Sprintf("cannot read %s: %s", name, err), + }) + return nil + } + + // An error rendering a file should emit a warning. + newtpl, err := tpl.Parse(string(data)) + if err != nil { + messages = append(messages, Message{ + Severity: ErrorSev, + Text: fmt.Sprintf("error processing %s: %s", name, err), + }) + return nil + } + tpl = newtpl + return nil + }) + + if err != nil { + messages = append(messages, Message{Severity: ErrorSev, Text: err.Error()}) + } + + return +} diff --git a/pkg/lint/template_test.go b/pkg/lint/template_test.go new file mode 100644 index 000000000..b8c7eadd4 --- /dev/null +++ b/pkg/lint/template_test.go @@ -0,0 +1,20 @@ +package lint + +import ( + "strings" + "testing" +) + +const templateTestBasedir = "./testdata/albatross" + +func TestTemplate(t *testing.T) { + res := Templates(templateTestBasedir) + + if len(res) != 1 { + t.Fatalf("Expected one error, got %d", len(res)) + } + + if !strings.Contains(res[0].Text, "deliberateSyntaxError") { + t.Errorf("Unexpected error: %s", res[0]) + } +} diff --git a/pkg/lint/testdata/albatross/Chart.yaml b/pkg/lint/testdata/albatross/Chart.yaml new file mode 100644 index 000000000..4aa22d376 --- /dev/null +++ b/pkg/lint/testdata/albatross/Chart.yaml @@ -0,0 +1,3 @@ +name: albatross +description: testing chart +version: 199.44.12345-Alpha.1+cafe009 diff --git a/pkg/lint/testdata/albatross/templates/albatross.yaml b/pkg/lint/testdata/albatross/templates/albatross.yaml new file mode 100644 index 000000000..6c2ceb8db --- /dev/null +++ b/pkg/lint/testdata/albatross/templates/albatross.yaml @@ -0,0 +1,2 @@ +metadata: + name: {{.name | default "foo" | title}} diff --git a/pkg/lint/testdata/albatross/templates/fail.yaml b/pkg/lint/testdata/albatross/templates/fail.yaml new file mode 100644 index 000000000..a11e0e90e --- /dev/null +++ b/pkg/lint/testdata/albatross/templates/fail.yaml @@ -0,0 +1 @@ +{{ deliberateSyntaxError }} diff --git a/pkg/lint/testdata/albatross/values.toml b/pkg/lint/testdata/albatross/values.toml new file mode 100644 index 000000000..388764d49 --- /dev/null +++ b/pkg/lint/testdata/albatross/values.toml @@ -0,0 +1 @@ +name = "mariner" diff --git a/pkg/lint/testdata/badchartfile/Chart.yaml b/pkg/lint/testdata/badchartfile/Chart.yaml new file mode 100644 index 000000000..dbb4a1501 --- /dev/null +++ b/pkg/lint/testdata/badchartfile/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +version: 0.0.0 +home: "" diff --git a/pkg/lint/testdata/badchartfile/values.toml b/pkg/lint/testdata/badchartfile/values.toml new file mode 100644 index 000000000..d6bba222c --- /dev/null +++ b/pkg/lint/testdata/badchartfile/values.toml @@ -0,0 +1,4 @@ +# Default values for badchartfile. +# This is a TOML-formatted file. https://github.com/toml-lang/toml +# Declare name/value pairs to be passed into your templates. +# name = "value" From 8404d743a05ed9d1e498f78f4bac49740dc132a6 Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Wed, 27 Apr 2016 09:38:26 -0600 Subject: [PATCH 72/86] ref(init): save helm paths to variables --- cmd/helm/home.go | 6 ++++- cmd/helm/init.go | 61 ++++++++++++++++++------------------------- cmd/helm/init_test.go | 11 +++++--- cmd/helm/package.go | 2 +- cmd/helm/search.go | 3 +-- cmd/helm/serve.go | 4 +-- 6 files changed, 42 insertions(+), 45 deletions(-) diff --git a/cmd/helm/home.go b/cmd/helm/home.go index 78d3473f1..1e9e63b64 100644 --- a/cmd/helm/home.go +++ b/cmd/helm/home.go @@ -23,5 +23,9 @@ func init() { } func home(cmd *cobra.Command, args []string) { - cmd.Printf(os.ExpandEnv(helmHome) + "\n") + cmd.Printf(homePath() + "\n") +} + +func homePath() string { + return os.ExpandEnv(helmHome) } diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 674e0d904..26688f6de 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -17,13 +17,13 @@ This command installs Tiller (the helm server side component) onto your Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.helm/) ` -const repositoriesPath = ".repositories" -const cachePath = "cache" -const localPath = "local" -const localCacheFilePath = localPath + "/cache.yaml" +var repositoriesFilePath string +var cachePath string +var localRepoPath string +var localCacheFilePath string +var tillerImg string var defaultRepo = map[string]string{"default-name": "default-url"} -var tillerImg string func init() { initCmd.Flags().StringVarP(&tillerImg, "tiller-image", "i", "", "override tiller image") @@ -43,7 +43,7 @@ func runInit(cmd *cobra.Command, args []string) error { return errors.New("This command does not accept arguments. \n") } - if err := ensureHome(os.ExpandEnv(helmHome)); err != nil { + if err := ensureHome(homePath()); err != nil { return err } @@ -51,7 +51,7 @@ func runInit(cmd *cobra.Command, args []string) error { return err } - fmt.Printf("Tiller (the helm server side component) has been installed into your Kubernetes Cluster.\n$HELM_HOME has also been configured at %s.\nHappy Helming!\n", helmHome) + fmt.Println("Happy Helming!") return nil } @@ -66,6 +66,7 @@ func installTiller() error { if err != nil { return fmt.Errorf("error installing %s %s", string(out), err) } + fmt.Println("\nTiller (the helm server side component) has been installed into your Kubernetes Cluster.") return nil } @@ -81,7 +82,13 @@ func buildKubectlRunner(kubectlPath string) kubectl.Runner { // // If $HELM_HOME does not exist, this function will create it. func ensureHome(home string) error { - configDirectories := []string{home, cacheDirectory(home), localDirectory(home)} + repositoriesFilePath = filepath.Join(home, "repositories.yaml") + cachePath = filepath.Join(home, "cache") + localRepoPath = filepath.Join(home, "local") + localCacheFilePath = filepath.Join(home, "cache.yaml") + + fmt.Println("home path: " + home) + configDirectories := []string{home, cachePath, localRepoPath} for _, p := range configDirectories { if fi, err := os.Stat(p); err != nil { @@ -94,44 +101,28 @@ func ensureHome(home string) error { } } - repoPath := repositoriesFile(home) - if fi, err := os.Stat(repoPath); err != nil { - fmt.Printf("Creating %s \n", repoPath) - if err := ioutil.WriteFile(repoPath, []byte("local: localhost:8879/charts\n"), 0644); err != nil { + if fi, err := os.Stat(repositoriesFilePath); err != nil { + fmt.Printf("Creating %s \n", repositoriesFilePath) + if err := ioutil.WriteFile(repositoriesFilePath, []byte("local: localhost:8879/charts\n"), 0644); err != nil { return err } } else if fi.IsDir() { - return fmt.Errorf("%s must be a file, not a directory", repoPath) + return fmt.Errorf("%s must be a file, not a directory", repositoriesFilePath) } - localCacheFile := localDirCacheFile(home) - if fi, err := os.Stat(localCacheFile); err != nil { - fmt.Printf("Creating %s \n", localCacheFile) - _, err := os.Create(localCacheFile) + if fi, err := os.Stat(localCacheFilePath); err != nil { + fmt.Printf("Creating %s \n", localCacheFilePath) + _, err := os.Create(localCacheFilePath) if err != nil { return err } //TODO: take this out and replace with helm update functionality - os.Symlink(localCacheFile, cacheDirectory(home)+"/local-cache.yaml") + os.Symlink(localCacheFilePath, filepath.Join(cachePath, "local-cache.yaml")) } else if fi.IsDir() { - return fmt.Errorf("%s must be a file, not a directory", repoPath) + return fmt.Errorf("%s must be a file, not a directory", localCacheFilePath) } - return nil -} - -func cacheDirectory(home string) string { - return filepath.Join(home, cachePath) -} - -func repositoriesFile(home string) string { - return filepath.Join(home, repositoriesPath) -} -func localDirectory(home string) string { - return filepath.Join(home, localPath) -} - -func localDirCacheFile(home string) string { - return filepath.Join(home, localCacheFilePath) + fmt.Printf("$HELM_HOME has also been configured at %s.\n", helmHome) + return nil } diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index 067d6ffc2..2b3728e6a 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -12,8 +12,8 @@ func TestEnsureHome(t *testing.T) { t.Errorf("%s", err) } - dirs := []string{home, cacheDirectory(home), localDirectory(home)} - for _, dir := range dirs { + expectedDirs := []string{home, cachePath, localRepoPath} + for _, dir := range expectedDirs { if fi, err := os.Stat(dir); err != nil { t.Errorf("%s", err) } else if !fi.IsDir() { @@ -21,12 +21,17 @@ func TestEnsureHome(t *testing.T) { } } - if fi, err := os.Stat(repositoriesFile(home)); err != nil { + if fi, err := os.Stat(repositoriesFilePath); err != nil { t.Errorf("%s", err) } else if fi.IsDir() { t.Errorf("%s should not be a directory", fi) } + if fi, err := os.Stat(localCacheFilePath); err != nil { + t.Errorf("%s", err) + } else if fi.IsDir() { + t.Errorf("%s should not be a directory", fi) + } } func createTmpHome() string { diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 6b3dc0348..66678a184 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -56,7 +56,7 @@ func runPackage(cmd *cobra.Command, args []string) error { // Save to $HELM_HOME/local directory. if save { - if err := repo.AddChartToLocalRepo(ch, localDirectory(os.ExpandEnv(helmHome))); err != nil { + if err := repo.AddChartToLocalRepo(ch, localRepoPath); err != nil { return err } } diff --git a/cmd/helm/search.go b/cmd/helm/search.go index e1276a58c..d3b451a2d 100644 --- a/cmd/helm/search.go +++ b/cmd/helm/search.go @@ -39,9 +39,8 @@ func search(cmd *cobra.Command, args []string) error { } func searchCacheForPattern(name string) ([]string, error) { - dir := cacheDirectory(os.ExpandEnv(helmHome)) fileList := []string{} - filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { + filepath.Walk(cachePath, func(path string, f os.FileInfo, err error) error { if !f.IsDir() { fileList = append(fileList, path) } diff --git a/cmd/helm/serve.go b/cmd/helm/serve.go index b69f18f13..7c046e139 100644 --- a/cmd/helm/serve.go +++ b/cmd/helm/serve.go @@ -1,8 +1,6 @@ package main import ( - "os" - "github.com/deis/tiller/pkg/repo" "github.com/spf13/cobra" ) @@ -24,5 +22,5 @@ var serveCmd = &cobra.Command{ } func serve(cmd *cobra.Command, args []string) { - repo.StartLocalRepo(localDirectory(os.ExpandEnv(helmHome))) + repo.StartLocalRepo(localRepoPath) } From e0eb95be8625f4a0d0ffe35e887719b14c4821c0 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 26 Apr 2016 15:49:15 -0600 Subject: [PATCH 73/86] docs(*): add arch, charts, and quickstart docs --- docs/architecture.md | 84 +++++++++++++++++++ docs/charts.md | 196 +++++++++++++++++++++++++++++++++++++++++++ docs/developers.md | 57 ++++++++++++- docs/quickstart.md | 75 +++++++++++++++++ 4 files changed, 409 insertions(+), 3 deletions(-) create mode 100644 docs/architecture.md create mode 100644 docs/charts.md create mode 100644 docs/quickstart.md diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 000000000..3cf6c186b --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,84 @@ +# The Kubernetes Helm Architecture + +This document describes the Helm architecture at a high level. + +## The Purpose of Helm + +Helm is a tool for managing Kubernetes packages called _charts_. Helm +can do the following: + +- Create new charts from scratch +- Package charts into chart archive (tgz) files +- Interact with chart repositories where charts are stored +- Install and uninstall charts into an existing Kubernetes cluster +- Manage the releases of charts that have been installed with Helm + +For Helm, there are three important concepts: + +1. The _chart_ is a bundle of information necessary to create an + instance of a Kubernetes application. +2. The _config_ contains configuration information that can be merged + into a packaged chart to create a releasable object. +3. A _release_ is a running instance of a _chart_, combined with a + specific _config_. + +Following the formula made famous by the 12 Factor App, _chart + config += release_. + +## Components + +Helm has two major components: + +**The Helm Client** is a command-line client for end users. The client +is responsible for the following domains: + +- Local chart development +- Managing repositories +- Interacting with the Tiller server + - Sending charts to be installed + - Asking for information about releases + - Requesting upgrading or uninstalling of existing releases + +**The Tiller Server** is an in-cluster server that interacts with the +Helm client, and interfaces with the Kubernetes API server. The server +is responsible for the following: + +- Listing for incomming requests from the Helm client +- Combining a chart and configuration to build a release +- Installing charts into Kubernetes, and then tracking the subsequent + release +- Upgrading and uninstalling charts by interacting with Kubernetes + +In a nutshell, the client is responsible for managing charts, and the +server is responsible for managing releases. + +## Implementation + +The Helm client is written in the Go programming language, and uses the +gRPC protocol suite to interact with the Tiller server. + +The Tiller server is also written in Go. It provides a gRPC server to +connect with the client, and it uses the Kubernetes client library to +communicate with Kubernetes. Currently, that library uses REST+JSON. + +The Tiller server stores information in ConfigMaps located inside of +Kubernetes. It does not need its own database. + +### Structure of the Code + +The individual programs are located in `cmd/`. Shared libraries are +stored in `pkg/`. The raw ProtoBuf files are stored in `_proto/hapi` +(where `hapi` stands for the Helm Application Programming Interface). +The Go files generated from the `proto` definitions are stored in +`pkg/proto`. + +Docker images are built by cross-compiling Linux binaries and then +building a Docker image from the files in `rootfs`. + +The `scripts/` directory contains a number of utility scripts, including +`local-cluster.sh`, which can start a full Kubernetes instance inside of +a Docker container. + +Go dependencies are managed with +[Glide](https://github.com/Masterminds/glide) and stored in the +`vendor/` directory. diff --git a/docs/charts.md b/docs/charts.md new file mode 100644 index 000000000..40f4b62f1 --- /dev/null +++ b/docs/charts.md @@ -0,0 +1,196 @@ +# Charts + +Helm uses a packaging format called _charts_. A chart is a collection of files +that collectively describe a set of Kubernetes resources. + +## The Chart File Structure + +A chart is organized as a collection of files inside of a directory. The +directory name is the name of the chart (without versioning information). Thus, +a chart describing Wordpress would be stored in the `wordpress/` directory. + +Inside of this directory, Helm will expect a structure that matches this: + +``` +wordpress/ + Chart.yaml # A YAML file containing information about the chart + LICENSE # A plain text file containing the license for the chart + README.md # A human-readable README file + values.toml # The default configuration values for this chart + charts/ # A directory containing any charts upon which this chart depends. + templates/ # A directory of templates that, when combined with values, + # will generate valid Kubernetes manifest files. +``` + +## The Chart.yaml File + +The Chart.yaml file is required for a chart. It contains the following fields: + +```yaml +name: The name of the chart (required) +version: A SemVer 2 version (required) +description: A single-sentence description of this project (optional) +keywords: + - A list of keywords about this project +home: The URL of this project's home page (optional) +sources: + - A list of URLs to source code for this project (optional) +maintainers: + - name: The maintainer's name + email: The maintainer's email +``` + +If you are familiar with the Chart.yaml file format for Helm Classic, you will +notice that fields specifying dependencies have been removed. That is because +the new Chart format expresses dependencies using the `charts/` directory. + +## Chart Dependencies + +In Helm, one chart may depend on any number of other charts. These +dependencies are expressed explicitly by copying the dependency charts +into the `charts/` directory. + +For example, if the Wordpress chart depends on the Apache chart, the +Apache chart (of the correct version) is supplied in the Wordpress +chart's `charts/` directory: + +``` +wordpress: + Chart.yaml + # ... + charts/ + apache/ + Chart.yaml + # ... + mysql/ + Chart.yaml + # ... +``` + +The example above shows how the Wordpress chart expresses its dependency +on Apache and MySQL by including those charts inside of its `charts/` +directory. + +## Templates and Values + +In Helm Charts, templates are written in the Go template language, with the +addition of 50 or so add-on template functions. + +All template files are stored in a chart's `templates/` folder. When +Helm renders the charts, it will pass every file in that directory +through the template engine. + +Values for the templates are supplied two ways: + - Chart developers may supply a file called `values.toml` inside of a + chart. This file can contain default values. + - Chart users may supply a TOML file that contains values. This can be + provided on the command line with `helm install`. + +When a user supplies custom values, these values will override the +values in the chart's `values.toml` file. + +### Template Files + +Template files follow the standard conventions for writing Go templates. +An example template file might look something like this: + +```yaml +apiVersion: v1 +kind: ReplicationController +metadata: + name: deis-database + namespace: deis + labels: + heritage: deis +spec: + replicas: 1 + selector: + app: deis-database + template: + metadata: + labels: + app: deis-database + spec: + serviceAccount: deis-database + containers: + - name: deis-database + image: {{.imageRegistry}}/postgres:{{.dockerTag}} + imagePullPolicy: {{.pullPolicy}} + ports: + - containerPort: 5432 + env: + - name: DATABASE_STORAGE + value: {{default "minio" .storage}} +``` + +The above example, based loosely on [https://github.com/deis/charts](the +chart for Deis), is a template for a Kubernetes replication controller. +It can use the following four template values: + +- `imageRegistry`: The source registry for the Docker image. +- `dockerTag`: The tag for the docker image. +- `pullPolicy`: The Kubernetes pull policy. +- `storage`: The storage backend, whose default is set to `"minio"` + +All of these values are defined by the template author. Helm does not +require or dictate parameters. + +### Values files + +Considering the template in the previous section, a `values.toml` file +that supplies the necessary values would look like this: + +```toml +imageRegistry = "quay.io/deis" +dockerTag = "latest" +pullPolicy = "alwaysPull" +storage = "s3" +``` + +When a chart includes dependency charts, values can be supplied to those +charts using TOML tables: + +```toml +imageRegistry = "quay.io/deis" +dockerTag = "latest" +pullPolicy = "alwaysPull" +storage = "s3" + +[router] +hostname = "example.com" +``` + +In the above example, the value of `hostname` will be passed to a chart +named `router` (if it exists) in the `charts/` directory. + +### References +- [Go templates](https://godoc.org/text/template) +- [Extra template functions](https://godoc.org/github.com/Masterminds/sprig) +- [The TOML format](https://github.com/toml-lang/toml) + +## Using Helm to Manage Charts + +The `helm` tool has several commands for working with charts. + +It can create a new chart for you: + +```console +$ helm create mychart +Created mychart/ +``` + +Once you have edited a chart, `helm` can package it into a chart archive +for you: + +```console +$ helm package mychart +Archived mychart-0.1.-.tgz +``` + +You can also use `helm` to help you find issues with your chart's +formatting or information: + +```console +$ helm lint mychart +No issues found +``` diff --git a/docs/developers.md b/docs/developers.md index 73edcfa52..ddea7e9ef 100644 --- a/docs/developers.md +++ b/docs/developers.md @@ -6,10 +6,33 @@ Helm and Tiller. ## Prerequisites - Go 1.6.0 or later +- Glide 0.10.2 or later - kubectl 1.2 or later - A Kubernetes cluster (optional) - The gRPC toolchain +## Building Helm/Tiller + +We use Make to build our programs. The simplest way to get started is: + +```console +$ make boostrap build +``` + +This will build both Helm and Tiller. + +To run all of the tests (without running the tests for `vendor/`), run +`make test`. + +To run Helm and Tiller locally, you can run `bin/helm` or `bin/tiller`. + +- Helm and Tiller are known to run on Mac OSX and most Linuxes, including + Alpine. +- Tiller must have access to a Kubernets cluster. It learns about the + cluster by examining the Kube config files that `kubectl` uese. + +## gRPC and Protobuf + Tiller uses gRPC. To get started with gRPC, you will need to... - Install `protoc` for compiling protobuf files. Releases are @@ -18,9 +41,37 @@ Tiller uses gRPC. To get started with gRPC, you will need to... Note that you need to be on protobuf 3.x (`protoc --version`) and use the latest Go plugin. -## The Helm API (HAPI) +### The Helm API (HAPI) -We use gRPC as an API layer. See `pkg/hapi` for the generated Go code, +We use gRPC as an API layer. See `pkg/proto/hapi` for the generated Go code, and `_proto` for the protocol buffer definitions. -To regenerate `hapi`, use `go generate pkg/hapi`. +To regenerate the Go files from the protobuf source, `cd _proto && +make`. + +## Docker Images + +To build Docker images, use `make docker-build` + +## Running a Local Cluster + +You can run tests locally using the `scripts/local-cluster.sh` script to +start Kubernetes inside of a Docker container. For OS X, you will need +to be running `docker-machine`. + +## Contribution Guidelines + +We welcome contributions. This project has set up some guidelines in +order to ensure that (a) code quality remains high, (b) the project +remains consistent, and (c) contributions follow the open source legal +requirements. Our intent is not to burden contributors, but to build +elegant and high-quality open source code so that our users will benefit. + +We follow the coding standards and guidelines outlined by the Deis +project: + +https://github.com/deis/workflow/blob/master/CONTRIBUTING.md +https://github.com/deis/workflow/blob/master/src/contributing/submitting-a-pull-request.md + +Adidtionally, contributors must have a CLA with CNCF/Google before we can +accept contributions. diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 000000000..04b431f5b --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,75 @@ +# Quickstart Guide + +This guide covers how you can quickly get started using Helm. + +## Prerequisites + +- You must have Kubernetes installed, and have a local configured copy + of `kubectl`. + +## Install Helm + +Download a binary release of the Helm client from the official project +page. + +Alternately, you can clone the GitHub project and build your own +client from source. The quickest route to installing from source is to +run `make boostrap build`, and then use `bin/helm`. + +## Initialize Helm and Install Tiller + +Once you have Helm ready, you can initialize the local CLI and also +install Tiller into your Kubernetes cluster in one step: + +```console +$ helm init +``` + +## Install an Existing Chart + +To install an existing chart, you can run the `helm install` command: + +_TODO:_ Update this to the correct URL. + +```console +$ helm install https://helm.sh/charts/nginx-0.1.0.tgz +Released smiling-penguin +``` + +In the example above, the `nginx` chart was released, and the name of +our new release is `smiling-penguin` + +## Learn About The Release + +To find out about our release, run `helm status`: + +```console +$ helm status smiling-penguin +Status: DEPLOYED +``` + +## Uninstall a Release + +To remove a release, use the `helm remove` command: + +```console +$ helm remove smiling-penguin +Removed smiling-penguin +``` + +This will uninstall `smiling-penguin` from Kubernetes, but you will +still be able to request information about that release: + +```console +$ helm status smiling-penguin +Status: DELETED +``` + +## Reading the Help Text + +To learn more about the available Helm commands, use `helm help` or type +a command followed by the `-h` flag: + +```console +$ helm get -h +``` From 95bc672629e16a447a935716fdd0719bf09d1f21 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Tue, 26 Apr 2016 16:03:44 -0600 Subject: [PATCH 74/86] docs(example): add example alpine chart. --- docs/examples/README.md | 4 ++++ docs/examples/alpine/Chart.yaml | 4 ++++ docs/examples/alpine/README.md | 9 +++++++++ docs/examples/alpine/templates/alpine-pod.yaml | 12 ++++++++++++ docs/examples/alpine/values.toml | 2 ++ 5 files changed, 31 insertions(+) create mode 100644 docs/examples/README.md create mode 100644 docs/examples/alpine/Chart.yaml create mode 100644 docs/examples/alpine/README.md create mode 100644 docs/examples/alpine/templates/alpine-pod.yaml create mode 100644 docs/examples/alpine/values.toml diff --git a/docs/examples/README.md b/docs/examples/README.md new file mode 100644 index 000000000..aa42ee075 --- /dev/null +++ b/docs/examples/README.md @@ -0,0 +1,4 @@ +# Helm Examples + +This directory contains example charts to help you get started with +chart development. diff --git a/docs/examples/alpine/Chart.yaml b/docs/examples/alpine/Chart.yaml new file mode 100644 index 000000000..6e9f807e0 --- /dev/null +++ b/docs/examples/alpine/Chart.yaml @@ -0,0 +1,4 @@ +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: "https://github.com/deis/tiller" diff --git a/docs/examples/alpine/README.md b/docs/examples/alpine/README.md new file mode 100644 index 000000000..a7c84fc41 --- /dev/null +++ b/docs/examples/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install docs/examples/alpine`. diff --git a/docs/examples/alpine/templates/alpine-pod.yaml b/docs/examples/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..000f106e3 --- /dev/null +++ b/docs/examples/alpine/templates/alpine-pod.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{default "alpine" .name}} + labels: + heritage: helm +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/docs/examples/alpine/values.toml b/docs/examples/alpine/values.toml new file mode 100644 index 000000000..504e6e1be --- /dev/null +++ b/docs/examples/alpine/values.toml @@ -0,0 +1,2 @@ +# The pod name +name = "my-alpine" From c98e1042b5914f669a2887d1f3ffbc78c573f58f Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 27 Apr 2016 11:58:12 -0600 Subject: [PATCH 75/86] docs(*): fix chart descriptions Make sure all fields are marked optional or required in the doc. --- docs/charts.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/charts.md b/docs/charts.md index 40f4b62f1..baa779238 100644 --- a/docs/charts.md +++ b/docs/charts.md @@ -31,13 +31,13 @@ name: The name of the chart (required) version: A SemVer 2 version (required) description: A single-sentence description of this project (optional) keywords: - - A list of keywords about this project + - A list of keywords about this project (optional) home: The URL of this project's home page (optional) sources: - A list of URLs to source code for this project (optional) -maintainers: - - name: The maintainer's name - email: The maintainer's email +maintainers: # (optional) + - name: The maintainer's name (required for each maintainer) + email: The maintainer's email (optional for each maintainer) ``` If you are familiar with the Chart.yaml file format for Helm Classic, you will From f1ebfec44662540119dfdfdad444e4e6798900a5 Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Wed, 27 Apr 2016 10:59:31 -0700 Subject: [PATCH 76/86] fix(Makefile): build before testing --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 945328cf0..fd5e64151 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,7 @@ docker-build: check-docker docker-binary docker tag -f ${IMAGE} ${MUTABLE_IMAGE} .PHONY: test +test: build test: TESTFLAGS += -race -v test: test-style test: test-unit From 0fbc9229cd1617d8acb40c317241c6b7c4af8618 Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Thu, 14 Apr 2016 23:21:16 -0700 Subject: [PATCH 77/86] feat(kube): add kubernetes client --- glide.lock | 276 +++++++++++++++++++- glide.yaml | 14 + pkg/kube/client.go | 69 +++++ pkg/kube/client_test.go | 36 +++ pkg/kube/testdata/guestbook-all-in-one.yaml | 179 +++++++++++++ 5 files changed, 570 insertions(+), 4 deletions(-) create mode 100644 pkg/kube/client.go create mode 100644 pkg/kube/client_test.go create mode 100644 pkg/kube/testdata/guestbook-all-in-one.yaml diff --git a/glide.lock b/glide.lock index f73ce1e65..9e70ce56b 100644 --- a/glide.lock +++ b/glide.lock @@ -1,20 +1,164 @@ -hash: 23af9670ae8c1600f0f3c70bd9191e0df9211fed1e57430160aed95d5c71c2d1 -updated: 2016-04-20T17:16:04.782839628-06:00 +hash: 0b39c22befe9d14b5805603bd36ff0c77043c4dbd604e42132a55dcaa4e61228 +updated: 2016-04-23T13:10:56.180563322-07:00 imports: +- name: bitbucket.org/ww/goautoneg + version: 75cd24fc2f2c - name: github.com/aokoli/goutils version: 9c37978a95bd5c709a15883b6242714ea6709e64 +- name: github.com/beorn7/perks + version: b965b613227fddccbfffe13eae360ed3fa822f8d + subpackages: + - quantile +- name: github.com/blang/semver + version: 31b736133b98f26d5e078ec9eb591666edfd091f - name: github.com/BurntSushi/toml version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f +- name: github.com/davecgh/go-spew + version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d + subpackages: + - spew +- name: github.com/docker/docker + version: 0f5c9d301b9b1cca66b3ea0f9dec3b5317d3686d + subpackages: + - pkg/jsonmessage + - pkg/mount + - pkg/parsers + - pkg/stdcopy + - pkg/symlink + - pkg/term + - pkg/timeutils + - pkg/units +- name: github.com/docker/go-units + version: 0bbddae09c5a5419a8c6dcdd7ff90da3d450393b +- name: github.com/emicklei/go-restful + version: 777bb3f19bcafe2575ffb2a3e46af92509ae9594 + subpackages: + - swagger + - log +- name: github.com/evanphx/json-patch + version: 7dd4489c2eb6073e5a9d7746c3274c5b5f0387df +- name: github.com/fsouza/go-dockerclient + version: 0099401a7342ad77e71ca9f9a57c5e72fb80f6b2 + subpackages: + - external/github.com/docker/docker/opts + - external/github.com/docker/docker/pkg/archive + - external/github.com/docker/docker/pkg/fileutils + - external/github.com/docker/docker/pkg/homedir + - external/github.com/docker/docker/pkg/stdcopy + - external/github.com/hashicorp/go-cleanhttp + - external/github.com/Sirupsen/logrus + - external/github.com/docker/docker/pkg/idtools + - external/github.com/docker/docker/pkg/ioutils + - external/github.com/docker/docker/pkg/pools + - external/github.com/docker/docker/pkg/promise + - external/github.com/docker/docker/pkg/system + - external/github.com/docker/docker/pkg/longpath + - external/github.com/opencontainers/runc/libcontainer/user + - external/golang.org/x/sys/unix + - external/golang.org/x/net/context + - external/github.com/docker/go-units +- name: github.com/ghodss/yaml + version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee +- name: github.com/gogo/protobuf + version: 82d16f734d6d871204a3feb1a73cb220cc92574c + subpackages: + - gogoproto + - plugin/defaultcheck + - plugin/description + - plugin/embedcheck + - plugin/enumstringer + - plugin/equal + - plugin/face + - plugin/gostring + - plugin/grpc + - plugin/marshalto + - plugin/oneofcheck + - plugin/populate + - plugin/size + - plugin/stringer + - plugin/testgen + - plugin/union + - plugin/unmarshal + - proto + - protoc-gen-gogo/descriptor + - protoc-gen-gogo/generator + - protoc-gen-gogo/plugin + - sortkeys + - vanity +- name: github.com/golang/glog + version: 44145f04b68cf362d9c4df2182967c2275eaefed +- name: github.com/golang/groupcache + version: 604ed5785183e59ae2789449d89e73f3a2a77987 + subpackages: + - lru - name: github.com/golang/protobuf version: f0a097ddac24fb00e07d2ac17f8671423f3ea47c subpackages: - proto - ptypes/any - ptypes/timestamp +- name: github.com/google/cadvisor + version: 546a3771589bdb356777c646c6eca24914fdd48b + subpackages: + - api + - cache/memory + - collector + - container + - events + - fs + - healthz + - http + - info/v1 + - info/v2 + - manager + - metrics + - pages + - storage + - summary + - utils + - validate + - version +- name: github.com/google/gofuzz + version: bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5 +- name: github.com/imdario/mergo + version: 6633656539c1639d9d78127b7d47c622b5d7b6dc +- name: github.com/inconshreveable/mousetrap + version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +- name: github.com/juju/ratelimit + version: 77ed1c8a01217656d2080ad51981f6e99adaa177 - name: github.com/Masterminds/semver version: 808ed7761c233af2de3f9729a041d68c62527f3a - name: github.com/Masterminds/sprig - version: 679bb747f11c6ffc3373965988fea8877c40b47b + version: e6494bc7e81206ba6db404d2fd96500ffc453407 +- name: github.com/matttproud/golang_protobuf_extensions + version: fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a + subpackages: + - pbutil +- name: github.com/opencontainers/runc + version: 7ca2aa4873aea7cb4265b1726acb24b90d8726c6 + subpackages: + - libcontainer + - libcontainer/cgroups/fs + - libcontainer/configs + - libcontainer/cgroups + - libcontainer/system +- name: github.com/pborman/uuid + version: ca53cad383cad2479bbba7f7a1a05797ec1386e4 +- name: github.com/prometheus/client_golang + version: 3b78d7a77f51ccbc364d4bc170920153022cfd08 + subpackages: + - prometheus +- name: github.com/prometheus/client_model + version: fa8ad6fec33561be4280a8f0514318c79d7f6cb6 + subpackages: + - go +- name: github.com/prometheus/common + version: ef7a9a5fb138aa5d3a19988537606226869a0390 + subpackages: + - expfmt + - model +- name: github.com/prometheus/procfs + version: 490cc6eb5fa45bf8a8b7b73c8bc82a8160e8531d - name: github.com/spf13/cobra version: 4c05eb1145f16d0e6bb4a3e1b6d769f4713cb41f subpackages: @@ -23,8 +167,12 @@ imports: version: 8f6a28b0916586e7f22fe931ae2fcfc380b1c0e6 - name: github.com/technosophos/moniker version: 9f956786b91d9786ca11aa5be6104542fa911546 +- name: github.com/ugorji/go + version: f4485b318aadd133842532f841dc205a8e339d74 + subpackages: + - codec - name: golang.org/x/net - version: fb93926129b8ec0056f2f458b1f519654814edf0 + version: c2528b2dd8352441850638a8bb678c2ad056fd3e subpackages: - context - http2 @@ -44,4 +192,124 @@ imports: - peer - name: gopkg.in/yaml.v2 version: a83829b6f1293c91addabc89d0571c246397bbf4 +- name: k8s.io/heapster + version: 0991ac528ea24aae194e45d6dcf01896cb42cbea + subpackages: + - api/v1/types +- name: k8s.io/kubernetes + version: 6a87dba0b8a50dccaddb67a4c7748696db1918ec + subpackages: + - pkg/api + - pkg/api/errors + - pkg/api/meta + - pkg/api/testapi + - pkg/api/unversioned + - pkg/client/restclient + - pkg/client/unversioned + - pkg/client/unversioned/clientcmd + - pkg/client/unversioned/clientcmd/api + - pkg/kubectl/cmd/util + - pkg/kubectl/resource + - pkg/runtime + - pkg/client/unversioned/auth + - pkg/client/unversioned/clientcmd/api/latest + - pkg/util/errors + - pkg/util/homedir + - pkg/util/validation + - pkg/api/validation + - pkg/apimachinery + - pkg/apimachinery/registered + - pkg/apis/autoscaling + - pkg/apis/batch + - pkg/apis/extensions + - pkg/apis/metrics + - pkg/client/typed/discovery + - pkg/client/unversioned/adapters/internalclientset + - pkg/kubectl + - pkg/labels + - pkg/registry/thirdpartyresourcedata + - pkg/runtime/serializer/json + - pkg/util/flag + - pkg/util/strategicpatch + - pkg/util/sets + - pkg/util/yaml + - pkg/watch + - pkg/api/resource + - pkg/auth/user + - pkg/conversion + - pkg/fields + - pkg/runtime/serializer + - pkg/types + - pkg/util + - pkg/util/intstr + - pkg/util/rand + - pkg/api/v1 + - pkg/client/metrics + - pkg/client/transport + - pkg/util/crypto + - pkg/util/flowcontrol + - pkg/util/net + - pkg/version + - pkg/watch/json + - pkg/client/unversioned/clientcmd/api/v1 + - pkg/runtime/serializer/versioning + - pkg/conversion/queryparams + - pkg/util/validation/field + - pkg/api/endpoints + - pkg/api/pod + - pkg/api/service + - pkg/api/util + - pkg/capabilities + - pkg/api/install + - pkg/apis/authorization/install + - pkg/apis/autoscaling/install + - pkg/apis/batch/install + - pkg/apis/componentconfig/install + - pkg/apis/extensions/install + - pkg/apis/metrics/install + - pkg/util/wait + - pkg/client/clientset_generated/internalclientset + - pkg/client/clientset_generated/internalclientset/typed/core/unversioned + - pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned + - pkg/apis/batch/v1 + - pkg/credentialprovider + - pkg/fieldpath + - pkg/kubelet/qos/util + - pkg/util/deployment + - pkg/util/integer + - pkg/util/jsonpath + - pkg/api/rest + - pkg/apis/extensions/v1beta1 + - pkg/apis/extensions/validation + - pkg/registry/generic + - third_party/forked/json + - pkg/util/runtime + - third_party/forked/reflect + - pkg/runtime/serializer/protobuf + - pkg/runtime/serializer/recognizer + - pkg/runtime/serializer/streaming + - pkg/util/parsers + - pkg/util/hash + - pkg/util/net/sets + - pkg/apis/authorization + - pkg/apis/authorization/v1beta1 + - pkg/apis/autoscaling/v1 + - pkg/apis/componentconfig + - pkg/apis/componentconfig/v1alpha1 + - pkg/apis/metrics/v1alpha1 + - pkg/controller + - pkg/util/labels + - pkg/util/pod + - third_party/golang/template + - pkg/api/unversioned/validation + - pkg/controller/podautoscaler + - pkg/storage + - pkg/kubelet/qos + - pkg/master/ports + - pkg/client/cache + - pkg/client/record + - pkg/controller/framework + - pkg/controller/podautoscaler/metrics +- name: speter.net/go/exp/math/dec/inf + version: 42ca6cd68aa922bc3f32f1e056e61b65945d9ad7 devImports: [] diff --git a/glide.yaml b/glide.yaml index 962fb5aee..c7240de0d 100644 --- a/glide.yaml +++ b/glide.yaml @@ -17,3 +17,17 @@ import: version: f0a097ddac24fb00e07d2ac17f8671423f3ea47c - package: google.golang.org/grpc version: dec33edc378cf4971a2741cfd86ed70a644d6ba3 +- package: k8s.io/kubernetes + subpackages: + - pkg/api + - pkg/api/errors + - pkg/api/meta + - pkg/api/testapi + - pkg/api/unversioned + - pkg/client/restclient + - pkg/client/unversioned + - pkg/client/unversioned/clientcmd + - pkg/client/unversioned/clientcmd/api + - pkg/kubectl/cmd/util + - pkg/kubectl/resource + - pkg/runtime diff --git a/pkg/kube/client.go b/pkg/kube/client.go new file mode 100644 index 000000000..a536505e3 --- /dev/null +++ b/pkg/kube/client.go @@ -0,0 +1,69 @@ +package kube + +import ( + "fmt" + "io" + + "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/resource" +) + +const includeThirdPartyAPIs = false + +// ResourceActorFunc performs an action on a signle resource. +type ResourceActorFunc func(*resource.Info) error + +// Create creates kubernetes resources from an io.reader +// +// Namespace will set the namespace +// Config allows for overiding values from kubectl +func Create(namespace string, reader io.Reader, config clientcmd.ClientConfig) error { + return perform(namespace, reader, createResource, config) +} + +func perform(namespace string, reader io.Reader, fn ResourceActorFunc, config clientcmd.ClientConfig) error { + f := cmdutil.NewFactory(config) + //schema, err := f.Validator(true, "") + //if err != nil { + //return err + //} + + r := f.NewBuilder(includeThirdPartyAPIs). + ContinueOnError(). + NamespaceParam(namespace). + RequireNamespace(). + Stream(reader, ""). + Flatten(). + Do() + + if r.Err() != nil { + return r.Err() + } + + count := 0 + err := r.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + err = fn(info) + + if err == nil { + count++ + } + return err + }) + + if err != nil { + return err + } + if count == 0 { + return fmt.Errorf("no objects passed to create") + } + return nil +} + +func createResource(info *resource.Info) error { + _, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object) + return err +} diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go new file mode 100644 index 000000000..15de2d2ac --- /dev/null +++ b/pkg/kube/client_test.go @@ -0,0 +1,36 @@ +package kube + +import ( + "os" + "testing" + + "k8s.io/kubernetes/pkg/kubectl/resource" +) + +func TestPerform(t *testing.T) { + f, err := os.Open("./testdata/guestbook-all-in-one.yaml") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + results := []*resource.Info{} + + fn := func(info *resource.Info) error { + results = append(results, info) + + if info.Namespace != "test" { + t.Errorf("expected namespace to be 'test', got %s", info.Namespace) + } + + return nil + } + + if err := perform("test", f, fn, nil); err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + if len(results) != 6 { + t.Errorf("expected 6 result objects, got %d", len(results)) + } +} diff --git a/pkg/kube/testdata/guestbook-all-in-one.yaml b/pkg/kube/testdata/guestbook-all-in-one.yaml new file mode 100644 index 000000000..c6675e0eb --- /dev/null +++ b/pkg/kube/testdata/guestbook-all-in-one.yaml @@ -0,0 +1,179 @@ +apiVersion: v1 +kind: Service +metadata: + name: redis-master + labels: + app: redis + tier: backend + role: master +spec: + ports: + # the port that this service should serve on + - port: 6379 + targetPort: 6379 + selector: + app: redis + tier: backend + role: master +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: redis-master + # these labels can be applied automatically + # from the labels in the pod template if not set + # labels: + # app: redis + # role: master + # tier: backend +spec: + # this replicas value is default + # modify it according to your case + replicas: 1 + # selector can be applied automatically + # from the labels in the pod template if not set + # selector: + # matchLabels: + # app: guestbook + # role: master + # tier: backend + template: + metadata: + labels: + app: redis + role: master + tier: backend + spec: + containers: + - name: master + image: gcr.io/google_containers/redis:e2e # or just image: redis + resources: + requests: + cpu: 100m + memory: 100Mi + ports: + - containerPort: 6379 +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-slave + labels: + app: redis + tier: backend + role: slave +spec: + ports: + # the port that this service should serve on + - port: 6379 + selector: + app: redis + tier: backend + role: slave +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: redis-slave + # these labels can be applied automatically + # from the labels in the pod template if not set + # labels: + # app: redis + # role: slave + # tier: backend +spec: + # this replicas value is default + # modify it according to your case + replicas: 2 + # selector can be applied automatically + # from the labels in the pod template if not set + # selector: + # matchLabels: + # app: guestbook + # role: slave + # tier: backend + template: + metadata: + labels: + app: redis + role: slave + tier: backend + spec: + containers: + - name: slave + image: gcr.io/google_samples/gb-redisslave:v1 + resources: + requests: + cpu: 100m + memory: 100Mi + env: + - name: GET_HOSTS_FROM + value: dns + # If your cluster config does not include a dns service, then to + # instead access an environment variable to find the master + # service's host, comment out the 'value: dns' line above, and + # uncomment the line below. + # value: env + ports: + - containerPort: 6379 +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend + labels: + app: guestbook + tier: frontend +spec: + # if your cluster supports it, uncomment the following to automatically create + # an external load-balanced IP for the frontend service. + # type: LoadBalancer + ports: + # the port that this service should serve on + - port: 80 + selector: + app: guestbook + tier: frontend +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: frontend + # these labels can be applied automatically + # from the labels in the pod template if not set + # labels: + # app: guestbook + # tier: frontend +spec: + # this replicas value is default + # modify it according to your case + replicas: 3 + # selector can be applied automatically + # from the labels in the pod template if not set + # selector: + # matchLabels: + # app: guestbook + # tier: frontend + template: + metadata: + labels: + app: guestbook + tier: frontend + spec: + containers: + - name: php-redis + image: gcr.io/google-samples/gb-frontend:v4 + resources: + requests: + cpu: 100m + memory: 100Mi + env: + - name: GET_HOSTS_FROM + value: dns + # If your cluster config does not include a dns service, then to + # instead access environment variables to find service host + # info, comment out the 'value: dns' line above, and uncomment the + # line below. + # value: env + ports: + - containerPort: 80 From 5251344318a6e78b4d1159c425b66fb0aae3597f Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Wed, 27 Apr 2016 13:46:26 -0600 Subject: [PATCH 78/86] feat(helm): add structure.go to hold paths --- cmd/helm/home.go | 6 ------ cmd/helm/init.go | 46 ++++++++++++++++++------------------------- cmd/helm/init_test.go | 9 +++++---- cmd/helm/package.go | 2 +- cmd/helm/search.go | 2 +- cmd/helm/serve.go | 2 +- cmd/helm/structure.go | 31 +++++++++++++++++++++++++++++ 7 files changed, 58 insertions(+), 40 deletions(-) create mode 100644 cmd/helm/structure.go diff --git a/cmd/helm/home.go b/cmd/helm/home.go index 1e9e63b64..0f3d4480a 100644 --- a/cmd/helm/home.go +++ b/cmd/helm/home.go @@ -1,8 +1,6 @@ package main import ( - "os" - "github.com/spf13/cobra" ) @@ -25,7 +23,3 @@ func init() { func home(cmd *cobra.Command, args []string) { cmd.Printf(homePath() + "\n") } - -func homePath() string { - return os.ExpandEnv(helmHome) -} diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 26688f6de..24dbde8c2 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -5,7 +5,6 @@ import ( "fmt" "io/ioutil" "os" - "path/filepath" "github.com/deis/tiller/pkg/client" "github.com/deis/tiller/pkg/kubectl" @@ -17,13 +16,10 @@ This command installs Tiller (the helm server side component) onto your Kubernetes Cluster and sets up local configuration in $HELM_HOME (default: ~/.helm/) ` -var repositoriesFilePath string -var cachePath string -var localRepoPath string -var localCacheFilePath string -var tillerImg string - -var defaultRepo = map[string]string{"default-name": "default-url"} +var ( + tillerImg string + defaultRepo = map[string]string{"default-name": "default-url"} +) func init() { initCmd.Flags().StringVarP(&tillerImg, "tiller-image", "i", "", "override tiller image") @@ -43,7 +39,7 @@ func runInit(cmd *cobra.Command, args []string) error { return errors.New("This command does not accept arguments. \n") } - if err := ensureHome(homePath()); err != nil { + if err := ensureHome(); err != nil { return err } @@ -81,14 +77,8 @@ func buildKubectlRunner(kubectlPath string) kubectl.Runner { // ensureHome checks to see if $HELM_HOME exists // // If $HELM_HOME does not exist, this function will create it. -func ensureHome(home string) error { - repositoriesFilePath = filepath.Join(home, "repositories.yaml") - cachePath = filepath.Join(home, "cache") - localRepoPath = filepath.Join(home, "local") - localCacheFilePath = filepath.Join(home, "cache.yaml") - - fmt.Println("home path: " + home) - configDirectories := []string{home, cachePath, localRepoPath} +func ensureHome() error { + configDirectories := []string{homePath(), cacheDirectory(), localRepoDirectory()} for _, p := range configDirectories { if fi, err := os.Stat(p); err != nil { @@ -101,28 +91,30 @@ func ensureHome(home string) error { } } - if fi, err := os.Stat(repositoriesFilePath); err != nil { - fmt.Printf("Creating %s \n", repositoriesFilePath) - if err := ioutil.WriteFile(repositoriesFilePath, []byte("local: localhost:8879/charts\n"), 0644); err != nil { + repoFile := repositoriesFile() + if fi, err := os.Stat(repoFile); err != nil { + fmt.Printf("Creating %s \n", repoFile) + if err := ioutil.WriteFile(repoFile, []byte("local: localhost:8879/charts\n"), 0644); err != nil { return err } } else if fi.IsDir() { - return fmt.Errorf("%s must be a file, not a directory", repositoriesFilePath) + return fmt.Errorf("%s must be a file, not a directory", repoFile) } - if fi, err := os.Stat(localCacheFilePath); err != nil { - fmt.Printf("Creating %s \n", localCacheFilePath) - _, err := os.Create(localCacheFilePath) + localRepoCacheFile := localRepoDirectory(localRepoCacheFilePath) + if fi, err := os.Stat(localRepoCacheFile); err != nil { + fmt.Printf("Creating %s \n", localRepoCacheFile) + _, err := os.Create(localRepoCacheFile) if err != nil { return err } //TODO: take this out and replace with helm update functionality - os.Symlink(localCacheFilePath, filepath.Join(cachePath, "local-cache.yaml")) + os.Symlink(localRepoCacheFile, cacheDirectory("local-cache.yaml")) } else if fi.IsDir() { - return fmt.Errorf("%s must be a file, not a directory", localCacheFilePath) + return fmt.Errorf("%s must be a file, not a directory", localRepoCacheFile) } - fmt.Printf("$HELM_HOME has also been configured at %s.\n", helmHome) + fmt.Printf("$HELM_HOME has been configured at %s.\n", helmHome) return nil } diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index 2b3728e6a..6c5bebbe9 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -8,11 +8,12 @@ import ( func TestEnsureHome(t *testing.T) { home := createTmpHome() - if err := ensureHome(home); err != nil { + helmHome = home + if err := ensureHome(); err != nil { t.Errorf("%s", err) } - expectedDirs := []string{home, cachePath, localRepoPath} + expectedDirs := []string{homePath(), cacheDirectory(), localRepoDirectory()} for _, dir := range expectedDirs { if fi, err := os.Stat(dir); err != nil { t.Errorf("%s", err) @@ -21,13 +22,13 @@ func TestEnsureHome(t *testing.T) { } } - if fi, err := os.Stat(repositoriesFilePath); err != nil { + if fi, err := os.Stat(repositoriesFile()); err != nil { t.Errorf("%s", err) } else if fi.IsDir() { t.Errorf("%s should not be a directory", fi) } - if fi, err := os.Stat(localCacheFilePath); err != nil { + if fi, err := os.Stat(localRepoDirectory(localRepoCacheFilePath)); err != nil { t.Errorf("%s", err) } else if fi.IsDir() { t.Errorf("%s should not be a directory", fi) diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 66678a184..98c12ce05 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -56,7 +56,7 @@ func runPackage(cmd *cobra.Command, args []string) error { // Save to $HELM_HOME/local directory. if save { - if err := repo.AddChartToLocalRepo(ch, localRepoPath); err != nil { + if err := repo.AddChartToLocalRepo(ch, localRepoDirectory()); err != nil { return err } } diff --git a/cmd/helm/search.go b/cmd/helm/search.go index d3b451a2d..b4175b99c 100644 --- a/cmd/helm/search.go +++ b/cmd/helm/search.go @@ -40,7 +40,7 @@ func search(cmd *cobra.Command, args []string) error { func searchCacheForPattern(name string) ([]string, error) { fileList := []string{} - filepath.Walk(cachePath, func(path string, f os.FileInfo, err error) error { + filepath.Walk(cacheDirectory(), func(path string, f os.FileInfo, err error) error { if !f.IsDir() { fileList = append(fileList, path) } diff --git a/cmd/helm/serve.go b/cmd/helm/serve.go index 7c046e139..fd713dafd 100644 --- a/cmd/helm/serve.go +++ b/cmd/helm/serve.go @@ -22,5 +22,5 @@ var serveCmd = &cobra.Command{ } func serve(cmd *cobra.Command, args []string) { - repo.StartLocalRepo(localRepoPath) + repo.StartLocalRepo(localRepoDirectory()) } diff --git a/cmd/helm/structure.go b/cmd/helm/structure.go new file mode 100644 index 000000000..056be291d --- /dev/null +++ b/cmd/helm/structure.go @@ -0,0 +1,31 @@ +package main + +import ( + "os" + "path/filepath" +) + +const ( + repositoriesFilePath string = "repositories.yaml" + cachePath string = "cache" + localRepoPath string = "local" + localRepoCacheFilePath string = "cache.yaml" +) + +func homePath() string { + return os.ExpandEnv(helmHome) +} + +func cacheDirectory(paths ...string) string { + fragments := append([]string{homePath(), cachePath}, paths...) + return filepath.Join(fragments...) +} + +func localRepoDirectory(paths ...string) string { + fragments := append([]string{homePath(), localRepoPath}, paths...) + return filepath.Join(fragments...) +} + +func repositoriesFile() string { + return filepath.Join(homePath(), repositoriesFilePath) +} From 9484d8d44089f0329a8ae80911a516d264b5005e Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Wed, 27 Apr 2016 11:10:37 -0700 Subject: [PATCH 79/86] fix(glide): update dependencies --- glide.lock | 109 ++++++++++++++++++++++++++++++++++------------------- glide.yaml | 16 ++++---- 2 files changed, 78 insertions(+), 47 deletions(-) diff --git a/glide.lock b/glide.lock index 9e70ce56b..c95060074 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 0b39c22befe9d14b5805603bd36ff0c77043c4dbd604e42132a55dcaa4e61228 -updated: 2016-04-23T13:10:56.180563322-07:00 +hash: 78e69db1e2c08b4c326c61c47d602ccb5d0d4a782e5047a460e984d0c22f43a3 +updated: 2016-04-27T13:31:53.384640094-07:00 imports: - name: bitbucket.org/ww/goautoneg version: 75cd24fc2f2c @@ -17,46 +17,47 @@ imports: version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d subpackages: - spew +- name: github.com/docker/distribution + version: 55f1b7651f6242617133312ff8af5c2e4e3628ea + subpackages: + - digest + - reference - name: github.com/docker/docker version: 0f5c9d301b9b1cca66b3ea0f9dec3b5317d3686d subpackages: - pkg/jsonmessage - pkg/mount - - pkg/parsers - pkg/stdcopy - pkg/symlink - pkg/term - pkg/timeutils - pkg/units +- name: github.com/docker/engine-api + version: 26cdffeca716ae4df98070051a852b3198d7d153 + subpackages: + - client + - types + - types/container + - types/filters + - types/network + - types/registry + - types/blkiodev + - types/strslice +- name: github.com/docker/go-connections + version: f549a9393d05688dff0992ef3efd8bbe6c628aeb + subpackages: + - nat + - sockets + - tlsconfig - name: github.com/docker/go-units version: 0bbddae09c5a5419a8c6dcdd7ff90da3d450393b - name: github.com/emicklei/go-restful - version: 777bb3f19bcafe2575ffb2a3e46af92509ae9594 + version: 496d495156da218b9912f03dfa7df7f80fbd8cc3 subpackages: - swagger - log - name: github.com/evanphx/json-patch version: 7dd4489c2eb6073e5a9d7746c3274c5b5f0387df -- name: github.com/fsouza/go-dockerclient - version: 0099401a7342ad77e71ca9f9a57c5e72fb80f6b2 - subpackages: - - external/github.com/docker/docker/opts - - external/github.com/docker/docker/pkg/archive - - external/github.com/docker/docker/pkg/fileutils - - external/github.com/docker/docker/pkg/homedir - - external/github.com/docker/docker/pkg/stdcopy - - external/github.com/hashicorp/go-cleanhttp - - external/github.com/Sirupsen/logrus - - external/github.com/docker/docker/pkg/idtools - - external/github.com/docker/docker/pkg/ioutils - - external/github.com/docker/docker/pkg/pools - - external/github.com/docker/docker/pkg/promise - - external/github.com/docker/docker/pkg/system - - external/github.com/docker/docker/pkg/longpath - - external/github.com/opencontainers/runc/libcontainer/user - - external/golang.org/x/sys/unix - - external/golang.org/x/net/context - - external/github.com/docker/go-units - name: github.com/ghodss/yaml version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee - name: github.com/gogo/protobuf @@ -160,11 +161,11 @@ imports: - name: github.com/prometheus/procfs version: 490cc6eb5fa45bf8a8b7b73c8bc82a8160e8531d - name: github.com/spf13/cobra - version: 4c05eb1145f16d0e6bb4a3e1b6d769f4713cb41f + version: e14e47b7a916ed178f4559ebd7e625cf16410181 subpackages: - cobra - name: github.com/spf13/pflag - version: 8f6a28b0916586e7f22fe931ae2fcfc380b1c0e6 + version: cb88ea77998c3f024757528e3305022ab50b43be - name: github.com/technosophos/moniker version: 9f956786b91d9786ca11aa5be6104542fa911546 - name: github.com/ugorji/go @@ -172,13 +173,38 @@ imports: subpackages: - codec - name: golang.org/x/net - version: c2528b2dd8352441850638a8bb678c2ad056fd3e + version: fb93926129b8ec0056f2f458b1f519654814edf0 subpackages: - context - http2 - trace - http2/hpack - internal/timeseries + - context/ctxhttp +- name: golang.org/x/oauth2 + version: b5adcc2dcdf009d0391547edc6ecbaff889f5bb9 + subpackages: + - google + - internal + - jwt + - jws +- name: google.golang.org/appengine + version: 12d5545dc1cfa6047a286d5e853841b6471f4c19 + subpackages: + - internal + - internal/app_identity + - internal/base + - internal/datastore + - internal/log + - internal/modules + - internal/remote_api + - urlfetch + - internal/urlfetch +- name: google.golang.org/cloud + version: eb47ba841d53d93506cfbfbc03927daf9cc48f88 + subpackages: + - compute/metadata + - internal - name: google.golang.org/grpc version: dec33edc378cf4971a2741cfd86ed70a644d6ba3 subpackages: @@ -197,33 +223,33 @@ imports: subpackages: - api/v1/types - name: k8s.io/kubernetes - version: 6a87dba0b8a50dccaddb67a4c7748696db1918ec + version: 95f2ca2ff65a03342746a2a49b8f360428dd94a2 subpackages: - - pkg/api - - pkg/api/errors - - pkg/api/meta - - pkg/api/testapi - - pkg/api/unversioned - - pkg/client/restclient - - pkg/client/unversioned - pkg/client/unversioned/clientcmd - - pkg/client/unversioned/clientcmd/api - pkg/kubectl/cmd/util - pkg/kubectl/resource - - pkg/runtime + - pkg/api + - pkg/api/unversioned + - pkg/client/restclient - pkg/client/unversioned/auth + - pkg/client/unversioned/clientcmd/api - pkg/client/unversioned/clientcmd/api/latest + - pkg/runtime - pkg/util/errors - pkg/util/homedir - pkg/util/validation + - pkg/api/errors + - pkg/api/meta - pkg/api/validation - pkg/apimachinery - pkg/apimachinery/registered + - pkg/apis/apps - pkg/apis/autoscaling - pkg/apis/batch - pkg/apis/extensions - pkg/apis/metrics - pkg/client/typed/discovery + - pkg/client/unversioned - pkg/client/unversioned/adapters/internalclientset - pkg/kubectl - pkg/labels @@ -254,6 +280,7 @@ imports: - pkg/client/unversioned/clientcmd/api/v1 - pkg/runtime/serializer/versioning - pkg/conversion/queryparams + - pkg/util/json - pkg/util/validation/field - pkg/api/endpoints - pkg/api/pod @@ -261,6 +288,7 @@ imports: - pkg/api/util - pkg/capabilities - pkg/api/install + - pkg/apis/apps/install - pkg/apis/authorization/install - pkg/apis/autoscaling/install - pkg/apis/batch/install @@ -268,6 +296,7 @@ imports: - pkg/apis/extensions/install - pkg/apis/metrics/install - pkg/util/wait + - plugin/pkg/client/auth - pkg/client/clientset_generated/internalclientset - pkg/client/clientset_generated/internalclientset/typed/core/unversioned - pkg/client/clientset_generated/internalclientset/typed/extensions/unversioned @@ -282,21 +311,25 @@ imports: - pkg/apis/extensions/v1beta1 - pkg/apis/extensions/validation - pkg/registry/generic + - pkg/util/framer - third_party/forked/json - pkg/util/runtime - third_party/forked/reflect - pkg/runtime/serializer/protobuf - pkg/runtime/serializer/recognizer - - pkg/runtime/serializer/streaming - pkg/util/parsers + - pkg/watch/versioned - pkg/util/hash - pkg/util/net/sets + - pkg/apis/apps/v1alpha1 - pkg/apis/authorization - pkg/apis/authorization/v1beta1 - pkg/apis/autoscaling/v1 - pkg/apis/componentconfig - pkg/apis/componentconfig/v1alpha1 - pkg/apis/metrics/v1alpha1 + - plugin/pkg/client/auth/gcp + - pkg/client/clientset_generated/internalclientset/typed/batch/unversioned - pkg/controller - pkg/util/labels - pkg/util/pod diff --git a/glide.yaml b/glide.yaml index c7240de0d..d150d3442 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,6 +1,7 @@ package: github.com/deis/tiller import: - package: golang.org/x/net + version: fb93926129b8ec0056f2f458b1f519654814edf0 subpackages: - context - package: github.com/spf13/cobra @@ -12,22 +13,19 @@ import: - package: github.com/Masterminds/semver version: 1.1.0 - package: github.com/BurntSushi/toml + version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f - package: github.com/technosophos/moniker - package: github.com/golang/protobuf version: f0a097ddac24fb00e07d2ac17f8671423f3ea47c + subpackages: + - proto + - ptypes/any + - ptypes/timestamp - package: google.golang.org/grpc version: dec33edc378cf4971a2741cfd86ed70a644d6ba3 - package: k8s.io/kubernetes + version: ^1.2 subpackages: - - pkg/api - - pkg/api/errors - - pkg/api/meta - - pkg/api/testapi - - pkg/api/unversioned - - pkg/client/restclient - - pkg/client/unversioned - pkg/client/unversioned/clientcmd - - pkg/client/unversioned/clientcmd/api - pkg/kubectl/cmd/util - pkg/kubectl/resource - - pkg/runtime From 15a085a9cb3c414355db4816193c66fcdc907119 Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 27 Apr 2016 14:40:40 -0600 Subject: [PATCH 80/86] docs(README): udate core docs pre-merge --- CONTRIBUTING.md | 37 +++++++++ LICENSE | 202 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 40 +++++++++- 3 files changed, 275 insertions(+), 4 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..00336c131 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Contributing Guidelines + +The Kubernetes Helm project accepts contributions via GitHub pull requests. This document outlines the process to help get your contribution accepted. + +## Contributor License Agreements + +We'd love to accept your patches! Before we can take them, we have to jump a couple of legal hurdles. + +Please fill out either the individual or corporate Contributor License Agreement (CLA). + + * If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html). + * If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html). + +Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests. + +***NOTE***: Only original source code from you and other people that have signed the CLA can be accepted into the main repository. + +## How to Contribute A Patch + +1. If you haven't already done so, sign a Contributor License Agreement (see details above). +1. Fork the desired repo, develop and test your code changes. +1. Submit a pull request. + +### Merge Approval + +Helm collaborators may add "LGTM" (Looks Good To Me) or an equivalent comment to indicate that a PR is acceptable. Any change requires at least one LGTM. No pull requests can be merged until at least one Helm collaborator signs off with an LGTM. + +If the PR is from a Helm collaborator, then he or she should be the one to merge and close it. This keeps the commit stream clean and gives the collaborator the benefit of revisiting the PR before deciding whether or not to merge the changes. + +## Support Channels + +Whether you are a user or contributor, official support channels include: + +- GitHub issues: https://github.com/kubenetes/helm/issues/new +- Slack: #Helm room in the [Kubernetes Slack](http://slack.kubernetes.io/) + +Before opening a new issue or submitting a new pull request, it's helpful to search the project - it's likely that another user has already reported the issue you're facing, or it's a known issue that we're already aware of. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md index 1ceae5fc6..81872159e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,38 @@ -# tiller -helmd +# Kubernetes Helm -# Runtime Requirements +Helm is a tool for managing Kubernetes charts. Charts are packages of +pre-configured Kubernetes resources. -- kubectl 1.2 or later +## Install + +Helm is in its early stages of development. At this time there are no +releases. + +To install Helm from source, follow this process: + +Make sure you have the prerequisites: +- Go 1.6 +- A running Kubernetes cluster +- `kubectl` properly configured to talk to your cluster +- Glide 0.10 or greater + +1. Clone (or otherwise download) this repository +2. Run `make boostrap build` + +You will now have two binaries built: + +- `bin/helm` is the client +- `bin/tiller` is the server + +You can locally run Tiller, or you build a Docker image (`make +docker-build`) and then deploy it (`helm init -i IMAGE_NAME`). + +The [documentation](docs) folder contains more information about the +architecture and usage of Helm/Tiller. + +## The History of the Project + +Kubernetes Helm is the merged result of [Helm +Classic](https://github.com/helm/helm) and the Kubernetes port of GCS Deployment +Manager. The project was jointly started by Google and Deis, though it +is now part of the CNCF. From e250328e224f0ea2afbc92c6ce5c4961614bbecd Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Mon, 25 Apr 2016 13:10:33 -0600 Subject: [PATCH 81/86] feat(helm): add helm repo add command --- cmd/helm/repo.go | 72 +++++++++++++++++++++++++++++++++++++++++++ cmd/helm/repo_test.go | 35 +++++++++++++++++++++ pkg/repo/repo.go | 40 ++++++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 cmd/helm/repo.go create mode 100644 cmd/helm/repo_test.go create mode 100644 pkg/repo/repo.go diff --git a/cmd/helm/repo.go b/cmd/helm/repo.go new file mode 100644 index 000000000..e60bd5f41 --- /dev/null +++ b/cmd/helm/repo.go @@ -0,0 +1,72 @@ +package main + +import ( + "fmt" + "os" + + "github.com/deis/tiller/pkg/repo" + "github.com/spf13/cobra" + "gopkg.in/yaml.v2" +) + +func init() { + repoCmd.AddCommand(repoAddCmd) + RootCommand.AddCommand(repoCmd) +} + +var repoCmd = &cobra.Command{ + Use: "repo add|remove|list [ARG]", + Short: "add, list, or remove chart repositories", +} + +var repoAddCmd = &cobra.Command{ + Use: "add [flags] [NAME] [URL]", + Short: "add a chart repository", + RunE: runRepoAdd, +} + +func runRepoAdd(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return fmt.Errorf("This command needs two argument, a name for the chart repository and the url of the chart repository") + } + + err := insertRepoLine(args[0], args[1]) + if err != nil { + return err + } + fmt.Println(args[0] + " has been added to your repositories") + return nil +} + +func insertRepoLine(name, url string) error { + err := checkUniqueName(name) + if err != nil { + return err + } + + b, _ := yaml.Marshal(map[string]string{name: url}) + f, err := os.OpenFile(repositoriesFile(), os.O_APPEND|os.O_WRONLY, 0666) + if err != nil { + return err + } + defer f.Close() + _, err = f.Write(b) + if err != nil { + return err + } + + return nil +} + +func checkUniqueName(name string) error { + file, err := repo.LoadRepositoriesFile(repositoriesFile()) + if err != nil { + return err + } + + _, ok := file.Repositories[name] + if ok { + return fmt.Errorf("The repository name you provided (%s) already exists. Please specifiy a different name.", name) + } + return nil +} diff --git a/cmd/helm/repo_test.go b/cmd/helm/repo_test.go new file mode 100644 index 000000000..f4c234ebf --- /dev/null +++ b/cmd/helm/repo_test.go @@ -0,0 +1,35 @@ +package main + +import ( + "testing" + + "github.com/deis/tiller/pkg/repo" +) + +func TestRepoAdd(t *testing.T) { + home := createTmpHome() + helmHome = home + if err := ensureHome(); err != nil { + t.Errorf("%s", err) + } + + testName := "test-name" + testURL := "test-url" + if err := insertRepoLine(testName, testURL); err != nil { + t.Errorf("%s", err) + } + + f, err := repo.LoadRepositoriesFile(repositoriesFile()) + if err != nil { + t.Errorf("%s", err) + } + _, ok := f.Repositories[testName] + if !ok { + t.Errorf("%s was not successfully inserted into %s", testName, repositoriesFile()) + } + + if err := insertRepoLine(testName, testURL); err == nil { + t.Errorf("Duplicate repository name was added") + } + +} diff --git a/pkg/repo/repo.go b/pkg/repo/repo.go new file mode 100644 index 000000000..13138c10d --- /dev/null +++ b/pkg/repo/repo.go @@ -0,0 +1,40 @@ +package repo + +import ( + "io/ioutil" + + "gopkg.in/yaml.v2" +) + +// RepoFile represents the .repositories file in $HELM_HOME +type RepoFile struct { + Repositories map[string]string +} + +// LoadRepositoriesFile takes a file at the given path and returns a RepoFile object +func LoadRepositoriesFile(path string) (*RepoFile, error) { + b, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + var r RepoFile + err = yaml.Unmarshal(b, &r) + if err != nil { + return nil, err + } + + return &r, nil +} + +// UnmarshalYAML unmarshals the repo file +func (rf *RepoFile) UnmarshalYAML(unmarshal func(interface{}) error) error { + var repos map[string]string + if err := unmarshal(&repos); err != nil { + if _, ok := err.(*yaml.TypeError); !ok { + return err + } + } + rf.Repositories = repos + return nil +} From 08d9b85f9954b65e011044e57a9ad96049159c4b Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Wed, 27 Apr 2016 14:31:26 -0700 Subject: [PATCH 82/86] fix(kube): use a fake client in unit tests --- pkg/kube/client.go | 11 +++-------- pkg/kube/client_test.go | 14 +++++++++++--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index a536505e3..7f6acf029 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -19,16 +19,11 @@ type ResourceActorFunc func(*resource.Info) error // Namespace will set the namespace // Config allows for overiding values from kubectl func Create(namespace string, reader io.Reader, config clientcmd.ClientConfig) error { - return perform(namespace, reader, createResource, config) -} - -func perform(namespace string, reader io.Reader, fn ResourceActorFunc, config clientcmd.ClientConfig) error { f := cmdutil.NewFactory(config) - //schema, err := f.Validator(true, "") - //if err != nil { - //return err - //} + return perform(f, namespace, reader, createResource) +} +func perform(f *cmdutil.Factory, namespace string, reader io.Reader, fn ResourceActorFunc) error { r := f.NewBuilder(includeThirdPartyAPIs). ContinueOnError(). NamespaceParam(namespace). diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 15de2d2ac..daeb3dee4 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -4,15 +4,18 @@ import ( "os" "testing" + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/client/unversioned/fake" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" ) func TestPerform(t *testing.T) { - f, err := os.Open("./testdata/guestbook-all-in-one.yaml") + input, err := os.Open("./testdata/guestbook-all-in-one.yaml") if err != nil { t.Fatal(err) } - defer f.Close() + defer input.Close() results := []*resource.Info{} @@ -26,7 +29,12 @@ func TestPerform(t *testing.T) { return nil } - if err := perform("test", f, fn, nil); err != nil { + f := cmdutil.NewFactory(nil) + f.ClientForMapping = func(mapping *meta.RESTMapping) (resource.RESTClient, error) { + return &fake.RESTClient{}, nil + } + + if err := perform(f, "test", input, fn); err != nil { t.Fatalf("Unexpected error: %s", err) } From b6411be797b381ffc7fede1c2ced29c118f4157b Mon Sep 17 00:00:00 2001 From: Michelle Noorali Date: Wed, 27 Apr 2016 15:50:56 -0600 Subject: [PATCH 83/86] feat(helm): add helm repo list command --- cmd/helm/repo.go | 27 +++++++++++++++++++++++++++ glide.lock | 11 +++++++++-- glide.yaml | 7 ++++--- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/cmd/helm/repo.go b/cmd/helm/repo.go index e60bd5f41..43f32f15a 100644 --- a/cmd/helm/repo.go +++ b/cmd/helm/repo.go @@ -5,12 +5,14 @@ import ( "os" "github.com/deis/tiller/pkg/repo" + "github.com/gosuri/uitable" "github.com/spf13/cobra" "gopkg.in/yaml.v2" ) func init() { repoCmd.AddCommand(repoAddCmd) + repoCmd.AddCommand(repoListCmd) RootCommand.AddCommand(repoCmd) } @@ -25,6 +27,12 @@ var repoAddCmd = &cobra.Command{ RunE: runRepoAdd, } +var repoListCmd = &cobra.Command{ + Use: "list [flags]", + Short: "list chart repositories", + RunE: runRepoList, +} + func runRepoAdd(cmd *cobra.Command, args []string) error { if len(args) != 2 { return fmt.Errorf("This command needs two argument, a name for the chart repository and the url of the chart repository") @@ -38,6 +46,25 @@ func runRepoAdd(cmd *cobra.Command, args []string) error { return nil } +func runRepoList(cmd *cobra.Command, args []string) error { + f, err := repo.LoadRepositoriesFile(repositoriesFile()) + if err != nil { + return err + } + if len(f.Repositories) == 0 { + fmt.Println("No repositories to show") + return nil + } + table := uitable.New() + table.MaxColWidth = 50 + table.AddRow("NAME", "URL") + for k, v := range f.Repositories { + table.AddRow(k, v) + } + fmt.Println(table) + return nil +} + func insertRepoLine(name, url string) error { err := checkUniqueName(name) if err != nil { diff --git a/glide.lock b/glide.lock index c95060074..52380e0f9 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 78e69db1e2c08b4c326c61c47d602ccb5d0d4a782e5047a460e984d0c22f43a3 -updated: 2016-04-27T13:31:53.384640094-07:00 +hash: 998d87445fec0bd715fa5ccbcc227cb4997e56ceff58dc8eb53ea2e0cc84abfd +updated: 2016-04-27T16:11:47.531200165-06:00 imports: - name: bitbucket.org/ww/goautoneg version: 75cd24fc2f2c @@ -121,6 +121,11 @@ imports: - version - name: github.com/google/gofuzz version: bbcb9da2d746f8bdbd6a936686a0a6067ada0ec5 +- name: github.com/gosuri/uitable + version: 36ee7e946282a3fb1cfecd476ddc9b35d8847e42 + subpackages: + - util/strutil + - util/wordwrap - name: github.com/imdario/mergo version: 6633656539c1639d9d78127b7d47c622b5d7b6dc - name: github.com/inconshreveable/mousetrap @@ -131,6 +136,8 @@ imports: version: 808ed7761c233af2de3f9729a041d68c62527f3a - name: github.com/Masterminds/sprig version: e6494bc7e81206ba6db404d2fd96500ffc453407 +- name: github.com/mattn/go-runewidth + version: d6bea18f789704b5f83375793155289da36a3c7f - name: github.com/matttproud/golang_protobuf_extensions version: fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a subpackages: diff --git a/glide.yaml b/glide.yaml index d150d3442..5cc7b0744 100644 --- a/glide.yaml +++ b/glide.yaml @@ -26,6 +26,7 @@ import: - package: k8s.io/kubernetes version: ^1.2 subpackages: - - pkg/client/unversioned/clientcmd - - pkg/kubectl/cmd/util - - pkg/kubectl/resource + - pkg/client/unversioned/clientcmd + - pkg/kubectl/cmd/util + - pkg/kubectl/resource +- package: github.com/gosuri/uitable From d58cfc46fd6d37b8fcfe033c1bb04bfd20c3fa8e Mon Sep 17 00:00:00 2001 From: Matt Butcher Date: Wed, 27 Apr 2016 21:29:54 -0600 Subject: [PATCH 84/86] reset --- .gitignore | 19 - CONTRIBUTING.md | 81 -- LICENSE | 202 ----- MAINTAINERS.md | 61 -- Makefile | 118 --- README.md | 162 ---- circle.yml | 33 - cmd/doc.go | 18 - cmd/expandybird/expander/expander.go | 131 --- cmd/expandybird/expander/expander_test.go | 832 ------------------ cmd/expandybird/main.go | 45 - cmd/expandybird/test/ExpectedOutput.yaml | 77 -- cmd/expandybird/test/InvalidFileName.yaml | 22 - cmd/expandybird/test/InvalidProperty.yaml | 22 - cmd/expandybird/test/InvalidTypeName.yaml | 22 - cmd/expandybird/test/MalformedContent.yaml | 20 - cmd/expandybird/test/MissingImports.yaml | 21 - cmd/expandybird/test/MissingResourceName.yaml | 21 - cmd/expandybird/test/MissingTypeName.yaml | 21 - cmd/expandybird/test/TestArchive.tar | Bin 9732 -> 0 bytes cmd/expandybird/test/ValidContent.yaml | 27 - cmd/expandybird/test/replicatedservice.py | 182 ---- cmd/expandybird/test/schemas/bad.jinja.schema | 9 - .../test/schemas/default_ref.jinja.schema | 14 - .../test/schemas/defaults.jinja.schema | 12 - .../test/schemas/defaults.py.schema | 12 - .../test/schemas/invalid_default.jinja.schema | 11 - .../test/schemas/invalid_reference.py.schema | 10 - .../invalid_reference_schema.py.schema | 8 - .../test/schemas/metadata.py.schema | 20 - .../test/schemas/missing_quote.py.schema | 11 - .../test/schemas/nested_defaults.py.schema | 33 - .../test/schemas/numbers.py.schema | 27 - .../schemas/ref_nested_defaults.py.schema | 36 - .../test/schemas/reference.jinja.schema | 14 - .../test/schemas/req_default_ref.py.schema | 14 - .../test/schemas/required.jinja.schema | 10 - .../schemas/required_default.jinja.schema | 11 - .../test/templates/description_text.txt | 1 - .../test/templates/duplicate_names.yaml | 9 - .../test/templates/duplicate_names_B.jinja | 5 - .../test/templates/duplicate_names_C.jinja | 5 - .../duplicate_names_in_subtemplates.jinja | 9 - .../duplicate_names_in_subtemplates.yaml | 5 - .../duplicate_names_mixed_level.yaml | 7 - .../duplicate_names_mixed_level_result.yaml | 22 - .../duplicate_names_parent_child.yaml | 7 - .../duplicate_names_parent_child_result.yaml | 17 - cmd/expandybird/test/templates/helper.jinja | 5 - .../test/templates/helper.jinja.schema | 4 - .../test/templates/helpers/common.jinja | 3 - .../test/templates/helpers/common.py | 6 - .../test/templates/helpers/extra/__init__.py | 1 - .../test/templates/helpers/extra/common2.py | 6 - .../test/templates/invalid_config.yaml | 2 - .../test/templates/jinja_defaults.jinja | 16 - .../templates/jinja_defaults.jinja.schema | 18 - .../test/templates/jinja_defaults.yaml | 9 - .../test/templates/jinja_defaults_result.yaml | 29 - .../templates/jinja_missing_required.jinja | 4 - .../jinja_missing_required.jinja.schema | 11 - .../templates/jinja_missing_required.yaml | 9 - .../templates/jinja_multiple_errors.jinja | 4 - .../jinja_multiple_errors.jinja.schema | 22 - .../test/templates/jinja_multiple_errors.yaml | 12 - .../test/templates/jinja_noparams.jinja | 19 - .../test/templates/jinja_noparams.yaml | 6 - .../test/templates/jinja_noparams_result.yaml | 41 - .../test/templates/jinja_template.jinja | 18 - .../test/templates/jinja_template.yaml | 10 - .../test/templates/jinja_template_result.yaml | 28 - .../templates/jinja_template_with_env.jinja | 18 - .../templates/jinja_template_with_env.yaml | 8 - .../jinja_template_with_env_result.yaml | 26 - .../jinja_template_with_import.jinja | 6 - .../templates/jinja_template_with_import.yaml | 5 - .../jinja_template_with_import_result.yaml | 13 - .../jinja_template_with_inlinedfile.jinja | 7 - .../jinja_template_with_inlinedfile.yaml | 7 - ...inja_template_with_inlinedfile_result.yaml | 21 - .../test/templates/jinja_unresolved.jinja | 18 - .../test/templates/jinja_unresolved.yaml | 10 - .../test/templates/no_properties.py | 5 - .../test/templates/no_properties.yaml | 6 - .../test/templates/no_properties_result.yaml | 6 - .../test/templates/no_resources.py | 6 - .../test/templates/no_resources.yaml | 6 - .../templates/python_and_jinja_template.jinja | 17 - .../templates/python_and_jinja_template.py | 35 - .../templates/python_and_jinja_template.yaml | 9 - .../python_and_jinja_template_result.yaml | 35 - .../test/templates/python_bad_schema.py | 3 - .../templates/python_bad_schema.py.schema | 19 - .../test/templates/python_bad_schema.yaml | 9 - .../test/templates/python_noparams.py | 12 - .../test/templates/python_noparams.yaml | 9 - .../templates/python_noparams_result.yaml | 19 - .../test/templates/python_schema.py | 57 -- .../test/templates/python_schema.py.schema | 14 - .../test/templates/python_schema.yaml | 10 - .../test/templates/python_schema_result.yaml | 46 - .../test/templates/python_template.py | 57 -- .../test/templates/python_template.yaml | 9 - .../templates/python_template_result.yaml | 47 - .../templates/python_template_with_env.py | 57 -- .../templates/python_template_with_env.yaml | 8 - .../python_template_with_env_result.yaml | 46 - .../templates/python_template_with_import.py | 18 - .../python_template_with_import.yaml | 5 - .../python_template_with_import_result.yaml | 13 - .../python_template_with_inlinedfile.py | 20 - .../python_template_with_inlinedfile.yaml | 7 - ...thon_template_with_inlinedfile_result.yaml | 21 - .../test/templates/python_with_exception.py | 7 - .../test/templates/python_with_exception.yaml | 9 - cmd/expandybird/test/templates/simple.yaml | 18 - .../test/templates/simple_result.yaml | 21 - .../test/templates/use_helper.jinja | 7 - .../test/templates/use_helper.jinja.schema | 4 - .../test/templates/use_helper.yaml | 3 - .../test/templates/use_helper_result.yaml | 26 - cmd/goexpander/expander/expander.go | 146 --- cmd/goexpander/expander/expander_test.go | 262 ------ cmd/goexpander/main.go | 41 - cmd/helm/Makefile | 39 - cmd/helm/chart.go | 113 --- cmd/helm/chart_upload.go | 115 --- cmd/helm/deploy.go | 111 --- cmd/helm/deployment.go | 187 ---- cmd/helm/deployment_test.go | 107 --- cmd/helm/doctor.go | 49 -- cmd/helm/helm.go | 119 --- cmd/helm/helm_test.go | 118 --- cmd/helm/properties.go | 56 -- cmd/helm/repository.go | 130 --- cmd/helm/server.go | 195 ---- cmd/manager/chartrepos.go | 169 ---- cmd/manager/chartrepos_test.go | 174 ---- cmd/manager/deployments.go | 425 --------- cmd/manager/deployments_test.go | 215 ----- cmd/manager/main.go | 88 -- cmd/manager/manager/deployer.go | 187 ---- cmd/manager/manager/deployer_test.go | 304 ------- cmd/manager/manager/expander.go | 225 ----- cmd/manager/manager/expander_test.go | 373 -------- cmd/manager/manager/manager.go | 447 ---------- cmd/manager/manager/manager_test.go | 551 ------------ .../repository/persistent/persistent.go | 488 ---------- .../repository/persistent/persistent_test.go | 110 --- cmd/manager/repository/repository.go | 49 -- cmd/manager/repository/test_common.go | 340 ------- cmd/manager/repository/transient/transient.go | 325 ------- .../repository/transient/transient_test.go | 55 -- cmd/manager/router/context.go | 69 -- cmd/manager/router/router.go | 96 -- cmd/manager/router/router_test.go | 56 -- cmd/manager/testutil.go | 172 ---- cmd/resourcifier/configurations.go | 255 ------ cmd/resourcifier/configurator/configurator.go | 262 ------ cmd/resourcifier/main.go | 84 -- docs/design/architecture.dia | Bin 1785 -> 0 bytes docs/design/architecture.png | Bin 13472 -> 0 bytes docs/design/chart_format.md | 363 -------- docs/design/design.md | 434 --------- docs/design/provenance_proposal.md | 67 -- docs/design/user_stories.md | 33 - docs/pushing.md | 22 - docs/pushing_charts.md | 64 -- docs/templates/registry.md | 289 ------ docs/test-architecture.md | 59 -- docs/usage_docs/authoring_charts.md | 1 - docs/usage_docs/getting-started-guide.md | 22 - docs/workflow/developer-workflows.md | 540 ------------ docs/workflow/helm-dm-diagrams.src.md | 84 -- docs/workflow/helm-official-workflow.png | Bin 30322 -> 0 bytes docs/workflow/private-chart-no-repo.png | Bin 18297 -> 0 bytes docs/workflow/private-chart-repo.png | Bin 45502 -> 0 bytes docs/workflow/public-chart-repo.png | Bin 22859 -> 0 bytes docs/workflow/team-workflows.md | 226 ----- examples/charts/nginx/Chart.yaml | 12 - examples/charts/nginx/LICENSE | 0 examples/charts/nginx/README.md | 1 - examples/charts/nginx/templates/nginx-rc.yaml | 18 - .../charts/nginx/templates/nginx-svc.yaml | 13 - examples/charts/redis/Chart.yaml | 7 - examples/charts/redis/templates/redis.jinja | 32 - .../charts/redis/templates/redis.jinja.schema | 10 - examples/charts/replicatedservice/Chart.yaml | 7 - .../templates/replicatedservice.py | 195 ---- .../templates/replicatedservice.py.schema | 91 -- examples/guestbook/README.md | 125 --- examples/guestbook/guestbook.yaml | 12 - examples/package/cassandra.yaml | 4 - examples/wordpress/README.md | 155 ---- examples/wordpress/architecture.png | Bin 33703 -> 0 bytes examples/wordpress/images/nginx/Dockerfile | 2 - examples/wordpress/images/nginx/Makefile | 23 - examples/wordpress/images/nginx/default.conf | 48 - examples/wordpress/wordpress-resources.yaml | 12 - examples/wordpress/wordpress.jinja | 71 -- examples/wordpress/wordpress.jinja.schema | 69 -- examples/wordpress/wordpress.yaml | 6 - expansion/expansion.py | 394 --------- expansion/expansion_test.py | 511 ----------- expansion/file_expander.py | 49 -- expansion/requirements.txt | 3 - expansion/sandbox_loader.py | 88 -- expansion/schema_validation.py | 240 ----- expansion/schema_validation_test.py | 619 ------------- expansion/schema_validation_utils.py | 99 --- get-install.sh | 60 -- glide.lock | 70 -- glide.yaml | 24 - hack/README.md | 24 - hack/Vagrantfile | 49 -- pkg/chart/chart.go | 460 ---------- pkg/chart/chart_test.go | 287 ------ pkg/chart/chartfile.go | 110 --- pkg/chart/chartfile_test.go | 91 -- pkg/chart/doc.go | 23 - pkg/chart/locator.go | 234 ----- pkg/chart/locator_test.go | 210 ----- pkg/chart/save.go | 120 --- pkg/chart/save_test.go | 123 --- pkg/chart/testdata/README.md | 9 - pkg/chart/testdata/frobnitz-0.0.1.tgz | Bin 2382 -> 0 bytes pkg/chart/testdata/frobnitz/Chart.yaml | 33 - pkg/chart/testdata/frobnitz/LICENSE | 1 - pkg/chart/testdata/frobnitz/README.md | 11 - pkg/chart/testdata/frobnitz/docs/README.md | 1 - .../testdata/frobnitz/hooks/pre-install.py | 1 - pkg/chart/testdata/frobnitz/icon.svg | 8 - .../templates/wordpress-resources.yaml | 12 - .../frobnitz/templates/wordpress.jinja | 72 -- .../frobnitz/templates/wordpress.jinja.schema | 69 -- .../frobnitz/templates/wordpress.yaml | 6 - pkg/chart/testdata/ill-1.2.3.tgz | Bin 2305 -> 0 bytes pkg/chart/testdata/ill/Chart.yaml | 28 - pkg/chart/testdata/ill/LICENSE | 1 - pkg/chart/testdata/ill/README.md | 11 - pkg/chart/testdata/ill/docs/README.md | 1 - pkg/chart/testdata/ill/hooks/pre-install.py | 1 - .../ill/templates/wordpress-resources.yaml | 12 - .../testdata/ill/templates/wordpress.jinja | 72 -- .../ill/templates/wordpress.jinja.schema | 69 -- .../testdata/ill/templates/wordpress.yaml | 6 - pkg/chart/testdata/nochart.tgz | Bin 325 -> 0 bytes pkg/chart/testdata/sprocket/Chart.yaml | 4 - pkg/chart/testdata/sprocket/LICENSE | 1 - pkg/chart/testdata/sprocket/README.md | 3 - pkg/chart/testdata/sprocket/docs/README.md | 1 - .../testdata/sprocket/hooks/pre-install.py | 1 - pkg/chart/testdata/sprocket/icon.svg | 8 - .../sprocket/templates/placeholder.txt | 1 - pkg/client/client.go | 282 ------ pkg/client/client_test.go | 107 --- pkg/client/deployments.go | 136 --- pkg/client/deployments_test.go | 163 ---- pkg/client/install.go | 237 ----- pkg/client/transport.go | 82 -- pkg/client/transport_test.go | 65 -- pkg/client/uninstall.go | 30 - pkg/common/types.go | 155 ---- pkg/doc.go | 2 - pkg/expansion/service.go | 96 -- pkg/expansion/service_test.go | 275 ------ pkg/expansion/types.go | 38 - pkg/expansion/validate.go | 126 --- pkg/expansion/validate_test.go | 194 ---- pkg/format/messages.go | 102 --- pkg/format/messages_test.go | 36 - pkg/httputil/doc.go | 21 - pkg/httputil/encoder.go | 194 ---- pkg/httputil/encoder_test.go | 162 ---- pkg/httputil/httperrors.go | 81 -- pkg/httputil/httperrors_test.go | 50 -- pkg/kubectl/cluster_info.go | 28 - pkg/kubectl/command.go | 49 -- pkg/kubectl/create.go | 37 - pkg/kubectl/create_test.go | 38 - pkg/kubectl/delete.go | 34 - pkg/kubectl/get.go | 74 -- pkg/kubectl/get_test.go | 45 - pkg/kubectl/kubectl.go | 48 - pkg/kubectl/kubectl_test.go | 32 - pkg/log/log.go | 68 -- pkg/log/log_test.go | 65 -- pkg/pkg_test.go | 15 - pkg/repo/filebased_credential_provider.go | 82 -- .../filebased_credential_provider_test.go | 57 -- pkg/repo/gcs_repo.go | 191 ---- pkg/repo/gcs_repo_test.go | 138 --- pkg/repo/inmem_credential_provider.go | 54 -- pkg/repo/inmem_credential_provider_test.go | 72 -- pkg/repo/inmem_repo_service.go | 156 ---- pkg/repo/inmem_repo_service_test.go | 145 --- pkg/repo/repo.go | 115 --- pkg/repo/repo_test.go | 67 -- pkg/repo/repoprovider.go | 258 ------ pkg/repo/repoprovider_test.go | 171 ---- pkg/repo/secrets_credential_provider.go | 133 --- pkg/repo/testdata/test_credentials_file.yaml | 6 - pkg/repo/types.go | 144 --- pkg/util/httpclient.go | 162 ---- pkg/util/httpclient_test.go | 163 ---- pkg/util/httputil.go | 251 ------ pkg/util/kubernetes.go | 119 --- pkg/util/kubernetes_kubectl.go | 147 ---- pkg/util/kubernetesutil.go | 54 -- pkg/util/kubernetesutil_test.go | 167 ---- pkg/util/templateutil.go | 196 ----- pkg/util/templateutil_test.go | 148 ---- pkg/version/version.go | 26 - rootfs/Makefile | 26 - rootfs/README.md | 26 - rootfs/expandybird/.dockerignore | 3 - rootfs/expandybird/Dockerfile | 27 - rootfs/expandybird/Makefile | 24 - rootfs/include.mk | 88 -- rootfs/manager/.dockerignore | 3 - rootfs/manager/Dockerfile | 22 - rootfs/manager/Makefile | 21 - rootfs/resourcifier/.dockerignore | 3 - rootfs/resourcifier/Dockerfile | 22 - rootfs/resourcifier/Makefile | 21 - scripts/build-go.sh | 28 - scripts/cluster/kube-system.yaml | 4 - scripts/cluster/skydns.yaml | 137 --- scripts/common.sh | 114 --- scripts/coverage.sh | 38 - scripts/docker.sh | 54 -- scripts/kube-down.sh | 74 -- scripts/kube-up.sh | 195 ---- scripts/kubectl.sh | 30 - scripts/start-local.sh | 69 -- scripts/stop-local.sh | 13 - scripts/validate-go.sh | 54 -- 337 files changed, 25864 deletions(-) delete mode 100644 .gitignore delete mode 100644 CONTRIBUTING.md delete mode 100644 LICENSE delete mode 100644 MAINTAINERS.md delete mode 100644 Makefile delete mode 100644 README.md delete mode 100644 circle.yml delete mode 100644 cmd/doc.go delete mode 100644 cmd/expandybird/expander/expander.go delete mode 100644 cmd/expandybird/expander/expander_test.go delete mode 100644 cmd/expandybird/main.go delete mode 100644 cmd/expandybird/test/ExpectedOutput.yaml delete mode 100644 cmd/expandybird/test/InvalidFileName.yaml delete mode 100644 cmd/expandybird/test/InvalidProperty.yaml delete mode 100644 cmd/expandybird/test/InvalidTypeName.yaml delete mode 100644 cmd/expandybird/test/MalformedContent.yaml delete mode 100644 cmd/expandybird/test/MissingImports.yaml delete mode 100644 cmd/expandybird/test/MissingResourceName.yaml delete mode 100644 cmd/expandybird/test/MissingTypeName.yaml delete mode 100644 cmd/expandybird/test/TestArchive.tar delete mode 100644 cmd/expandybird/test/ValidContent.yaml delete mode 100644 cmd/expandybird/test/replicatedservice.py delete mode 100644 cmd/expandybird/test/schemas/bad.jinja.schema delete mode 100644 cmd/expandybird/test/schemas/default_ref.jinja.schema delete mode 100644 cmd/expandybird/test/schemas/defaults.jinja.schema delete mode 100644 cmd/expandybird/test/schemas/defaults.py.schema delete mode 100644 cmd/expandybird/test/schemas/invalid_default.jinja.schema delete mode 100644 cmd/expandybird/test/schemas/invalid_reference.py.schema delete mode 100644 cmd/expandybird/test/schemas/invalid_reference_schema.py.schema delete mode 100644 cmd/expandybird/test/schemas/metadata.py.schema delete mode 100644 cmd/expandybird/test/schemas/missing_quote.py.schema delete mode 100644 cmd/expandybird/test/schemas/nested_defaults.py.schema delete mode 100644 cmd/expandybird/test/schemas/numbers.py.schema delete mode 100644 cmd/expandybird/test/schemas/ref_nested_defaults.py.schema delete mode 100644 cmd/expandybird/test/schemas/reference.jinja.schema delete mode 100644 cmd/expandybird/test/schemas/req_default_ref.py.schema delete mode 100644 cmd/expandybird/test/schemas/required.jinja.schema delete mode 100644 cmd/expandybird/test/schemas/required_default.jinja.schema delete mode 100644 cmd/expandybird/test/templates/description_text.txt delete mode 100644 cmd/expandybird/test/templates/duplicate_names.yaml delete mode 100644 cmd/expandybird/test/templates/duplicate_names_B.jinja delete mode 100644 cmd/expandybird/test/templates/duplicate_names_C.jinja delete mode 100644 cmd/expandybird/test/templates/duplicate_names_in_subtemplates.jinja delete mode 100644 cmd/expandybird/test/templates/duplicate_names_in_subtemplates.yaml delete mode 100644 cmd/expandybird/test/templates/duplicate_names_mixed_level.yaml delete mode 100644 cmd/expandybird/test/templates/duplicate_names_mixed_level_result.yaml delete mode 100644 cmd/expandybird/test/templates/duplicate_names_parent_child.yaml delete mode 100644 cmd/expandybird/test/templates/duplicate_names_parent_child_result.yaml delete mode 100644 cmd/expandybird/test/templates/helper.jinja delete mode 100644 cmd/expandybird/test/templates/helper.jinja.schema delete mode 100644 cmd/expandybird/test/templates/helpers/common.jinja delete mode 100644 cmd/expandybird/test/templates/helpers/common.py delete mode 100644 cmd/expandybird/test/templates/helpers/extra/__init__.py delete mode 100644 cmd/expandybird/test/templates/helpers/extra/common2.py delete mode 100644 cmd/expandybird/test/templates/invalid_config.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_defaults.jinja delete mode 100644 cmd/expandybird/test/templates/jinja_defaults.jinja.schema delete mode 100644 cmd/expandybird/test/templates/jinja_defaults.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_defaults_result.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_missing_required.jinja delete mode 100644 cmd/expandybird/test/templates/jinja_missing_required.jinja.schema delete mode 100644 cmd/expandybird/test/templates/jinja_missing_required.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_multiple_errors.jinja delete mode 100644 cmd/expandybird/test/templates/jinja_multiple_errors.jinja.schema delete mode 100644 cmd/expandybird/test/templates/jinja_multiple_errors.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_noparams.jinja delete mode 100644 cmd/expandybird/test/templates/jinja_noparams.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_noparams_result.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_template.jinja delete mode 100644 cmd/expandybird/test/templates/jinja_template.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_template_result.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_template_with_env.jinja delete mode 100644 cmd/expandybird/test/templates/jinja_template_with_env.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_template_with_env_result.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_template_with_import.jinja delete mode 100644 cmd/expandybird/test/templates/jinja_template_with_import.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_template_with_import_result.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_template_with_inlinedfile.jinja delete mode 100644 cmd/expandybird/test/templates/jinja_template_with_inlinedfile.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_template_with_inlinedfile_result.yaml delete mode 100644 cmd/expandybird/test/templates/jinja_unresolved.jinja delete mode 100644 cmd/expandybird/test/templates/jinja_unresolved.yaml delete mode 100644 cmd/expandybird/test/templates/no_properties.py delete mode 100644 cmd/expandybird/test/templates/no_properties.yaml delete mode 100644 cmd/expandybird/test/templates/no_properties_result.yaml delete mode 100644 cmd/expandybird/test/templates/no_resources.py delete mode 100644 cmd/expandybird/test/templates/no_resources.yaml delete mode 100644 cmd/expandybird/test/templates/python_and_jinja_template.jinja delete mode 100644 cmd/expandybird/test/templates/python_and_jinja_template.py delete mode 100644 cmd/expandybird/test/templates/python_and_jinja_template.yaml delete mode 100644 cmd/expandybird/test/templates/python_and_jinja_template_result.yaml delete mode 100644 cmd/expandybird/test/templates/python_bad_schema.py delete mode 100644 cmd/expandybird/test/templates/python_bad_schema.py.schema delete mode 100644 cmd/expandybird/test/templates/python_bad_schema.yaml delete mode 100644 cmd/expandybird/test/templates/python_noparams.py delete mode 100644 cmd/expandybird/test/templates/python_noparams.yaml delete mode 100644 cmd/expandybird/test/templates/python_noparams_result.yaml delete mode 100644 cmd/expandybird/test/templates/python_schema.py delete mode 100644 cmd/expandybird/test/templates/python_schema.py.schema delete mode 100644 cmd/expandybird/test/templates/python_schema.yaml delete mode 100644 cmd/expandybird/test/templates/python_schema_result.yaml delete mode 100644 cmd/expandybird/test/templates/python_template.py delete mode 100644 cmd/expandybird/test/templates/python_template.yaml delete mode 100644 cmd/expandybird/test/templates/python_template_result.yaml delete mode 100644 cmd/expandybird/test/templates/python_template_with_env.py delete mode 100644 cmd/expandybird/test/templates/python_template_with_env.yaml delete mode 100644 cmd/expandybird/test/templates/python_template_with_env_result.yaml delete mode 100644 cmd/expandybird/test/templates/python_template_with_import.py delete mode 100644 cmd/expandybird/test/templates/python_template_with_import.yaml delete mode 100644 cmd/expandybird/test/templates/python_template_with_import_result.yaml delete mode 100644 cmd/expandybird/test/templates/python_template_with_inlinedfile.py delete mode 100644 cmd/expandybird/test/templates/python_template_with_inlinedfile.yaml delete mode 100644 cmd/expandybird/test/templates/python_template_with_inlinedfile_result.yaml delete mode 100644 cmd/expandybird/test/templates/python_with_exception.py delete mode 100644 cmd/expandybird/test/templates/python_with_exception.yaml delete mode 100644 cmd/expandybird/test/templates/simple.yaml delete mode 100644 cmd/expandybird/test/templates/simple_result.yaml delete mode 100644 cmd/expandybird/test/templates/use_helper.jinja delete mode 100644 cmd/expandybird/test/templates/use_helper.jinja.schema delete mode 100644 cmd/expandybird/test/templates/use_helper.yaml delete mode 100644 cmd/expandybird/test/templates/use_helper_result.yaml delete mode 100644 cmd/goexpander/expander/expander.go delete mode 100644 cmd/goexpander/expander/expander_test.go delete mode 100644 cmd/goexpander/main.go delete mode 100644 cmd/helm/Makefile delete mode 100644 cmd/helm/chart.go delete mode 100644 cmd/helm/chart_upload.go delete mode 100644 cmd/helm/deploy.go delete mode 100644 cmd/helm/deployment.go delete mode 100644 cmd/helm/deployment_test.go delete mode 100644 cmd/helm/doctor.go delete mode 100644 cmd/helm/helm.go delete mode 100644 cmd/helm/helm_test.go delete mode 100644 cmd/helm/properties.go delete mode 100644 cmd/helm/repository.go delete mode 100644 cmd/helm/server.go delete mode 100644 cmd/manager/chartrepos.go delete mode 100644 cmd/manager/chartrepos_test.go delete mode 100644 cmd/manager/deployments.go delete mode 100644 cmd/manager/deployments_test.go delete mode 100644 cmd/manager/main.go delete mode 100644 cmd/manager/manager/deployer.go delete mode 100644 cmd/manager/manager/deployer_test.go delete mode 100644 cmd/manager/manager/expander.go delete mode 100644 cmd/manager/manager/expander_test.go delete mode 100644 cmd/manager/manager/manager.go delete mode 100644 cmd/manager/manager/manager_test.go delete mode 100644 cmd/manager/repository/persistent/persistent.go delete mode 100644 cmd/manager/repository/persistent/persistent_test.go delete mode 100644 cmd/manager/repository/repository.go delete mode 100644 cmd/manager/repository/test_common.go delete mode 100644 cmd/manager/repository/transient/transient.go delete mode 100644 cmd/manager/repository/transient/transient_test.go delete mode 100644 cmd/manager/router/context.go delete mode 100644 cmd/manager/router/router.go delete mode 100644 cmd/manager/router/router_test.go delete mode 100644 cmd/manager/testutil.go delete mode 100644 cmd/resourcifier/configurations.go delete mode 100644 cmd/resourcifier/configurator/configurator.go delete mode 100644 cmd/resourcifier/main.go delete mode 100644 docs/design/architecture.dia delete mode 100644 docs/design/architecture.png delete mode 100644 docs/design/chart_format.md delete mode 100644 docs/design/design.md delete mode 100644 docs/design/provenance_proposal.md delete mode 100644 docs/design/user_stories.md delete mode 100644 docs/pushing.md delete mode 100644 docs/pushing_charts.md delete mode 100644 docs/templates/registry.md delete mode 100644 docs/test-architecture.md delete mode 100644 docs/usage_docs/authoring_charts.md delete mode 100644 docs/usage_docs/getting-started-guide.md delete mode 100644 docs/workflow/developer-workflows.md delete mode 100644 docs/workflow/helm-dm-diagrams.src.md delete mode 100644 docs/workflow/helm-official-workflow.png delete mode 100644 docs/workflow/private-chart-no-repo.png delete mode 100644 docs/workflow/private-chart-repo.png delete mode 100644 docs/workflow/public-chart-repo.png delete mode 100644 docs/workflow/team-workflows.md delete mode 100644 examples/charts/nginx/Chart.yaml delete mode 100644 examples/charts/nginx/LICENSE delete mode 100644 examples/charts/nginx/README.md delete mode 100644 examples/charts/nginx/templates/nginx-rc.yaml delete mode 100644 examples/charts/nginx/templates/nginx-svc.yaml delete mode 100644 examples/charts/redis/Chart.yaml delete mode 100644 examples/charts/redis/templates/redis.jinja delete mode 100644 examples/charts/redis/templates/redis.jinja.schema delete mode 100644 examples/charts/replicatedservice/Chart.yaml delete mode 100644 examples/charts/replicatedservice/templates/replicatedservice.py delete mode 100644 examples/charts/replicatedservice/templates/replicatedservice.py.schema delete mode 100644 examples/guestbook/README.md delete mode 100644 examples/guestbook/guestbook.yaml delete mode 100644 examples/package/cassandra.yaml delete mode 100644 examples/wordpress/README.md delete mode 100644 examples/wordpress/architecture.png delete mode 100644 examples/wordpress/images/nginx/Dockerfile delete mode 100644 examples/wordpress/images/nginx/Makefile delete mode 100644 examples/wordpress/images/nginx/default.conf delete mode 100644 examples/wordpress/wordpress-resources.yaml delete mode 100644 examples/wordpress/wordpress.jinja delete mode 100644 examples/wordpress/wordpress.jinja.schema delete mode 100644 examples/wordpress/wordpress.yaml delete mode 100755 expansion/expansion.py delete mode 100644 expansion/expansion_test.py delete mode 100644 expansion/file_expander.py delete mode 100644 expansion/requirements.txt delete mode 100644 expansion/sandbox_loader.py delete mode 100644 expansion/schema_validation.py delete mode 100644 expansion/schema_validation_test.py delete mode 100644 expansion/schema_validation_utils.py delete mode 100755 get-install.sh delete mode 100644 glide.lock delete mode 100644 glide.yaml delete mode 100644 hack/README.md delete mode 100644 hack/Vagrantfile delete mode 100644 pkg/chart/chart.go delete mode 100644 pkg/chart/chart_test.go delete mode 100644 pkg/chart/chartfile.go delete mode 100644 pkg/chart/chartfile_test.go delete mode 100644 pkg/chart/doc.go delete mode 100644 pkg/chart/locator.go delete mode 100644 pkg/chart/locator_test.go delete mode 100644 pkg/chart/save.go delete mode 100644 pkg/chart/save_test.go delete mode 100644 pkg/chart/testdata/README.md delete mode 100644 pkg/chart/testdata/frobnitz-0.0.1.tgz delete mode 100644 pkg/chart/testdata/frobnitz/Chart.yaml delete mode 100644 pkg/chart/testdata/frobnitz/LICENSE delete mode 100644 pkg/chart/testdata/frobnitz/README.md delete mode 100644 pkg/chart/testdata/frobnitz/docs/README.md delete mode 100644 pkg/chart/testdata/frobnitz/hooks/pre-install.py delete mode 100644 pkg/chart/testdata/frobnitz/icon.svg delete mode 100644 pkg/chart/testdata/frobnitz/templates/wordpress-resources.yaml delete mode 100644 pkg/chart/testdata/frobnitz/templates/wordpress.jinja delete mode 100644 pkg/chart/testdata/frobnitz/templates/wordpress.jinja.schema delete mode 100644 pkg/chart/testdata/frobnitz/templates/wordpress.yaml delete mode 100644 pkg/chart/testdata/ill-1.2.3.tgz delete mode 100644 pkg/chart/testdata/ill/Chart.yaml delete mode 100644 pkg/chart/testdata/ill/LICENSE delete mode 100644 pkg/chart/testdata/ill/README.md delete mode 100644 pkg/chart/testdata/ill/docs/README.md delete mode 100644 pkg/chart/testdata/ill/hooks/pre-install.py delete mode 100644 pkg/chart/testdata/ill/templates/wordpress-resources.yaml delete mode 100644 pkg/chart/testdata/ill/templates/wordpress.jinja delete mode 100644 pkg/chart/testdata/ill/templates/wordpress.jinja.schema delete mode 100644 pkg/chart/testdata/ill/templates/wordpress.yaml delete mode 100644 pkg/chart/testdata/nochart.tgz delete mode 100644 pkg/chart/testdata/sprocket/Chart.yaml delete mode 100644 pkg/chart/testdata/sprocket/LICENSE delete mode 100644 pkg/chart/testdata/sprocket/README.md delete mode 100644 pkg/chart/testdata/sprocket/docs/README.md delete mode 100644 pkg/chart/testdata/sprocket/hooks/pre-install.py delete mode 100644 pkg/chart/testdata/sprocket/icon.svg delete mode 100644 pkg/chart/testdata/sprocket/templates/placeholder.txt delete mode 100644 pkg/client/client.go delete mode 100644 pkg/client/client_test.go delete mode 100644 pkg/client/deployments.go delete mode 100644 pkg/client/deployments_test.go delete mode 100644 pkg/client/install.go delete mode 100644 pkg/client/transport.go delete mode 100644 pkg/client/transport_test.go delete mode 100644 pkg/client/uninstall.go delete mode 100644 pkg/common/types.go delete mode 100644 pkg/doc.go delete mode 100644 pkg/expansion/service.go delete mode 100644 pkg/expansion/service_test.go delete mode 100644 pkg/expansion/types.go delete mode 100644 pkg/expansion/validate.go delete mode 100644 pkg/expansion/validate_test.go delete mode 100644 pkg/format/messages.go delete mode 100644 pkg/format/messages_test.go delete mode 100644 pkg/httputil/doc.go delete mode 100644 pkg/httputil/encoder.go delete mode 100644 pkg/httputil/encoder_test.go delete mode 100644 pkg/httputil/httperrors.go delete mode 100644 pkg/httputil/httperrors_test.go delete mode 100644 pkg/kubectl/cluster_info.go delete mode 100644 pkg/kubectl/command.go delete mode 100644 pkg/kubectl/create.go delete mode 100644 pkg/kubectl/create_test.go delete mode 100644 pkg/kubectl/delete.go delete mode 100644 pkg/kubectl/get.go delete mode 100644 pkg/kubectl/get_test.go delete mode 100644 pkg/kubectl/kubectl.go delete mode 100644 pkg/kubectl/kubectl_test.go delete mode 100644 pkg/log/log.go delete mode 100644 pkg/log/log_test.go delete mode 100644 pkg/pkg_test.go delete mode 100644 pkg/repo/filebased_credential_provider.go delete mode 100644 pkg/repo/filebased_credential_provider_test.go delete mode 100644 pkg/repo/gcs_repo.go delete mode 100644 pkg/repo/gcs_repo_test.go delete mode 100644 pkg/repo/inmem_credential_provider.go delete mode 100644 pkg/repo/inmem_credential_provider_test.go delete mode 100644 pkg/repo/inmem_repo_service.go delete mode 100644 pkg/repo/inmem_repo_service_test.go delete mode 100644 pkg/repo/repo.go delete mode 100644 pkg/repo/repo_test.go delete mode 100644 pkg/repo/repoprovider.go delete mode 100644 pkg/repo/repoprovider_test.go delete mode 100644 pkg/repo/secrets_credential_provider.go delete mode 100644 pkg/repo/testdata/test_credentials_file.yaml delete mode 100644 pkg/repo/types.go delete mode 100644 pkg/util/httpclient.go delete mode 100644 pkg/util/httpclient_test.go delete mode 100644 pkg/util/httputil.go delete mode 100644 pkg/util/kubernetes.go delete mode 100644 pkg/util/kubernetes_kubectl.go delete mode 100644 pkg/util/kubernetesutil.go delete mode 100644 pkg/util/kubernetesutil_test.go delete mode 100644 pkg/util/templateutil.go delete mode 100644 pkg/util/templateutil_test.go delete mode 100644 pkg/version/version.go delete mode 100644 rootfs/Makefile delete mode 100644 rootfs/README.md delete mode 100644 rootfs/expandybird/.dockerignore delete mode 100644 rootfs/expandybird/Dockerfile delete mode 100644 rootfs/expandybird/Makefile delete mode 100644 rootfs/include.mk delete mode 100644 rootfs/manager/.dockerignore delete mode 100644 rootfs/manager/Dockerfile delete mode 100644 rootfs/manager/Makefile delete mode 100644 rootfs/resourcifier/.dockerignore delete mode 100644 rootfs/resourcifier/Dockerfile delete mode 100644 rootfs/resourcifier/Makefile delete mode 100755 scripts/build-go.sh delete mode 100644 scripts/cluster/kube-system.yaml delete mode 100644 scripts/cluster/skydns.yaml delete mode 100644 scripts/common.sh delete mode 100755 scripts/coverage.sh delete mode 100644 scripts/docker.sh delete mode 100755 scripts/kube-down.sh delete mode 100755 scripts/kube-up.sh delete mode 100755 scripts/kubectl.sh delete mode 100755 scripts/start-local.sh delete mode 100755 scripts/stop-local.sh delete mode 100755 scripts/validate-go.sh diff --git a/.gitignore b/.gitignore deleted file mode 100644 index e2227a57b..000000000 --- a/.gitignore +++ /dev/null @@ -1,19 +0,0 @@ -*~ -.*.swp -*.pyc -.project -nohup.out -/.coverage -/bin -/vendor/* -/rootfs/manager/bin/manager -/rootfs/manager/bin/kubectl -/rootfs/manager/bin/v1.* -/rootfs/resourcifier/bin/resourcifier -/rootfs/resourcifier/bin/kubectl -/rootfs/resourcifier/bin/v1.* -/rootfs/expandybird/bin/expandybird -/rootfs/expandybird/opt/expansion -.DS_Store -/log/ -/scripts/env.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index d1a245afe..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,81 +0,0 @@ -# Contributing Guidelines - -The Kubernetes Helm project accepts contributions via GitHub pull requests. This document outlines the process to help get your contribution accepted. - -## Contributor License Agreements - -We'd love to accept your patches! Before we can take them, we have to jump a couple of legal hurdles. - -Please fill out either the individual or corporate Contributor License Agreement (CLA). - - * If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA](http://code.google.com/legal/individual-cla-v1.0.html). - * If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA](http://code.google.com/legal/corporate-cla-v1.0.html). - -Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests. - -***NOTE***: Only original source code from you and other people that have signed the CLA can be accepted into the main repository. - -## Development Lifecycle - -The project uses a combination of milestones and priority labels on GitHub issues to help development flow smoothly. While exceptions may be required on occasion, the team observes the following guidelines: - -* At appropriate intervals, the Helm team creates a milestone and assigns - issues to it. This represents the team's priorities and intent. -* PRs/Issues related to the current milestone are prioritized over other PRs. -* PRs/Issues that fix a broken master build (or meet other P0 criteria) are - prioritized over other PRs. - -## How to Contribute A Patch - -### Overview - -1. Submit an issue describing your proposed change to the repo in question. -1. A collaborator will respond to your issue. -1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above). -1. Fork the desired repo, develop and test your code changes. -1. Submit a pull request. - -### Feature Proposals - -Before adding a feature or making a major change to the code, open an -issue marked as a _proposal_ and explain your idea. For complex changes, -you may be asked to produce a design document. - -### Single Issue - -When fixing or implementing a GitHub issue, resist the temptation to refactor nearby code or to fix that potential bug you noticed. Instead, open a new pull request just for that change. - -Keeping concerns separated allows pull requests to be tested, reviewed, and merged more quickly. - -Squash and rebase the commit or commits in your pull request into logical units of work with `git`. Include tests and documentation changes in the same commit, so that a revert would remove all traces of the feature or fix. - -If a PR completely resolves an existing issue, this should be noted. In the PR description–not in the commit itself–include a line such as "Closes #1234". The issue referenced will then be closed when your PR is merged. If it otherwise relates to an existing issue, that should be noted in the comment as well. - -### Include Tests & Documentation - -If you change or add functionality, your changes should include the necessary tests to prove that it works. While working on local code changes, always run the tests. Any change that could affect a user's experience also needs a change or addition to the relevant documentation. - -Pull requests that do not include sufficient tests or documentation will be rejected. - -***NOTE***: Please note that we are currently using Go version 1.6, and tests will fail if you run them on any other version of Go. - -### Coding Standards - -Go code should always be run through `gofmt` on the default settings. Lines of code may be up to 99 characters long. Documentation strings and tests are required for all public methods. Use of third-party go packages should be minimal, but when doing so, vendor code using [Glide](http://glide.sh/). - -Python code should conform to [PEP8](https://www.python.org/dev/peps/pep-0008/). - -### Merge Approval - -Helm collaborators may add "LGTM" (Looks Good To Me) or an equivalent comment to indicate that a PR is acceptable. Any change requires at least one LGTM. No pull requests can be merged until at least one Helm collaborator signs off with an LGTM. - -If the PR is from a Helm collaborator, then he or she should be the one to merge and close it. This keeps the commit stream clean and gives the collaborator the benefit of revisiting the PR before deciding whether or not to merge the changes. - -## Support Channels - -Whether you are a user or contributor, official support channels include: - -- GitHub issues: https://github.com/kubenetes/helm/issues/new -- Slack: #Helm room in the [Kubernetes Slack](http://slack.kubernetes.io/) - -Before opening a new issue or submitting a new pull request, it's helpful to search the project - it's likely that another user has already reported the issue you're facing, or it's a known issue that we're already aware of. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d64569567..000000000 --- a/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/MAINTAINERS.md b/MAINTAINERS.md deleted file mode 100644 index 235618051..000000000 --- a/MAINTAINERS.md +++ /dev/null @@ -1,61 +0,0 @@ -# Helm Maintainers - -This document explains the leadership structure of the Kubernetes Helm project, and list the current project maintainers. - -## What is a Maintainer? - -(Unabashedly stolen from the [Docker](https://github.com/docker/docker/blob/master/MAINTAINERS) project) - -There are different types of maintainers, with different responsibilities, but -all maintainers have 3 things in common: - -1. They share responsibility in the project's success. -2. They have made a long-term, recurring time investment to improve the project. -3. They spend that time doing whatever needs to be done, not necessarily what -is the most interesting or fun. - -## Types of Maintainers - -The Helm project includes two types of official maintainers: maintainers and core maintainers. - -### Helm Maintainers - -Helm maintainers are developers who have commit access to the Helm repository. -The duties of a maintainer include: - -* Classify and respond to GitHub issues and review pull requests -* Perform code reviews -* Shape the Helm roadmap and lead efforts to accomplish roadmap milestones -* Participate actively in feature development and bug fixing -* Answer questions and help users -* Participate in planning meetings - -### Helm Core Maintainers - -In addition to the duties of a Maintainer, Helm Core Maintainers also: - -* Coordinate planning meetings -* Triage GitHub issues for milestone planning -* Escalate emergency issues (broken builds, security flaws) outside of - the normal planning process - -The current core maintainers of Helm: - -* Jack Greenfield - [@jackgr](https://github.com/jackgr) -* Matt Butcher - [@technosophos](https://github.com/technosophos) - -## Project Planning - -The Helm team holds regular planning meetings to set the project direction, milestones, and relative prioritization of issues. Planning meetings are coordinated via the #Helm room in the [Kubernetes Slack](http://slack.kubernetes.io/). - -In order to solicit feedback from the community, planning meetings are run in public whenever possible. - -## Becoming a Maintainer - -Generally, potential maintainers are selected by the existing core maintainers based in part on the following criteria: - -* Sustained contributions to the project over a period of time (usually months) -* A willingness to help users on GitHub and in the [#Helm Slack room](http://slack.kubernetes.io/) -* A friendly attitude - -The Helm core maintainers must unanimously agree before inviting a community member to join as a maintainer, although in many cases the candidate has already been acting in the capacity of a maintainer for some time, and has been consulted on issues, pull requests, etc. \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index aba820c6b..000000000 --- a/Makefile +++ /dev/null @@ -1,118 +0,0 @@ -# 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. - -GO_DIRS ?= $(shell glide nv -x ) -GO_PKGS ?= $(shell glide nv) - -ROOTFS := rootfs -CLIENT := cmd/helm - -.PHONY: info -info: - $(MAKE) -C $(ROOTFS) $@ - -.PHONY: gocheck -ifndef GOPATH - $(error No GOPATH set) -endif - -.PHONY: build -build: gocheck - @scripts/build-go.sh - -.PHONY: build-static -build-static: gocheck - @BUILD_TYPE=STATIC scripts/build-go.sh - -.PHONY: build-cross -build-cross: gocheck - @BUILD_TYPE=CROSS scripts/build-go.sh - -.PHONY: all -all: build - -.PHONY: clean -clean: - $(MAKE) -C $(ROOTFS) $@ - go clean -v $(GO_PKGS) - rm -rf bin - -.PHONY: test -test: build test-style test-unit test-flake8 - -.PHONY: quicktest -quicktest: test-style - go test $(GO_PKGS) - -.PHONY: push -push: push-server push-client - -.PHONY: push-server -push-server: build-static - $(MAKE) -C $(ROOTFS) push - -.PHONY: push-client -push-client: gocheck - @BUILD_TYPE=CROSS scripts/build-go.sh $(CLIENT) - $(MAKE) -C $(CLIENT) push - -.PHONY: container -container: build-static - $(MAKE) -C $(ROOTFS) $@ - -.PHONY: test-unit -test-unit: - @echo Running tests... - go test -race -v $(GO_PKGS) - -.PHONY: test-flake8 -test-flake8: - @echo Running flake8... - flake8 expansion - @echo ---------------- - -.PHONY: test-style -test-style: - @scripts/validate-go.sh - -HAS_GLIDE := $(shell command -v glide;) -HAS_GOLINT := $(shell command -v golint;) -HAS_GOVET := $(shell command -v go tool vet;) -HAS_GOX := $(shell command -v gox;) -HAS_PIP := $(shell command -v pip;) -HAS_FLAKE8 := $(shell command -v flake8;) - -.PHONY: bootstrap -bootstrap: - @echo Installing deps -ifndef HAS_PIP - $(error Please install the latest version of Python pip) -endif -ifndef HAS_GLIDE - go get -u github.com/Masterminds/glide -endif -ifndef HAS_GOLINT - go get -u github.com/golang/lint/golint -endif -ifndef HAS_GOVET - go get -u golang.org/x/tools/cmd/vet -endif -ifndef HAS_GOX - go get -u github.com/mitchellh/gox -endif -ifndef HAS_FLAKE8 - pip install flake8 -endif - glide install - pip install --user -r expansion/requirements.txt diff --git a/README.md b/README.md deleted file mode 100644 index 31cd2761d..000000000 --- a/README.md +++ /dev/null @@ -1,162 +0,0 @@ -# Helm - -[![Circle CI](https://circleci.com/gh/kubernetes/helm.svg?style=svg)](https://circleci.com/gh/kubernetes/helm) [![Go Report Card](http://goreportcard.com/badge/kubernetes/helm)](http://goreportcard.com/report/kubernetes/helm) - -Helm makes it easy to create, describe, update and -delete Kubernetes resources using declarative configuration. A configuration is -just a `YAML` file that configures Kubernetes resources or supplies parameters -to templates. - -Helm Manager runs server side, in your Kubernetes cluster, so it can tell you what templates -you've instantiated there, what resources they created, and even how the resources -are organized. So, for example, you can ask questions like: - -* What Redis instances are running in this cluster? -* What Redis master and slave services are part of this Redis instance? -* What pods are part of this Redis slave? - -The official Helm repository of charts is available in the -[kubernetes/charts](https://github.com/kubernetes/charts) repository. - -Please hang out with us in [the Slack chat room](https://kubernetes.slack.com/messages/helm/). - -## Installing Helm - -Note: if you're exploring or using the project, you'll probably want to pull -[the latest release](https://github.com/kubernetes/helm/releases/latest), -since there may be undiscovered or unresolved issues at HEAD. - -From a Linux or Mac OS X client: - -``` -$ git clone https://github.com/kubernetes/helm.git -$ cd helm -$ make build -$ bin/helm server install -``` - -That's it. You can now use `kubectl` to see Helm running in your cluster like this: - -``` -$ kubectl get pod,rc,service --namespace=helm -NAME READY STATUS RESTARTS AGE -expandybird-rc-e0whp 1/1 Running 0 35m -expandybird-rc-zdp8w 1/1 Running 0 35m -manager-rc-bl4i4 1/1 Running 0 35m -resourcifier-rc-21clg 1/1 Running 0 35m -resourcifier-rc-i2zhi 1/1 Running 0 35m -NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE -expandybird-service 10.0.0.248 8081/TCP 35m -manager-service 10.0.0.49 8080/TCP 35m -resourcifier-service 10.0.0.184 8082/TCP 35m -NAME DESIRED CURRENT AGE -expandybird-rc 2 2 35m -manager-rc 1 1 35m -resourcifier-rc 2 2 35m -``` - -If you see expandybird, manager and resourcifier services, as well as expandybird, manager and resourcifier replication controllers with pods that are READY, then Helm is up and running! - -## Using Helm - -Run a Kubernetes proxy to allow the Helm client to connect to the remote cluster: - -``` -kubectl proxy --port=8001 & -``` - -Configure the HELM_HOST environment variable to let the local Helm client talk to the Helm manager service running in your remote Kubernetes cluster using the proxy. - -``` -export HELM_HOST=http://localhost:8001/api/v1/proxy/namespaces/helm/services/manager-service:manager -``` - -## Installing Charts - -To quickly deploy a chart, you can use the Helm command line tool. - -Currently here is the step by step guide. - -First add a respository of Charts used for testing: - -``` -$ bin/helm repo add kubernetes-charts-testing gs://kubernetes-charts-testing -``` - -Then deploy a Chart from this repository. For example to start a Redis cluster: - -``` -$ bin/helm deploy --name test --properties "workers=2" gs://kubernetes-charts-testing/redis-2.0.0.tgz -``` -The command above will create a helm "deployment" called `test` using the `redis-2.0.0.tgz` chart stored in the google storage bucket `kubernetes-charts-testing`. - -`$ bin/helm deployment describe test` will allow you to see the status of the resources you just created using the redis-2.0.0.tgz chart. You can also use kubectl to see the the same resources. It'll look like this: - -``` -$ kubectl get pods,svc,rc -NAME READY STATUS RESTARTS AGE -barfoo-barfoo 5/5 Running 0 45m -redis-master-rc-8wrqt 1/1 Running 0 41m -redis-slave-rc-6ptx6 1/1 Running 0 41m -redis-slave-rc-yc12q 1/1 Running 0 41m -NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE -kubernetes 10.0.0.1 443/TCP 45m -redis-master 10.0.0.67 6379/TCP 41m -redis-slave 10.0.0.168 6379/TCP 41m -NAME DESIRED CURRENT AGE -redis-master-rc 1 1 41m -redis-slave-rc 2 2 41m -``` - -To connect to your Redis master with a local `redis-cli` just use `kubectl port-forward` in a similar manner to: - -``` -$ kubectl port-forward redis-master-rc-8wrqt 6379:639 & -$ redis-cli -127.0.0.1:6379> info -... -role:master -connected_slaves:2 -slave0:ip=172.17.0.10,port=6379,state=online,offset=925,lag=0 -slave1:ip=172.17.0.11,port=6379,state=online,offset=925,lag=1 -``` - -Once you are done, you can delete your deployment with - -``` -$ bin/helm deployment list -test -$ bin/helm deployment rm test -```` - -## Uninstalling Helm from Kubernetes - -You can uninstall Helm entirely using the following command: - -``` -$ bin/helm server uninstall -``` - -This command will remove everything in the Helm namespace being used. - -## Design of Helm - -There is a more detailed [design document](docs/design/design.md) available. - -## Status of the Project - -This project is still under active development, so you might run into issues. If -you do, please don't be shy about letting us know, or better yet, contribute a -fix or feature. - -## Contributing -Your contributions are welcome. - -We use the same [workflow](https://github.com/kubernetes/kubernetes/blob/master/docs/devel/development.md#git-setup), -[License](LICENSE) and [Contributor License Agreement](CONTRIBUTING.md) as the main Kubernetes repository. - -## Relationship to Google Cloud Platform's Deployment Manager and Deis's Helm -Kubernetes Helm represent a merge of Google's Deployment Manager (DM) and the original Helm from Deis. -Kubernetes Helm uses many of the same concepts and languages as -[Google Cloud Deployment Manager](https://cloud.google.com/deployment-manager/overview), -but creates resources in Kubernetes clusters, not in Google Cloud Platform projects. It also brings several concepts from the original Helm such as Charts. diff --git a/circle.yml b/circle.yml deleted file mode 100644 index c66114bea..000000000 --- a/circle.yml +++ /dev/null @@ -1,33 +0,0 @@ -machine: - environment: - GLIDE_VERSION: "0.10.1" - GO15VENDOREXPERIMENT: 1 - GOPATH: /usr/local/go_workspace - HOME: /home/ubuntu - IMPORT_PATH: "github.com/kubernetes/helm" - PATH: $HOME/go/bin:$PATH - GOROOT: $HOME/go - -dependencies: - override: - - mkdir -p $HOME/go - - wget "https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz" - - tar -C $HOME -xzf go1.6.linux-amd64.tar.gz - - go version - - go env - - sudo chown -R $(whoami):staff /usr/local - - cd $GOPATH - - mkdir -p $GOPATH/src/$IMPORT_PATH - - cd $HOME/helm - - rsync -az --delete ./ "$GOPATH/src/$IMPORT_PATH/" - - 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" - - cd $GOPATH/src/$IMPORT_PATH - - sudo pip install -r expansion/requirements.txt - - sudo pip install flake8 - -test: - override: - - cd $GOPATH/src/$IMPORT_PATH && make bootstrap test diff --git a/cmd/doc.go b/cmd/doc.go deleted file mode 100644 index 8a962fc29..000000000 --- a/cmd/doc.go +++ /dev/null @@ -1,18 +0,0 @@ -/* -Copyright 2016 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. -*/ - -// Package cmd contains the executables for Helm. -package cmd diff --git a/cmd/expandybird/expander/expander.go b/cmd/expandybird/expander/expander.go deleted file mode 100644 index bb987163b..000000000 --- a/cmd/expandybird/expander/expander.go +++ /dev/null @@ -1,131 +0,0 @@ -/* -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. -*/ - -package expander - -import ( - "bytes" - "encoding/json" - "fmt" - "github.com/ghodss/yaml" - "log" - "os/exec" - - "github.com/kubernetes/helm/pkg/expansion" -) - -type expander struct { - ExpansionBinary string -} - -// NewExpander returns an ExpandyBird expander. -func NewExpander(binary string) expansion.Expander { - return &expander{binary} -} - -type expandyBirdConfigOutput struct { - Resources []interface{} `yaml:"resources,omitempty"` -} - -type expandyBirdOutput struct { - Config *expandyBirdConfigOutput `yaml:"config,omitempty"` - Layout interface{} `yaml:"layout,omitempty"` -} - -// ExpandChart passes the given configuration to the expander and returns the -// expanded configuration as a string on success. -func (e *expander) ExpandChart(request *expansion.ServiceRequest) (*expansion.ServiceResponse, error) { - - if err := expansion.ValidateRequest(request); err != nil { - return nil, err - } - - request, err := expansion.ValidateProperties(request) - if err != nil { - return nil, err - } - - chartInv := request.ChartInvocation - chartFile := request.Chart.Chartfile - chartMembers := request.Chart.Members - - if e.ExpansionBinary == "" { - message := fmt.Sprintf("expansion binary cannot be empty") - return nil, fmt.Errorf("%s: %s", chartInv.Name, message) - } - - entrypointIndex := -1 - for i, f := range chartMembers { - if f.Path == chartFile.Expander.Entrypoint { - entrypointIndex = i - } - } - if entrypointIndex == -1 { - message := fmt.Sprintf("The entrypoint in the chart.yaml cannot be found: %s", chartFile.Expander.Entrypoint) - return nil, fmt.Errorf("%s: %s", chartInv.Name, message) - } - - // Those are automatically increasing buffers, so writing arbitrary large - // data here won't block the child process. - var stdout bytes.Buffer - var stderr bytes.Buffer - - // Now we convert the new chart representation into the form that classic ExpandyBird takes. - - chartInvJSON, err := json.Marshal(chartInv) - if err != nil { - return nil, fmt.Errorf("error marshalling chart invocation %s: %s", chartInv.Name, err) - } - content := "{ \"resources\": [" + string(chartInvJSON) + "] }" - - cmd := &exec.Cmd{ - Path: e.ExpansionBinary, - // Note, that binary name still has to be passed argv[0]. - Args: []string{e.ExpansionBinary, content}, - Stdout: &stdout, - Stderr: &stderr, - } - - for i, f := range chartMembers { - name := f.Path - path := f.Path - if i == entrypointIndex { - // This is how expandyBird identifies the entrypoint. - name = chartInv.Type - } - cmd.Args = append(cmd.Args, name, path, string(f.Content)) - } - - if err := cmd.Start(); err != nil { - log.Printf("error starting expansion process: %s", err) - return nil, err - } - - cmd.Wait() - - log.Printf("Expansion process: pid: %d SysTime: %v UserTime: %v", cmd.ProcessState.Pid(), - cmd.ProcessState.SystemTime(), cmd.ProcessState.UserTime()) - if stderr.String() != "" { - return nil, fmt.Errorf("%s: %s", chartInv.Name, stderr.String()) - } - - output := &expandyBirdOutput{} - if err := yaml.Unmarshal(stdout.Bytes(), output); err != nil { - return nil, fmt.Errorf("cannot unmarshal expansion result (%s):\n%s", err, output) - } - - return &expansion.ServiceResponse{Resources: output.Config.Resources}, nil -} diff --git a/cmd/expandybird/expander/expander_test.go b/cmd/expandybird/expander/expander_test.go deleted file mode 100644 index df13a78c9..000000000 --- a/cmd/expandybird/expander/expander_test.go +++ /dev/null @@ -1,832 +0,0 @@ -/* -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. -*/ - -package expander - -import ( - "fmt" - "path/filepath" - "reflect" - "runtime" - "strings" - "testing" - - "github.com/kubernetes/helm/pkg/chart" - "github.com/kubernetes/helm/pkg/common" - "github.com/kubernetes/helm/pkg/expansion" -) - -var expanderName = "../../../expansion/expansion.py" - -// content provides an easy way to provide file content verbatim in tests. -func content(lines []string) []byte { - return []byte(strings.Join(lines, "\n") + "\n") -} - -func getChartNameFromPC(pc uintptr) string { - rf := runtime.FuncForPC(pc) - fn := rf.Name() - bn := filepath.Base(fn) - split := strings.Split(bn, ".") - if len(split) > 1 { - split = split[1:] - } - - cn := fmt.Sprintf("%s-1.2.3.tgz", split[0]) - return cn -} - -func getChartURLFromPC(pc uintptr) string { - cn := getChartNameFromPC(pc) - cu := fmt.Sprintf("gs://kubernetes-charts-testing/%s", cn) - return cu -} - -func getTestChartName(t *testing.T) string { - pc, _, _, _ := runtime.Caller(1) - cu := getChartURLFromPC(pc) - cl, err := chart.Parse(cu) - if err != nil { - t.Fatalf("cannot parse chart reference %s: %s", cu, err) - } - - return cl.Name -} - -func getTestChartURL() string { - pc, _, _, _ := runtime.Caller(1) - cu := getChartURLFromPC(pc) - return cu -} - -func testExpansion(t *testing.T, req *expansion.ServiceRequest, - expResponse *expansion.ServiceResponse, expError string) { - backend := NewExpander(expanderName) - response, err := backend.ExpandChart(req) - if err != nil { - message := err.Error() - if expResponse != nil || !strings.Contains(message, expError) { - t.Fatalf("unexpected error: %v\n", err) - } - } else { - if expResponse == nil { - t.Fatalf("expected error did not occur: %s\n", expError) - } - if !reflect.DeepEqual(response, expResponse) { - message := fmt.Sprintf( - "want:\n%s\nhave:\n%s\n", expResponse, response) - t.Fatalf("output mismatch:\n%s\n", message) - } - } -} - -var pyExpander = &chart.Expander{ - Name: "ExpandyBird", - Entrypoint: "templates/main.py", -} - -var jinjaExpander = &chart.Expander{ - Name: "ExpandyBird", - Entrypoint: "templates/main.jinja", -} - -func TestEmptyJinja(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: jinjaExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.jinja", - Content: content([]string{"resources:"}), - }, - }, - }, - }, - &expansion.ServiceResponse{ - Resources: []interface{}{}, - }, - "", // Error - ) -} - -func TestEmptyPython(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: pyExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.py", - Content: content([]string{ - "def GenerateConfig(ctx):", - " return 'resources:'", - }), - }, - }, - }, - }, - &expansion.ServiceResponse{ - Resources: []interface{}{}, - }, - "", // Error - ) -} - -func TestSimpleJinja(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: jinjaExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.jinja", - Content: content([]string{ - "resources:", - "- name: foo", - " type: bar", - }), - }, - }, - }, - }, - &expansion.ServiceResponse{ - Resources: []interface{}{ - map[string]interface{}{ - "name": "foo", - "type": "bar", - }, - }, - }, - "", // Error - ) -} - -func TestSimplePython(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: pyExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.py", - Content: content([]string{ - "def GenerateConfig(ctx):", - " return '''resources:", - "- name: foo", - " type: bar", - "'''", - }), - }, - }, - }, - }, - &expansion.ServiceResponse{ - Resources: []interface{}{ - map[string]interface{}{ - "name": "foo", - "type": "bar", - }, - }, - }, - "", // Error - ) -} - -func TestPropertiesJinja(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - Properties: map[string]interface{}{ - "prop1": 3.0, - "prop2": "foo", - }, - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: jinjaExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.jinja", - Content: content([]string{ - "resources:", - "- name: foo", - " type: {{ properties.prop2 }}", - " properties:", - " something: {{ properties.prop1 }}", - }), - }, - }, - }, - }, - &expansion.ServiceResponse{ - Resources: []interface{}{ - map[string]interface{}{ - "name": "foo", - "properties": map[string]interface{}{ - "something": 3.0, - }, - "type": "foo", - }, - }, - }, - "", // Error - ) -} - -func TestPropertiesPython(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - Properties: map[string]interface{}{ - "prop1": 3.0, - "prop2": "foo", - }, - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: pyExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.py", - Content: content([]string{ - "def GenerateConfig(ctx):", - " return '''resources:", - "- name: foo", - " type: %(prop2)s", - " properties:", - " something: %(prop1)s", - "''' % ctx.properties", - }), - }, - }, - }, - }, - &expansion.ServiceResponse{ - Resources: []interface{}{ - map[string]interface{}{ - "name": "foo", - "properties": map[string]interface{}{ - "something": 3.0, - }, - "type": "foo", - }, - }, - }, - "", // Error - ) -} - -func TestMultiFileJinja(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: jinjaExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.jinja", - Content: content([]string{"{% include 'templates/secondary.jinja' %}"}), - }, - { - Path: "templates/secondary.jinja", - Content: content([]string{ - "resources:", - "- name: foo", - " type: bar", - }), - }, - }, - }, - }, - &expansion.ServiceResponse{ - Resources: []interface{}{ - map[string]interface{}{ - "name": "foo", - "type": "bar", - }, - }, - }, - "", // Error - ) -} - -var schemaContent = content([]string{ - `{`, - ` "required": ["prop1", "prop2"],`, - ` "additionalProperties": false,`, - ` "properties": {`, - ` "prop1": {`, - ` "description": "Nice description.",`, - ` "type": "integer"`, - ` },`, - ` "prop2": {`, - ` "description": "Nice description.",`, - ` "type": "string"`, - ` }`, - ` }`, - `}`, -}) - -func TestSchema(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - Properties: map[string]interface{}{ - "prop1": 3.0, - "prop2": "foo", - }, - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: jinjaExpander, - Schema: "Schema.yaml", - }, - Members: []*chart.Member{ - { - Path: "Schema.yaml", - Content: schemaContent, - }, - { - Path: "templates/main.jinja", - Content: content([]string{ - "resources:", - "- name: foo", - " type: {{ properties.prop2 }}", - " properties:", - " something: {{ properties.prop1 }}", - }), - }, - }, - }, - }, - &expansion.ServiceResponse{ - Resources: []interface{}{ - map[string]interface{}{ - "name": "foo", - "properties": map[string]interface{}{ - "something": 3.0, - }, - "type": "foo", - }, - }, - }, - "", // Error - ) -} - -func TestSchemaFail(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - Properties: map[string]interface{}{ - "prop1": 3.0, - "prop3": "foo", - }, - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: jinjaExpander, - Schema: "Schema.yaml", - }, - Members: []*chart.Member{ - { - Path: "Schema.yaml", - Content: schemaContent, - }, - { - Path: "templates/main.jinja", - Content: content([]string{ - "resources:", - "- name: foo", - " type: {{ properties.prop2 }}", - " properties:", - " something: {{ properties.prop1 }}", - }), - }, - }, - }, - }, - nil, // Response. - `"prop2" property is missing and required`, - ) -} - -func TestMultiFileJinjaMissing(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: jinjaExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.jinja", - Content: content([]string{"{% include 'templates/secondary.jinja' %}"}), - }, - }, - }, - }, - nil, // Response - "TemplateNotFound: templates/secondary.jinja", - ) -} - -func TestMultiFilePython(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: pyExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.py", - Content: content([]string{ - "from templates import second", - "import templates.third", - "def GenerateConfig(ctx):", - " t2 = second.Gen()", - " t3 = templates.third.Gen()", - " return t2", - }), - }, - { - Path: "templates/second.py", - Content: content([]string{ - "def Gen():", - " return '''resources:", - "- name: foo", - " type: bar", - "'''", - }), - }, - { - Path: "templates/third.py", - Content: content([]string{ - "def Gen():", - " return '''resources:", - "- name: foo", - " type: bar", - "'''", - }), - }, - }, - }, - }, - &expansion.ServiceResponse{ - Resources: []interface{}{ - map[string]interface{}{ - "name": "foo", - "type": "bar", - }, - }, - }, - "", // Error - ) -} - -func TestMultiFilePythonMissing(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: pyExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.py", - Content: content([]string{ - "from templates import second", - }), - }, - }, - }, - }, - nil, // Response - "cannot import name second", // Error - ) -} - -func TestWrongChartName(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: "WrongName", - Expander: jinjaExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.jinja", - Content: content([]string{"resources:"}), - }, - }, - }, - }, - nil, // Response - "does not match provided chart", - ) -} - -func TestEntrypointNotFound(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: jinjaExpander, - }, - Members: []*chart.Member{}, - }, - }, - nil, // Response - "The entrypoint in the chart.yaml cannot be found", - ) -} - -func TestMalformedResource(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: jinjaExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.jinja", - Content: content([]string{ - "resources:", - "fail", - }), - }, - }, - }, - }, - nil, // Response - "could not found expected ':'", // [sic] - ) -} - -func TestResourceNoName(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: jinjaExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.jinja", - Content: content([]string{ - "resources:", - "- type: bar", - }), - }, - }, - }, - }, - nil, // Response. - "Resource does not have a name", - ) -} - -func TestResourceNoType(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: getTestChartURL(), - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: getTestChartName(t), - Expander: jinjaExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.jinja", - Content: content([]string{ - "resources:", - "- name: foo", - }), - }, - }, - }, - }, - nil, // Response. - "Resource does not have type defined", - ) -} - -func TestReplicatedService(t *testing.T) { - replicatedService, err := chart.LoadDir("../../../examples/charts/replicatedservice") - if err != nil { - t.Fatal(err) - } - replicatedServiceContent, err := replicatedService.LoadContent() - if err != nil { - t.Fatal(err) - } - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: "gs://kubernetes-charts-testing/replicatedservice-1.2.3.tgz", - Properties: map[string]interface{}{ - "image": "myimage", - "container_port": 1234, - "replicas": 3, - }, - }, - Chart: replicatedServiceContent, - }, - &expansion.ServiceResponse{ - Resources: []interface{}{ - map[string]interface{}{ - "name": "test_invocation-rc", - "properties": map[string]interface{}{ - "apiVersion": "v1", - "kind": "ReplicationController", - "metadata": map[string]interface{}{ - "labels": map[string]interface{}{ - "name": "test_invocation-rc", - }, - "name": "test_invocation-rc", - "namespace": "default", - }, - "spec": map[string]interface{}{ - "replicas": 3.0, - "selector": map[string]interface{}{ - "name": "test_invocation", - }, - "template": map[string]interface{}{ - "metadata": map[string]interface{}{ - "labels": map[string]interface{}{ - "name": "test_invocation", - }, - }, - "spec": map[string]interface{}{ - "containers": []interface{}{ - map[string]interface{}{ - "env": []interface{}{}, - "image": "myimage", - "name": "test_invocation", - "ports": []interface{}{ - map[string]interface{}{ - "containerPort": 1234.0, - "name": "test_invocation", - }, - }, - }, - }, - }, - }, - }, - }, - "type": "ReplicationController", - }, - map[string]interface{}{ - "name": "test_invocation-service", - "properties": map[string]interface{}{ - "apiVersion": "v1", - "kind": "Service", - "metadata": map[string]interface{}{ - "labels": map[string]interface{}{ - "name": "test_invocation-service", - }, - "name": "test_invocation-service", - "namespace": "default", - }, - "spec": map[string]interface{}{ - "ports": []interface{}{ - map[string]interface{}{ - "name": "test_invocation", - "port": 1234.0, - "targetPort": 1234.0, - }, - }, - "selector": map[string]interface{}{ - "name": "test_invocation", - }, - }, - }, - "type": "Service", - }, - }, - }, - "", // Error. - ) -} diff --git a/cmd/expandybird/main.go b/cmd/expandybird/main.go deleted file mode 100644 index 7160eb459..000000000 --- a/cmd/expandybird/main.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -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. -*/ - -package main - -import ( - "github.com/kubernetes/helm/cmd/expandybird/expander" - "github.com/kubernetes/helm/pkg/expansion" - "github.com/kubernetes/helm/pkg/version" - - "flag" - "log" -) - -// interface that we are going to listen on -var address = flag.String("address", "", "Interface to listen on") - -// port that we are going to listen on -var port = flag.Int("port", 8080, "Port to listen on") - -// path to expansion binary -var expansionBinary = flag.String("expansion_binary", "../../../expansion/expansion.py", - "The path to the expansion binary that will be used to expand the template.") - -func main() { - flag.Parse() - backend := expander.NewExpander(*expansionBinary) - service := expansion.NewService(*address, *port, backend) - log.Printf("Version: %s", version.Version) - log.Printf("Listening on http://%s:%d/expand", *address, *port) - log.Fatal(service.ListenAndServe()) -} diff --git a/cmd/expandybird/test/ExpectedOutput.yaml b/cmd/expandybird/test/ExpectedOutput.yaml deleted file mode 100644 index 02bf868b8..000000000 --- a/cmd/expandybird/test/ExpectedOutput.yaml +++ /dev/null @@ -1,77 +0,0 @@ -###################################################################### -# 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. -###################################################################### - -config: - resources: - - name: expandybird-service - properties: - apiVersion: v1 - kind: Service - metadata: - labels: - app: expandybird - name: expandybird-service - namespace: default - spec: - ports: - - name: expandybird - port: 8080 - targetPort: 8080 - selector: - app: expandybird - type: LoadBalancer - type: Service - - name: expandybird-rc - properties: - apiVersion: v1 - kind: ReplicationController - metadata: - labels: - app: expandybird - name: expandybird-rc - namespace: default - spec: - replicas: 3 - selector: - app: expandybird - template: - metadata: - labels: - app: expandybird - spec: - containers: - - env: [] - image: gcr.io/kubernetes-helm/expandybird - name: expandybird - ports: - - containerPort: 8080 - name: expandybird - type: ReplicationController -layout: - resources: - - name: expandybird - properties: - container_port: 8080 - external_service: true - image: gcr.io/kubernetes-helm/expandybird - labels: - app: expandybird - replicas: 3 - service_port: 8080 - target_port: 8080 - resources: - - name: expandybird-service - type: Service - - name: expandybird-rc - type: ReplicationController - type: replicatedservice.py \ No newline at end of file diff --git a/cmd/expandybird/test/InvalidFileName.yaml b/cmd/expandybird/test/InvalidFileName.yaml deleted file mode 100644 index fa29b84d2..000000000 --- a/cmd/expandybird/test/InvalidFileName.yaml +++ /dev/null @@ -1,22 +0,0 @@ -###################################################################### -# 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. -###################################################################### - -imports: -- path: invalidfilename.py -resources: -- name: expandybird - type: replicatedservice.py - properties: - service_port: 8080 - target_port: 8080 - image: gcr.io/kubernetes-helm/expandybird diff --git a/cmd/expandybird/test/InvalidProperty.yaml b/cmd/expandybird/test/InvalidProperty.yaml deleted file mode 100644 index aab9b6341..000000000 --- a/cmd/expandybird/test/InvalidProperty.yaml +++ /dev/null @@ -1,22 +0,0 @@ -###################################################################### -# 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. -###################################################################### - -imports: -- path: replicatedservice.py -resources: -- name: expandybird - type: replicatedservice.py - properties: - service_port: 8080 - target_port: 8080 - invalidproperty: gcr.io/kubernetes-helm/expandybird diff --git a/cmd/expandybird/test/InvalidTypeName.yaml b/cmd/expandybird/test/InvalidTypeName.yaml deleted file mode 100644 index 67b7050f0..000000000 --- a/cmd/expandybird/test/InvalidTypeName.yaml +++ /dev/null @@ -1,22 +0,0 @@ -###################################################################### -# 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. -###################################################################### - -imports: -- path: replicatedservice.py -resources: -- name: expandybird - type: invalidtypename.py - properties: - service_port: 8080 - target_port: 8080 - image: gcr.io/kubernetes-helm/expandybird diff --git a/cmd/expandybird/test/MalformedContent.yaml b/cmd/expandybird/test/MalformedContent.yaml deleted file mode 100644 index c96ae41d2..000000000 --- a/cmd/expandybird/test/MalformedContent.yaml +++ /dev/null @@ -1,20 +0,0 @@ -###################################################################### -# 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. -###################################################################### - -imports: -- path: replicatedservice.py -resources: -- name: expandybird - type: replicatedservice.py - thisisnotalist: somevalue - shouldnotbehere: anothervalue diff --git a/cmd/expandybird/test/MissingImports.yaml b/cmd/expandybird/test/MissingImports.yaml deleted file mode 100644 index 91c11fe33..000000000 --- a/cmd/expandybird/test/MissingImports.yaml +++ /dev/null @@ -1,21 +0,0 @@ -###################################################################### -# 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. -###################################################################### - -imports: -resources: -- name: expandybird - type: replicatedservice.py - properties: - service_port: 8080 - target_port: 8080 - image: gcr.io/kubernetes-helm/expandybird diff --git a/cmd/expandybird/test/MissingResourceName.yaml b/cmd/expandybird/test/MissingResourceName.yaml deleted file mode 100644 index 00f7e7d61..000000000 --- a/cmd/expandybird/test/MissingResourceName.yaml +++ /dev/null @@ -1,21 +0,0 @@ -###################################################################### -# 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. -###################################################################### - -imports: -- path: replicatedservice.py -resources: -- type: replicatedservice.py - properties: - service_port: 8080 - target_port: 8080 - image: gcr.io/kubernetes-helm/expandybird diff --git a/cmd/expandybird/test/MissingTypeName.yaml b/cmd/expandybird/test/MissingTypeName.yaml deleted file mode 100644 index 4a867f86c..000000000 --- a/cmd/expandybird/test/MissingTypeName.yaml +++ /dev/null @@ -1,21 +0,0 @@ -###################################################################### -# 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. -###################################################################### - -imports: -- path: replicatedservice.py -resources: -- name: expandybird - properties: - service_port: 8080 - target_port: 8080 - image: gcr.io/kubernetes-helm/expandybird diff --git a/cmd/expandybird/test/TestArchive.tar b/cmd/expandybird/test/TestArchive.tar deleted file mode 100644 index 9cb986b978e50715ef04142faa80f1bf381f1f49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9732 zcmeHMZBH9X63%D;iV~4-Kxd30Kvq8G153C&Ygt$UnQ9@JDo1AGr^jlTHE*=B+QG7 zCSp!`VW=u4M5vNRDw8C?rgCaX<&+kaX{JP;O1e|##xCQ9uo7Rqh+<#_s0HY!b1FtL z9SH+U{@oZe?QLz{-Q9JBRkc&-@mA&xDYuUMhes#pN1IR=41eaCEZO9LS4zXo!JNbt zx};(N4Kr~^g(fl95_6_tUGKCqDj&D0EJo%|XbCApRT`}Zm056*r!rL|mBOxkT#2!yaWO+8z#N)NJy9hG zvV?j=D3qy*GQ!xACi+cSUM4?V(7Vv4?6o#&D$Gp}v9SJ!FF6enw$60kf|W(3Q)%gP zF_AsC9g5BeN)KB^WZJ^H2lOVW(V5ZtLy0c_XvQ)}keZ@C;<;RVB{H z1!I50TJ@^Z|HcEW()p!VdKv?4zuVjCdJM3?zYB1HcgW7WomUL-(s5WNuk|7|_AQF} zD+qW60Y5PaSX*2BC`Wk70({U}4FJv^0I)B2hGwaj03<*wg90c~P?!e}U`%y(*lD#c z7++3`VFmnV>8(t_17Nz0!~jT5q>O4TEYekO;g1NIfFYh}MF2U?(1=LgCPp0@;9(6i zSO$XwU1S-sm{p<{1LS#&cmA+t3SbJxYAOeS#5&L)s#Uo%ofhnFc?3k<J@#P5i|x^c1RBM1)<0ON0|wwv^bHeX z{q89vWM)O3NmJ?EOFOXZTj)ifSI#u>M+(+yhG zTc<(gS{I!r&;k=)frop8*Y+Ai!FxRM&&946W~|(Hm}6Ope6XJ%>$xTiysp1BUfo^8#zo|2KnCBKe)kbR$1Hw8&|Pxt7j=QnVLdBp()3o z(=YtK`Fp0Qb+3Tk(>h&|u6_pum$_BY_qzPAha;7K zC`qF&dDy&5f_NLG??~AtWkal9DqFqyA4pW~5Jrgv<7DI)RO2xYj6H!UlOIpb*j3hD z#=j9)?3?G@S^MqT{cCr{^3u${uzzcLkL=@mzTT+ps7t@Y!fbqv%V0bn?i0K%qs@Gj z&q6g=2wotMuc1!Yx-3-cm@>Xk?19T8;dOHlVjJ`GeNvaRCoB(4&v_uw{g^p5ckKl( zsgIaf)7bN>hdCUC#n(_iIoM-#=DAVnKph7_k6X83x)v6t*n!P2B6XwEn48* z8CH`iClxqQJr^+V@>|aCk_X7wZLV;zI?~0&o|&>Ym+OPyhgW>;>Krrn@S4G?2WIXR zj}lb&q{mA#4*4u`7Je%&m7JpZH0Rh)E>z-u$bg(Xh*_cdz%x@hAjO#b6$X0TJ@$ z#%d<>HU&c?I~xnXw;KrRrRdq%Y(CUy8%AiPiaR!{smAVITj!#W(k8i}vO|JO?BAvS zVW?6QR$#H7n_D~!lX^3eRlKf7V*_?>$8eN9nO=L!2Jbf-In3Y)BBX$1Y`E^7Q|uy~ z&wvpMjUT>f0R*f!3$*cNAa!jR*}mjf&fDa)VyD!abQF3}{7uSr+qAJmniVg>#!$mD zVmZGt8iJ)Au^N}H-WP&w9Rv(&y5_oD{y#+ALn2$uXp^|`an$)!QvF@S!B!X^{i4Hs zuwB@reQV0oY>2Zd(jw!f!mV3wRfD>mV9r@w0u=gt>{#gJl4`+>$I~h%i;^og=5gkT zD&nmzdYmzkxMtGD8?BPhT+R!(4ed;5dy#Lptl8|2^4xVbHRK2QCSIFId$V-wQDeVOa|YAwVoj7dcl`92?DqR{TW}UoQE3Ao2)b zN5S$PM<4%1v4ajsL#P=viS$5DqQ_a4lNmln#zl{%H2R~e*ratekM3=7eK+hY4>zw| z7ZZ-Z%U+i80Vg3C;F{zfAHK1CI|Z=U1~I>)*?JO{yO&RfmaW_df;dDz<&S<=6w4A diff --git a/cmd/expandybird/test/ValidContent.yaml b/cmd/expandybird/test/ValidContent.yaml deleted file mode 100644 index 5798efec3..000000000 --- a/cmd/expandybird/test/ValidContent.yaml +++ /dev/null @@ -1,27 +0,0 @@ -###################################################################### -# 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. -###################################################################### - -imports: -- path: replicatedservice.py -resources: -- name: expandybird - type: replicatedservice.py - properties: - service_port: 8080 - target_port: 8080 - container_port: 8080 - external_service: true - replicas: 3 - image: gcr.io/kubernetes-helm/expandybird - labels: - app: expandybird \ No newline at end of file diff --git a/cmd/expandybird/test/replicatedservice.py b/cmd/expandybird/test/replicatedservice.py deleted file mode 100644 index 231fb2640..000000000 --- a/cmd/expandybird/test/replicatedservice.py +++ /dev/null @@ -1,182 +0,0 @@ - -###################################################################### -# 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. -###################################################################### - -"""Defines a ReplicatedService type by creating both a Service and an RC. - -This module creates a typical abstraction for running a service in a -Kubernetes cluster, namely a replication controller and a service packaged -together into a single unit. -""" - -import yaml - -SERVICE_TYPE_COLLECTION = 'Service' -RC_TYPE_COLLECTION = 'ReplicationController' - - -def GenerateConfig(context): - """Generates a Replication Controller and a matching Service. - - Args: - context: Template context. See schema for context properties. - - Returns: - A Container Manifest as a YAML string. - """ - # YAML config that we're going to create for both RC & Service - config = {'resources': []} - - name = context.env['name'] - container_name = context.properties.get('container_name', name) - namespace = context.properties.get('namespace', 'default') - - # Define things that the Service cares about - service_name = context.properties.get('service_name', name + '-service') - service_type = SERVICE_TYPE_COLLECTION - - # Define things that the Replication Controller (rc) cares about - rc_name = name + '-rc' - rc_type = RC_TYPE_COLLECTION - - service = { - 'name': service_name, - 'type': service_type, - 'properties': { - 'apiVersion': 'v1', - 'kind': 'Service', - 'metadata': { - 'name': service_name, - 'namespace': namespace, - 'labels': GenerateLabels(context, service_name), - }, - 'spec': { - 'ports': [GenerateServicePorts(context, container_name)], - 'selector': GenerateLabels(context, name) - } - } - } - set_up_external_lb = context.properties.get('external_service', None) - if set_up_external_lb: - service['properties']['spec']['type'] = 'LoadBalancer' - config['resources'].append(service) - - rc = { - 'name': rc_name, - 'type': rc_type, - 'properties': { - 'apiVersion': 'v1', - 'kind': 'ReplicationController', - 'metadata': { - 'name': rc_name, - 'namespace': namespace, - 'labels': GenerateLabels(context, rc_name), - }, - 'spec': { - 'replicas': context.properties['replicas'], - 'selector': GenerateLabels(context, name), - 'template': { - 'metadata': { - 'labels': GenerateLabels(context, name), - }, - 'spec': { - 'containers': [ - { - 'env': GenerateEnv(context), - 'name': container_name, - 'image': context.properties['image'], - 'ports': [ - { - 'name': container_name, - 'containerPort': context.properties['container_port'], - } - ] - } - ] - } - } - } - } - } - - config['resources'].append(rc) - return yaml.dump(config) - - -def GenerateLabels(context, name): - """Generates labels either from the context.properties['labels'] or - generates a default label 'app':name - - We make a deep copy of the context.properties['labels'] section to avoid - linking in the yaml document, which I believe reduces readability of the - expanded template. If no labels are given, generate a default 'app':name. - - Args: - context: Template context, which can contain the following properties: - labels - Labels to generate - - Returns: - A dict containing labels in a name:value format - """ - tmp_labels = context.properties.get('labels', None) - ret_labels = {'app': name} - if isinstance(tmp_labels, dict): - for key, value in tmp_labels.iteritems(): - ret_labels[key] = value - return ret_labels - - -def GenerateServicePorts(context, name): - """Generates a ports section for a service. - - Args: - context: Template context, which can contain the following properties: - service_port - Port to use for the service - target_port - Target port for the service - protocol - Protocol to use. - - Returns: - A dict containing a port definition - """ - service_port = context.properties.get('service_port', None) - target_port = context.properties.get('target_port', None) - protocol = context.properties.get('protocol') - - ports = {} - if name: - ports['name'] = name - if service_port: - ports['port'] = service_port - if target_port: - ports['targetPort'] = target_port - if protocol: - ports['protocol'] = protocol - - return ports - -def GenerateEnv(context): - """Generates environmental variables for a pod. - - Args: - context: Template context, which can contain the following properties: - env - Environment variables to set. - - Returns: - A list containing env variables in dict format {name: 'name', value: 'value'} - """ - env = [] - tmp_env = context.properties.get('env', []) - for entry in tmp_env: - if isinstance(entry, dict): - env.append({'name': entry.get('name'), 'value': entry.get('value')}) - return env diff --git a/cmd/expandybird/test/schemas/bad.jinja.schema b/cmd/expandybird/test/schemas/bad.jinja.schema deleted file mode 100644 index 825f8dcf7..000000000 --- a/cmd/expandybird/test/schemas/bad.jinja.schema +++ /dev/null @@ -1,9 +0,0 @@ -info: - title: Schema with a lots of errors in it - -imports: - -properties: - exclusiveMin: - type: integer - exclusiveMinimum: 0 diff --git a/cmd/expandybird/test/schemas/default_ref.jinja.schema b/cmd/expandybird/test/schemas/default_ref.jinja.schema deleted file mode 100644 index 51f83d2c8..000000000 --- a/cmd/expandybird/test/schemas/default_ref.jinja.schema +++ /dev/null @@ -1,14 +0,0 @@ -info: - title: Schema with a property that has a referenced default value - -imports: - -properties: - number: - $ref: '#/level/mult' - -level: - mult: - type: integer - multipleOf: 1 - default: 1 diff --git a/cmd/expandybird/test/schemas/defaults.jinja.schema b/cmd/expandybird/test/schemas/defaults.jinja.schema deleted file mode 100644 index bcb7ee34e..000000000 --- a/cmd/expandybird/test/schemas/defaults.jinja.schema +++ /dev/null @@ -1,12 +0,0 @@ -info: - title: Schema with properties that have default values - -imports: - -properties: - one: - type: integer - default: 1 - alpha: - type: string - default: alpha diff --git a/cmd/expandybird/test/schemas/defaults.py.schema b/cmd/expandybird/test/schemas/defaults.py.schema deleted file mode 100644 index bcb7ee34e..000000000 --- a/cmd/expandybird/test/schemas/defaults.py.schema +++ /dev/null @@ -1,12 +0,0 @@ -info: - title: Schema with properties that have default values - -imports: - -properties: - one: - type: integer - default: 1 - alpha: - type: string - default: alpha diff --git a/cmd/expandybird/test/schemas/invalid_default.jinja.schema b/cmd/expandybird/test/schemas/invalid_default.jinja.schema deleted file mode 100644 index e60d11148..000000000 --- a/cmd/expandybird/test/schemas/invalid_default.jinja.schema +++ /dev/null @@ -1,11 +0,0 @@ -info: - title: Schema with a required integer property that has a default string value - -imports: - -required: - - number -properties: - number: - type: integer - default: string diff --git a/cmd/expandybird/test/schemas/invalid_reference.py.schema b/cmd/expandybird/test/schemas/invalid_reference.py.schema deleted file mode 100644 index 7c3fa3e10..000000000 --- a/cmd/expandybird/test/schemas/invalid_reference.py.schema +++ /dev/null @@ -1,10 +0,0 @@ -info: - title: Schema with references to something that doesnt exist - -imports: - -properties: - odd: - type: integer - not: - $ref: '#/wheeeeeee' diff --git a/cmd/expandybird/test/schemas/invalid_reference_schema.py.schema b/cmd/expandybird/test/schemas/invalid_reference_schema.py.schema deleted file mode 100644 index 6c824568c..000000000 --- a/cmd/expandybird/test/schemas/invalid_reference_schema.py.schema +++ /dev/null @@ -1,8 +0,0 @@ -info: - title: Schema with references to something that doesnt exist - -imports: - -properties: - odd: - $ref: '#/wheeeeeee' diff --git a/cmd/expandybird/test/schemas/metadata.py.schema b/cmd/expandybird/test/schemas/metadata.py.schema deleted file mode 100644 index 3d6e1e346..000000000 --- a/cmd/expandybird/test/schemas/metadata.py.schema +++ /dev/null @@ -1,20 +0,0 @@ -info: - title: Schema with properties that have extra metadata - -imports: - -properties: - one: - type: integer - default: 1 - metadata: - gcloud: is great! - compute: is awesome - alpha: - type: string - default: alpha - metadata: - - you - - can - - do - - anything diff --git a/cmd/expandybird/test/schemas/missing_quote.py.schema b/cmd/expandybird/test/schemas/missing_quote.py.schema deleted file mode 100644 index ddd4b5bfd..000000000 --- a/cmd/expandybird/test/schemas/missing_quote.py.schema +++ /dev/null @@ -1,11 +0,0 @@ -info: - title: Schema with references - -imports: - -properties: - number: - $ref: #/number - -number: - type: integer diff --git a/cmd/expandybird/test/schemas/nested_defaults.py.schema b/cmd/expandybird/test/schemas/nested_defaults.py.schema deleted file mode 100644 index b5288c91b..000000000 --- a/cmd/expandybird/test/schemas/nested_defaults.py.schema +++ /dev/null @@ -1,33 +0,0 @@ -info: - title: VM with Disks - author: Kubernetes - description: Creates a single vm, then attaches disks to it. - -required: -- zone - -properties: - zone: - type: string - description: GCP zone - default: us-central1-a - disks: - type: array - items: - type: object - required: - - name - properties: - name: - type: string - description: Suffix for this disk - sizeGb: - type: integer - default: 100 - diskType: - type: string - enum: - - pd-standard - - pd-ssd - default: pd-standard - additionalProperties: false diff --git a/cmd/expandybird/test/schemas/numbers.py.schema b/cmd/expandybird/test/schemas/numbers.py.schema deleted file mode 100644 index eff245182..000000000 --- a/cmd/expandybird/test/schemas/numbers.py.schema +++ /dev/null @@ -1,27 +0,0 @@ -info: - title: Schema with a lots of number properties and restrictions - -imports: - -properties: - minimum0: - type: integer - minimum: 0 - exclusiveMin0: - type: integer - minimum: 0 - exclusiveMinimum: true - maximum10: - type: integer - maximum: 10 - exclusiveMax10: - type: integer - maximum: 10 - exclusiveMaximum: true - even: - type: integer - multipleOf: 2 - odd: - type: integer - not: - multipleOf: 2 diff --git a/cmd/expandybird/test/schemas/ref_nested_defaults.py.schema b/cmd/expandybird/test/schemas/ref_nested_defaults.py.schema deleted file mode 100644 index 80813b73d..000000000 --- a/cmd/expandybird/test/schemas/ref_nested_defaults.py.schema +++ /dev/null @@ -1,36 +0,0 @@ -info: - title: VM with Disks - author: Kubernetes - description: Creates a single vm, then attaches disks to it. - -required: -- zone - -properties: - zone: - type: string - description: GCP zone - default: us-central1-a - disks: - type: array - items: - $ref: '#/disk' - -disk: - type: object - required: - - name - properties: - name: - type: string - description: Suffix for this disk - sizeGb: - type: integer - default: 100 - diskType: - type: string - enum: - - pd-standard - - pd-ssd - default: pd-standard - additionalProperties: false diff --git a/cmd/expandybird/test/schemas/reference.jinja.schema b/cmd/expandybird/test/schemas/reference.jinja.schema deleted file mode 100644 index e90251c39..000000000 --- a/cmd/expandybird/test/schemas/reference.jinja.schema +++ /dev/null @@ -1,14 +0,0 @@ -info: - title: Schema with references - -imports: - -properties: - odd: - type: integer - not: - $ref: '#/even' - - -even: - multipleOf: 2 diff --git a/cmd/expandybird/test/schemas/req_default_ref.py.schema b/cmd/expandybird/test/schemas/req_default_ref.py.schema deleted file mode 100644 index 08b1da3e9..000000000 --- a/cmd/expandybird/test/schemas/req_default_ref.py.schema +++ /dev/null @@ -1,14 +0,0 @@ -info: - title: Schema with a required property that has a referenced default value - -imports: - -required: - - number -properties: - number: - $ref: '#/default_val' - -default_val: - type: integer - default: my_name diff --git a/cmd/expandybird/test/schemas/required.jinja.schema b/cmd/expandybird/test/schemas/required.jinja.schema deleted file mode 100644 index 94c8e39f8..000000000 --- a/cmd/expandybird/test/schemas/required.jinja.schema +++ /dev/null @@ -1,10 +0,0 @@ -info: - title: Schema with a required property - -imports: - -required: - - name -properties: - name: - type: string diff --git a/cmd/expandybird/test/schemas/required_default.jinja.schema b/cmd/expandybird/test/schemas/required_default.jinja.schema deleted file mode 100644 index d739e2c20..000000000 --- a/cmd/expandybird/test/schemas/required_default.jinja.schema +++ /dev/null @@ -1,11 +0,0 @@ -info: - title: Schema with a required property that has a default value - -imports: - -required: - - name -properties: - name: - type: string - default: my_name diff --git a/cmd/expandybird/test/templates/description_text.txt b/cmd/expandybird/test/templates/description_text.txt deleted file mode 100644 index 33e5dea2e..000000000 --- a/cmd/expandybird/test/templates/description_text.txt +++ /dev/null @@ -1 +0,0 @@ -"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." diff --git a/cmd/expandybird/test/templates/duplicate_names.yaml b/cmd/expandybird/test/templates/duplicate_names.yaml deleted file mode 100644 index f7386f186..000000000 --- a/cmd/expandybird/test/templates/duplicate_names.yaml +++ /dev/null @@ -1,9 +0,0 @@ -resources: -- type: compute.v1.instance - name: my_instance - properties: - zone: test-zone-a -- type: compute.v1.instance - name: my_instance - properties: - zone: test-zone-b diff --git a/cmd/expandybird/test/templates/duplicate_names_B.jinja b/cmd/expandybird/test/templates/duplicate_names_B.jinja deleted file mode 100644 index 8ae8a7923..000000000 --- a/cmd/expandybird/test/templates/duplicate_names_B.jinja +++ /dev/null @@ -1,5 +0,0 @@ -resources: -- type: compute.v1.instance - name: B - properties: - zone: test-zone-b diff --git a/cmd/expandybird/test/templates/duplicate_names_C.jinja b/cmd/expandybird/test/templates/duplicate_names_C.jinja deleted file mode 100644 index 3aff269fe..000000000 --- a/cmd/expandybird/test/templates/duplicate_names_C.jinja +++ /dev/null @@ -1,5 +0,0 @@ -resources: -- type: compute.v1.instance - name: C - properties: - zone: test-zone-c diff --git a/cmd/expandybird/test/templates/duplicate_names_in_subtemplates.jinja b/cmd/expandybird/test/templates/duplicate_names_in_subtemplates.jinja deleted file mode 100644 index f7386f186..000000000 --- a/cmd/expandybird/test/templates/duplicate_names_in_subtemplates.jinja +++ /dev/null @@ -1,9 +0,0 @@ -resources: -- type: compute.v1.instance - name: my_instance - properties: - zone: test-zone-a -- type: compute.v1.instance - name: my_instance - properties: - zone: test-zone-b diff --git a/cmd/expandybird/test/templates/duplicate_names_in_subtemplates.yaml b/cmd/expandybird/test/templates/duplicate_names_in_subtemplates.yaml deleted file mode 100644 index 06e8a263c..000000000 --- a/cmd/expandybird/test/templates/duplicate_names_in_subtemplates.yaml +++ /dev/null @@ -1,5 +0,0 @@ -imports: ["duplicate_names_in_subtemplates.jinja"] - -resources: -- name: subtemplate - type: duplicate_names_in_subtemplates.jinja diff --git a/cmd/expandybird/test/templates/duplicate_names_mixed_level.yaml b/cmd/expandybird/test/templates/duplicate_names_mixed_level.yaml deleted file mode 100644 index 7cb82e5e7..000000000 --- a/cmd/expandybird/test/templates/duplicate_names_mixed_level.yaml +++ /dev/null @@ -1,7 +0,0 @@ -imports: ["duplicate_names_B.jinja", "duplicate_names_C.jinja"] - -resources: -- name: A - type: duplicate_names_B.jinja -- name: B - type: duplicate_names_C.jinja diff --git a/cmd/expandybird/test/templates/duplicate_names_mixed_level_result.yaml b/cmd/expandybird/test/templates/duplicate_names_mixed_level_result.yaml deleted file mode 100644 index de34fa47a..000000000 --- a/cmd/expandybird/test/templates/duplicate_names_mixed_level_result.yaml +++ /dev/null @@ -1,22 +0,0 @@ -config: - resources: - - name: B - properties: - zone: test-zone-b - type: compute.v1.instance - - name: C - properties: - zone: test-zone-c - type: compute.v1.instance -layout: - resources: - - name: A - resources: - - name: B - type: compute.v1.instance - type: duplicate_names_B.jinja - - name: B - resources: - - name: C - type: compute.v1.instance - type: duplicate_names_C.jinja diff --git a/cmd/expandybird/test/templates/duplicate_names_parent_child.yaml b/cmd/expandybird/test/templates/duplicate_names_parent_child.yaml deleted file mode 100644 index 4c3ae6bf1..000000000 --- a/cmd/expandybird/test/templates/duplicate_names_parent_child.yaml +++ /dev/null @@ -1,7 +0,0 @@ -imports: ["duplicate_names_B.jinja"] - -resources: -- name: A - type: duplicate_names_B.jinja -- name: B - type: compute.v1.instance diff --git a/cmd/expandybird/test/templates/duplicate_names_parent_child_result.yaml b/cmd/expandybird/test/templates/duplicate_names_parent_child_result.yaml deleted file mode 100644 index 8384a72ee..000000000 --- a/cmd/expandybird/test/templates/duplicate_names_parent_child_result.yaml +++ /dev/null @@ -1,17 +0,0 @@ -config: - resources: - - name: B - properties: - zone: test-zone-b - type: compute.v1.instance - - name: B - type: compute.v1.instance -layout: - resources: - - name: A - resources: - - name: B - type: compute.v1.instance - type: duplicate_names_B.jinja - - name: B - type: compute.v1.instance diff --git a/cmd/expandybird/test/templates/helper.jinja b/cmd/expandybird/test/templates/helper.jinja deleted file mode 100644 index d174617f4..000000000 --- a/cmd/expandybird/test/templates/helper.jinja +++ /dev/null @@ -1,5 +0,0 @@ -resources: -- name: helper - type: bar - properties: - test: {{ properties["foobar"] }} diff --git a/cmd/expandybird/test/templates/helper.jinja.schema b/cmd/expandybird/test/templates/helper.jinja.schema deleted file mode 100644 index 7bbdf5b5e..000000000 --- a/cmd/expandybird/test/templates/helper.jinja.schema +++ /dev/null @@ -1,4 +0,0 @@ -properties: - foobar: - type: string - default: Use this schema diff --git a/cmd/expandybird/test/templates/helpers/common.jinja b/cmd/expandybird/test/templates/helpers/common.jinja deleted file mode 100644 index 056435742..000000000 --- a/cmd/expandybird/test/templates/helpers/common.jinja +++ /dev/null @@ -1,3 +0,0 @@ -{%- macro GenerateMachineName(prefix='', suffix='') -%} - {{ prefix + "-" + suffix }} -{%- endmacro %} diff --git a/cmd/expandybird/test/templates/helpers/common.py b/cmd/expandybird/test/templates/helpers/common.py deleted file mode 100644 index a553e65dd..000000000 --- a/cmd/expandybird/test/templates/helpers/common.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Dummy helper methods invoked in other constructors.""" - - -def GenerateMachineName(prefix, suffix): - """Generates name of a VM.""" - return prefix + "-" + suffix diff --git a/cmd/expandybird/test/templates/helpers/extra/__init__.py b/cmd/expandybird/test/templates/helpers/extra/__init__.py deleted file mode 100644 index 27e204d9a..000000000 --- a/cmd/expandybird/test/templates/helpers/extra/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Package marker file.""" diff --git a/cmd/expandybird/test/templates/helpers/extra/common2.py b/cmd/expandybird/test/templates/helpers/extra/common2.py deleted file mode 100644 index 469354b81..000000000 --- a/cmd/expandybird/test/templates/helpers/extra/common2.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Dummy helper methods invoked in other constructors.""" - - -def GenerateMachineSize(): - """Generates size of a VM.""" - return "big" diff --git a/cmd/expandybird/test/templates/invalid_config.yaml b/cmd/expandybird/test/templates/invalid_config.yaml deleted file mode 100644 index d2e04dfb4..000000000 --- a/cmd/expandybird/test/templates/invalid_config.yaml +++ /dev/null @@ -1,2 +0,0 @@ -resources: -- name: foo properties: bar: baz diff --git a/cmd/expandybird/test/templates/jinja_defaults.jinja b/cmd/expandybird/test/templates/jinja_defaults.jinja deleted file mode 100644 index 74837f2a2..000000000 --- a/cmd/expandybird/test/templates/jinja_defaults.jinja +++ /dev/null @@ -1,16 +0,0 @@ -resources: -- type: compute.v1.instance - name: vm-created-by-cloud-config-{{ properties["deployment"] }} - properties: - zone: {{ properties["zone"] }} - machineType: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/zones/{{ properties["zone"] }}/machineTypes/f1-micro - disks: - - deviceName: boot - type: PERSISTENT - boot: true - autoDelete: true - initializeParams: - diskName: disk-created-by-cloud-config-{{ properties["deployment"] }} - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/global/networks/default diff --git a/cmd/expandybird/test/templates/jinja_defaults.jinja.schema b/cmd/expandybird/test/templates/jinja_defaults.jinja.schema deleted file mode 100644 index 7cf4ae108..000000000 --- a/cmd/expandybird/test/templates/jinja_defaults.jinja.schema +++ /dev/null @@ -1,18 +0,0 @@ -info: - title: Schema for a basic jinja template that includes default values - -imports: - -properties: - foo: - description: blah - type: string - zone: - type: string - default: test-zone - project: - type: string - default: test-project - deployment: - type: string - default: test-deployment diff --git a/cmd/expandybird/test/templates/jinja_defaults.yaml b/cmd/expandybird/test/templates/jinja_defaults.yaml deleted file mode 100644 index 1b3ee64f4..000000000 --- a/cmd/expandybird/test/templates/jinja_defaults.yaml +++ /dev/null @@ -1,9 +0,0 @@ -imports: -- path: "jinja_defaults.jinja" -- path: "jinja_defaults.jinja.schema" - -resources: -- name: jinja_defaults_name - type: jinja_defaults.jinja - properties: - foo: bar diff --git a/cmd/expandybird/test/templates/jinja_defaults_result.yaml b/cmd/expandybird/test/templates/jinja_defaults_result.yaml deleted file mode 100644 index 60c03b408..000000000 --- a/cmd/expandybird/test/templates/jinja_defaults_result.yaml +++ /dev/null @@ -1,29 +0,0 @@ -config: - resources: - - name: vm-created-by-cloud-config-test-deployment - properties: - disks: - - autoDelete: true - boot: true - deviceName: boot - initializeParams: - diskName: disk-created-by-cloud-config-test-deployment - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - type: PERSISTENT - machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default - zone: test-zone - type: compute.v1.instance -layout: - resources: - - name: jinja_defaults_name - properties: - deployment: test-deployment - foo: bar - project: test-project - zone: test-zone - resources: - - name: vm-created-by-cloud-config-test-deployment - type: compute.v1.instance - type: jinja_defaults.jinja diff --git a/cmd/expandybird/test/templates/jinja_missing_required.jinja b/cmd/expandybird/test/templates/jinja_missing_required.jinja deleted file mode 100644 index 2247bf2e1..000000000 --- a/cmd/expandybird/test/templates/jinja_missing_required.jinja +++ /dev/null @@ -1,4 +0,0 @@ -Nothing here because this file should never be called. -The validation will fail before this file is used. - -{{% Invalid diff --git a/cmd/expandybird/test/templates/jinja_missing_required.jinja.schema b/cmd/expandybird/test/templates/jinja_missing_required.jinja.schema deleted file mode 100644 index 387b64dde..000000000 --- a/cmd/expandybird/test/templates/jinja_missing_required.jinja.schema +++ /dev/null @@ -1,11 +0,0 @@ -info: - title: Schema with a required property - -imports: - -required: - - important -properties: - important: - type: string - diff --git a/cmd/expandybird/test/templates/jinja_missing_required.yaml b/cmd/expandybird/test/templates/jinja_missing_required.yaml deleted file mode 100644 index 412bccf8e..000000000 --- a/cmd/expandybird/test/templates/jinja_missing_required.yaml +++ /dev/null @@ -1,9 +0,0 @@ -imports: -- path: "jinja_missing_required.jinja" -- path: "jinja_missing_required.jinja.schema" - -resources: -- name: jinja_missing_required_resource_name - type: jinja_missing_required.jinja - properties: - less-important: an optional property diff --git a/cmd/expandybird/test/templates/jinja_multiple_errors.jinja b/cmd/expandybird/test/templates/jinja_multiple_errors.jinja deleted file mode 100644 index 2247bf2e1..000000000 --- a/cmd/expandybird/test/templates/jinja_multiple_errors.jinja +++ /dev/null @@ -1,4 +0,0 @@ -Nothing here because this file should never be called. -The validation will fail before this file is used. - -{{% Invalid diff --git a/cmd/expandybird/test/templates/jinja_multiple_errors.jinja.schema b/cmd/expandybird/test/templates/jinja_multiple_errors.jinja.schema deleted file mode 100644 index 5d73e125d..000000000 --- a/cmd/expandybird/test/templates/jinja_multiple_errors.jinja.schema +++ /dev/null @@ -1,22 +0,0 @@ -info: - title: Schema with several rules - -imports: - -properties: - number: - type: integer - short-string: - type: string - maxLength: 10 - odd: - type: integer - not: - multipleOf: 2 - abc: - type: string - enum: - - a - - b - - c - diff --git a/cmd/expandybird/test/templates/jinja_multiple_errors.yaml b/cmd/expandybird/test/templates/jinja_multiple_errors.yaml deleted file mode 100644 index 0fc663628..000000000 --- a/cmd/expandybird/test/templates/jinja_multiple_errors.yaml +++ /dev/null @@ -1,12 +0,0 @@ -imports: -- path: "jinja_multiple_errors.jinja" -- path: "jinja_multiple_errors.jinja.schema" - -resources: -- name: jinja_multiple_errors - type: jinja_multiple_errors.jinja - properties: - number: a string - short-string: longer than 10 chars - odd: 6 - abc: d diff --git a/cmd/expandybird/test/templates/jinja_noparams.jinja b/cmd/expandybird/test/templates/jinja_noparams.jinja deleted file mode 100644 index 9b0ec9476..000000000 --- a/cmd/expandybird/test/templates/jinja_noparams.jinja +++ /dev/null @@ -1,19 +0,0 @@ -resources: -{% for name in ['name1', 'name2'] %} -- type: compute.v1.instance - name: {{ name }} - properties: - zone: test-zone - machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro - disks: - - deviceName: boot - type: PERSISTENT - boot: true - autoDelete: true - initializeParams: - diskName: disk-created-by-cloud-config-test-deployment - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default -{% endfor %} - diff --git a/cmd/expandybird/test/templates/jinja_noparams.yaml b/cmd/expandybird/test/templates/jinja_noparams.yaml deleted file mode 100644 index 7debc85e2..000000000 --- a/cmd/expandybird/test/templates/jinja_noparams.yaml +++ /dev/null @@ -1,6 +0,0 @@ -imports: ["jinja_noparams.jinja"] - -resources: -- name: jinja_noparams_name - type: jinja_noparams.jinja - diff --git a/cmd/expandybird/test/templates/jinja_noparams_result.yaml b/cmd/expandybird/test/templates/jinja_noparams_result.yaml deleted file mode 100644 index 5a5f0e52c..000000000 --- a/cmd/expandybird/test/templates/jinja_noparams_result.yaml +++ /dev/null @@ -1,41 +0,0 @@ -config: - resources: - - name: name1 - properties: - disks: - - autoDelete: true - boot: true - deviceName: boot - initializeParams: - diskName: disk-created-by-cloud-config-test-deployment - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - type: PERSISTENT - machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default - zone: test-zone - type: compute.v1.instance - - name: name2 - properties: - disks: - - autoDelete: true - boot: true - deviceName: boot - initializeParams: - diskName: disk-created-by-cloud-config-test-deployment - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - type: PERSISTENT - machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default - zone: test-zone - type: compute.v1.instance -layout: - resources: - - name: jinja_noparams_name - resources: - - name: name1 - type: compute.v1.instance - - name: name2 - type: compute.v1.instance - type: jinja_noparams.jinja diff --git a/cmd/expandybird/test/templates/jinja_template.jinja b/cmd/expandybird/test/templates/jinja_template.jinja deleted file mode 100644 index 5febf21bd..000000000 --- a/cmd/expandybird/test/templates/jinja_template.jinja +++ /dev/null @@ -1,18 +0,0 @@ -resources: -- type: compute.v1.instance - name: vm-created-by-cloud-config-{{ properties["deployment"] }} - properties: - zone: {{ properties["zone"] }} - machineType: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/zones/{{ properties["zone"] }}/machineTypes/f1-micro - disks: - - deviceName: boot - type: PERSISTENT - boot: true - autoDelete: true - initializeParams: - diskName: disk-created-by-cloud-config-{{ properties["deployment"] }} - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/global/networks/default - - diff --git a/cmd/expandybird/test/templates/jinja_template.yaml b/cmd/expandybird/test/templates/jinja_template.yaml deleted file mode 100644 index bc2b2b0db..000000000 --- a/cmd/expandybird/test/templates/jinja_template.yaml +++ /dev/null @@ -1,10 +0,0 @@ -imports: ["jinja_template.jinja"] - -resources: -- name: jinja_template_name - type: jinja_template.jinja - properties: - zone: test-zone - project: test-project - deployment: test-deployment - diff --git a/cmd/expandybird/test/templates/jinja_template_result.yaml b/cmd/expandybird/test/templates/jinja_template_result.yaml deleted file mode 100644 index 10a36fa56..000000000 --- a/cmd/expandybird/test/templates/jinja_template_result.yaml +++ /dev/null @@ -1,28 +0,0 @@ -config: - resources: - - name: vm-created-by-cloud-config-test-deployment - properties: - disks: - - autoDelete: true - boot: true - deviceName: boot - initializeParams: - diskName: disk-created-by-cloud-config-test-deployment - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - type: PERSISTENT - machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default - zone: test-zone - type: compute.v1.instance -layout: - resources: - - name: jinja_template_name - properties: - deployment: test-deployment - project: test-project - zone: test-zone - resources: - - name: vm-created-by-cloud-config-test-deployment - type: compute.v1.instance - type: jinja_template.jinja diff --git a/cmd/expandybird/test/templates/jinja_template_with_env.jinja b/cmd/expandybird/test/templates/jinja_template_with_env.jinja deleted file mode 100644 index 545824e5e..000000000 --- a/cmd/expandybird/test/templates/jinja_template_with_env.jinja +++ /dev/null @@ -1,18 +0,0 @@ -resources: -- type: compute.v1.instance - name: vm-created-by-cloud-config-{{ env["deployment"] }} - properties: - zone: {{ properties["zone"] }} - machineType: https://www.googleapis.com/compute/v1/projects/{{ env["project"] }}/zones/{{ properties["zone"] }}/machineTypes/f1-micro - disks: - - deviceName: boot - type: PERSISTENT - boot: true - autoDelete: true - initializeParams: - diskName: disk-created-by-cloud-config-{{ env["deployment"] }}-{{ env["name"] }}-{{ env["type"] }} - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/{{ env["project"] }}/global/networks/default - - diff --git a/cmd/expandybird/test/templates/jinja_template_with_env.yaml b/cmd/expandybird/test/templates/jinja_template_with_env.yaml deleted file mode 100644 index 58388635c..000000000 --- a/cmd/expandybird/test/templates/jinja_template_with_env.yaml +++ /dev/null @@ -1,8 +0,0 @@ -imports: ["jinja_template_with_env.jinja"] - -resources: -- name: jinja_template_with_env_name - type: jinja_template_with_env.jinja - properties: - zone: test-zone - diff --git a/cmd/expandybird/test/templates/jinja_template_with_env_result.yaml b/cmd/expandybird/test/templates/jinja_template_with_env_result.yaml deleted file mode 100644 index a69f89ab1..000000000 --- a/cmd/expandybird/test/templates/jinja_template_with_env_result.yaml +++ /dev/null @@ -1,26 +0,0 @@ -config: - resources: - - name: vm-created-by-cloud-config-test-deployment - properties: - disks: - - autoDelete: true - boot: true - deviceName: boot - initializeParams: - diskName: disk-created-by-cloud-config-test-deployment-jinja_template_with_env_name-jinja_template_with_env.jinja - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - type: PERSISTENT - machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default - zone: test-zone - type: compute.v1.instance -layout: - resources: - - name: jinja_template_with_env_name - properties: - zone: test-zone - resources: - - name: vm-created-by-cloud-config-test-deployment - type: compute.v1.instance - type: jinja_template_with_env.jinja diff --git a/cmd/expandybird/test/templates/jinja_template_with_import.jinja b/cmd/expandybird/test/templates/jinja_template_with_import.jinja deleted file mode 100644 index b5c125726..000000000 --- a/cmd/expandybird/test/templates/jinja_template_with_import.jinja +++ /dev/null @@ -1,6 +0,0 @@ -{% import 'helpers/common.jinja' as common %} -resources: -- name: {{ common.GenerateMachineName("myFrontend", "prod") }} - type: compute.v1.instance - properties: - machineSize: big diff --git a/cmd/expandybird/test/templates/jinja_template_with_import.yaml b/cmd/expandybird/test/templates/jinja_template_with_import.yaml deleted file mode 100644 index d4ec9f327..000000000 --- a/cmd/expandybird/test/templates/jinja_template_with_import.yaml +++ /dev/null @@ -1,5 +0,0 @@ -imports: ["jinja_template_with_import.jinja", "helpers/common.jinja"] - -resources: -- name: jinja_template_with_import_name - type: jinja_template_with_import.jinja diff --git a/cmd/expandybird/test/templates/jinja_template_with_import_result.yaml b/cmd/expandybird/test/templates/jinja_template_with_import_result.yaml deleted file mode 100644 index 3a3c3b2e5..000000000 --- a/cmd/expandybird/test/templates/jinja_template_with_import_result.yaml +++ /dev/null @@ -1,13 +0,0 @@ -config: - resources: - - name: myFrontend-prod - properties: - machineSize: big - type: compute.v1.instance -layout: - resources: - - name: jinja_template_with_import_name - resources: - - name: myFrontend-prod - type: compute.v1.instance - type: jinja_template_with_import.jinja diff --git a/cmd/expandybird/test/templates/jinja_template_with_inlinedfile.jinja b/cmd/expandybird/test/templates/jinja_template_with_inlinedfile.jinja deleted file mode 100644 index 01d7642e4..000000000 --- a/cmd/expandybird/test/templates/jinja_template_with_inlinedfile.jinja +++ /dev/null @@ -1,7 +0,0 @@ -{% import 'helpers/common.jinja' as common %} -resources: -- name: {{ common.GenerateMachineName("myFrontend", "prod") }} - type: compute.v1.instance - properties: - description: {{ imports[properties["description-file"]] }} - machineSize: big diff --git a/cmd/expandybird/test/templates/jinja_template_with_inlinedfile.yaml b/cmd/expandybird/test/templates/jinja_template_with_inlinedfile.yaml deleted file mode 100644 index e8bec0891..000000000 --- a/cmd/expandybird/test/templates/jinja_template_with_inlinedfile.yaml +++ /dev/null @@ -1,7 +0,0 @@ -imports: ["jinja_template_with_inlinedfile.jinja", "helpers/common.jinja", "description_text.txt"] - -resources: -- name: jinja_template_with_inlinedfile_name - type: jinja_template_with_inlinedfile.jinja - properties: - description-file: description_text.txt diff --git a/cmd/expandybird/test/templates/jinja_template_with_inlinedfile_result.yaml b/cmd/expandybird/test/templates/jinja_template_with_inlinedfile_result.yaml deleted file mode 100644 index 6f4bf9eee..000000000 --- a/cmd/expandybird/test/templates/jinja_template_with_inlinedfile_result.yaml +++ /dev/null @@ -1,21 +0,0 @@ -config: - resources: - - name: myFrontend-prod - properties: - description: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim - veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo - consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse - cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat - non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - machineSize: big - type: compute.v1.instance -layout: - resources: - - name: jinja_template_with_inlinedfile_name - properties: - description-file: description_text.txt - resources: - - name: myFrontend-prod - type: compute.v1.instance - type: jinja_template_with_inlinedfile.jinja diff --git a/cmd/expandybird/test/templates/jinja_unresolved.jinja b/cmd/expandybird/test/templates/jinja_unresolved.jinja deleted file mode 100644 index 6ad1ed1cd..000000000 --- a/cmd/expandybird/test/templates/jinja_unresolved.jinja +++ /dev/null @@ -1,18 +0,0 @@ -resources: -- type: compute.v1.instance - name: vm-created-by-cloud-config-{{ porcelain["deployment"] }} - properties: - zone: {{ properties["zone"] }} - machineType: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/zones/{{ properties["zone"] }}/machineTypes/f1-micro - disks: - - deviceName: boot - type: PERSISTENT - boot: true - autoDelete: true - initializeParams: - diskName: disk-created-by-cloud-config-{{ properties["deployment"] }} - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/global/networks/default - - diff --git a/cmd/expandybird/test/templates/jinja_unresolved.yaml b/cmd/expandybird/test/templates/jinja_unresolved.yaml deleted file mode 100644 index 8bc31af11..000000000 --- a/cmd/expandybird/test/templates/jinja_unresolved.yaml +++ /dev/null @@ -1,10 +0,0 @@ -imports: ["jinja_unresolved.jinja"] - -resources: -- name: jinja_template_name - type: jinja_unresolved.jinja - properties: - zone: test-zone - project: test-project - deployment: test-deployment - diff --git a/cmd/expandybird/test/templates/no_properties.py b/cmd/expandybird/test/templates/no_properties.py deleted file mode 100644 index 66cd164c5..000000000 --- a/cmd/expandybird/test/templates/no_properties.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Return empty resources block.""" - - -def GenerateConfig(_): - return """resources:""" diff --git a/cmd/expandybird/test/templates/no_properties.yaml b/cmd/expandybird/test/templates/no_properties.yaml deleted file mode 100644 index 1c7d29795..000000000 --- a/cmd/expandybird/test/templates/no_properties.yaml +++ /dev/null @@ -1,6 +0,0 @@ -imports: -- path: "no_properties.py" - -resources: -- name: test-resource - type: no_properties.py diff --git a/cmd/expandybird/test/templates/no_properties_result.yaml b/cmd/expandybird/test/templates/no_properties_result.yaml deleted file mode 100644 index 41ccb5602..000000000 --- a/cmd/expandybird/test/templates/no_properties_result.yaml +++ /dev/null @@ -1,6 +0,0 @@ -config: - resources: [] -layout: - resources: - - name: test-resource - type: no_properties.py diff --git a/cmd/expandybird/test/templates/no_resources.py b/cmd/expandybird/test/templates/no_resources.py deleted file mode 100644 index c387ebca0..000000000 --- a/cmd/expandybird/test/templates/no_resources.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Does nothing.""" - - -def GenerateConfig(_): - """Returns empty string.""" - return '' diff --git a/cmd/expandybird/test/templates/no_resources.yaml b/cmd/expandybird/test/templates/no_resources.yaml deleted file mode 100644 index d9257d8ea..000000000 --- a/cmd/expandybird/test/templates/no_resources.yaml +++ /dev/null @@ -1,6 +0,0 @@ -imports: -- path: "no_resources.py" - -resources: -- name: test-resource - type: no_resources.py diff --git a/cmd/expandybird/test/templates/python_and_jinja_template.jinja b/cmd/expandybird/test/templates/python_and_jinja_template.jinja deleted file mode 100644 index 8a670b476..000000000 --- a/cmd/expandybird/test/templates/python_and_jinja_template.jinja +++ /dev/null @@ -1,17 +0,0 @@ -resources: -- type: compute.v1.instance - name: vm-created-by-cloud-config-{{ properties["deployment"] }} - properties: - zone: {{ properties["zone"] }} - machineType: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/zones/{{ properties["zone"] }}/machineTypes/f1-micro - disks: - - deviceName: boot - type: PERSISTENT - boot: true - autoDelete: true - initializeParams: - diskName: disk-created-by-cloud-config-{{ properties["deployment"] }} - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/{{ properties["project"] }}/global/networks/default - diff --git a/cmd/expandybird/test/templates/python_and_jinja_template.py b/cmd/expandybird/test/templates/python_and_jinja_template.py deleted file mode 100644 index 86562c9d0..000000000 --- a/cmd/expandybird/test/templates/python_and_jinja_template.py +++ /dev/null @@ -1,35 +0,0 @@ -#% description: Creates a VM running a Salt master daemon in a Docker container. -#% parameters: -#% - name: masterAddress -#% type: string -#% description: Name of the Salt master VM. -#% required: true -#% - name: project -#% type: string -#% description: Name of the Cloud project. -#% required: true -#% - name: zone -#% type: string -#% description: Zone to create the resources in. -#% required: true - -"""Generates config for a VM running a SaltStack master. - -Just for fun this template is in Python, while the others in this -directory are in Jinja2. -""" - - -def GenerateConfig(evaluation_context): - return """ -resources: -- name: python_and_jinja_template_jinja_name - type: python_and_jinja_template.jinja - properties: - zone: %(zone)s - project: %(project)s - deployment: %(master)s - -""" % {"master": evaluation_context.properties["masterAddress"], - "project": evaluation_context.properties["project"], - "zone": evaluation_context.properties["zone"]} diff --git a/cmd/expandybird/test/templates/python_and_jinja_template.yaml b/cmd/expandybird/test/templates/python_and_jinja_template.yaml deleted file mode 100644 index 46daafc27..000000000 --- a/cmd/expandybird/test/templates/python_and_jinja_template.yaml +++ /dev/null @@ -1,9 +0,0 @@ -imports: ["python_and_jinja_template.jinja", "python_and_jinja_template.py"] - -resources: -- name: python_and_jinja_template_name - type: python_and_jinja_template.py - properties: - masterAddress: master-address - project: my-project - zone: my-zone diff --git a/cmd/expandybird/test/templates/python_and_jinja_template_result.yaml b/cmd/expandybird/test/templates/python_and_jinja_template_result.yaml deleted file mode 100644 index 3d23dcfbf..000000000 --- a/cmd/expandybird/test/templates/python_and_jinja_template_result.yaml +++ /dev/null @@ -1,35 +0,0 @@ -config: - resources: - - name: vm-created-by-cloud-config-master-address - properties: - disks: - - autoDelete: true - boot: true - deviceName: boot - initializeParams: - diskName: disk-created-by-cloud-config-master-address - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - type: PERSISTENT - machineType: https://www.googleapis.com/compute/v1/projects/my-project/zones/my-zone/machineTypes/f1-micro - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default - zone: my-zone - type: compute.v1.instance -layout: - resources: - - name: python_and_jinja_template_name - properties: - masterAddress: master-address - project: my-project - zone: my-zone - resources: - - name: python_and_jinja_template_jinja_name - properties: - deployment: master-address - project: my-project - zone: my-zone - resources: - - name: vm-created-by-cloud-config-master-address - type: compute.v1.instance - type: python_and_jinja_template.jinja - type: python_and_jinja_template.py diff --git a/cmd/expandybird/test/templates/python_bad_schema.py b/cmd/expandybird/test/templates/python_bad_schema.py deleted file mode 100644 index 0ebda1f6c..000000000 --- a/cmd/expandybird/test/templates/python_bad_schema.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Throws an exception.""" - -raise Exception diff --git a/cmd/expandybird/test/templates/python_bad_schema.py.schema b/cmd/expandybird/test/templates/python_bad_schema.py.schema deleted file mode 100644 index cf5d5d6d0..000000000 --- a/cmd/expandybird/test/templates/python_bad_schema.py.schema +++ /dev/null @@ -1,19 +0,0 @@ -info: - title: Schema with several errors - -imports: - -properties: - bad-type: - type: int - missing-cond: - type: string - exclusiveMaximum: 10 - odd-string: - type: string - not: - multipleOf: 2 - bad-enum: - type: string - enum: not a list - diff --git a/cmd/expandybird/test/templates/python_bad_schema.yaml b/cmd/expandybird/test/templates/python_bad_schema.yaml deleted file mode 100644 index d52024b6f..000000000 --- a/cmd/expandybird/test/templates/python_bad_schema.yaml +++ /dev/null @@ -1,9 +0,0 @@ -imports: -- path: "python_bad_schema.py" -- path: "python_bad_schema.py.schema" - -resources: -- name: python_bad_schema - type: python_bad_schema.py - properties: - innocent: true diff --git a/cmd/expandybird/test/templates/python_noparams.py b/cmd/expandybird/test/templates/python_noparams.py deleted file mode 100644 index 2542d71bb..000000000 --- a/cmd/expandybird/test/templates/python_noparams.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Constructs a VM.""" - - -def GenerateConfig(_): - """Generates config of a VM.""" - return """ -resources: -- name: myBackend - type: compute.v1.instance - properties: - machineSize: big -""" diff --git a/cmd/expandybird/test/templates/python_noparams.yaml b/cmd/expandybird/test/templates/python_noparams.yaml deleted file mode 100644 index b7abeaf55..000000000 --- a/cmd/expandybird/test/templates/python_noparams.yaml +++ /dev/null @@ -1,9 +0,0 @@ -imports: ["python_noparams.py"] - -resources: -- name: myFrontend - type: compute.v1.instance - properties: - machineSize: big -- name: python_noparams_name - type: python_noparams.py diff --git a/cmd/expandybird/test/templates/python_noparams_result.yaml b/cmd/expandybird/test/templates/python_noparams_result.yaml deleted file mode 100644 index 944d9018a..000000000 --- a/cmd/expandybird/test/templates/python_noparams_result.yaml +++ /dev/null @@ -1,19 +0,0 @@ -config: - resources: - - name: myFrontend - properties: - machineSize: big - type: compute.v1.instance - - name: myBackend - properties: - machineSize: big - type: compute.v1.instance -layout: - resources: - - name: myFrontend - type: compute.v1.instance - - name: python_noparams_name - resources: - - name: myBackend - type: compute.v1.instance - type: python_noparams.py diff --git a/cmd/expandybird/test/templates/python_schema.py b/cmd/expandybird/test/templates/python_schema.py deleted file mode 100644 index 2d935f7ad..000000000 --- a/cmd/expandybird/test/templates/python_schema.py +++ /dev/null @@ -1,57 +0,0 @@ -#% description: Creates a VM running a Salt master daemon in a Docker container. -#% parameters: -#% - name: masterAddress -#% type: string -#% description: Name of the Salt master VM. -#% required: true -#% - name: project -#% type: string -#% description: Name of the Cloud project. -#% required: true -#% - name: zone -#% type: string -#% description: Zone to create the resources in. -#% required: true - -"""Generates config for a VM running a SaltStack master. - -Just for fun this template is in Python, while the others in this -directory are in Jinja2. -""" - - -def GenerateConfig(evaluation_context): - return """ -resources: -- type: compute.v1.firewall - name: %(master)s-firewall - properties: - network: https://www.googleapis.com/compute/v1/projects/%(project)s/global/networks/default - sourceRanges: [ "0.0.0.0/0" ] - allowed: - - IPProtocol: tcp - ports: [ "4505", "4506" ] -- type: compute.v1.instance - name: %(master)s - properties: - zone: %(zone)s - machineType: https://www.googleapis.com/compute/v1/projects/%(project)s/zones/%(zone)s/machineTypes/f1-micro - disks: - - deviceName: boot - type: PERSISTENT - boot: true - autoDelete: true - initializeParams: - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/%(project)s/global/networks/default - accessConfigs: - - name: External NAT - type: ONE_TO_ONE_NAT - metadata: - items: - - key: startup-script - value: startup-script-value -""" % {"master": evaluation_context.properties["masterAddress"], - "project": evaluation_context.env["project"], - "zone": evaluation_context.properties["zone"]} diff --git a/cmd/expandybird/test/templates/python_schema.py.schema b/cmd/expandybird/test/templates/python_schema.py.schema deleted file mode 100644 index cb960bcfd..000000000 --- a/cmd/expandybird/test/templates/python_schema.py.schema +++ /dev/null @@ -1,14 +0,0 @@ -info: - title: A simple python template that has a schema. - -imports: - -properties: - masterAddress: - type: string - default: slave-address - description: masterAddress - zone: - type: string - default: not-test-zone - description: zone diff --git a/cmd/expandybird/test/templates/python_schema.yaml b/cmd/expandybird/test/templates/python_schema.yaml deleted file mode 100644 index 94f94d4e5..000000000 --- a/cmd/expandybird/test/templates/python_schema.yaml +++ /dev/null @@ -1,10 +0,0 @@ -imports: -- path: "python_schema.py" -- path: "python_schema.py.schema" - -resources: -- name: python_schema - type: python_schema.py - properties: - masterAddress: master-address - zone: my-zone diff --git a/cmd/expandybird/test/templates/python_schema_result.yaml b/cmd/expandybird/test/templates/python_schema_result.yaml deleted file mode 100644 index 2b75d97c2..000000000 --- a/cmd/expandybird/test/templates/python_schema_result.yaml +++ /dev/null @@ -1,46 +0,0 @@ -config: - resources: - - name: master-address-firewall - properties: - allowed: - - IPProtocol: tcp - ports: - - '4505' - - '4506' - network: https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default - sourceRanges: - - 0.0.0.0/0 - type: compute.v1.firewall - - name: master-address - properties: - disks: - - autoDelete: true - boot: true - deviceName: boot - initializeParams: - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - type: PERSISTENT - machineType: https://www.googleapis.com/compute/v1/projects/my-project/zones/my-zone/machineTypes/f1-micro - metadata: - items: - - key: startup-script - value: startup-script-value - networkInterfaces: - - accessConfigs: - - name: External NAT - type: ONE_TO_ONE_NAT - network: https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default - zone: my-zone - type: compute.v1.instance -layout: - resources: - - name: python_schema - properties: - masterAddress: master-address - zone: my-zone - resources: - - name: master-address-firewall - type: compute.v1.firewall - - name: master-address - type: compute.v1.instance - type: python_schema.py diff --git a/cmd/expandybird/test/templates/python_template.py b/cmd/expandybird/test/templates/python_template.py deleted file mode 100644 index 57ff7fe73..000000000 --- a/cmd/expandybird/test/templates/python_template.py +++ /dev/null @@ -1,57 +0,0 @@ -#% description: Creates a VM running a Salt master daemon in a Docker container. -#% parameters: -#% - name: masterAddress -#% type: string -#% description: Name of the Salt master VM. -#% required: true -#% - name: project -#% type: string -#% description: Name of the Cloud project. -#% required: true -#% - name: zone -#% type: string -#% description: Zone to create the resources in. -#% required: true - -"""Generates config for a VM running a SaltStack master. - -Just for fun this template is in Python, while the others in this -directory are in Jinja2. -""" - - -def GenerateConfig(evaluation_context): - return """ -resources: -- type: compute.v1.firewall - name: %(master)s-firewall - properties: - network: https://www.googleapis.com/compute/v1/projects/%(project)s/global/networks/default - sourceRanges: [ "0.0.0.0/0" ] - allowed: - - IPProtocol: tcp - ports: [ "4505", "4506" ] -- type: compute.v1.instance - name: %(master)s - properties: - zone: %(zone)s - machineType: https://www.googleapis.com/compute/v1/projects/%(project)s/zones/%(zone)s/machineTypes/f1-micro - disks: - - deviceName: boot - type: PERSISTENT - boot: true - autoDelete: true - initializeParams: - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/%(project)s/global/networks/default - accessConfigs: - - name: External NAT - type: ONE_TO_ONE_NAT - metadata: - items: - - key: startup-script - value: startup-script-value -""" % {"master": evaluation_context.properties["masterAddress"], - "project": evaluation_context.properties["project"], - "zone": evaluation_context.properties["zone"]} diff --git a/cmd/expandybird/test/templates/python_template.yaml b/cmd/expandybird/test/templates/python_template.yaml deleted file mode 100644 index 08ae8b8bb..000000000 --- a/cmd/expandybird/test/templates/python_template.yaml +++ /dev/null @@ -1,9 +0,0 @@ -imports: ["python_template.py"] - -resources: -- name: python_template_name - type: python_template.py - properties: - masterAddress: master-address - project: my-project - zone: my-zone diff --git a/cmd/expandybird/test/templates/python_template_result.yaml b/cmd/expandybird/test/templates/python_template_result.yaml deleted file mode 100644 index 1b82f3fed..000000000 --- a/cmd/expandybird/test/templates/python_template_result.yaml +++ /dev/null @@ -1,47 +0,0 @@ -config: - resources: - - name: master-address-firewall - properties: - allowed: - - IPProtocol: tcp - ports: - - '4505' - - '4506' - network: https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default - sourceRanges: - - 0.0.0.0/0 - type: compute.v1.firewall - - name: master-address - properties: - disks: - - autoDelete: true - boot: true - deviceName: boot - initializeParams: - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - type: PERSISTENT - machineType: https://www.googleapis.com/compute/v1/projects/my-project/zones/my-zone/machineTypes/f1-micro - metadata: - items: - - key: startup-script - value: startup-script-value - networkInterfaces: - - accessConfigs: - - name: External NAT - type: ONE_TO_ONE_NAT - network: https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default - zone: my-zone - type: compute.v1.instance -layout: - resources: - - name: python_template_name - properties: - masterAddress: master-address - project: my-project - zone: my-zone - resources: - - name: master-address-firewall - type: compute.v1.firewall - - name: master-address - type: compute.v1.instance - type: python_template.py diff --git a/cmd/expandybird/test/templates/python_template_with_env.py b/cmd/expandybird/test/templates/python_template_with_env.py deleted file mode 100644 index 2d935f7ad..000000000 --- a/cmd/expandybird/test/templates/python_template_with_env.py +++ /dev/null @@ -1,57 +0,0 @@ -#% description: Creates a VM running a Salt master daemon in a Docker container. -#% parameters: -#% - name: masterAddress -#% type: string -#% description: Name of the Salt master VM. -#% required: true -#% - name: project -#% type: string -#% description: Name of the Cloud project. -#% required: true -#% - name: zone -#% type: string -#% description: Zone to create the resources in. -#% required: true - -"""Generates config for a VM running a SaltStack master. - -Just for fun this template is in Python, while the others in this -directory are in Jinja2. -""" - - -def GenerateConfig(evaluation_context): - return """ -resources: -- type: compute.v1.firewall - name: %(master)s-firewall - properties: - network: https://www.googleapis.com/compute/v1/projects/%(project)s/global/networks/default - sourceRanges: [ "0.0.0.0/0" ] - allowed: - - IPProtocol: tcp - ports: [ "4505", "4506" ] -- type: compute.v1.instance - name: %(master)s - properties: - zone: %(zone)s - machineType: https://www.googleapis.com/compute/v1/projects/%(project)s/zones/%(zone)s/machineTypes/f1-micro - disks: - - deviceName: boot - type: PERSISTENT - boot: true - autoDelete: true - initializeParams: - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/%(project)s/global/networks/default - accessConfigs: - - name: External NAT - type: ONE_TO_ONE_NAT - metadata: - items: - - key: startup-script - value: startup-script-value -""" % {"master": evaluation_context.properties["masterAddress"], - "project": evaluation_context.env["project"], - "zone": evaluation_context.properties["zone"]} diff --git a/cmd/expandybird/test/templates/python_template_with_env.yaml b/cmd/expandybird/test/templates/python_template_with_env.yaml deleted file mode 100644 index d3bbc26c7..000000000 --- a/cmd/expandybird/test/templates/python_template_with_env.yaml +++ /dev/null @@ -1,8 +0,0 @@ -imports: ["python_template_with_env.py"] - -resources: -- name: python_template_with_env_name - type: python_template_with_env.py - properties: - masterAddress: master-address - zone: my-zone diff --git a/cmd/expandybird/test/templates/python_template_with_env_result.yaml b/cmd/expandybird/test/templates/python_template_with_env_result.yaml deleted file mode 100644 index 027732c8f..000000000 --- a/cmd/expandybird/test/templates/python_template_with_env_result.yaml +++ /dev/null @@ -1,46 +0,0 @@ -config: - resources: - - name: master-address-firewall - properties: - allowed: - - IPProtocol: tcp - ports: - - '4505' - - '4506' - network: https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default - sourceRanges: - - 0.0.0.0/0 - type: compute.v1.firewall - - name: master-address - properties: - disks: - - autoDelete: true - boot: true - deviceName: boot - initializeParams: - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - type: PERSISTENT - machineType: https://www.googleapis.com/compute/v1/projects/my-project/zones/my-zone/machineTypes/f1-micro - metadata: - items: - - key: startup-script - value: startup-script-value - networkInterfaces: - - accessConfigs: - - name: External NAT - type: ONE_TO_ONE_NAT - network: https://www.googleapis.com/compute/v1/projects/my-project/global/networks/default - zone: my-zone - type: compute.v1.instance -layout: - resources: - - name: python_template_with_env_name - properties: - masterAddress: master-address - zone: my-zone - resources: - - name: master-address-firewall - type: compute.v1.firewall - - name: master-address - type: compute.v1.instance - type: python_template_with_env.py diff --git a/cmd/expandybird/test/templates/python_template_with_import.py b/cmd/expandybird/test/templates/python_template_with_import.py deleted file mode 100644 index ba362233a..000000000 --- a/cmd/expandybird/test/templates/python_template_with_import.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Constructs a VM.""" -import json - -import helpers.common -import helpers.extra.common2 - - -def GenerateConfig(_): - """Generates config of a VM.""" - return """ -resources: -- name: %s - type: compute.v1.instance - properties: - machineSize: %s -""" % (helpers.common.GenerateMachineName( - json.dumps('myFrontend').strip('"'), 'prod'), - helpers.extra.common2.GenerateMachineSize()) diff --git a/cmd/expandybird/test/templates/python_template_with_import.yaml b/cmd/expandybird/test/templates/python_template_with_import.yaml deleted file mode 100644 index 73cce18f1..000000000 --- a/cmd/expandybird/test/templates/python_template_with_import.yaml +++ /dev/null @@ -1,5 +0,0 @@ -imports: ["python_template_with_import.py", "helpers/common.py", "helpers/common2.py", "helpers/__init__.py"] - -resources: -- name: python_template_with_import_name - type: python_template_with_import.py diff --git a/cmd/expandybird/test/templates/python_template_with_import_result.yaml b/cmd/expandybird/test/templates/python_template_with_import_result.yaml deleted file mode 100644 index d8e283308..000000000 --- a/cmd/expandybird/test/templates/python_template_with_import_result.yaml +++ /dev/null @@ -1,13 +0,0 @@ -config: - resources: - - name: myFrontend-prod - properties: - machineSize: big - type: compute.v1.instance -layout: - resources: - - name: python_template_with_import_name - resources: - - name: myFrontend-prod - type: compute.v1.instance - type: python_template_with_import.py diff --git a/cmd/expandybird/test/templates/python_template_with_inlinedfile.py b/cmd/expandybird/test/templates/python_template_with_inlinedfile.py deleted file mode 100644 index 2ea1fb8ed..000000000 --- a/cmd/expandybird/test/templates/python_template_with_inlinedfile.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Constructs a VM.""" - -# Verify that both ways of hierarchical imports work. -from helpers import common -import helpers.extra.common2 - - -def GenerateConfig(evaluation_context): - """Generates config of a VM.""" - return """ -resources: -- name: %s - type: compute.v1.instance - properties: - description: %s - machineSize: %s -""" % (common.GenerateMachineName("myFrontend", "prod"), - evaluation_context.imports[ - evaluation_context.properties["description-file"]], - helpers.extra.common2.GenerateMachineSize()) diff --git a/cmd/expandybird/test/templates/python_template_with_inlinedfile.yaml b/cmd/expandybird/test/templates/python_template_with_inlinedfile.yaml deleted file mode 100644 index 8c1d8c38c..000000000 --- a/cmd/expandybird/test/templates/python_template_with_inlinedfile.yaml +++ /dev/null @@ -1,7 +0,0 @@ -imports: ["python_template_with_inlinedfile.py", "helpers/common.py", "helpers/common2.py", "helpers/__init__.py", "description_text.txt"] - -resources: -- name: python_template_with_inlinedfile_name - type: python_template_with_inlinedfile.py - properties: - description-file: description_text.txt diff --git a/cmd/expandybird/test/templates/python_template_with_inlinedfile_result.yaml b/cmd/expandybird/test/templates/python_template_with_inlinedfile_result.yaml deleted file mode 100644 index 92706a0fd..000000000 --- a/cmd/expandybird/test/templates/python_template_with_inlinedfile_result.yaml +++ /dev/null @@ -1,21 +0,0 @@ -config: - resources: - - name: myFrontend-prod - properties: - description: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim - veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo - consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse - cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat - non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - machineSize: big - type: compute.v1.instance -layout: - resources: - - name: python_template_with_inlinedfile_name - properties: - description-file: description_text.txt - resources: - - name: myFrontend-prod - type: compute.v1.instance - type: python_template_with_inlinedfile.py diff --git a/cmd/expandybird/test/templates/python_with_exception.py b/cmd/expandybird/test/templates/python_with_exception.py deleted file mode 100644 index bf982a82f..000000000 --- a/cmd/expandybird/test/templates/python_with_exception.py +++ /dev/null @@ -1,7 +0,0 @@ -"""A python script that raise exceptions. - -""" - - -def GenerateConfig(unused_context): - raise NameError('No file found') diff --git a/cmd/expandybird/test/templates/python_with_exception.yaml b/cmd/expandybird/test/templates/python_with_exception.yaml deleted file mode 100644 index fadc23970..000000000 --- a/cmd/expandybird/test/templates/python_with_exception.yaml +++ /dev/null @@ -1,9 +0,0 @@ -imports: ["python_with_exception.py"] - -resources: -- name: python_with_exception_name - type: python_with_exception.py - properties: - masterAddress: master-address - project: my-project - zone: my-zone diff --git a/cmd/expandybird/test/templates/simple.yaml b/cmd/expandybird/test/templates/simple.yaml deleted file mode 100644 index 5065fa564..000000000 --- a/cmd/expandybird/test/templates/simple.yaml +++ /dev/null @@ -1,18 +0,0 @@ -resources: -- type: compute.v1.instance - name: vm-created-by-cloud-config-{{ params["deployment"] }} - properties: - zone: test-zone - machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro - disks: - - deviceName: boot - type: PERSISTENT - boot: true - autoDelete: true - initializeParams: - diskName: disk-created-by-cloud-config-test-deployment - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default - - diff --git a/cmd/expandybird/test/templates/simple_result.yaml b/cmd/expandybird/test/templates/simple_result.yaml deleted file mode 100644 index 4353c7194..000000000 --- a/cmd/expandybird/test/templates/simple_result.yaml +++ /dev/null @@ -1,21 +0,0 @@ -config: - resources: - - name: vm-created-by-cloud-config-{{ params["deployment"] }} - properties: - disks: - - autoDelete: true - boot: true - deviceName: boot - initializeParams: - diskName: disk-created-by-cloud-config-test-deployment - sourceImage: https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-7-wheezy-v20140619 - type: PERSISTENT - machineType: https://www.googleapis.com/compute/v1/projects/test-project/zones/test-zone/machineTypes/f1-micro - networkInterfaces: - - network: https://www.googleapis.com/compute/v1/projects/test-project/global/networks/default - zone: test-zone - type: compute.v1.instance -layout: - resources: - - name: vm-created-by-cloud-config-{{ params["deployment"] }} - type: compute.v1.instance diff --git a/cmd/expandybird/test/templates/use_helper.jinja b/cmd/expandybird/test/templates/use_helper.jinja deleted file mode 100644 index c748a8ae4..000000000 --- a/cmd/expandybird/test/templates/use_helper.jinja +++ /dev/null @@ -1,7 +0,0 @@ -resources: -- name: use-helper - type: foo - properties: - test: {{ properties['barfoo'] }} -- name: use-helper-helper - type: helper.jinja diff --git a/cmd/expandybird/test/templates/use_helper.jinja.schema b/cmd/expandybird/test/templates/use_helper.jinja.schema deleted file mode 100644 index 69986603d..000000000 --- a/cmd/expandybird/test/templates/use_helper.jinja.schema +++ /dev/null @@ -1,4 +0,0 @@ -properties: - barfoo: - type: string - default: Use this schema also diff --git a/cmd/expandybird/test/templates/use_helper.yaml b/cmd/expandybird/test/templates/use_helper.yaml deleted file mode 100644 index 818b0d284..000000000 --- a/cmd/expandybird/test/templates/use_helper.yaml +++ /dev/null @@ -1,3 +0,0 @@ -resources: -- name: use-helper - type: use_helper.jinja diff --git a/cmd/expandybird/test/templates/use_helper_result.yaml b/cmd/expandybird/test/templates/use_helper_result.yaml deleted file mode 100644 index 51c17afc8..000000000 --- a/cmd/expandybird/test/templates/use_helper_result.yaml +++ /dev/null @@ -1,26 +0,0 @@ -config: - resources: - - name: use-helper - properties: - test: Use this schema also - type: foo - - name: helper - properties: - test: Use this schema - type: bar -layout: - resources: - - name: use-helper - properties: - barfoo: Use this schema also - resources: - - name: use-helper - type: foo - - name: use-helper-helper - properties: - foobar: Use this schema - resources: - - name: helper - type: bar - type: helper.jinja - type: use_helper.jinja diff --git a/cmd/goexpander/expander/expander.go b/cmd/goexpander/expander/expander.go deleted file mode 100644 index ee7c513f3..000000000 --- a/cmd/goexpander/expander/expander.go +++ /dev/null @@ -1,146 +0,0 @@ -/* -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. -*/ - -package expander - -import ( - "bytes" - "fmt" - "io" - "strings" - "text/template" - - "github.com/Masterminds/sprig" - "github.com/cloudfoundry-incubator/candiedyaml" - "github.com/ghodss/yaml" - "github.com/kubernetes/helm/pkg/expansion" -) - -// parseYAMLStream takes an encoded YAML stream and turns it into a slice of JSON-marshalable -// objects, one for each document in the stream. -func parseYAMLStream(in io.Reader) ([]interface{}, error) { - // Use candiedyaml because it's the only one that supports streams. - decoder := candiedyaml.NewDecoder(in) - var document interface{} - stream := []interface{}{} - for { - err := decoder.Decode(&document) - if err != nil { - if strings.Contains(err.Error(), "Expected document start at line") { - return stream, nil - } - return nil, err - } - // Now it's held in document but we have to do a bit of a dance to get it in a form that can - // be marshaled as JSON for our API response. The fundamental problem is that YAML is a - // superset of JSON in that it can represent non-string keys, and full IEEE floating point - // values (NaN etc). JSON only allows string keys and its definition of a number is based - // around a sequence of digits. - - // Kubernetes does not make use of these features, as it uses YAML as just "pretty JSON". - // Consequently this does not affect Helm either. However, both candiedyaml and go-yaml - // return types that are too wide for JSON marshalling (i.e. map[interface{}]interface{} - // instead of map[string]interface{}), so we have to do some explicit conversion. Luckily, - // ghodss/yaml has code to help with this, since decoding from YAML to JSON-marshalable - // values is exactly the problem that it was designed to solve. - - // 1) Marshal it back to YAML string. - yamlBytes, err := candiedyaml.Marshal(document) - if err != nil { - return nil, err - } - - // 2) Use ghodss/yaml to unmarshal that string into JSON-compatible data structures. - var jsonObj interface{} - if err := yaml.Unmarshal(yamlBytes, &jsonObj); err != nil { - return nil, err - } - - // Now it's suitable for embedding in an API response. - stream = append(stream, jsonObj) - } -} - -type expander struct { -} - -// NewExpander returns an Go Templating expander. -func NewExpander() expansion.Expander { - return &expander{} -} - -// ExpandChart resolves the given files to a sequence of JSON-marshalable values. -func (e *expander) ExpandChart(request *expansion.ServiceRequest) (*expansion.ServiceResponse, error) { - - err := expansion.ValidateRequest(request) - if err != nil { - return nil, err - } - - request, err = expansion.ValidateProperties(request) - if err != nil { - return nil, err - } - - chartInv := request.ChartInvocation - chartMembers := request.Chart.Members - - resources := []interface{}{} - for _, file := range chartMembers { - name := file.Path - content := file.Content - tmpl := template.New(name).Funcs(sprig.HermeticTxtFuncMap()) - - for _, otherFile := range chartMembers { - otherName := otherFile.Path - otherContent := otherFile.Content - if name == otherName { - continue - } - _, err := tmpl.Parse(string(otherContent)) - if err != nil { - return nil, err - } - } - - // Have to put something in that resolves non-empty or Go templates get confused. - _, err := tmpl.Parse("# Content begins now") - if err != nil { - return nil, err - } - - tmpl, err = tmpl.Parse(string(content)) - if err != nil { - return nil, err - } - - generated := bytes.NewBuffer(nil) - if err := tmpl.ExecuteTemplate(generated, name, chartInv.Properties); err != nil { - return nil, err - } - - stream, err := parseYAMLStream(generated) - if err != nil { - return nil, fmt.Errorf("%s\nContent:\n%s", err.Error(), generated) - } - - for _, doc := range stream { - resources = append(resources, doc) - } - } - - return &expansion.ServiceResponse{Resources: resources}, nil -} diff --git a/cmd/goexpander/expander/expander_test.go b/cmd/goexpander/expander/expander_test.go deleted file mode 100644 index 95ce37c9d..000000000 --- a/cmd/goexpander/expander/expander_test.go +++ /dev/null @@ -1,262 +0,0 @@ -/* -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. -*/ - -package expander - -import ( - "fmt" - "reflect" - "strings" - "testing" - - "github.com/kubernetes/helm/pkg/chart" - "github.com/kubernetes/helm/pkg/common" - "github.com/kubernetes/helm/pkg/expansion" -) - -// content provides an easy way to provide file content verbatim in tests. -func content(lines []string) []byte { - return []byte(strings.Join(lines, "\n") + "\n") -} - -func testExpansion(t *testing.T, req *expansion.ServiceRequest, - expResponse *expansion.ServiceResponse, expError string) { - backend := NewExpander() - response, err := backend.ExpandChart(req) - if err != nil { - message := err.Error() - if expResponse != nil || !strings.Contains(message, expError) { - t.Fatalf("unexpected error: %s\n", message) - } - } else { - if expResponse == nil { - t.Fatalf("expected error did not occur: %s\n", expError) - } - if !reflect.DeepEqual(response, expResponse) { - message := fmt.Sprintf( - "want:\n%s\nhave:\n%s\n", expResponse, response) - t.Fatalf("output mismatch:\n%s\n", message) - } - } -} - -var goExpander = &chart.Expander{ - Name: "GoTemplating", - Entrypoint: "templates/main.py", -} - -func TestEmpty(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: "gs://kubernetes-charts-testing/Test-1.2.3.tgz", - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: "Test", - Expander: goExpander, - }, - }, - }, - &expansion.ServiceResponse{ - Resources: []interface{}{}, - }, - "", // Error - ) -} - -func TestSingle(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: "gs://kubernetes-charts-testing/Test-1.2.3.tgz", - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: "Test", - Expander: goExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.yaml", - Content: content([]string{ - "name: foo", - "type: bar", - }), - }, - }, - }, - }, - &expansion.ServiceResponse{ - Resources: []interface{}{ - map[string]interface{}{ - "name": "foo", - "type": "bar", - }, - }, - }, - "", // Error - ) -} - -func TestProperties(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: "gs://kubernetes-charts-testing/Test-1.2.3.tgz", - Properties: map[string]interface{}{ - "prop1": 3.0, - "prop2": "foo", - }, - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: "Test", - Expander: goExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/main.yaml", - Content: content([]string{ - "name: foo", - "type: {{ .prop2 }}", - "properties:", - " something: {{ .prop1 }}", - }), - }, - }, - }, - }, - &expansion.ServiceResponse{ - Resources: []interface{}{ - map[string]interface{}{ - "name": "foo", - "properties": map[string]interface{}{ - "something": 3.0, - }, - "type": "foo", - }, - }, - }, - "", // Error - ) -} - -func TestComplex(t *testing.T) { - testExpansion( - t, - &expansion.ServiceRequest{ - ChartInvocation: &common.Resource{ - Name: "test_invocation", - Type: "gs://kubernetes-charts-testing/Test-1.2.3.tgz", - Properties: map[string]interface{}{ - "DatabaseName": "mydb", - "NumRepicas": 3, - }, - }, - Chart: &chart.Content{ - Chartfile: &chart.Chartfile{ - Name: "Test", - Expander: goExpander, - }, - Members: []*chart.Member{ - { - Path: "templates/bar.tmpl", - Content: content([]string{ - `{{ template "banana" . }}`, - }), - }, - { - Path: "templates/base.tmpl", - Content: content([]string{ - `{{ define "apple" }}`, - `name: Abby`, - `kind: Apple`, - `dbname: {{default "whatdb" .DatabaseName}}`, - `{{ end }}`, - ``, - `{{ define "banana" }}`, - `name: Bobby`, - `kind: Banana`, - `dbname: {{default "whatdb" .DatabaseName}}`, - `{{ end }}`, - }), - }, - { - Path: "templates/foo.tmpl", - Content: content([]string{ - `---`, - `foo:`, - ` bar: baz`, - `---`, - `{{ template "apple" . }}`, - `---`, - `{{ template "apple" . }}`, - `...`, - }), - }, - { - Path: "templates/docs.txt", - Content: content([]string{ - `{{/*`, - `File contains only a comment.`, - `Suitable for documentation within templates/`, - `*/}}`, - }), - }, - { - Path: "templates/docs2.txt", - Content: content([]string{ - `# File contains only a comment.`, - `# Suitable for documentation within templates/`, - }), - }, - }, - }, - }, - &expansion.ServiceResponse{ - Resources: []interface{}{ - map[string]interface{}{ - "name": "Bobby", - "kind": "Banana", - "dbname": "mydb", - }, - map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": "baz", - }, - }, - map[string]interface{}{ - "name": "Abby", - "kind": "Apple", - "dbname": "mydb", - }, - map[string]interface{}{ - "name": "Abby", - "kind": "Apple", - "dbname": "mydb", - }, - }, - }, - "", // Error - ) -} diff --git a/cmd/goexpander/main.go b/cmd/goexpander/main.go deleted file mode 100644 index ded71a35c..000000000 --- a/cmd/goexpander/main.go +++ /dev/null @@ -1,41 +0,0 @@ -/* -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. -*/ - -package main - -import ( - "github.com/kubernetes/helm/cmd/goexpander/expander" - "github.com/kubernetes/helm/pkg/expansion" - "github.com/kubernetes/helm/pkg/version" - - "flag" - "log" -) - -// interface that we are going to listen on -var address = flag.String("address", "", "Interface to listen on") - -// port that we are going to listen on -var port = flag.Int("port", 8080, "Port to listen on") - -func main() { - flag.Parse() - backend := expander.NewExpander() - service := expansion.NewService(*address, *port, backend) - log.Printf("Version: %s", version.Version) - log.Printf("Listening on http://%s:%d/expand", *address, *port) - log.Fatal(service.ListenAndServe()) -} diff --git a/cmd/helm/Makefile b/cmd/helm/Makefile deleted file mode 100644 index 30cfef5a5..000000000 --- a/cmd/helm/Makefile +++ /dev/null @@ -1,39 +0,0 @@ -# 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. - -SHELL := /bin/bash - -GOLANG_CROSSPLATFORMS := darwin/386 darwin/amd64 freebsd/386 freebsd/amd64 freebsd/arm linux/386 linux/amd64 linux/arm windows/386 windows/amd64 - -ROOT_DIR := $(abspath ./../..) -BIN_DIR := $(ROOT_DIR)/bin - -DEFAULT_BUCKET := gs://get-helm -STORAGE_BUCKET ?= $(DEFAULT_BUCKET) - -DEFAULT_TAG := git-$(shell git rev-parse --short HEAD) -TAG ?= $(DEFAULT_TAG) - -all: push - -push: - for platform in ${GOLANG_CROSSPLATFORMS}; do \ - echo $$platform; \ - PLATFORM=$${platform%/*} && ARCH=$${platform##*/} && \ - BINARY=$${PLATFORM}-$${ARCH} && \ - ZIP=${TAG}-helm-$${BINARY}.zip && \ - zip -j $${ZIP} ${BIN_DIR}/$${BINARY}/helm* && \ - gsutil cp $${ZIP} ${STORAGE_BUCKET} && \ - rm $${ZIP} ; \ - done diff --git a/cmd/helm/chart.go b/cmd/helm/chart.go deleted file mode 100644 index 91d4e7e85..000000000 --- a/cmd/helm/chart.go +++ /dev/null @@ -1,113 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package main - -import ( - "errors" - "fmt" - "os" - "path/filepath" - - "github.com/codegangsta/cli" - "github.com/kubernetes/helm/pkg/chart" - "github.com/kubernetes/helm/pkg/format" -) - -func init() { - addCommands(chartCommands()) -} - -const chartDesc = `A Chart is a package that can be installed and managed by Helm. - - The 'helm chart' subcommands provide tools for working with Helm charts. To - get started creating your own chart, use 'helm chart create NAME'. - - For details, use 'helm chart CMD -h'. -` - -func chartCommands() cli.Command { - return cli.Command{ - // Names following form prescribed here: http://is.gd/QUSEOF - Name: "chart", - Usage: "Perform chart-centered operations.", - Description: chartDesc, - Subcommands: []cli.Command{ - { - Name: "create", - Usage: "Create a new chart directory and set up base files and directories.", - ArgsUsage: "CHARTNAME", - Action: func(c *cli.Context) { run(c, createChart) }, - }, - { - 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 createChart(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 - -} - -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) - } - - fname, err := packDir(dir) - if err != nil { - return err - } - format.Msg(fname) - return nil -} - -func packDir(dir string) (string, error) { - c, err := chart.LoadDir(dir) - if err != nil { - return "", fmt.Errorf("Failed to load %s: %s", dir, err) - } - - return chart.Save(c, ".") -} diff --git a/cmd/helm/chart_upload.go b/cmd/helm/chart_upload.go deleted file mode 100644 index 6077675d3..000000000 --- a/cmd/helm/chart_upload.go +++ /dev/null @@ -1,115 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package main - -import ( - "errors" - "fmt" - "os" - "regexp" - "strings" - - "github.com/aokoli/goutils" - "github.com/codegangsta/cli" - "github.com/kubernetes/helm/pkg/chart" - "github.com/kubernetes/helm/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 := NewClient(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 -} diff --git a/cmd/helm/deploy.go b/cmd/helm/deploy.go deleted file mode 100644 index 5db207bc6..000000000 --- a/cmd/helm/deploy.go +++ /dev/null @@ -1,111 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package main - -import ( - "fmt" - "io/ioutil" - "os" - - "github.com/codegangsta/cli" - "github.com/kubernetes/helm/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,n", - 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 { - - res := &common.Resource{ - // By default - Properties: map[string]interface{}{}, - } - - if c.String("config") != "" { - // If there is a configuration file, use it. - err := loadConfig(c.String("config"), &res.Properties) - if err != nil { - return err - } - } - - args := c.Args() - if len(args) == 0 { - return fmt.Errorf("Need chart name on commandline") - } - res.Type = args[0] - - if name := c.String("name"); len(name) > 0 { - res.Name = name - } else { - return fmt.Errorf("Need deployed name on commandline") - } - - 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 { - res.Properties[n] = v - } - } - - return NewClient(c).PostDeployment(res) -} - -// isLocalChart returns true if the given path can be statted. -func isLocalChart(path string) bool { - _, err := os.Stat(path) - return err == nil -} - -// loadConfig loads chart arguments into c -func loadConfig(filename string, dest *map[string]interface{}) error { - data, err := ioutil.ReadFile(filename) - if err != nil { - return err - } - return yaml.Unmarshal(data, dest) -} diff --git a/cmd/helm/deployment.go b/cmd/helm/deployment.go deleted file mode 100644 index c17b68023..000000000 --- a/cmd/helm/deployment.go +++ /dev/null @@ -1,187 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package main - -import ( - "errors" - "os" - "regexp" - "text/template" - - "github.com/codegangsta/cli" - "github.com/kubernetes/helm/pkg/format" -) - -var ( - errMissingDeploymentArg = errors.New("First argument, deployment name, is required. Try 'helm get --help'") - errTooManyArgs = errors.New("Too many arguments provided. Try 'helm dep describe [DEPLOYMENT]'") -) - -const deploymentDesc = `A deployment is an instance of a Chart running in the cluster. - - Deployments have a name, a chart, and possibly a set of properites. The deployment - commands provide tools for managing deployments. - - To deploy a new chart, use the top-level 'helm deploy' command. From there, - the 'helm deployment' commands may be used to work with the deployed - application. - - For more help, use 'helm deployment CMD -h'.` - -const defaultShowFormat = `Name: {{.Name}} -Status: {{.State.Status}} -{{with .State.Errors}}Errors: -{{range .}} {{.}}{{end}} -{{end}}` - -const defaultShowResourceFormat = `Name: {{.Name}} -Type: {{.Type}} -Status: {{.State.Status}} -{{with .State.Errors}}Errors: -{{range .}} {{.}}{{end}} -{{end}}` - -func init() { - addCommands(deploymentCommands()) -} - -func deploymentCommands() cli.Command { - return cli.Command{ - // Names following form prescribed here: http://is.gd/QUSEOF - Name: "deployment", - Aliases: []string{"dep"}, - Usage: "Perform deployment-centered operations.", - Description: deploymentDesc, - Subcommands: []cli.Command{ - { - Name: "remove", - Aliases: []string{"rm"}, - Usage: "Deletes the named deployment(s).", - ArgsUsage: "DEPLOYMENT [DEPLOYMENT [...]]", - Action: func(c *cli.Context) { run(c, deleteDeployment) }, - }, - { - Name: "describe", - Usage: "Describes the kubernetes resources for the named deployment(s).", - ArgsUsage: "DEPLOYMENT", - Action: func(c *cli.Context) { run(c, describeDeployment) }, - }, - { - Name: "show", - Aliases: []string{"info"}, - Usage: "Provide details about this deployment.", - ArgsUsage: "", - Action: func(c *cli.Context) { run(c, showDeployment) }, - }, - { - Name: "list", - Aliases: []string{"ls"}, - Usage: "list all deployments, or filter by an optional regular expression.", - ArgsUsage: "REGEXP", - Action: func(c *cli.Context) { run(c, listDeployments) }, - }, - }, - } -} - -func listDeployments(c *cli.Context) error { - list, err := NewClient(c).ListDeployments() - if err != nil { - return err - } - args := c.Args() - if len(args) >= 1 { - pattern := args[0] - r, err := regexp.Compile(pattern) - if err != nil { - return err - } - - newlist := []string{} - for _, i := range list { - if r.MatchString(i) { - newlist = append(newlist, i) - } - } - list = newlist - } - - if len(list) == 0 { - return errors.New("no deployments found") - } - - format.List(list) - return nil -} - -func deleteDeployment(c *cli.Context) error { - args := c.Args() - if len(args) < 1 { - return errMissingDeploymentArg - } - for _, name := range args { - deployment, err := NewClient(c).DeleteDeployment(name) - if err != nil { - return err - } - format.Info("Deleted %q at %s", name, deployment.DeletedAt) - } - return nil -} - -func describeDeployment(c *cli.Context) error { - args := c.Args() - if len(args) < 1 { - return errMissingDeploymentArg - } - if len(args) > 1 { - return errTooManyArgs - } - name := args[0] - manifest, err := NewClient(c).DescribeDeployment(name) - if err != nil { - return err - } - - if manifest.ExpandedConfig == nil { - return errors.New("No ExpandedConfig found for: " + name) - } - - for _, resource := range manifest.ExpandedConfig.Resources { - tmpl := template.Must(template.New("showresource").Parse(defaultShowResourceFormat)) - err = tmpl.Execute(os.Stdout, resource) - if err != nil { - return err - } - - } - return nil -} - -func showDeployment(c *cli.Context) error { - args := c.Args() - if len(args) < 1 { - return errMissingDeploymentArg - } - name := args[0] - deployment, err := NewClient(c).GetDeployment(name) - if err != nil { - return err - } - tmpl := template.Must(template.New("show").Parse(defaultShowFormat)) - return tmpl.Execute(os.Stdout, deployment) -} diff --git a/cmd/helm/deployment_test.go b/cmd/helm/deployment_test.go deleted file mode 100644 index 581cbc6aa..000000000 --- a/cmd/helm/deployment_test.go +++ /dev/null @@ -1,107 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package main - -import ( - "encoding/json" - "net/http" - "testing" - - "github.com/kubernetes/helm/pkg/common" -) - -type pathAndResponse struct { - path string - resp interface{} -} - -func TestDeployment(t *testing.T) { - var deploymentTestCases = []struct { - args []string - resp []pathAndResponse - expected string - }{ - { - []string{"deployment", "show", "guestbook.yaml"}, - []pathAndResponse{{"/deployments/", &common.Deployment{ - Name: "guestbook.yaml", - State: &common.DeploymentState{Status: common.CreatedStatus}, - }}}, - "Name: guestbook.yaml\nStatus: Created\n", - }, - { - []string{"deployment", "show", "guestbook.yaml"}, - []pathAndResponse{{"/deployments/", &common.Deployment{ - Name: "guestbook.yaml", - State: &common.DeploymentState{ - Status: common.FailedStatus, - Errors: []string{"error message"}, - }, - }}}, - "Name: guestbook.yaml\nStatus: Failed\nErrors:\n error message\n", - }, - { - []string{"deployment", "list"}, - []pathAndResponse{{"/deployments/", []string{"guestbook.yaml"}}}, - "guestbook.yaml\n", - }, - { - []string{"deployment", "describe", "guestbook.yaml"}, - []pathAndResponse{{ - "/deployments/guestbook.yaml", - &common.Deployment{Name: "guestbook.yaml", - State: &common.DeploymentState{Status: common.CreatedStatus}, - LatestManifest: "manifestxyz", - }}, - {"/deployments/guestbook.yaml/manifests/manifestxyz", &common.Manifest{ - Deployment: "guestbook.yaml", - Name: "manifestxyz", - ExpandedConfig: &common.Configuration{ - Resources: []*common.Resource{ - {Name: "fe-rc", Type: "ReplicationController", State: &common.ResourceState{Status: common.Created}}, - {Name: "fe", Type: "Service", State: &common.ResourceState{Status: common.Created}}, - {Name: "be-rc", Type: "ReplicationController", State: &common.ResourceState{Status: common.Created}}, - {Name: "be", Type: "Service", State: &common.ResourceState{Status: common.Created}}, - }, - }, - }}}, - "Name: fe-rc\nType: ReplicationController\nStatus: Created\n" + - "Name: fe\nType: Service\nStatus: Created\n" + - "Name: be-rc\nType: ReplicationController\nStatus: Created\n" + - "Name: be\nType: Service\nStatus: Created\n", - }, - } - - for _, tc := range deploymentTestCases { - th := testHelm(t) - for _, pathAndResponse := range tc.resp { - var response = pathAndResponse.resp - th.mux.HandleFunc(pathAndResponse.path, func(w http.ResponseWriter, r *http.Request) { - data, err := json.Marshal(response) - th.must(err) - w.Write(data) - }) - } - - th.run(tc.args...) - - if tc.expected != th.output { - t.Errorf("Expected %v got %v", tc.expected, th.output) - } - th.cleanup() - } -} diff --git a/cmd/helm/doctor.go b/cmd/helm/doctor.go deleted file mode 100644 index 5fb8c1e43..000000000 --- a/cmd/helm/doctor.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package main - -import ( - "github.com/codegangsta/cli" - "github.com/kubernetes/helm/pkg/client" - "github.com/kubernetes/helm/pkg/format" - "github.com/kubernetes/helm/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 client.IsInstalled(runner) { - format.Success("You have everything you need. Go forth my friend!") - } else { - format.Warning("Looks like you don't have the helm server-side components installed.\nRun: `helm server install`") - } - - return nil -} diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go deleted file mode 100644 index 94b529671..000000000 --- a/cmd/helm/helm.go +++ /dev/null @@ -1,119 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package main - -import ( - "os" - - "github.com/codegangsta/cli" - "github.com/kubernetes/helm/pkg/client" - "github.com/kubernetes/helm/pkg/format" - "github.com/kubernetes/helm/pkg/version" -) - -const desc = `Helm: the package and deployment manager for Kubernetes - - Helm is a tool for packaging, deploying, and managing Kubernetes - applications. It has a client component (this tool) and several in-cluster - components. - - Before you can use Helm to manage applications, you must install the - in-cluster components into the target Kubernetes cluster: - - $ helm server install - - Once the in-cluster portion is running, you can use 'helm deploy' to - deploy a new application: - - $ helm deploy -n NAME CHART - - For more information on Helm commands, you can use the following tools: - - $ helm help # top-level help - $ helm CMD --help # help for a particular command or set of commands -` - -var commands []cli.Command - -func init() { - addCommands(cmds()...) -} - -// debug indicates whether the process is in debug mode. -// -// This is set at app start-up time, based on the presence of the --debug -// flag. -var debug bool - -func main() { - app := cli.NewApp() - app.Name = "helm" - app.Version = version.Version - app.Usage = desc - 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.StringFlag{ - Name: "kubectl", - Usage: "The path to the kubectl binary", - EnvVar: "KUBECTL", - }, - cli.IntFlag{ - Name: "timeout", - Usage: "Time in seconds to wait for response", - Value: 10, - }, - cli.BoolFlag{ - Name: "debug", - Usage: "Enable verbose debugging output", - }, - } - app.Before = func(c *cli.Context) error { - debug = c.GlobalBool("debug") - return nil - } - app.Run(os.Args) -} - -func cmds() []cli.Command { - return []cli.Command{} -} - -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 { - format.Err(err) - os.Exit(1) - } -} - -// NewClient creates a new client instance preconfigured for CLI usage. -func NewClient(c *cli.Context) *client.Client { - host := c.GlobalString("host") - timeout := c.GlobalInt("timeout") - return client.NewClient(host).SetDebug(debug).SetTimeout(timeout) -} diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go deleted file mode 100644 index 481be8a85..000000000 --- a/cmd/helm/helm_test.go +++ /dev/null @@ -1,118 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package main - -import ( - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "testing" - - "github.com/codegangsta/cli" - "github.com/kubernetes/helm/pkg/format" -) - -type testHelmData struct { - t *testing.T - mux *http.ServeMux - server *httptest.Server - app *cli.App - output string -} - -func testHelm(t *testing.T) *testHelmData { - th := &testHelmData{t: t} - - th.app = cli.NewApp() - th.app.Commands = commands - - th.app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "host,u", - Value: "https://localhost:8000/", - }, - cli.IntFlag{ - Name: "timeout", - Value: 10, - }, - cli.BoolFlag{ - Name: "debug", - }, - } - th.app.Before = func(c *cli.Context) error { - debug = c.GlobalBool("debug") - return nil - } - - th.mux = http.NewServeMux() - th.server = httptest.NewServer(th.mux) - - return th -} - -func (th *testHelmData) cleanup() { - th.server.Close() -} - -func (th *testHelmData) URL() string { - return th.server.URL -} - -// must gives a fatal error if err is not nil. -func (th *testHelmData) must(err error) { - if err != nil { - th.t.Fatal(err) - } -} - -// check gives a test non-fatal error if err is not nil. -func (th *testHelmData) check(err error) { - if err != nil { - th.t.Error(err) - } -} - -func (th *testHelmData) run(args ...string) { - th.output = "" - args = append([]string{"helm", "--host", th.URL()}, args...) - th.output = captureOutput(func() { - th.app.Run(args) - }) -} - -// captureOutput redirect all log/std streams, capture and replace -func captureOutput(fn func()) string { - osStdout, osStderr := os.Stdout, os.Stderr - logStdout, logStderr := format.Stdout, format.Stderr - defer func() { - os.Stdout, os.Stderr = osStdout, osStderr - format.Stdout, format.Stderr = logStdout, logStderr - }() - - r, w, _ := os.Pipe() - - os.Stdout, os.Stderr = w, w - format.Stdout, format.Stderr = w, w - - fn() - - // read test output and restore previous stdout - w.Close() - b, _ := ioutil.ReadAll(r) - return string(b) -} diff --git a/cmd/helm/properties.go b/cmd/helm/properties.go deleted file mode 100644 index df8797f78..000000000 --- a/cmd/helm/properties.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2016 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. -*/ - -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) < 2 { - return properties, errInvalidProperty - } - - // If the value looks int-like, convert it. - if i, err := strconv.Atoi(pair[1]); err == nil { - properties[pair[0]] = i - } else { - properties[pair[0]] = pair[1] - } - } - - return properties, nil -} diff --git a/cmd/helm/repository.go b/cmd/helm/repository.go deleted file mode 100644 index f17c2fae2..000000000 --- a/cmd/helm/repository.go +++ /dev/null @@ -1,130 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package main - -import ( - "encoding/json" - "errors" - "path/filepath" - - "github.com/codegangsta/cli" - "github.com/kubernetes/helm/pkg/format" - "github.com/kubernetes/helm/pkg/repo" -) - -func init() { - addCommands(repoCommands()) -} - -const chartRepoPath = "repositories" - -const repoDesc = `Helm repositories store Helm charts. - - The repository commands are used to manage which Helm repositories Helm may - use as a source for Charts. The repositories are accessed by in-cluster Helm - components. - - To list the repositories that your server knows about, use 'helm repo list'. - - For more details, use 'helm repo CMD -h'. -` - -const addRepoDesc = `The add repository command is used to add a name a repository url to your - chart repository list. The repository url must begin with a valid protocoal. At the moment, - we only support google cloud storage for chart repositories. - - A valid command might look like: - $ helm repo add charts gs://kubernetes-charts -` - -func repoCommands() cli.Command { - return cli.Command{ - Name: "repository", - Aliases: []string{"repo"}, - Usage: "Perform chart repository operations.", - Description: repoDesc, - Subcommands: []cli.Command{ - { - Name: "add", - Usage: "Add a chart repository to the remote manager.", - Description: addRepoDesc, - ArgsUsage: "[NAME] [REPOSITORY_URL]", - Action: func(c *cli.Context) { run(c, addRepo) }, - }, - { - Name: "list", - Usage: "List the chart repositories on the remote manager.", - ArgsUsage: "", - Action: func(c *cli.Context) { run(c, listRepos) }, - }, - { - Name: "remove", - Aliases: []string{"rm"}, - Usage: "Remove a chart repository from the remote manager.", - ArgsUsage: "REPOSITORY_NAME", - Action: func(c *cli.Context) { run(c, removeRepo) }, - }, - }, - } -} - -func addRepo(c *cli.Context) error { - args := c.Args() - if len(args) < 2 { - return errors.New("'helm repo add' requires a name and repository url as arguments") - } - name := args[0] - repoURL := args[1] - payload, _ := json.Marshal(repo.Repo{URL: repoURL, Name: name}) - msg := "" - if _, err := NewClient(c).Post(chartRepoPath, payload, &msg); err != nil { - return err - } - format.Info(name + " has been added to your chart repositories!") - return nil -} - -func listRepos(c *cli.Context) error { - dest := map[string]string{} - if _, err := NewClient(c).Get(chartRepoPath, &dest); err != nil { - return err - } - if len(dest) < 1 { - format.Info("Looks like you don't have any chart repositories.") - format.Info("Add a chart repository using the `helm repo add [REPOSITORY_URL]` command.") - } else { - format.Msg("Chart Repositories:\n") - for k, v := range dest { - //TODO: make formatting pretty - format.Msg(k + "\t" + v + "\n") - } - } - return nil -} - -func removeRepo(c *cli.Context) error { - args := c.Args() - if len(args) < 1 { - return errors.New("'helm repo remove' requires a repository name as an argument") - } - name := args[0] - if _, err := NewClient(c).Delete(filepath.Join(chartRepoPath, name), nil); err != nil { - return err - } - format.Msg(name + " has been removed.\n") - return nil -} diff --git a/cmd/helm/server.go b/cmd/helm/server.go deleted file mode 100644 index ee902da40..000000000 --- a/cmd/helm/server.go +++ /dev/null @@ -1,195 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package main - -import ( - "errors" - "fmt" - - "github.com/codegangsta/cli" - "github.com/kubernetes/helm/pkg/client" - "github.com/kubernetes/helm/pkg/format" - "github.com/kubernetes/helm/pkg/kubectl" -) - -// ErrAlreadyInstalled indicates that Helm Server is already installed. -var ErrAlreadyInstalled = errors.New("Already Installed") - -func init() { - addCommands(dmCmd()) -} - -func dmCmd() cli.Command { - return cli.Command{ - Name: "server", - Usage: "Manage Helm server-side components", - Description: `Server commands manage the in-cluster portion of Helm. - - Helm has several components that run inside of Kubernetes. Before Helm can - be used to install and manage packages, it must be installed into the - Kubernetes cluster in which packages will be installed. - - The 'helm server' commands rely upon a properly configured 'kubectl' to - communicate with the Kubernetes cluster. To verify that your 'kubectl' - client is pointed to the correct cluster, use 'kubectl cluster-info'. - - Use 'helm server install' to install the in-cluster portion of Helm. -`, - Subcommands: []cli.Command{ - { - Name: "install", - Usage: "Install Helm server components on Kubernetes.", - ArgsUsage: "", - Description: `Use kubectl to install Helm components in their own namespace on Kubernetes. - - Make sure your Kubernetes environment is pointed to the cluster on which you - wish to install.`, - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "dry-run", - Usage: "Show what would be installed, but don't install anything.", - }, - cli.StringFlag{ - Name: "resourcifier-image", - Usage: "The full image name of the Docker image for resourcifier.", - EnvVar: "HELM_RESOURCIFIER_IMAGE", - }, - cli.StringFlag{ - Name: "expandybird-image", - Usage: "The full image name of the Docker image for expandybird.", - EnvVar: "HELM_EXPANDYBIRD_IMAGE", - }, - cli.StringFlag{ - Name: "manager-image", - Usage: "The full image name of the Docker image for manager.", - EnvVar: "HELM_MANAGER_IMAGE", - }, - }, - Action: func(c *cli.Context) { run(c, installServer) }, - }, - { - Name: "uninstall", - Usage: "Uninstall the Helm server-side from Kubernetes.", - ArgsUsage: "", - Description: ``, - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "dry-run", - Usage: "Show what would be uninstalled, but don't remove anything.", - }, - }, - Action: func(c *cli.Context) { run(c, uninstallServer) }, - }, - { - Name: "status", - Usage: "Show status of Helm server-side components.", - ArgsUsage: "", - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "dry-run", - Usage: "Only display the underlying kubectl commands.", - }, - }, - Action: func(c *cli.Context) { run(c, statusServer) }, - }, - { - Name: "target", - Usage: "Displays information about the Kubernetes cluster.", - ArgsUsage: "", - Action: func(c *cli.Context) { run(c, targetServer) }, - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "dry-run", - Usage: "Only display the underlying kubectl commands.", - }, - }, - }, - }, - } -} - -func installServer(c *cli.Context) error { - resImg := c.String("resourcifier-image") - ebImg := c.String("expandybird-image") - manImg := c.String("manager-image") - - dryRun := c.Bool("dry-run") - kubectlPath := c.GlobalString("kubectl") - runner := buildKubectlRunner(kubectlPath, dryRun) - - i := client.NewInstaller() - i.Manager["Image"] = manImg - i.Resourcifier["Image"] = resImg - i.Expandybird["Image"] = ebImg - - out, err := i.Install(runner) - if err != nil { - return fmt.Errorf("error installing %s %s", string(out), err) - } - format.Msg(out) - return nil -} - -func uninstallServer(c *cli.Context) error { - dryRun := c.Bool("dry-run") - kubectlPath := c.GlobalString("kubectl") - runner := buildKubectlRunner(kubectlPath, dryRun) - - out, err := client.Uninstall(runner) - if err != nil { - return fmt.Errorf("error uninstalling: %s %s", out, err) - } - format.Msg(out) - return nil -} - -func statusServer(c *cli.Context) error { - dryRun := c.Bool("dry-run") - kubectlPath := c.GlobalString("kubectl") - runner := buildKubectlRunner(kubectlPath, dryRun) - - out, err := runner.GetByKind("pods", "", "helm") - if err != nil { - return err - } - format.Msg(string(out)) - return nil -} - -func targetServer(c *cli.Context) error { - dryRun := c.Bool("dry-run") - kubectlPath := c.GlobalString("kubectl") - runner := buildKubectlRunner(kubectlPath, dryRun) - - out, err := runner.ClusterInfo() - if err != nil { - return fmt.Errorf("%s (%s)", out, err) - } - format.Msg(string(out)) - return nil -} - -func buildKubectlRunner(kubectlPath string, dryRun bool) kubectl.Runner { - if dryRun { - return &kubectl.PrintRunner{} - } - // TODO: Refactor out kubectl.Path global - if kubectlPath != "" { - kubectl.Path = kubectlPath - } - return &kubectl.RealRunner{} -} diff --git a/cmd/manager/chartrepos.go b/cmd/manager/chartrepos.go deleted file mode 100644 index 8a1ab1304..000000000 --- a/cmd/manager/chartrepos.go +++ /dev/null @@ -1,169 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package main - -import ( - "github.com/kubernetes/helm/cmd/manager/router" - "github.com/kubernetes/helm/pkg/httputil" - "github.com/kubernetes/helm/pkg/repo" - "github.com/kubernetes/helm/pkg/util" - - "encoding/json" - "net/http" - "net/url" - "regexp" -) - -func registerChartRepoRoutes(c *router.Context, h *router.Handler) { - h.Add("GET /repositories", listChartReposHandlerFunc) - h.Add("GET /repositories/*", getChartRepoHandlerFunc) - h.Add("GET /repositories/*/charts", listRepoChartsHandlerFunc) - h.Add("GET /repositories/*/charts/*", getRepoChartHandlerFunc) - h.Add("POST /repositories", addChartRepoHandlerFunc) - h.Add("DELETE /repositories/*", removeChartRepoHandlerFunc) -} - -func listChartReposHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: list chart repositories" - repos, err := c.Manager.ListRepos() - if err != nil { - return err - } - - util.LogHandlerExitWithJSON(handler, w, repos, http.StatusOK) - return nil -} - -func addChartRepoHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: add chart repository" - util.LogHandlerEntry(handler, r) - defer r.Body.Close() - cr := &repo.Repo{} - if err := httputil.Decode(w, r, cr); err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - if string(cr.Format) == "" { - cr.Format = repo.GCSRepoFormat - } - - if string(cr.Type) == "" { - cr.Type = repo.GCSRepoType - } - - if err := c.Manager.AddRepo(cr); err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - msg, _ := json.Marshal(cr.Name + " has been added to the list of chart repositories.") - util.LogHandlerExitWithJSON(handler, w, msg, http.StatusCreated) - return nil -} - -func removeChartRepoHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: remove chart repository" - util.LogHandlerEntry(handler, r) - defer r.Body.Close() - name, err := pos(w, r, 2) - if err != nil { - return err - } - - err = c.Manager.RemoveRepo(name) - if err != nil { - return err - } - - msg, _ := json.Marshal(name + " has been removed from the list of chart repositories.") - util.LogHandlerExitWithJSON(handler, w, msg, http.StatusOK) - return nil -} - -func getChartRepoHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: get repository" - util.LogHandlerEntry(handler, r) - repoURL, err := pos(w, r, 2) - if err != nil { - return err - } - - cr, err := c.Manager.GetRepo(repoURL) - if err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - util.LogHandlerExitWithJSON(handler, w, cr, http.StatusOK) - return nil -} - -func listRepoChartsHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: list repository charts" - util.LogHandlerEntry(handler, r) - repoURL, err := pos(w, r, 2) - if err != nil { - return err - } - - values, err := url.ParseQuery(r.URL.RawQuery) - if err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - var regex *regexp.Regexp - regexString := values.Get("regex") - if regexString != "" { - regex, err = regexp.Compile(regexString) - if err != nil { - httputil.BadRequest(w, r, err) - return nil - } - } - - repoCharts, err := c.Manager.ListRepoCharts(repoURL, regex) - if err != nil { - return err - } - - util.LogHandlerExitWithJSON(handler, w, repoCharts, http.StatusOK) - return nil -} - -func getRepoChartHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: get repository charts" - util.LogHandlerEntry(handler, r) - repoURL, err := pos(w, r, 2) - if err != nil { - return err - } - - chartName, err := pos(w, r, 4) - if err != nil { - return err - } - - repoChart, err := c.Manager.GetChartForRepo(repoURL, chartName) - if err != nil { - return err - } - - util.LogHandlerExitWithJSON(handler, w, repoChart, http.StatusOK) - return nil -} diff --git a/cmd/manager/chartrepos_test.go b/cmd/manager/chartrepos_test.go deleted file mode 100644 index 4662bac6d..000000000 --- a/cmd/manager/chartrepos_test.go +++ /dev/null @@ -1,174 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package main - -import ( - "github.com/kubernetes/helm/pkg/repo" - - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "testing" -) - -var ( - TestRepoBucket = "kubernetes-charts-testing" - TestRepoURL = "gs://" + TestRepoBucket - TestChartName = "frobnitz-0.0.1.tgz" - TestRepoType = string(repo.GCSRepoType) - TestRepoFormat = string(repo.GCSRepoFormat) - TestRepoCredentialName = "default" -) - -func TestListChartRepos(t *testing.T) { - c := stubContext() - s := httpHarness(c, "GET /repositories", listChartReposHandlerFunc) - defer s.Close() - - URL := getTestURL(t, s.URL, "", "") - res, err := http.Get(URL) - if err != nil { - t.Fatalf("Failed GET: %s", err) - } - - if res.StatusCode != http.StatusOK { - t.Errorf("Expected status %d, got %d", http.StatusOK, res.StatusCode) - } -} - -func TestGetChartRepo(t *testing.T) { - c := stubContext() - s := httpHarness(c, "GET /repositories/*", getChartRepoHandlerFunc) - defer s.Close() - - URL := getTestURL(t, s.URL, TestRepoBucket, "") - res, err := http.Get(URL) - if err != nil { - t.Fatalf("Failed GET: %s", err) - } - - if res.StatusCode != http.StatusOK { - t.Errorf("Expected status %d, got %d", http.StatusOK, res.StatusCode) - } -} - -func TestListRepoCharts(t *testing.T) { - c := stubContext() - s := httpHarness(c, "GET /repositories/*/charts", listRepoChartsHandlerFunc) - defer s.Close() - - URL := getTestURL(t, s.URL, TestRepoBucket, "charts") - res, err := http.Get(URL) - if err != nil { - t.Fatalf("Failed GET: %s", err) - } - - if res.StatusCode != http.StatusOK { - t.Errorf("Expected status %d, got %d", http.StatusOK, res.StatusCode) - } -} - -func TestGetRepoChart(t *testing.T) { - c := stubContext() - s := httpHarness(c, "GET /repositories/*/charts/*", getRepoChartHandlerFunc) - defer s.Close() - - chartURL := fmt.Sprintf("charts/%s", TestChartName) - URL := getTestURL(t, s.URL, TestRepoBucket, chartURL) - res, err := http.Get(URL) - if err != nil { - t.Fatalf("Failed GET: %s", err) - } - - if res.StatusCode != http.StatusOK { - t.Errorf("Expected status %d, got %d", http.StatusOK, res.StatusCode) - } -} - -func TestAddChartRepo(t *testing.T) { - c := stubContext() - s := httpHarness(c, "POST /repositories", addChartRepoHandlerFunc) - defer s.Close() - - URL := getTestURL(t, s.URL, "", "") - body := getTestRepo(t, URL) - res, err := http.Post(URL, "application/json", body) - if err != nil { - t.Fatalf("Failed POST: %s", err) - } - - if res.StatusCode != http.StatusCreated { - t.Errorf("Expected status %d, got %d", http.StatusOK, res.StatusCode) - } -} - -func TestRemoveChartRepo(t *testing.T) { - c := stubContext() - s := httpHarness(c, "DELETE /repositories/*", removeChartRepoHandlerFunc) - defer s.Close() - - URL := getTestURL(t, s.URL, TestRepoBucket, "") - req, err := http.NewRequest("DELETE", URL, nil) - if err != nil { - t.Fatalf("Cannot create DELETE request: %s", err) - } - - res, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Failed DELETE: %s", err) - } - - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - t.Errorf("Expected status %d, got %d", http.StatusOK, res.StatusCode) - } -} - -func getTestRepo(t *testing.T, URL string) io.Reader { - tr, err := repo.NewRepo(URL, TestRepoCredentialName, TestRepoBucket, TestRepoFormat, TestRepoType) - if err != nil { - t.Fatalf("Cannot create test repository: %s", err) - } - - trb, err := json.Marshal(&tr) - if err != nil { - t.Fatalf("Cannot marshal test repository: %s", err) - } - - return bytes.NewReader(trb) -} - -func getTestURL(t *testing.T, baseURL, repoURL, chartURL string) string { - URL := fmt.Sprintf("%s/repositories", baseURL) - if repoURL != "" { - URL = fmt.Sprintf("%s/%s", URL, repoURL) - } - - if chartURL != "" { - URL = fmt.Sprintf("%s/%s", URL, chartURL) - } - - u, err := url.Parse(URL) - if err != nil { - t.Fatalf("cannot parse test URL %s: %s", URL, err) - } - - return u.String() -} diff --git a/cmd/manager/deployments.go b/cmd/manager/deployments.go deleted file mode 100644 index 7948d17f3..000000000 --- a/cmd/manager/deployments.go +++ /dev/null @@ -1,425 +0,0 @@ -/* -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. -*/ - -package main - -import ( - "errors" - "fmt" - "log" - "net/http" - "strings" - - "github.com/kubernetes/helm/cmd/manager/manager" - "github.com/kubernetes/helm/cmd/manager/repository" - "github.com/kubernetes/helm/cmd/manager/repository/persistent" - "github.com/kubernetes/helm/cmd/manager/repository/transient" - "github.com/kubernetes/helm/cmd/manager/router" - "github.com/kubernetes/helm/pkg/common" - "github.com/kubernetes/helm/pkg/httputil" - "github.com/kubernetes/helm/pkg/repo" - "github.com/kubernetes/helm/pkg/util" -) - -func registerDeploymentRoutes(c *router.Context, h *router.Handler) { - h.Add("GET /healthz", healthz) - h.Add("GET /deployments", listDeploymentsHandlerFunc) - h.Add("GET /deployments/*", getDeploymentHandlerFunc) - h.Add("POST /deployments", createDeploymentHandlerFunc) - h.Add("DELETE /deployments/*", deleteDeploymentHandlerFunc) - h.Add("PUT /deployments/*", putDeploymentHandlerFunc) - h.Add("GET /deployments/*/manifests", listManifestsHandlerFunc) - h.Add("GET /deployments/*/manifests/*", getManifestHandlerFunc) - h.Add("POST /expand", expandHandlerFunc) - h.Add("GET /charts", listChartsHandlerFunc) - h.Add("GET /charts/*/instances", listChartInstancesHandlerFunc) - h.Add("GET /charts/*/repository", getRepoForChartHandlerFunc) - h.Add("GET /charts/*/metadata", getMetadataForChartHandlerFunc) - h.Add("GET /charts/*", getChartHandlerFunc) - h.Add("POST /credentials/*", createCredentialHandlerFunc) - h.Add("GET /credentials/*", getCredentialHandlerFunc) -} - -func healthz(w http.ResponseWriter, r *http.Request, c *router.Context) error { - log.Println("manager: healthz checkpoint") - // TODO: This should check the availability of the repository, and fail if it - // cannot connect. - fmt.Fprintln(w, "OK") - return nil -} - -func setupDependencies(c *router.Context) error { - var credentialProvider repo.ICredentialProvider - if c.Config.CredentialFile != "" { - if c.Config.CredentialSecrets { - return errors.New("Both credentialFile and credentialSecrets are set") - } - var err error - credentialProvider, err = repo.NewFilebasedCredentialProvider(c.Config.CredentialFile) - if err != nil { - return fmt.Errorf("cannot create credential provider %s: %s", c.Config.CredentialFile, err) - } - } else if *credentialSecrets { - credentialProvider = repo.NewSecretsCredentialProvider() - } else { - credentialProvider = repo.NewInmemCredentialProvider() - } - c.CredentialProvider = credentialProvider - c.Manager = newManager(c) - - return nil -} - -func newManager(c *router.Context) manager.Manager { - cfg := c.Config - service := repo.NewInmemRepoService() - cp := c.CredentialProvider - rp := repo.NewRepoProvider(service, repo.NewGCSRepoProvider(cp), cp) - expander := manager.NewExpander(cfg.ExpanderPort, cfg.ExpanderURL, rp) - deployer := manager.NewDeployer(util.GetServiceURLOrDie(cfg.DeployerName, cfg.DeployerPort, cfg.DeployerURL)) - address := strings.TrimPrefix(util.GetServiceURLOrDie(cfg.MongoName, cfg.MongoPort, cfg.MongoAddress), "http://") - repository := createRepository(address) - return manager.NewManager(expander, deployer, repository, rp, service, c.CredentialProvider) -} - -func createRepository(address string) repository.Repository { - r, err := persistent.NewRepository(address) - if err != nil { - r = transient.NewRepository() - } - - return r -} - -func listDeploymentsHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: list deployments" - util.LogHandlerEntry(handler, r) - l, err := c.Manager.ListDeployments() - if err != nil { - util.LogAndReturnError(handler, http.StatusInternalServerError, err, w) - return nil - } - var names []string - for _, d := range l { - names = append(names, d.Name) - } - - util.LogHandlerExitWithJSON(handler, w, names, http.StatusOK) - return nil -} - -func getDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: get deployment" - util.LogHandlerEntry(handler, r) - name, err := pos(w, r, 2) - if err != nil { - return nil - } - - d, err := c.Manager.GetDeployment(name) - if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return nil - } - - util.LogHandlerExitWithJSON(handler, w, d, http.StatusOK) - return nil -} - -func createDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: create deployment" - util.LogHandlerEntry(handler, r) - defer r.Body.Close() - depReq := getDeploymentRequest(w, r, handler) - if depReq != nil { - d, err := c.Manager.CreateDeployment(depReq) - if err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - util.LogHandlerExitWithJSON(handler, w, d, http.StatusCreated) - } - - return nil -} - -func deleteDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: delete deployment" - util.LogHandlerEntry(handler, r) - defer r.Body.Close() - name, err := pos(w, r, 2) - if err != nil { - return err - } - - d, err := c.Manager.DeleteDeployment(name, true) - if err != nil { - return err - } - - util.LogHandlerExitWithJSON(handler, w, d, http.StatusOK) - return nil -} - -func putDeploymentHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: update deployment" - util.LogHandlerEntry(handler, r) - defer r.Body.Close() - name, err := pos(w, r, 2) - if err != nil { - return err - } - - depReq := getDeploymentRequest(w, r, handler) - if depReq != nil { - d, err := c.Manager.PutDeployment(name, depReq) - if err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - util.LogHandlerExitWithJSON(handler, w, d, http.StatusCreated) - } - - return nil -} - -// pos gets a path item by position. -// -// For example. the path "/foo/bar" has three positions: "" at 0, "foo" at -// 1, and "bar" at 2. -// -// For verb/path combos, position 0 will be the verb: "GET /foo/bar" will have -// "GET " at position 0. -func pos(w http.ResponseWriter, r *http.Request, i int) (string, error) { - parts := strings.Split(r.URL.Path, "/") - if len(parts) < i-1 { - return "", fmt.Errorf("No index for %d", i) - } - return parts[i], nil -} - -func getDeploymentRequest(w http.ResponseWriter, r *http.Request, handler string) *common.DeploymentRequest { - util.LogHandlerEntry(handler, r) - depReq := &common.DeploymentRequest{} - if err := httputil.Decode(w, r, depReq); err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - return depReq -} - -func listManifestsHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: list manifests" - util.LogHandlerEntry(handler, r) - deploymentName, err := pos(w, r, 2) - if err != nil { - return err - } - - m, err := c.Manager.ListManifests(deploymentName) - if err != nil { - return err - } - - var manifestNames []string - for _, manifest := range m { - manifestNames = append(manifestNames, manifest.Name) - } - - util.LogHandlerExitWithJSON(handler, w, manifestNames, http.StatusOK) - return nil -} - -func getManifestHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: get manifest" - util.LogHandlerEntry(handler, r) - deploymentName, err := pos(w, r, 2) - if err != nil { - return err - } - - manifestName, err := pos(w, r, 4) - if err != nil { - return err - } - - m, err := c.Manager.GetManifest(deploymentName, manifestName) - if err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - util.LogHandlerExitWithJSON(handler, w, m, http.StatusOK) - return nil -} - -func expandHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: expand config" - util.LogHandlerEntry(handler, r) - defer r.Body.Close() - depReq := getDeploymentRequest(w, r, handler) - if depReq != nil { - c, err := c.Manager.Expand(depReq) - if err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - util.LogHandlerExitWithJSON(handler, w, c, http.StatusCreated) - } - - return nil -} - -// Putting Type handlers here for now because deployments.go -// currently owns its own Manager backend and doesn't like to share. -func listChartsHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: list charts" - util.LogHandlerEntry(handler, r) - types, err := c.Manager.ListCharts() - if err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - util.LogHandlerExitWithJSON(handler, w, types, http.StatusOK) - return nil -} - -func listChartInstancesHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: list chart instances" - util.LogHandlerEntry(handler, r) - chartName, err := pos(w, r, 2) - if err != nil { - return err - } - - instances, err := c.Manager.ListChartInstances(chartName) - if err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - util.LogHandlerExitWithJSON(handler, w, instances, http.StatusOK) - return nil -} - -func getRepoForChartHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: get repository for chart" - util.LogHandlerEntry(handler, r) - chartName, err := pos(w, r, 2) - if err != nil { - return err - } - - repository, err := c.Manager.GetRepoForChart(chartName) - if err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - util.LogHandlerExitWithJSON(handler, w, repository, http.StatusOK) - return nil -} - -func getMetadataForChartHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: get chart metadata" - util.LogHandlerEntry(handler, r) - chartName, err := pos(w, r, 2) - if err != nil { - return err - } - - metadata, err := c.Manager.GetMetadataForChart(chartName) - if err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - util.LogHandlerExitWithJSON(handler, w, metadata, http.StatusOK) - return nil -} - -func getChartHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: get chart" - util.LogHandlerEntry(handler, r) - chartName, err := pos(w, r, 2) - if err != nil { - return err - } - - ch, err := c.Manager.GetChart(chartName) - if err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - util.LogHandlerExitWithJSON(handler, w, ch, http.StatusOK) - return nil -} - -func getCredential(w http.ResponseWriter, r *http.Request, handler string) *repo.Credential { - util.LogHandlerEntry(handler, r) - t := &repo.Credential{} - if err := httputil.Decode(w, r, t); err != nil { - httputil.BadRequest(w, r, err) - return nil - } - return t -} - -func createCredentialHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: create credential" - util.LogHandlerEntry(handler, r) - defer r.Body.Close() - credentialName, err := pos(w, r, 2) - if err != nil { - return err - } - - cr := getCredential(w, r, handler) - if cr != nil { - err = c.Manager.CreateCredential(credentialName, cr) - if err != nil { - httputil.BadRequest(w, r, err) - return nil - } - } - - util.LogHandlerExitWithJSON(handler, w, c, http.StatusOK) - return nil -} - -func getCredentialHandlerFunc(w http.ResponseWriter, r *http.Request, c *router.Context) error { - handler := "manager: get credential" - util.LogHandlerEntry(handler, r) - credentialName, err := pos(w, r, 2) - if err != nil { - return err - } - - cr, err := c.Manager.GetCredential(credentialName) - if err != nil { - httputil.BadRequest(w, r, err) - return nil - } - - util.LogHandlerExitWithJSON(handler, w, cr, http.StatusOK) - return nil -} diff --git a/cmd/manager/deployments_test.go b/cmd/manager/deployments_test.go deleted file mode 100644 index b3b9023c4..000000000 --- a/cmd/manager/deployments_test.go +++ /dev/null @@ -1,215 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package main - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "net/http" - "strings" - "testing" - - "github.com/kubernetes/helm/pkg/common" -) - -func TestHealthz(t *testing.T) { - c := stubContext() - s := httpHarness(c, "GET /", healthz) - defer s.Close() - - res, err := http.Get(s.URL) - if err != nil { - t.Fatalf("err on http get: %v", err) - } - body, err := ioutil.ReadAll(res.Body) - defer res.Body.Close() - - if err != nil { - t.Fatalf("Failed to GET healthz: %s", err) - } else if res.StatusCode != 200 { - t.Fatalf("Unexpected status: %d", res.StatusCode) - } - - expectedBody := "OK" - if bytes.Equal(body, []byte(expectedBody)) { - t.Fatalf("Expected response body: %s, Actual response body: %s", - expectedBody, string(body)) - } - - expectedContentType := "text/plain" - contentType := res.Header["Content-Type"][0] - if !strings.Contains(contentType, expectedContentType) { - t.Fatalf("Expected Content-Type to include %s", expectedContentType) - } -} - -func TestCreateDeployments(t *testing.T) { - c := stubContext() - depReq := &common.DeploymentRequest{Name: "foo"} - s := httpHarness(c, "POST /deployments", createDeploymentHandlerFunc) - defer s.Close() - - var b bytes.Buffer - if err := json.NewEncoder(&b).Encode(depReq); err != nil { - t.Fatal(err) - } - - res, err := http.Post(s.URL+"/deployments", "application/json", &b) - if err != nil { - t.Errorf("Failed POST: %s", err) - } else if res.StatusCode != http.StatusCreated { - t.Errorf("Expected status %d, got %d", http.StatusCreated, res.StatusCode) - } -} - -func TestListDeployments(t *testing.T) { - c := stubContext() - s := httpHarness(c, "GET /deployments", listDeploymentsHandlerFunc) - defer s.Close() - - man := c.Manager.(*mockManager) - man.deployments = []*common.Deployment{ - {Name: "one", State: &common.DeploymentState{Status: common.CreatedStatus}}, - {Name: "two", State: &common.DeploymentState{Status: common.DeployedStatus}}, - } - - res, err := http.Get(s.URL + "/deployments") - if err != nil { - t.Errorf("Failed GET: %s", err) - } else if res.StatusCode != http.StatusOK { - t.Errorf("Unexpected status code: %d", res.StatusCode) - } - - var out []string - if err := json.NewDecoder(res.Body).Decode(&out); err != nil { - t.Errorf("Failed to parse results: %s", err) - return - } - if len(out) != 2 { - t.Errorf("Expected 2 names, got %d", len(out)) - } -} - -func TestGetDeployments(t *testing.T) { - c := stubContext() - s := httpHarness(c, "GET /deployments/*", getDeploymentHandlerFunc) - defer s.Close() - - man := c.Manager.(*mockManager) - man.deployments = []*common.Deployment{ - {Name: "portunes", State: &common.DeploymentState{Status: common.CreatedStatus}}, - } - - res, err := http.Get(s.URL + "/deployments/portunes") - if err != nil { - t.Errorf("Failed GET: %s", err) - } else if res.StatusCode != http.StatusOK { - t.Errorf("Unexpected status code: %d", res.StatusCode) - } - - var out common.Deployment - if err := json.NewDecoder(res.Body).Decode(&out); err != nil { - t.Errorf("Failed to parse results: %s", err) - return - } - - if out.Name != "portunes" { - t.Errorf("Unexpected name %q", out.Name) - } - - if out.State.Status != common.CreatedStatus { - t.Errorf("Unexpected status %v", out.State.Status) - } -} - -func TestDeleteDeployments(t *testing.T) { - c := stubContext() - s := httpHarness(c, "DELETE /deployments/*", deleteDeploymentHandlerFunc) - defer s.Close() - - man := c.Manager.(*mockManager) - man.deployments = []*common.Deployment{ - {Name: "portunes", State: &common.DeploymentState{Status: common.CreatedStatus}}, - } - - req, err := http.NewRequest("DELETE", s.URL+"/deployments/portunes", nil) - if err != nil { - t.Fatal("Failed to create delete request") - } - - res, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Failed to execute delete request: %s", err) - } - - if res.StatusCode != 200 { - t.Errorf("Expected status code 200, got %d", res.StatusCode) - } - - var out common.Deployment - if err := json.NewDecoder(res.Body).Decode(&out); err != nil { - t.Errorf("Failed to parse results: %s", err) - return - } - - if out.Name != "portunes" { - t.Errorf("Unexpected name %q", out.Name) - } -} - -func TestPutDeployment(t *testing.T) { - c := stubContext() - s := httpHarness(c, "PUT /deployments/*", putDeploymentHandlerFunc) - defer s.Close() - - man := c.Manager.(*mockManager) - man.deployments = []*common.Deployment{ - {Name: "demeter", State: &common.DeploymentState{Status: common.CreatedStatus}}, - } - - depreq := &common.DeploymentRequest{Name: "demeter"} - depreq.Configuration = common.Configuration{Resources: []*common.Resource{}} - out, err := json.Marshal(depreq) - if err != nil { - t.Fatalf("Failed to marshal DeploymentRequest: %s", err) - } - - req, err := http.NewRequest("PUT", s.URL+"/deployments/demeter", bytes.NewBuffer(out)) - if err != nil { - t.Fatal("Failed to create PUT request") - } - - res, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Failed to execute PUT request: %s", err) - } - - if res.StatusCode != 201 { - t.Errorf("Expected status code 201, got %d", res.StatusCode) - } - - d := &common.Deployment{} - if err := json.NewDecoder(res.Body).Decode(&d); err != nil { - t.Errorf("Failed to parse results: %s", err) - return - } - - if d.Name != "demeter" { - t.Errorf("Unexpected name %q", d.Name) - } -} diff --git a/cmd/manager/main.go b/cmd/manager/main.go deleted file mode 100644 index c6747d05d..000000000 --- a/cmd/manager/main.go +++ /dev/null @@ -1,88 +0,0 @@ -/* -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. -*/ - -package main - -import ( - "flag" - "fmt" - "log" - "net/http" - "os" - - "github.com/kubernetes/helm/cmd/manager/router" - "github.com/kubernetes/helm/pkg/httputil" - "github.com/kubernetes/helm/pkg/version" -) - -var ( - port = flag.Int("port", 8080, "The port to listen on") - maxLength = flag.Int64("maxLength", 1024, "The maximum length (KB) of a template.") - expanderPort = flag.String("expanderPort", "8081", "The IP port of the default expander service.") - expanderURL = flag.String("expanderURL", "", "The URL for the default expander service.") - deployerName = flag.String("deployer", "resourcifier-service", "The DNS name of the deployer service.") - deployerPort = flag.String("deployerPort", "8082", "The IP port of the deployer service.") - deployerURL = flag.String("deployerURL", "", "The URL for the deployer service.") - credentialFile = flag.String("credentialFile", "", "Local file to use for credentials.") - credentialSecrets = flag.Bool("credentialSecrets", true, "Use secrets for credentials.") - mongoName = flag.String("mongoName", "mongodb", "The DNS name of the mongodb service.") - mongoPort = flag.String("mongoPort", "27017", "The port of the mongodb service.") - mongoAddress = flag.String("mongoAddress", "mongodb:27017", "The address of the mongodb service.") -) - -func main() { - // Set up dependencies - c := &router.Context{ - Config: parseFlags(), - } - - if err := setupDependencies(c); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - - httputil.DefaultEncoder.MaxReadLen = c.Config.MaxTemplateLength - - // Set up routes - handler := router.NewHandler(c) - registerDeploymentRoutes(c, handler) - registerChartRepoRoutes(c, handler) - - // Now create a server. - log.Printf("Starting Manager %s on %s", version.Version, c.Config.Address) - if err := http.ListenAndServe(c.Config.Address, handler); err != nil { - log.Printf("Server exited with error %s", err) - os.Exit(1) - } -} - -func parseFlags() *router.Config { - flag.Parse() - return &router.Config{ - Address: fmt.Sprintf(":%d", *port), - MaxTemplateLength: *maxLength, - ExpanderPort: *expanderPort, - ExpanderURL: *expanderURL, - DeployerName: *deployerName, - DeployerPort: *deployerPort, - DeployerURL: *deployerURL, - CredentialFile: *credentialFile, - CredentialSecrets: *credentialSecrets, - MongoName: *mongoName, - MongoPort: *mongoPort, - MongoAddress: *mongoAddress, - } -} diff --git a/cmd/manager/manager/deployer.go b/cmd/manager/manager/deployer.go deleted file mode 100644 index 52179886b..000000000 --- a/cmd/manager/manager/deployer.go +++ /dev/null @@ -1,187 +0,0 @@ -/* -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. -*/ - -package manager - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "log" - "net/http" - "net/url" - "strings" - "time" - - "github.com/ghodss/yaml" - "github.com/kubernetes/helm/pkg/common" -) - -// Deployer abstracts interactions with the expander and deployer services. -type Deployer interface { - GetConfiguration(cached *common.Configuration) (*common.Configuration, error) - CreateConfiguration(configuration *common.Configuration) (*common.Configuration, error) - DeleteConfiguration(configuration *common.Configuration) (*common.Configuration, error) - PutConfiguration(configuration *common.Configuration) (*common.Configuration, error) -} - -// NewDeployer returns a new initialized Deployer. -// TODO(vaikas): Add a flag for setting the timeout. -func NewDeployer(url string) Deployer { - return &deployer{url, 15} -} - -type deployer struct { - deployerURL string - timeout int -} - -func (d *deployer) getBaseURL() string { - return fmt.Sprintf("%s/configurations", d.deployerURL) -} - -type formatter func(err error) error - -// GetConfiguration reads and returns the actual configuration -// of the resources described by a cached configuration. -func (d *deployer) GetConfiguration(cached *common.Configuration) (*common.Configuration, error) { - errors := &Error{} - actual := &common.Configuration{} - for _, resource := range cached.Resources { - rtype := url.QueryEscape(resource.Type) - rname := url.QueryEscape(resource.Name) - url := fmt.Sprintf("%s/%s/%s", d.getBaseURL(), rtype, rname) - body, err := d.callService("GET", url, nil, func(e error) error { - return fmt.Errorf("cannot get configuration for resource (%s)", e) - }) - if err != nil { - log.Println(errors.appendError(err)) - continue - } - - if len(body) != 0 { - result := &common.Resource{Name: resource.Name, Type: resource.Type} - if err := yaml.Unmarshal(body, &result.Properties); err != nil { - return nil, fmt.Errorf("cannot get configuration for resource (%v)", err) - } - - actual.Resources = append(actual.Resources, result) - } - } - - if len(errors.errors) > 0 { - return nil, errors - } - - return actual, nil -} - -// CreateConfiguration deploys the set of resources described by a configuration and returns -// the Configuration with status for each resource filled in. -func (d *deployer) CreateConfiguration(configuration *common.Configuration) (*common.Configuration, error) { - return d.callServiceWithConfiguration("POST", "create", configuration) -} - -// DeleteConfiguration deletes the set of resources described by a configuration. -func (d *deployer) DeleteConfiguration(configuration *common.Configuration) (*common.Configuration, error) { - return d.callServiceWithConfiguration("DELETE", "delete", configuration) -} - -// PutConfiguration replaces the set of resources described by a configuration and returns -// the Configuration with status for each resource filled in. -func (d *deployer) PutConfiguration(configuration *common.Configuration) (*common.Configuration, error) { - return d.callServiceWithConfiguration("PUT", "replace", configuration) -} - -func (d *deployer) callServiceWithConfiguration(method, operation string, configuration *common.Configuration) (*common.Configuration, error) { - callback := func(e error) error { - return fmt.Errorf("cannot %s configuration: %s", operation, e) - } - - y, err := yaml.Marshal(configuration) - if err != nil { - return nil, callback(err) - } - - reader := ioutil.NopCloser(bytes.NewReader(y)) - resp, err := d.callService(method, d.getBaseURL(), reader, callback) - - if err != nil { - return nil, err - } - - result := &common.Configuration{} - if len(resp) != 0 { - if err := yaml.Unmarshal(resp, &result); err != nil { - return nil, fmt.Errorf("cannot unmarshal response: (%v)", err) - } - } - return result, nil -} - -func (d *deployer) callService(method, url string, reader io.Reader, callback formatter) ([]byte, error) { - request, err := http.NewRequest(method, url, reader) - if err != nil { - return nil, callback(err) - } - - if method != "GET" { - request.Header.Add("Content-Type", "application/json") - } - - timeout := time.Duration(time.Duration(d.timeout) * time.Second) - client := http.Client{Timeout: timeout} - response, err := client.Do(request) - if err != nil { - return nil, callback(err) - } - - defer response.Body.Close() - body, err := ioutil.ReadAll(response.Body) - if err != nil { - return nil, callback(err) - } - - if response.StatusCode < http.StatusOK || - response.StatusCode >= http.StatusMultipleChoices { - err := fmt.Errorf("resourcifier response:\n%s", body) - return nil, callback(err) - } - - return body, nil -} - -// Error is an error type that captures errors from the multiple calls to kubectl -// made for a single configuration. -type Error struct { - errors []error -} - -// Error returns the string value of an Error. -func (e *Error) Error() string { - errs := []string{} - for _, err := range e.errors { - errs = append(errs, err.Error()) - } - - return strings.Join(errs, "\n") -} - -func (e *Error) appendError(err error) error { - e.errors = append(e.errors, err) - return err -} diff --git a/cmd/manager/manager/deployer_test.go b/cmd/manager/manager/deployer_test.go deleted file mode 100644 index bab7fb463..000000000 --- a/cmd/manager/manager/deployer_test.go +++ /dev/null @@ -1,304 +0,0 @@ -/* -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. -*/ - -package manager - -import ( - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "path" - "reflect" - "strings" - "testing" - - "github.com/kubernetes/helm/pkg/common" - "github.com/kubernetes/helm/pkg/util" - - "github.com/ghodss/yaml" -) - -var validConfigurationTestCaseData = []byte(` -resources: - - name: test-controller-v1 - type: ReplicationController - properties: - kind: ReplicationController - apiVersion: v1 - metadata: - name: test-controller-v1 - namespace: dm - labels: - k8s-app: test - version: v1 - spec: - replicas: 1 - selector: - k8s-app: test - version: v1 - template: - metadata: - labels: - k8s-app: test - version: v1 - spec: - containers: - - name: test - image: deployer/test:latest - ports: - - name: test - containerPort: 8080 - protocol: TCP - - name: test - type: Service - properties: - apiVersion: v1 - kind: Service - metadata: - name: test - namespace: dm - labels: - k8s-app: test - version: v1 - spec: - type: LoadBalancer - selector: - k8s-app: test - version: v1 - ports: - - name: test - port: 8080 - targetPort: test - protocol: TCP -`) - -type DeployerTestCases struct { - TestCases []DeployerTestCase -} - -type DeployerTestCase struct { - Description string - Error string - Handler func(w http.ResponseWriter, r *http.Request) -} - -func TestGetConfiguration(t *testing.T) { - valid := getValidConfiguration(t) - tests := []DeployerTestCase{ - { - "expect success for GetConfiguration", - "", - func(w http.ResponseWriter, r *http.Request) { - // Get name from path, find in valid, and return its properties. - rtype := path.Base(path.Dir(r.URL.Path)) - rname := path.Base(r.URL.Path) - for _, resource := range valid.Resources { - if resource.Type == rtype && resource.Name == rname { - util.LogHandlerExitWithYAML("resourcifier: get configuration", w, resource.Properties, http.StatusOK) - return - } - } - - status := fmt.Sprintf("resource %s of type %s not found", rname, rtype) - http.Error(w, status, http.StatusInternalServerError) - }, - }, - { - "expect error for GetConfiguration", - "cannot get configuration", - deployerErrorHandler, - }, - } - - for _, dtc := range tests { - ts := httptest.NewServer(http.HandlerFunc(dtc.Handler)) - defer ts.Close() - - deployer := NewDeployer(ts.URL) - result, err := deployer.GetConfiguration(valid) - if err != nil { - message := err.Error() - if !strings.Contains(message, dtc.Error) { - t.Errorf("error in test case:%s:%s\n", dtc.Description, message) - } - } else { - if dtc.Error != "" { - t.Errorf("expected error:%s\ndid not occur in test case:%s\n", - dtc.Error, dtc.Description) - } - - if !reflect.DeepEqual(valid, result) { - t.Errorf("error in test case:%s:\nwant:%s\nhave:%s\n", - dtc.Description, util.ToYAMLOrError(valid), util.ToYAMLOrError(result)) - } - } - } -} - -func TestCreateConfiguration(t *testing.T) { - valid := getValidConfiguration(t) - tests := []DeployerTestCase{ - { - "expect success for CreateConfiguration", - "", - deployerSuccessHandler, - }, - { - "expect error for CreateConfiguration", - "cannot create configuration", - deployerErrorHandler, - }, - } - - for _, dtc := range tests { - ts := httptest.NewServer(http.HandlerFunc(dtc.Handler)) - defer ts.Close() - - deployer := NewDeployer(ts.URL) - _, err := deployer.CreateConfiguration(valid) - if err != nil { - message := err.Error() - if !strings.Contains(message, dtc.Error) { - t.Errorf("error in test case:%s:%s\n", dtc.Description, message) - } - } else { - if dtc.Error != "" { - t.Errorf("expected error:%s\ndid not occur in test case:%s\n", - dtc.Error, dtc.Description) - } - } - } -} - -func TestDeleteConfiguration(t *testing.T) { - valid := getValidConfiguration(t) - tests := []DeployerTestCase{ - { - "expect success for DeleteConfiguration", - "", - deployerSuccessHandler, - }, - { - "expect error for DeleteConfiguration", - "cannot delete configuration", - deployerErrorHandler, - }, - } - - for _, dtc := range tests { - ts := httptest.NewServer(http.HandlerFunc(dtc.Handler)) - defer ts.Close() - - deployer := NewDeployer(ts.URL) - _, err := deployer.DeleteConfiguration(valid) - if err != nil { - message := err.Error() - if !strings.Contains(message, dtc.Error) { - t.Errorf("error in test case:%s:%s\n", dtc.Description, message) - } - } else { - if dtc.Error != "" { - t.Errorf("expected error:%s\ndid not occur in test case:%s\n", - dtc.Error, dtc.Description) - } - } - } -} - -func TestPutConfiguration(t *testing.T) { - valid := getValidConfiguration(t) - tests := []DeployerTestCase{ - { - "expect success for PutConfiguration", - "", - deployerSuccessHandler, - }, - { - "expect error for PutConfiguration", - "cannot replace configuration", - deployerErrorHandler, - }, - } - - for _, dtc := range tests { - ts := httptest.NewServer(http.HandlerFunc(dtc.Handler)) - defer ts.Close() - - deployer := NewDeployer(ts.URL) - _, err := deployer.PutConfiguration(valid) - if err != nil { - message := err.Error() - if !strings.Contains(message, dtc.Error) { - t.Errorf("error in test case:%s:%s\n", dtc.Description, message) - } - } else { - if dtc.Error != "" { - t.Errorf("expected error:%s\ndid not occur in test case:%s\n", - dtc.Error, dtc.Description) - } - } - } -} - -func getValidConfiguration(t *testing.T) *common.Configuration { - valid := &common.Configuration{} - err := yaml.Unmarshal(validConfigurationTestCaseData, valid) - if err != nil { - t.Errorf("cannot unmarshal test case data:%s\n", err) - } - - return valid -} - -func deployerErrorHandler(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - http.Error(w, "something failed", http.StatusInternalServerError) -} - -func deployerSuccessHandler(w http.ResponseWriter, r *http.Request) { - valid := &common.Configuration{} - err := yaml.Unmarshal(validConfigurationTestCaseData, valid) - if err != nil { - status := fmt.Sprintf("cannot unmarshal test case data:%s", err) - http.Error(w, status, http.StatusInternalServerError) - return - } - - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - if err != nil { - status := fmt.Sprintf("cannot read request body:%s", err) - http.Error(w, status, http.StatusInternalServerError) - return - } - - result := &common.Configuration{} - if err := yaml.Unmarshal(body, result); err != nil { - status := fmt.Sprintf("cannot unmarshal request body:%s", err) - http.Error(w, status, http.StatusInternalServerError) - return - } - - if !reflect.DeepEqual(valid, result) { - status := fmt.Sprintf("error in http handler:\nwant:%s\nhave:%s\n", - util.ToYAMLOrError(valid), util.ToYAMLOrError(result)) - http.Error(w, status, http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) -} diff --git a/cmd/manager/manager/expander.go b/cmd/manager/manager/expander.go deleted file mode 100644 index 4f4c081b4..000000000 --- a/cmd/manager/manager/expander.go +++ /dev/null @@ -1,225 +0,0 @@ -/* -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. -*/ - -package manager - -import ( - "github.com/kubernetes/helm/pkg/common" - "github.com/kubernetes/helm/pkg/expansion" - "github.com/kubernetes/helm/pkg/repo" - "github.com/kubernetes/helm/pkg/util" - - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "strings" -) - -// ExpandedConfiguration is the structure returned by the expansion service. -type ExpandedConfiguration struct { - Config *common.Configuration `json:"config"` - Layout *common.Layout `json:"layout"` -} - -// Expander abstracts interactions with the expander and deployer services. -type Expander interface { - ExpandConfiguration(conf *common.Configuration) (*ExpandedConfiguration, error) -} - -// NewExpander returns a new initialized Expander. -func NewExpander(port, URL string, rp repo.IRepoProvider) Expander { - if rp == nil { - rp = repo.NewRepoProvider(nil, nil, nil) - } - - return &expander{expanderPort: port, expanderURL: URL, repoProvider: rp} -} - -type expander struct { - repoProvider repo.IRepoProvider - expanderPort string - expanderURL string -} - -// ExpandConfiguration expands the supplied configuration and returns -// an expanded configuration. -func (e *expander) ExpandConfiguration(conf *common.Configuration) (*ExpandedConfiguration, error) { - expConf, err := e.expandConfiguration(conf) - if err != nil { - return nil, err - } - - return expConf, nil -} - -func (e *expander) expandConfiguration(conf *common.Configuration) (*ExpandedConfiguration, error) { - resources := []*common.Resource{} - layouts := []*common.LayoutResource{} - - // Iterate over all of the resources in the unexpanded configuration - for _, resource := range conf.Resources { - additions := []*common.Resource{resource} - layout := &common.LayoutResource{ - Resource: common.Resource{ - Name: resource.Name, Type: resource.Type, - }, - } - - // If the type is a chart reference - if repo.IsChartReference(resource.Type) { - // Fetch, decompress and unpack - cbr, _, err := e.repoProvider.GetChartByReference(resource.Type) - if err != nil { - return nil, err - } - - defer cbr.Close() - - // Load the charts contents into strings that we can pass to exapnsion - content, err := cbr.LoadContent() - if err != nil { - return nil, err - } - - expander := cbr.Chartfile().Expander - if expander != nil && expander.Name != "" { - // Build a request to the expansion service and call it to do the expansion - svcReq := &expansion.ServiceRequest{ - ChartInvocation: resource, - Chart: content, - } - - svcResp, err := e.callService(expander.Name, svcReq) - if err != nil { - return nil, err - } - - // Call ourselves recursively with the list of resources returned by expansion - expConf, err := e.expandConfiguration(svcResp) - if err != nil { - return nil, err - } - - // Append the reources returned by the recursion to the flat list of resources - additions = expConf.Config.Resources - - // This was not a primitive resource, so add its properties to the layout - // Then add the all of the layout resources returned by the recursion to the layout - layout.Resources = expConf.Layout.Resources - layout.Properties = resource.Properties - } else { - // Raise an error if a non template chart supplies properties - if resource.Properties != nil { - return nil, fmt.Errorf("properties provided for non template chart %s", resource.Type) - } - - additions = []*common.Resource{} - for _, member := range content.Members { - segments := strings.Split(member.Path, "/") - if len(segments) > 1 && segments[0] == "templates" { - if strings.HasSuffix(member.Path, "yaml") || strings.HasSuffix(member.Path, "json") { - resource, err := util.ParseKubernetesObject(member.Content) - if err != nil { - return nil, err - } - - resources = append(resources, resource) - } - } - } - } - } - - resources = append(resources, additions...) - layouts = append(layouts, layout) - } - - // All done with this level, so return the expanded configuration - result := &ExpandedConfiguration{ - Config: &common.Configuration{Resources: resources}, - Layout: &common.Layout{Resources: layouts}, - } - - return result, nil -} - -func (e *expander) callService(svcName string, svcReq *expansion.ServiceRequest) (*common.Configuration, error) { - svcURL, err := e.getServiceURL(svcName) - if err != nil { - return nil, err - } - - j, err := json.Marshal(svcReq) - if err != nil { - return nil, err - } - - reader := ioutil.NopCloser(bytes.NewReader(j)) - request, err := http.NewRequest("POST", svcURL, reader) - if err != nil { - return nil, err - } - - request.Header.Set("Content-Type", "application/json") - request.Header.Set("Accept", "*/*") - - response, err := http.DefaultClient.Do(request) - if err != nil { - e := fmt.Errorf("call failed (%s) with payload:\n%s\n", err, string(j)) - return nil, e - } - - defer response.Body.Close() - body, err := ioutil.ReadAll(response.Body) - if err != nil { - e := fmt.Errorf("error reading response: %s", err) - return nil, e - } - - if response.StatusCode != http.StatusOK { - err := fmt.Errorf("expandybird response:\n%s", body) - return nil, err - } - - svcResp := &common.Configuration{} - if err := json.Unmarshal(body, svcResp); err != nil { - e := fmt.Errorf("cannot unmarshal response body (%s):%s", err, body) - return nil, e - } - - return svcResp, nil -} - -func (e *expander) getServiceURL(svcName string) (string, error) { - if !strings.HasPrefix(svcName, "http:") && !strings.HasPrefix(svcName, "https:") { - var err error - svcName, err = util.GetServiceURL(svcName, e.expanderPort, e.expanderURL) - if err != nil { - return "", err - } - } - - u, err := url.Parse(svcName) - if err != nil { - return "", err - } - - u.Path = fmt.Sprintf("%s/expand", u.Path) - return u.String(), nil -} diff --git a/cmd/manager/manager/expander_test.go b/cmd/manager/manager/expander_test.go deleted file mode 100644 index 8ab757581..000000000 --- a/cmd/manager/manager/expander_test.go +++ /dev/null @@ -1,373 +0,0 @@ -/* -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. -*/ - -package manager - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "reflect" - "strings" - "testing" - - "github.com/ghodss/yaml" - "github.com/kubernetes/helm/pkg/common" - "github.com/kubernetes/helm/pkg/expansion" - "github.com/kubernetes/helm/pkg/repo" - "github.com/kubernetes/helm/pkg/util" -) - -var ( - TestRepoBucket = "kubernetes-charts-testing" - TestRepoURL = "gs://" + TestRepoBucket - TestChartName = "frobnitz" - TestChartVersion = "0.0.1" - TestArchiveName = TestChartName + "-" + TestChartVersion + ".tgz" - TestResourceType = TestRepoURL + "/" + TestArchiveName - TestRepoType = string(repo.GCSRepoType) - TestRepoFormat = string(repo.GCSRepoFormat) - TestRepoCredentialName = "default" - TestRepoName = TestRepoBucket -) - -var validResponseTestCaseData = []byte(` -resources: -- name: test-service - properties: - test-property: test-value - type: Service -- name: test-rc - properties: - test-property: test-value - type: ReplicationController -- name: test3-service - properties: - test-property: test-value - type: Service -- name: test3-rc - properties: - test-property: test-value - type: ReplicationController -- name: test4-service - properties: - test-property: test-value - type: Service -- name: test4-rc - properties: - test-property: test-value - type: ReplicationController -`) - -var validLayoutTestCaseData = []byte(` -resources: -- name: test_invocation - resources: - - name: test-service - type: Service - - name: test-rc - type: ReplicationController - - name: test3-service - type: Service - - name: test3-rc - type: ReplicationController - - name: test4-service - type: Service - - name: test4-rc - type: ReplicationController - type: gs://kubernetes-charts-testing/frobnitz-0.0.1.tgz -`) - -/* -[]byte(` -resources: -- name: test - properties: - test-property: test-value - resources: - - name: test-service - type: Service - - name: test-rc - type: ReplicationController - type: test-type.py -- name: test2 - properties: null - resources: - - name: test3 - properties: - test-property: test-value - resources: - - name: test3-service - type: Service - - name: test3-rc - type: ReplicationController - type: test-type.py - - name: test4 - properties: - test-property: test-value - resources: - - name: test4-service - type: Service - - name: test4-rc - type: ReplicationController - type: test-type.py - type: test2.jinja -`) - -var roundTripContent = ` -resources: -- name: test - type: test.py - properties: - test: test -` - -var roundTripExpanded = ` -resources: -- name: test2 - type: test2.py - properties: - test: test -` - -var roundTripLayout = ` -resources: -- name: test - type: test.py - properties: - test: test - resources: - - name: test2 - type: test2.py - properties: - test: test -` - -var roundTripExpanded2 = ` -resources: -- name: test3 - type: Service - properties: - test: test -` - -var roundTripLayout2 = ` -resources: -- name: test2 - type: test2.py - properties: - test: test - resources: - - name: test3 - type: Service - properties: - test: test -` - -var finalExpanded = ` -config: - resources: - - name: test3 - type: Service - properties: - test: test -layout: - resources: - - name: test - type: test.py - properties: - test: test - resources: - - name: test2 - type: test2.py - properties: - test: test - resources: - - name: test3 - type: Service - properties: - test: test -` - -var roundTripResponse = &ExpandedConfiguration{ - Config: roundTripExpanded, -} - -var roundTripResponse2 = &ExpandedConfiguration{ - Config: roundTripExpanded2, -} - -var roundTripResponses = []*ExpandedConfiguration{ - roundTripResponse, - roundTripResponse2, -} -*/ - -type ExpanderTestCase struct { - Description string - Error string - Handler func(w http.ResponseWriter, r *http.Request) - ValidResponse *ExpandedConfiguration -} - -func TestExpandTemplate(t *testing.T) { - // roundTripResponse := &ExpandedConfiguration{} - // if err := yaml.Unmarshal([]byte(finalExpanded), roundTripResponse); err != nil { - // panic(err) - // } - - tests := []ExpanderTestCase{ - { - "expect success for ExpandConfiguration", - "", - expanderSuccessHandler, - getValidExpandedConfiguration(), - }, - { - "expect error for ExpandConfiguration", - "simulated failure", - expanderErrorHandler, - nil, - }, - } - - for _, etc := range tests { - ts := httptest.NewServer(http.HandlerFunc(etc.Handler)) - defer ts.Close() - - expander := NewExpander("8081", ts.URL, getTestRepoProvider(t)) - resource := &common.Resource{ - Name: "test_invocation", - Type: TestResourceType, - } - - conf := &common.Configuration{ - Resources: []*common.Resource{ - resource, - }, - } - - actualResponse, err := expander.ExpandConfiguration(conf) - if err != nil { - message := err.Error() - if etc.Error == "" { - t.Errorf("unexpected error in test case %s: %s", etc.Description, err) - } - if !strings.Contains(message, etc.Error) { - t.Errorf("error in test case:%s:%s\n", etc.Description, message) - } - } else { - if etc.Error != "" { - t.Errorf("expected error:%s\ndid not occur in test case:%s\n", - etc.Error, etc.Description) - } - - expectedResponse := etc.ValidResponse - if !reflect.DeepEqual(expectedResponse, actualResponse) { - t.Errorf("error in test case:%s:\nwant:%s\nhave:%s\n", - etc.Description, util.ToYAMLOrError(expectedResponse), util.ToYAMLOrError(actualResponse)) - } - } - } -} - -func getValidServiceResponse() *common.Configuration { - conf := &common.Configuration{} - if err := yaml.Unmarshal(validResponseTestCaseData, conf); err != nil { - panic(fmt.Errorf("cannot unmarshal valid response: %s\n", err)) - } - - return conf -} - -func getValidExpandedConfiguration() *ExpandedConfiguration { - conf := getValidServiceResponse() - layout := &common.Layout{} - if err := yaml.Unmarshal(validLayoutTestCaseData, layout); err != nil { - panic(fmt.Errorf("cannot unmarshal valid response: %s\n", err)) - } - - return &ExpandedConfiguration{Config: conf, Layout: layout} - -} - -func expanderErrorHandler(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - http.Error(w, "simulated failure", http.StatusInternalServerError) -} - -/* -func roundTripHandler(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - handler := "expandybird: expand" - util.LogHandlerEntry(handler, r) - if len(roundTripResponses) < 1 { - http.Error(w, "Too many calls to round trip handler", http.StatusInternalServerError) - return - } - - util.LogHandlerExitWithJSON(handler, w, roundTripResponses[0], http.StatusOK) - roundTripResponses = roundTripResponses[1:] -} -*/ - -func expanderSuccessHandler(w http.ResponseWriter, r *http.Request) { - handler := "expandybird: expand" - util.LogHandlerEntry(handler, r) - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - if err != nil { - status := fmt.Sprintf("cannot read request body:%s", err) - http.Error(w, status, http.StatusInternalServerError) - return - } - - svcReq := &expansion.ServiceRequest{} - if err := json.Unmarshal(body, svcReq); err != nil { - status := fmt.Sprintf("cannot unmarshal request body:%s\n%s\n", err, body) - http.Error(w, status, http.StatusInternalServerError) - return - } - - /* - if !reflect.DeepEqual(validRequestTestCaseData, *svcReq) { - status := fmt.Sprintf("error in http handler:\nwant:%s\nhave:%s\n", - util.ToJSONOrError(validRequestTestCaseData), util.ToJSONOrError(template)) - http.Error(w, status, http.StatusInternalServerError) - return - } - */ - - svcResp := getValidServiceResponse() - util.LogHandlerExitWithJSON(handler, w, svcResp, http.StatusOK) -} - -func getTestRepoProvider(t *testing.T) repo.IRepoProvider { - rs := repo.NewInmemRepoService() - rp := repo.NewRepoProvider(rs, nil, nil) - tr, err := repo.NewRepo(TestRepoURL, TestRepoCredentialName, TestRepoName, TestRepoFormat, TestRepoType) - if err != nil { - t.Fatalf("cannot create test repository: %s", err) - } - - if err := rs.CreateRepo(tr); err != nil { - t.Fatalf("cannot initialize repository service: %s", err) - } - - return rp -} diff --git a/cmd/manager/manager/manager.go b/cmd/manager/manager/manager.go deleted file mode 100644 index 2b148ac60..000000000 --- a/cmd/manager/manager/manager.go +++ /dev/null @@ -1,447 +0,0 @@ -/* -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. -*/ - -package manager - -import ( - "fmt" - "log" - "regexp" - "time" - - "github.com/kubernetes/helm/cmd/manager/repository" - "github.com/kubernetes/helm/pkg/chart" - "github.com/kubernetes/helm/pkg/common" - "github.com/kubernetes/helm/pkg/repo" -) - -// Manager manages a persistent set of Deployments. -type Manager interface { - // Deployments - ListDeployments() ([]common.Deployment, error) - GetDeployment(name string) (*common.Deployment, error) - CreateDeployment(depReq *common.DeploymentRequest) (*common.Deployment, error) - DeleteDeployment(name string, forget bool) (*common.Deployment, error) - PutDeployment(name string, depReq *common.DeploymentRequest) (*common.Deployment, error) - - // Manifests - ListManifests(deploymentName string) (map[string]*common.Manifest, error) - GetManifest(deploymentName string, manifest string) (*common.Manifest, error) - Expand(t *common.DeploymentRequest) (*common.Manifest, error) - - // Charts - ListCharts() ([]string, error) - ListChartInstances(chartName string) ([]*common.ChartInstance, error) - GetRepoForChart(chartName string) (string, error) - GetMetadataForChart(chartName string) (*chart.Chartfile, error) - GetChart(chartName string) (*chart.Chart, error) - - // Repo Charts - ListRepoCharts(repoName string, regex *regexp.Regexp) ([]string, error) - GetChartForRepo(repoName, chartName string) (*chart.Chart, error) - - // Credentials - CreateCredential(name string, c *repo.Credential) error - GetCredential(name string) (*repo.Credential, error) - - // Chart Repositories - ListRepos() (map[string]string, error) - AddRepo(addition repo.IRepo) error - RemoveRepo(repoName string) error - GetRepo(repoName string) (repo.IRepo, error) -} - -type manager struct { - expander Expander - deployer Deployer - repository repository.Repository - repoProvider repo.IRepoProvider - service repo.IRepoService - //TODO: add chart repo service - credentialProvider repo.ICredentialProvider -} - -// NewManager returns a new initialized Manager. -func NewManager(expander Expander, - deployer Deployer, - repository repository.Repository, - repoProvider repo.IRepoProvider, - service repo.IRepoService, - credentialProvider repo.ICredentialProvider) Manager { - return &manager{expander, deployer, repository, repoProvider, service, credentialProvider} -} - -// ListDeployments returns the list of deployments -func (m *manager) ListDeployments() ([]common.Deployment, error) { - l, err := m.repository.ListDeployments() - if err != nil { - return nil, err - } - return l, nil -} - -// GetDeployment retrieves the configuration stored for a given deployment -// as well as the current configuration from the cluster. -func (m *manager) GetDeployment(name string) (*common.Deployment, error) { - d, err := m.repository.GetDeployment(name) - if err != nil { - return nil, err - } - - return d, nil -} - -// ListManifests retrieves the manifests for a given deployment -// of each of the deployments in the repository and returns the deployments. -func (m *manager) ListManifests(deploymentName string) (map[string]*common.Manifest, error) { - l, err := m.repository.ListManifests(deploymentName) - if err != nil { - return nil, err - } - - return l, nil -} - -// GetManifest retrieves the specified manifest for a given deployment -func (m *manager) GetManifest(deploymentName string, manifestName string) (*common.Manifest, error) { - d, err := m.repository.GetManifest(deploymentName, manifestName) - if err != nil { - return nil, err - } - - return d, nil -} - -// CreateDeployment expands the supplied configuration, creates the resulting -// resources in the cluster, creates a new deployment that tracks it, stores the -// deployment in the repository and returns the deployment. -func (m *manager) CreateDeployment(depReq *common.DeploymentRequest) (*common.Deployment, error) { - log.Printf("Creating deployment: %s", depReq.Name) - _, err := m.repository.CreateDeployment(depReq.Name) - if err != nil { - log.Printf("CreateDeployment failed %v", err) - return nil, err - } - - manifest, err := m.Expand(depReq) - if err != nil { - log.Printf("Manifest creation failed: %v", err) - m.repository.SetDeploymentState(depReq.Name, failState(err)) - return nil, err - } - - if err := m.repository.AddManifest(manifest); err != nil { - log.Printf("AddManifest failed %v", err) - m.repository.SetDeploymentState(depReq.Name, failState(err)) - return nil, err - } - - actualConfig, err := m.deployer.CreateConfiguration(manifest.ExpandedConfig) - if err != nil { - // Deployment failed, mark as failed - log.Printf("CreateConfiguration failed: %v", err) - m.repository.SetDeploymentState(depReq.Name, failState(err)) - - // If we failed before being able to create some of the resources, then - // return the failure as such. Otherwise, we're going to add the manifest - // and hence resource specific errors down below. - if actualConfig == nil { - return nil, err - } - } else { - // May be errors in the resources themselves. - errs := getResourceErrors(actualConfig) - if len(errs) > 0 { - e := fmt.Errorf("Found resource errors during deployment: %v", errs) - m.repository.SetDeploymentState(depReq.Name, failState(e)) - return nil, e - } - - m.repository.SetDeploymentState(depReq.Name, &common.DeploymentState{Status: common.DeployedStatus}) - } - - // Update the manifest with the actual state of the reified resources - manifest.ExpandedConfig = actualConfig - if err := m.repository.SetManifest(manifest); err != nil { - log.Printf("SetManifest failed %v", err) - m.repository.SetDeploymentState(depReq.Name, failState(err)) - return nil, err - } - - // Finally update the type instances for this deployment. - m.setChartInstances(depReq.Name, manifest.Name, manifest.Layout) - return m.repository.GetValidDeployment(depReq.Name) -} - -func (m *manager) setChartInstances(deploymentName string, manifestName string, layout *common.Layout) { - m.repository.ClearChartInstancesForDeployment(deploymentName) - - instances := make(map[string][]*common.ChartInstance) - for i, r := range layout.Resources { - addChartInstances(&instances, r, deploymentName, manifestName, fmt.Sprintf("$.resources[%d]", i)) - } - - m.repository.AddChartInstances(instances) -} - -func addChartInstances(instances *map[string][]*common.ChartInstance, r *common.LayoutResource, deploymentName string, manifestName string, jsonPath string) { - // Add this resource. - inst := &common.ChartInstance{ - Name: r.Name, - Type: r.Type, - Deployment: deploymentName, - Manifest: manifestName, - Path: jsonPath, - } - - (*instances)[r.Type] = append((*instances)[r.Type], inst) - - // Add all sub resources if they exist. - for i, sr := range r.Resources { - addChartInstances(instances, sr, deploymentName, manifestName, fmt.Sprintf("%s.resources[%d]", jsonPath, i)) - } -} - -// DeleteDeployment deletes the configuration for the deployment with -// the supplied identifier from the cluster.repository. If forget is true, then -// the deployment is removed from the repository. Otherwise, it is marked -// as deleted and retained. -func (m *manager) DeleteDeployment(name string, forget bool) (*common.Deployment, error) { - log.Printf("Deleting deployment: %s", name) - d, err := m.repository.GetValidDeployment(name) - if err != nil { - return nil, err - } - - // If there's a latest manifest, delete the underlying resources. - latest, err := m.repository.GetLatestManifest(name) - if err != nil { - m.repository.SetDeploymentState(name, failState(err)) - return nil, err - } - - if latest != nil { - log.Printf("Deleting resources from the latest manifest") - // Clear previous state. - for _, r := range latest.ExpandedConfig.Resources { - r.State = nil - } - - if _, err := m.deployer.DeleteConfiguration(latest.ExpandedConfig); err != nil { - log.Printf("Failed to delete resources from the latest manifest: %v", err) - m.repository.SetDeploymentState(name, failState(err)) - return nil, err - } - - // Create an empty manifest since resources have been deleted. - if !forget { - manifest := &common.Manifest{Deployment: name, Name: generateManifestName()} - if err := m.repository.AddManifest(manifest); err != nil { - log.Printf("Failed to add empty manifest") - return nil, err - } - } - } - - d, err = m.repository.DeleteDeployment(name, forget) - if err != nil { - return nil, err - } - - // Finally remove the type instances for this deployment. - m.repository.ClearChartInstancesForDeployment(name) - return d, nil -} - -// PutDeployment replaces the configuration of the deployment with -// the supplied identifier in the cluster, and returns the deployment. -func (m *manager) PutDeployment(name string, depReq *common.DeploymentRequest) (*common.Deployment, error) { - _, err := m.repository.GetValidDeployment(name) - if err != nil { - return nil, err - } - - manifest, err := m.Expand(depReq) - if err != nil { - log.Printf("Manifest creation failed: %v", err) - m.repository.SetDeploymentState(name, failState(err)) - return nil, err - } - - actualConfig, err := m.deployer.PutConfiguration(manifest.ExpandedConfig) - if err != nil { - m.repository.SetDeploymentState(name, failState(err)) - return nil, err - } - - manifest.ExpandedConfig = actualConfig - err = m.repository.AddManifest(manifest) - if err != nil { - m.repository.SetDeploymentState(name, failState(err)) - return nil, err - } - - // Finally update the type instances for this deployment. - m.setChartInstances(depReq.Name, manifest.Name, manifest.Layout) - return m.repository.GetValidDeployment(depReq.Name) -} - -func (m *manager) Expand(depReq *common.DeploymentRequest) (*common.Manifest, error) { - expConf, err := m.expander.ExpandConfiguration(&depReq.Configuration) - if err != nil { - log.Printf("Expansion failed %v", err) - return nil, err - } - - return &common.Manifest{ - Name: generateManifestName(), - Deployment: depReq.Name, - InputConfig: &depReq.Configuration, - ExpandedConfig: expConf.Config, - Layout: expConf.Layout, - }, nil -} - -func (m *manager) ListCharts() ([]string, error) { - return m.repository.ListCharts() -} - -func (m *manager) ListChartInstances(chartName string) ([]*common.ChartInstance, error) { - return m.repository.GetChartInstances(chartName) -} - -// GetRepoForChart returns the repository where the referenced chart resides. -func (m *manager) GetRepoForChart(reference string) (string, error) { - _, r, err := m.repoProvider.GetChartByReference(reference) - if err != nil { - return "", err - } - - return r.GetURL(), nil -} - -// GetMetadataForChart returns the metadata for the referenced chart. -func (m *manager) GetMetadataForChart(reference string) (*chart.Chartfile, error) { - c, _, err := m.repoProvider.GetChartByReference(reference) - if err != nil { - return nil, err - } - - return c.Chartfile(), nil -} - -// GetChart returns the referenced chart. -func (m *manager) GetChart(reference string) (*chart.Chart, error) { - c, _, err := m.repoProvider.GetChartByReference(reference) - if err != nil { - return nil, err - } - - return c, nil -} - -// ListRepos returns the list of available repository URLs -func (m *manager) ListRepos() (map[string]string, error) { - return m.service.ListRepos() -} - -// AddRepo adds a repository to the list -func (m *manager) AddRepo(addition repo.IRepo) error { - return m.service.CreateRepo(addition) -} - -// RemoveRepo removes a repository from the list by URL -func (m *manager) RemoveRepo(repoName string) error { - repoURL, err := m.service.GetRepoURLByName(repoName) - if err != nil { - return err - } - - return m.service.DeleteRepo(repoURL) -} - -// GetRepo returns the repository with the given name -func (m *manager) GetRepo(repoName string) (repo.IRepo, error) { - repoURL, err := m.service.GetRepoURLByName(repoName) - if err != nil { - return nil, err - } - - return m.service.GetRepoByURL(repoURL) -} - -func generateManifestName() string { - return fmt.Sprintf("manifest-%d", time.Now().UTC().UnixNano()) -} - -func failState(e error) *common.DeploymentState { - return &common.DeploymentState{ - Status: common.FailedStatus, - Errors: []string{e.Error()}, - } -} - -func getResourceErrors(c *common.Configuration) []string { - var errs []string - for _, r := range c.Resources { - if r.State.Status == common.Failed { - errs = append(errs, r.State.Errors...) - } - } - - return errs -} - -// ListRepoCharts lists charts in a given repository whose names -// conform to the supplied regular expression, or all charts, if the regular -// expression is nil. -func (m *manager) ListRepoCharts(repoName string, regex *regexp.Regexp) ([]string, error) { - repoURL, err := m.service.GetRepoURLByName(repoName) - if err != nil { - return nil, err - } - - r, err := m.repoProvider.GetRepoByURL(repoURL) - if err != nil { - return nil, err - } - - return r.ListCharts(regex) -} - -// GetChartForRepo returns a chart by name from a given repository. -func (m *manager) GetChartForRepo(repoName, chartName string) (*chart.Chart, error) { - repoURL, err := m.service.GetRepoURLByName(repoName) - if err != nil { - return nil, err - } - - r, err := m.repoProvider.GetRepoByURL(repoURL) - if err != nil { - return nil, err - } - - return r.GetChart(chartName) -} - -// CreateCredential creates a credential that can be used to authenticate to repository -func (m *manager) CreateCredential(name string, c *repo.Credential) error { - return m.credentialProvider.SetCredential(name, c) -} - -func (m *manager) GetCredential(name string) (*repo.Credential, error) { - return m.credentialProvider.GetCredential(name) -} diff --git a/cmd/manager/manager/manager_test.go b/cmd/manager/manager/manager_test.go deleted file mode 100644 index 150431630..000000000 --- a/cmd/manager/manager/manager_test.go +++ /dev/null @@ -1,551 +0,0 @@ -/* -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. -*/ - -package manager - -import ( - "github.com/kubernetes/helm/pkg/common" - "github.com/kubernetes/helm/pkg/repo" - - "errors" - "reflect" - "strings" - "testing" -) - -var layout = common.Layout{ - Resources: []*common.LayoutResource{{Resource: common.Resource{Name: "test", Type: "test"}}}, -} -var configuration = common.Configuration{ - Resources: []*common.Resource{{Name: "test", Type: "test"}}, -} -var resourcesWithSuccessState = common.Configuration{ - Resources: []*common.Resource{{Name: "test", Type: "test", State: &common.ResourceState{Status: common.Created}}}, -} -var resourcesWithFailureState = common.Configuration{ - Resources: []*common.Resource{{ - Name: "test", - Type: "test", - State: &common.ResourceState{ - Status: common.Failed, - Errors: []string{"test induced error"}, - }, - }}, -} -var deploymentRequest = common.DeploymentRequest{Name: "test", Configuration: configuration} - -var expandedConfig = ExpandedConfiguration{ - Config: &configuration, - Layout: &layout, -} - -var deploymentName = "deployment" - -var manifestName = "manifest-2" -var manifest = common.Manifest{Name: manifestName, ExpandedConfig: &configuration, Layout: &layout} -var manifestMap = map[string]*common.Manifest{manifest.Name: &manifest} - -var deployment = common.Deployment{ - Name: "test", -} - -var deploymentList = []common.Deployment{deployment, {Name: "test2"}} - -var typeInstMap = map[string][]string{"test": {"test"}} - -var errTest = errors.New("test error") - -type expanderStub struct{} - -func (expander *expanderStub) ExpandConfiguration(conf *common.Configuration) (*ExpandedConfiguration, error) { - if reflect.DeepEqual(conf, &configuration) { - return &expandedConfig, nil - } - - return nil, errTest -} - -type deployerStub struct { - FailCreate bool - Created []*common.Configuration - FailDelete bool - Deleted []*common.Configuration - FailCreateResource bool -} - -func (deployer *deployerStub) reset() { - deployer.FailCreate = false - deployer.Created = make([]*common.Configuration, 0) - deployer.FailDelete = false - deployer.Deleted = make([]*common.Configuration, 0) - deployer.FailCreateResource = false -} - -func newDeployerStub() *deployerStub { - ret := &deployerStub{} - return ret -} - -func (deployer *deployerStub) GetConfiguration(cached *common.Configuration) (*common.Configuration, error) { - return nil, nil -} - -func (deployer *deployerStub) CreateConfiguration(configuration *common.Configuration) (*common.Configuration, error) { - if deployer.FailCreate { - return nil, errTest - } - if deployer.FailCreateResource { - return &resourcesWithFailureState, errTest - } - - deployer.Created = append(deployer.Created, configuration) - return &resourcesWithSuccessState, nil -} - -func (deployer *deployerStub) DeleteConfiguration(configuration *common.Configuration) (*common.Configuration, error) { - if deployer.FailDelete { - return nil, errTest - } - deployer.Deleted = append(deployer.Deleted, configuration) - return nil, nil -} - -func (deployer *deployerStub) PutConfiguration(configuration *common.Configuration) (*common.Configuration, error) { - return nil, nil -} - -type repositoryStub struct { - FailListDeployments bool - Created []string - ManifestAdd map[string]*common.Manifest - ManifestSet map[string]*common.Manifest - Deleted []string - GetValid []string - ChartInstances map[string][]string - ChartInstancesCleared bool - GetChartInstancesCalled bool - ListTypesCalled bool - DeploymentStates []*common.DeploymentState -} - -func (repository *repositoryStub) reset() { - repository.FailListDeployments = false - repository.Created = make([]string, 0) - repository.ManifestAdd = make(map[string]*common.Manifest) - repository.ManifestSet = make(map[string]*common.Manifest) - repository.Deleted = make([]string, 0) - repository.GetValid = make([]string, 0) - repository.ChartInstances = make(map[string][]string) - repository.ChartInstancesCleared = false - repository.GetChartInstancesCalled = false - repository.ListTypesCalled = false - repository.DeploymentStates = []*common.DeploymentState{} -} - -func newRepositoryStub() *repositoryStub { - ret := &repositoryStub{} - return ret -} - -// Deployments. -func (repository *repositoryStub) ListDeployments() ([]common.Deployment, error) { - if repository.FailListDeployments { - return deploymentList, errTest - } - - return deploymentList, nil -} - -func (repository *repositoryStub) GetDeployment(d string) (*common.Deployment, error) { - if d == deploymentName { - return &deployment, nil - } - - return nil, errTest -} - -func (repository *repositoryStub) GetValidDeployment(d string) (*common.Deployment, error) { - repository.GetValid = append(repository.GetValid, d) - return &deployment, nil -} - -func (repository *repositoryStub) CreateDeployment(d string) (*common.Deployment, error) { - repository.Created = append(repository.Created, d) - return &deployment, nil -} - -func (repository *repositoryStub) DeleteDeployment(d string, forget bool) (*common.Deployment, error) { - repository.Deleted = append(repository.Deleted, d) - return &deployment, nil -} - -func (repository *repositoryStub) SetDeploymentState(name string, state *common.DeploymentState) error { - repository.DeploymentStates = append(repository.DeploymentStates, state) - return nil -} - -// Manifests. -func (repository *repositoryStub) AddManifest(manifest *common.Manifest) error { - repository.ManifestAdd[manifest.Deployment] = manifest - return nil -} - -func (repository *repositoryStub) SetManifest(manifest *common.Manifest) error { - repository.ManifestSet[manifest.Deployment] = manifest - return nil -} - -func (repository *repositoryStub) ListManifests(d string) (map[string]*common.Manifest, error) { - if d == deploymentName { - return manifestMap, nil - } - - return nil, errTest -} - -func (repository *repositoryStub) GetManifest(d string, m string) (*common.Manifest, error) { - if d == deploymentName && m == manifestName { - return &manifest, nil - } - - return nil, errTest -} - -func (repository *repositoryStub) GetLatestManifest(d string) (*common.Manifest, error) { - if d == deploymentName { - return repository.ManifestAdd[d], nil - } - - return nil, errTest -} - -// Types. -func (repository *repositoryStub) ListCharts() ([]string, error) { - repository.ListTypesCalled = true - return []string{}, nil -} - -func (repository *repositoryStub) GetChartInstances(t string) ([]*common.ChartInstance, error) { - repository.GetChartInstancesCalled = true - return []*common.ChartInstance{}, nil -} - -func (repository *repositoryStub) ClearChartInstancesForDeployment(d string) error { - repository.ChartInstancesCleared = true - return nil -} - -func (repository *repositoryStub) AddChartInstances(is map[string][]*common.ChartInstance) error { - for t, instances := range is { - for _, instance := range instances { - d := instance.Deployment - repository.ChartInstances[d] = append(repository.ChartInstances[d], t) - } - } - - return nil -} - -func (repository *repositoryStub) Close() {} - -var testExpander = &expanderStub{} -var testRepository = newRepositoryStub() -var testDeployer = newDeployerStub() -var testRepoService = repo.NewInmemRepoService() -var testCredentialProvider = repo.NewInmemCredentialProvider() -var testProvider = repo.NewRepoProvider(nil, repo.NewGCSRepoProvider(testCredentialProvider), testCredentialProvider) -var testManager = NewManager(testExpander, testDeployer, testRepository, testProvider, testRepoService, testCredentialProvider) - -func TestListDeployments(t *testing.T) { - testRepository.reset() - d, err := testManager.ListDeployments() - if err != nil { - t.Fatalf(err.Error()) - } - - if !reflect.DeepEqual(d, deploymentList) { - t.Fatalf("invalid deployment list") - } -} - -func TestListDeploymentsFail(t *testing.T) { - testRepository.reset() - testRepository.FailListDeployments = true - d, err := testManager.ListDeployments() - if err != errTest { - t.Fatalf(err.Error()) - } - - if d != nil { - t.Fatalf("deployment list is not empty") - } -} - -func TestGetDeployment(t *testing.T) { - testRepository.reset() - d, err := testManager.GetDeployment(deploymentName) - if err != nil { - t.Fatalf(err.Error()) - } - - if !reflect.DeepEqual(d, &deployment) { - t.Fatalf("invalid deployment") - } -} - -func TestListManifests(t *testing.T) { - testRepository.reset() - m, err := testManager.ListManifests(deploymentName) - if err != nil { - t.Fatalf(err.Error()) - } - - if !reflect.DeepEqual(m, manifestMap) { - t.Fatalf("invalid manifest map") - } -} - -func TestGetManifest(t *testing.T) { - testRepository.reset() - m, err := testManager.GetManifest(deploymentName, manifestName) - if err != nil { - t.Fatalf(err.Error()) - } - - if !reflect.DeepEqual(m, &manifest) { - t.Fatalf("invalid manifest") - } -} - -func TestCreateDeployment(t *testing.T) { - testRepository.reset() - testDeployer.reset() - d, err := testManager.CreateDeployment(&deploymentRequest) - if !reflect.DeepEqual(d, &deployment) || err != nil { - t.Fatalf("Expected a different set of response values from invoking CreateDeployment."+ - "Received: %v, %s. Expected: %#v, %s.", d, err, &deployment, "nil") - } - - if testRepository.Created[0] != deploymentRequest.Name { - t.Fatalf("Repository CreateDeployment was called with %s but expected %s.", - testRepository.Created[0], deploymentRequest.Name) - } - - if !strings.HasPrefix(testRepository.ManifestAdd[deploymentRequest.Name].Name, "manifest-") { - t.Fatalf("Repository AddManifest was called with %s but expected manifest name"+ - "to begin with manifest-.", testRepository.ManifestAdd[deploymentRequest.Name].Name) - } - - if !strings.HasPrefix(testRepository.ManifestSet[deploymentRequest.Name].Name, "manifest-") { - t.Fatalf("Repository SetManifest was called with %s but expected manifest name"+ - "to begin with manifest-.", testRepository.ManifestSet[deploymentRequest.Name].Name) - } - - if !reflect.DeepEqual(*testDeployer.Created[0], configuration) || err != nil { - t.Fatalf("Deployer CreateConfiguration was called with %s but expected %s.", - testDeployer.Created[0], configuration) - } - - if testRepository.DeploymentStates[0].Status != common.DeployedStatus { - t.Fatal("CreateDeployment success did not mark deployment as deployed") - } - - if !testRepository.ChartInstancesCleared { - t.Fatal("Repository did not clear type instances during creation") - } - - if !reflect.DeepEqual(testRepository.ChartInstances, typeInstMap) { - t.Fatalf("Unexpected type instances after CreateDeployment: %s", testRepository.ChartInstances) - } -} - -func TestCreateDeploymentCreationFailure(t *testing.T) { - testRepository.reset() - testDeployer.reset() - testDeployer.FailCreate = true - d, err := testManager.CreateDeployment(&deploymentRequest) - - if testRepository.Created[0] != deploymentRequest.Name { - t.Fatalf("Repository CreateDeployment was called with %s but expected %s.", - testRepository.Created[0], deploymentRequest.Name) - } - - if len(testRepository.Deleted) != 0 { - t.Fatalf("DeleteDeployment was called with %s but not expected", - testRepository.Created[0]) - } - - if testRepository.DeploymentStates[0].Status != common.FailedStatus { - t.Fatal("CreateDeployment failure did not mark deployment as failed") - } - - if err != errTest || d != nil { - t.Fatalf("Expected a different set of response values from invoking CreateDeployment."+ - "Received: %v, %s. Expected: %s, %s.", d, err, "nil", errTest) - } - - if testRepository.ChartInstancesCleared { - t.Fatal("Unexpected change to type instances during CreateDeployment failure.") - } -} - -func TestCreateDeploymentCreationResourceFailure(t *testing.T) { - testRepository.reset() - testDeployer.reset() - testDeployer.FailCreateResource = true - d, err := testManager.CreateDeployment(&deploymentRequest) - - if testRepository.Created[0] != deploymentRequest.Name { - t.Fatalf("Repository CreateDeployment was called with %s but expected %s.", - testRepository.Created[0], deploymentRequest.Name) - } - - if len(testRepository.Deleted) != 0 { - t.Fatalf("DeleteDeployment was called with %s but not expected", - testRepository.Created[0]) - } - - if testRepository.DeploymentStates[0].Status != common.FailedStatus { - t.Fatal("CreateDeployment failure did not mark deployment as failed") - } - - if manifest, ok := testRepository.ManifestAdd[deploymentRequest.Name]; ok { - if !strings.HasPrefix(manifest.Name, "manifest-") { - t.Fatalf("Repository AddManifest was called with %s but expected manifest name"+ - "to begin with manifest-.", manifest.Name) - } - } - - if manifest, ok := testRepository.ManifestSet[deploymentRequest.Name]; ok { - if !strings.HasPrefix(manifest.Name, "manifest-") { - t.Fatalf("Repository AddManifest was called with %s but expected manifest name"+ - "to begin with manifest-.", manifest.Name) - } - } - - if err != nil || !reflect.DeepEqual(d, &deployment) { - t.Fatalf("Expected a different set of response values from invoking CreateDeployment.\n"+ - "Received: %v, %v. Expected: %v, %v.", d, err, &deployment, "nil") - } - - if !testRepository.ChartInstancesCleared { - t.Fatal("Repository did not clear type instances during creation") - } -} - -func TestDeleteDeploymentForget(t *testing.T) { - testRepository.reset() - testDeployer.reset() - d, err := testManager.CreateDeployment(&deploymentRequest) - if !reflect.DeepEqual(d, &deployment) || err != nil { - t.Fatalf("Expected a different set of response values from invoking CreateDeployment."+ - "Received: %v, %s. Expected: %#v, %s.", d, err, &deployment, "nil") - } - - if testRepository.Created[0] != deploymentRequest.Name { - t.Fatalf("Repository CreateDeployment was called with %s but expected %s.", - testRepository.Created[0], deploymentRequest.Name) - } - - if !strings.HasPrefix(testRepository.ManifestAdd[deploymentRequest.Name].Name, "manifest-") { - t.Fatalf("Repository AddManifest was called with %s but expected manifest name"+ - "to begin with manifest-.", testRepository.ManifestAdd[deploymentRequest.Name].Name) - } - - if !strings.HasPrefix(testRepository.ManifestSet[deploymentRequest.Name].Name, "manifest-") { - t.Fatalf("Repository SetManifest was called with %s but expected manifest name"+ - "to begin with manifest-.", testRepository.ManifestSet[deploymentRequest.Name].Name) - } - - if !reflect.DeepEqual(*testDeployer.Created[0], configuration) || err != nil { - t.Fatalf("Deployer CreateConfiguration was called with %s but expected %s.", - testDeployer.Created[0], configuration) - } - d, err = testManager.DeleteDeployment(deploymentName, true) - if err != nil { - t.Fatalf("DeleteDeployment failed with %v", err) - } - - // Make sure the resources were deleted through deployer. - if len(testDeployer.Deleted) > 0 { - c := testDeployer.Deleted[0] - if c != nil { - if !reflect.DeepEqual(*c, configuration) || err != nil { - t.Fatalf("Deployer DeleteConfiguration was called with %s but expected %s.", - testDeployer.Created[0], configuration) - } - } - } - - if !testRepository.ChartInstancesCleared { - t.Fatal("Expected type instances to be cleared during DeleteDeployment.") - } -} - -func TestExpand(t *testing.T) { - m, err := testManager.Expand(&deploymentRequest) - if err != nil { - t.Fatal("Failed to expand deployment request into manifest.") - } - - if !reflect.DeepEqual(*m.ExpandedConfig, configuration) { - t.Fatalf("Expanded config not correct in output manifest: %v", *m) - } - - if !reflect.DeepEqual(*m.Layout, layout) { - t.Fatalf("Layout not correct in output manifest: %v", *m) - } -} - -func TestListTypes(t *testing.T) { - testRepository.reset() - - testManager.ListCharts() - - if !testRepository.ListTypesCalled { - t.Fatal("expected repository ListCharts() call.") - } -} - -func TestListInstances(t *testing.T) { - testRepository.reset() - - testManager.ListChartInstances("all") - - if !testRepository.GetChartInstancesCalled { - t.Fatal("expected repository GetChartInstances() call.") - } -} - -// TODO(jackgr): Implement TestListRepoCharts -func TestListRepoCharts(t *testing.T) { - /* - types, err := testManager.ListRepoCharts("", nil) - if err != nil { - t.Fatalf("cannot list repository types: %s", err) - } - */ -} - -// TODO(jackgr): Implement TestGetDownloadURLs -func TestGetDownloadURLs(t *testing.T) { - /* - urls, err := testManager.GetDownloadURLs("", repo.Type{}) - if err != nil { - t.Fatalf("cannot list get download urls: %s", err) - } - */ -} diff --git a/cmd/manager/repository/persistent/persistent.go b/cmd/manager/repository/persistent/persistent.go deleted file mode 100644 index 06e6abb0c..000000000 --- a/cmd/manager/repository/persistent/persistent.go +++ /dev/null @@ -1,488 +0,0 @@ -/* -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. -*/ - -// Package persistent implements a persistent deployment repository. -// -// This package is currently implemented using MondoDB, but there is no -// guarantee that it will continue to be implemented using MondoDB in the -// future. -package persistent - -import ( - "fmt" - "log" - "net/url" - "os" - "time" - - "github.com/kubernetes/helm/cmd/manager/repository" - "github.com/kubernetes/helm/pkg/common" - - "gopkg.in/mgo.v2" - "gopkg.in/mgo.v2/bson" -) - -type pDeployment struct { - ID string `bson:"_id"` - common.Deployment -} - -type pManifest struct { - ID string `bson:"_id"` - common.Manifest -} - -type pInstance struct { - ID string `bson:"_id"` - common.ChartInstance -} - -type pRepository struct { - Session *mgo.Session // mongodb session - Deployments *mgo.Collection // deployments collection - Manifests *mgo.Collection // manifests collection - Instances *mgo.Collection // instances collection -} - -// Constants used to configure the MongoDB database. -const ( - DatabaseName = "deployment_manager" - DeploymentsCollectionName = "deployments_collection" - ManifestsCollectionName = "manifests_collection" - InstancesCollectionName = "instances_collection" -) - -// NewRepository returns a new persistent repository. Its lifetime is decopuled -// from the lifetime of the current process. When the process dies, its contents -// will not be affected. -// -// The server argument provides connection information for the repository server. -// It is parsed as a URL, and the username, password, host and port, if provided, -// are used to create the connection string. -func NewRepository(server string) (repository.Repository, error) { - travis := os.Getenv("TRAVIS") - if travis == "true" { - err := fmt.Errorf("cannot use MongoDB in Travis CI due to gopkg.in/mgo.v2 issue #218") - log.Println(err.Error()) - return nil, err - } - - u, err := url.Parse(server) - if err != nil { - err2 := fmt.Errorf("cannot parse url '%s': %s\n", server, err) - log.Println(err2.Error()) - return nil, err2 - } - - u2 := &url.URL{Scheme: "mongodb", User: u.User, Host: u.Host} - server = u2.String() - - session, err := mgo.Dial(server) - if err != nil { - err2 := fmt.Errorf("cannot connect to MongoDB at %s: %s\n", server, err) - log.Println(err2.Error()) - return nil, err2 - } - - session.SetMode(mgo.Strong, false) - session.SetSafe(&mgo.Safe{WMode: "majority"}) - database := session.DB(DatabaseName) - deployments, err := createCollection(database, DeploymentsCollectionName, nil) - if err != nil { - return nil, err - } - - manifests, err := createCollection(database, ManifestsCollectionName, - [][]string{{"manifest.deployment"}}) - if err != nil { - return nil, err - } - - instances, err := createCollection(database, InstancesCollectionName, - [][]string{{"typeinstance.type"}, {"typeinstance.deployment"}}) - if err != nil { - return nil, err - } - - pr := &pRepository{ - Session: session, - Deployments: deployments, - Manifests: manifests, - Instances: instances, - } - - return pr, nil -} - -func createCollection(db *mgo.Database, cName string, keys [][]string) (*mgo.Collection, error) { - c := db.C(cName) - for _, key := range keys { - if err := createIndex(c, key...); err != nil { - return nil, err - } - } - - return c, nil -} - -func createIndex(c *mgo.Collection, key ...string) error { - if err := c.EnsureIndexKey(key...); err != nil { - err2 := fmt.Errorf("cannot create index %v for collection %s: %s\n", key, c.Name, err) - log.Println(err2.Error()) - return err2 - } - - return nil -} - -// Reset returns the repository to its initial state. -func (r *pRepository) Reset() error { - database := r.Session.DB(DatabaseName) - if err := database.DropDatabase(); err != nil { - return fmt.Errorf("cannot drop database %s", database.Name) - } - - r.Close() - return nil -} - -// Close cleans up any resources used by the repository. -func (r *pRepository) Close() { - r.Session.Close() -} - -// ListDeployments returns of all of the deployments in the repository. -func (r *pRepository) ListDeployments() ([]common.Deployment, error) { - var result []pDeployment - if err := r.Deployments.Find(nil).All(&result); err != nil { - return nil, fmt.Errorf("cannot list deployments: %s", err) - } - - deployments := []common.Deployment{} - for _, pd := range result { - deployments = append(deployments, pd.Deployment) - } - - return deployments, nil -} - -// GetDeployment returns the deployment with the supplied name. -// If the deployment is not found, it returns an error. -func (r *pRepository) GetDeployment(name string) (*common.Deployment, error) { - result := pDeployment{} - if err := r.Deployments.FindId(name).One(&result); err != nil { - return nil, fmt.Errorf("cannot get deployment %s: %s", name, err) - } - - return &result.Deployment, nil -} - -// GetValidDeployment returns the deployment with the supplied name. -// If the deployment is not found or marked as deleted, it returns an error. -func (r *pRepository) GetValidDeployment(name string) (*common.Deployment, error) { - d, err := r.GetDeployment(name) - if err != nil { - return nil, err - } - - if d.State.Status == common.DeletedStatus { - return nil, fmt.Errorf("deployment %s is deleted", name) - } - - return d, nil -} - -// CreateDeployment creates a new deployment and stores it in the repository. -func (r *pRepository) CreateDeployment(name string) (*common.Deployment, error) { - exists, _ := r.GetValidDeployment(name) - if exists != nil { - return nil, fmt.Errorf("deployment %s already exists", name) - } - - d := common.NewDeployment(name) - if err := r.insertDeployment(d); err != nil { - return nil, err - } - - log.Printf("created deployment: %v", d) - return d, nil -} - -// SetDeploymentStatus sets the DeploymentStatus of the deployment and updates ModifiedAt -func (r *pRepository) SetDeploymentState(name string, state *common.DeploymentState) error { - d, err := r.GetValidDeployment(name) - if err != nil { - return err - } - - d.State = state - return r.updateDeployment(d) -} - -func (r *pRepository) AddManifest(manifest *common.Manifest) error { - deploymentName := manifest.Deployment - d, err := r.GetValidDeployment(deploymentName) - if err != nil { - return err - } - - count, err := r.Manifests.FindId(manifest.Name).Count() - if err != nil { - return fmt.Errorf("cannot search for manifest %s: %s", manifest.Name, err) - } - - if count > 0 { - return fmt.Errorf("manifest %s already exists", manifest.Name) - } - - if err := r.insertManifest(manifest); err != nil { - return err - } - - d.LatestManifest = manifest.Name - if err := r.updateDeployment(d); err != nil { - return err - } - - log.Printf("Added manifest %s to deployment: %s", manifest.Name, deploymentName) - return nil -} - -// DeleteDeployment deletes the deployment with the supplied name. -// If forget is true, then the deployment is removed from the repository. -// Otherwise, it is marked as deleted and retained. -func (r *pRepository) DeleteDeployment(name string, forget bool) (*common.Deployment, error) { - d, err := r.GetValidDeployment(name) - if err != nil { - return nil, err - } - - if !forget { - d.DeletedAt = time.Now() - d.State = &common.DeploymentState{Status: common.DeletedStatus} - if err := r.updateDeployment(d); err != nil { - return nil, err - } - } else { - d.LatestManifest = "" - if err := r.removeManifestsForDeployment(d); err != nil { - return nil, err - } - - if err := r.removeDeployment(d); err != nil { - return nil, err - } - } - - log.Printf("deleted deployment: %v", d) - return d, nil -} - -func (r *pRepository) insertDeployment(d *common.Deployment) error { - if d != nil && d.Name != "" { - wrapper := pDeployment{ID: d.Name, Deployment: *d} - if err := r.Deployments.Insert(&wrapper); err != nil { - return fmt.Errorf("cannot insert deployment %v: %s", wrapper, err) - } - } - - return nil -} - -func (r *pRepository) removeDeployment(d *common.Deployment) error { - if d != nil && d.Name != "" { - if err := r.Deployments.RemoveId(d.Name); err != nil { - return fmt.Errorf("cannot remove deployment %s: %s", d.Name, err) - } - } - - return nil -} - -func (r *pRepository) updateDeployment(d *common.Deployment) error { - if d != nil && d.Name != "" { - if d.State.Status != common.DeletedStatus { - d.ModifiedAt = time.Now() - } - - wrapper := pDeployment{ID: d.Name, Deployment: *d} - if err := r.Deployments.UpdateId(d.Name, &wrapper); err != nil { - return fmt.Errorf("cannot update deployment %v: %s", wrapper, err) - } - } - - return nil -} - -func (r *pRepository) ListManifests(deploymentName string) (map[string]*common.Manifest, error) { - _, err := r.GetValidDeployment(deploymentName) - if err != nil { - return nil, err - } - - return r.listManifestsForDeployment(deploymentName) -} - -func (r *pRepository) GetManifest(deploymentName string, manifestName string) (*common.Manifest, error) { - _, err := r.GetValidDeployment(deploymentName) - if err != nil { - return nil, err - } - - return r.getManifestForDeployment(deploymentName, manifestName) -} - -// GetLatestManifest returns the latest manifest for a given deployment, -// which by definition is the manifest with the largest time stamp. -func (r *pRepository) GetLatestManifest(deploymentName string) (*common.Manifest, error) { - d, err := r.GetValidDeployment(deploymentName) - if err != nil { - return nil, err - } - - if d.LatestManifest == "" { - return nil, nil - } - - return r.getManifestForDeployment(deploymentName, d.LatestManifest) -} - -// SetManifest sets an existing manifest in the repository to provided manifest. -func (r *pRepository) SetManifest(manifest *common.Manifest) error { - _, err := r.GetManifest(manifest.Deployment, manifest.Name) - if err != nil { - return err - } - - return r.updateManifest(manifest) -} - -func (r *pRepository) updateManifest(m *common.Manifest) error { - if m != nil && m.Name != "" { - wrapper := pManifest{ID: m.Name, Manifest: *m} - if err := r.Manifests.UpdateId(m.Name, &wrapper); err != nil { - return fmt.Errorf("cannot update manifest %v: %s", wrapper, err) - } - } - - return nil -} - -func (r *pRepository) listManifestsForDeployment(deploymentName string) (map[string]*common.Manifest, error) { - query := bson.M{"manifest.deployment": deploymentName} - var result []pManifest - if err := r.Manifests.Find(query).All(&result); err != nil { - return nil, fmt.Errorf("cannot list manifests for deployment %s: %s", deploymentName, err) - } - - l := make(map[string]*common.Manifest, 0) - for _, pm := range result { - l[pm.Name] = &pm.Manifest - } - - return l, nil -} - -func (r *pRepository) getManifestForDeployment(deploymentName string, manifestName string) (*common.Manifest, error) { - result := pManifest{} - if err := r.Manifests.FindId(manifestName).One(&result); err != nil { - return nil, fmt.Errorf("cannot get manifest %s: %s", manifestName, err) - } - - if result.Deployment != deploymentName { - return nil, fmt.Errorf("manifest %s not found in deployment %s", manifestName, deploymentName) - } - - return &result.Manifest, nil -} - -func (r *pRepository) insertManifest(m *common.Manifest) error { - if m != nil && m.Name != "" { - wrapper := pManifest{ID: m.Name, Manifest: *m} - if err := r.Manifests.Insert(&wrapper); err != nil { - return fmt.Errorf("cannot insert manifest %v: %s", wrapper, err) - } - } - - return nil -} - -func (r *pRepository) removeManifestsForDeployment(d *common.Deployment) error { - if d != nil && d.Name != "" { - query := bson.M{"manifest.deployment": d.Name} - if _, err := r.Manifests.RemoveAll(query); err != nil { - return fmt.Errorf("cannot remove all manifests for deployment %s: %s", d.Name, err) - } - } - - return nil -} - -// ListCharts returns all types known from existing instances. -func (r *pRepository) ListCharts() ([]string, error) { - var result []string - if err := r.Instances.Find(nil).Distinct("typeinstance.type", &result); err != nil { - return nil, fmt.Errorf("cannot list type instances: %s", err) - } - - return result, nil -} - -// GetChartInstances returns all instances of a given type. If typeName is empty -// or equal to "all", returns all instances of all types. -func (r *pRepository) GetChartInstances(typeName string) ([]*common.ChartInstance, error) { - query := bson.M{"typeinstance.type": typeName} - if typeName == "" || typeName == "all" { - query = nil - } - - var result []pInstance - if err := r.Instances.Find(query).All(&result); err != nil { - return nil, fmt.Errorf("cannot get instances of type %s: %s", typeName, err) - } - - instances := []*common.ChartInstance{} - for _, pi := range result { - instances = append(instances, &pi.ChartInstance) - } - - return instances, nil -} - -// ClearChartInstancesForDeployment deletes all type instances associated with the given -// deployment from the repository. -func (r *pRepository) ClearChartInstancesForDeployment(deploymentName string) error { - if deploymentName != "" { - query := bson.M{"typeinstance.deployment": deploymentName} - if _, err := r.Instances.RemoveAll(query); err != nil { - return fmt.Errorf("cannot clear type instances for deployment %s: %s", deploymentName, err) - } - } - - return nil -} - -// AddChartInstances adds the supplied type instances to the repository. -func (r *pRepository) AddChartInstances(instances map[string][]*common.ChartInstance) error { - for _, is := range instances { - for _, i := range is { - key := fmt.Sprintf("%s.%s.%s", i.Deployment, i.Type, i.Name) - wrapper := pInstance{ID: key, ChartInstance: *i} - if err := r.Instances.Insert(&wrapper); err != nil { - return fmt.Errorf("cannot insert type instance %v: %s", wrapper, err) - } - } - } - - return nil -} diff --git a/cmd/manager/repository/persistent/persistent_test.go b/cmd/manager/repository/persistent/persistent_test.go deleted file mode 100644 index 4581c22bc..000000000 --- a/cmd/manager/repository/persistent/persistent_test.go +++ /dev/null @@ -1,110 +0,0 @@ -/* -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. -*/ - -package persistent - -import ( - "github.com/kubernetes/helm/cmd/manager/repository" - - "sync" - "testing" -) - -var tryRepository = true -var repositoryLock sync.RWMutex - -func createRepository() repository.Repository { - repositoryLock.Lock() - defer repositoryLock.Unlock() - - if tryRepository { - r, err := NewRepository("mongodb://localhost") - if err == nil { - return r - } - } - - tryRepository = false - return nil -} - -func resetRepository(t *testing.T, r repository.Repository) { - if r != nil { - if err := r.(*pRepository).Reset(); err != nil { - t.Fatalf("cannot reset repository: %s\n", err) - } - } -} - -func TestRepositoryListEmpty(t *testing.T) { - if r := createRepository(); r != nil { - defer resetRepository(t, r) - repository.TestRepositoryListEmpty(t, r) - } -} - -func TestRepositoryGetFailsWithNonExistentDeployment(t *testing.T) { - if r := createRepository(); r != nil { - defer resetRepository(t, r) - repository.TestRepositoryGetFailsWithNonExistentDeployment(t, r) - } -} - -func TestRepositoryCreateDeploymentWorks(t *testing.T) { - if r := createRepository(); r != nil { - defer resetRepository(t, r) - repository.TestRepositoryCreateDeploymentWorks(t, r) - } -} - -func TestRepositoryMultipleManifestsWorks(t *testing.T) { - if r := createRepository(); r != nil { - defer resetRepository(t, r) - repository.TestRepositoryMultipleManifestsWorks(t, r) - } -} - -func TestRepositoryDeleteFailsWithNonExistentDeployment(t *testing.T) { - if r := createRepository(); r != nil { - defer resetRepository(t, r) - repository.TestRepositoryDeleteFailsWithNonExistentDeployment(t, r) - } -} - -func TestRepositoryDeleteWorksWithNoLatestManifest(t *testing.T) { - if r := createRepository(); r != nil { - defer resetRepository(t, r) - repository.TestRepositoryDeleteWorksWithNoLatestManifest(t, r) - } -} - -func TestRepositoryDeleteDeploymentWorksNoForget(t *testing.T) { - if r := createRepository(); r != nil { - defer resetRepository(t, r) - repository.TestRepositoryDeleteDeploymentWorksNoForget(t, r) - } -} - -func TestRepositoryDeleteDeploymentWorksForget(t *testing.T) { - if r := createRepository(); r != nil { - defer resetRepository(t, r) - repository.TestRepositoryDeleteDeploymentWorksForget(t, r) - } -} - -func TestRepositoryChartInstances(t *testing.T) { - if r := createRepository(); r != nil { - defer resetRepository(t, r) - repository.TestRepositoryChartInstances(t, r) - } -} diff --git a/cmd/manager/repository/repository.go b/cmd/manager/repository/repository.go deleted file mode 100644 index 40ebaee37..000000000 --- a/cmd/manager/repository/repository.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -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. -*/ - -// Package repository defines a deployment repository. -package repository - -import ( - "github.com/kubernetes/helm/pkg/common" -) - -// Repository manages storage for all Helm entities, as well as -// the common operations to store, access and manage them. -type Repository interface { - // Deployments. - ListDeployments() ([]common.Deployment, error) - GetDeployment(name string) (*common.Deployment, error) - GetValidDeployment(name string) (*common.Deployment, error) - CreateDeployment(name string) (*common.Deployment, error) - DeleteDeployment(name string, forget bool) (*common.Deployment, error) - SetDeploymentState(name string, state *common.DeploymentState) error - - // Manifests. - AddManifest(manifest *common.Manifest) error - SetManifest(manifest *common.Manifest) error - ListManifests(deploymentName string) (map[string]*common.Manifest, error) - GetManifest(deploymentName string, manifestName string) (*common.Manifest, error) - GetLatestManifest(deploymentName string) (*common.Manifest, error) - - // Types. - ListCharts() ([]string, error) - GetChartInstances(chartName string) ([]*common.ChartInstance, error) - ClearChartInstancesForDeployment(deploymentName string) error - AddChartInstances(instances map[string][]*common.ChartInstance) error - - Close() -} diff --git a/cmd/manager/repository/test_common.go b/cmd/manager/repository/test_common.go deleted file mode 100644 index 60efbe40f..000000000 --- a/cmd/manager/repository/test_common.go +++ /dev/null @@ -1,340 +0,0 @@ -/* -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. -*/ - -package repository - -import ( - "github.com/kubernetes/helm/pkg/common" - - "fmt" - "testing" -) - -// TestRepositoryListEmpty checks that listing an empty repository works. -func TestRepositoryListEmpty(t *testing.T, r Repository) { - d, err := r.ListDeployments() - if err != nil { - t.Fatal("List Deployments failed") - } - - if len(d) != 0 { - t.Fatal("Returned non zero list") - } -} - -// TestRepositoryGetFailsWithNonExistentDeployment checks that getting a non-existent deployment fails. -func TestRepositoryGetFailsWithNonExistentDeployment(t *testing.T, r Repository) { - _, err := r.GetDeployment("nothere") - if err == nil { - t.Fatal("GetDeployment didn't fail with non-existent deployment") - } -} - -func testCreateDeploymentWithManifests(t *testing.T, r Repository, count int) { - var deploymentName = "mydeployment" - - d, err := r.CreateDeployment(deploymentName) - if err != nil { - t.Fatalf("CreateDeployment failed: %v", err) - } - - l, err := r.ListDeployments() - if err != nil { - t.Fatalf("ListDeployments failed: %v", err) - } - - if len(l) != 1 { - t.Fatalf("Number of deployments listed is not 1: %d", len(l)) - } - - dNew, err := r.GetDeployment(deploymentName) - if err != nil { - t.Fatalf("GetDeployment failed: %v", err) - } - - if dNew.Name != d.Name { - t.Fatalf("Deployment Names don't match, got: %v, expected %v", dNew, d) - } - - mList, err := r.ListManifests(deploymentName) - if err != nil { - t.Fatalf("ListManifests failed: %v", err) - } - - if len(mList) != 0 { - t.Fatalf("Deployment has non-zero manifest count: %d", len(mList)) - } - - for i := 0; i < count; i++ { - var manifestName = fmt.Sprintf("manifest-%d", i) - manifest := common.Manifest{Deployment: deploymentName, Name: manifestName} - err := r.AddManifest(&manifest) - if err != nil { - t.Fatalf("AddManifest failed: %v", err) - } - - d, err = r.GetDeployment(deploymentName) - if err != nil { - t.Fatalf("GetDeployment failed: %v", err) - } - - if d.LatestManifest != manifestName { - t.Fatalf("AddManifest did not update LatestManifest: %#v", d) - } - - mListNew, err := r.ListManifests(deploymentName) - if err != nil { - t.Fatalf("ListManifests failed: %v", err) - } - - if len(mListNew) != i+1 { - t.Fatalf("Deployment has unexpected manifest count: want %d, have %d", i+1, len(mListNew)) - } - - m, err := r.GetManifest(deploymentName, manifestName) - if err != nil { - t.Fatalf("GetManifest failed: %v", err) - } - - if m.Name != manifestName { - t.Fatalf("Unexpected manifest name: want %s, have %s", manifestName, m.Name) - } - } -} - -// TestRepositoryCreateDeploymentWorks checks that creating a deployment works. -func TestRepositoryCreateDeploymentWorks(t *testing.T, r Repository) { - testCreateDeploymentWithManifests(t, r, 1) -} - -// TestRepositoryMultipleManifestsWorks checks that creating a deploymente with multiple manifests works. -func TestRepositoryMultipleManifestsWorks(t *testing.T, r Repository) { - testCreateDeploymentWithManifests(t, r, 7) -} - -// TestRepositoryDeleteFailsWithNonExistentDeployment checks that deleting a non-existent deployment fails. -func TestRepositoryDeleteFailsWithNonExistentDeployment(t *testing.T, r Repository) { - var deploymentName = "mydeployment" - d, err := r.DeleteDeployment(deploymentName, false) - if err == nil { - t.Fatalf("DeleteDeployment didn't fail with non existent deployment") - } - - if d != nil { - t.Fatalf("DeleteDeployment returned non-nil for non existent deployment") - } -} - -// TestRepositoryDeleteWorksWithNoLatestManifest checks that deleting a deployment with no latest manifest works. -func TestRepositoryDeleteWorksWithNoLatestManifest(t *testing.T, r Repository) { - var deploymentName = "mydeployment" - _, err := r.CreateDeployment(deploymentName) - if err != nil { - t.Fatalf("CreateDeployment failed: %v", err) - } - - dDeleted, err := r.DeleteDeployment(deploymentName, false) - if err != nil { - t.Fatalf("DeleteDeployment failed: %v", err) - } - - if dDeleted.State.Status != common.DeletedStatus { - t.Fatalf("Deployment Status is not deleted") - } - - if _, err := r.ListManifests(deploymentName); err == nil { - t.Fatalf("Manifests are not deleted") - } -} - -// TestRepositoryDeleteDeploymentWorksNoForget checks that deleting a deployment without forgetting it works. -func TestRepositoryDeleteDeploymentWorksNoForget(t *testing.T, r Repository) { - var deploymentName = "mydeployment" - var manifestName = "manifest-0" - manifest := common.Manifest{Deployment: deploymentName, Name: manifestName} - _, err := r.CreateDeployment(deploymentName) - if err != nil { - t.Fatalf("CreateDeployment failed: %v", err) - } - - err = r.AddManifest(&manifest) - if err != nil { - t.Fatalf("AddManifest failed: %v", err) - } - - dDeleted, err := r.DeleteDeployment(deploymentName, false) - if err != nil { - t.Fatalf("DeleteDeployment failed: %v", err) - } - - if dDeleted.State.Status != common.DeletedStatus { - t.Fatalf("Deployment Status is not deleted") - } -} - -// TestRepositoryDeleteDeploymentWorksForget checks that deleting and forgetting a deployment works. -func TestRepositoryDeleteDeploymentWorksForget(t *testing.T, r Repository) { - var deploymentName = "mydeployment" - var manifestName = "manifest-0" - manifest := common.Manifest{Deployment: deploymentName, Name: manifestName} - _, err := r.CreateDeployment(deploymentName) - if err != nil { - t.Fatalf("CreateDeployment failed: %v", err) - } - - err = r.AddManifest(&manifest) - if err != nil { - t.Fatalf("AddManifest failed: %v", err) - } - - dDeleted, err := r.DeleteDeployment(deploymentName, true) - if err != nil { - t.Fatalf("DeleteDeployment failed: %v", err) - } - - if dDeleted.State.Status != common.CreatedStatus { - t.Fatalf("Deployment Status is not created") - } -} - -// TestRepositoryChartInstances checks that type instances can be listed and retrieved successfully. -func TestRepositoryChartInstances(t *testing.T, r Repository) { - d1Map := map[string][]*common.ChartInstance{ - "t1": { - { - Name: "i1", - Type: "t1", - Deployment: "d1", - Manifest: "m1", - Path: "p1", - }, - }, - } - - d2Map := map[string][]*common.ChartInstance{ - "t2": { - { - Name: "i2", - Type: "t2", - Deployment: "d2", - Manifest: "m2", - Path: "p2", - }, - }, - } - - d3Map := map[string][]*common.ChartInstance{ - "t2": { - { - Name: "i3", - Type: "t2", - Deployment: "d3", - Manifest: "m3", - Path: "p3", - }, - }, - } - - instances, err := r.GetChartInstances("noinstances") - if err != nil { - t.Fatal(err) - } - - if len(instances) != 0 { - t.Fatalf("expected no instances: %v", instances) - } - - types, err := r.ListCharts() - if err != nil { - t.Fatal(err) - } - - if len(types) != 0 { - t.Fatalf("expected no types: %v", types) - } - - r.AddChartInstances(d1Map) - r.AddChartInstances(d2Map) - r.AddChartInstances(d3Map) - - instances, err = r.GetChartInstances("unknowntype") - if err != nil { - t.Fatal(err) - } - - if len(instances) != 0 { - t.Fatalf("expected no instances: %v", instances) - } - - instances, err = r.GetChartInstances("t1") - if err != nil { - t.Fatal(err) - } - - if len(instances) != 1 { - t.Fatalf("expected one instance: %v", instances) - } - - instances, err = r.GetChartInstances("t2") - if err != nil { - t.Fatal(err) - } - - if len(instances) != 2 { - t.Fatalf("expected two instances: %v", instances) - } - - instances, err = r.GetChartInstances("all") - if err != nil { - t.Fatal(err) - } - - if len(instances) != 3 { - t.Fatalf("expected three total instances: %v", instances) - } - - types, err = r.ListCharts() - if err != nil { - t.Fatal(err) - } - - if len(types) != 2 { - t.Fatalf("expected two total types: %v", types) - } - - err = r.ClearChartInstancesForDeployment("d1") - if err != nil { - t.Fatal(err) - } - - instances, err = r.GetChartInstances("t1") - if err != nil { - t.Fatal(err) - } - - if len(instances) != 0 { - t.Fatalf("expected no instances after clear: %v", instances) - } - - types, err = r.ListCharts() - if err != nil { - t.Fatal(err) - } - - if len(types) != 1 { - t.Fatalf("expected one total type: %v", types) - } -} diff --git a/cmd/manager/repository/transient/transient.go b/cmd/manager/repository/transient/transient.go deleted file mode 100644 index 774d26e75..000000000 --- a/cmd/manager/repository/transient/transient.go +++ /dev/null @@ -1,325 +0,0 @@ -/* -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. -*/ - -// Package transient implements a transient deployment repository. -package transient - -import ( - "fmt" - "log" - "sync" - "time" - - "github.com/kubernetes/helm/cmd/manager/repository" - "github.com/kubernetes/helm/pkg/common" -) - -// deploymentChartInstanceMap stores type instances mapped by deployment name. -// This allows for simple updating and deleting of per-deployment instances -// when deployments are created/updated/deleted. -type deploymentChartInstanceMap map[string][]*common.ChartInstance - -type tRepository struct { - sync.RWMutex - deployments map[string]common.Deployment - manifests map[string]map[string]*common.Manifest - instances map[string]deploymentChartInstanceMap -} - -// NewRepository returns a new transient repository. Its lifetime is coupled -// to the lifetime of the current process. When the process dies, its contents -// will be permanently destroyed. -func NewRepository() repository.Repository { - return &tRepository{ - deployments: make(map[string]common.Deployment, 0), - manifests: make(map[string]map[string]*common.Manifest, 0), - instances: make(map[string]deploymentChartInstanceMap, 0), - } -} - -func (r *tRepository) Close() { - r.deployments = make(map[string]common.Deployment, 0) - r.manifests = make(map[string]map[string]*common.Manifest, 0) - r.instances = make(map[string]deploymentChartInstanceMap, 0) -} - -// ListDeployments returns of all of the deployments in the repository. -func (r *tRepository) ListDeployments() ([]common.Deployment, error) { - l := []common.Deployment{} - for _, deployment := range r.deployments { - l = append(l, deployment) - } - - return l, nil -} - -// GetDeployment returns the deployment with the supplied name. -// If the deployment is not found, it returns an error. -func (r *tRepository) GetDeployment(name string) (*common.Deployment, error) { - d, ok := r.deployments[name] - if !ok { - return nil, fmt.Errorf("deployment %s not found", name) - } - - return &d, nil -} - -// GetValidDeployment returns the deployment with the supplied name. -// If the deployment is not found or marked as deleted, it returns an error. -func (r *tRepository) GetValidDeployment(name string) (*common.Deployment, error) { - d, err := r.GetDeployment(name) - if err != nil { - return nil, err - } - - if d.State.Status == common.DeletedStatus { - return nil, fmt.Errorf("deployment %s is deleted", name) - } - - return d, nil -} - -// SetDeploymentState sets the DeploymentState of the deployment and updates ModifiedAt -func (r *tRepository) SetDeploymentState(name string, state *common.DeploymentState) error { - r.Lock() - defer r.Unlock() - - d, err := r.GetValidDeployment(name) - if err != nil { - return err - } - - d.State = state - d.ModifiedAt = time.Now() - r.deployments[name] = *d - return nil -} - -// CreateDeployment creates a new deployment and stores it in the repository. -func (r *tRepository) CreateDeployment(name string) (*common.Deployment, error) { - r.Lock() - defer r.Unlock() - - exists, _ := r.GetValidDeployment(name) - if exists != nil { - return nil, fmt.Errorf("Deployment %s already exists", name) - } - - d := common.NewDeployment(name) - r.deployments[name] = *d - - log.Printf("created deployment: %v", d) - return d, nil -} - -// AddManifest adds a manifest to the repository and repoints the latest -// manifest to it for the corresponding deployment. -func (r *tRepository) AddManifest(manifest *common.Manifest) error { - r.Lock() - defer r.Unlock() - - deploymentName := manifest.Deployment - l, err := r.ListManifests(deploymentName) - if err != nil { - return err - } - - // Make sure the manifest doesn't already exist, and if not, add the manifest to - // map of manifests this deployment has - if _, ok := l[manifest.Name]; ok { - return fmt.Errorf("Manifest %s already exists in deployment %s", manifest.Name, deploymentName) - } - - d, err := r.GetValidDeployment(deploymentName) - if err != nil { - return err - } - - l[manifest.Name] = manifest - d.LatestManifest = manifest.Name - d.ModifiedAt = time.Now() - r.deployments[deploymentName] = *d - - log.Printf("Added manifest %s to deployment: %s", manifest.Name, deploymentName) - return nil -} - -// SetManifest sets an existing manifest in the repository to provided manifest. -func (r *tRepository) SetManifest(manifest *common.Manifest) error { - r.Lock() - defer r.Unlock() - - l, err := r.ListManifests(manifest.Deployment) - if err != nil { - return err - } - - if _, ok := l[manifest.Name]; !ok { - return fmt.Errorf("manifest %s not found", manifest.Name) - } - - l[manifest.Name] = manifest - return nil -} - -// DeleteDeployment deletes the deployment with the supplied name. -// If forget is true, then the deployment is removed from the repository. -// Otherwise, it is marked as deleted and retained. -func (r *tRepository) DeleteDeployment(name string, forget bool) (*common.Deployment, error) { - r.Lock() - defer r.Unlock() - - d, err := r.GetValidDeployment(name) - if err != nil { - return nil, err - } - - if !forget { - d.DeletedAt = time.Now() - d.State = &common.DeploymentState{Status: common.DeletedStatus} - r.deployments[name] = *d - } else { - delete(r.deployments, name) - delete(r.manifests, name) - d.LatestManifest = "" - } - - log.Printf("deleted deployment: %v", d) - return d, nil -} - -func (r *tRepository) ListManifests(deploymentName string) (map[string]*common.Manifest, error) { - _, err := r.GetValidDeployment(deploymentName) - if err != nil { - return nil, err - } - - return r.listManifestsForDeployment(deploymentName) -} - -func (r *tRepository) listManifestsForDeployment(deploymentName string) (map[string]*common.Manifest, error) { - l, ok := r.manifests[deploymentName] - if !ok { - l = make(map[string]*common.Manifest, 0) - r.manifests[deploymentName] = l - } - - return l, nil -} - -func (r *tRepository) GetManifest(deploymentName string, manifestName string) (*common.Manifest, error) { - _, err := r.GetValidDeployment(deploymentName) - if err != nil { - return nil, err - } - - return r.getManifestForDeployment(deploymentName, manifestName) -} - -func (r *tRepository) getManifestForDeployment(deploymentName string, manifestName string) (*common.Manifest, error) { - l, err := r.listManifestsForDeployment(deploymentName) - if err != nil { - return nil, err - } - - m, ok := l[manifestName] - if !ok { - return nil, fmt.Errorf("manifest %s not found in deployment %s", manifestName, deploymentName) - } - - return m, nil -} - -// GetLatestManifest returns the latest manifest for a given deployment, -// which by definition is the manifest with the largest time stamp. -func (r *tRepository) GetLatestManifest(deploymentName string) (*common.Manifest, error) { - d, err := r.GetValidDeployment(deploymentName) - if err != nil { - return nil, err - } - - if d.LatestManifest == "" { - return nil, nil - } - - return r.getManifestForDeployment(deploymentName, d.LatestManifest) -} - -// ListCharts returns all types known from existing instances. -func (r *tRepository) ListCharts() ([]string, error) { - var keys []string - for k := range r.instances { - keys = append(keys, k) - } - - return keys, nil -} - -// GetChartInstances returns all instances of a given type. If type is empty, -// returns all instances for all types. -func (r *tRepository) GetChartInstances(typeName string) ([]*common.ChartInstance, error) { - var instances []*common.ChartInstance - for t, dInstMap := range r.instances { - if t == typeName || typeName == "" || typeName == "all" { - for _, i := range dInstMap { - instances = append(instances, i...) - } - } - } - - return instances, nil -} - -// ClearChartInstancesForDeployment deletes all type instances associated with the given -// deployment from the repository. -func (r *tRepository) ClearChartInstancesForDeployment(deploymentName string) error { - r.Lock() - defer r.Unlock() - - for t, dMap := range r.instances { - delete(dMap, deploymentName) - if len(dMap) == 0 { - delete(r.instances, t) - } - } - - return nil -} - -// AddChartInstances adds the supplied type instances to the repository. -func (r *tRepository) AddChartInstances(instances map[string][]*common.ChartInstance) error { - r.Lock() - defer r.Unlock() - - // Add instances to the appropriate type and deployment maps. - for t, is := range instances { - if r.instances[t] == nil { - r.instances[t] = make(deploymentChartInstanceMap) - } - - tmap := r.instances[t] - for _, instance := range is { - deployment := instance.Deployment - if tmap[deployment] == nil { - tmap[deployment] = make([]*common.ChartInstance, 0) - } - - tmap[deployment] = append(tmap[deployment], instance) - } - } - - return nil -} diff --git a/cmd/manager/repository/transient/transient_test.go b/cmd/manager/repository/transient/transient_test.go deleted file mode 100644 index d74fbfc36..000000000 --- a/cmd/manager/repository/transient/transient_test.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -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. -*/ - -package transient - -import ( - "github.com/kubernetes/helm/cmd/manager/repository" - "testing" -) - -func TestRepositoryListEmpty(t *testing.T) { - repository.TestRepositoryListEmpty(t, NewRepository()) -} - -func TestRepositoryGetFailsWithNonExistentDeployment(t *testing.T) { - repository.TestRepositoryGetFailsWithNonExistentDeployment(t, NewRepository()) -} - -func TestRepositoryCreateDeploymentWorks(t *testing.T) { - repository.TestRepositoryCreateDeploymentWorks(t, NewRepository()) -} - -func TestRepositoryMultipleManifestsWorks(t *testing.T) { - repository.TestRepositoryMultipleManifestsWorks(t, NewRepository()) -} - -func TestRepositoryDeleteFailsWithNonExistentDeployment(t *testing.T) { - repository.TestRepositoryDeleteFailsWithNonExistentDeployment(t, NewRepository()) -} - -func TestRepositoryDeleteWorksWithNoLatestManifest(t *testing.T) { - repository.TestRepositoryDeleteWorksWithNoLatestManifest(t, NewRepository()) -} - -func TestRepositoryDeleteDeploymentWorksNoForget(t *testing.T) { - repository.TestRepositoryDeleteDeploymentWorksNoForget(t, NewRepository()) -} - -func TestRepositoryDeleteDeploymentWorksForget(t *testing.T) { - repository.TestRepositoryDeleteDeploymentWorksForget(t, NewRepository()) -} - -func TestRepositoryChartInstances(t *testing.T) { - repository.TestRepositoryChartInstances(t, NewRepository()) -} diff --git a/cmd/manager/router/context.go b/cmd/manager/router/context.go deleted file mode 100644 index 6e878c762..000000000 --- a/cmd/manager/router/context.go +++ /dev/null @@ -1,69 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package router - -import ( - "github.com/kubernetes/helm/cmd/manager/manager" - helmhttp "github.com/kubernetes/helm/pkg/httputil" - "github.com/kubernetes/helm/pkg/repo" -) - -// Config holds the global configuration parameters passed into the router. -// -// Config is used concurrently. Once a config is created, it should be treated -// as immutable. -type Config struct { - // Address is the host and port (:8080) - Address string - // MaxTemplateLength is the maximum length of a template. - MaxTemplateLength int64 - // ExpanderPort is the default expander's IP port - ExpanderPort string - // ExpanderURL is the default expander's URL - ExpanderURL string - // DeployerName is the deployer's DNS name - DeployerName string - // DeployerPort is the deployer's IP port - DeployerPort string - // DeployerURL is the deployer's URL - DeployerURL string - // CredentialFile is the file to the credentials. - CredentialFile string - // CredentialSecrets tells the service to use a secrets file instead. - CredentialSecrets bool - // MongoName is the DNS name of the mongo server. - MongoName string - // MongoPort is the port for the MongoDB protocol on the mongo server. - // It is a string for historical reasons. - MongoPort string - // MongoAddress is the name and port. - MongoAddress string -} - -// Context contains dependencies that are passed to each handler function. -// -// Context carries typed information, often scoped to interfaces, so that the -// caller's contract with the service is known at compile time. -// -// Members of the context must be concurrency safe. -type Context struct { - Config *Config - // Manager is a helm/manager/manager.Manager - Manager manager.Manager - Encoder helmhttp.Encoder - CredentialProvider repo.ICredentialProvider -} diff --git a/cmd/manager/router/router.go b/cmd/manager/router/router.go deleted file mode 100644 index 65d1e86fe..000000000 --- a/cmd/manager/router/router.go +++ /dev/null @@ -1,96 +0,0 @@ -/* -Copyright 2016 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. -*/ - -/*Package router is an HTTP router. - -This router provides appropriate dependency injection/encapsulation for the -HTTP routing layer. This removes the requirement to set global variables for -resources like database handles. - -This library does not replace the default HTTP mux because there is no need. -Instead, it implements an HTTP handler. - -It then defines a handler function that is given a context as well as a -request and response. -*/ -package router - -import ( - "log" - "net/http" - "reflect" - - "github.com/Masterminds/httputil" - helmhttp "github.com/kubernetes/helm/pkg/httputil" -) - -// HandlerFunc responds to an individual HTTP request. -// -// Returned errors will be captured, logged, and returned as HTTP 500 errors. -type HandlerFunc func(w http.ResponseWriter, r *http.Request, c *Context) error - -// Handler implements an http.Handler. -// -// This is the top level route handler. -type Handler struct { - c *Context - resolver *httputil.Resolver - routes map[string]HandlerFunc - paths []string -} - -// NewHandler creates a new Handler. -// -// Routes cannot be modified after construction. The order that the route -// names are returned by Routes.Paths() determines the lookup order. -func NewHandler(c *Context) *Handler { - return &Handler{ - c: c, - resolver: httputil.NewResolver([]string{}), - routes: map[string]HandlerFunc{}, - paths: []string{}, - } -} - -// Add a route to a handler. -// -// The route name is "VERB /ENPOINT/PATH", e.g. "GET /foo". -func (h *Handler) Add(route string, fn HandlerFunc) { - log.Printf("Map %q to %s", route, reflect.ValueOf(fn).Type().Name()) - h.routes[route] = fn - h.paths = append(h.paths, route) - h.resolver = httputil.NewResolver(h.paths) -} - -// ServeHTTP serves an HTTP request. -func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - log.Printf(helmhttp.LogAccess, r.Method, r.URL) - route, err := h.resolver.Resolve(r) - if err != nil { - helmhttp.NotFound(w, r) - return - } - - fn, ok := h.routes[route] - if !ok { - // This is a 500 because the route was registered, but not here. - helmhttp.Fatal(w, r, "route %s missing", route) - } - - if err := fn(w, r, h.c); err != nil { - helmhttp.Fatal(w, r, err.Error()) - } -} diff --git a/cmd/manager/router/router_test.go b/cmd/manager/router/router_test.go deleted file mode 100644 index d34057919..000000000 --- a/cmd/manager/router/router_test.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package router - -import ( - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" -) - -func TestHandler(t *testing.T) { - c := &Context{} - - h := NewHandler(c) - h.Add("GET /", func(w http.ResponseWriter, r *http.Request, c *Context) error { - fmt.Fprintln(w, "hello") - return nil - }) - h.Add("POST /", func(w http.ResponseWriter, r *http.Request, c *Context) error { - fmt.Fprintln(w, "goodbye") - return nil - }) - - s := httptest.NewServer(h) - defer s.Close() - - res, err := http.Get(s.URL) - if err != nil { - t.Fatal(err) - } - data, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - t.Fatal(err) - } - - if "hello\n" != string(data) { - t.Errorf("Expected 'hello', got %q", data) - } -} diff --git a/cmd/manager/testutil.go b/cmd/manager/testutil.go deleted file mode 100644 index cdf10bdc1..000000000 --- a/cmd/manager/testutil.go +++ /dev/null @@ -1,172 +0,0 @@ -/* -Copyright 2016 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. -*/ - -package main - -import ( - "errors" - "fmt" - "net/http/httptest" - "regexp" - - "github.com/kubernetes/helm/cmd/manager/router" - "github.com/kubernetes/helm/pkg/chart" - "github.com/kubernetes/helm/pkg/common" - "github.com/kubernetes/helm/pkg/httputil" - "github.com/kubernetes/helm/pkg/repo" -) - -// httpHarness is a simple test server fixture. -// Simple fixture for standing up a test server with a single route. -// -// You must Close() the returned server. -func httpHarness(c *router.Context, route string, fn router.HandlerFunc) *httptest.Server { - h := router.NewHandler(c) - h.Add(route, fn) - return httptest.NewServer(h) -} - -// stubContext creates a stub of a Context object. -// -// This creates a stub context with the following properties: -// - Config is initialized to empty values -// - Encoder is initialized to httputil.DefaultEncoder -// - CredentialProvider is initialized to repo.InmemCredentialProvider -// - Manager is initialized to mockManager. -func stubContext() *router.Context { - return &router.Context{ - Config: &router.Config{}, - Manager: newMockManager(), - CredentialProvider: repo.NewInmemCredentialProvider(), - Encoder: httputil.DefaultEncoder, - } -} - -func newMockManager() *mockManager { - return &mockManager{ - deployments: []*common.Deployment{}, - } -} - -type mockManager struct { - deployments []*common.Deployment -} - -func (m *mockManager) ListDeployments() ([]common.Deployment, error) { - d := make([]common.Deployment, len(m.deployments)) - for i, dd := range m.deployments { - d[i] = *dd - } - return d, nil -} - -func (m *mockManager) GetDeployment(name string) (*common.Deployment, error) { - - for _, d := range m.deployments { - if d.Name == name { - return d, nil - } - } - - return nil, errors.New("mock error: No such deployment") -} - -func (m *mockManager) CreateDeployment(depReq *common.DeploymentRequest) (*common.Deployment, error) { - return &common.Deployment{}, nil -} - -func (m *mockManager) DeleteDeployment(name string, forget bool) (*common.Deployment, error) { - for _, d := range m.deployments { - if d.Name == name { - return d, nil - } - } - fmt.Printf("Could not find %s", name) - return nil, errors.New("Deployment not found") -} - -func (m *mockManager) PutDeployment(name string, depReq *common.DeploymentRequest) (*common.Deployment, error) { - for _, d := range m.deployments { - if d.Name == name { - d.State.Status = common.ModifiedStatus - return d, nil - } - } - return nil, errors.New("Deployment not found") -} - -func (m *mockManager) ListManifests(deploymentName string) (map[string]*common.Manifest, error) { - return map[string]*common.Manifest{}, nil -} - -func (m *mockManager) GetManifest(deploymentName string, manifest string) (*common.Manifest, error) { - return &common.Manifest{}, nil -} - -func (m *mockManager) Expand(depReq *common.DeploymentRequest) (*common.Manifest, error) { - return &common.Manifest{}, nil -} - -func (m *mockManager) ListCharts() ([]string, error) { - return []string{}, nil -} - -func (m *mockManager) ListChartInstances(chartName string) ([]*common.ChartInstance, error) { - return []*common.ChartInstance{}, nil -} - -func (m *mockManager) GetRepoForChart(chartName string) (string, error) { - return "", nil -} - -func (m *mockManager) GetMetadataForChart(chartName string) (*chart.Chartfile, error) { - return &chart.Chartfile{}, nil -} - -func (m *mockManager) GetChart(chartName string) (*chart.Chart, error) { - return &chart.Chart{}, nil -} - -func (m *mockManager) ListRepoCharts(repoName string, regex *regexp.Regexp) ([]string, error) { - return []string{}, nil -} - -func (m *mockManager) GetChartForRepo(repoName, chartName string) (*chart.Chart, error) { - return &chart.Chart{}, nil -} - -func (m *mockManager) CreateCredential(name string, c *repo.Credential) error { - return nil -} -func (m *mockManager) GetCredential(name string) (*repo.Credential, error) { - return &repo.Credential{}, nil -} - -func (m *mockManager) ListRepos() (map[string]string, error) { - return map[string]string{}, nil -} - -func (m *mockManager) AddRepo(addition repo.IRepo) error { - return nil -} - -func (m *mockManager) RemoveRepo(name string) error { - return nil -} - -func (m *mockManager) GetRepo(name string) (repo.IRepo, error) { - return &repo.Repo{}, nil -} diff --git a/cmd/resourcifier/configurations.go b/cmd/resourcifier/configurations.go deleted file mode 100644 index c1b457ab8..000000000 --- a/cmd/resourcifier/configurations.go +++ /dev/null @@ -1,255 +0,0 @@ -/* -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. -*/ - -package main - -import ( - "github.com/kubernetes/helm/cmd/resourcifier/configurator" - "github.com/kubernetes/helm/pkg/common" - "github.com/kubernetes/helm/pkg/util" - - "encoding/json" - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - - "github.com/ghodss/yaml" - "github.com/gorilla/mux" -) - -var configurations = []Route{ - {"ListConfigurations", "/configurations/{type}", "GET", listConfigurationsHandlerFunc, ""}, - {"GetConfiguration", "/configurations/{type}/{name}", "GET", getConfigurationHandlerFunc, ""}, - {"CreateConfiguration", "/configurations", "POST", createConfigurationHandlerFunc, "JSON"}, - {"DeleteConfiguration", "/configurations", "DELETE", deleteConfigurationHandlerFunc, "JSON"}, - {"PutConfiguration", "/configurations", "PUT", putConfigurationHandlerFunc, "JSON"}, -} - -var ( - maxLength = flag.Int64("maxLength", 1024*8, "The maximum length (KB) of a configuration.") - kubePath = flag.String("kubectl", "./kubectl", "The path to the kubectl binary.") - kubeService = flag.String("service", "", "The DNS name of the kubernetes service.") - kubeServer = flag.String("server", "", "The IP address and optional port of the kubernetes master.") - kubeInsecure = flag.Bool("insecure-skip-tls-verify", false, "Do not check the server's certificate for validity.") - kubeConfig = flag.String("config", "", "Path to a kubeconfig file.") - kubeCertAuth = flag.String("certificate-authority", "", "Path to a file for the certificate authority.") - kubeClientCert = flag.String("client-certificate", "", "Path to a client certificate file.") - kubeClientKey = flag.String("client-key", "", "Path to a client key file.") - kubeToken = flag.String("token", "", "A service account token.") - kubeUsername = flag.String("username", "", "The username to use for basic auth.") - kubePassword = flag.String("password", "", "The password to use for basic auth.") -) - -var backend *configurator.Configurator - -func init() { - if !flag.Parsed() { - flag.Parse() - } - - routes = append(routes, configurations...) - backend = getConfigurator() -} - -func listConfigurationsHandlerFunc(w http.ResponseWriter, r *http.Request) { - handler := "resourcifier: list configurations" - util.LogHandlerEntry(handler, r) - rtype, err := getPathVariable(w, r, "type", handler) - if err != nil { - return - } - - c := &common.Configuration{ - Resources: []*common.Resource{ - {Type: rtype}, - }, - } - - output, err := backend.Configure(c, configurator.GetOperation) - if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return - } - - util.LogHandlerExit(handler, http.StatusOK, output, w) - util.WriteYAML(handler, w, []byte(output), http.StatusOK) -} - -func getConfigurationHandlerFunc(w http.ResponseWriter, r *http.Request) { - handler := "resourcifier: get configuration" - util.LogHandlerEntry(handler, r) - rtype, err := getPathVariable(w, r, "type", handler) - if err != nil { - return - } - - rname, err := getPathVariable(w, r, "name", handler) - if err != nil { - return - } - - c := &common.Configuration{ - Resources: []*common.Resource{ - {Name: rname, Type: rtype}, - }, - } - - output, err := backend.Configure(c, configurator.GetOperation) - if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return - } - - util.LogHandlerExit(handler, http.StatusOK, output, w) - util.WriteYAML(handler, w, []byte(output), http.StatusOK) -} - -func createConfigurationHandlerFunc(w http.ResponseWriter, r *http.Request) { - handler := "resourcifier: create configuration" - util.LogHandlerEntry(handler, r) - defer r.Body.Close() - c := getConfiguration(w, r, handler) - if c != nil { - _, err := backend.Configure(c, configurator.CreateOperation) - if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return - } - - util.LogHandlerExitWithYAML(handler, w, c, http.StatusCreated) - return - } - - util.LogHandlerExit(handler, http.StatusOK, "OK", w) -} - -func deleteConfigurationHandlerFunc(w http.ResponseWriter, r *http.Request) { - handler := "resourcifier: delete configuration" - util.LogHandlerEntry(handler, r) - defer r.Body.Close() - c := getConfiguration(w, r, handler) - if c != nil { - if _, err := backend.Configure(c, configurator.DeleteOperation); err != nil { - e := errors.New("cannot delete configuration: " + err.Error() + "\n") - util.LogAndReturnError(handler, http.StatusBadRequest, e, w) - return - } - - w.WriteHeader(http.StatusNoContent) - util.LogHandlerExit(handler, http.StatusNoContent, "No Content", w) - return - } - - util.LogHandlerExit(handler, http.StatusOK, "OK", w) -} - -func putConfigurationHandlerFunc(w http.ResponseWriter, r *http.Request) { - handler := "resourcifier: update configuration" - util.LogHandlerEntry(handler, r) - defer r.Body.Close() - c := getConfiguration(w, r, handler) - if c != nil { - if _, err := backend.Configure(c, configurator.ReplaceOperation); err != nil { - e := errors.New("cannot replace configuration: " + err.Error() + "\n") - util.LogAndReturnError(handler, http.StatusBadRequest, e, w) - return - } - - util.LogHandlerExitWithYAML(handler, w, c, http.StatusCreated) - return - } - - util.LogHandlerExit(handler, http.StatusOK, "OK", w) -} - -func getConfigurator() *configurator.Configurator { - kubernetesConfig := &util.KubernetesConfig{ - KubePath: *kubePath, - KubeService: *kubeService, - KubeServer: *kubeServer, - KubeInsecure: *kubeInsecure, - KubeConfig: *kubeConfig, - KubeCertAuth: *kubeCertAuth, - KubeClientCert: *kubeClientCert, - KubeClientKey: *kubeClientKey, - KubeToken: *kubeToken, - KubeUsername: *kubeUsername, - KubePassword: *kubePassword, - } - return configurator.NewConfigurator(util.NewKubernetesKubectl(kubernetesConfig)) -} - -func getPathVariable(w http.ResponseWriter, r *http.Request, variable, handler string) (string, error) { - vars := mux.Vars(r) - escaped, ok := vars[variable] - if !ok { - e := fmt.Errorf("%s name not found in URL", variable) - util.LogAndReturnError(handler, http.StatusBadRequest, e, w) - return "", e - } - - unescaped, err := url.QueryUnescape(escaped) - if err != nil { - e := fmt.Errorf("cannot decode name (%v)", variable) - util.LogAndReturnError(handler, http.StatusBadRequest, e, w) - return "", e - } - - return unescaped, nil -} - -func getConfiguration(w http.ResponseWriter, r *http.Request, handler string) *common.Configuration { - b := io.LimitReader(r.Body, *maxLength*1024) - y, err := ioutil.ReadAll(b) - if err != nil { - util.LogAndReturnError(handler, http.StatusBadRequest, err, w) - return nil - } - - // Reject the input if it exceeded the length limit, - // since we may not have read all of it into the buffer. - if _, err = b.Read(make([]byte, 0, 1)); err != io.EOF { - e := fmt.Errorf("configuration exceeds maximum length of %d KB", *maxLength) - util.LogAndReturnError(handler, http.StatusBadRequest, e, w) - return nil - } - - j, err := yaml.YAMLToJSON(y) - if err != nil { - e := errors.New(err.Error() + "\n" + string(y)) - util.LogAndReturnError(handler, http.StatusBadRequest, e, w) - return nil - } - - c := &common.Configuration{} - if err := json.Unmarshal(j, c); err != nil { - e := errors.New(err.Error() + "\n" + string(j)) - util.LogAndReturnError(handler, http.StatusBadRequest, e, w) - return nil - } - - if len(c.Resources) < 1 { - e := fmt.Errorf("configuration is empty") - util.LogAndReturnError(handler, http.StatusBadRequest, e, w) - return nil - } - - return c -} diff --git a/cmd/resourcifier/configurator/configurator.go b/cmd/resourcifier/configurator/configurator.go deleted file mode 100644 index 49255be94..000000000 --- a/cmd/resourcifier/configurator/configurator.go +++ /dev/null @@ -1,262 +0,0 @@ -/* -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. -*/ - -package configurator - -import ( - "fmt" - "log" - "regexp" - "strings" - - "github.com/ghodss/yaml" - "github.com/kubernetes/helm/pkg/common" - "github.com/kubernetes/helm/pkg/util" -) - -// Configurator configures a Kubernetes cluster using kubectl. -type Configurator struct { - k util.Kubernetes -} - -// NewConfigurator creates a new Configurator. -func NewConfigurator(kubernetes util.Kubernetes) *Configurator { - return &Configurator{kubernetes} -} - -// operation is an enumeration type for kubectl operations. -type operation string - -// These constants implement the operation enumeration type. -const ( - CreateOperation operation = "create" - DeleteOperation operation = "delete" - GetOperation operation = "get" - ReplaceOperation operation = "replace" -) - -// TODO(jackgr): Configure resources without dependencies in parallel. - -// Error is an error type that captures errors from the multiple calls to kubectl -// made for a single configuration. -type Error struct { - errors []error -} - -// Error returns the string value of an Error. -func (e *Error) Error() string { - errs := []string{} - for _, err := range e.errors { - errs = append(errs, err.Error()) - } - - return strings.Join(errs, "\n") -} - -func (e *Error) appendError(err error) error { - e.errors = append(e.errors, err) - return err -} - -// DependencyMap maps a resource name to a set of dependencies. -type DependencyMap map[string]map[string]bool - -var refRe = regexp.MustCompile("\\$\\(ref\\.([^\\.]+)\\.([^\\)]+)\\)") - -// Configure passes each resource in the configuration to kubectl and performs the appropriate -// action on it (create/delete/replace) and updates the State of the resource with the resulting -// status. In case of errors with a resource, Resource.State.Errors is set. -// and then updates the deployment with the completion status and completion time. -func (a *Configurator) Configure(c *common.Configuration, o operation) (string, error) { - errors := &Error{} - var output []string - - deps, err := getDependencies(c, o) - if err != nil { - e := fmt.Errorf("Error generating dependencies: %s", err.Error()) - return "", e - } - - for { - resources := getUnprocessedResources(c) - - // No more resources to process. - if len(resources) == 0 { - break - } - - for _, r := range resources { - // Resource still has dependencies. - if len(deps[r.Name]) != 0 { - continue - } - - out, err := a.configureResource(r, o) - if err != nil { - log.Println(errors.appendError(err)) - abortDependants(c, deps, r.Name) - - // Resource states have changed, need to recalculate unprocessed - // resources. - break - } - - output = append(output, out) - removeDependencies(deps, r.Name) - } - } - - return strings.Join(output, "\n"), nil -} - -func marshalResource(resource *common.Resource) (string, error) { - if len(resource.Properties) > 0 { - y, err := yaml.Marshal(resource.Properties) - if err != nil { - return "", fmt.Errorf("yaml marshal failed for resource: %v: %v", resource.Name, err) - } - return string(y), nil - } - return "", nil -} - -func (a *Configurator) configureResource(resource *common.Resource, o operation) (string, error) { - ret := "" - var err error - - switch o { - case CreateOperation: - obj, err := marshalResource(resource) - if err != nil { - resource.State = failState(err) - return "", err - } - ret, err = a.k.Create(obj) - if err != nil { - resource.State = failState(err) - } else { - resource.State = &common.ResourceState{Status: common.Created} - } - return ret, nil - case ReplaceOperation: - obj, err := marshalResource(resource) - if err != nil { - resource.State = failState(err) - return "", err - } - ret, err = a.k.Replace(obj) - if err != nil { - resource.State = failState(err) - } else { - resource.State = &common.ResourceState{Status: common.Created} - } - return ret, nil - case GetOperation: - return a.k.Get(resource.Name, resource.Type) - case DeleteOperation: - obj, err := marshalResource(resource) - if err != nil { - resource.State = failState(err) - return "", err - } - ret, err = a.k.Delete(obj) - // Treat deleting a non-existent resource as success. - if err != nil { - if strings.HasSuffix(strings.TrimSpace(ret), "not found") { - resource.State = &common.ResourceState{Status: common.Created} - return ret, nil - } - resource.State = failState(err) - } - return ret, err - default: - return "", fmt.Errorf("invalid operation %s for resource: %v: %v", o, resource.Name, err) - } -} - -func failState(e error) *common.ResourceState { - return &common.ResourceState{ - Status: common.Failed, - Errors: []string{e.Error()}, - } -} - -func getUnprocessedResources(c *common.Configuration) []*common.Resource { - var resources []*common.Resource - for _, r := range c.Resources { - if r.State == nil { - resources = append(resources, r) - } - } - - return resources -} - -// getDependencies iterates over resources and returns a map of resource name to -// the set of dependencies that resource has. -// -// Dependencies are reversed for delete operation. -func getDependencies(c *common.Configuration, o operation) (DependencyMap, error) { - deps := DependencyMap{} - - // Prepopulate map. This will be used later to validate referenced resources - // actually exist. - for _, r := range c.Resources { - deps[r.Name] = make(map[string]bool) - } - - for _, r := range c.Resources { - props, err := yaml.Marshal(r.Properties) - if err != nil { - return nil, fmt.Errorf("Failed to deserialize resource properties for resource %s: %v", r.Name, r.Properties) - } - - refs := refRe.FindAllStringSubmatch(string(props), -1) - for _, ref := range refs { - // Validate referenced resource exists in config. - if _, ok := deps[ref[1]]; !ok { - return nil, fmt.Errorf("Invalid resource name in reference: %s", ref[1]) - } - - // Delete dependencies should be reverse of create. - if o == DeleteOperation { - deps[ref[1]][r.Name] = true - } else { - deps[r.Name][ref[1]] = true - } - } - } - - return deps, nil -} - -// updateDependants removes the dependency dep from the set of dependencies for -// all resource. -func removeDependencies(deps DependencyMap, dep string) { - for _, d := range deps { - delete(d, dep) - } -} - -// abortDependants changes the state of all of the dependants of a resource to -// Aborted. -func abortDependants(c *common.Configuration, deps DependencyMap, dep string) { - for _, r := range c.Resources { - if _, ok := deps[r.Name][dep]; ok { - r.State = &common.ResourceState{Status: common.Aborted} - } - } -} diff --git a/cmd/resourcifier/main.go b/cmd/resourcifier/main.go deleted file mode 100644 index bc8ae084a..000000000 --- a/cmd/resourcifier/main.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -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. -*/ - -package main - -import ( - "flag" - "fmt" - "log" - "net/http" - "os" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "github.com/kubernetes/helm/pkg/util" - "github.com/kubernetes/helm/pkg/version" -) - -// Route defines a routing table entry to be registered with gorilla/mux. -type Route struct { - Name string - Path string - Methods string - HandlerFunc http.HandlerFunc - Type string -} - -var routes = []Route{ - {"HealthCheck", "/healthz", "GET", healthCheckHandlerFunc, ""}, -} - -// port to listen on -var port = flag.Int("port", 8080, "The port to listen on") - -func main() { - if !flag.Parsed() { - flag.Parse() - } - - router := mux.NewRouter() - router.StrictSlash(true) - for _, route := range routes { - handler := http.Handler(http.HandlerFunc(route.HandlerFunc)) - switch route.Type { - case "JSON": - handler = handlers.ContentTypeHandler(handler, "application/json") - case "": - break - default: - log.Fatalf("invalid route type: %v", route) - } - - r := router.NewRoute() - r.Name(route.Name). - Path(route.Path). - Methods(route.Methods). - Handler(handler) - } - - address := fmt.Sprintf(":%d", *port) - handler := handlers.CombinedLoggingHandler(os.Stderr, router) - log.Printf("Version: %s", version.Version) - log.Printf("Listening on port %d...", *port) - log.Fatal(http.ListenAndServe(address, handler)) -} - -func healthCheckHandlerFunc(w http.ResponseWriter, r *http.Request) { - handler := "manager: get health" - util.LogHandlerEntry(handler, r) - util.LogHandlerExitWithText(handler, w, "OK", http.StatusOK) -} diff --git a/docs/design/architecture.dia b/docs/design/architecture.dia deleted file mode 100644 index e2256d456a7bc3ea0afe714abb80a496d94fa400..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1785 zcmVbiwFP!000021MOT(Z{s!)zWY}Q&ZPxnS|ml0T5q!G7FhI9poebH0c|n1 zy0YX_bP`|s+e^wyV%bvskm-Gt1`8ju=mmp1+pb*1< zd7AQ}9yILV_B?M>K+F|4=c=<@DVdCyN_ZKci<|zKPrgiLzRcqOdQ>%TlBc=!9(a1w zzq&6!{b40$Sd;UH>;<2Su@wAEyJ%m3qUaRS3n90~&KG%+=pkyg*cs9y$Nb(HQ;jP0 zplo`3^-FNIzNPBKtE{FQZB?S?Tuzg0SC5{XbUhIWFockv3=*-(rvZh}b8@`8@nzMe zmsKY(t1c<-7P(ZCCu&#aIL}kTv$a+wm*R9i#e}DNiM?f32P^IqrSd)Zf6vq65Chhy zU%c&D&z;I7e%o|M*JN zUiE-0#PRq8*`G6{}vNf8T%>CA15;|l{fG{=1=|CAVh~CNqp1)!>`ZQwtZTf z2Ev``TI4TliU35e0}zt!IoL&zHU;DHwtT5g;Mp`4FFK5h5ytutxyGbsy z-3Z})7L`g$DA~2@g{mlEQSH+~*GweSnQB)CLv7G$^2fQ1h1_S251_yPz##G=3?VvA zT`|ia?;1kW{k(gOmIaUFI_K`#bK-O<@wD?Cw()0cOxsM9k!Nk(R*>2fqP~IaZ9zhw zsU}6{ym!y%NxIVa!?U9A6>4PwNMpzP(%(e-AXGBpn>cy=9qcUN#%IF(`X}h7w2o@c zI6$u<0uk+|sLpk7Bt*X7O_7cyr`cRIKTVM>=haLesHKs~+SqvbNd< z(I$D8X}8t*QtY_b^MAaVMx<(as=N$_z1RPmz-c$G-;#PB;6o1h9Pl~dbHMk113v74 z&jFtUJ_me17Vzyay9Ql>j{z29O0J;~h)w9zdf>C35yXHJs24l|%xVet4Avb|h7AD2 zG}x%Z5{qS6a&i^cl|>JyygASKRLEZMrz9K9#XOg*UZHZSPyX!a&^jGLfEXfWtn`DG zP2pE6o9^cl(m9A`I*6eFS`DF@mT#1d5COrpW)f(XK|0!W2zwam8_oqoY?VEtO=Ui& zZb#4U=s9{ij-D6@%b*e?m#tlfoWe7Z6V%KB1C6$p3Ca)GdVDfMSm!=)gj(b(45C)YcI07)JftY{rJN-9iI9#+Y!iv;LDNBO2(U|XF2f_VE05?$ z=4+shfbU!0Du^_VFdeRI76||&>vWh9t2ENxy2I*al-25Gp;Z&z=52u$8U0JKOeMhh&p=;Q?Z z0ELEDD6pHHbhhmfQo(=*o1_A5k_y~TO`szc+obYkIo69gbp&FaKp4Q7QInUTz%j^$ zy4Itv3=)FyI<&j{VpI^5gNzu^3#68}eygzYYq~6yD`wb-jotO&=GyWM$ z?Tnh*1xA)D?M4j+6pwVy!)U29px;3#Bc2xeF|5Xkj2$jrV7IPeIXZ0mma*ygBohr; zHthatpH35f(|U*jwA<^AL5N{lh4BdqN2ZY^GRZd9L3MCq4bpUx-ia(O4LZflmIfD7 zDmoRb^%9sYwr{2Bt0Im{!n62}OPN1*Zs%>4e3y!B`gZdQ>8fIHdknGIlN{80lG)BG zXt)+u*OL0R^xW@pwD~tWhkpk_Xg0zT_L*e|rz|SB+(Uv&2ZxlES+kEX;oyySwzmd^ zr}rQ@AOAjle6!}c-^s(T-)C_8(Vc? diff --git a/docs/design/architecture.png b/docs/design/architecture.png deleted file mode 100644 index 9b7966c9208532f5810fe34d5e30abf6a39a1d5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13472 zcmd6O2T+vjmTjXbARq`L2oe+pBqKQoB@2io$pT7Fk~4@XIVedaDzr!zN!lb2NS2&I zKyuExNwZt;IdkXSc~dnruU@_1Ds7wQ58t=<+H0-7JNTZmEa5rIa|i^2P+soNeFWlE z3j%>9i+c**8CZ+^1pk~hR+PPiIKli$tIUi>Am|YCcO=!_l9op-9$eI_YFM|7YHiH- zr)$lgU>Xe)2o@l{di_f$qm)0_OM!ZkA3-EHSY6*SolSpIdxOH1LDhue&2&PTkmcPg z+u44q>21ggT=MWOU*J+eyDb6AgQM7oc5s1df z04xMzO11_*yg_dSfA7EKL?HYQf(goK39 z(3S{l;S6c!SOv=T?s?4ugXM(6&O1=lfV;R!4K6^%i{Dg>m*m$RH9$Q)IYHIcsIul(Hh)PNl@j2Xk zNiRzy=Jk1y>-qENDJdxv?xdcc2NM&<7B6-3Rt8+K5DJ*fo;r2v7~Nl8DpudqQ;b6C zSjWf5D;|xGjvnl;R=aPS8XFs%n|~M>oO%bhlbo7baH4(t_Os$3m1KlO!R>!6uDrZ_ zaB%RA8#i?Fc4$xb8d^B2e?0mgfJe$_GnCP3zOe}po64~r7!aU`a>~ljS0=`u_iz2< z8IGBmnW9NeZS8DpVtjJ4zOHWK%a>eMeYyF*$_T%FDL8#N1=lq}!Tp{rH3I{KOP4N% zUSjhQz_uPiZLf{jKEol}+1>R$-k(Jtr=_L+{P|E(vBh=FdlzotqOggHiMO{ma*`DG z{^`@FfBf+W{C<&H`^uUI65*F3aYMf;vg#@?Z|~UHSbICijT_SQItawk%g>Ax|8Jrl za>>rG>kOpMeKobCqodgTQ_DYRXX9VJdgnwIMcVKys{~$PrL|AAPZ&Hst?W(q`@KJt z7BU##56R=gJ!AnCdY@f;(>vI`2K14v-qqB9>Qi?S(Tf2z8f0SdGfV4N6XM>|FN#F zF6_zApFefWY=e)OV=Wi^^4QtgO-)Us8I+p8f4_JDiA~O~XAb|^*;PN8lquZI%1l9{ z7YmT#m&nKj1q6PSyQ&ZenDR3x-%!m`hd@(SRz7?7ESu7KCq}>-T-{P@c_k$!W#zWk z*1VjY(8x$~?D1M6@gH9tXIDnb&q(eq8lIr6`tvg~u0J^}G-+TrsCb={;xpYG>%7<( zoXl<3ruW(22!dEu^<{ecWEWBqxgSX*3R&v`cblb_$^PJ@#FAdQ%Q8GvHymAE=V`@Q zUFz%V@^nfRGP{F`wDNSwE?>5=uo&!;p%QlAnA+c*t#eLHzA~rjvBh@t=1qUPz5V^0 z`nYKsJIB}DJUk4lytFa7CM+zhXrraoKQQnB{@&PdC{!a3$jxOfOioCcUs*X`3)o#5 zp@KZ-IUXvpGS*G+e4UUGY~$+cYLOBaMlet4KJhKs(0e=9prG1gdjXPymr{v^Dz=XV z`)yj91alg)$&+rh1WmJU*33nKAU=nWFDfdk0T_q;R942p&VK1&d1-0pdqR=V5&Ahk z`Ox5?{N%`wAKzY-mwQ@RWDO`QD&phfek?46wU3L7>(AGZt{)yA4h?^L8mF(PCuq5= z>%IjI5fPEaM>mS}w{MN=LdZhNIcNmzAF@!{+|AQeT#jSFK@mdf!2X3G97kvRIy&ff zPWC1veUCR&cbA9bU%!4G9ZetJ-qI3H84(d-?Ae#A?Ks=k+SHU)P*ClCP|72(c!io8 z>F(g@h&=xKRl>w%5~&!UBI@aKe7K)yS6o=1s%K;2kTx8}iIBi0B1+}AGjMTnv9Ym{ zk*UwsF2a`VFz0cc`M$r9Q()jRkBk&Q-iy^TczIs@j;w4vJXc(tjI3;Xim2{Vk2w$E zM5O4>10y4&2M-45`O!mG1@fDBq^0E{fSK9ZZ71u)TjDwR`S{|{i6iEojK3c*ASfv4 z>({So5^s$NQBdmAp)mi^(whwSY8s1Ta){!H3y zMhHKVkdTnGgp?UhWTI`bJ9De61x{r55D+#~MvosqwzbWNpPVm{uda(t`Cs!4M8^fl zfO(RR^uMS}2emqL?+ZezrKKfn8yj*q9d%t@mK`Y3kn`mz)X=~{z>61_Jd_q*#>VfW zqobcna@+MN#X)4+*eusP$A?f`US8(6{lP@-Z0@ip=`qkH!l<0IwzI>>&F%Q)$zY*L zU~z~7azgH`4rZ-w9hL?PAyM9EW*UBRcszn~HlB@R(}hX^CwFvoTwGX~n4CneGpeMB z_#Rg%XIiZ#*v4H!xUPT4D|7U}7?V{OhvcMn?39%w=R= zbTEJ@C-8~q$-06vKB*PL&WSQS>=q7&^gCQw+Ee*6=Tu(PsmZEamt z;U5?tCMPFXob0tM>CrsO9YnGz-Ph4!w&|&@oqS1F%F_R|iR1GbyOeef*VrpuA9n3*VO)cuVV_nEC zBH}e#;XXJxSW;4Q`SRr=)7FKr-`SKlFepG39uo4ZUgb5X5%4g|a0YR4UjU%UB*yF4 zr=V_3EZ)?27jj$s2~l)jJ=Dh-Q{E-aMk+n|SIzeK_aT#kq59MPKshVUcllhUqx(8M zT%MnA3wYNO$3`klf9)Cp0fGOsXTWG>Bd+WpAFR4QoQY!y4h;FMdQWooLad6%7? z{pr*Fim4)P=GcwZvXCD8@tQNVw5fQc3_K}1xxM_s)Q5e6GWLnCHA@Np#<=W8wS6^|Pb`YbFtfd`OsRAX-Cz7wY| zZee47CCs4UWG+3DK_x{`Rn^GceBjai#&kriEfOKlEDR(*EssqExT`0Pfl0TuMZ5yv2rD#J8$mTNe$ms$bq)3=B+@yW=@j%Iz2 z&8)4hAvPa8cwlP!e(Py3#H6r*0Mv-Yu|ozXCb`aA(;5Ftr6h?{JQlJFs*iOj)zsCe ze*UDR`ASS=EFW5GJF)bvXfXQEZaxHvfnD%>4ug|+Sf4om)YAZ`A^ zb53ji;yGD%Tha?%DA`CFc$`WgQ9#ehSs&23Yt_AVq z7v$!~JW-Y;2DJL-0_#>@UOtc_@MJ>E&>PvmSYS{o$k+`khOqM-h28vVFGrM{?SnbYl15R zAK$;1v5t?8m3Q=TbCYu{<)N7)AUW$tGuxM^3*<6M+}B51TKeI`ho+`Lw76s06|evZ z?$>s9s#@cmJxfVN#i5VdS{f|sA}=Ug-->$n>=}=lvT_6u<1d2l5qEU38+n=Abak{6 z@C|4vdM42>L%iT1ac*v|>(>D0=<^=WYU>*lcDgz{bHD`}|~bJ~OqgvC-{lyC3dU9t#T#)F6njm6a8MsAx1S zf@~NCm+OjN79$^DUw{AZOW9j-9EM^ZTjsQwBbCdf9zw=hV;-uv1kw6(*;oYJJ&=lL zX9fi`fS;&_S<-U2Qcr4)ApkQ}v3nOqyRYcx8sUlFs{qO+?V6~Iqf=Nk9 zNSuT)1>tzV-Is*JPYe`Y8a`b5`t|D~`%}^L^YipFVFJzzJ?QO4B$cS7!^Af{J3BjW zZf|S&M<|<3smkWk%eZH@sE=3cusOtGgSP zlqEu5P7WSR-NC^DmKPLVX6iLGZC#ZpjXpi-dtz+h$4LB_se3n6bn-Okr93`Be&YK7 zTwKg?y6RDBFxJuW(dWnm)9YMFfvJEW)>DQ zt2#$BX}xj7x)e2Yo%sBnVV zva+&;Y)5AX(VXM)@lFB)0&=ym!Wl*QfZ;6U;$wK+vU}9rS2FK@ce-moJ?DvG| zgakp@4A@;>DJdy-eU!SE79+0zRx&*)!`*>mi)fkjruOz!abKUw_0G;tfJb9% zF;q%`kB1i=7RCzd`Q}Y{L4b#M;b6>dG8}YPMMXuq>ncdLmQ9h)R1kfQ4-a;hO&TIZ zfiwR6%x4p-OR$1QOsJT|&7*bJ+@w&ps;ejO(DVZg&k560YQsew=3RD1uq zyqsSw=Tvd51G#^i;?}s;`_ElP&I2gy7d zg+;VHc5ds<2Omo#?|UDvH`v* zR5k6v4^S5CQc0sC!k?p^Cp+4K2O@I`sJxjo%tJ$F|KX~pz( zD)lk`#iyd8Iu`)awas-)6_s|(Ixd5|0-$7ZTwlgC=eHuF=&<%v5+wL~|0BSTk&ksd z6Rh&>e`vq*2`aii=L*=zz<2ii6((H&&yg2te|a6n#l;XMR8&;43Xd%;+?R)VEw)|4 zIapX6ot?>syIRj;OEUe|Id(b^?d%No^jO&z$jG}jG&CxY(Tm_hLQ(QR_;bim)V!dwvU` z)V%dWrK*5;I12Le>l+*BiBJw2&!s}(b;$xxYU=8w=iec=?g7v7kxcId(kZetU;?TD zTn%a(T3`dDq-vU&;}R(;{WchR?TP#x!ouaHrF``CCLl=kH+ZJrcbd<%r_vG=-+S<2 zuSZP4^}#u&`r^dRF^LY zAN|dOja#wpiS`Ds9MS|sObZLwG1~Y3eah5bEI-qioE?U9R3i#sVgo^Dm3!?w0M&>4 zzH=v_XKZx+{rmSozstcO+FtB`=I{T4h?aeaih|e%dSU|8iWnLi0^1o30v3H`;hw&}`i2I((TXos zRmbZMv|}Y!{SeBS>dgV32TI`)=E&&MC?1BgR{(nq(w~ZwGXFv0HFkDoR1h{|C?Wvs zCnFFn$T^SI%3T@}H%`l*4;Xp`RU32tGO$XBu_Zq{OgB^6RXkbw0S3>r8;Wtc4+?8Q z0$_IB_&Pyab@#jz6~C<>7z;{D;emk-04iW4a?eRKOcutbp`5ST5_&(`6jJPfdMKRrReCo&*sHHuTL(gOeu4)QZhE;n>(%zf_b%ye zV~98~nYRRfJi(uHEwvk4nVxpunp4HW3A`P69)dUA4NJlWq89xUh)&gZpDrXe8MYB< zDX8a%>x5~cQJj9q#*iB0;0;bM=0!T?DOozSC9j4(LXJAy#hIPD4eNI0D6GtUA>V zD~*cD9m%MAnj}`i85oLLs<#VBWx!`uRFW7-hL!xu%m^nCn$Dfnm;^r3QK9x4i zl)x!;xc=mw{FuqZwQk=!OsfXyp^C`*H|S0@5S|sH7Ze<9X;}>Ar~)r$3^*}dK9G>= zJ)j->f>>S%jids_JKw`e+U`u1-Su@r`=1EG^hzGeRw%Bi?lTEcyCLC#83DXH2>RLf zmEfgURY8>+1>XX?L|_Xj#VOdi1NYrt=*`d1=i}o8AfXJGq5SB)s0juPH+K;z^2es8 z3RYR_%%fZfuNki1CJw+b@~tjK8XB76GP{C;0(q#6RoyT;pw&D~uswC_38oqnIV>sPOigB%3P0+-Cu%(Ju@_{-MH%gr72IamgA zcr)UPs0R=Y=f3WYKy*U;1~T}+GuX9RF6H6|vbdw!@&vl@aF)FG|!;ktggOE?w3 z^1K6L%TS#9?A$x*ouCW!JF8>jE=vQ3)*G{4bi6W=G~PeIK2=SZ!SlBV>du(r_72SR zEA{~JP#`3UvvYEAh-g?>7EnS)pfj`|JfNVW0#+nmQ5<{ZWMh-(gm@nep8(T@r~CuF z1%R*UTRv9qS}-VT3tcGqe^JgBA$a*kA3xqhg`~^{()zp>asLEFA9$?K$-&qrFEJtp zT7?U%t79d2wco!lf%pNa3m2wUrZ|mr-nwF!my?tDq~7M$bsB@h&>+0@#Tl)Tr%xpn z`2+cG-J;|%zgHA`_wHRC^d}|QT?TQzrZmZCARyl8KQc1vTxwhGRuQ+E9;k=shhnn0 zxLBf-S6KK&vGxvVnxpYR+Ny(<^1i@rF5{ZD_4WO+lOsc<6;?+EvuoFzKmQ;jc6Q`t0~TgWJYx&S}-`ivpZ$ivQNpCMR=@85F=rQxo5xVS7_R9Oxz zpbHepp+}584y7IHAUFZQ>RR&$OuT`QwWdT|=C$lOSgjTZk`-t0m4Sy+$=G;;Nc@ma zY{_o6hvsrMBF)purUvLA|Fvt^pzf$ySuJYhXx?C8;1g#3?b!i9fJz1Zs12rFzoG9) zb1}3C+uGV_L_M7P)sIM_Z2(~DG|zzY2V=eKhEqrFA5a0F04)KuGA-YE1MCP|$<$oN z0ec?}>+lhCf+#*C4D3;cn>NSXqzs=KaUHX=LRMka^~h7VtGH4+Izt)q(HVhVZ!c&#()#$kRzRY`wgNr`aRDXS6TLML)gKuyif3W~oD6&1sBm%L&$a0w6#1>N%?i@}Gw;caZ*o2>!D zicLQUA0Zk;6COk>!^_XFg^G$G=2+X>+*CtFx#R$jc>B5WzR=9E@qP{LR3`l;jlxOg z8OhHs%Wnqy%z2;}*_zLeE2XWSz>|wC5fK&TFsx!N1&;wr#&8J=WSraNxk`i7moLE! zV$U|-qf;_K97^P)KEmHOx z)xH2k;w_h}C<|F@A(B(%zho`)40E;L{kS?Z=pBH?J6z%ccL5h1*jZZxITfp^mD77) zS0tx7l&3UdSn31IpFr2(lj zgwzE)7Z+^o^XE%HetZq&N%6wl8^FM6MLmimF-QArE)zJ14yA>;xq$JvAoxWW)FZ^4 zroN{*LD~V~6#-5F6(J#X$J^Udj;Rs0J4T8}Y@!#%jj1nO=q&90?E_)VVMBf50F03g z#`Ed3YdPoA;>T_9y*t2)k7buU`*-I@q8!KpP4izCPpc0WA!5rO2veZtYJ)B!O?KRl z9`S2@CGDd{_xn7lqQ1N3aGnd zUYkmucEiu(KK$A7qE%N{?@6$`aR<}!irEE!?TU!oy|{q4{fn^EK#_&SHC8piM$phe zI+1x3Ytg3b!>$@aab0f{UIivQJU9sY^RShF>|!;}okK0J(_ot@#`b!9K6>*tXP8`3X>=r>B?VgPtz~?0QaaV1CHP zctq%H-x5?SHTT(ekGc zN=z&V&J1v=rJ?Z;hr3f@56b~WO#@Nlrb z-J#fkU)Idy!A!yvH{?`RQ-g^jBXAPT%*ZcXxT=5i=HZ4zIEa^UO5WziM!7uA(6L(fJeo_F zOl@pLfdT+a0mD7`2!tIl6o?>La;ip zpYRBE_4O_)Mz0t$m5-sz=(vFGS0P8?cL*rJpqzx6qZw*#Rm#`3a6TjEd@5}bjJJJh z8JSDu{cxxHn+QN)uDEJ~ZT<@VD+tR{G&QJ8<`DwyQ zoX%w`cQ|g1+bOG}k;}NyCU&=9z{Yg?5DxGe#09+5euo0ir~H1|-<}KK1_u+=7$s6+ zcClmnO_ESnZtheMUdJzUnp8O%adZwE7Os=<7A>5eDSb0j83uh0VFPTL$Ruzu?`73y zDlaT9*22tMTXM_eD-!K@;AHg0yFE=9?0WFvGo&6sUfDs;nKe1M5mC8em1MwVX=@lm zF$D+hTyN+~TCXLvb04La>Kk6~BO4RyTdZoztpxOc3_R`Pu|sW&CCE&L6Z=P-jcSbG z7GDmX|`mzYC^M@a3BtBY>1agVc{rwqP0NM{L^KfQSK5{E%jR1_Ue+dQLSZDZqy{QMP|oVx;| z8Un%%e0C(uLYmLU=Bp^q4lDB#oio(~AIk(BXTrL~nf3*Rg`w*WQ&CDH&KPG9#3+Nw z%4nr9v=snWnC~0$cbs^=V1hQ_P_c!Q&;i&=^o3CUs`qCSVW#mG9o+zfgR_d`Cqpy)uU9w&FBz!k2j9)0{j!UZ2HbCvq#V8y&iOmua-4GkS8Cg_}t0mBE0_KZku z55{Vb!SQ*ZtzCJvGYrr`wtIB`{CVHKT3Q3Q@#l;E1qCqsMsOL{5g6ssQc*j$B&KJG z8BWdD`*J_2Xk9md>tj7fNl*2sN#CDRp77?y$&0|pKY zkkJ&6w>&DHW?yAzGq0!lYvy435ZEw;2>7Lxe;ndE~Z6F@X3 z=h7|i=H_N}Z-LJZTq#(Od-v`^=ami@W+%k#e}4U-nhtIov?!s^rV6|tx8~o?0|s4d zmkmBmmaUXcc=%QJR|X;U&KV3|0ENJWC(;^}wAmD~e@?O0nAtH)& zi5>d2-x&;L4|spM6;T7FrKOjspeankkqAQ|mj&$8=wLP#F07zuX=^J6O<+l4%>8>8 z0WyF``?&{(4Re1$#{&YJH~{PP*&y+65YKXfLT~JyoxQ;_y##|>8XCPzO);8DBJN!< z+B-CaPIeu=;0Y(fOa)=O$5cdZppGJqfKNi}A|%x4#+;hM|p})}5+zc*7w9$lE)@B1IrT7(IOLed{6rVxs z1uB3U^peD^=P6!*=2Tw>GdN)8yyDz8`U1U7@h*jo;&jtC14mT`Oa=hKQk_!M-`UwA zTCD~S2M!xUU-wg5)ZRo)TwDYtuO-Y;7|+6HfVJTc&KGpQV9>ChqoEpwzQE$(&R|>MQo$i1B*HKQf+1TBXNoI_42oNh z=Xol-9%FP)8ViYxqVOvyRWNjCQ8KhzbcMp#@uV1^lyHg9;ODq-S<>|vf zJn^B@dHTS|XDL^bd@U?I17d8x&Hyc`@p4QT(3yCp~3 zJzni~(`{Wz(ovyci|scffw|Z6&!N10d_IeLWe|#Q2<}zXc;w4{p{pD46$uYk;4J_B?^U=G;vlvzU}#ai-$?_E^@3!LfNx@WRARJ_qJ$dQ z=l}P=objK2^@H#p3cjvEVp*btynj<)n)b=bu{YeoDrnvge)vKJlm4lW0%-nk@j{?` zRDzsFgDH9Trv51pQlN5G^>D}YBnW#XBb??G@TLFxmq-57`}=FY^MCu*l>a}^&Mz^4 rs^bO>jlx9tuW#{iVB!F=e?&`lyI7e~i_8%Ihme<6zLPKY(Er~6ZHfTt diff --git a/docs/design/chart_format.md b/docs/design/chart_format.md deleted file mode 100644 index 095ff4829..000000000 --- a/docs/design/chart_format.md +++ /dev/null @@ -1,363 +0,0 @@ -# Helm Charts - -This document describes the Helm chart format, its presentation as an -archive, and its storage and retrieval. - - -* [Changes](#changes) -* [tl;dr: Summary](#tldr-summary) -* [Goals](#goals) - * [Non-Goals](#non-goals) -* [The Chart Format](#the-chart-format) - * [Directory Layout](#directory-layout) - * [The Chart File](#the-chart-file) - * [Releasing a Chart](#releasing-a-chart) -* [The Chart Repository](#the-chart-repository) - * [Repository Protocol](#repository-protocol) - * [Aside: Why a Flat Repository Namespace?](#aside-why-a-flat-repository-namespace) - * [Aside: Why Not Git(Hub) or VCS?](#aside-why-not-github-or-vcs) -* [Chart References: Long form, short form, and local reference](#chart-references-long-form-short-form-and-local-reference) - * [Long Form](#long-form) - * [Short Form](#short-form) - * [Local References](#local-references) -* [References](#references) - - -## Changes - -| Date | Author | Changes | -| ------------|:--------------:|:-------------------------------------------------------| -| 2016-01-21 | mbutcher@deis | Added manifests/ to chart. | -| 2016-01-25 | mbutcher@deis | Added clarifications based on comments. | -| 2016-01-26 | mbutcher@deis | Added hook/, removed manifests/. Added generate header.| -| 2016-03-24 | mbutcher@deis | Updated title and intro of document. | -| 2016-04-01 | dcunnin@google | Updated expander / schema post-DM merge | - - -## tl;dr: Summary -* A **chart** is a folder or archive containing a _Chart.yaml_ file, a _templates/_ directory with one or more template files, supporting files, such as schemas and UI directives, and optionally a _README.md_ and _LICENSE_ file. -* When a chart is **released**, it is tested, versioned, and loaded into a chart repository. -* A **chart repository** is organized according to the conventions of object storage. It is accessible by combining a domain with a repository ID (bucket), and can be browsed. A chart repository contains one or more versioned charts. -* There are three ways to reference charts. A **local reference** references a chart by relative or absolute file system path. It is intended for development. A **long name**, or fully qualified URL, refers to a released chart (in a chart repository) by URL. A **short name**, or mnemonic, is a shortened reference to a chart in a chart repository. A short name can be converted to a **long name** deterministically, and can therefore be used anywhere a long name can be used. - -## Goals -The goal of this document is to define the following aspects of Helm charts: - - -* The **format of a chart** -* The **layout of a chart repository**, with recommendations about implementations -* The format of a **short name** (mnemonic name) of a chart, as well as its fully qualified **long name**, and conventions for referencing local charts instead of short/long names. - - -We assume that we are developing a technology that will enjoy widespread use among developers and operators who… - - -* Are familiar with general Kubernetes design -* Are capable of reading and writing high-level formats like JSON, YAML, and maybe shell scripts -* Have low tolerance for complexity, and are not willing to become domain experts, but… -* Advanced users may be interested in, and willing to, learn advanced features to build much more interesting charts - - -Based on these, our design goal is to make it **easy to find, use, author, release, and maintain charts**, while still making it possible for advanced users to build sophisticated charts. - -This design is based on the integration of Deployment Manager (DM) and Helm, formerly at github.com/deis/helm. It does not lose any of the functionality of DM, and loses only Helm functionality that we believe is disposable. Substantial portions of the chart and template handling logic from the original implementations of these two tools have changed as a result of this integration. The new chart format, described in this document, is one of the primary drivers of those changes. - - -### Non-Goals -This document does not define how either the client or the server must use this data, though we make some non-normative recommendations (e.g. “this data may be displayed to the user” or “a suitable backend will be able to translate this to a fully qualified URL”). - -Consequently, this document does not describe the implementation of either client or server sides of this spec. While it defines the pieces necessary for developing and deploying a chart, it does not define a development workflow. Development workflows are discussed in [another document](../workflow/team-workflows.md). - - -## The Chart Format -We define a Chart as a bundle of data and metadata whose contents are sufficient for installing a workload into a Kubernetes cluster. - - -A Chart is composed of the following pieces: - - -1. A human- and machine-readable metadata file called `Chart.yaml` -2. A directory named `templates/` -3. An optional `README.md` file -4. An optional `LICENSE` file -5. An optional `docs/` directory -7. An optional `icon.svg` - - -A chart _may_ have other directories and files, but such files are not defined by or required by this document. For the purposes of this document, any additional files do not contribute to the core functionality of chart installation. - - -The Chart.yaml file format is described [later in this document](#the-chart-file). - - -The `templates/` directory contains one or more template files, as defined in the [Directory Layout](#directory-layout) section. Templates, along with all of their supporting files, are located therein. - - -An optional `README.md` file may be specified, which contains long-form text information about using this chart. Tools may display this information, if present. The README.md file is in Markdown format, and should contain information about a Chart’s purpose, usage, and development. - - -An optional `LICENSE` file may be present, which specifies license information for this chart and/or the images dependent on it. - - -An optional `docs/` directory may be present, and may contain one or more documentation files. This directory contains documentation that is more specific, verbose, or thorough than what is present in the `README.md` file. - -### Directory Layout -A chart is laid out as follows. The top level directory (represented by the placeholder ROOT) must be the name of the chart (verified by linter). For example, if the chart is named `nginx`, the ROOT directory must be named `nginx/`. - -``` -ROOT/ - Chart.yaml - README.md - LICENSE - docs/ - some.md - templates/ - some.yaml - some.jinja - some.jinja.schema -``` - -Templates are stored in a separate directory for the following reasons: - - -* This future-proofs the format: we can add other directories at the top level and deprecate `templates/`. -* It allows authors to add other files (such as documentation) in a straightforward way that will not confuse definitions-aware tools. -* It allows for the possibility that a chart definition may be embedded inside of project code. - - -Charts must not assume that they can perform file system operations to load another chart or supporting resources directly via the filesystem, nor should they store any operational files outside of the chart directory. This point becomes important in the case where there is Python/Jinja or other executable code inside the chart. These executable components especially should not assume they can access the host filesystem. It should be possible to archive the chart directory (e.g. `tar -cf ROOT.tar ROOT/`) and not lose any necessary information. A chart is an all-encompassing unit that can be processed by the client/server. - -### The Chart File -The `Chart.yaml` file specifies package metadata for the definition. Package metadata is any information that explains the package structure, purpose, and provenance. Its intended audience is tooling that surfaces such information to the user (e.g. a command line client, a web interface, a search engine). - - -A definition file does not specify anything about individual pieces of the definition (e.g. description of per-field schema or metadata), nor does it contain information about the parent environment (e.g. that it is hosted on S3 or in GitHub). Its scope is the definition as a whole. - - -Fields: - - -* name: A human-readable name of the definition. This may contain UTF-8 alphanumeric text, together with dash and underscore characters. -* description: A human-readable description of the definition, not to exceed one paragraph of text. No text formatting is supported. A description may use any printable text characters allowed by the file format. -* version: A SemVer 2 semantic version of the chart (template files). Refer to the [instruction on semver.org](http://semver.org/). -* keywords: A list of human-readable keywords. A keyword may contain alphanumeric text and spaces. -* maintainers: A list of author objects, where an author object has two properties: - * name: Author name - * email: Author email (optional) -* source: A URL to the source code for this chart -* home: A URL to the home page for the project -* expander: Indicates how to process the contents of templates/ (optional) - * name: The name of the expander, as a Kubernetes service name or URL. - * entrypoint: If the expander requires an entrypoint, gives the file (optional). -* schema: The file used to validate the properties (user-configurable inputs) of this chart before expansion. (optional) - -Example: - -``` -name: nginx -description: The nginx web server as a replication controller and service pair. -version: 0.5.1 -keywords: -* https -* http -* web server -* proxy -source: https://github.com/foo/bar -home: http://nginx.com -expander: - name: goexpander-service -schema: Schema.yaml -``` - -### Expanders and Templates -The content of the `templates/` directory and the schema file are defined by the particular expander invoked by name in the Chart.yaml file. Such expanders consume these files, in the context of properties, to generate content. If a schema is given, the expander may use it to validate the properties before expansion. Discussion of the available expanders and how they intepret the content of /templates is outside the scope of this document. - -If no expander is specified, files with yaml or json extensions in the templates/ directory are parsed as Kubernetes API objects and included in the deployment without transformation. Charts may therefore contain Kubernetes API objects that do not contain any parameters or require any server side processing before being sent to Kubernetes. Such charts can therefore not invoke other charts. - -### Releasing a Chart -A chart is _released_ when the source of the chart is tested, versioned, packaged into a gzipped tar file. At that point, that particular release of a chart is considered immutable. No further changes are allowed. To enforce immutability through tamper detection, Charts must be digitally signed. - - -Releases must follow a SemVer 2 version pattern. - - -A released chart may be moved into a chart repository. - - -NON-NORMATIVE: A release pattern _might_ look like this: - -``` -$ helm release -r 1.1.2 ./nginx/ --> generated archive --> signed archive. Signature ‘iEYEARECAAYFAkjil’ --> generated ./nginx-1.1.2.tgz --> uploading to gs://kubernetes-charts/nginx-1.1.2.tgz --> done -``` - -## The Chart Repository -A _Chart Repository_ is a place where _released copies_ of one or more charts may reside. A Helm Chart Repository is analogous to a Debian package repository or a Ruby Gems repository. It is a remote storage location accessible over a well-documented protocol (HTTP(S), and following fixed structural conventions. - - -Chart repositories are styled after the predominant pattern of [object storage systems](https://cloud.google.com/storage/docs/key-terms). A _domain_ hosts one or more _repositories_. A repository is a bucket in which one or more charts may be stored. In an object storage system, this is represented by the pattern: **https://domain/bucket/object**. In object storage, the _object_ part of the URL may contain slashes, hyphens, underscores, and dots. Thus in the URL [https://storage.googleapis.com/helm/nginx-1.1.2] the object is _nginx-1.1.2_. The general pattern of a chart repository is: https://domain/repository/chart - - -A chart name should be of the form _name-version.ext_, where _name_ is the chart name (alpha-numeric plus dash and underscore), and version is a SemVer v2 version. The extension, _ext_, should reflect the type of compression used to archive the object. This specification only discusses gzipped tar archives, but other mechanisms could be supported. - - -Because of the way object storage systems work, a repository should be viewable as a directory tree: - -``` -gs://kubernetes-charts/charts/ - apache-2.3.4.tgz - nginx-1.1.1.tgz - nginx-1.1.2.tgz - redis-0.4.0-alpha.1.tgz -``` - -A helm chart is a gzip-compressed tar archive (e.g. `tar -zcf …`). - -### Repository Protocol -A repository must implement HTTP(S) GET operations on both chart names (nginx-1.2.3) and chart signatures (nginx-1.2.3.sig). HTTP GET is a rational base level of functionality because it is well understood, nearly ubiquitous, and simple. - - -A repository may implement the full Object Storage APIs for compatibility with S3 and Google Cloud Storage. In this case, a client may detect this and use those APIs to perform create, read, update, delete, and list operations (as permissions allow) on the object storage system. - - -Object storage is a rational choice for this model because it is [optimized for highly resilient, distributed, highly available read-heavy traffic](https://en.wikipedia.org/wiki/Object_storage). The S3-like API has become a de facto standard, with myriad client libraries and tools, and major implementations both as services (S3, GCS), platforms (OpenStack Swift), and individual stand-alone servers (RiakCS, Minio, Ceph). - - -This document does not mandate a particular authentication mechanism, but implementors may implement the authentication token mechanisms on GCS and S3 compatible object storage systems. - -### Aside: Why a Flat Repository Namespace? -The format for a repository has packages stored in a flat namespace, with version information appended to the filename. There are a few reasons we opted for this over against more hierarchical directory structures: - - -* Keeping package name and version in the file name makes the experience easier for development. In cases where manual examination is necessary, tools and humans are working with filenames that are easily identifiable (_nginx-1.2.3.tgz_). -* The flat namespace means tooling needs to pay less attention to directory hierarchy, which has three positive implications: - * Less directory recursion, especially important for base operations like counting charts in a repo. - * No importance is granted to directories, which means we could introduce non-semantic directory hierarchy if necessary (e.g. for the GitHub model of _username/reponame_. - * For object storage systems, where slashes are part of names but with special meaning, the flat namespace reduces the number of API operations that need to be done, and allows for using some of the [object storage URL components](https://cloud.google.com/storage/docs/json_api/v1/) when necessary. -* Brevity is desirable when developers have occasion to type these URLs directly, as is done in chart/template references (see, for example, the [S3 API](http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingObjects.html)). - -### Aside: Why Not Git(Hub) or VCS? -GitHub-backed repositories for released charts suffer from some severe limitations (listed below). Our repository design does not have these limitations. - - -* Versioning Multiple Things - * Git does not provide a good way of versioning independent objects in the same repo. (Two attempts we made at solving this [here](https://github.com/helm/helm/issues/199) and [here](https://github.com/deis/helm-dm/issues/1)) - * A release should be immutable, or very close to it. Achieving this in Git is hard if the thing released is not the entire repository. -* Developer Experience - * Combining release artifacts and source code causes confusion - * Substantial development workflow overhead (and over-reliance on conventions) make directory-based versioning problematic - * In most of the models specified, there is no way of determining whether a resource is in development, or is complete -* Infrastructure - * Both teams have hit GitHub API rate limiting - * Git is optimized for fetching an entire repository, not a small fraction of the repository - * Git is optimized for fetching an entire history, not a snapshot of a particular time - * Discovery of which versions are available is exogenous to Git itself (unless we use a crazy tagging mechanism) - * We hit vendor lock-in for GitHub - - -The object storage based repository solution above solves all of these problems by: - - -* Explicitly defining a release artifact as an archive file with a known format and a known naming convention. -* Explicitly defining a release as an act of archiving, naming, and signing. -* Selecting a service (object storage, but with fallback to plain HTTP) that is resilient, widely deployed, and built specifically for the delivery of release files. -* Distinguishing the development workflow from the release workflow at a location that is intuitive and common for developers -* Providing a method for verifying immutability of a version by checksum -* Removing vendor reliance -* Separating the concept of code from the concept of release artifact - - -We want to make clear that a chart may be developed in any place (such as Github repositories). This aside is referring to released and versioned charts that can be stored and shared. We do not dictate where a chart is developed. - -## Chart References: Long form, short form, and local reference -There are three reference forms for a chart. A fully qualified (long) form, a mnemonic (short) form, and a local path spec. -### Long Form -A long form chart reference is an exact URL to the chart. It should use HTTP or HTTPS protocols, and follow the typical rules of a complete URL: https://example.com/charts/nginx-1.2.3.tgz - - -A long form reference must include a protocol spec. It may also contain any pattern allowed in URLs. - - -The following URI schemes must be supported: -* http: -* https: - - -#### Special Form Google Storage and S3 -The [Google Storage scheme](https://cloud.google.com/storage/docs/interoperability) (`gs://`) and the S3 scheme (`s3://`) can also be supported. These forms do not follow the URL standard (bucket name is placed in the host field), but their behavior is well-documented. - - -Examples: -``` -gs://charts/nginx-1.2.3.tgz -s3://charts/nginx-1.2.3.tgz -``` -### Short Form -A short form package reference provides the minimum necessary information for accessing a chart. Short form URLs use a defined [URI scheme](https://tools.ietf.org/html/rfc3986#section-3.1) of `helm`:. - - -A generic short form maps to an object storage reference (`DOMAIN/REPOSITORY/RELEASE`). - -``` -helm:example.com/charts/nginx-1.2.3.tgz -``` - -Or, in the case of the Google Storage (`gs://`) and the S3 scheme (`s3://`), the domain indicates the storage scheme. - -``` -helm:gs/kubernetes-charts/nginx-1.2.3.tgz -``` - -For the purpose of providing versioning ranges, and also for backward compatibility, version requirements are expressed as a suffix condition, instead of as part of the path: - -``` -helm:example.com/charts/nginx#1.2.3 // Exact version -helm:example.com/charts/nginx#~2.1 // Version range -helm:example.com/charts/nginx // Latest release -``` - -The first of the above three short names is equivalent to helm:example.com/charts/nginx-1.2.3.tgz. The second example uses a semantic version filter ~1.2, which means “>=1.2.0, <1.3.0”, or “any patch version of 1.2”. Other filters include the “^” operator (^1 is “any minor version in the 1.x line), and “>”, “>=”, “=”, “<”, and “<=” operators. These are standard SemVer filter operators. - - -Any short form handler should be able to resolve the default short form as specified above. - - -### Local References -During chart development, and in other special circumstances, it may be desirable to work with an unversioned, unpackaged local copy of a chart. For the sake of consistency across products, an explicit naming formula is to be followed. - - -In cases where a local path is used in lieu of a full or short name, the path string _must_ begin with either a dot (.) or a slash (/) or the file schema(`file:`). - - -Legal examples of this include: -``` - ./foo - ../foo - /foo - //foo - /.foo - file:///example/foo -``` - -Unprefixed relative paths are not valid. For example, `foo/` is not allowed as a local path, as it conflicts with a legal short name, and is thus ambiguous. - - -## References - -The Debian Package Repo. [http://ftp.us.debian.org/debian/pool/main/h/] - -The Debian Maintainers Guide. [https://www.debian.org/doc/manuals/maint-guide/] - -Arch packages: [https://wiki.archlinux.org/index.php/Arch_User_Repository#Creating_a_new_package] - -Keybase.io: [https://keybase.io/] - -Google Cloud Storage API: [https://cloud.google.com/storage/docs/json_api/v1/] - -Amazon S3: [http://docs.aws.amazon.com/AmazonS3/latest/dev/Welcome.html] - -URIs (RFC 3986): [https://tools.ietf.org/html/rfc3986#section-3.1] diff --git a/docs/design/design.md b/docs/design/design.md deleted file mode 100644 index 290bdceed..000000000 --- a/docs/design/design.md +++ /dev/null @@ -1,434 +0,0 @@ -# Deployment Manager Design - -## Overview - -Deployment Manager (DM) is a service that runs in a Kubernetes cluster, -supported by a command line interface. It provides a declarative `YAML`-based -language for configuring Kubernetes resources, and a mechanism for deploying, -updating, and deleting configurations. This document describes the configuration -language, the API model, and the service architecture in detail. - -## Configuration Language - -DM uses a `YAML`-based configuration language with a templating mechanism. A -configuration is a `YAML` file that describes a list of resources. A resource has -three properties: - -* `name`: the name to use when managing the resource -* `type`: the type of the resource being configured -* `properties`: the configuration properties of the resource - -Here's a snippet from a typical configuration file: - -``` -resources: -- name: my-rc - type: ReplicationController - properties: - metadata: - name: my-rc - spec: - replicas: 1 - ... -- name: my-service - type: Service - properties: - ... -``` - -It describes two resources: - -* A replication controller named `my-rc`, and -* A service named `my-service` - -## Types - -Resource types are either primitives or templates. - -### Primitives - -Primitives are types implemented by the Kubernetes runtime, such as: - -* `Pod` -* `ReplicationController` -* `Service` -* `Namespace` -* `Secret` - -DM processes primitive resources by passing their properties directly to -`kubectl` to create, update, or delete the corresponding objects in the cluster. - -(Note that DM runs `kubectl` server side, in a container.) - -### Templates - -Templates are abstract types created using Python or -[Jinja](http://jinja.pocoo.org/). A template takes a set of properties as input, -and must output a valid `YAML` configuration. Properties are bound to values when -a template is instantiated by a configuration. - -Templates are expanded before primitive resources are processed. The -configuration produced by expanding a template may contain primitive resources -and/or additional template invocations. All template invocations are expanded -recursively until the resulting configuration is a list of primitive resources. - -(Note, however, that DM preserves the template hierarchy and any dependencies -between resources in a layout that can be used to reason programmatically about -the structure of the resulting collection of resources created in the cluster, -as described in greater detail below.) - -Here's an example of a template written in [Jinja](http://jinja.pocoo.org/): - -``` -resources: -- name: {{ env['name'] }}-service - type: Service - properties: - prop1: {{ properties['prop1'] }} - ... -``` - -As you can see, it's just a `YAML` file that contains expansion directives. For -more information about the kinds of things you can do in a Jinja based template, -see [the Jina documentation](http://jinja.pocoo.org/docs/). - -Here's an example of a template written in Python: - -``` -import yaml - -def GenerateConfig(context): - resources = [{ - 'name': context.env['name'] + '-service', - 'type': 'Service', - 'properties': { - 'prop1': context.properties['prop1'], - ... - } - }] - - return yaml.dump({'resources': resources}) -``` - -Of course, you can do a lot more in Python than in Jinja, but basic things, such -as simple parameter substitution, may be easier to implement and easier to read in -Jinja than in Python. - -Templates provide access to multiple sets of data that can be used to -parameterize or further customize configurations: - -* `env`: a map of key/value pairs from the environment, including pairs -defined by Deployment Manager, such as `deployment`, `name`, and `type` -* `properties`: a map of the key/value pairs passed in the properties section -of the template invocation -* `imports`: a map of import file names to file contents for all imports -originally specified for the configuration - -In Jinja, these variables are available in the global scope. In Python, they are -available as properties of the `context` object passed into the `GenerateConfig` -method. - -### Template schemas - -A template can optionally be accompanied by a schema that describes it in more -detail, including: - -* `info`: more information about the template, including long description and title -* `imports`: any files imported by this template (may be relative paths or URLs) -* `required`: properties that must have values when the template is expanded -* `properties`: A `JSON Schema` description of each property the template accepts - -Here's an example of a template schema: - -``` -info: - title: The Example - description: A template being used as an example to illustrate concepts. - -imports: -- path: helper.py - -required: -- prop1 - -properties: - prop1: - description: The first property - type: string - default: prop-value -``` - -When a schema is provided for a template, DM uses it to validate properties -passed to the template by its invocation, and to provide default values for -properties that were not given values. - -Schemas must be supplied to DM along with the templates they describe. - -### Supplying templates - -Templates can be supplied to DM in two different ways: - -* They can be passed to DM along with configurations that import them, or -* They can be retrieved by DM from public HTTP endpoints for configurations that -reference them. - -#### Template imports - -Configurations can import templates using path declarations. For example: - -``` -imports: -- path: example.py - -resources: -- name: example - type: example.py - properties: - prop1: prop-value -``` - -The `imports` list is not understood by the Deployment Manager service. -It's a directive used by client-side tools to specify what additional files -should be included when passing the configuration to the API. - -If you are calling the Deployment Manager service directly, you must embed the -imported templates in the configuration passed to the API. - -#### Template references - -Configurations can also reference templates using URLs for public HTTP endpoints. -DM will attempt to resolve template references during expansion. For example: - -``` -resources: -- name: my-template - type: https://raw.githubusercontent.com/my-template/my-template.py - properties: - prop1: prop-value -``` - -When resolving template references, DM assumes that templates are stored in -directories, which may also contain schemas, examples and other supporting files. -It therefore processes template references as follows: - -1. Attempt to fetch the template, and treat it as an import. -1. Attempt to fetch the schema for the template from -`/