From 93adf30e1867737fee9ffb7055902e82c4903ba1 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Mon, 26 Nov 2018 09:44:20 -0500 Subject: [PATCH 01/12] pkg/helm; copying helm files from helm-app-operator-kit --- pkg/helm/client/client.go | 73 ++++++ pkg/helm/client/doc.go | 17 ++ pkg/helm/controller/controller.go | 75 ++++++ pkg/helm/controller/doc.go | 18 ++ pkg/helm/controller/reconcile.go | 179 +++++++++++++ pkg/helm/engine/doc.go | 17 ++ pkg/helm/engine/ownerref.go | 122 +++++++++ pkg/helm/engine/ownerref_test.go | 138 ++++++++++ pkg/helm/internal/types/doc.go | 17 ++ pkg/helm/internal/types/types.go | 115 +++++++++ pkg/helm/internal/types/types_test.go | 113 ++++++++ pkg/helm/internal/util/diff.go | 58 +++++ pkg/helm/internal/util/doc.go | 17 ++ pkg/helm/internal/util/resource.go | 26 ++ pkg/helm/release/doc.go | 18 ++ pkg/helm/release/manager.go | 357 ++++++++++++++++++++++++++ pkg/helm/release/manager_factory.go | 105 ++++++++ pkg/helm/release/new.go | 168 ++++++++++++ 18 files changed, 1633 insertions(+) create mode 100644 pkg/helm/client/client.go create mode 100644 pkg/helm/client/doc.go create mode 100644 pkg/helm/controller/controller.go create mode 100644 pkg/helm/controller/doc.go create mode 100644 pkg/helm/controller/reconcile.go create mode 100644 pkg/helm/engine/doc.go create mode 100644 pkg/helm/engine/ownerref.go create mode 100644 pkg/helm/engine/ownerref_test.go create mode 100644 pkg/helm/internal/types/doc.go create mode 100644 pkg/helm/internal/types/types.go create mode 100644 pkg/helm/internal/types/types_test.go create mode 100644 pkg/helm/internal/util/diff.go create mode 100644 pkg/helm/internal/util/doc.go create mode 100644 pkg/helm/internal/util/resource.go create mode 100644 pkg/helm/release/doc.go create mode 100644 pkg/helm/release/manager.go create mode 100644 pkg/helm/release/manager_factory.go create mode 100644 pkg/helm/release/new.go diff --git a/pkg/helm/client/client.go b/pkg/helm/client/client.go new file mode 100644 index 0000000000..c8b405a413 --- /dev/null +++ b/pkg/helm/client/client.go @@ -0,0 +1,73 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 client + +import ( + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/helm/pkg/kube" + "sigs.k8s.io/controller-runtime/pkg/manager" +) + +// NewFromManager returns a Kubernetes client that can be used with +// a Tiller server. +func NewFromManager(mgr manager.Manager) (*kube.Client, error) { + c, err := newClientGetter(mgr) + if err != nil { + return nil, err + } + return kube.New(c), nil +} + +type clientGetter struct { + restConfig *rest.Config + discoveryClient discovery.CachedDiscoveryInterface + restMapper meta.RESTMapper +} + +func (c *clientGetter) ToRESTConfig() (*rest.Config, error) { + return c.restConfig, nil +} + +func (c *clientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { + return c.discoveryClient, nil +} + +func (c *clientGetter) ToRESTMapper() (meta.RESTMapper, error) { + return c.restMapper, nil +} + +func (c *clientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig { + return nil +} + +func newClientGetter(mgr manager.Manager) (*clientGetter, error) { + cfg := mgr.GetConfig() + dc, err := discovery.NewDiscoveryClientForConfig(cfg) + if err != nil { + return nil, err + } + cdc := cached.NewMemCacheClient(dc) + rm := mgr.GetRESTMapper() + + return &clientGetter{ + restConfig: cfg, + discoveryClient: cdc, + restMapper: rm, + }, nil +} diff --git a/pkg/helm/client/doc.go b/pkg/helm/client/doc.go new file mode 100644 index 0000000000..93e885bc89 --- /dev/null +++ b/pkg/helm/client/doc.go @@ -0,0 +1,17 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 client provides helper functions for API clients used by the helm +// operator. +package client diff --git a/pkg/helm/controller/controller.go b/pkg/helm/controller/controller.go new file mode 100644 index 0000000000..438f6e5b6b --- /dev/null +++ b/pkg/helm/controller/controller.go @@ -0,0 +1,75 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 controller + +import ( + "fmt" + "strings" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/controller" + crthandler "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" + "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/operator-framework/helm-app-operator-kit/helm-app-operator/pkg/helm/release" +) + +var log = logf.Log.WithName("helm.controller") + +// WatchOptions contains the necessary values to create a new controller that +// manages helm releases in a particular namespace based on a GVK watch. +type WatchOptions struct { + Namespace string + GVK schema.GroupVersionKind + ManagerFactory release.ManagerFactory + ResyncPeriod time.Duration +} + +// Add creates a new helm operator controller and adds it to the manager +func Add(mgr manager.Manager, options WatchOptions) error { + if options.ResyncPeriod == 0 { + options.ResyncPeriod = time.Minute + } + r := &HelmOperatorReconciler{ + Client: mgr.GetClient(), + GVK: options.GVK, + ManagerFactory: options.ManagerFactory, + ResyncPeriod: options.ResyncPeriod, + } + + // Register the GVK with the schema + mgr.GetScheme().AddKnownTypeWithName(options.GVK, &unstructured.Unstructured{}) + metav1.AddToGroupVersion(mgr.GetScheme(), options.GVK.GroupVersion()) + + controllerName := fmt.Sprintf("%v-controller", strings.ToLower(options.GVK.Kind)) + c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + o := &unstructured.Unstructured{} + o.SetGroupVersionKind(options.GVK) + if err := c.Watch(&source.Kind{Type: o}, &crthandler.EnqueueRequestForObject{}); err != nil { + return err + } + + log.Info("Watching resource", "apiVersion", options.GVK.GroupVersion(), "kind", options.GVK.Kind, "namespace", options.Namespace, "resyncPeriod", options.ResyncPeriod.String()) + return nil +} diff --git a/pkg/helm/controller/doc.go b/pkg/helm/controller/doc.go new file mode 100644 index 0000000000..e466a25065 --- /dev/null +++ b/pkg/helm/controller/doc.go @@ -0,0 +1,18 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 controller provides functions for creating and registering a Helm +// controller with a `controller-runtime` manager. It also provides a Helm +// reconciler implementation that can be used to create a Helm-based operator. +package controller diff --git a/pkg/helm/controller/reconcile.go b/pkg/helm/controller/reconcile.go new file mode 100644 index 0000000000..79b688308d --- /dev/null +++ b/pkg/helm/controller/reconcile.go @@ -0,0 +1,179 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 controller + +import ( + "context" + "fmt" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/operator-framework/helm-app-operator-kit/helm-app-operator/pkg/helm/internal/types" + "github.com/operator-framework/helm-app-operator-kit/helm-app-operator/pkg/helm/internal/util" + "github.com/operator-framework/helm-app-operator-kit/helm-app-operator/pkg/helm/release" +) + +var _ reconcile.Reconciler = &HelmOperatorReconciler{} + +// HelmOperatorReconciler reconciles custom resources as Helm releases. +type HelmOperatorReconciler struct { + Client client.Client + GVK schema.GroupVersionKind + ManagerFactory release.ManagerFactory + ResyncPeriod time.Duration +} + +const ( + finalizer = "uninstall-helm-release" +) + +// Reconcile reconciles the requested resource by installing, updating, or +// uninstalling a Helm release based on the resource's current state. If no +// release changes are necessary, Reconcile will create or patch the underlying +// resources to match the expected release manifest. +func (r HelmOperatorReconciler) Reconcile(request reconcile.Request) (reconcile.Result, error) { + o := &unstructured.Unstructured{} + o.SetGroupVersionKind(r.GVK) + o.SetNamespace(request.Namespace) + o.SetName(request.Name) + log := log.WithValues( + "namespace", o.GetNamespace(), + "name", o.GetName(), + "apiVersion", o.GetAPIVersion(), + "kind", o.GetKind(), + ) + log.V(1).Info("Reconciling") + + err := r.Client.Get(context.TODO(), request.NamespacedName, o) + if apierrors.IsNotFound(err) { + return reconcile.Result{}, nil + } + if err != nil { + log.Error(err, "failed to lookup resource") + return reconcile.Result{}, err + } + + manager := r.ManagerFactory.NewManager(o) + status := types.StatusFor(o) + log = log.WithValues("release", manager.ReleaseName()) + + deleted := o.GetDeletionTimestamp() != nil + pendingFinalizers := o.GetFinalizers() + if !deleted && !contains(pendingFinalizers, finalizer) { + log.V(1).Info("Adding finalizer", "finalizer", finalizer) + finalizers := append(pendingFinalizers, finalizer) + o.SetFinalizers(finalizers) + err := r.Client.Update(context.TODO(), o) + return reconcile.Result{}, err + } + + if err := manager.Sync(context.TODO()); err != nil { + log.Error(err, "failed to sync release") + return reconcile.Result{}, err + } + + if deleted { + if !contains(pendingFinalizers, finalizer) { + log.Info("Resource is terminated, skipping reconciliation") + return reconcile.Result{}, nil + } + + uninstalledRelease, err := manager.UninstallRelease(context.TODO()) + if err != nil && err != release.ErrNotFound { + log.Error(err, "failed to uninstall release") + return reconcile.Result{}, err + } + if err == release.ErrNotFound { + log.Info("Release not found, removing finalizer") + } else { + log.Info("Uninstalled release") + if log.Enabled() { + fmt.Println(util.Diff(uninstalledRelease.GetManifest(), "")) + } + } + finalizers := []string{} + for _, pendingFinalizer := range pendingFinalizers { + if pendingFinalizer != finalizer { + finalizers = append(finalizers, pendingFinalizer) + } + } + o.SetFinalizers(finalizers) + err = r.Client.Update(context.TODO(), o) + return reconcile.Result{}, err + } + + if !manager.IsInstalled() { + installedRelease, err := manager.InstallRelease(context.TODO()) + if err != nil { + log.Error(err, "failed to install release") + return reconcile.Result{}, err + } + + log.Info("Installed release") + if log.Enabled() { + fmt.Println(util.Diff("", installedRelease.GetManifest())) + } + log.V(1).Info("Config values", "values", installedRelease.GetConfig()) + status.SetRelease(installedRelease) + status.SetPhase(types.PhaseApplied, types.ReasonApplySuccessful, installedRelease.GetInfo().GetStatus().GetNotes()) + err = r.updateResourceStatus(o, status) + return reconcile.Result{RequeueAfter: r.ResyncPeriod}, err + } + + if manager.IsUpdateRequired() { + previousRelease, updatedRelease, err := manager.UpdateRelease(context.TODO()) + if err != nil { + log.Error(err, "failed to update release") + return reconcile.Result{}, err + } + log.Info("Updated release") + if log.Enabled() { + fmt.Println(util.Diff(previousRelease.GetManifest(), updatedRelease.GetManifest())) + } + log.V(1).Info("Config values", "values", updatedRelease.GetConfig()) + status.SetRelease(updatedRelease) + status.SetPhase(types.PhaseApplied, types.ReasonApplySuccessful, updatedRelease.GetInfo().GetStatus().GetNotes()) + err = r.updateResourceStatus(o, status) + return reconcile.Result{RequeueAfter: r.ResyncPeriod}, err + } + + _, err = manager.ReconcileRelease(context.TODO()) + if err != nil { + log.Error(err, "failed to reconcile release") + return reconcile.Result{}, err + } + + log.Info("Reconciled release") + return reconcile.Result{RequeueAfter: r.ResyncPeriod}, nil +} + +func (r HelmOperatorReconciler) updateResourceStatus(o *unstructured.Unstructured, status *types.HelmAppStatus) error { + o.Object["status"] = status + return r.Client.Update(context.TODO(), o) +} + +func contains(l []string, s string) bool { + for _, elem := range l { + if elem == s { + return true + } + } + return false +} diff --git a/pkg/helm/engine/doc.go b/pkg/helm/engine/doc.go new file mode 100644 index 0000000000..895b595666 --- /dev/null +++ b/pkg/helm/engine/doc.go @@ -0,0 +1,17 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 engine provides an implementation of Helm's templating engine +// required for a Helm operator. +package engine diff --git a/pkg/helm/engine/ownerref.go b/pkg/helm/engine/ownerref.go new file mode 100644 index 0000000000..3a9be56ddf --- /dev/null +++ b/pkg/helm/engine/ownerref.go @@ -0,0 +1,122 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 engine + +import ( + "bytes" + "fmt" + "sort" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/releaseutil" + "k8s.io/helm/pkg/tiller/environment" +) + +// OwnerRefEngine wraps a tiller Render engine, adding ownerrefs to rendered assets +type OwnerRefEngine struct { + environment.Engine + refs []metav1.OwnerReference +} + +// assert interface +var _ environment.Engine = &OwnerRefEngine{} + +// Render proxies to the wrapped Render engine and then adds ownerRefs to each rendered file +func (o *OwnerRefEngine) Render(chart *chart.Chart, values chartutil.Values) (map[string]string, error) { + rendered, err := o.Engine.Render(chart, values) + if err != nil { + return nil, err + } + + ownedRenderedFiles := map[string]string{} + for fileName, renderedFile := range rendered { + if !strings.HasSuffix(fileName, ".yaml") { + continue + } + withOwner, err := o.addOwnerRefs(renderedFile) + if err != nil { + return nil, err + } + if withOwner == "" { + continue + } + ownedRenderedFiles[fileName] = withOwner + } + return ownedRenderedFiles, nil +} + +// addOwnerRefs adds the configured ownerRefs to a single rendered file +// Adds the ownerrefs to all the documents in a YAML file +func (o *OwnerRefEngine) addOwnerRefs(fileContents string) (string, error) { + const documentSeparator = "---\n" + var outBuf bytes.Buffer + + manifests := releaseutil.SplitManifests(fileContents) + for _, manifest := range sortManifests(manifests) { + manifestMap := chartutil.FromYaml(manifest) + if errors, ok := manifestMap["Error"]; ok { + return "", fmt.Errorf("error parsing rendered template to add ownerrefs: %v", errors) + } + + // Check if the document is empty + if len(manifestMap) == 0 { + continue + } + + unst, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&manifestMap) + if err != nil { + return "", err + } + + unstructured := &unstructured.Unstructured{Object: unst} + unstructured.SetOwnerReferences(o.refs) + + // Write the document with owner ref to the buffer + // Also add document start marker + _, err = outBuf.WriteString(documentSeparator + chartutil.ToYaml(unstructured.Object)) + if err != nil { + return "", fmt.Errorf("error writing the document to buffer: %v", err) + } + } + + return outBuf.String(), nil +} + +// NewOwnerRefEngine creates a new OwnerRef engine with a set of metav1.OwnerReferences to be added to assets +func NewOwnerRefEngine(baseEngine environment.Engine, refs []metav1.OwnerReference) environment.Engine { + return &OwnerRefEngine{ + Engine: baseEngine, + refs: refs, + } +} + +func sortManifests(in map[string]string) []string { + var keys []string + for k := range in { + keys = append(keys, k) + } + sort.Strings(keys) + + var manifests []string + for _, k := range keys { + manifests = append(manifests, in[k]) + } + return manifests +} diff --git a/pkg/helm/engine/ownerref_test.go b/pkg/helm/engine/ownerref_test.go new file mode 100644 index 0000000000..0ef539dfe2 --- /dev/null +++ b/pkg/helm/engine/ownerref_test.go @@ -0,0 +1,138 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 engine + +import ( + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +type mockEngine struct { + out map[string]string +} + +func (e *mockEngine) Render(chrt *chart.Chart, v chartutil.Values) (map[string]string, error) { + return e.out, nil +} + +func TestOwnerRefEngine(t *testing.T) { + ownerRefs := []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Test", + Name: "test", + UID: "123", + }, + } + + baseOut := `apiVersion: stable.nicolerenee.io/v1 +kind: Character +metadata: + name: nemo +spec: + Name: Nemo +` + + expectedOut := `--- +apiVersion: stable.nicolerenee.io/v1 +kind: Character +metadata: + name: nemo + ownerReferences: + - apiVersion: v1 + kind: Test + name: test + uid: "123" +spec: + Name: Nemo +` + expected := map[string]string{"template.yaml": expectedOut, "template2.yaml": expectedOut} + + baseEngineOutput := map[string]string{ + "template.yaml": baseOut, + "template2.yaml": baseOut, + "empty.yaml": "", + "comment.yaml": "# This is empty", + } + + engine := NewOwnerRefEngine(&mockEngine{out: baseEngineOutput}, ownerRefs) + out, err := engine.Render(&chart.Chart{}, map[string]interface{}{}) + require.NoError(t, err) + require.EqualValues(t, expected, out) +} + +func TestOwnerRefEngine_MultiDocumentYaml(t *testing.T) { + ownerRefs := []metav1.OwnerReference{ + { + APIVersion: "v1", + Kind: "Test", + Name: "test", + UID: "123", + }, + } + + baseOut := `kind: ConfigMap +apiVersion: v1 +metadata: + name: eighth + data: + name: value +--- +apiVersion: v1 +kind: Pod +metadata: + name: example-test +` + + expectedOut := `--- +apiVersion: v1 +kind: ConfigMap +metadata: + data: + name: value + name: eighth + ownerReferences: + - apiVersion: v1 + kind: Test + name: test + uid: "123" +--- +apiVersion: v1 +kind: Pod +metadata: + name: example-test + ownerReferences: + - apiVersion: v1 + kind: Test + name: test + uid: "123" +` + + expected := map[string]string{"template.yaml": expectedOut} + + baseEngineOutput := map[string]string{ + "template.yaml": baseOut, + } + + engine := NewOwnerRefEngine(&mockEngine{out: baseEngineOutput}, ownerRefs) + out, err := engine.Render(&chart.Chart{}, map[string]interface{}{}) + + require.NoError(t, err) + require.Equal(t, expected, out) +} diff --git a/pkg/helm/internal/types/doc.go b/pkg/helm/internal/types/doc.go new file mode 100644 index 0000000000..18255ab307 --- /dev/null +++ b/pkg/helm/internal/types/doc.go @@ -0,0 +1,17 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 types contains types used by various components of the Helm +// operator +package types diff --git a/pkg/helm/internal/types/types.go b/pkg/helm/internal/types/types.go new file mode 100644 index 0000000000..b8a2532ee1 --- /dev/null +++ b/pkg/helm/internal/types/types.go @@ -0,0 +1,115 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 types + +import ( + "encoding/json" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/helm/pkg/proto/hapi/release" +) + +type HelmAppList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + Items []HelmApp `json:"items"` +} + +type HelmApp struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + Spec HelmAppSpec `json:"spec"` + Status HelmAppStatus `json:"status,omitempty"` +} + +type HelmAppSpec map[string]interface{} + +type ResourcePhase string + +const ( + PhaseNone ResourcePhase = "" + PhaseApplying ResourcePhase = "Applying" + PhaseApplied ResourcePhase = "Applied" + PhaseFailed ResourcePhase = "Failed" +) + +type ConditionReason string + +const ( + ReasonUnknown ConditionReason = "Unknown" + ReasonCustomResourceAdded ConditionReason = "CustomResourceAdded" + ReasonCustomResourceUpdated ConditionReason = "CustomResourceUpdated" + ReasonApplySuccessful ConditionReason = "ApplySuccessful" + ReasonApplyFailed ConditionReason = "ApplyFailed" +) + +type HelmAppStatus struct { + Release *release.Release `json:"release"` + Phase ResourcePhase `json:"phase"` + Reason ConditionReason `json:"reason,omitempty"` + Message string `json:"message,omitempty"` + LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"` + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` +} + +func (s *HelmAppStatus) ToMap() (map[string]interface{}, error) { + var out map[string]interface{} + jsonObj, err := json.Marshal(&s) + if err != nil { + return nil, err + } + json.Unmarshal(jsonObj, &out) + return out, nil +} + +// SetPhase takes a custom resource status and returns the updated status, without updating the resource in the cluster. +func (s *HelmAppStatus) SetPhase(phase ResourcePhase, reason ConditionReason, message string) *HelmAppStatus { + s.LastUpdateTime = metav1.Now() + if s.Phase != phase { + s.Phase = phase + s.LastTransitionTime = metav1.Now() + } + s.Message = message + s.Reason = reason + return s +} + +// SetRelease takes a release object and adds or updates the release on the status object +func (s *HelmAppStatus) SetRelease(release *release.Release) *HelmAppStatus { + s.Release = release + return s +} + +// StatusFor safely returns a typed status block from a custom resource. +func StatusFor(cr *unstructured.Unstructured) *HelmAppStatus { + switch cr.Object["status"].(type) { + case *HelmAppStatus: + return cr.Object["status"].(*HelmAppStatus) + case map[string]interface{}: + var status *HelmAppStatus + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(cr.Object["status"].(map[string]interface{}), &status); err != nil { + return &HelmAppStatus{ + Phase: PhaseFailed, + Reason: ReasonApplyFailed, + Message: err.Error(), + } + } + return status + default: + return &HelmAppStatus{} + } +} diff --git a/pkg/helm/internal/types/types_test.go b/pkg/helm/internal/types/types_test.go new file mode 100644 index 0000000000..0083675067 --- /dev/null +++ b/pkg/helm/internal/types/types_test.go @@ -0,0 +1,113 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/helm/pkg/proto/hapi/release" +) + +const ( + testNamespaceName = "helm-test" + testReleaseName = "helm-test-dory" +) + +var now = metav1.Now() + +func TestSetPhase(t *testing.T) { + newStatus, err := newTestStatus().SetPhase(PhaseApplying, ReasonCustomResourceUpdated, "working on it").ToMap() + assert.NoError(t, err) + + assert.Equal(t, string(PhaseApplying), newStatus["phase"]) + assert.Equal(t, string(ReasonCustomResourceUpdated), newStatus["reason"]) + assert.Equal(t, "working on it", newStatus["message"]) + assert.NotEqual(t, metav1.Now(), newStatus["lastUpdateTime"]) + assert.NotEqual(t, metav1.Now(), newStatus["lastTransitionTime"]) +} + +func TestStatusForEmpty(t *testing.T) { + status := StatusFor(newTestResource()) + + assert.Equal(t, &HelmAppStatus{}, status) +} + +func TestStatusForFilled(t *testing.T) { + expectedResource := newTestResource() + expectedResource.Object["status"] = newTestStatus() + status := StatusFor(expectedResource) + + assert.EqualValues(t, newTestStatus().Phase, status.Phase) + assert.EqualValues(t, newTestStatus().Reason, status.Reason) + assert.EqualValues(t, newTestStatus().Message, status.Message) +} + +func TestStatusForFilledRaw(t *testing.T) { + expectedResource := newTestResource() + expectedResource.Object["status"] = newTestStatusRaw() + status := StatusFor(expectedResource) + + assert.EqualValues(t, newTestStatus().Phase, status.Phase) + assert.EqualValues(t, newTestStatus().Reason, status.Reason) + assert.EqualValues(t, newTestStatus().Message, status.Message) +} + +func TestSetRelease(t *testing.T) { + releaseName := "TestRelease" + release := release.Release{Name: releaseName} + newStatus := newTestStatus().SetRelease(&release) + assert.EqualValues(t, newStatus.Release.Name, releaseName) +} + +func newTestResource() *unstructured.Unstructured { + return &unstructured.Unstructured{ + Object: map[string]interface{}{ + "kind": "Character", + "apiVersion": "stable.nicolerenee.io", + "metadata": map[string]interface{}{ + "name": "dory", + "namespace": testNamespaceName, + }, + "spec": map[string]interface{}{ + "Name": "Dory", + "From": "Finding Nemo", + "By": "Disney", + }, + }, + } +} + +func newTestStatus() *HelmAppStatus { + return &HelmAppStatus{ + Phase: PhaseApplied, + Reason: ReasonApplySuccessful, + Message: "some message", + LastUpdateTime: now, + LastTransitionTime: now, + } +} + +func newTestStatusRaw() map[string]interface{} { + return map[string]interface{}{ + "phase": PhaseApplied, + "reason": ReasonApplySuccessful, + "message": "some message", + "lastUpdateTime": now.UTC(), + "lastTransitionTime": now.UTC(), + } +} diff --git a/pkg/helm/internal/util/diff.go b/pkg/helm/internal/util/diff.go new file mode 100644 index 0000000000..1b562793b5 --- /dev/null +++ b/pkg/helm/internal/util/diff.go @@ -0,0 +1,58 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 util + +import ( + "bytes" + "regexp" + "strings" + + "github.com/sergi/go-diff/diffmatchpatch" +) + +func Diff(a, b string) string { + dmp := diffmatchpatch.New() + wSrc, wDst, warray := dmp.DiffLinesToRunes(a, b) + diffs := dmp.DiffMainRunes(wSrc, wDst, false) + diffs = dmp.DiffCharsToLines(diffs, warray) + var buff bytes.Buffer + for _, diff := range diffs { + text := diff.Text + switch diff.Type { + case diffmatchpatch.DiffInsert: + _, _ = buff.WriteString("\x1b[32m") + _, _ = buff.WriteString(prefixLines(text, "+")) + _, _ = buff.WriteString("\x1b[0m") + case diffmatchpatch.DiffDelete: + _, _ = buff.WriteString("\x1b[31m") + _, _ = buff.WriteString(prefixLines(text, "-")) + _, _ = buff.WriteString("\x1b[0m") + case diffmatchpatch.DiffEqual: + _, _ = buff.WriteString(prefixLines(text, " ")) + } + } + return buff.String() +} + +func prefixLines(s, prefix string) string { + var buf bytes.Buffer + lines := strings.Split(s, "\n") + ls := regexp.MustCompile("^") + for _, line := range lines[:len(lines)-1] { + buf.WriteString(ls.ReplaceAllString(line, prefix)) + buf.WriteString("\n") + } + return buf.String() +} diff --git a/pkg/helm/internal/util/doc.go b/pkg/helm/internal/util/doc.go new file mode 100644 index 0000000000..a132aa2f48 --- /dev/null +++ b/pkg/helm/internal/util/doc.go @@ -0,0 +1,17 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 util contains helper functions and utilities used by various +// components of the Helm operator +package util diff --git a/pkg/helm/internal/util/resource.go b/pkg/helm/internal/util/resource.go new file mode 100644 index 0000000000..dacaf513c7 --- /dev/null +++ b/pkg/helm/internal/util/resource.go @@ -0,0 +1,26 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 util + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +// ResourceString returns a human friendly string for the custom resource +func ResourceString(r *unstructured.Unstructured) string { + return fmt.Sprintf("apiVersion=%s kind=%s name=%s/%s", r.GetAPIVersion(), r.GetKind(), r.GetNamespace(), r.GetName()) +} diff --git a/pkg/helm/release/doc.go b/pkg/helm/release/doc.go new file mode 100644 index 0000000000..76c4c34496 --- /dev/null +++ b/pkg/helm/release/doc.go @@ -0,0 +1,18 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 release provides interfaces and default implementations for a Helm +// release manager, which is used by the Helm controller and reconciler to +// manage Helm releases in a cluster based on watched custom resources. +package release diff --git a/pkg/helm/release/manager.go b/pkg/helm/release/manager.go new file mode 100644 index 0000000000..628b6885bb --- /dev/null +++ b/pkg/helm/release/manager.go @@ -0,0 +1,357 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 release + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "strings" + + yaml "gopkg.in/yaml.v2" + apierrors "k8s.io/apimachinery/pkg/api/errors" + apitypes "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/kube" + cpb "k8s.io/helm/pkg/proto/hapi/chart" + rpb "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/storage" + "k8s.io/helm/pkg/tiller" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" + + "github.com/operator-framework/helm-app-operator-kit/helm-app-operator/pkg/helm/internal/types" +) + +var ( + // ErrNotFound indicates the release was not found. + ErrNotFound = errors.New("release not found") +) + +// Manager manages a Helm release. It can install, update, reconcile, +// and uninstall a release. +type Manager interface { + ReleaseName() string + IsInstalled() bool + IsUpdateRequired() bool + Sync(context.Context) error + InstallRelease(context.Context) (*rpb.Release, error) + UpdateRelease(context.Context) (*rpb.Release, *rpb.Release, error) + ReconcileRelease(context.Context) (*rpb.Release, error) + UninstallRelease(context.Context) (*rpb.Release, error) +} + +type manager struct { + storageBackend *storage.Storage + tillerKubeClient *kube.Client + chartDir string + + tiller *tiller.ReleaseServer + releaseName string + namespace string + + spec interface{} + status *types.HelmAppStatus + + isInstalled bool + isUpdateRequired bool + deployedRelease *rpb.Release + chart *cpb.Chart + config *cpb.Config +} + +// ReleaseName returns the name of the release. +func (m manager) ReleaseName() string { + return m.releaseName +} + +func (m manager) IsInstalled() bool { + return m.isInstalled +} + +func (m manager) IsUpdateRequired() bool { + return m.isUpdateRequired +} + +// Sync ensures the Helm storage backend is in sync with the status of the +// custom resource. +func (m *manager) Sync(ctx context.Context) error { + if err := m.syncReleaseStatus(*m.status); err != nil { + return fmt.Errorf("failed to sync release status to storage backend: %s", err) + } + + // Get release history for this release name + releases, err := m.storageBackend.History(m.releaseName) + if err != nil && !notFoundErr(err) { + return fmt.Errorf("failed to retrieve release history: %s", err) + } + + // Cleanup non-deployed release versions. If all release versions are + // non-deployed, this will ensure that failed installations are correctly + // retried. + for _, rel := range releases { + if rel.GetInfo().GetStatus().GetCode() != rpb.Status_DEPLOYED { + _, err := m.storageBackend.Delete(rel.GetName(), rel.GetVersion()) + if err != nil && !notFoundErr(err) { + return fmt.Errorf("failed to delete stale release version: %s", err) + } + } + } + + // Load the chart and config based on the current state of the custom resource. + chart, config, err := m.loadChartAndConfig() + if err != nil { + return fmt.Errorf("failed to load chart and config: %s", err) + } + m.chart = chart + m.config = config + + // Load the most recently deployed release from the storage backend. + deployedRelease, err := m.getDeployedRelease() + if err == ErrNotFound { + return nil + } + if err != nil { + return fmt.Errorf("failed to get deployed release: %s", err) + } + m.deployedRelease = deployedRelease + m.isInstalled = true + + // Get the next candidate release to determine if an update is necessary. + candidateRelease, err := m.getCandidateRelease(ctx, m.tiller, m.releaseName, chart, config) + if err != nil { + return fmt.Errorf("failed to get candidate release: %s", err) + } + if deployedRelease.GetManifest() != candidateRelease.GetManifest() { + m.isUpdateRequired = true + } + + return nil +} + +func (m manager) syncReleaseStatus(status types.HelmAppStatus) error { + if status.Release == nil { + return nil + } + + name := status.Release.GetName() + version := status.Release.GetVersion() + _, err := m.storageBackend.Get(name, version) + if err == nil { + return nil + } + + if !notFoundErr(err) { + return err + } + return m.storageBackend.Create(status.Release) +} + +func notFoundErr(err error) bool { + return strings.Contains(err.Error(), "not found") +} + +func (m manager) loadChartAndConfig() (*cpb.Chart, *cpb.Config, error) { + // chart is mutated by the call to processRequirements, + // so we need to reload it from disk every time. + chart, err := chartutil.LoadDir(m.chartDir) + if err != nil { + return nil, nil, fmt.Errorf("failed to load chart: %s", err) + } + + cr, err := yaml.Marshal(m.spec) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse values: %s", err) + } + config := &cpb.Config{Raw: string(cr)} + + err = processRequirements(chart, config) + if err != nil { + return nil, nil, fmt.Errorf("failed to process chart requirements: %s", err) + } + return chart, config, nil +} + +// processRequirements will process the requirements file +// It will disable/enable the charts based on condition in requirements file +// Also imports the specified chart values from child to parent. +func processRequirements(chart *cpb.Chart, values *cpb.Config) error { + err := chartutil.ProcessRequirementsEnabled(chart, values) + if err != nil { + return err + } + err = chartutil.ProcessRequirementsImportValues(chart) + if err != nil { + return err + } + return nil +} + +func (m manager) getDeployedRelease() (*rpb.Release, error) { + deployedRelease, err := m.storageBackend.Deployed(m.releaseName) + if err != nil { + if strings.Contains(err.Error(), "has no deployed releases") { + return nil, ErrNotFound + } + return nil, err + } + return deployedRelease, nil +} + +func (m manager) getCandidateRelease(ctx context.Context, tiller *tiller.ReleaseServer, name string, chart *cpb.Chart, config *cpb.Config) (*rpb.Release, error) { + dryRunReq := &services.UpdateReleaseRequest{ + Name: name, + Chart: chart, + Values: config, + DryRun: true, + } + dryRunResponse, err := tiller.UpdateRelease(ctx, dryRunReq) + if err != nil { + return nil, err + } + return dryRunResponse.GetRelease(), nil +} + +// InstallRelease performs a Helm release install. +func (m manager) InstallRelease(ctx context.Context) (*rpb.Release, error) { + return installRelease(ctx, m.tiller, m.namespace, m.releaseName, m.chart, m.config) +} + +func installRelease(ctx context.Context, tiller *tiller.ReleaseServer, namespace, name string, chart *cpb.Chart, config *cpb.Config) (*rpb.Release, error) { + installReq := &services.InstallReleaseRequest{ + Namespace: namespace, + Name: name, + Chart: chart, + Values: config, + } + + releaseResponse, err := tiller.InstallRelease(ctx, installReq) + if err != nil { + // Workaround for helm/helm#3338 + if releaseResponse.GetRelease() != nil { + uninstallReq := &services.UninstallReleaseRequest{ + Name: releaseResponse.GetRelease().GetName(), + Purge: true, + } + _, uninstallErr := tiller.UninstallRelease(ctx, uninstallReq) + if uninstallErr != nil { + return nil, fmt.Errorf("failed to roll back failed installation: %s: %s", uninstallErr, err) + } + } + return nil, err + } + return releaseResponse.GetRelease(), nil +} + +// UpdateRelease performs a Helm release update. +func (m manager) UpdateRelease(ctx context.Context) (*rpb.Release, *rpb.Release, error) { + updatedRelease, err := updateRelease(ctx, m.tiller, m.releaseName, m.chart, m.config) + return m.deployedRelease, updatedRelease, err +} + +func updateRelease(ctx context.Context, tiller *tiller.ReleaseServer, name string, chart *cpb.Chart, config *cpb.Config) (*rpb.Release, error) { + updateReq := &services.UpdateReleaseRequest{ + Name: name, + Chart: chart, + Values: config, + } + + releaseResponse, err := tiller.UpdateRelease(ctx, updateReq) + if err != nil { + // Workaround for helm/helm#3338 + if releaseResponse.GetRelease() != nil { + rollbackReq := &services.RollbackReleaseRequest{ + Name: name, + Force: true, + } + _, rollbackErr := tiller.RollbackRelease(ctx, rollbackReq) + if rollbackErr != nil { + return nil, fmt.Errorf("failed to roll back failed update: %s: %s", rollbackErr, err) + } + } + return nil, err + } + return releaseResponse.GetRelease(), nil +} + +// ReconcileRelease creates or patches resources as necessary to match the +// deployed release's manifest. +func (m manager) ReconcileRelease(ctx context.Context) (*rpb.Release, error) { + err := reconcileRelease(ctx, m.tillerKubeClient, m.namespace, m.deployedRelease.GetManifest()) + return m.deployedRelease, err +} + +func reconcileRelease(ctx context.Context, tillerKubeClient *kube.Client, namespace string, expectedManifest string) error { + expectedInfos, err := tillerKubeClient.BuildUnstructured(namespace, bytes.NewBufferString(expectedManifest)) + if err != nil { + return err + } + return expectedInfos.Visit(func(expected *resource.Info, err error) error { + if err != nil { + return err + } + + expectedClient := resource.NewClientWithOptions(expected.Client, func(r *rest.Request) { + *r = *r.Context(ctx) + }) + helper := resource.NewHelper(expectedClient, expected.Mapping) + _, err = helper.Create(expected.Namespace, true, expected.Object) + if err == nil { + return nil + } + if !apierrors.IsAlreadyExists(err) { + return fmt.Errorf("create error: %s", err) + } + + patch, err := json.Marshal(expected.Object) + if err != nil { + return fmt.Errorf("failed to marshal JSON patch: %s", err) + } + + _, err = helper.Patch(expected.Namespace, expected.Name, apitypes.MergePatchType, patch) + if err != nil { + return fmt.Errorf("patch error: %s", err) + } + return nil + }) +} + +// UninstallRelease performs a Helm release uninstall. +func (m manager) UninstallRelease(ctx context.Context) (*rpb.Release, error) { + return uninstallRelease(ctx, m.storageBackend, m.tiller, m.releaseName) +} + +func uninstallRelease(ctx context.Context, storageBackend *storage.Storage, tiller *tiller.ReleaseServer, releaseName string) (*rpb.Release, error) { + // Get history of this release + h, err := storageBackend.History(releaseName) + if err != nil { + return nil, fmt.Errorf("failed to get release history: %s", err) + } + + // If there is no history, the release has already been uninstalled, + // so return ErrNotFound. + if len(h) == 0 { + return nil, ErrNotFound + } + + uninstallResponse, err := tiller.UninstallRelease(ctx, &services.UninstallReleaseRequest{ + Name: releaseName, + Purge: true, + }) + return uninstallResponse.GetRelease(), err +} diff --git a/pkg/helm/release/manager_factory.go b/pkg/helm/release/manager_factory.go new file mode 100644 index 0000000000..ead9810829 --- /dev/null +++ b/pkg/helm/release/manager_factory.go @@ -0,0 +1,105 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 release + +import ( + "fmt" + "strings" + + "github.com/martinlindhe/base36" + "github.com/pborman/uuid" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + apitypes "k8s.io/apimachinery/pkg/types" + helmengine "k8s.io/helm/pkg/engine" + "k8s.io/helm/pkg/kube" + "k8s.io/helm/pkg/storage" + "k8s.io/helm/pkg/tiller" + "k8s.io/helm/pkg/tiller/environment" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + + "github.com/operator-framework/helm-app-operator-kit/helm-app-operator/pkg/helm/engine" + "github.com/operator-framework/helm-app-operator-kit/helm-app-operator/pkg/helm/internal/types" +) + +// ManagerFactory creates Managers that are specific to custom resources. It is +// used by the HelmOperatorReconciler during resource reconciliation, and it +// improves decoupling between reconciliation logic and the Helm backend +// components used to manage releases. +type ManagerFactory interface { + NewManager(r *unstructured.Unstructured) Manager +} + +type managerFactory struct { + storageBackend *storage.Storage + tillerKubeClient *kube.Client + chartDir string +} + +func (f managerFactory) NewManager(r *unstructured.Unstructured) Manager { + return f.newManagerForCR(r) +} + +func (f managerFactory) newManagerForCR(r *unstructured.Unstructured) Manager { + return &manager{ + storageBackend: f.storageBackend, + tillerKubeClient: f.tillerKubeClient, + chartDir: f.chartDir, + + tiller: f.tillerRendererForCR(r), + releaseName: getReleaseName(r), + namespace: r.GetNamespace(), + + spec: r.Object["spec"], + status: types.StatusFor(r), + } +} + +// tillerRendererForCR creates a ReleaseServer configured with a rendering engine that adds ownerrefs to rendered assets +// based on the CR. +func (f managerFactory) tillerRendererForCR(r *unstructured.Unstructured) *tiller.ReleaseServer { + controllerRef := metav1.NewControllerRef(r, r.GroupVersionKind()) + ownerRefs := []metav1.OwnerReference{ + *controllerRef, + } + baseEngine := helmengine.New() + e := engine.NewOwnerRefEngine(baseEngine, ownerRefs) + var ey environment.EngineYard = map[string]environment.Engine{ + environment.GoTplEngine: e, + } + env := &environment.Environment{ + EngineYard: ey, + Releases: f.storageBackend, + KubeClient: f.tillerKubeClient, + } + kubeconfig, _ := f.tillerKubeClient.ToRESTConfig() + internalClientSet, _ := internalclientset.NewForConfig(kubeconfig) + + return tiller.NewReleaseServer(env, internalClientSet, false) +} + +func getReleaseName(r *unstructured.Unstructured) string { + return fmt.Sprintf("%s-%s", r.GetName(), shortenUID(r.GetUID())) +} + +func shortenUID(uid apitypes.UID) string { + u := uuid.Parse(string(uid)) + uidBytes, err := u.MarshalBinary() + if err != nil { + return strings.Replace(string(uid), "-", "", -1) + } + return strings.ToLower(base36.EncodeBytes(uidBytes)) +} diff --git a/pkg/helm/release/new.go b/pkg/helm/release/new.go new file mode 100644 index 0000000000..793b5ee5b3 --- /dev/null +++ b/pkg/helm/release/new.go @@ -0,0 +1,168 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 release + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + + yaml "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/kube" + "k8s.io/helm/pkg/storage" +) + +const ( + // HelmChartWatchesEnvVar is the environment variable for a YAML + // configuration file containing mappings of GVKs to helm charts. Use of + // this environment variable overrides the watch configuration provided + // by API_VERSION, KIND, and HELM_CHART, and it allows users to configure + // multiple watches, each with a different chart. + HelmChartWatchesEnvVar = "HELM_CHART_WATCHES" + + // APIVersionEnvVar is the environment variable for the group and version + // to be watched using the format `/` + // (e.g. "example.com/v1alpha1"). + APIVersionEnvVar = "API_VERSION" + + // KindEnvVar is the environment variable for the kind to be watched. The + // value is typically singular and should be CamelCased (e.g. "MyApp"). + KindEnvVar = "KIND" + + // HelmChartEnvVar is the environment variable for the directory location + // of the helm chart to be installed for CRs that match the values for the + // API_VERSION and KIND environment variables. + HelmChartEnvVar = "HELM_CHART" + + defaultHelmChartWatchesFile = "/opt/helm/watches.yaml" +) + +type watch struct { + Group string `yaml:"group"` + Version string `yaml:"version"` + Kind string `yaml:"kind"` + Chart string `yaml:"chart"` +} + +// NewManagerFactory returns a new Helm manager factory capable of installing and uninstalling releases. +func NewManagerFactory(storageBackend *storage.Storage, tillerKubeClient *kube.Client, chartDir string) ManagerFactory { + return &managerFactory{storageBackend, tillerKubeClient, chartDir} +} + +// newManagerFactoryFromEnv returns a GVK and manager factory based on configuration provided +// in the environment. +func newManagerFactoryFromEnv(storageBackend *storage.Storage, tillerKubeClient *kube.Client) (schema.GroupVersionKind, ManagerFactory, error) { + apiVersion := os.Getenv(APIVersionEnvVar) + kind := os.Getenv(KindEnvVar) + chartDir := os.Getenv(HelmChartEnvVar) + + var gvk schema.GroupVersionKind + gv, err := schema.ParseGroupVersion(apiVersion) + if err != nil { + return gvk, nil, err + } + gvk = gv.WithKind(kind) + + if err := verifyGVK(gvk); err != nil { + return gvk, nil, fmt.Errorf("invalid GVK: %s: %s", gvk, err) + } + + if _, err := chartutil.IsChartDir(chartDir); err != nil { + return gvk, nil, fmt.Errorf("invalid chart directory %s: %s", chartDir, err) + } + + managerFactory := NewManagerFactory(storageBackend, tillerKubeClient, chartDir) + return gvk, managerFactory, nil +} + +// NewManagerFactoriesFromEnv returns a map of managers, keyed by GVK, based on +// configuration provided in the environment. +func NewManagerFactoriesFromEnv(storageBackend *storage.Storage, tillerKubeClient *kube.Client) (map[schema.GroupVersionKind]ManagerFactory, error) { + if watchesFile, ok := getWatchesFile(); ok { + return NewManagerFactoriesFromFile(storageBackend, tillerKubeClient, watchesFile) + } + gvk, managerFactory, err := newManagerFactoryFromEnv(storageBackend, tillerKubeClient) + if err != nil { + return nil, err + } + return map[schema.GroupVersionKind]ManagerFactory{gvk: managerFactory}, nil +} + +// NewManagerFactoriesFromFile reads the config file at the provided path and returns a map +// of managers, keyed by each GVK in the config. +func NewManagerFactoriesFromFile(storageBackend *storage.Storage, tillerKubeClient *kube.Client, path string) (map[schema.GroupVersionKind]ManagerFactory, error) { + b, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read config file: %s", err) + } + watches := []watch{} + err = yaml.Unmarshal(b, &watches) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal config: %s", err) + } + + m := map[schema.GroupVersionKind]ManagerFactory{} + for _, w := range watches { + gvk := schema.GroupVersionKind{ + Group: w.Group, + Version: w.Version, + Kind: w.Kind, + } + + if err := verifyGVK(gvk); err != nil { + return nil, fmt.Errorf("invalid GVK: %s: %s", gvk, err) + } + + if _, err := chartutil.IsChartDir(w.Chart); err != nil { + return nil, fmt.Errorf("invalid chart directory %s: %s", w.Chart, err) + } + + if _, ok := m[gvk]; ok { + return nil, fmt.Errorf("duplicate GVK: %s", gvk) + } + m[gvk] = NewManagerFactory(storageBackend, tillerKubeClient, w.Chart) + } + return m, nil +} + +func verifyGVK(gvk schema.GroupVersionKind) error { + // A GVK without a group is valid. Certain scenarios may cause a GVK + // without a group to fail in other ways later in the initialization + // process. + if gvk.Version == "" { + return errors.New("version must not be empty") + } + if gvk.Kind == "" { + return errors.New("kind must not be empty") + } + return nil +} + +func getWatchesFile() (string, bool) { + // If the watches env variable is set (even if it's an empty string), use it + // since the user explicitly set it. + if watchesFile, ok := os.LookupEnv(HelmChartWatchesEnvVar); ok { + return watchesFile, true + } + + // Next, check if the default watches file is present. If so, use it. + if _, err := os.Stat(defaultHelmChartWatchesFile); err == nil { + return defaultHelmChartWatchesFile, true + } + return "", false +} From 11a462be1f477d96fc6c07d875e3d69dfde38b16 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Mon, 26 Nov 2018 10:19:05 -0500 Subject: [PATCH 02/12] pkg/helm,Gopkg.*: fixing imports and deps --- Gopkg.lock | 576 +++++++++++++++++++++++++++- Gopkg.toml | 24 ++ pkg/helm/controller/controller.go | 2 +- pkg/helm/controller/reconcile.go | 6 +- pkg/helm/release/manager.go | 2 +- pkg/helm/release/manager_factory.go | 4 +- 6 files changed, 606 insertions(+), 8 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index f18c468c0a..2d44f3ace1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,6 +1,17 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + branch = "master" + digest = "1:6978a38432a017763a148afbc7ce6491734b54292af7d3e969d84d2e9dd242e2" + name = "github.com/Azure/go-ansiterm" + packages = [ + ".", + "winterm", + ] + pruneopts = "" + revision = "d6e3b3328b783f23731bc4d058875b0371ff8109" + [[projects]] digest = "1:e4b30804a381d7603b8a344009987c1ba351c26043501b23b8c7ce21f0b67474" name = "github.com/BurntSushi/toml" @@ -9,6 +20,30 @@ revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005" version = "v0.3.1" +[[projects]] + branch = "master" + digest = "1:6b250e53b3b7b9abd7e62f55875c234d2f8b77b846bff200fecafa2dc09af09a" + name = "github.com/MakeNowJust/heredoc" + packages = ["."] + pruneopts = "" + revision = "e9091a26100e9cfb2b6a8f470085bfa541931a91" + +[[projects]] + digest = "1:b856d8248663c39265a764561c1a1a149783f6cc815feb54a1f3a591b91f6eca" + name = "github.com/Masterminds/semver" + packages = ["."] + pruneopts = "" + revision = "c7af12943936e8c39859482e61f0574c2fd7fc75" + version = "v1.4.2" + +[[projects]] + digest = "1:aec6720081aad903219cf427bb9acdefc9c440743add4d02df38ff387c69886a" + name = "github.com/Masterminds/sprig" + packages = ["."] + pruneopts = "" + revision = "15f9564e7e9cf0da02a48e0d25f12a7b83559aa6" + version = "v2.16.0" + [[projects]] digest = "1:8e47871087b94913898333f37af26732faaab30cdb41571136cf7aec9921dae7" name = "github.com/PuerkitoBio/purell" @@ -25,6 +60,14 @@ pruneopts = "" revision = "de5bf2ad457846296e2031421a34e2568e304e35" +[[projects]] + digest = "1:24cff84fefa06791f78f9bc6e05c2a3a90710c81ae6b76225280daf33b267d36" + name = "github.com/aokoli/goutils" + packages = ["."] + pruneopts = "" + revision = "3391d3790d23d03408670993e957e8f408993c34" + version = "v1.0.1" + [[projects]] branch = "master" digest = "1:c0bec5f9b98d0bc872ff5e834fac186b807b656683bd29cb82fb207a1513fabb" @@ -33,6 +76,19 @@ pruneopts = "" revision = "3a771d992973f24aa725d07868b467d1ddfceafb" +[[projects]] + branch = "master" + digest = "1:9c19f8c33e635e0439c8afc167d6d02e3aa6eea5b69d64880244fd354a99edc4" + name = "github.com/chai2010/gettext-go" + packages = [ + "gettext", + "gettext/mo", + "gettext/plural", + "gettext/po", + ] + pruneopts = "" + revision = "bf70f2a70fb1b1f36d90d671a72795984eab0fcb" + [[projects]] digest = "1:83222dd265299f065a2ae969c7780b6c6a225c770975051955320a95b0526239" name = "github.com/coreos/prometheus-operator" @@ -49,6 +105,67 @@ revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" version = "v1.1.1" +[[projects]] + branch = "master" + digest = "1:a0937269866f68424e50a4d44e7dbaa0237891864c489ec2d44815786e810aa6" + name = "github.com/docker/distribution" + packages = [ + "digestset", + "reference", + ] + pruneopts = "" + revision = "93e082742a009850ac46962150b2f652a822c5ff" + +[[projects]] + branch = "master" + digest = "1:be1362b6824834f01e7b338e3e40d0ed0eb494d2e21f6270e406c2df9c1157d4" + name = "github.com/docker/docker" + packages = [ + "api/types", + "api/types/blkiodev", + "api/types/container", + "api/types/filters", + "api/types/mount", + "api/types/network", + "api/types/registry", + "api/types/strslice", + "api/types/swarm", + "api/types/swarm/runtime", + "api/types/versions", + "errdefs", + "pkg/term", + "pkg/term/windows", + ] + pruneopts = "" + revision = "0b7cb16dde4a20d024c7be59801d63bcfd18611b" + +[[projects]] + digest = "1:ebe593d8b65a2947b78b6e164a2dac1a230b977a700b694da3a398b03b7afb04" + name = "github.com/docker/go-connections" + packages = ["nat"] + pruneopts = "" + revision = "7395e3f8aa162843a74ed6d48e79627d9792ac55" + version = "v0.4.0" + +[[projects]] + digest = "1:582d54fcb7233da8dde1dfd2210a5b9675d0685f84246a8d317b07d680c18b1b" + name = "github.com/docker/go-units" + packages = ["."] + pruneopts = "" + revision = "47565b4f722fb6ceae66b95f853feed578a4a51c" + version = "v0.3.3" + +[[projects]] + branch = "master" + digest = "1:d6c13a378213e3de60445e49084b8a0a9ce582776dfc77927775dbeb3ff72a35" + name = "github.com/docker/spdystream" + packages = [ + ".", + "spdy", + ] + pruneopts = "" + revision = "6480d4af844c189cf5dd913db24ddd339d3a4f85" + [[projects]] digest = "1:8a34d7a37b8f07239487752e14a5faafcbbc718fc385ad429a2c4ac6f27a207f" name = "github.com/emicklei/go-restful" @@ -60,6 +177,30 @@ revision = "3eb9738c1697594ea6e71a7156a9bb32ed216cf0" version = "v2.8.0" +[[projects]] + digest = "1:4216202f4088a73e2982df875e2f0d1401137bbc248e57391e70547af167a18a" + name = "github.com/evanphx/json-patch" + packages = ["."] + pruneopts = "" + revision = "72bf35d0ff611848c1dc9df0f976c81192392fa5" + version = "v4.1.0" + +[[projects]] + branch = "master" + digest = "1:549f95037fea25e00a5341ac6a169a5b3e5306be107f45260440107b779b74f9" + name = "github.com/exponent-io/jsonpath" + packages = ["."] + pruneopts = "" + revision = "d6023ce2651d8eafb5c75bb0c7167536102ec9f5" + +[[projects]] + digest = "1:23a5efa4b272df86a8ebffc942f5e0c1aac4b750836037394cc450b6d91e241a" + name = "github.com/fatih/camelcase" + packages = ["."] + pruneopts = "" + revision = "44e46d280b43ec1531bb25252440e34f1b800b65" + version = "v1.0.0" + [[projects]] digest = "1:b13707423743d41665fd23f0c36b2f37bb49c30e94adb813319c44188a51ba22" name = "github.com/ghodss/yaml" @@ -124,6 +265,23 @@ revision = "047ecc927cd0b7d27bab83eb948e120fb7d1ea68" version = "v1.6.5" +[[projects]] + digest = "1:9ab1b1c637d7c8f49e39d8538a650d7eb2137b076790cff69d160823b505964c" + name = "github.com/gobwas/glob" + packages = [ + ".", + "compiler", + "match", + "syntax", + "syntax/ast", + "syntax/lexer", + "util/runes", + "util/strings", + ] + pruneopts = "" + revision = "5ccd90ef52e1e632236f7326478d4faa74f99438" + version = "v0.2.3" + [[projects]] digest = "1:6e73003ecd35f4487a5e88270d3ca0a81bc80dc88053ac7e4dcfec5fba30d918" name = "github.com/gogo/protobuf" @@ -212,6 +370,14 @@ pruneopts = "" revision = "9cad4c3443a7200dd6400aef47183728de563a38" +[[projects]] + digest = "1:e24dc5ef44694848785de507f439a24e9e6d96d7b43b8cf3d6cfa857aa1e2186" + name = "github.com/grpc-ecosystem/go-grpc-prometheus" + packages = ["."] + pruneopts = "" + revision = "c225b8c3b01faf2899099b768856a9e916e5087b" + version = "v1.2.0" + [[projects]] digest = "1:3313a63031ae281e5f6fd7b0bbca733dfa04d2429df86519e3b4d4c016ccb836" name = "github.com/hashicorp/golang-lru" @@ -223,6 +389,14 @@ revision = "20f1fb78b0740ba8c3cb143a61e86ba5c8669768" version = "v0.5.0" +[[projects]] + digest = "1:35979179658d20a73693589e67bdc3baf4dc0ef9f524b1dfd3cc55fb5f6ae384" + name = "github.com/huandu/xstrings" + packages = ["."] + pruneopts = "" + revision = "f02667b379e2fb5916c3cda2cf31e0eb885d79f8" + version = "v1.2.0" + [[projects]] digest = "1:7ab38c15bd21e056e3115c8b526d201eaf74e0308da9370997c6b3c187115d36" name = "github.com/imdario/mergo" @@ -283,6 +457,14 @@ revision = "28bf78dadb0f64748ff13a0b6547e4972a5cea64" version = "v1.0.1" +[[projects]] + branch = "master" + digest = "1:ca34c37fb23c64d230ef3d367e63860db359fd73d3f0494b3941618100e63c9e" + name = "github.com/martinlindhe/base36" + packages = ["."] + pruneopts = "" + revision = "5cda0030da1725952be91a1223090c4ecef8dfb2" + [[projects]] branch = "master" digest = "1:58050e2bc9621cc6b68c1da3e4a0d1c40ad1f89062b9855c26521fd42a97a106" @@ -299,6 +481,14 @@ revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" version = "v1.0.1" +[[projects]] + digest = "1:713b341855f1480e4baca1e7c5434e1d266441340685ecbde32d59bdc065fb3f" + name = "github.com/mitchellh/go-wordwrap" + packages = ["."] + pruneopts = "" + revision = "9e67c67572bc5dd02aef930e2b0ae3c02a4b5a5c" + version = "v1.0.0" + [[projects]] digest = "1:0c0ff2a89c1bb0d01887e1dac043ad7efbf3ec77482ef058ac423d13497e16fd" name = "github.com/modern-go/concurrent" @@ -323,6 +513,25 @@ pruneopts = "" revision = "cca7078d478f8520f85629ad7c68962d31ed7682" +[[projects]] + digest = "1:5d9b668b0b4581a978f07e7d2e3314af18eb27b3fb5d19b70185b7c575723d11" + name = "github.com/opencontainers/go-digest" + packages = ["."] + pruneopts = "" + revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf" + version = "v1.0.0-rc1" + +[[projects]] + digest = "1:f26c8670b11e29a49c8e45f7ec7f2d5bac62e8fd4e3c0ae1662baa4a697f984a" + name = "github.com/opencontainers/image-spec" + packages = [ + "specs-go", + "specs-go/v1", + ] + pruneopts = "" + revision = "d60099175f88c47cd379c4738d158884749ed235" + version = "v1.0.1" + [[projects]] digest = "1:a5484d4fa43127138ae6e7b2299a6a52ae006c7f803d98d717f60abf3e97192e" name = "github.com/pborman/uuid" @@ -355,6 +564,14 @@ revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" +[[projects]] + digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411" + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + pruneopts = "" + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + [[projects]] digest = "1:f3e56d302f80d760e718743f89f4e7eaae532d4218ba330e979bd051f78de141" name = "github.com/prometheus/client_golang" @@ -400,6 +617,14 @@ pruneopts = "" revision = "185b4288413d2a0dd0806f78c90dde719829e5ae" +[[projects]] + branch = "master" + digest = "1:7e0b48d8bdbf9278ce4a68510c5bf3d074e4d7f8ba6d18512b7c6eb50cc5c6e6" + name = "github.com/russross/blackfriday" + packages = ["."] + pruneopts = "" + revision = "abafa45cd843f4f4935c29a2e8f3d73a01820fc3" + [[projects]] digest = "1:3962f553b77bf6c03fc07cd687a22dd3b00fe11aa14d31194f5505f5bb65cdc8" name = "github.com/sergi/go-diff" @@ -443,6 +668,25 @@ revision = "298182f68c66c05229eb03ac171abe6e309ee79a" version = "v1.0.3" +[[projects]] + digest = "1:c587772fb8ad29ad4db67575dad25ba17a51f072ff18a22b4f0257a4d9c24f75" + name = "github.com/stretchr/testify" + packages = [ + "assert", + "require", + ] + pruneopts = "" + revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" + version = "v1.2.2" + +[[projects]] + digest = "1:55f6dbdbb14deb241820938fb00e80dec4c1b3536cc60dc26f70556e05658e07" + name = "github.com/technosophos/moniker" + packages = ["."] + pruneopts = "" + revision = "a5dbd03a2245d554160e3ae6bfdcf969fe58b431" + version = "0.2.0" + [[projects]] digest = "1:74f86c458e82e1c4efbab95233e0cf51b7cc02dc03193be9f62cd81224e10401" name = "go.uber.org/atomic" @@ -478,7 +722,13 @@ branch = "master" digest = "1:78f41d38365ccef743e54ed854a2faf73313ba0750c621116a8eeb0395590bd0" name = "golang.org/x/crypto" - packages = ["ssh/terminal"] + packages = [ + "ed25519", + "ed25519/internal/edwards25519", + "pbkdf2", + "scrypt", + "ssh/terminal", + ] pruneopts = "" revision = "0c41d7ab0a0ee717d4590a44bcb987dfd9e183eb" @@ -494,6 +744,8 @@ "http2", "http2/hpack", "idna", + "internal/timeseries", + "trace", ] pruneopts = "" revision = "04a2e542c03f1d053ab3e4d6e5abcd4b66e2be8e" @@ -515,12 +767,18 @@ packages = [ "collate", "collate/build", + "encoding", + "encoding/internal", + "encoding/internal/identifier", + "encoding/unicode", "internal/colltab", "internal/gen", "internal/tag", "internal/triegen", "internal/ucd", + "internal/utf8internal", "language", + "runes", "secure/bidirule", "transform", "unicode/bidi", @@ -554,6 +812,49 @@ pruneopts = "" revision = "6adeb8aab2ded9eb693b831d5fd090c10a6ebdfa" +[[projects]] + branch = "master" + digest = "1:212d4045ef941b209a154001718705dc723bd77e0200fcea36d15ec87ed49dec" + name = "google.golang.org/genproto" + packages = ["googleapis/rpc/status"] + pruneopts = "" + revision = "b5d43981345bdb2c233eb4bf3277847b48c6fdc6" + +[[projects]] + digest = "1:1293087271e314cfa2b3decededba2ecba0ff327e7b7809e00f73f616449191c" + name = "google.golang.org/grpc" + packages = [ + ".", + "balancer", + "balancer/base", + "balancer/roundrobin", + "codes", + "connectivity", + "credentials", + "encoding", + "encoding/proto", + "grpclog", + "internal", + "internal/backoff", + "internal/channelz", + "internal/envconfig", + "internal/grpcrand", + "internal/transport", + "keepalive", + "metadata", + "naming", + "peer", + "resolver", + "resolver/dns", + "resolver/passthrough", + "stats", + "status", + "tap", + ] + pruneopts = "" + revision = "2e463a05d100327ca47ac218281906921038fd95" + version = "v1.16.0" + [[projects]] digest = "1:75fb3fcfc73a8c723efde7777b40e8e8ff9babf30d8c56160d01beffea8a95a6" name = "gopkg.in/inf.v0" @@ -562,6 +863,19 @@ revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf" version = "v0.9.1" +[[projects]] + digest = "1:ddc5fa8f9159bea7d1ce58143e6d8fd8054018f7bc3709940aa7f7bc92855ed9" + name = "gopkg.in/square/go-jose.v2" + packages = [ + ".", + "cipher", + "json", + "jwt", + ] + pruneopts = "" + revision = "ef984e69dd356202fd4e4910d4d9c24468bdf0b8" + version = "v2.1.9" + [[projects]] digest = "1:f0620375dd1f6251d9973b5f2596228cc8042e887cd7f827e4220bc1ce8c30e2" name = "gopkg.in/yaml.v2" @@ -593,6 +907,7 @@ "core/v1", "events/v1beta1", "extensions/v1beta1", + "imagepolicy/v1alpha1", "networking/v1", "policy/v1beta1", "rbac/v1", @@ -616,6 +931,7 @@ "pkg/apis/apiextensions", "pkg/apis/apiextensions/v1beta1", "pkg/client/clientset/clientset/scheme", + "pkg/features", ] pruneopts = "" revision = "408db4a50408e2149acbd657bceb2480c13cb0a4" @@ -625,12 +941,17 @@ digest = "1:b6b2fb7b4da1ac973b64534ace2299a02504f16bc7820cb48edb8ca4077183e1" name = "k8s.io/apimachinery" packages = [ + "pkg/api/equality", "pkg/api/errors", "pkg/api/meta", "pkg/api/resource", + "pkg/api/validation", + "pkg/api/validation/path", "pkg/apis/meta/internalversion", "pkg/apis/meta/v1", "pkg/apis/meta/v1/unstructured", + "pkg/apis/meta/v1/unstructured/unstructuredscheme", + "pkg/apis/meta/v1/validation", "pkg/apis/meta/v1beta1", "pkg/conversion", "pkg/conversion/queryparams", @@ -649,14 +970,18 @@ "pkg/util/cache", "pkg/util/clock", "pkg/util/diff", + "pkg/util/duration", "pkg/util/errors", "pkg/util/framer", "pkg/util/httpstream", + "pkg/util/httpstream/spdy", "pkg/util/intstr", "pkg/util/json", "pkg/util/mergepatch", "pkg/util/net", "pkg/util/proxy", + "pkg/util/rand", + "pkg/util/remotecommand", "pkg/util/runtime", "pkg/util/sets", "pkg/util/strategicpatch", @@ -675,6 +1000,22 @@ revision = "103fd098999dc9c0c88536f5c9ad2e5da39373ae" version = "kubernetes-1.11.2" +[[projects]] + digest = "1:e6cecaccdaf6f0202cd76db4427a95f8591b37f7ae7ce287219ad48747828c0c" + name = "k8s.io/apiserver" + packages = [ + "pkg/apis/audit", + "pkg/authentication/authenticator", + "pkg/authentication/serviceaccount", + "pkg/authentication/user", + "pkg/endpoints/request", + "pkg/features", + "pkg/util/feature", + ] + pruneopts = "" + revision = "1844acd6a03501626cd17e86248e6ebc0f3df5d9" + version = "kubernetes-1.11.2" + [[projects]] digest = "1:da788b52eda4a8cd4c564a69051b029f310f4ec232cfa3ec0e49b80b0e7b6616" name = "k8s.io/client-go" @@ -714,6 +1055,8 @@ "kubernetes/typed/storage/v1", "kubernetes/typed/storage/v1alpha1", "kubernetes/typed/storage/v1beta1", + "listers/apps/v1", + "listers/core/v1", "pkg/apis/clientauthentication", "pkg/apis/clientauthentication/v1alpha1", "pkg/apis/clientauthentication/v1beta1", @@ -722,7 +1065,16 @@ "rest", "rest/watch", "restmapper", + "scale", + "scale/scheme", + "scale/scheme/appsint", + "scale/scheme/appsv1beta1", + "scale/scheme/appsv1beta2", + "scale/scheme/autoscalingv1", + "scale/scheme/extensionsint", + "scale/scheme/extensionsv1beta1", "testing", + "third_party/forked/golang/template", "tools/auth", "tools/cache", "tools/clientcmd", @@ -733,15 +1085,20 @@ "tools/leaderelection/resourcelock", "tools/metrics", "tools/pager", + "tools/portforward", "tools/record", "tools/reference", + "tools/remotecommand", "transport", + "transport/spdy", "util/buffer", "util/cert", "util/connrotation", + "util/exec", "util/flowcontrol", "util/homedir", "util/integer", + "util/jsonpath", "util/retry", "util/workqueue", ] @@ -749,6 +1106,37 @@ revision = "1f13a808da65775f22cbf47862c4e5898d8f4ca1" version = "kubernetes-1.11.2" +[[projects]] + digest = "1:3836fc3b7c902b550b5e523ac479e957e15f6c367e3706a651e77f0d8ebefa8d" + name = "k8s.io/helm" + packages = [ + "pkg/chartutil", + "pkg/engine", + "pkg/hooks", + "pkg/ignore", + "pkg/kube", + "pkg/manifest", + "pkg/proto/hapi/chart", + "pkg/proto/hapi/release", + "pkg/proto/hapi/rudder", + "pkg/proto/hapi/services", + "pkg/proto/hapi/version", + "pkg/releasetesting", + "pkg/releaseutil", + "pkg/rudder", + "pkg/storage", + "pkg/storage/driver", + "pkg/storage/errors", + "pkg/sympath", + "pkg/tiller", + "pkg/tiller/environment", + "pkg/timeconv", + "pkg/version", + ] + pruneopts = "" + revision = "2e55dbe1fdb5fdb96b75ff144a339489417b146b" + version = "v2.11.0" + [[projects]] branch = "master" digest = "1:27b5d6ad25d086dda2c482099d4b918a2c3e947f80e0671fa366732daf59afed" @@ -756,10 +1144,171 @@ packages = [ "pkg/common", "pkg/util/proto", + "pkg/util/proto/validation", ] pruneopts = "" revision = "e494cc58111187acad93e64529228a2fc0153e39" +[[projects]] + digest = "1:89eef4670d767c8f3e520d71509f40e77698b4bc898305b6ee0220300840b788" + name = "k8s.io/kubernetes" + packages = [ + "pkg/api/events", + "pkg/api/legacyscheme", + "pkg/api/pod", + "pkg/api/ref", + "pkg/api/resource", + "pkg/api/service", + "pkg/api/v1/pod", + "pkg/apis/admissionregistration", + "pkg/apis/admissionregistration/install", + "pkg/apis/admissionregistration/v1alpha1", + "pkg/apis/admissionregistration/v1beta1", + "pkg/apis/apps", + "pkg/apis/apps/install", + "pkg/apis/apps/v1", + "pkg/apis/apps/v1beta1", + "pkg/apis/apps/v1beta2", + "pkg/apis/authentication", + "pkg/apis/authentication/install", + "pkg/apis/authentication/v1", + "pkg/apis/authentication/v1beta1", + "pkg/apis/authorization", + "pkg/apis/authorization/install", + "pkg/apis/authorization/v1", + "pkg/apis/authorization/v1beta1", + "pkg/apis/autoscaling", + "pkg/apis/autoscaling/install", + "pkg/apis/autoscaling/v1", + "pkg/apis/autoscaling/v2beta1", + "pkg/apis/batch", + "pkg/apis/batch/install", + "pkg/apis/batch/v1", + "pkg/apis/batch/v1beta1", + "pkg/apis/batch/v2alpha1", + "pkg/apis/certificates", + "pkg/apis/certificates/install", + "pkg/apis/certificates/v1beta1", + "pkg/apis/componentconfig", + "pkg/apis/componentconfig/install", + "pkg/apis/componentconfig/v1alpha1", + "pkg/apis/core", + "pkg/apis/core/helper", + "pkg/apis/core/helper/qos", + "pkg/apis/core/install", + "pkg/apis/core/pods", + "pkg/apis/core/v1", + "pkg/apis/core/v1/helper", + "pkg/apis/core/validation", + "pkg/apis/events", + "pkg/apis/events/install", + "pkg/apis/events/v1beta1", + "pkg/apis/extensions", + "pkg/apis/extensions/install", + "pkg/apis/extensions/v1beta1", + "pkg/apis/networking", + "pkg/apis/networking/install", + "pkg/apis/networking/v1", + "pkg/apis/policy", + "pkg/apis/policy/install", + "pkg/apis/policy/v1beta1", + "pkg/apis/rbac", + "pkg/apis/rbac/install", + "pkg/apis/rbac/v1", + "pkg/apis/rbac/v1alpha1", + "pkg/apis/rbac/v1beta1", + "pkg/apis/scheduling", + "pkg/apis/scheduling/install", + "pkg/apis/scheduling/v1alpha1", + "pkg/apis/scheduling/v1beta1", + "pkg/apis/settings", + "pkg/apis/settings/install", + "pkg/apis/settings/v1alpha1", + "pkg/apis/storage", + "pkg/apis/storage/install", + "pkg/apis/storage/util", + "pkg/apis/storage/v1", + "pkg/apis/storage/v1alpha1", + "pkg/apis/storage/v1beta1", + "pkg/capabilities", + "pkg/client/clientset_generated/internalclientset", + "pkg/client/clientset_generated/internalclientset/scheme", + "pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/apps/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/batch/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/core/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/events/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/networking/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/policy/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/settings/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/storage/internalversion", + "pkg/controller", + "pkg/controller/deployment/util", + "pkg/credentialprovider", + "pkg/features", + "pkg/fieldpath", + "pkg/generated", + "pkg/kubectl", + "pkg/kubectl/apps", + "pkg/kubectl/cmd/get", + "pkg/kubectl/cmd/templates", + "pkg/kubectl/cmd/util", + "pkg/kubectl/cmd/util/openapi", + "pkg/kubectl/cmd/util/openapi/validation", + "pkg/kubectl/genericclioptions", + "pkg/kubectl/genericclioptions/printers", + "pkg/kubectl/genericclioptions/resource", + "pkg/kubectl/scheme", + "pkg/kubectl/util", + "pkg/kubectl/util/hash", + "pkg/kubectl/util/i18n", + "pkg/kubectl/util/slice", + "pkg/kubectl/util/term", + "pkg/kubectl/validation", + "pkg/kubelet/apis", + "pkg/kubelet/types", + "pkg/master/ports", + "pkg/printers", + "pkg/printers/internalversion", + "pkg/registry/rbac/validation", + "pkg/scheduler/algorithm", + "pkg/scheduler/algorithm/priorities/util", + "pkg/scheduler/api", + "pkg/scheduler/cache", + "pkg/scheduler/util", + "pkg/security/apparmor", + "pkg/serviceaccount", + "pkg/util/file", + "pkg/util/hash", + "pkg/util/interrupt", + "pkg/util/labels", + "pkg/util/net/sets", + "pkg/util/node", + "pkg/util/parsers", + "pkg/util/pointer", + "pkg/util/slice", + "pkg/util/taints", + "pkg/version", + ] + pruneopts = "" + revision = "bf9a868e8ea3d3a8fa53cbb22f566771b3f8068b" + version = "v1.11.4" + +[[projects]] + branch = "master" + digest = "1:bea542e853f98bfcc80ecbe8fe0f32bc52c97664102aacdd7dca676354ef2faa" + name = "k8s.io/utils" + packages = ["exec"] + pruneopts = "" + revision = "0d26856f57b32ec3398579285e5c8a2bfe8c5243" + [[projects]] digest = "1:6cad2468c5831529b860a01f09032f6ff38202bc4f76332ef7ad74a993e4aa5a" name = "sigs.k8s.io/controller-runtime" @@ -795,6 +1344,14 @@ revision = "53fc44b56078cd095b11bd44cfa0288ee4cf718f" version = "v0.1.4" +[[projects]] + branch = "master" + digest = "1:ef95cf7afffff0466e8fcfcb793cb2f76a60f3942dcfc16a56ce07473757a395" + name = "vbom.ml/util" + packages = ["sortorder"] + pruneopts = "" + revision = "efcd4e0f97874370259c7d93e12aad57911dea81" + [solve-meta] analyzer-name = "dep" analyzer-version = 1 @@ -804,11 +1361,15 @@ "github.com/ghodss/yaml", "github.com/go-logr/logr", "github.com/markbates/inflect", + "github.com/martinlindhe/base36", + "github.com/pborman/uuid", "github.com/prometheus/client_golang/prometheus/promhttp", "github.com/sergi/go-diff/diffmatchpatch", "github.com/sirupsen/logrus", "github.com/spf13/afero", "github.com/spf13/cobra", + "github.com/stretchr/testify/assert", + "github.com/stretchr/testify/require", "golang.org/x/tools/imports", "gopkg.in/yaml.v2", "k8s.io/api/core/v1", @@ -826,6 +1387,7 @@ "k8s.io/apimachinery/pkg/util/net", "k8s.io/apimachinery/pkg/util/proxy", "k8s.io/apimachinery/pkg/util/wait", + "k8s.io/client-go/discovery", "k8s.io/client-go/discovery/cached", "k8s.io/client-go/kubernetes", "k8s.io/client-go/kubernetes/scheme", @@ -834,6 +1396,18 @@ "k8s.io/client-go/tools/clientcmd", "k8s.io/client-go/tools/clientcmd/api", "k8s.io/client-go/transport", + "k8s.io/helm/pkg/chartutil", + "k8s.io/helm/pkg/engine", + "k8s.io/helm/pkg/kube", + "k8s.io/helm/pkg/proto/hapi/chart", + "k8s.io/helm/pkg/proto/hapi/release", + "k8s.io/helm/pkg/proto/hapi/services", + "k8s.io/helm/pkg/releaseutil", + "k8s.io/helm/pkg/storage", + "k8s.io/helm/pkg/tiller", + "k8s.io/helm/pkg/tiller/environment", + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset", + "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource", "sigs.k8s.io/controller-runtime/pkg/client", "sigs.k8s.io/controller-runtime/pkg/client/config", "sigs.k8s.io/controller-runtime/pkg/client/fake", diff --git a/Gopkg.toml b/Gopkg.toml index 0e3dbae0e4..efccc37e74 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -18,10 +18,34 @@ name = "k8s.io/apiextensions-apiserver" version = "kubernetes-1.11.2" +[[override]] + name = "k8s.io/apiserver" + version = "kubernetes-1.11.2" + [[override]] name = "k8s.io/client-go" version = "kubernetes-1.11.2" +[[override]] + name = "k8s.io/kubernetes" + version = "1.11.2" + +[[constraint]] + name = "k8s.io/helm" + version = "2.11.0" + +[[override]] + name = "github.com/russross/blackfriday" + branch = "master" + +[[override]] + name = "github.com/docker/distribution" + branch = "master" + +[[override]] + name = "github.com/docker/docker" + branch = "master" + [[constraint]] name = "github.com/sergi/go-diff" version = "1.0.0" diff --git a/pkg/helm/controller/controller.go b/pkg/helm/controller/controller.go index 438f6e5b6b..48dca8ece6 100644 --- a/pkg/helm/controller/controller.go +++ b/pkg/helm/controller/controller.go @@ -28,7 +28,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "sigs.k8s.io/controller-runtime/pkg/source" - "github.com/operator-framework/helm-app-operator-kit/helm-app-operator/pkg/helm/release" + "github.com/operator-framework/operator-sdk/pkg/helm/release" ) var log = logf.Log.WithName("helm.controller") diff --git a/pkg/helm/controller/reconcile.go b/pkg/helm/controller/reconcile.go index 79b688308d..8bfeb1f2a5 100644 --- a/pkg/helm/controller/reconcile.go +++ b/pkg/helm/controller/reconcile.go @@ -25,9 +25,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "github.com/operator-framework/helm-app-operator-kit/helm-app-operator/pkg/helm/internal/types" - "github.com/operator-framework/helm-app-operator-kit/helm-app-operator/pkg/helm/internal/util" - "github.com/operator-framework/helm-app-operator-kit/helm-app-operator/pkg/helm/release" + "github.com/operator-framework/operator-sdk/pkg/helm/internal/types" + "github.com/operator-framework/operator-sdk/pkg/helm/internal/util" + "github.com/operator-framework/operator-sdk/pkg/helm/release" ) var _ reconcile.Reconciler = &HelmOperatorReconciler{} diff --git a/pkg/helm/release/manager.go b/pkg/helm/release/manager.go index 628b6885bb..4d149818bd 100644 --- a/pkg/helm/release/manager.go +++ b/pkg/helm/release/manager.go @@ -35,7 +35,7 @@ import ( "k8s.io/helm/pkg/tiller" "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" - "github.com/operator-framework/helm-app-operator-kit/helm-app-operator/pkg/helm/internal/types" + "github.com/operator-framework/operator-sdk/pkg/helm/internal/types" ) var ( diff --git a/pkg/helm/release/manager_factory.go b/pkg/helm/release/manager_factory.go index ead9810829..3e9938717c 100644 --- a/pkg/helm/release/manager_factory.go +++ b/pkg/helm/release/manager_factory.go @@ -31,8 +31,8 @@ import ( "k8s.io/helm/pkg/tiller/environment" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - "github.com/operator-framework/helm-app-operator-kit/helm-app-operator/pkg/helm/engine" - "github.com/operator-framework/helm-app-operator-kit/helm-app-operator/pkg/helm/internal/types" + "github.com/operator-framework/operator-sdk/pkg/helm/engine" + "github.com/operator-framework/operator-sdk/pkg/helm/internal/types" ) // ManagerFactory creates Managers that are specific to custom resources. It is From 3413baaa1217ff23ba2ab6aa94f536ce8c302645 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Mon, 26 Nov 2018 10:24:39 -0500 Subject: [PATCH 03/12] pkg/helm/internal/util: removing unused resource.go --- pkg/helm/internal/util/resource.go | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 pkg/helm/internal/util/resource.go diff --git a/pkg/helm/internal/util/resource.go b/pkg/helm/internal/util/resource.go deleted file mode 100644 index dacaf513c7..0000000000 --- a/pkg/helm/internal/util/resource.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2018 The Operator-SDK Authors -// -// 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 util - -import ( - "fmt" - - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -// ResourceString returns a human friendly string for the custom resource -func ResourceString(r *unstructured.Unstructured) string { - return fmt.Sprintf("apiVersion=%s kind=%s name=%s/%s", r.GetAPIVersion(), r.GetKind(), r.GetNamespace(), r.GetName()) -} From 37784a3db72d51fc63cc110c727db829b014891c Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Mon, 26 Nov 2018 11:20:00 -0500 Subject: [PATCH 04/12] commands,pkg/scaffold/helm: adding helm operator CLI (new, up local) --- Gopkg.lock | 1 + commands/operator-sdk/cmd/new.go | 54 ++++++++++++++++++- commands/operator-sdk/cmd/up/local.go | 59 ++++++++++++++++++++ internal/util/projutil/project_util.go | 17 ++++-- pkg/scaffold/ansible/galaxy_init.go | 9 +++- pkg/scaffold/helm/chart.go | 51 ++++++++++++++++++ pkg/scaffold/helm/dockerfile.go | 42 +++++++++++++++ pkg/scaffold/helm/operator.go | 74 ++++++++++++++++++++++++++ pkg/scaffold/helm/watches.go | 45 ++++++++++++++++ pkg/scaffold/role.go | 6 +++ pkg/scaffold/role_test.go | 12 +++++ 11 files changed, 363 insertions(+), 7 deletions(-) create mode 100644 pkg/scaffold/helm/chart.go create mode 100644 pkg/scaffold/helm/dockerfile.go create mode 100644 pkg/scaffold/helm/operator.go create mode 100644 pkg/scaffold/helm/watches.go diff --git a/Gopkg.lock b/Gopkg.lock index 2d44f3ace1..d97f8899d7 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1404,6 +1404,7 @@ "k8s.io/helm/pkg/proto/hapi/services", "k8s.io/helm/pkg/releaseutil", "k8s.io/helm/pkg/storage", + "k8s.io/helm/pkg/storage/driver", "k8s.io/helm/pkg/tiller", "k8s.io/helm/pkg/tiller/environment", "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset", diff --git a/commands/operator-sdk/cmd/new.go b/commands/operator-sdk/cmd/new.go index e84c74587f..66c4f100e9 100644 --- a/commands/operator-sdk/cmd/new.go +++ b/commands/operator-sdk/cmd/new.go @@ -24,6 +24,7 @@ import ( "github.com/operator-framework/operator-sdk/internal/util/projutil" "github.com/operator-framework/operator-sdk/pkg/scaffold" "github.com/operator-framework/operator-sdk/pkg/scaffold/ansible" + "github.com/operator-framework/operator-sdk/pkg/scaffold/helm" "github.com/operator-framework/operator-sdk/pkg/scaffold/input" log "github.com/sirupsen/logrus" @@ -89,6 +90,8 @@ func newFunc(cmd *cobra.Command, args []string) { pullDep() case projutil.OperatorTypeAnsible: doAnsibleScaffold() + case projutil.OperatorTypeHelm: + doHelmScaffold() } initGit() @@ -241,9 +244,56 @@ func doAnsibleScaffold() { } } +func doHelmScaffold() { + cfg := &input.Config{ + AbsProjectPath: filepath.Join(projutil.MustGetwd(), projectName), + ProjectName: projectName, + } + + resource, err := scaffold.NewResource(apiVersion, kind) + if err != nil { + log.Fatalf("invalid apiVersion and kind: (%v)", err) + } + + s := &scaffold.Scaffold{} + err = s.Execute(cfg, + &helm.Dockerfile{}, + &helm.WatchesYAML{ + Resource: resource, + }, + &scaffold.ServiceAccount{}, + &scaffold.Role{ + IsClusterScoped: isClusterScoped, + }, + &scaffold.RoleBinding{ + IsClusterScoped: isClusterScoped, + }, + &helm.Operator{ + IsClusterScoped: isClusterScoped, + }, + &scaffold.Crd{ + Resource: resource, + }, + &scaffold.Cr{ + Resource: resource, + }, + ) + if err != nil { + log.Fatalf("new scaffold failed: (%v)", err) + } + + if err := helm.CreateChartForResource(resource, cfg.AbsProjectPath); err != nil { + log.Fatalf("failed to create initial chart for resource (%v, %v): (%v)", resource.APIVersion, resource.Kind, err) + } + + if err := scaffold.UpdateRoleForResource(resource, cfg.AbsProjectPath); err != nil { + log.Fatalf("failed to update the RBAC manifest for resource (%v, %v): (%v)", resource.APIVersion, resource.Kind, err) + } +} + func verifyFlags() { - if operatorType != projutil.OperatorTypeGo && operatorType != projutil.OperatorTypeAnsible { - log.Fatal("--type can only be `go` or `ansible`") + if operatorType != projutil.OperatorTypeGo && operatorType != projutil.OperatorTypeAnsible && operatorType != projutil.OperatorTypeHelm { + log.Fatal("--type can only be `go`, `ansible`, or `helm`") } if operatorType != projutil.OperatorTypeAnsible && generatePlaybook { log.Fatal("--generate-playbook can only be used with --type `ansible`") diff --git a/commands/operator-sdk/cmd/up/local.go b/commands/operator-sdk/cmd/up/local.go index d5ad938968..54663f7ca5 100644 --- a/commands/operator-sdk/cmd/up/local.go +++ b/commands/operator-sdk/cmd/up/local.go @@ -26,13 +26,21 @@ import ( "syscall" "time" + "sigs.k8s.io/controller-runtime/pkg/runtime/signals" + "github.com/operator-framework/operator-sdk/internal/util/projutil" ansibleOperator "github.com/operator-framework/operator-sdk/pkg/ansible/operator" proxy "github.com/operator-framework/operator-sdk/pkg/ansible/proxy" + "github.com/operator-framework/operator-sdk/pkg/helm/client" + "github.com/operator-framework/operator-sdk/pkg/helm/controller" + "github.com/operator-framework/operator-sdk/pkg/helm/release" "github.com/operator-framework/operator-sdk/pkg/k8sutil" "github.com/operator-framework/operator-sdk/pkg/scaffold" ansibleScaffold "github.com/operator-framework/operator-sdk/pkg/scaffold/ansible" + helmScaffold "github.com/operator-framework/operator-sdk/pkg/scaffold/helm" sdkVersion "github.com/operator-framework/operator-sdk/version" + "k8s.io/helm/pkg/storage" + "k8s.io/helm/pkg/storage/driver" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -82,6 +90,8 @@ func upLocalFunc(cmd *cobra.Command, args []string) { upLocal() case projutil.OperatorTypeAnsible: upLocalAnsible() + case projutil.OperatorTypeHelm: + upLocalHelm() default: log.Fatal("failed to determine operator type") } @@ -171,6 +181,55 @@ func upLocalAnsible() { log.Info("Exiting.") } +func upLocalHelm() { + // Set the kubeconfig that the manager will be able to grab + os.Setenv(k8sutil.KubeConfigEnvVar, kubeConfig) + + logf.SetLogger(logf.ZapLogger(false)) + + printVersion() + + cfg, err := config.GetConfig() + if err != nil { + log.Fatal(err) + } + + mgr, err := manager.New(cfg, manager.Options{Namespace: namespace}) + if err != nil { + log.Fatal(err) + } + + // Create Tiller's storage backend and kubernetes client + storageBackend := storage.Init(driver.NewMemory()) + tillerKubeClient, err := client.NewFromManager(mgr) + if err != nil { + log.Fatal(err) + } + + factories, err := release.NewManagerFactoriesFromFile(storageBackend, tillerKubeClient, helmScaffold.WatchesYamlFile) + if err != nil { + log.Fatal(err) + } + + for gvk, factory := range factories { + // Register the controller with the factory. + err := controller.Add(mgr, controller.WatchOptions{ + Namespace: namespace, + GVK: gvk, + ManagerFactory: factory, + ResyncPeriod: time.Second * 5, + }) + if err != nil { + log.Fatal(err) + } + } + + // Start the Cmd + if err := mgr.Start(signals.SetupSignalHandler()); err != nil { + log.Fatal(err) + } +} + func printVersion() { log.Infof("Go Version: %s", runtime.Version()) log.Infof("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH) diff --git a/internal/util/projutil/project_util.go b/internal/util/projutil/project_util.go index c0a20c3428..d342021435 100644 --- a/internal/util/projutil/project_util.go +++ b/internal/util/projutil/project_util.go @@ -19,6 +19,8 @@ import ( "path/filepath" "strings" + "github.com/operator-framework/operator-sdk/pkg/scaffold/ansible" + "github.com/operator-framework/operator-sdk/pkg/scaffold/helm" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -41,6 +43,10 @@ const ( OperatorTypeGo OperatorType = "go" // OperatorTypeAnsible - ansible type of operator. OperatorTypeAnsible OperatorType = "ansible" + // OperatorTypeHelm - helm type of operator. + OperatorTypeHelm OperatorType = "helm" + // OperatorTypeUnknown - unknown type of operator. + OperatorTypeUnknown OperatorType = "unknown" ) // MustInProjectRoot checks if the current dir is the project root and returns the current repo's import path @@ -90,11 +96,16 @@ func CheckAndGetProjectGoPkg() string { // e.g: "go", "ansible" func GetOperatorType() OperatorType { // Assuming that if main.go exists then this is a Go operator - _, err := os.Stat(mainFile) - if err != nil && os.IsNotExist(err) { + if _, err := os.Stat(mainFile); err == nil { + return OperatorTypeGo + } + if stat, err := os.Stat(ansible.RolesDir); err == nil && stat.IsDir() { return OperatorTypeAnsible } - return OperatorTypeGo + if stat, err := os.Stat(helm.HelmChartsDir); err == nil && stat.IsDir() { + return OperatorTypeHelm + } + return OperatorTypeUnknown } // GetGopath gets GOPATH and makes sure it is set and non-empty. diff --git a/pkg/scaffold/ansible/galaxy_init.go b/pkg/scaffold/ansible/galaxy_init.go index e30bd8c57a..62de7612ba 100644 --- a/pkg/scaffold/ansible/galaxy_init.go +++ b/pkg/scaffold/ansible/galaxy_init.go @@ -15,6 +15,7 @@ package ansible import ( + "fmt" "io/ioutil" "path/filepath" @@ -22,6 +23,10 @@ import ( "github.com/operator-framework/operator-sdk/pkg/scaffold/input" ) +// RolesDir is the relative directory within an SDK project where Ansible roles +// are stored. +const RolesDir string = "roles" + // GalaxyInit - wrapper type GalaxyInit struct { input.Input @@ -41,7 +46,7 @@ func (g *GalaxyInit) GetInput() (input.Input, error) { if g.Path == "" { g.Path = filepath.Join(g.Dir, "galaxy_init.sh") } - g.TemplateBody = galaxyInitTmpl + g.TemplateBody = fmt.Sprintf(galaxyInitTmpl, RolesDir) g.IsExec = true return g.Input, nil } @@ -54,5 +59,5 @@ if ! which ansible-galaxy > /dev/null; then fi echo "Initializing role skeleton..." -ansible-galaxy init --init-path={{.Input.AbsProjectPath}}/roles/ {{.Resource.Kind}} +ansible-galaxy init --init-path={{.Input.AbsProjectPath}}/%s/ {{.Resource.Kind}} ` diff --git a/pkg/scaffold/helm/chart.go b/pkg/scaffold/helm/chart.go new file mode 100644 index 0000000000..e0dbb7d472 --- /dev/null +++ b/pkg/scaffold/helm/chart.go @@ -0,0 +1,51 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 helm + +import ( + "os" + "path/filepath" + + "github.com/operator-framework/operator-sdk/pkg/scaffold" + + log "github.com/sirupsen/logrus" + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +// HelmChartsDir is the relative directory within an SDK project where Helm +// charts are stored. +const HelmChartsDir string = "helm-charts" + +// CreateChartForResource creates a new helm chart in the SDK project for the +// provided resource. +func CreateChartForResource(r *scaffold.Resource, projectDir string) error { + log.Infof("Create %s/%s/", HelmChartsDir, r.LowerKind) + + chartfile := &chart.Metadata{ + Name: r.LowerKind, + Description: "A Helm chart for Kubernetes", + Version: "0.1.0", + AppVersion: "1.0", + ApiVersion: chartutil.ApiVersionV1, + } + + chartDir := filepath.Join(projectDir, HelmChartsDir) + if err := os.MkdirAll(chartDir, 0755); err != nil { + return err + } + _, err := chartutil.Create(chartfile, chartDir) + return err +} diff --git a/pkg/scaffold/helm/dockerfile.go b/pkg/scaffold/helm/dockerfile.go new file mode 100644 index 0000000000..40b9541ffd --- /dev/null +++ b/pkg/scaffold/helm/dockerfile.go @@ -0,0 +1,42 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 helm + +import ( + "path/filepath" + + "github.com/operator-framework/operator-sdk/pkg/scaffold" + "github.com/operator-framework/operator-sdk/pkg/scaffold/input" +) + +// Dockerfile specifies the Helm Dockerfile scaffold +type Dockerfile struct { + input.Input +} + +// GetInput gets the scaffold execution input +func (d *Dockerfile) GetInput() (input.Input, error) { + if d.Path == "" { + d.Path = filepath.Join(scaffold.BuildDir, scaffold.DockerfileFile) + } + d.TemplateBody = dockerFileHelmTmpl + return d.Input, nil +} + +const dockerFileHelmTmpl = `FROM quay.io/water-hole/helm-operator + +COPY helm-charts/ ${HOME}/helm-charts/ +COPY watches.yaml ${HOME}/watches.yaml +` diff --git a/pkg/scaffold/helm/operator.go b/pkg/scaffold/helm/operator.go new file mode 100644 index 0000000000..67453e35aa --- /dev/null +++ b/pkg/scaffold/helm/operator.go @@ -0,0 +1,74 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 helm + +import ( + "path/filepath" + + "github.com/operator-framework/operator-sdk/pkg/scaffold" + "github.com/operator-framework/operator-sdk/pkg/scaffold/input" +) + +// Operator specifies the Helm operator.yaml manifest scaffold +type Operator struct { + input.Input + + IsClusterScoped bool +} + +// GetInput gets the scaffold execution input +func (s *Operator) GetInput() (input.Input, error) { + if s.Path == "" { + s.Path = filepath.Join(scaffold.DeployDir, scaffold.OperatorYamlFile) + } + s.TemplateBody = operatorTemplate + return s.Input, nil +} + +const operatorTemplate = `apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{.ProjectName}} +spec: + replicas: 1 + selector: + matchLabels: + name: {{.ProjectName}} + template: + metadata: + labels: + name: {{.ProjectName}} + spec: + serviceAccountName: {{.ProjectName}} + containers: + - name: {{.ProjectName}} + # Replace this with the built image name + image: REPLACE_IMAGE + ports: + - containerPort: 60000 + name: metrics + imagePullPolicy: Always + env: + - name: WATCH_NAMESPACE + {{- if .IsClusterScoped }} + value: "" + {{- else }} + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- end}} + - name: OPERATOR_NAME + value: "{{.ProjectName}}" +` diff --git a/pkg/scaffold/helm/watches.go b/pkg/scaffold/helm/watches.go new file mode 100644 index 0000000000..8b6be88f1a --- /dev/null +++ b/pkg/scaffold/helm/watches.go @@ -0,0 +1,45 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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 helm + +import ( + "github.com/operator-framework/operator-sdk/pkg/scaffold" + "github.com/operator-framework/operator-sdk/pkg/scaffold/input" +) + +const WatchesYamlFile = "watches.yaml" + +// WatchesYAML specifies the Helm watches.yaml manifest scaffold +type WatchesYAML struct { + input.Input + + Resource *scaffold.Resource +} + +// GetInput gets the scaffold execution input +func (s *WatchesYAML) GetInput() (input.Input, error) { + if s.Path == "" { + s.Path = WatchesYamlFile + } + s.TemplateBody = watchesYAMLTmpl + return s.Input, nil +} + +const watchesYAMLTmpl = `--- +- version: {{.Resource.Version}} + group: {{.Resource.FullGroup}} + kind: {{.Resource.Kind}} + chart: /opt/helm/helm-charts/{{.Resource.LowerKind}} +` diff --git a/pkg/scaffold/role.go b/pkg/scaffold/role.go index b77b24db2f..6a6fca8530 100644 --- a/pkg/scaffold/role.go +++ b/pkg/scaffold/role.go @@ -167,6 +167,12 @@ rules: - secrets verbs: - "*" +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get - apiGroups: - apps resources: diff --git a/pkg/scaffold/role_test.go b/pkg/scaffold/role_test.go index 30897f0f0c..3c963a1803 100644 --- a/pkg/scaffold/role_test.go +++ b/pkg/scaffold/role_test.go @@ -63,6 +63,12 @@ rules: - secrets verbs: - "*" +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get - apiGroups: - apps resources: @@ -98,6 +104,12 @@ rules: - secrets verbs: - "*" +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get - apiGroups: - apps resources: From aacf6f619cdf21685ed0e64166c8f3c195cb671a Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Mon, 26 Nov 2018 12:24:27 -0500 Subject: [PATCH 05/12] Makefile,test,hack/tests,.travis.yml: adding helm e2e test --- .gitignore | 1 + .travis.yml | 6 + Makefile | 9 +- hack/tests/e2e-helm.sh | 120 +++++++++++++++++++ test/helm-operator/Dockerfile | 16 +++ test/helm-operator/README.md | 2 + test/helm-operator/bin/entrypoint | 12 ++ test/helm-operator/bin/user_setup | 13 ++ test/helm-operator/cmd/helm-operator/main.go | 108 +++++++++++++++++ 9 files changed, 285 insertions(+), 2 deletions(-) create mode 100755 hack/tests/e2e-helm.sh create mode 100644 test/helm-operator/Dockerfile create mode 100644 test/helm-operator/README.md create mode 100755 test/helm-operator/bin/entrypoint create mode 100755 test/helm-operator/bin/user_setup create mode 100644 test/helm-operator/cmd/helm-operator/main.go diff --git a/.gitignore b/.gitignore index 7a8c82e322..bff7e98d50 100644 --- a/.gitignore +++ b/.gitignore @@ -102,6 +102,7 @@ tags # Build artifacts build/* test/ansible-operator/ansible-operator +test/helm-operator/helm-operator # Test artifacts pkg/ansible/runner/testdata/valid.yaml diff --git a/.travis.yml b/.travis.yml index f5834d721d..cdb972abaf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,12 @@ jobs: name: Ansible on OpenShift services: - docker + - before_script: hack/ci/setup-openshift.sh + env: CLUSTER=openshift + script: make test/ci-helm + name: Helm on OpenShift + services: + - docker - name: Markdown Link Checker language: bash before_install: true diff --git a/Makefile b/Makefile index f025dec618..cc17fdb290 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,8 @@ test/ci-go: test/sanity test/unit test/subcommand test/e2e/go test/ci-ansible: test/e2e/ansible +test/ci-helm: test/e2e/helm + test/sanity: ./hack/tests/sanity-check.sh @@ -74,7 +76,7 @@ test/unit: test/subcommand: ./hack/tests/test-subcommand.sh -test/e2e: test/e2e/go test/e2e/ansible +test/e2e: test/e2e/go test/e2e/ansible test/e2e/helm test/e2e/go: ./hack/tests/e2e-go.sh @@ -82,4 +84,7 @@ test/e2e/go: test/e2e/ansible: ./hack/tests/e2e-ansible.sh -.PHONY: test test/sanity test/unit test/subcommand test/e2e test/e2e/go test/e2e/ansible test/ci-go test/ci-ansible +test/e2e/helm: + ./hack/tests/e2e-helm.sh + +.PHONY: test test/sanity test/unit test/subcommand test/e2e test/e2e/go test/e2e/ansible test/e2e/helm test/ci-go test/ci-ansible diff --git a/hack/tests/e2e-helm.sh b/hack/tests/e2e-helm.sh new file mode 100755 index 0000000000..62aad6a98b --- /dev/null +++ b/hack/tests/e2e-helm.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash + +#=================================================================== +# FUNCTION trap_add () +# +# Purpose: prepends a command to a trap +# +# - 1st arg: code to add +# - remaining args: names of traps to modify +# +# Example: trap_add 'echo "in trap DEBUG"' DEBUG +# +# See: http://stackoverflow.com/questions/3338030/multiple-bash-traps-for-the-same-signal +#=================================================================== +trap_add() { + trap_add_cmd=$1; shift || fatal "${FUNCNAME} usage error" + new_cmd= + for trap_add_name in "$@"; do + # Grab the currently defined trap commands for this trap + existing_cmd=`trap -p "${trap_add_name}" | awk -F"'" '{print $2}'` + + # Define default command + [ -z "${existing_cmd}" ] && existing_cmd="echo exiting @ `date`" + + # Generate the new command + new_cmd="${trap_add_cmd};${existing_cmd}" + + # Assign the test + trap "${new_cmd}" "${trap_add_name}" || \ + fatal "unable to add to trap ${trap_add_name}" + done +} + +DEST_IMAGE="quay.io/example/memcached-operator:v0.0.2" + +set -ex + +# switch to the "default" namespace if on openshift, to match the minikube test +if which oc 2>/dev/null; then oc project default; fi + +# build operator binary and base image +GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o test/helm-operator/helm-operator test/helm-operator/cmd/helm-operator/main.go +pushd test/helm-operator +docker build -t quay.io/water-hole/helm-operator . +popd + +# Make a test directory for Helm tests so we avoid using default GOPATH. +# Save test directory so we can delete it on exit. +HELM_TEST_DIR="$(mktemp -d)" +trap_add 'rm -rf $HELM_TEST_DIR' EXIT +cp -a test/helm-* "$HELM_TEST_DIR" +pushd "$HELM_TEST_DIR" + +# Helm tests should not run in a Golang environment. +unset GOPATH GOROOT + +# create and build the operator +operator-sdk new memcached-operator --api-version=helm.example.com/v1alpha1 --kind=Memcached --type=helm +pushd memcached-operator +rm -rf helm-charts/memcached +wget -qO- https://storage.googleapis.com/kubernetes-charts/memcached-2.3.1.tgz | tar -xzv -C helm-charts + +operator-sdk build "$DEST_IMAGE" +sed -i "s|REPLACE_IMAGE|$DEST_IMAGE|g" deploy/operator.yaml +sed -i 's|Always|Never|g' deploy/operator.yaml +sed -i 's|size: 3|replicaCount: 1|g' deploy/crds/helm_v1alpha1_memcached_cr.yaml +cat << EOF >> deploy/role.yaml +- apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - "*" +EOF + +DIR2="$(pwd)" +# deploy the operator +kubectl create -f deploy/service_account.yaml +trap_add 'kubectl delete -f ${DIR2}/deploy/service_account.yaml' EXIT +kubectl create -f deploy/role.yaml +trap_add 'kubectl delete -f ${DIR2}/deploy/role.yaml' EXIT +kubectl create -f deploy/role_binding.yaml +trap_add 'kubectl delete -f ${DIR2}/deploy/role_binding.yaml' EXIT +kubectl create -f deploy/crds/helm_v1alpha1_memcached_crd.yaml +trap_add 'kubectl delete -f ${DIR2}/deploy/crds/helm_v1alpha1_memcached_crd.yaml' EXIT +kubectl create -f deploy/operator.yaml +trap_add 'kubectl delete -f ${DIR2}/deploy/operator.yaml' EXIT + +# wait for operator pod to run +if ! timeout 1m kubectl rollout status deployment/memcached-operator; +then + kubectl logs deployment/memcached-operator + exit 1 +fi + +# create CR +kubectl create -f deploy/crds/helm_v1alpha1_memcached_cr.yaml +trap_add 'kubectl delete --ignore-not-found -f ${DIR2}/deploy/crds/helm_v1alpha1_memcached_cr.yaml' EXIT +if ! timeout 1m bash -c -- 'until kubectl get memcacheds.helm.example.com example-memcached -o jsonpath="{..status.release.info.status.code}" | grep 1; do sleep 1; done'; +then + kubectl logs deployment/memcached-operator + exit 1 +fi + +release_name=$(kubectl get memcacheds.helm.example.com example-memcached -o jsonpath="{..status.release.name}") +memcached_statefulset=$(kubectl get statefulset -l release=${release_name} -o jsonpath="{..metadata.name}") +kubectl patch statefulset ${memcached_statefulset} -p '{"spec":{"updateStrategy":{"type":"RollingUpdate"}}}' +if ! timeout 1m kubectl rollout status statefulset/${memcached_statefulset}; +then + kubectl describe pods -l release=${release_name} + kubectl describe statefulsets ${memcached_statefulset} + kubectl logs statefulset/${memcached_statefulset} + exit 1 +fi + +kubectl delete -f deploy/crds/helm_v1alpha1_memcached_cr.yaml --wait=true +kubectl logs deployment/memcached-operator | grep "Uninstalled release" | grep "${release_name}" + +popd +popd diff --git a/test/helm-operator/Dockerfile b/test/helm-operator/Dockerfile new file mode 100644 index 0000000000..9808aef590 --- /dev/null +++ b/test/helm-operator/Dockerfile @@ -0,0 +1,16 @@ +FROM alpine:3.6 + +ENV OPERATOR=/usr/local/bin/helm-operator \ + USER_UID=1001 \ + USER_NAME=helm-operator \ + HOME=/opt/helm + +# install operator binary +COPY helm-operator ${OPERATOR} + +COPY bin /usr/local/bin +RUN /usr/local/bin/user_setup + +ENTRYPOINT ["/usr/local/bin/entrypoint"] + +USER ${USER_UID} diff --git a/test/helm-operator/README.md b/test/helm-operator/README.md new file mode 100644 index 0000000000..a8b63d71ea --- /dev/null +++ b/test/helm-operator/README.md @@ -0,0 +1,2 @@ +This directory temporarily holds the artifacts that are required to build the +helm operator base image, until they find a more permanent home. diff --git a/test/helm-operator/bin/entrypoint b/test/helm-operator/bin/entrypoint new file mode 100755 index 0000000000..00d310c6ed --- /dev/null +++ b/test/helm-operator/bin/entrypoint @@ -0,0 +1,12 @@ +#!/bin/sh -e + +# This is documented here: +# https://docs.openshift.com/container-platform/3.10/creating_images/guidelines.html#openshift-specific-guidelines + +if ! whoami &>/dev/null; then + if [ -w /etc/passwd ]; then + echo "${USER_NAME:-helm}:x:$(id -u):$(id -g):${USER_NAME:-helm} user:${HOME}:/sbin/nologin" >> /etc/passwd + fi +fi + +exec "${OPERATOR:-/usr/local/bin/helm-operator}" diff --git a/test/helm-operator/bin/user_setup b/test/helm-operator/bin/user_setup new file mode 100755 index 0000000000..1e36064cbf --- /dev/null +++ b/test/helm-operator/bin/user_setup @@ -0,0 +1,13 @@ +#!/bin/sh +set -x + +# ensure $HOME exists and is accessible by group 0 (we don't know what the runtime UID will be) +mkdir -p ${HOME} +chown ${USER_UID}:0 ${HOME} +chmod ug+rwx ${HOME} + +# runtime user will need to be able to self-insert in /etc/passwd +chmod g+rw /etc/passwd + +# no need for this script to remain in the image after running +rm $0 diff --git a/test/helm-operator/cmd/helm-operator/main.go b/test/helm-operator/cmd/helm-operator/main.go new file mode 100644 index 0000000000..33beaccc64 --- /dev/null +++ b/test/helm-operator/cmd/helm-operator/main.go @@ -0,0 +1,108 @@ +// Copyright 2018 The Operator-SDK Authors +// +// 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" + "os" + "runtime" + "time" + + "github.com/operator-framework/operator-sdk/pkg/helm/client" + "github.com/operator-framework/operator-sdk/pkg/helm/controller" + "github.com/operator-framework/operator-sdk/pkg/helm/release" + "github.com/operator-framework/operator-sdk/pkg/k8sutil" + sdkVersion "github.com/operator-framework/operator-sdk/version" + + "k8s.io/helm/pkg/storage" + "k8s.io/helm/pkg/storage/driver" + "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/manager" + logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" + "sigs.k8s.io/controller-runtime/pkg/runtime/signals" +) + +var log = logf.Log.WithName("cmd") + +func printVersion() { + log.Info(fmt.Sprintf("Go Version: %s", runtime.Version())) + log.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) + log.Info(fmt.Sprintf("operator-sdk Version: %v", sdkVersion.Version)) +} + +func main() { + flag.Parse() + + logf.SetLogger(logf.ZapLogger(true)) + + printVersion() + + namespace, err := k8sutil.GetWatchNamespace() + if err != nil { + log.Error(err, "failed to get watch namespace") + os.Exit(1) + } + + cfg, err := config.GetConfig() + if err != nil { + log.Error(err, "") + os.Exit(1) + } + + mgr, err := manager.New(cfg, manager.Options{Namespace: namespace}) + if err != nil { + log.Error(err, "") + os.Exit(1) + } + + log.Info("Registering Components.") + + // Create Tiller's storage backend and kubernetes client + storageBackend := storage.Init(driver.NewMemory()) + tillerKubeClient, err := client.NewFromManager(mgr) + if err != nil { + log.Error(err, "") + os.Exit(1) + } + + factories, err := release.NewManagerFactoriesFromFile(storageBackend, tillerKubeClient, "/opt/helm/watches.yaml") + if err != nil { + log.Error(err, "") + os.Exit(1) + } + + for gvk, factory := range factories { + // Register the controller with the factory. + err := controller.Add(mgr, controller.WatchOptions{ + Namespace: namespace, + GVK: gvk, + ManagerFactory: factory, + ResyncPeriod: 5 * time.Second, + }) + if err != nil { + log.Error(err, "") + os.Exit(1) + } + } + + log.Info("Starting the Cmd.") + + // Start the Cmd + if err := mgr.Start(signals.SetupSignalHandler()); err != nil { + log.Error(err, "manager exited non-zero") + os.Exit(1) + } +} From c86c4d3f3f9eb66b721334bb98ab204dd0106e99 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Mon, 26 Nov 2018 14:45:49 -0500 Subject: [PATCH 06/12] pkg/scaffold: use RolesDir and HelmChartsDir everywhere --- pkg/scaffold/ansible/dockerfile.go | 4 +++- pkg/scaffold/ansible/galaxy_init.go | 7 ++++--- pkg/scaffold/ansible/watches.go | 4 +++- pkg/scaffold/helm/dockerfile.go | 5 ++++- pkg/scaffold/helm/watches.go | 6 ++++-- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/pkg/scaffold/ansible/dockerfile.go b/pkg/scaffold/ansible/dockerfile.go index dc49c141df..79b1a538a9 100644 --- a/pkg/scaffold/ansible/dockerfile.go +++ b/pkg/scaffold/ansible/dockerfile.go @@ -26,6 +26,7 @@ type Dockerfile struct { input.Input GeneratePlaybook bool + RolesDir string } // GetInput - gets the input @@ -33,13 +34,14 @@ func (d *Dockerfile) GetInput() (input.Input, error) { if d.Path == "" { d.Path = filepath.Join(scaffold.BuildDir, scaffold.DockerfileFile) } + d.RolesDir = RolesDir d.TemplateBody = dockerFileAnsibleTmpl return d.Input, nil } const dockerFileAnsibleTmpl = `FROM quay.io/water-hole/ansible-operator -COPY roles/ ${HOME}/roles/ +COPY {{.RolesDir}}/ ${HOME}/{{.RolesDir}}/ {{- if .GeneratePlaybook }} COPY playbook.yaml ${HOME}/playbook.yaml{{ end }} COPY watches.yaml ${HOME}/watches.yaml diff --git a/pkg/scaffold/ansible/galaxy_init.go b/pkg/scaffold/ansible/galaxy_init.go index 62de7612ba..792c60231c 100644 --- a/pkg/scaffold/ansible/galaxy_init.go +++ b/pkg/scaffold/ansible/galaxy_init.go @@ -15,7 +15,6 @@ package ansible import ( - "fmt" "io/ioutil" "path/filepath" @@ -32,6 +31,7 @@ type GalaxyInit struct { input.Input Resource scaffold.Resource Dir string + RolesDir string } // GetInput - get input @@ -46,7 +46,8 @@ func (g *GalaxyInit) GetInput() (input.Input, error) { if g.Path == "" { g.Path = filepath.Join(g.Dir, "galaxy_init.sh") } - g.TemplateBody = fmt.Sprintf(galaxyInitTmpl, RolesDir) + g.RolesDir = RolesDir + g.TemplateBody = galaxyInitTmpl g.IsExec = true return g.Input, nil } @@ -59,5 +60,5 @@ if ! which ansible-galaxy > /dev/null; then fi echo "Initializing role skeleton..." -ansible-galaxy init --init-path={{.Input.AbsProjectPath}}/%s/ {{.Resource.Kind}} +ansible-galaxy init --init-path={{.Input.AbsProjectPath}}/{{.RolesDir}}/ {{.Resource.Kind}} ` diff --git a/pkg/scaffold/ansible/watches.go b/pkg/scaffold/ansible/watches.go index 749bda165e..d541e39c13 100644 --- a/pkg/scaffold/ansible/watches.go +++ b/pkg/scaffold/ansible/watches.go @@ -27,6 +27,7 @@ type WatchesYAML struct { Resource scaffold.Resource GeneratePlaybook bool + RolesDir string } // GetInput - gets the input @@ -34,6 +35,7 @@ func (s *WatchesYAML) GetInput() (input.Input, error) { if s.Path == "" { s.Path = WatchesYamlFile } + s.RolesDir = RolesDir s.TemplateBody = watchesYAMLTmpl return s.Input, nil } @@ -42,5 +44,5 @@ const watchesYAMLTmpl = `--- - version: {{.Resource.Version}} group: {{.Resource.FullGroup}} kind: {{.Resource.Kind}} -{{ if .GeneratePlaybook }} playbook: /opt/ansible/playbook.yaml{{ else }} role: /opt/ansible/roles/{{.Resource.Kind}}{{ end }} +{{ if .GeneratePlaybook }} playbook: /opt/ansible/playbook.yaml{{ else }} role: /opt/ansible/{{.RolesDir}}/{{.Resource.Kind}}{{ end }} ` diff --git a/pkg/scaffold/helm/dockerfile.go b/pkg/scaffold/helm/dockerfile.go index 40b9541ffd..578e4e99cf 100644 --- a/pkg/scaffold/helm/dockerfile.go +++ b/pkg/scaffold/helm/dockerfile.go @@ -24,6 +24,8 @@ import ( // Dockerfile specifies the Helm Dockerfile scaffold type Dockerfile struct { input.Input + + HelmChartsDir string } // GetInput gets the scaffold execution input @@ -31,12 +33,13 @@ func (d *Dockerfile) GetInput() (input.Input, error) { if d.Path == "" { d.Path = filepath.Join(scaffold.BuildDir, scaffold.DockerfileFile) } + d.HelmChartsDir = HelmChartsDir d.TemplateBody = dockerFileHelmTmpl return d.Input, nil } const dockerFileHelmTmpl = `FROM quay.io/water-hole/helm-operator -COPY helm-charts/ ${HOME}/helm-charts/ +COPY {{.HelmChartsDir}}/ ${HOME}/{{.HelmChartsDir}}/ COPY watches.yaml ${HOME}/watches.yaml ` diff --git a/pkg/scaffold/helm/watches.go b/pkg/scaffold/helm/watches.go index 8b6be88f1a..99ca1ba55f 100644 --- a/pkg/scaffold/helm/watches.go +++ b/pkg/scaffold/helm/watches.go @@ -25,7 +25,8 @@ const WatchesYamlFile = "watches.yaml" type WatchesYAML struct { input.Input - Resource *scaffold.Resource + Resource *scaffold.Resource + HelmChartsDir string } // GetInput gets the scaffold execution input @@ -33,6 +34,7 @@ func (s *WatchesYAML) GetInput() (input.Input, error) { if s.Path == "" { s.Path = WatchesYamlFile } + s.HelmChartsDir = HelmChartsDir s.TemplateBody = watchesYAMLTmpl return s.Input, nil } @@ -41,5 +43,5 @@ const watchesYAMLTmpl = `--- - version: {{.Resource.Version}} group: {{.Resource.FullGroup}} kind: {{.Resource.Kind}} - chart: /opt/helm/helm-charts/{{.Resource.LowerKind}} + chart: /opt/helm/{{.HelmChartsDir}}/{{.Resource.LowerKind}} ` From f522bb850578c79589ebc28f7a498349311094cf Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Tue, 27 Nov 2018 13:28:57 -0500 Subject: [PATCH 07/12] commands/operator-sdk/cmd/new.go: adding operator type to failure logs --- commands/operator-sdk/cmd/new.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/commands/operator-sdk/cmd/new.go b/commands/operator-sdk/cmd/new.go index 66c4f100e9..7c5a8f96fb 100644 --- a/commands/operator-sdk/cmd/new.go +++ b/commands/operator-sdk/cmd/new.go @@ -152,7 +152,7 @@ func doScaffold() { &scaffold.GopkgToml{}, ) if err != nil { - log.Fatalf("new scaffold failed: (%v)", err) + log.Fatalf("new go scaffold failed: (%v)", err) } } @@ -205,7 +205,7 @@ func doAnsibleScaffold() { }, ) if err != nil { - log.Fatalf("new scaffold failed: (%v)", err) + log.Fatalf("new ansible scaffold failed: (%v)", err) } // Decide on playbook. @@ -218,7 +218,7 @@ func doAnsibleScaffold() { }, ) if err != nil { - log.Fatalf("new playbook scaffold failed: (%v)", err) + log.Fatalf("new ansible playbook scaffold failed: (%v)", err) } } @@ -279,11 +279,11 @@ func doHelmScaffold() { }, ) if err != nil { - log.Fatalf("new scaffold failed: (%v)", err) + log.Fatalf("new helm scaffold failed: (%v)", err) } if err := helm.CreateChartForResource(resource, cfg.AbsProjectPath); err != nil { - log.Fatalf("failed to create initial chart for resource (%v, %v): (%v)", resource.APIVersion, resource.Kind, err) + log.Fatalf("failed to create initial helm chart for resource (%v, %v): (%v)", resource.APIVersion, resource.Kind, err) } if err := scaffold.UpdateRoleForResource(resource, cfg.AbsProjectPath); err != nil { From ee8b12ddc7b709aad94ea8c9dcb366b248aba545 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Tue, 27 Nov 2018 13:32:54 -0500 Subject: [PATCH 08/12] hack/tests/e2e-helm.sh: use default nginx helm chart instead of downloading memcached chart --- hack/tests/e2e-helm.sh | 57 ++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/hack/tests/e2e-helm.sh b/hack/tests/e2e-helm.sh index 62aad6a98b..b3327fd6bc 100755 --- a/hack/tests/e2e-helm.sh +++ b/hack/tests/e2e-helm.sh @@ -31,11 +31,11 @@ trap_add() { done } -DEST_IMAGE="quay.io/example/memcached-operator:v0.0.2" +DEST_IMAGE="quay.io/example/nginx-operator:v0.0.2" set -ex -# switch to the "default" namespace if on openshift, to match the minikube test +# switch to the "default" namespace if on openshift if which oc 2>/dev/null; then oc project default; fi # build operator binary and base image @@ -55,23 +55,13 @@ pushd "$HELM_TEST_DIR" unset GOPATH GOROOT # create and build the operator -operator-sdk new memcached-operator --api-version=helm.example.com/v1alpha1 --kind=Memcached --type=helm -pushd memcached-operator -rm -rf helm-charts/memcached -wget -qO- https://storage.googleapis.com/kubernetes-charts/memcached-2.3.1.tgz | tar -xzv -C helm-charts +operator-sdk new nginx-operator --api-version=helm.example.com/v1alpha1 --kind=Nginx --type=helm +pushd nginx-operator operator-sdk build "$DEST_IMAGE" sed -i "s|REPLACE_IMAGE|$DEST_IMAGE|g" deploy/operator.yaml sed -i 's|Always|Never|g' deploy/operator.yaml -sed -i 's|size: 3|replicaCount: 1|g' deploy/crds/helm_v1alpha1_memcached_cr.yaml -cat << EOF >> deploy/role.yaml -- apiGroups: - - policy - resources: - - poddisruptionbudgets - verbs: - - "*" -EOF +sed -i 's|size: 3|replicaCount: 1|g' deploy/crds/helm_v1alpha1_nginx_cr.yaml DIR2="$(pwd)" # deploy the operator @@ -81,40 +71,43 @@ kubectl create -f deploy/role.yaml trap_add 'kubectl delete -f ${DIR2}/deploy/role.yaml' EXIT kubectl create -f deploy/role_binding.yaml trap_add 'kubectl delete -f ${DIR2}/deploy/role_binding.yaml' EXIT -kubectl create -f deploy/crds/helm_v1alpha1_memcached_crd.yaml -trap_add 'kubectl delete -f ${DIR2}/deploy/crds/helm_v1alpha1_memcached_crd.yaml' EXIT +kubectl create -f deploy/crds/helm_v1alpha1_nginx_crd.yaml +trap_add 'kubectl delete -f ${DIR2}/deploy/crds/helm_v1alpha1_nginx_crd.yaml' EXIT kubectl create -f deploy/operator.yaml trap_add 'kubectl delete -f ${DIR2}/deploy/operator.yaml' EXIT # wait for operator pod to run -if ! timeout 1m kubectl rollout status deployment/memcached-operator; +if ! timeout 1m kubectl rollout status deployment/nginx-operator; then - kubectl logs deployment/memcached-operator + kubectl logs deployment/nginx-operator exit 1 fi # create CR -kubectl create -f deploy/crds/helm_v1alpha1_memcached_cr.yaml -trap_add 'kubectl delete --ignore-not-found -f ${DIR2}/deploy/crds/helm_v1alpha1_memcached_cr.yaml' EXIT -if ! timeout 1m bash -c -- 'until kubectl get memcacheds.helm.example.com example-memcached -o jsonpath="{..status.release.info.status.code}" | grep 1; do sleep 1; done'; +kubectl create -f deploy/crds/helm_v1alpha1_nginx_cr.yaml +trap_add 'kubectl delete --ignore-not-found -f ${DIR2}/deploy/crds/helm_v1alpha1_nginx_cr.yaml' EXIT +if ! timeout 1m bash -c -- 'until kubectl get nginxes.helm.example.com example-nginx -o jsonpath="{..status.release.info.status.code}" | grep 1; do sleep 1; done'; then - kubectl logs deployment/memcached-operator + kubectl logs deployment/nginx-operator exit 1 fi -release_name=$(kubectl get memcacheds.helm.example.com example-memcached -o jsonpath="{..status.release.name}") -memcached_statefulset=$(kubectl get statefulset -l release=${release_name} -o jsonpath="{..metadata.name}") -kubectl patch statefulset ${memcached_statefulset} -p '{"spec":{"updateStrategy":{"type":"RollingUpdate"}}}' -if ! timeout 1m kubectl rollout status statefulset/${memcached_statefulset}; +release_name=$(kubectl get nginxes.helm.example.com example-nginx -o jsonpath="{..status.release.name}") +nginx_deployment=$(kubectl get deployment -l "app.kubernetes.io/instance=${release_name}" -o jsonpath="{..metadata.name}") + +if ! timeout 1m kubectl rollout status deployment/${nginx_deployment}; then - kubectl describe pods -l release=${release_name} - kubectl describe statefulsets ${memcached_statefulset} - kubectl logs statefulset/${memcached_statefulset} + kubectl describe pods -l "app.kubernetes.io/instance=${release_name}" + kubectl describe deployments ${nginx_deployment} + kubectl logs deployment/${nginx_deployment} exit 1 fi -kubectl delete -f deploy/crds/helm_v1alpha1_memcached_cr.yaml --wait=true -kubectl logs deployment/memcached-operator | grep "Uninstalled release" | grep "${release_name}" +nginx_service=$(kubectl get service -l "app.kubernetes.io/instance=${release_name}" -o jsonpath="{..metadata.name}") +kubectl get service ${nginx_service} + +kubectl delete -f deploy/crds/helm_v1alpha1_nginx_cr.yaml --wait=true +kubectl logs deployment/nginx-operator | grep "Uninstalled release" | grep "${release_name}" popd popd From 8e6a2b12e7a476173f196dda0fb89fab34514662 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Tue, 27 Nov 2018 16:14:07 -0500 Subject: [PATCH 09/12] hack/tests/e2e-helm.sh: allow nginx image to run as root --- hack/tests/e2e-helm.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/hack/tests/e2e-helm.sh b/hack/tests/e2e-helm.sh index b3327fd6bc..7a61c3546f 100755 --- a/hack/tests/e2e-helm.sh +++ b/hack/tests/e2e-helm.sh @@ -35,8 +35,14 @@ DEST_IMAGE="quay.io/example/nginx-operator:v0.0.2" set -ex -# switch to the "default" namespace if on openshift -if which oc 2>/dev/null; then oc project default; fi +# if on openshift switch to the "default" namespace +# and allow containers to run as root (necessary for +# default nginx image) +if which oc 2>/dev/null; +then + oc project default + oc adm policy add-scc-to-user anyuid -z default +fi # build operator binary and base image GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o test/helm-operator/helm-operator test/helm-operator/cmd/helm-operator/main.go From 5191db813e65e7adf1b727b61250e54738ede231 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Wed, 28 Nov 2018 11:04:18 -0500 Subject: [PATCH 10/12] Gopkg.*: better constraints and overrides --- Gopkg.lock | 22 +++++++++++----------- Gopkg.toml | 5 ++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index d97f8899d7..30759697fd 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -107,18 +107,18 @@ [[projects]] branch = "master" - digest = "1:a0937269866f68424e50a4d44e7dbaa0237891864c489ec2d44815786e810aa6" + digest = "1:7942de144228c1b62eaba44ec5153a29d25cd016e8fb23cb9533c77f6111b397" name = "github.com/docker/distribution" packages = [ "digestset", "reference", ] pruneopts = "" - revision = "93e082742a009850ac46962150b2f652a822c5ff" + revision = "aa985ba8897a426e5db31b36cd8cca83cf85cead" [[projects]] branch = "master" - digest = "1:be1362b6824834f01e7b338e3e40d0ed0eb494d2e21f6270e406c2df9c1157d4" + digest = "1:c1c4dbf9ef82408677dc73925b395f6494d0d25815c064582f3a0ac4838b75a9" name = "github.com/docker/docker" packages = [ "api/types", @@ -137,7 +137,7 @@ "pkg/term/windows", ] pruneopts = "" - revision = "0b7cb16dde4a20d024c7be59801d63bcfd18611b" + revision = "852542b3976754f62232f1fafca7fd35deeb1da3" [[projects]] digest = "1:ebe593d8b65a2947b78b6e164a2dac1a230b977a700b694da3a398b03b7afb04" @@ -618,12 +618,12 @@ revision = "185b4288413d2a0dd0806f78c90dde719829e5ae" [[projects]] - branch = "master" - digest = "1:7e0b48d8bdbf9278ce4a68510c5bf3d074e4d7f8ba6d18512b7c6eb50cc5c6e6" + digest = "1:2761e287c811d0948d47d0252b82281eca3801eb3c9d5f9530956643d5b9f430" name = "github.com/russross/blackfriday" packages = ["."] pruneopts = "" - revision = "abafa45cd843f4f4935c29a2e8f3d73a01820fc3" + revision = "05f3235734ad95d0016f6a23902f06461fcf567a" + version = "v1.5.2" [[projects]] digest = "1:3962f553b77bf6c03fc07cd687a22dd3b00fe11aa14d31194f5505f5bb65cdc8" @@ -818,7 +818,7 @@ name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] pruneopts = "" - revision = "b5d43981345bdb2c233eb4bf3277847b48c6fdc6" + revision = "31ac5d88444a9e7ad18077db9a165d793ad06a2e" [[projects]] digest = "1:1293087271e314cfa2b3decededba2ecba0ff327e7b7809e00f73f616449191c" @@ -1150,7 +1150,7 @@ revision = "e494cc58111187acad93e64529228a2fc0153e39" [[projects]] - digest = "1:89eef4670d767c8f3e520d71509f40e77698b4bc898305b6ee0220300840b788" + digest = "1:13614139aef760b502fe057a8d34e9c4257d1581fb85498b0053267596e0514c" name = "k8s.io/kubernetes" packages = [ "pkg/api/events", @@ -1298,8 +1298,8 @@ "pkg/version", ] pruneopts = "" - revision = "bf9a868e8ea3d3a8fa53cbb22f566771b3f8068b" - version = "v1.11.4" + revision = "bb9ffb1654d4a729bb4cec18ff088eacc153c239" + version = "v1.11.2" [[projects]] branch = "master" diff --git a/Gopkg.toml b/Gopkg.toml index efccc37e74..9b760ff3da 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -28,7 +28,7 @@ [[override]] name = "k8s.io/kubernetes" - version = "1.11.2" + version = "=1.11.2" [[constraint]] name = "k8s.io/helm" @@ -36,7 +36,7 @@ [[override]] name = "github.com/russross/blackfriday" - branch = "master" + version = "1.5.2" [[override]] name = "github.com/docker/distribution" @@ -53,4 +53,3 @@ [[constraint]] name = "sigs.k8s.io/controller-runtime" version = "v0.1.4" - From 2da5294d66a5edcc85c0436e3fad74feace4a93e Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Wed, 28 Nov 2018 14:37:37 -0500 Subject: [PATCH 11/12] Gopkg.*: use k8s.io/helm's locked dep revisions --- Gopkg.lock | 24 ++++++++++++++---------- Gopkg.toml | 30 +++++++++++++++++------------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 30759697fd..900bb72562 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -106,19 +106,17 @@ version = "v1.1.1" [[projects]] - branch = "master" - digest = "1:7942de144228c1b62eaba44ec5153a29d25cd016e8fb23cb9533c77f6111b397" + digest = "1:522eff2a1f014a64fb403db60fc0110653e4dc5b59779894d208e697b0708ddc" name = "github.com/docker/distribution" packages = [ "digestset", "reference", ] pruneopts = "" - revision = "aa985ba8897a426e5db31b36cd8cca83cf85cead" + revision = "edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c" [[projects]] - branch = "master" - digest = "1:c1c4dbf9ef82408677dc73925b395f6494d0d25815c064582f3a0ac4838b75a9" + digest = "1:6bbfd267f02fc9371e3df1f249dc69e0ff71e91f28e9dfe8b96376c4abe28bed" name = "github.com/docker/docker" packages = [ "api/types", @@ -132,12 +130,11 @@ "api/types/swarm", "api/types/swarm/runtime", "api/types/versions", - "errdefs", "pkg/term", "pkg/term/windows", ] pruneopts = "" - revision = "852542b3976754f62232f1fafca7fd35deeb1da3" + revision = "4f3616fb1c112e206b88cb7a9922bf49067a7756" [[projects]] digest = "1:ebe593d8b65a2947b78b6e164a2dac1a230b977a700b694da3a398b03b7afb04" @@ -618,12 +615,11 @@ revision = "185b4288413d2a0dd0806f78c90dde719829e5ae" [[projects]] - digest = "1:2761e287c811d0948d47d0252b82281eca3801eb3c9d5f9530956643d5b9f430" + digest = "1:67d5130351f71fe24139c6168dd2f8ab5fde543a1230754403d84173bb25a097" name = "github.com/russross/blackfriday" packages = ["."] pruneopts = "" - revision = "05f3235734ad95d0016f6a23902f06461fcf567a" - version = "v1.5.2" + revision = "300106c228d52c8941d4b3de6054a6062a86dda3" [[projects]] digest = "1:3962f553b77bf6c03fc07cd687a22dd3b00fe11aa14d31194f5505f5bb65cdc8" @@ -633,6 +629,14 @@ revision = "1744e2970ca51c86172c8190fadad617561ed6e7" version = "v1.0.0" +[[projects]] + branch = "master" + digest = "1:606fa779c7a80ab6a9ec5836cd12759f86cd6e1d82d5d60cf7ec5133e78c6cf8" + name = "github.com/shurcooL/sanitized_anchor_name" + packages = ["."] + pruneopts = "" + revision = "86672fcb3f950f35f2e675df2240550f2a50762f" + [[projects]] digest = "1:5f48b818f16848d05cf74f4cbdd0cbe9e0dcddb3c459b4c510c6e2c8e1b4dff1" name = "github.com/sirupsen/logrus" diff --git a/Gopkg.toml b/Gopkg.toml index 9b760ff3da..b75808ffe7 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -26,30 +26,34 @@ name = "k8s.io/client-go" version = "kubernetes-1.11.2" -[[override]] - name = "k8s.io/kubernetes" - version = "=1.11.2" +[[constraint]] + name = "github.com/sergi/go-diff" + version = "1.0.0" + +[[constraint]] + name = "sigs.k8s.io/controller-runtime" + version = "v0.1.4" [[constraint]] name = "k8s.io/helm" version = "2.11.0" +[[override]] + name = "k8s.io/kubernetes" + version = "=1.11.2" + +# We need overrides for the following imports because dep can't resolve them +# correctly. The easiest way to get this right is to use the versions that +# k8s.io/helm uses. See https://github.com/helm/helm/blob/v2.11.0/glide.lock [[override]] name = "github.com/russross/blackfriday" - version = "1.5.2" + revision = "300106c228d52c8941d4b3de6054a6062a86dda3" [[override]] name = "github.com/docker/distribution" - branch = "master" + revision = "edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c" [[override]] name = "github.com/docker/docker" - branch = "master" - -[[constraint]] - name = "github.com/sergi/go-diff" - version = "1.0.0" + revision = "4f3616fb1c112e206b88cb7a9922bf49067a7756" -[[constraint]] - name = "sigs.k8s.io/controller-runtime" - version = "v0.1.4" From 47c846274cbea5f1d3c712534ec74df2de997178 Mon Sep 17 00:00:00 2001 From: Joe Lanford Date: Mon, 3 Dec 2018 10:17:23 -0500 Subject: [PATCH 12/12] commands/.../up/local.go: checking error on os.Setenv --- commands/operator-sdk/cmd/up/local.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/commands/operator-sdk/cmd/up/local.go b/commands/operator-sdk/cmd/up/local.go index 54663f7ca5..20d9a6a4ba 100644 --- a/commands/operator-sdk/cmd/up/local.go +++ b/commands/operator-sdk/cmd/up/local.go @@ -147,7 +147,9 @@ func upLocal() { func upLocalAnsible() { // Set the kubeconfig that the manager will be able to grab - os.Setenv(k8sutil.KubeConfigEnvVar, kubeConfig) + if err := os.Setenv(k8sutil.KubeConfigEnvVar, kubeConfig); err != nil { + log.Fatalf("failed to set %s environment variable: (%v)", k8sutil.KubeConfigEnvVar, err) + } logf.SetLogger(logf.ZapLogger(false)) @@ -183,7 +185,9 @@ func upLocalAnsible() { func upLocalHelm() { // Set the kubeconfig that the manager will be able to grab - os.Setenv(k8sutil.KubeConfigEnvVar, kubeConfig) + if err := os.Setenv(k8sutil.KubeConfigEnvVar, kubeConfig); err != nil { + log.Fatalf("failed to set %s environment variable: (%v)", k8sutil.KubeConfigEnvVar, err) + } logf.SetLogger(logf.ZapLogger(false))