diff --git a/cmd/tiller/environment/environment.go b/cmd/tiller/environment/environment.go index 12690a84f..5e0e81216 100644 --- a/cmd/tiller/environment/environment.go +++ b/cmd/tiller/environment/environment.go @@ -23,6 +23,7 @@ These dependencies are expressed as interfaces so that alternate implementations package environment import ( + "errors" "io" "k8s.io/helm/pkg/chartutil" @@ -31,11 +32,9 @@ import ( "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/storage/driver" + "k8s.io/kubernetes/pkg/client/unversioned" ) -// UseConfigMaps is a feature flags to toggle use of configmaps storage driver. -const UseConfigMaps = false - // TillerNamespace is the namespace tiller is running in. const TillerNamespace = "kube-system" @@ -135,6 +134,9 @@ type KubeClient interface { // reader must contain a YAML stream (one or more YAML documents separated // by "\n---\n"). Update(namespace string, originalReader, modifiedReader io.Reader) error + + // APIClient gets a raw API client for Kubernetes. + APIClient() (unversioned.Interface, error) } // PrintingKubeClient implements KubeClient, but simply prints the reader to @@ -143,6 +145,14 @@ type PrintingKubeClient struct { Out io.Writer } +// APIClient always returns an error. +// +// The printing client does not have access to a Kubernetes client at all. So it +// will always return an error if the client is accessed. +func (p *PrintingKubeClient) APIClient() (unversioned.Interface, error) { + return nil, errors.New("no API client found") +} + // Create prints the values of what would be created with a real KubeClient. func (p *PrintingKubeClient) Create(ns string, r io.Reader) error { _, err := io.Copy(p.Out, r) @@ -196,23 +206,9 @@ func New() *Environment { GoTplEngine: e, } - kbc := kube.New(nil) - - var sd *storage.Storage - if UseConfigMaps { - c, err := kbc.Client() - if err != nil { - // panic because we cant initliaze driver with no client - panic(err) - } - sd = storage.Init(driver.NewConfigMaps(c.ConfigMaps(TillerNamespace))) - } else { - sd = storage.Init(driver.NewMemory()) - } - return &Environment{ EngineYard: ey, - Releases: sd, //storage.Init(driver.NewMemory()), - KubeClient: kbc, //kube.New(nil), //&PrintingKubeClient{Out: os.Stdout}, + Releases: storage.Init(driver.NewMemory()), + KubeClient: kube.New(nil), } } diff --git a/cmd/tiller/environment/environment_test.go b/cmd/tiller/environment/environment_test.go index 16cb9ef7a..236df6a2b 100644 --- a/cmd/tiller/environment/environment_test.go +++ b/cmd/tiller/environment/environment_test.go @@ -26,6 +26,8 @@ import ( "k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/storage" "k8s.io/helm/pkg/storage/driver" + unversionedclient "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/client/unversioned/testclient" ) type mockEngine struct { @@ -47,6 +49,10 @@ func (r *mockReleaseStorage) Create(v *release.Release) error { return nil } +func (r *mockReleaseStorage) Name() string { + return "mockReleaseStorage" +} + func (r *mockReleaseStorage) Get(k string) (*release.Release, error) { return r.rel, nil } @@ -81,6 +87,10 @@ func (r *mockReleaseStorage) History(n string) ([]*release.Release, error) { type mockKubeClient struct { } +func (k *mockKubeClient) APIClient() (unversionedclient.Interface, error) { + return testclient.NewSimpleFake(), nil +} + func (k *mockKubeClient) Create(ns string, r io.Reader) error { return nil } diff --git a/cmd/tiller/tiller.go b/cmd/tiller/tiller.go index 9dfb2c648..2c0e1dd9a 100644 --- a/cmd/tiller/tiller.go +++ b/cmd/tiller/tiller.go @@ -26,6 +26,13 @@ import ( "google.golang.org/grpc" "k8s.io/helm/cmd/tiller/environment" + "k8s.io/helm/pkg/storage" + "k8s.io/helm/pkg/storage/driver" +) + +const ( + storageMemory = "memory" + storageConfigMap = "configmap" ) // rootServer is the root gRPC server. @@ -38,8 +45,11 @@ var rootServer = grpc.NewServer() // Any changes to env should be done before rootServer.Serve() is called. var env = environment.New() -var addr = ":44134" -var probe = ":44135" +var ( + addr = ":44134" + probe = ":44135" + store = storageConfigMap +) const globalUsage = `The Kubernetes Helm server. @@ -58,10 +68,22 @@ var rootCommand = &cobra.Command{ func main() { pf := rootCommand.PersistentFlags() pf.StringVarP(&addr, "listen", "l", ":44134", "The address:port to listen on") + pf.StringVar(&store, "storage", storageConfigMap, "The storage driver to use. One of 'configmap' or 'memory'") rootCommand.Execute() } func start(c *cobra.Command, args []string) { + switch store { + case storageMemory: + env.Releases = storage.Init(driver.NewMemory()) + case storageConfigMap: + c, err := env.KubeClient.APIClient() + if err != nil { + fmt.Fprintf(os.Stderr, "Cannot initialize Kubernetes connection: %s", err) + } + env.Releases = storage.Init(driver.NewConfigMaps(c.ConfigMaps(environment.TillerNamespace))) + } + lstn, err := net.Listen("tcp", addr) if err != nil { fmt.Fprintf(os.Stderr, "Server died: %s\n", err) @@ -70,6 +92,7 @@ func start(c *cobra.Command, args []string) { fmt.Printf("Tiller is running on %s\n", addr) fmt.Printf("Tiller probes server is running on %s\n", probe) + fmt.Printf("Storage driver is %s\n", env.Releases.Name()) srvErrCh := make(chan error) probeErrCh := make(chan error) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index cb7e4590b..6e5e74e30 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -29,6 +29,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apis/batch" + unversionedclient "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -54,6 +55,16 @@ func New(config clientcmd.ClientConfig) *Client { // ResourceActorFunc performs an action on a single resource. type ResourceActorFunc func(*resource.Info) error +// APIClient returns a Kubernetes API client. +// +// This is necessary because cmdutil.Client is a field, not a method, which +// means it can't satisfy an interface's method requirement. In order to ensure +// that an implementation of environment.KubeClient can access the raw API client, +// it is necessary to add this method. +func (c *Client) APIClient() (unversionedclient.Interface, error) { + return c.Client() +} + // Create creates kubernetes resources from an io.reader // // Namespace will set the namespace diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go index 0925ebc26..afe031bf5 100644 --- a/pkg/storage/driver/cfgmaps.go +++ b/pkg/storage/driver/cfgmaps.go @@ -32,6 +32,9 @@ import ( client "k8s.io/kubernetes/pkg/client/unversioned" ) +// ConfigMapsDriverName is the string name of the driver. +const ConfigMapsDriverName = "ConfigMap" + var b64 = base64.StdEncoding // labels is a map of key value pairs to be included as metadata in a configmap object. @@ -54,6 +57,11 @@ func NewConfigMaps(impl client.ConfigMapsInterface) *ConfigMaps { return &ConfigMaps{impl: impl} } +// Name returns the name of the driver. +func (cfgmaps *ConfigMaps) Name() string { + return ConfigMapsDriverName +} + // Get fetches the release named by key. The corresponding release is returned // or error if not found. func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) { diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go index 95638057d..bf2c3f7da 100644 --- a/pkg/storage/driver/cfgmaps_test.go +++ b/pkg/storage/driver/cfgmaps_test.go @@ -27,6 +27,15 @@ import ( "k8s.io/kubernetes/pkg/client/unversioned" ) +var _ Driver = &ConfigMaps{} + +func TestConfigMapName(t *testing.T) { + c := newTestFixture(t) + if c.Name() != ConfigMapsDriverName { + t.Errorf("Expected name to be %q, got %q", ConfigMapsDriverName, c.Name()) + } +} + func TestConfigMapGet(t *testing.T) { key := "key-1" rel := newTestRelease(key, 1, rspb.Status_DEPLOYED) diff --git a/pkg/storage/driver/driver.go b/pkg/storage/driver/driver.go index 0cf51d6f6..f3593d278 100644 --- a/pkg/storage/driver/driver.go +++ b/pkg/storage/driver/driver.go @@ -73,4 +73,5 @@ type Driver interface { Updator Deletor Queryor + Name() string } diff --git a/pkg/storage/driver/memory.go b/pkg/storage/driver/memory.go index bea495133..76351b1a7 100644 --- a/pkg/storage/driver/memory.go +++ b/pkg/storage/driver/memory.go @@ -22,6 +22,9 @@ import ( rspb "k8s.io/helm/pkg/proto/hapi/release" ) +// MemoryDriverName is the string name of this driver. +const MemoryDriverName = "Memory" + // Memory is the in-memory storage driver implementation. type Memory struct { sync.RWMutex @@ -33,6 +36,11 @@ func NewMemory() *Memory { return &Memory{cache: map[string]*rspb.Release{}} } +// Name returns the name of the driver. +func (mem *Memory) Name() string { + return MemoryDriverName +} + // Get returns the release named by key or returns ErrReleaseNotFound. func (mem *Memory) Get(key string) (*rspb.Release, error) { defer unlock(mem.rlock()) diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go index b02f8350b..2c50fd1a4 100644 --- a/pkg/storage/driver/memory_test.go +++ b/pkg/storage/driver/memory_test.go @@ -23,6 +23,15 @@ import ( rspb "k8s.io/helm/pkg/proto/hapi/release" ) +var _ Driver = &Memory{} + +func TestMemoryName(t *testing.T) { + mem := NewMemory() + if mem.Name() != MemoryDriverName { + t.Errorf("Expected name to be %q, got %q", MemoryDriverName, mem.Name()) + } +} + func TestMemoryGet(t *testing.T) { key := "test-1" rls := &rspb.Release{Name: key}