239
vendor/github.com/operator-framework/helm-operator-plugins/pkg/annotation/annotation.go
generated
vendored
Normal file
239
vendor/github.com/operator-framework/helm-operator-plugins/pkg/annotation/annotation.go
generated
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
Copyright 2020 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 annotation allows to set custom install, upgrade or uninstall options on custom resource objects with annotations.
|
||||
// To create custom annotations implement the Install, Upgrade or Uninstall interface.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// To disable hooks based on annotations the InstallDisableHooks is passed to the reconciler as an option.
|
||||
//
|
||||
// r, err := reconciler.New(
|
||||
// reconciler.WithChart(*w.Chart),
|
||||
// reconciler.WithGroupVersionKind(w.GroupVersionKind),
|
||||
// reconciler.WithInstallAnnotations(annotation.InstallDisableHook{}),
|
||||
// )
|
||||
//
|
||||
// If the reconciler detects an annotation named "helm.sdk.operatorframework.io/install-disable-hooks"
|
||||
// on the watched custom resource it sets the install.DisableHooks option to the annotations value. For more information
|
||||
// take a look at the InstallDisableHooks.InstallOption method.
|
||||
//
|
||||
// kind: OperatorHelmKind
|
||||
// apiVersion: test.example.com/v1
|
||||
// metadata:
|
||||
// name: nginx-sample
|
||||
// annotations:
|
||||
// "helm.sdk.operatorframework.io/install-disable-hooks": true
|
||||
//
|
||||
package annotation
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
|
||||
helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultInstallAnnotations = []Install{InstallDescription{}, InstallDisableHooks{}}
|
||||
DefaultUpgradeAnnotations = []Upgrade{UpgradeDescription{}, UpgradeDisableHooks{}, UpgradeForce{}}
|
||||
DefaultUninstallAnnotations = []Uninstall{UninstallDescription{}, UninstallDisableHooks{}}
|
||||
)
|
||||
|
||||
// Install configures an install annotation.
|
||||
type Install interface {
|
||||
Name() string
|
||||
InstallOption(string) helmclient.InstallOption
|
||||
}
|
||||
|
||||
// Upgrade configures an upgrade annotation.
|
||||
type Upgrade interface {
|
||||
Name() string
|
||||
UpgradeOption(string) helmclient.UpgradeOption
|
||||
}
|
||||
|
||||
// Uninstall configures an install annotation.
|
||||
type Uninstall interface {
|
||||
Name() string
|
||||
UninstallOption(string) helmclient.UninstallOption
|
||||
}
|
||||
|
||||
const (
|
||||
defaultDomain = "helm.sdk.operatorframework.io"
|
||||
defaultInstallDisableHooksName = defaultDomain + "/install-disable-hooks"
|
||||
defaultUpgradeDisableHooksName = defaultDomain + "/upgrade-disable-hooks"
|
||||
defaultUninstallDisableHooksName = defaultDomain + "/uninstall-disable-hooks"
|
||||
|
||||
defaultUpgradeForceName = defaultDomain + "/upgrade-force"
|
||||
|
||||
defaultInstallDescriptionName = defaultDomain + "/install-description"
|
||||
defaultUpgradeDescriptionName = defaultDomain + "/upgrade-description"
|
||||
defaultUninstallDescriptionName = defaultDomain + "/uninstall-description"
|
||||
)
|
||||
|
||||
type InstallDisableHooks struct {
|
||||
CustomName string
|
||||
}
|
||||
|
||||
var _ Install = &InstallDisableHooks{}
|
||||
|
||||
func (i InstallDisableHooks) Name() string {
|
||||
if i.CustomName != "" {
|
||||
return i.CustomName
|
||||
}
|
||||
return defaultInstallDisableHooksName
|
||||
}
|
||||
|
||||
func (i InstallDisableHooks) InstallOption(val string) helmclient.InstallOption {
|
||||
disableHooks := false
|
||||
if v, err := strconv.ParseBool(val); err == nil {
|
||||
disableHooks = v
|
||||
}
|
||||
return func(install *action.Install) error {
|
||||
install.DisableHooks = disableHooks
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type UpgradeDisableHooks struct {
|
||||
CustomName string
|
||||
}
|
||||
|
||||
var _ Upgrade = &UpgradeDisableHooks{}
|
||||
|
||||
func (u UpgradeDisableHooks) Name() string {
|
||||
if u.CustomName != "" {
|
||||
return u.CustomName
|
||||
}
|
||||
return defaultUpgradeDisableHooksName
|
||||
}
|
||||
|
||||
func (u UpgradeDisableHooks) UpgradeOption(val string) helmclient.UpgradeOption {
|
||||
disableHooks := false
|
||||
if v, err := strconv.ParseBool(val); err == nil {
|
||||
disableHooks = v
|
||||
}
|
||||
return func(upgrade *action.Upgrade) error {
|
||||
upgrade.DisableHooks = disableHooks
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type UpgradeForce struct {
|
||||
CustomName string
|
||||
}
|
||||
|
||||
var _ Upgrade = &UpgradeForce{}
|
||||
|
||||
func (u UpgradeForce) Name() string {
|
||||
if u.CustomName != "" {
|
||||
return u.CustomName
|
||||
}
|
||||
return defaultUpgradeForceName
|
||||
}
|
||||
|
||||
func (u UpgradeForce) UpgradeOption(val string) helmclient.UpgradeOption {
|
||||
force := false
|
||||
if v, err := strconv.ParseBool(val); err == nil {
|
||||
force = v
|
||||
}
|
||||
return func(upgrade *action.Upgrade) error {
|
||||
upgrade.Force = force
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type UninstallDisableHooks struct {
|
||||
CustomName string
|
||||
}
|
||||
|
||||
var _ Uninstall = &UninstallDisableHooks{}
|
||||
|
||||
func (u UninstallDisableHooks) Name() string {
|
||||
if u.CustomName != "" {
|
||||
return u.CustomName
|
||||
}
|
||||
return defaultUninstallDisableHooksName
|
||||
}
|
||||
|
||||
func (u UninstallDisableHooks) UninstallOption(val string) helmclient.UninstallOption {
|
||||
disableHooks := false
|
||||
if v, err := strconv.ParseBool(val); err == nil {
|
||||
disableHooks = v
|
||||
}
|
||||
return func(uninstall *action.Uninstall) error {
|
||||
uninstall.DisableHooks = disableHooks
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var _ Install = &InstallDescription{}
|
||||
|
||||
type InstallDescription struct {
|
||||
CustomName string
|
||||
}
|
||||
|
||||
func (i InstallDescription) Name() string {
|
||||
if i.CustomName != "" {
|
||||
return i.CustomName
|
||||
}
|
||||
return defaultInstallDescriptionName
|
||||
}
|
||||
func (i InstallDescription) InstallOption(v string) helmclient.InstallOption {
|
||||
return func(i *action.Install) error {
|
||||
i.Description = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var _ Upgrade = &UpgradeDescription{}
|
||||
|
||||
type UpgradeDescription struct {
|
||||
CustomName string
|
||||
}
|
||||
|
||||
func (u UpgradeDescription) Name() string {
|
||||
if u.CustomName != "" {
|
||||
return u.CustomName
|
||||
}
|
||||
return defaultUpgradeDescriptionName
|
||||
}
|
||||
func (u UpgradeDescription) UpgradeOption(v string) helmclient.UpgradeOption {
|
||||
return func(upgrade *action.Upgrade) error {
|
||||
upgrade.Description = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var _ Uninstall = &UninstallDescription{}
|
||||
|
||||
type UninstallDescription struct {
|
||||
CustomName string
|
||||
}
|
||||
|
||||
func (u UninstallDescription) Name() string {
|
||||
if u.CustomName != "" {
|
||||
return u.CustomName
|
||||
}
|
||||
return defaultUninstallDescriptionName
|
||||
}
|
||||
func (u UninstallDescription) UninstallOption(v string) helmclient.UninstallOption {
|
||||
return func(uninstall *action.Uninstall) error {
|
||||
uninstall.Description = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
351
vendor/github.com/operator-framework/helm-operator-plugins/pkg/client/actionclient.go
generated
vendored
Normal file
351
vendor/github.com/operator-framework/helm-operator-plugins/pkg/client/actionclient.go
generated
vendored
Normal file
@@ -0,0 +1,351 @@
|
||||
/*
|
||||
Copyright 2020 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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
sdkhandler "github.com/operator-framework/operator-lib/handler"
|
||||
"gomodules.xyz/jsonpatch/v2"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/kube"
|
||||
helmkube "helm.sh/helm/v3/pkg/kube"
|
||||
"helm.sh/helm/v3/pkg/postrender"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
"helm.sh/helm/v3/pkg/storage/driver"
|
||||
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
apitypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/internal/sdk/controllerutil"
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/manifestutil"
|
||||
)
|
||||
|
||||
type ActionClientGetter interface {
|
||||
ActionClientFor(obj client.Object) (ActionInterface, error)
|
||||
}
|
||||
|
||||
type ActionClientGetterFunc func(obj client.Object) (ActionInterface, error)
|
||||
|
||||
func (acgf ActionClientGetterFunc) ActionClientFor(obj client.Object) (ActionInterface, error) {
|
||||
return acgf(obj)
|
||||
}
|
||||
|
||||
type ActionInterface interface {
|
||||
Get(name string, opts ...GetOption) (*release.Release, error)
|
||||
Install(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...InstallOption) (*release.Release, error)
|
||||
Upgrade(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...UpgradeOption) (*release.Release, error)
|
||||
Uninstall(name string, opts ...UninstallOption) (*release.UninstallReleaseResponse, error)
|
||||
Reconcile(rel *release.Release) error
|
||||
}
|
||||
|
||||
type GetOption func(*action.Get) error
|
||||
type InstallOption func(*action.Install) error
|
||||
type UpgradeOption func(*action.Upgrade) error
|
||||
type UninstallOption func(*action.Uninstall) error
|
||||
|
||||
func NewActionClientGetter(acg ActionConfigGetter) ActionClientGetter {
|
||||
return &actionClientGetter{acg}
|
||||
}
|
||||
|
||||
type actionClientGetter struct {
|
||||
acg ActionConfigGetter
|
||||
}
|
||||
|
||||
var _ ActionClientGetter = &actionClientGetter{}
|
||||
|
||||
func (hcg *actionClientGetter) ActionClientFor(obj client.Object) (ActionInterface, error) {
|
||||
actionConfig, err := hcg.acg.ActionConfigFor(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rm, err := actionConfig.RESTClientGetter.ToRESTMapper()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
postRenderer := createPostRenderer(rm, actionConfig.KubeClient, obj)
|
||||
return &actionClient{actionConfig, postRenderer}, nil
|
||||
}
|
||||
|
||||
type actionClient struct {
|
||||
conf *action.Configuration
|
||||
postRenderer postrender.PostRenderer
|
||||
}
|
||||
|
||||
var _ ActionInterface = &actionClient{}
|
||||
|
||||
func (c *actionClient) Get(name string, opts ...GetOption) (*release.Release, error) {
|
||||
get := action.NewGet(c.conf)
|
||||
for _, o := range opts {
|
||||
if err := o(get); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return get.Run(name)
|
||||
}
|
||||
|
||||
func (c *actionClient) Install(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...InstallOption) (*release.Release, error) {
|
||||
install := action.NewInstall(c.conf)
|
||||
install.PostRenderer = c.postRenderer
|
||||
for _, o := range opts {
|
||||
if err := o(install); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
install.ReleaseName = name
|
||||
install.Namespace = namespace
|
||||
c.conf.Log("Starting install")
|
||||
rel, err := install.Run(chrt, vals)
|
||||
if err != nil {
|
||||
c.conf.Log("Install failed")
|
||||
if rel != nil {
|
||||
// Uninstall the failed release installation so that we can retry
|
||||
// the installation again during the next reconciliation. In many
|
||||
// cases, the issue is unresolvable without a change to the CR, but
|
||||
// controller-runtime will backoff on retries after failed attempts.
|
||||
//
|
||||
// In certain cases, Install will return a partial release in
|
||||
// the response even when it doesn't record the release in its release
|
||||
// store (e.g. when there is an error rendering the release manifest).
|
||||
// In that case the rollback will fail with a not found error because
|
||||
// there was nothing to rollback.
|
||||
//
|
||||
// Only return an error about a rollback failure if the failure was
|
||||
// caused by something other than the release not being found.
|
||||
_, uninstallErr := c.Uninstall(name)
|
||||
if !errors.Is(uninstallErr, driver.ErrReleaseNotFound) {
|
||||
return nil, fmt.Errorf("uninstall failed: %v: original install error: %w", uninstallErr, err)
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
func (c *actionClient) Upgrade(name, namespace string, chrt *chart.Chart, vals map[string]interface{}, opts ...UpgradeOption) (*release.Release, error) {
|
||||
upgrade := action.NewUpgrade(c.conf)
|
||||
upgrade.PostRenderer = c.postRenderer
|
||||
for _, o := range opts {
|
||||
if err := o(upgrade); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
upgrade.Namespace = namespace
|
||||
rel, err := upgrade.Run(name, chrt, vals)
|
||||
if err != nil {
|
||||
if rel != nil {
|
||||
rollback := action.NewRollback(c.conf)
|
||||
rollback.Force = true
|
||||
|
||||
// As of Helm 2.13, if Upgrade returns a non-nil release, that
|
||||
// means the release was also recorded in the release store.
|
||||
// Therefore, we should perform the rollback when we have a non-nil
|
||||
// release. Any rollback error here would be unexpected, so always
|
||||
// log both the update and rollback errors.
|
||||
rollbackErr := rollback.Run(name)
|
||||
if rollbackErr != nil {
|
||||
return nil, fmt.Errorf("rollback failed: %v: original upgrade error: %w", rollbackErr, err)
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
func (c *actionClient) Uninstall(name string, opts ...UninstallOption) (*release.UninstallReleaseResponse, error) {
|
||||
uninstall := action.NewUninstall(c.conf)
|
||||
for _, o := range opts {
|
||||
if err := o(uninstall); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return uninstall.Run(name)
|
||||
}
|
||||
|
||||
func (c *actionClient) Reconcile(rel *release.Release) error {
|
||||
infos, err := c.conf.KubeClient.Build(bytes.NewBufferString(rel.Manifest), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return infos.Visit(func(expected *resource.Info, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("visit error: %w", err)
|
||||
}
|
||||
|
||||
helper := resource.NewHelper(expected.Client, expected.Mapping)
|
||||
|
||||
existing, err := helper.Get(expected.Namespace, expected.Name)
|
||||
if apierrors.IsNotFound(err) {
|
||||
if _, err := helper.Create(expected.Namespace, true, expected.Object); err != nil {
|
||||
return fmt.Errorf("create error: %w", err)
|
||||
}
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("could not get object: %w", err)
|
||||
}
|
||||
|
||||
patch, patchType, err := createPatch(existing, expected)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating patch: %w", err)
|
||||
}
|
||||
|
||||
if patch == nil {
|
||||
// nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = helper.Patch(expected.Namespace, expected.Name, patchType, patch,
|
||||
&metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("patch error: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func createPatch(existing runtime.Object, expected *resource.Info) ([]byte, apitypes.PatchType, error) {
|
||||
existingJSON, err := json.Marshal(existing)
|
||||
if err != nil {
|
||||
return nil, apitypes.StrategicMergePatchType, err
|
||||
}
|
||||
expectedJSON, err := json.Marshal(expected.Object)
|
||||
if err != nil {
|
||||
return nil, apitypes.StrategicMergePatchType, err
|
||||
}
|
||||
|
||||
// Get a versioned object
|
||||
versionedObject := helmkube.AsVersioned(expected)
|
||||
|
||||
// Unstructured objects, such as CRDs, may not have an not registered error
|
||||
// returned from ConvertToVersion. Anything that's unstructured should
|
||||
// use the jsonpatch.CreateMergePatch. Strategic Merge Patch is not supported
|
||||
// on objects like CRDs.
|
||||
_, isUnstructured := versionedObject.(runtime.Unstructured)
|
||||
|
||||
// On newer K8s versions, CRDs aren't unstructured but has this dedicated type
|
||||
_, isCRDv1beta1 := versionedObject.(*apiextv1beta1.CustomResourceDefinition)
|
||||
_, isCRDv1 := versionedObject.(*apiextv1.CustomResourceDefinition)
|
||||
|
||||
if isUnstructured || isCRDv1beta1 || isCRDv1 {
|
||||
// fall back to generic JSON merge patch
|
||||
patch, err := createJSONMergePatch(existingJSON, expectedJSON)
|
||||
return patch, apitypes.JSONPatchType, err
|
||||
}
|
||||
|
||||
patchMeta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject)
|
||||
if err != nil {
|
||||
return nil, apitypes.StrategicMergePatchType, err
|
||||
}
|
||||
|
||||
patch, err := strategicpatch.CreateThreeWayMergePatch(expectedJSON, expectedJSON, existingJSON, patchMeta, true)
|
||||
return patch, apitypes.StrategicMergePatchType, err
|
||||
}
|
||||
|
||||
func createJSONMergePatch(existingJSON, expectedJSON []byte) ([]byte, error) {
|
||||
ops, err := jsonpatch.CreatePatch(existingJSON, expectedJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We ignore the "remove" operations from the full patch because they are
|
||||
// fields added by Kubernetes or by the user after the existing release
|
||||
// resource has been applied. The goal for this patch is to make sure that
|
||||
// the fields managed by the Helm chart are applied.
|
||||
// All "add" operations without a value (null) can be ignored
|
||||
patchOps := make([]jsonpatch.JsonPatchOperation, 0)
|
||||
for _, op := range ops {
|
||||
if op.Operation != "remove" && !(op.Operation == "add" && op.Value == nil) {
|
||||
patchOps = append(patchOps, op)
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no patch operations, return nil. Callers are expected
|
||||
// to check for a nil response and skip the patch operation to avoid
|
||||
// unnecessary chatter with the API server.
|
||||
if len(patchOps) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return json.Marshal(patchOps)
|
||||
}
|
||||
|
||||
func createPostRenderer(rm meta.RESTMapper, kubeClient kube.Interface, owner client.Object) postrender.PostRenderer {
|
||||
return &ownerPostRenderer{rm, kubeClient, owner}
|
||||
}
|
||||
|
||||
type ownerPostRenderer struct {
|
||||
rm meta.RESTMapper
|
||||
kubeClient kube.Interface
|
||||
owner client.Object
|
||||
}
|
||||
|
||||
func (pr *ownerPostRenderer) Run(in *bytes.Buffer) (*bytes.Buffer, error) {
|
||||
resourceList, err := pr.kubeClient.Build(in, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := bytes.Buffer{}
|
||||
|
||||
err = resourceList.Visit(func(r *resource.Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(r.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u := &unstructured.Unstructured{Object: objMap}
|
||||
useOwnerRef, err := controllerutil.SupportsOwnerReference(pr.rm, pr.owner, u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if useOwnerRef && !manifestutil.HasResourcePolicyKeep(u.GetAnnotations()) {
|
||||
ownerRef := metav1.NewControllerRef(pr.owner, pr.owner.GetObjectKind().GroupVersionKind())
|
||||
ownerRefs := append(u.GetOwnerReferences(), *ownerRef)
|
||||
u.SetOwnerReferences(ownerRefs)
|
||||
} else {
|
||||
if err := sdkhandler.SetOwnerAnnotations(pr.owner, u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
outData, err := yaml.Marshal(u.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := out.WriteString("---\n" + string(outData)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
117
vendor/github.com/operator-framework/helm-operator-plugins/pkg/client/actionconfig.go
generated
vendored
Normal file
117
vendor/github.com/operator-framework/helm-operator-plugins/pkg/client/actionconfig.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
Copyright 2020 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/kube"
|
||||
"helm.sh/helm/v3/pkg/storage"
|
||||
"helm.sh/helm/v3/pkg/storage/driver"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
"k8s.io/client-go/rest"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
type ActionConfigGetter interface {
|
||||
ActionConfigFor(obj client.Object) (*action.Configuration, error)
|
||||
}
|
||||
|
||||
func NewActionConfigGetter(cfg *rest.Config, rm meta.RESTMapper, log logr.Logger) ActionConfigGetter {
|
||||
return &actionConfigGetter{
|
||||
cfg: cfg,
|
||||
restMapper: rm,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
var _ ActionConfigGetter = &actionConfigGetter{}
|
||||
|
||||
type actionConfigGetter struct {
|
||||
cfg *rest.Config
|
||||
restMapper meta.RESTMapper
|
||||
log logr.Logger
|
||||
}
|
||||
|
||||
func (acg *actionConfigGetter) ActionConfigFor(obj client.Object) (*action.Configuration, error) {
|
||||
// Create a RESTClientGetter
|
||||
rcg := newRESTClientGetter(acg.cfg, acg.restMapper, obj.GetNamespace())
|
||||
|
||||
// Setup the debug log function that Helm will use
|
||||
debugLog := func(format string, v ...interface{}) {
|
||||
if acg.log != nil {
|
||||
acg.log.V(1).Info(fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Create a client that helm will use to manage release resources.
|
||||
// The passed object is used as an owner reference on every
|
||||
// object the client creates.
|
||||
kc := kube.New(rcg)
|
||||
kc.Log = debugLog
|
||||
|
||||
// Create the Kubernetes Secrets client. The passed object is
|
||||
// also used as an owner reference in the release secrets
|
||||
// created by this client.
|
||||
kcs, err := cmdutil.NewFactory(rcg).KubernetesClientSet()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ownerRef := metav1.NewControllerRef(obj, obj.GetObjectKind().GroupVersionKind())
|
||||
d := driver.NewSecrets(&ownerRefSecretClient{
|
||||
SecretInterface: kcs.CoreV1().Secrets(obj.GetNamespace()),
|
||||
refs: []metav1.OwnerReference{*ownerRef},
|
||||
})
|
||||
|
||||
// Also, use the debug log for the storage driver
|
||||
d.Log = debugLog
|
||||
|
||||
// Initialize the storage backend
|
||||
s := storage.Init(d)
|
||||
|
||||
return &action.Configuration{
|
||||
RESTClientGetter: rcg,
|
||||
Releases: s,
|
||||
KubeClient: kc,
|
||||
Log: debugLog,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var _ v1.SecretInterface = &ownerRefSecretClient{}
|
||||
|
||||
type ownerRefSecretClient struct {
|
||||
v1.SecretInterface
|
||||
refs []metav1.OwnerReference
|
||||
}
|
||||
|
||||
func (c *ownerRefSecretClient) Create(ctx context.Context, in *corev1.Secret, opts metav1.CreateOptions) (*corev1.Secret, error) {
|
||||
in.OwnerReferences = append(in.OwnerReferences, c.refs...)
|
||||
return c.SecretInterface.Create(ctx, in, opts)
|
||||
}
|
||||
|
||||
func (c *ownerRefSecretClient) Update(ctx context.Context, in *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) {
|
||||
in.OwnerReferences = append(in.OwnerReferences, c.refs...)
|
||||
return c.SecretInterface.Update(ctx, in, opts)
|
||||
}
|
||||
100
vendor/github.com/operator-framework/helm-operator-plugins/pkg/client/restclientgetter.go
generated
vendored
Normal file
100
vendor/github.com/operator-framework/helm-operator-plugins/pkg/client/restclientgetter.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
Copyright 2020 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 (
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/client-go/discovery"
|
||||
cached "k8s.io/client-go/discovery/cached"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
var _ genericclioptions.RESTClientGetter = &restClientGetter{}
|
||||
|
||||
func newRESTClientGetter(cfg *rest.Config, rm meta.RESTMapper, ns string) genericclioptions.RESTClientGetter {
|
||||
return &restClientGetter{
|
||||
restConfig: cfg,
|
||||
restMapper: rm,
|
||||
namespaceConfig: &namespaceClientConfig{ns},
|
||||
}
|
||||
}
|
||||
|
||||
type restClientGetter struct {
|
||||
restConfig *rest.Config
|
||||
restMapper meta.RESTMapper
|
||||
namespaceConfig clientcmd.ClientConfig
|
||||
|
||||
setupDiscoveryClient sync.Once
|
||||
cachedDiscoveryClient discovery.CachedDiscoveryInterface
|
||||
}
|
||||
|
||||
func (c *restClientGetter) ToRESTConfig() (*rest.Config, error) {
|
||||
return c.restConfig, nil
|
||||
}
|
||||
|
||||
func (c *restClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
|
||||
var (
|
||||
dc discovery.DiscoveryInterface
|
||||
err error
|
||||
)
|
||||
c.setupDiscoveryClient.Do(func() {
|
||||
dc, err = discovery.NewDiscoveryClientForConfig(c.restConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.cachedDiscoveryClient = cached.NewMemCacheClient(dc)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.cachedDiscoveryClient, nil
|
||||
}
|
||||
|
||||
func (c *restClientGetter) ToRESTMapper() (meta.RESTMapper, error) {
|
||||
return c.restMapper, nil
|
||||
}
|
||||
|
||||
func (c *restClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig {
|
||||
return c.namespaceConfig
|
||||
}
|
||||
|
||||
var _ clientcmd.ClientConfig = &namespaceClientConfig{}
|
||||
|
||||
type namespaceClientConfig struct {
|
||||
namespace string
|
||||
}
|
||||
|
||||
func (c namespaceClientConfig) RawConfig() (clientcmdapi.Config, error) {
|
||||
return clientcmdapi.Config{}, nil
|
||||
}
|
||||
|
||||
func (c namespaceClientConfig) ClientConfig() (*rest.Config, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c namespaceClientConfig) Namespace() (string, bool, error) {
|
||||
return c.namespace, false, nil
|
||||
}
|
||||
|
||||
func (c namespaceClientConfig) ConfigAccess() clientcmd.ConfigAccess {
|
||||
return nil
|
||||
}
|
||||
44
vendor/github.com/operator-framework/helm-operator-plugins/pkg/hook/hook.go
generated
vendored
Normal file
44
vendor/github.com/operator-framework/helm-operator-plugins/pkg/hook/hook.go
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright 2020 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 hook
|
||||
|
||||
import (
|
||||
"github.com/go-logr/logr"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
type PreHook interface {
|
||||
Exec(*unstructured.Unstructured, chartutil.Values, logr.Logger) error
|
||||
}
|
||||
|
||||
type PreHookFunc func(*unstructured.Unstructured, chartutil.Values, logr.Logger) error
|
||||
|
||||
func (f PreHookFunc) Exec(obj *unstructured.Unstructured, vals chartutil.Values, log logr.Logger) error {
|
||||
return f(obj, vals, log)
|
||||
}
|
||||
|
||||
type PostHook interface {
|
||||
Exec(*unstructured.Unstructured, release.Release, logr.Logger) error
|
||||
}
|
||||
|
||||
type PostHookFunc func(*unstructured.Unstructured, release.Release, logr.Logger) error
|
||||
|
||||
func (f PostHookFunc) Exec(obj *unstructured.Unstructured, rel release.Release, log logr.Logger) error {
|
||||
return f(obj, rel, log)
|
||||
}
|
||||
90
vendor/github.com/operator-framework/helm-operator-plugins/pkg/internal/sdk/controllerutil/controllerutil.go
generated
vendored
Normal file
90
vendor/github.com/operator-framework/helm-operator-plugins/pkg/internal/sdk/controllerutil/controllerutil.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright 2020 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 controllerutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
)
|
||||
|
||||
var (
|
||||
AddFinalizer = controllerutil.AddFinalizer
|
||||
RemoveFinalizer = controllerutil.RemoveFinalizer
|
||||
ContainsFinalizer = func(obj metav1.Object, finalizer string) bool {
|
||||
for _, f := range obj.GetFinalizers() {
|
||||
if f == finalizer {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
)
|
||||
|
||||
func WaitForDeletion(ctx context.Context, cl client.Reader, o client.Object) error {
|
||||
key := client.ObjectKeyFromObject(o)
|
||||
|
||||
return wait.PollImmediateUntil(time.Millisecond*10, func() (bool, error) {
|
||||
err := cl.Get(ctx, key, o)
|
||||
if apierrors.IsNotFound(err) {
|
||||
return true, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}, ctx.Done())
|
||||
}
|
||||
|
||||
func SupportsOwnerReference(restMapper meta.RESTMapper, owner, dependent client.Object) (bool, error) {
|
||||
ownerGVK := owner.GetObjectKind().GroupVersionKind()
|
||||
ownerMapping, err := restMapper.RESTMapping(ownerGVK.GroupKind(), ownerGVK.Version)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
depGVK := dependent.GetObjectKind().GroupVersionKind()
|
||||
depMapping, err := restMapper.RESTMapping(depGVK.GroupKind(), depGVK.Version)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
ownerClusterScoped := ownerMapping.Scope.Name() == meta.RESTScopeNameRoot
|
||||
ownerNamespace := owner.GetNamespace()
|
||||
depClusterScoped := depMapping.Scope.Name() == meta.RESTScopeNameRoot
|
||||
depNamespace := dependent.GetNamespace()
|
||||
|
||||
if ownerClusterScoped {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if depClusterScoped {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if ownerNamespace != depNamespace {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
81
vendor/github.com/operator-framework/helm-operator-plugins/pkg/internal/sdk/predicate/predicates.go
generated
vendored
Normal file
81
vendor/github.com/operator-framework/helm-operator-plugins/pkg/internal/sdk/predicate/predicates.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2020 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 predicate
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
crtpredicate "sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
)
|
||||
|
||||
var log = logf.Log.WithName("predicate")
|
||||
|
||||
type GenerationChangedPredicate = crtpredicate.GenerationChangedPredicate
|
||||
|
||||
// DependentPredicateFuncs returns functions defined for filtering events
|
||||
func DependentPredicateFuncs() crtpredicate.Funcs {
|
||||
dependentPredicate := crtpredicate.Funcs{
|
||||
// We don't need to reconcile dependent resource creation events
|
||||
// because dependent resources are only ever created during
|
||||
// reconciliation. Another reconcile would be redundant.
|
||||
CreateFunc: func(e event.CreateEvent) bool {
|
||||
o := e.Object.(*unstructured.Unstructured)
|
||||
log.V(1).Info("Skipping reconciliation for dependent resource creation", "name", o.GetName(), "namespace", o.GetNamespace(), "apiVersion", o.GroupVersionKind().GroupVersion(), "kind", o.GroupVersionKind().Kind)
|
||||
return false
|
||||
},
|
||||
|
||||
// Reconcile when a dependent resource is deleted so that it can be
|
||||
// recreated.
|
||||
DeleteFunc: func(e event.DeleteEvent) bool {
|
||||
o := e.Object.(*unstructured.Unstructured)
|
||||
log.V(1).Info("Reconciling due to dependent resource deletion", "name", o.GetName(), "namespace", o.GetNamespace(), "apiVersion", o.GroupVersionKind().GroupVersion(), "kind", o.GroupVersionKind().Kind)
|
||||
return true
|
||||
},
|
||||
|
||||
// Don't reconcile when a generic event is received for a dependent
|
||||
GenericFunc: func(e event.GenericEvent) bool {
|
||||
o := e.Object.(*unstructured.Unstructured)
|
||||
log.V(1).Info("Skipping reconcile due to generic event", "name", o.GetName(), "namespace", o.GetNamespace(), "apiVersion", o.GroupVersionKind().GroupVersion(), "kind", o.GroupVersionKind().Kind)
|
||||
return false
|
||||
},
|
||||
|
||||
// Reconcile when a dependent resource is updated, so that it can
|
||||
// be patched back to the resource managed by the CR, if
|
||||
// necessary. Ignore updates that only change the status and
|
||||
// resourceVersion.
|
||||
UpdateFunc: func(e event.UpdateEvent) bool {
|
||||
old := e.ObjectOld.(*unstructured.Unstructured).DeepCopy()
|
||||
new := e.ObjectNew.(*unstructured.Unstructured).DeepCopy()
|
||||
|
||||
delete(old.Object, "status")
|
||||
delete(new.Object, "status")
|
||||
old.SetResourceVersion("")
|
||||
new.SetResourceVersion("")
|
||||
|
||||
if reflect.DeepEqual(old.Object, new.Object) {
|
||||
return false
|
||||
}
|
||||
log.V(1).Info("Reconciling due to dependent resource update", "name", new.GetName(), "namespace", new.GetNamespace(), "apiVersion", new.GroupVersionKind().GroupVersion(), "kind", new.GroupVersionKind().Kind)
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
return dependentPredicate
|
||||
}
|
||||
191
vendor/github.com/operator-framework/helm-operator-plugins/pkg/internal/sdk/status/conditions.go
generated
vendored
Normal file
191
vendor/github.com/operator-framework/helm-operator-plugins/pkg/internal/sdk/status/conditions.go
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
Copyright 2020 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 status
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kubeclock "k8s.io/apimachinery/pkg/util/clock"
|
||||
)
|
||||
|
||||
// clock is used to set status condition timestamps.
|
||||
// This variable makes it easier to test conditions.
|
||||
var clock kubeclock.Clock = &kubeclock.RealClock{}
|
||||
|
||||
// ConditionType is the type of the condition and is typically a CamelCased
|
||||
// word or short phrase.
|
||||
//
|
||||
// Condition types should indicate state in the "abnormal-true" polarity. For
|
||||
// example, if the condition indicates when a policy is invalid, the "is valid"
|
||||
// case is probably the norm, so the condition should be called "Invalid".
|
||||
type ConditionType string
|
||||
|
||||
// ConditionReason is intended to be a one-word, CamelCase representation of
|
||||
// the category of cause of the current status. It is intended to be used in
|
||||
// concise output, such as one-line kubectl get output, and in summarizing
|
||||
// occurrences of causes.
|
||||
type ConditionReason string
|
||||
|
||||
// Condition represents an observation of an object's state. Conditions are an
|
||||
// extension mechanism intended to be used when the details of an observation
|
||||
// are not a priori known or would not apply to all instances of a given Kind.
|
||||
//
|
||||
// Conditions should be added to explicitly convey properties that users and
|
||||
// components care about rather than requiring those properties to be inferred
|
||||
// from other observations. Once defined, the meaning of a Condition can not be
|
||||
// changed arbitrarily - it becomes part of the API, and has the same
|
||||
// backwards- and forwards-compatibility concerns of any other part of the API.
|
||||
type Condition struct {
|
||||
Type ConditionType `json:"type"`
|
||||
Status corev1.ConditionStatus `json:"status"`
|
||||
Reason ConditionReason `json:"reason,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
|
||||
}
|
||||
|
||||
// IsTrue Condition whether the condition status is "True".
|
||||
func (c Condition) IsTrue() bool {
|
||||
return c.Status == corev1.ConditionTrue
|
||||
}
|
||||
|
||||
// IsFalse returns whether the condition status is "False".
|
||||
func (c Condition) IsFalse() bool {
|
||||
return c.Status == corev1.ConditionFalse
|
||||
}
|
||||
|
||||
// IsUnknown returns whether the condition status is "Unknown".
|
||||
func (c Condition) IsUnknown() bool {
|
||||
return c.Status == corev1.ConditionUnknown
|
||||
}
|
||||
|
||||
// DeepCopyInto copies in into out.
|
||||
func (c *Condition) DeepCopyInto(cpy *Condition) {
|
||||
*cpy = *c
|
||||
}
|
||||
|
||||
// Conditions is a set of Condition instances.
|
||||
type Conditions []Condition
|
||||
|
||||
// NewConditions initializes a set of conditions with the given list of
|
||||
// conditions.
|
||||
func NewConditions(conds ...Condition) Conditions {
|
||||
conditions := Conditions{}
|
||||
for _, c := range conds {
|
||||
conditions.SetCondition(c)
|
||||
}
|
||||
return conditions
|
||||
}
|
||||
|
||||
// IsTrueFor searches the set of conditions for a condition with the given
|
||||
// ConditionType. If found, it returns `condition.IsTrue()`. If not found,
|
||||
// it returns false.
|
||||
func (conditions Conditions) IsTrueFor(t ConditionType) bool {
|
||||
for _, condition := range conditions {
|
||||
if condition.Type == t {
|
||||
return condition.IsTrue()
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsFalseFor searches the set of conditions for a condition with the given
|
||||
// ConditionType. If found, it returns `condition.IsFalse()`. If not found,
|
||||
// it returns false.
|
||||
func (conditions Conditions) IsFalseFor(t ConditionType) bool {
|
||||
for _, condition := range conditions {
|
||||
if condition.Type == t {
|
||||
return condition.IsFalse()
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsUnknownFor searches the set of conditions for a condition with the given
|
||||
// ConditionType. If found, it returns `condition.IsUnknown()`. If not found,
|
||||
// it returns true.
|
||||
func (conditions Conditions) IsUnknownFor(t ConditionType) bool {
|
||||
for _, condition := range conditions {
|
||||
if condition.Type == t {
|
||||
return condition.IsUnknown()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SetCondition adds (or updates) the set of conditions with the given
|
||||
// condition. It returns a boolean value indicating whether the set condition
|
||||
// is new or was a change to the existing condition with the same type.
|
||||
func (conditions *Conditions) SetCondition(newCond Condition) bool {
|
||||
newCond.LastTransitionTime = metav1.Time{Time: clock.Now()}
|
||||
|
||||
for i, condition := range *conditions {
|
||||
if condition.Type == newCond.Type {
|
||||
if condition.Status == newCond.Status {
|
||||
newCond.LastTransitionTime = condition.LastTransitionTime
|
||||
}
|
||||
changed := condition.Status != newCond.Status ||
|
||||
condition.Reason != newCond.Reason ||
|
||||
condition.Message != newCond.Message
|
||||
(*conditions)[i] = newCond
|
||||
return changed
|
||||
}
|
||||
}
|
||||
*conditions = append(*conditions, newCond)
|
||||
return true
|
||||
}
|
||||
|
||||
// GetCondition searches the set of conditions for the condition with the given
|
||||
// ConditionType and returns it. If the matching condition is not found,
|
||||
// GetCondition returns nil.
|
||||
func (conditions Conditions) GetCondition(t ConditionType) *Condition {
|
||||
for _, condition := range conditions {
|
||||
if condition.Type == t {
|
||||
return &condition
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveCondition removes the condition with the given ConditionType from
|
||||
// the conditions set. If no condition with that type is found, RemoveCondition
|
||||
// returns without performing any action. If the passed condition type is not
|
||||
// found in the set of conditions, RemoveCondition returns false.
|
||||
func (conditions *Conditions) RemoveCondition(t ConditionType) bool {
|
||||
if conditions == nil {
|
||||
return false
|
||||
}
|
||||
for i, condition := range *conditions {
|
||||
if condition.Type == t {
|
||||
*conditions = append((*conditions)[:i], (*conditions)[i+1:]...)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MarshalJSON marshals the set of conditions as a JSON array, sorted by
|
||||
// condition type.
|
||||
func (conditions Conditions) MarshalJSON() ([]byte, error) {
|
||||
conds := []Condition(conditions)
|
||||
sort.Slice(conds, func(a, b int) bool {
|
||||
return conds[a].Type < conds[b].Type
|
||||
})
|
||||
return json.Marshal(conds)
|
||||
}
|
||||
35
vendor/github.com/operator-framework/helm-operator-plugins/pkg/manifestutil/resourcepolicykeep.go
generated
vendored
Normal file
35
vendor/github.com/operator-framework/helm-operator-plugins/pkg/manifestutil/resourcepolicykeep.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright 2021 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 manifestutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"helm.sh/helm/v3/pkg/kube"
|
||||
)
|
||||
|
||||
func HasResourcePolicyKeep(annotations map[string]string) bool {
|
||||
if annotations == nil {
|
||||
return false
|
||||
}
|
||||
resourcePolicyType, ok := annotations[kube.ResourcePolicyAnno]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
resourcePolicyType = strings.ToLower(strings.TrimSpace(resourcePolicyType))
|
||||
return resourcePolicyType == kube.KeepPolicy
|
||||
}
|
||||
70
vendor/github.com/operator-framework/helm-operator-plugins/pkg/reconciler/internal/conditions/conditions.go
generated
vendored
Normal file
70
vendor/github.com/operator-framework/helm-operator-plugins/pkg/reconciler/internal/conditions/conditions.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2020 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 conditions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/internal/sdk/status"
|
||||
)
|
||||
|
||||
const (
|
||||
TypeInitialized = "Initialized"
|
||||
TypeDeployed = "Deployed"
|
||||
TypeReleaseFailed = "ReleaseFailed"
|
||||
TypeIrreconcilable = "Irreconcilable"
|
||||
|
||||
ReasonInstallSuccessful = status.ConditionReason("InstallSuccessful")
|
||||
ReasonUpgradeSuccessful = status.ConditionReason("UpgradeSuccessful")
|
||||
ReasonUninstallSuccessful = status.ConditionReason("UninstallSuccessful")
|
||||
|
||||
ReasonErrorGettingClient = status.ConditionReason("ErrorGettingClient")
|
||||
ReasonErrorGettingValues = status.ConditionReason("ErrorGettingValues")
|
||||
ReasonErrorGettingReleaseState = status.ConditionReason("ErrorGettingReleaseState")
|
||||
ReasonInstallError = status.ConditionReason("InstallError")
|
||||
ReasonUpgradeError = status.ConditionReason("UpgradeError")
|
||||
ReasonReconcileError = status.ConditionReason("ReconcileError")
|
||||
ReasonUninstallError = status.ConditionReason("UninstallError")
|
||||
)
|
||||
|
||||
func Initialized(stat corev1.ConditionStatus, reason status.ConditionReason, message interface{}) status.Condition {
|
||||
return newCondition(TypeInitialized, stat, reason, message)
|
||||
}
|
||||
|
||||
func Deployed(stat corev1.ConditionStatus, reason status.ConditionReason, message interface{}) status.Condition {
|
||||
return newCondition(TypeDeployed, stat, reason, message)
|
||||
}
|
||||
|
||||
func ReleaseFailed(stat corev1.ConditionStatus, reason status.ConditionReason, message interface{}) status.Condition {
|
||||
return newCondition(TypeReleaseFailed, stat, reason, message)
|
||||
}
|
||||
|
||||
func Irreconcilable(stat corev1.ConditionStatus, reason status.ConditionReason, message interface{}) status.Condition {
|
||||
return newCondition(TypeIrreconcilable, stat, reason, message)
|
||||
}
|
||||
|
||||
func newCondition(t status.ConditionType, s corev1.ConditionStatus, r status.ConditionReason, m interface{}) status.Condition {
|
||||
message := fmt.Sprintf("%s", m)
|
||||
return status.Condition{
|
||||
Type: t,
|
||||
Status: s,
|
||||
Reason: r,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
100
vendor/github.com/operator-framework/helm-operator-plugins/pkg/reconciler/internal/hook/hook.go
generated
vendored
Normal file
100
vendor/github.com/operator-framework/helm-operator-plugins/pkg/reconciler/internal/hook/hook.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
Copyright 2020 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 hook
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
sdkhandler "github.com/operator-framework/operator-lib/handler"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
"helm.sh/helm/v3/pkg/releaseutil"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/hook"
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/internal/sdk/controllerutil"
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/internal/sdk/predicate"
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/manifestutil"
|
||||
)
|
||||
|
||||
func NewDependentResourceWatcher(c controller.Controller, rm meta.RESTMapper) hook.PostHook {
|
||||
return &dependentResourceWatcher{
|
||||
controller: c,
|
||||
restMapper: rm,
|
||||
m: sync.Mutex{},
|
||||
watches: make(map[schema.GroupVersionKind]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
type dependentResourceWatcher struct {
|
||||
controller controller.Controller
|
||||
restMapper meta.RESTMapper
|
||||
|
||||
m sync.Mutex
|
||||
watches map[schema.GroupVersionKind]struct{}
|
||||
}
|
||||
|
||||
func (d *dependentResourceWatcher) Exec(owner *unstructured.Unstructured, rel release.Release, log logr.Logger) error {
|
||||
// using predefined functions for filtering events
|
||||
dependentPredicate := predicate.DependentPredicateFuncs()
|
||||
|
||||
resources := releaseutil.SplitManifests(rel.Manifest)
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
for _, r := range resources {
|
||||
var obj unstructured.Unstructured
|
||||
err := yaml.Unmarshal([]byte(r), &obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
depGVK := obj.GroupVersionKind()
|
||||
if _, ok := d.watches[depGVK]; ok || depGVK.Empty() {
|
||||
continue
|
||||
}
|
||||
|
||||
useOwnerRef, err := controllerutil.SupportsOwnerReference(d.restMapper, owner, &obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if useOwnerRef && !manifestutil.HasResourcePolicyKeep(obj.GetAnnotations()) {
|
||||
if err := d.controller.Watch(&source.Kind{Type: &obj}, &handler.EnqueueRequestForOwner{
|
||||
OwnerType: owner,
|
||||
IsController: true,
|
||||
}, dependentPredicate); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := d.controller.Watch(&source.Kind{Type: &obj}, &sdkhandler.EnqueueRequestForAnnotation{
|
||||
Type: owner.GetObjectKind().GroupVersionKind().GroupKind(),
|
||||
}, dependentPredicate); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.watches[depGVK] = struct{}{}
|
||||
log.V(1).Info("Watching dependent resource", "dependentAPIVersion", depGVK.GroupVersion(), "dependentKind", depGVK.Kind)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
192
vendor/github.com/operator-framework/helm-operator-plugins/pkg/reconciler/internal/updater/updater.go
generated
vendored
Normal file
192
vendor/github.com/operator-framework/helm-operator-plugins/pkg/reconciler/internal/updater/updater.go
generated
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
Copyright 2020 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 updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/internal/sdk/controllerutil"
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/internal/sdk/status"
|
||||
)
|
||||
|
||||
func New(client client.Client) Updater {
|
||||
return Updater{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type Updater struct {
|
||||
client client.Client
|
||||
updateFuncs []UpdateFunc
|
||||
updateStatusFuncs []UpdateStatusFunc
|
||||
}
|
||||
|
||||
type UpdateFunc func(*unstructured.Unstructured) bool
|
||||
type UpdateStatusFunc func(*helmAppStatus) bool
|
||||
|
||||
func (u *Updater) Update(fs ...UpdateFunc) {
|
||||
u.updateFuncs = append(u.updateFuncs, fs...)
|
||||
}
|
||||
|
||||
func (u *Updater) UpdateStatus(fs ...UpdateStatusFunc) {
|
||||
u.updateStatusFuncs = append(u.updateStatusFuncs, fs...)
|
||||
}
|
||||
|
||||
func (u *Updater) Apply(ctx context.Context, obj *unstructured.Unstructured) error {
|
||||
backoff := retry.DefaultRetry
|
||||
|
||||
// Always update the status first. During uninstall, if
|
||||
// we remove the finalizer, updating the status will fail
|
||||
// because the object and its status will be garbage-collected
|
||||
if err := retry.RetryOnConflict(backoff, func() error {
|
||||
st := statusFor(obj)
|
||||
needsStatusUpdate := false
|
||||
for _, f := range u.updateStatusFuncs {
|
||||
needsStatusUpdate = f(st) || needsStatusUpdate
|
||||
}
|
||||
if needsStatusUpdate {
|
||||
uSt, err := runtime.DefaultUnstructuredConverter.ToUnstructured(st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj.Object["status"] = uSt
|
||||
return u.client.Status().Update(ctx, obj)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := retry.RetryOnConflict(backoff, func() error {
|
||||
needsUpdate := false
|
||||
for _, f := range u.updateFuncs {
|
||||
needsUpdate = f(obj) || needsUpdate
|
||||
}
|
||||
if needsUpdate {
|
||||
return u.client.Update(ctx, obj)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureFinalizer(finalizer string) UpdateFunc {
|
||||
return func(obj *unstructured.Unstructured) bool {
|
||||
if controllerutil.ContainsFinalizer(obj, finalizer) {
|
||||
return false
|
||||
}
|
||||
controllerutil.AddFinalizer(obj, finalizer)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveFinalizer(finalizer string) UpdateFunc {
|
||||
return func(obj *unstructured.Unstructured) bool {
|
||||
if !controllerutil.ContainsFinalizer(obj, finalizer) {
|
||||
return false
|
||||
}
|
||||
controllerutil.RemoveFinalizer(obj, finalizer)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func EnsureCondition(condition status.Condition) UpdateStatusFunc {
|
||||
return func(status *helmAppStatus) bool {
|
||||
return status.Conditions.SetCondition(condition)
|
||||
}
|
||||
}
|
||||
|
||||
func EnsureConditionUnknown(t status.ConditionType) UpdateStatusFunc {
|
||||
return func(s *helmAppStatus) bool {
|
||||
return s.Conditions.SetCondition(status.Condition{
|
||||
Type: t,
|
||||
Status: corev1.ConditionUnknown,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func EnsureDeployedRelease(rel *release.Release) UpdateStatusFunc {
|
||||
return func(status *helmAppStatus) bool {
|
||||
newRel := helmAppReleaseFor(rel)
|
||||
if status.DeployedRelease == nil && newRel == nil {
|
||||
return false
|
||||
}
|
||||
if status.DeployedRelease != nil && newRel != nil &&
|
||||
*status.DeployedRelease == *newRel {
|
||||
return false
|
||||
}
|
||||
status.DeployedRelease = newRel
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveDeployedRelease() UpdateStatusFunc {
|
||||
return EnsureDeployedRelease(nil)
|
||||
}
|
||||
|
||||
type helmAppStatus struct {
|
||||
Conditions status.Conditions `json:"conditions"`
|
||||
DeployedRelease *helmAppRelease `json:"deployedRelease,omitempty"`
|
||||
}
|
||||
|
||||
type helmAppRelease struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Manifest string `json:"manifest,omitempty"`
|
||||
}
|
||||
|
||||
func statusFor(obj *unstructured.Unstructured) *helmAppStatus {
|
||||
if obj == nil || obj.Object == nil {
|
||||
return nil
|
||||
}
|
||||
status, ok := obj.Object["status"]
|
||||
if !ok {
|
||||
return &helmAppStatus{}
|
||||
}
|
||||
|
||||
switch s := status.(type) {
|
||||
case *helmAppStatus:
|
||||
return s
|
||||
case helmAppStatus:
|
||||
return &s
|
||||
case map[string]interface{}:
|
||||
out := &helmAppStatus{}
|
||||
_ = runtime.DefaultUnstructuredConverter.FromUnstructured(s, out)
|
||||
return out
|
||||
default:
|
||||
return &helmAppStatus{}
|
||||
}
|
||||
}
|
||||
|
||||
func helmAppReleaseFor(rel *release.Release) *helmAppRelease {
|
||||
if rel == nil {
|
||||
return nil
|
||||
}
|
||||
return &helmAppRelease{
|
||||
Name: rel.Name,
|
||||
Manifest: rel.Manifest,
|
||||
}
|
||||
}
|
||||
70
vendor/github.com/operator-framework/helm-operator-plugins/pkg/reconciler/internal/values/values.go
generated
vendored
Normal file
70
vendor/github.com/operator-framework/helm-operator-plugins/pkg/reconciler/internal/values/values.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2020 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 values
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
"helm.sh/helm/v3/pkg/strvals"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/values"
|
||||
)
|
||||
|
||||
type Values struct {
|
||||
m map[string]interface{}
|
||||
}
|
||||
|
||||
func FromUnstructured(obj *unstructured.Unstructured) (*Values, error) {
|
||||
if obj == nil || obj.Object == nil {
|
||||
return nil, fmt.Errorf("nil object")
|
||||
}
|
||||
spec, ok := obj.Object["spec"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("spec not found")
|
||||
}
|
||||
specMap, ok := spec.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("spec must be a map")
|
||||
}
|
||||
return New(specMap), nil
|
||||
}
|
||||
|
||||
func New(m map[string]interface{}) *Values {
|
||||
return &Values{m: m}
|
||||
}
|
||||
|
||||
func (v *Values) Map() map[string]interface{} {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
return v.m
|
||||
}
|
||||
|
||||
func (v *Values) ApplyOverrides(in map[string]string) error {
|
||||
for inK, inV := range in {
|
||||
val := fmt.Sprintf("%s=%s", inK, os.ExpandEnv(inV))
|
||||
if err := strvals.ParseInto(val, v.m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var DefaultMapper = values.MapperFunc(func(v chartutil.Values) chartutil.Values { return v })
|
||||
797
vendor/github.com/operator-framework/helm-operator-plugins/pkg/reconciler/reconciler.go
generated
vendored
Normal file
797
vendor/github.com/operator-framework/helm-operator-plugins/pkg/reconciler/reconciler.go
generated
vendored
Normal file
@@ -0,0 +1,797 @@
|
||||
/*
|
||||
Copyright 2020 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 reconciler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
sdkhandler "github.com/operator-framework/operator-lib/handler"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
"helm.sh/helm/v3/pkg/release"
|
||||
"helm.sh/helm/v3/pkg/storage/driver"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/annotation"
|
||||
helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client"
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/hook"
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/internal/sdk/controllerutil"
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/reconciler/internal/conditions"
|
||||
internalhook "github.com/operator-framework/helm-operator-plugins/pkg/reconciler/internal/hook"
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/reconciler/internal/updater"
|
||||
internalvalues "github.com/operator-framework/helm-operator-plugins/pkg/reconciler/internal/values"
|
||||
"github.com/operator-framework/helm-operator-plugins/pkg/values"
|
||||
)
|
||||
|
||||
const uninstallFinalizer = "uninstall-helm-release"
|
||||
|
||||
// Reconciler reconciles a Helm object
|
||||
type Reconciler struct {
|
||||
client client.Client
|
||||
actionClientGetter helmclient.ActionClientGetter
|
||||
valueMapper values.Mapper
|
||||
eventRecorder record.EventRecorder
|
||||
preHooks []hook.PreHook
|
||||
postHooks []hook.PostHook
|
||||
|
||||
log logr.Logger
|
||||
gvk *schema.GroupVersionKind
|
||||
chrt *chart.Chart
|
||||
overrideValues map[string]string
|
||||
skipDependentWatches bool
|
||||
maxConcurrentReconciles int
|
||||
reconcilePeriod time.Duration
|
||||
|
||||
annotSetupOnce sync.Once
|
||||
annotations map[string]struct{}
|
||||
installAnnotations map[string]annotation.Install
|
||||
upgradeAnnotations map[string]annotation.Upgrade
|
||||
uninstallAnnotations map[string]annotation.Uninstall
|
||||
}
|
||||
|
||||
// New creates a new Reconciler that reconciles custom resources that define a
|
||||
// Helm release. New takes variadic Option arguments that are used to configure
|
||||
// the Reconciler.
|
||||
//
|
||||
// Required options are:
|
||||
// - WithGroupVersionKind
|
||||
// - WithChart
|
||||
//
|
||||
// Other options are defaulted to sane defaults when SetupWithManager is called.
|
||||
//
|
||||
// If an error occurs configuring or validating the Reconciler, it is returned.
|
||||
func New(opts ...Option) (*Reconciler, error) {
|
||||
r := &Reconciler{}
|
||||
r.annotSetupOnce.Do(r.setupAnnotationMaps)
|
||||
for _, o := range opts {
|
||||
if err := o(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) setupAnnotationMaps() {
|
||||
r.annotations = make(map[string]struct{})
|
||||
r.installAnnotations = make(map[string]annotation.Install)
|
||||
r.upgradeAnnotations = make(map[string]annotation.Upgrade)
|
||||
r.uninstallAnnotations = make(map[string]annotation.Uninstall)
|
||||
}
|
||||
|
||||
// SetupWithManager configures a controller for the Reconciler and registers
|
||||
// watches. It also uses the passed Manager to initialize default values for the
|
||||
// Reconciler and sets up the manager's scheme with the Reconciler's configured
|
||||
// GroupVersionKind.
|
||||
//
|
||||
// If an error occurs setting up the Reconciler with the manager, it is
|
||||
// returned.
|
||||
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
controllerName := fmt.Sprintf("%v-controller", strings.ToLower(r.gvk.Kind))
|
||||
|
||||
r.addDefaults(mgr, controllerName)
|
||||
r.setupScheme(mgr)
|
||||
|
||||
c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r, MaxConcurrentReconciles: r.maxConcurrentReconciles})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.setupWatches(mgr, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.log.Info("Watching resource",
|
||||
"group", r.gvk.Group,
|
||||
"version", r.gvk.Version,
|
||||
"kind", r.gvk.Kind,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Option is a function that configures the helm Reconciler.
|
||||
type Option func(r *Reconciler) error
|
||||
|
||||
// WithClient is an Option that configures a Reconciler's client.
|
||||
//
|
||||
// By default, manager.GetClient() is used if this option is not configured.
|
||||
func WithClient(cl client.Client) Option {
|
||||
return func(r *Reconciler) error {
|
||||
r.client = cl
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithActionClientGetter is an Option that configures a Reconciler's
|
||||
// ActionClientGetter.
|
||||
//
|
||||
// A default ActionClientGetter is used if this option is not configured.
|
||||
func WithActionClientGetter(actionClientGetter helmclient.ActionClientGetter) Option {
|
||||
return func(r *Reconciler) error {
|
||||
r.actionClientGetter = actionClientGetter
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithEventRecorder is an Option that configures a Reconciler's EventRecorder.
|
||||
//
|
||||
// By default, manager.GetEventRecorderFor() is used if this option is not
|
||||
// configured.
|
||||
func WithEventRecorder(er record.EventRecorder) Option {
|
||||
return func(r *Reconciler) error {
|
||||
r.eventRecorder = er
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLog is an Option that configures a Reconciler's logger.
|
||||
//
|
||||
// A default logger is used if this option is not configured.
|
||||
func WithLog(log logr.Logger) Option {
|
||||
return func(r *Reconciler) error {
|
||||
r.log = log
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithGroupVersionKind is an Option that configures a Reconciler's
|
||||
// GroupVersionKind.
|
||||
//
|
||||
// This option is required.
|
||||
func WithGroupVersionKind(gvk schema.GroupVersionKind) Option {
|
||||
return func(r *Reconciler) error {
|
||||
r.gvk = &gvk
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithChart is an Option that configures a Reconciler's helm chart.
|
||||
//
|
||||
// This option is required.
|
||||
func WithChart(chrt chart.Chart) Option {
|
||||
return func(r *Reconciler) error {
|
||||
r.chrt = &chrt
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithOverrideValues is an Option that configures a Reconciler's override
|
||||
// values.
|
||||
//
|
||||
// Override values can be used to enforce that certain values provided by the
|
||||
// chart's default values.yaml or by a CR spec are always overridden when
|
||||
// rendering the chart. If a value in overrides is set by a CR, it is
|
||||
// overridden by the override value. The override value can be static but can
|
||||
// also refer to an environment variable.
|
||||
//
|
||||
// If an environment variable reference is listed in override values but is not
|
||||
// present in the environment when this function runs, it will resolve to an
|
||||
// empty string and override all other values. Therefore, when using
|
||||
// environment variable expansion, ensure that the environment variable is set.
|
||||
func WithOverrideValues(overrides map[string]string) Option {
|
||||
return func(r *Reconciler) error {
|
||||
// Validate that overrides can be parsed and applied
|
||||
// so that we fail fast during operator setup rather
|
||||
// than during the first reconciliation.
|
||||
m := internalvalues.New(map[string]interface{}{})
|
||||
if err := m.ApplyOverrides(overrides); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.overrideValues = overrides
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithDependentWatchesEnabled is an Option that configures whether the
|
||||
// Reconciler will register watches for dependent objects in releases and
|
||||
// trigger reconciliations when they change.
|
||||
//
|
||||
// By default, dependent watches are enabled.
|
||||
func SkipDependentWatches(skip bool) Option {
|
||||
return func(r *Reconciler) error {
|
||||
r.skipDependentWatches = skip
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithMaxConcurrentReconciles is an Option that configures the number of
|
||||
// concurrent reconciles that the controller will run.
|
||||
//
|
||||
// The default is 1.
|
||||
func WithMaxConcurrentReconciles(max int) Option {
|
||||
return func(r *Reconciler) error {
|
||||
if max < 1 {
|
||||
return errors.New("maxConcurrentReconciles must be at least 1")
|
||||
}
|
||||
r.maxConcurrentReconciles = max
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithReconcilePeriod is an Option that configures the reconcile period of the
|
||||
// controller. This will cause the controller to reconcile CRs at least once
|
||||
// every period. By default, the reconcile period is set to 0, which means no
|
||||
// time-based reconciliations will occur.
|
||||
func WithReconcilePeriod(rp time.Duration) Option {
|
||||
return func(r *Reconciler) error {
|
||||
if rp < 0 {
|
||||
return errors.New("reconcile period must not be negative")
|
||||
}
|
||||
r.reconcilePeriod = rp
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithInstallAnnotations is an Option that configures Install annotations
|
||||
// to enable custom action.Install fields to be set based on the value of
|
||||
// annotations found in the custom resource watched by this reconciler.
|
||||
// Duplicate annotation names will result in an error.
|
||||
func WithInstallAnnotations(as ...annotation.Install) Option {
|
||||
return func(r *Reconciler) error {
|
||||
r.annotSetupOnce.Do(r.setupAnnotationMaps)
|
||||
|
||||
for _, a := range as {
|
||||
name := a.Name()
|
||||
if _, ok := r.annotations[name]; ok {
|
||||
return fmt.Errorf("annotation %q already exists", name)
|
||||
}
|
||||
|
||||
r.annotations[name] = struct{}{}
|
||||
r.installAnnotations[name] = a
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithUpgradeAnnotations is an Option that configures Upgrade annotations
|
||||
// to enable custom action.Upgrade fields to be set based on the value of
|
||||
// annotations found in the custom resource watched by this reconciler.
|
||||
// Duplicate annotation names will result in an error.
|
||||
func WithUpgradeAnnotations(as ...annotation.Upgrade) Option {
|
||||
return func(r *Reconciler) error {
|
||||
r.annotSetupOnce.Do(r.setupAnnotationMaps)
|
||||
|
||||
for _, a := range as {
|
||||
name := a.Name()
|
||||
if _, ok := r.annotations[name]; ok {
|
||||
return fmt.Errorf("annotation %q already exists", name)
|
||||
}
|
||||
|
||||
r.annotations[name] = struct{}{}
|
||||
r.upgradeAnnotations[name] = a
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithUninstallAnnotations is an Option that configures Uninstall annotations
|
||||
// to enable custom action.Uninstall fields to be set based on the value of
|
||||
// annotations found in the custom resource watched by this reconciler.
|
||||
// Duplicate annotation names will result in an error.
|
||||
func WithUninstallAnnotations(as ...annotation.Uninstall) Option {
|
||||
return func(r *Reconciler) error {
|
||||
r.annotSetupOnce.Do(r.setupAnnotationMaps)
|
||||
|
||||
for _, a := range as {
|
||||
name := a.Name()
|
||||
if _, ok := r.annotations[name]; ok {
|
||||
return fmt.Errorf("annotation %q already exists", name)
|
||||
}
|
||||
|
||||
r.annotations[name] = struct{}{}
|
||||
r.uninstallAnnotations[name] = a
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithPreHook is an Option that configures the reconciler to run the given
|
||||
// PreHook just before performing any actions (e.g. install, upgrade, uninstall,
|
||||
// or reconciliation).
|
||||
func WithPreHook(h hook.PreHook) Option {
|
||||
return func(r *Reconciler) error {
|
||||
r.preHooks = append(r.preHooks, h)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithPostHook is an Option that configures the reconciler to run the given
|
||||
// PostHook just after performing any non-uninstall release actions.
|
||||
func WithPostHook(h hook.PostHook) Option {
|
||||
return func(r *Reconciler) error {
|
||||
r.postHooks = append(r.postHooks, h)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithValueMapper is an Option that configures a function that maps values
|
||||
// from a custom resource spec to the values passed to Helm
|
||||
func WithValueMapper(m values.Mapper) Option {
|
||||
return func(r *Reconciler) error {
|
||||
r.valueMapper = m
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Reconcile reconciles a CR that defines a Helm v3 release.
|
||||
//
|
||||
// - If a release does not exist for this CR, a new release is installed.
|
||||
// - If a release exists and the CR spec has changed since the last,
|
||||
// reconciliation, the release is upgraded.
|
||||
// - If a release exists and the CR spec has not changed since the last
|
||||
// reconciliation, the release is reconciled. Any dependent resources that
|
||||
// have diverged from the release manifest are re-created or patched so that
|
||||
// they are re-aligned with the release.
|
||||
// - If the CR has been deleted, the release will be uninstalled. The
|
||||
// Reconciler uses a finalizer to ensure the release uninstall succeeds
|
||||
// before CR deletion occurs.
|
||||
//
|
||||
// If an error occurs during release installation or upgrade, the change will be
|
||||
// rolled back to restore the previous state.
|
||||
//
|
||||
// Reconcile also manages the status field of the custom resource. It includes
|
||||
// the release name and manifest in `status.deployedRelease`, and it updates
|
||||
// `status.conditions` based on reconciliation progress and success. Condition
|
||||
// types include:
|
||||
//
|
||||
// - Deployed - a release for this CR is deployed (but not necessarily ready).
|
||||
// - ReleaseFailed - an installation or upgrade failed.
|
||||
// - Irreconcilable - an error occurred during reconciliation
|
||||
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, err error) {
|
||||
log := r.log.WithValues(strings.ToLower(r.gvk.Kind), req.NamespacedName)
|
||||
|
||||
obj := &unstructured.Unstructured{}
|
||||
obj.SetGroupVersionKind(*r.gvk)
|
||||
err = r.client.Get(ctx, req.NamespacedName, obj)
|
||||
if apierrors.IsNotFound(err) {
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
u := updater.New(r.client)
|
||||
defer func() {
|
||||
applyErr := u.Apply(ctx, obj)
|
||||
if err == nil && !apierrors.IsNotFound(applyErr) {
|
||||
err = applyErr
|
||||
}
|
||||
}()
|
||||
|
||||
actionClient, err := r.actionClientGetter.ActionClientFor(obj)
|
||||
if err != nil {
|
||||
u.UpdateStatus(
|
||||
updater.EnsureCondition(conditions.Irreconcilable(corev1.ConditionTrue, conditions.ReasonErrorGettingClient, err)),
|
||||
updater.EnsureConditionUnknown(conditions.TypeDeployed),
|
||||
updater.EnsureConditionUnknown(conditions.TypeInitialized),
|
||||
updater.EnsureConditionUnknown(conditions.TypeReleaseFailed),
|
||||
updater.EnsureDeployedRelease(nil),
|
||||
)
|
||||
// NOTE: If obj has the uninstall finalizer, that means a release WAS deployed at some point
|
||||
// in the past, but we don't know if it still is because we don't have an actionClient to check.
|
||||
// So the question is, what do we do with the finalizer? We could:
|
||||
// - Leave it in place. This would make the CR impossible to delete without either resolving this error, or
|
||||
// manually uninstalling the release, deleting the finalizer, and deleting the CR.
|
||||
// - Remove the finalizer. This would make it possible to delete the CR, but it would leave around any
|
||||
// release resources that are not owned by the CR (those in the cluster scope or in other namespaces).
|
||||
//
|
||||
// The decision made for now is to leave the finalizer in place, so that the user can intervene and try to
|
||||
// resolve the issue, instead of the operator silently leaving some dangling resources hanging around after the
|
||||
// CR is deleted.
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// As soon as we get the actionClient, lookup the release and
|
||||
// update the status with this info. We need to do this as
|
||||
// early as possible in case other irreconcilable errors occur.
|
||||
//
|
||||
// We also make sure not to return any errors we encounter so
|
||||
// we can still attempt an uninstall if the CR is being deleted.
|
||||
rel, err := actionClient.Get(obj.GetName())
|
||||
if errors.Is(err, driver.ErrReleaseNotFound) {
|
||||
u.UpdateStatus(updater.EnsureCondition(conditions.Deployed(corev1.ConditionFalse, "", "")))
|
||||
} else if err == nil {
|
||||
ensureDeployedRelease(&u, rel)
|
||||
}
|
||||
u.UpdateStatus(updater.EnsureCondition(conditions.Initialized(corev1.ConditionTrue, "", "")))
|
||||
|
||||
if obj.GetDeletionTimestamp() != nil {
|
||||
err := r.handleDeletion(ctx, actionClient, obj, log)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
vals, err := r.getValues(obj)
|
||||
if err != nil {
|
||||
u.UpdateStatus(
|
||||
updater.EnsureCondition(conditions.Irreconcilable(corev1.ConditionTrue, conditions.ReasonErrorGettingValues, err)),
|
||||
updater.EnsureConditionUnknown(conditions.TypeReleaseFailed),
|
||||
)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
rel, state, err := r.getReleaseState(actionClient, obj, vals.AsMap())
|
||||
if err != nil {
|
||||
u.UpdateStatus(
|
||||
updater.EnsureCondition(conditions.Irreconcilable(corev1.ConditionTrue, conditions.ReasonErrorGettingReleaseState, err)),
|
||||
updater.EnsureConditionUnknown(conditions.TypeReleaseFailed),
|
||||
updater.EnsureConditionUnknown(conditions.TypeDeployed),
|
||||
updater.EnsureDeployedRelease(nil),
|
||||
)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
u.UpdateStatus(updater.EnsureCondition(conditions.Irreconcilable(corev1.ConditionFalse, "", "")))
|
||||
|
||||
for _, h := range r.preHooks {
|
||||
if err := h.Exec(obj, vals, log); err != nil {
|
||||
log.Error(err, "pre-release hook failed")
|
||||
}
|
||||
}
|
||||
|
||||
switch state {
|
||||
case stateNeedsInstall:
|
||||
rel, err = r.doInstall(actionClient, &u, obj, vals.AsMap(), log)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
case stateNeedsUpgrade:
|
||||
rel, err = r.doUpgrade(actionClient, &u, obj, vals.AsMap(), log)
|
||||
if err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
case stateUnchanged:
|
||||
if err := r.doReconcile(actionClient, &u, rel, log); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
default:
|
||||
return ctrl.Result{}, fmt.Errorf("unexpected release state: %s", state)
|
||||
}
|
||||
|
||||
for _, h := range r.postHooks {
|
||||
if err := h.Exec(obj, *rel, log); err != nil {
|
||||
log.Error(err, "post-release hook failed", "name", rel.Name, "version", rel.Version)
|
||||
}
|
||||
}
|
||||
|
||||
ensureDeployedRelease(&u, rel)
|
||||
u.UpdateStatus(
|
||||
updater.EnsureCondition(conditions.ReleaseFailed(corev1.ConditionFalse, "", "")),
|
||||
updater.EnsureCondition(conditions.Irreconcilable(corev1.ConditionFalse, "", "")),
|
||||
)
|
||||
|
||||
return ctrl.Result{RequeueAfter: r.reconcilePeriod}, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) getValues(obj *unstructured.Unstructured) (chartutil.Values, error) {
|
||||
crVals, err := internalvalues.FromUnstructured(obj)
|
||||
if err != nil {
|
||||
return chartutil.Values{}, err
|
||||
}
|
||||
if err := crVals.ApplyOverrides(r.overrideValues); err != nil {
|
||||
return chartutil.Values{}, err
|
||||
}
|
||||
vals := r.valueMapper.Map(crVals.Map())
|
||||
vals, err = chartutil.CoalesceValues(r.chrt, vals)
|
||||
if err != nil {
|
||||
return chartutil.Values{}, err
|
||||
}
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
type helmReleaseState string
|
||||
|
||||
const (
|
||||
stateNeedsInstall helmReleaseState = "needs install"
|
||||
stateNeedsUpgrade helmReleaseState = "needs upgrade"
|
||||
stateUnchanged helmReleaseState = "unchanged"
|
||||
stateError helmReleaseState = "error"
|
||||
)
|
||||
|
||||
func (r *Reconciler) handleDeletion(ctx context.Context, actionClient helmclient.ActionInterface, obj *unstructured.Unstructured, log logr.Logger) error {
|
||||
if !controllerutil.ContainsFinalizer(obj, uninstallFinalizer) {
|
||||
log.Info("Resource is terminated, skipping reconciliation")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use defer in a closure so that it executes before we wait for
|
||||
// the deletion of the CR. This might seem unnecessary since we're
|
||||
// applying changes to the CR after is has a deletion timestamp.
|
||||
// However, if uninstall fails, the finalizer will not be removed
|
||||
// and we need to be able to update the conditions on the CR to
|
||||
// indicate that the uninstall failed.
|
||||
if err := func() (err error) {
|
||||
uninstallUpdater := updater.New(r.client)
|
||||
defer func() {
|
||||
applyErr := uninstallUpdater.Apply(ctx, obj)
|
||||
if err == nil {
|
||||
err = applyErr
|
||||
}
|
||||
}()
|
||||
return r.doUninstall(actionClient, &uninstallUpdater, obj, log)
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Since the client is hitting a cache, waiting for the
|
||||
// deletion here will guarantee that the next reconciliation
|
||||
// will see that the CR has been deleted and that there's
|
||||
// nothing left to do.
|
||||
if err := controllerutil.WaitForDeletion(ctx, r.client, obj); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) getReleaseState(client helmclient.ActionInterface, obj metav1.Object, vals map[string]interface{}) (*release.Release, helmReleaseState, error) {
|
||||
currentRelease, err := client.Get(obj.GetName())
|
||||
if err != nil && !errors.Is(err, driver.ErrReleaseNotFound) {
|
||||
return nil, stateError, err
|
||||
}
|
||||
|
||||
if errors.Is(err, driver.ErrReleaseNotFound) {
|
||||
return nil, stateNeedsInstall, nil
|
||||
}
|
||||
|
||||
var opts []helmclient.UpgradeOption
|
||||
for name, annot := range r.upgradeAnnotations {
|
||||
if v, ok := obj.GetAnnotations()[name]; ok {
|
||||
opts = append(opts, annot.UpgradeOption(v))
|
||||
}
|
||||
}
|
||||
opts = append(opts, func(u *action.Upgrade) error {
|
||||
u.DryRun = true
|
||||
return nil
|
||||
})
|
||||
specRelease, err := client.Upgrade(obj.GetName(), obj.GetNamespace(), r.chrt, vals, opts...)
|
||||
if err != nil {
|
||||
return currentRelease, stateError, err
|
||||
}
|
||||
if specRelease.Manifest != currentRelease.Manifest ||
|
||||
currentRelease.Info.Status == release.StatusFailed ||
|
||||
currentRelease.Info.Status == release.StatusSuperseded {
|
||||
return currentRelease, stateNeedsUpgrade, nil
|
||||
}
|
||||
return currentRelease, stateUnchanged, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) doInstall(actionClient helmclient.ActionInterface, u *updater.Updater, obj *unstructured.Unstructured, vals map[string]interface{}, log logr.Logger) (*release.Release, error) {
|
||||
var opts []helmclient.InstallOption
|
||||
for name, annot := range r.installAnnotations {
|
||||
if v, ok := obj.GetAnnotations()[name]; ok {
|
||||
opts = append(opts, annot.InstallOption(v))
|
||||
}
|
||||
}
|
||||
rel, err := actionClient.Install(obj.GetName(), obj.GetNamespace(), r.chrt, vals, opts...)
|
||||
if err != nil {
|
||||
u.UpdateStatus(
|
||||
updater.EnsureCondition(conditions.Irreconcilable(corev1.ConditionTrue, conditions.ReasonReconcileError, err)),
|
||||
updater.EnsureCondition(conditions.ReleaseFailed(corev1.ConditionTrue, conditions.ReasonInstallError, err)),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
r.reportOverrideEvents(obj)
|
||||
|
||||
log.Info("Release installed", "name", rel.Name, "version", rel.Version)
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) doUpgrade(actionClient helmclient.ActionInterface, u *updater.Updater, obj *unstructured.Unstructured, vals map[string]interface{}, log logr.Logger) (*release.Release, error) {
|
||||
var opts []helmclient.UpgradeOption
|
||||
for name, annot := range r.upgradeAnnotations {
|
||||
if v, ok := obj.GetAnnotations()[name]; ok {
|
||||
opts = append(opts, annot.UpgradeOption(v))
|
||||
}
|
||||
}
|
||||
|
||||
rel, err := actionClient.Upgrade(obj.GetName(), obj.GetNamespace(), r.chrt, vals, opts...)
|
||||
if err != nil {
|
||||
u.UpdateStatus(
|
||||
updater.EnsureCondition(conditions.Irreconcilable(corev1.ConditionTrue, conditions.ReasonReconcileError, err)),
|
||||
updater.EnsureCondition(conditions.ReleaseFailed(corev1.ConditionTrue, conditions.ReasonUpgradeError, err)),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
r.reportOverrideEvents(obj)
|
||||
|
||||
log.Info("Release upgraded", "name", rel.Name, "version", rel.Version)
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) reportOverrideEvents(obj runtime.Object) {
|
||||
for k, v := range r.overrideValues {
|
||||
r.eventRecorder.Eventf(obj, "Warning", "ValueOverridden",
|
||||
"Chart value %q overridden to %q by operator", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reconciler) doReconcile(actionClient helmclient.ActionInterface, u *updater.Updater, rel *release.Release, log logr.Logger) error {
|
||||
// If a change is made to the CR spec that causes a release failure, a
|
||||
// ConditionReleaseFailed is added to the status conditions. If that change
|
||||
// is then reverted to its previous state, the operator will stop
|
||||
// attempting the release and will resume reconciling. In this case, we
|
||||
// need to set the ConditionReleaseFailed to false because the failing
|
||||
// release is no longer being attempted.
|
||||
u.UpdateStatus(
|
||||
updater.EnsureCondition(conditions.ReleaseFailed(corev1.ConditionFalse, "", "")),
|
||||
)
|
||||
|
||||
if err := actionClient.Reconcile(rel); err != nil {
|
||||
u.UpdateStatus(updater.EnsureCondition(conditions.Irreconcilable(corev1.ConditionTrue, conditions.ReasonReconcileError, err)))
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Release reconciled", "name", rel.Name, "version", rel.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) doUninstall(actionClient helmclient.ActionInterface, u *updater.Updater, obj *unstructured.Unstructured, log logr.Logger) error {
|
||||
var opts []helmclient.UninstallOption
|
||||
for name, annot := range r.uninstallAnnotations {
|
||||
if v, ok := obj.GetAnnotations()[name]; ok {
|
||||
opts = append(opts, annot.UninstallOption(v))
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := actionClient.Uninstall(obj.GetName(), opts...)
|
||||
if errors.Is(err, driver.ErrReleaseNotFound) {
|
||||
log.Info("Release not found, removing finalizer")
|
||||
} else if err != nil {
|
||||
u.UpdateStatus(
|
||||
updater.EnsureCondition(conditions.Irreconcilable(corev1.ConditionTrue, conditions.ReasonReconcileError, err)),
|
||||
updater.EnsureCondition(conditions.ReleaseFailed(corev1.ConditionTrue, conditions.ReasonUninstallError, err)),
|
||||
)
|
||||
return err
|
||||
} else {
|
||||
log.Info("Release uninstalled", "name", resp.Release.Name, "version", resp.Release.Version)
|
||||
}
|
||||
u.Update(updater.RemoveFinalizer(uninstallFinalizer))
|
||||
u.UpdateStatus(
|
||||
updater.EnsureCondition(conditions.ReleaseFailed(corev1.ConditionFalse, "", "")),
|
||||
updater.EnsureCondition(conditions.Deployed(corev1.ConditionFalse, conditions.ReasonUninstallSuccessful, "")),
|
||||
updater.RemoveDeployedRelease(),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) validate() error {
|
||||
if r.gvk == nil {
|
||||
return errors.New("gvk must not be nil")
|
||||
}
|
||||
if r.chrt == nil {
|
||||
return errors.New("chart must not be nil")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reconciler) addDefaults(mgr ctrl.Manager, controllerName string) {
|
||||
if r.client == nil {
|
||||
r.client = mgr.GetClient()
|
||||
}
|
||||
if r.log == nil {
|
||||
r.log = ctrl.Log.WithName("controllers").WithName("Helm")
|
||||
}
|
||||
if r.actionClientGetter == nil {
|
||||
actionConfigGetter := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(), r.log)
|
||||
r.actionClientGetter = helmclient.NewActionClientGetter(actionConfigGetter)
|
||||
}
|
||||
if r.eventRecorder == nil {
|
||||
r.eventRecorder = mgr.GetEventRecorderFor(controllerName)
|
||||
}
|
||||
if r.valueMapper == nil {
|
||||
r.valueMapper = internalvalues.DefaultMapper
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reconciler) setupScheme(mgr ctrl.Manager) {
|
||||
mgr.GetScheme().AddKnownTypeWithName(*r.gvk, &unstructured.Unstructured{})
|
||||
metav1.AddToGroupVersion(mgr.GetScheme(), r.gvk.GroupVersion())
|
||||
}
|
||||
|
||||
func (r *Reconciler) setupWatches(mgr ctrl.Manager, c controller.Controller) error {
|
||||
obj := &unstructured.Unstructured{}
|
||||
obj.SetGroupVersionKind(*r.gvk)
|
||||
if err := c.Watch(
|
||||
&source.Kind{Type: obj},
|
||||
&sdkhandler.InstrumentedEnqueueRequestForObject{},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secret := &corev1.Secret{}
|
||||
secret.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
})
|
||||
|
||||
if err := c.Watch(
|
||||
&source.Kind{Type: secret},
|
||||
&handler.EnqueueRequestForOwner{
|
||||
OwnerType: obj,
|
||||
IsController: true,
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !r.skipDependentWatches {
|
||||
r.postHooks = append([]hook.PostHook{internalhook.NewDependentResourceWatcher(c, mgr.GetRESTMapper())}, r.postHooks...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureDeployedRelease(u *updater.Updater, rel *release.Release) {
|
||||
reason := conditions.ReasonInstallSuccessful
|
||||
message := "release was successfully installed"
|
||||
if rel.Version > 1 {
|
||||
reason = conditions.ReasonUpgradeSuccessful
|
||||
message = "release was successfully upgraded"
|
||||
}
|
||||
if rel.Info != nil && len(rel.Info.Notes) > 0 {
|
||||
message = rel.Info.Notes
|
||||
}
|
||||
u.Update(updater.EnsureFinalizer(uninstallFinalizer))
|
||||
u.UpdateStatus(
|
||||
updater.EnsureCondition(conditions.Deployed(corev1.ConditionTrue, reason, message)),
|
||||
updater.EnsureDeployedRelease(rel),
|
||||
)
|
||||
}
|
||||
31
vendor/github.com/operator-framework/helm-operator-plugins/pkg/values/values.go
generated
vendored
Normal file
31
vendor/github.com/operator-framework/helm-operator-plugins/pkg/values/values.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2020 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 values
|
||||
|
||||
import (
|
||||
"helm.sh/helm/v3/pkg/chartutil"
|
||||
)
|
||||
|
||||
type Mapper interface {
|
||||
Map(chartutil.Values) chartutil.Values
|
||||
}
|
||||
|
||||
type MapperFunc func(chartutil.Values) chartutil.Values
|
||||
|
||||
func (m MapperFunc) Map(v chartutil.Values) chartutil.Values {
|
||||
return m(v)
|
||||
}
|
||||
107
vendor/github.com/operator-framework/helm-operator-plugins/pkg/watches/watches.go
generated
vendored
Normal file
107
vendor/github.com/operator-framework/helm-operator-plugins/pkg/watches/watches.go
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright 2020 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 watches
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type Watch struct {
|
||||
schema.GroupVersionKind `json:",inline"`
|
||||
ChartPath string `json:"chart"`
|
||||
|
||||
WatchDependentResources *bool `json:"watchDependentResources,omitempty"`
|
||||
OverrideValues map[string]string `json:"overrideValues,omitempty"`
|
||||
ReconcilePeriod *metav1.Duration `json:"reconcilePeriod,omitempty"`
|
||||
MaxConcurrentReconciles *int `json:"maxConcurrentReconciles,omitempty"`
|
||||
|
||||
Chart *chart.Chart `json:"-"`
|
||||
}
|
||||
|
||||
// Load loads a slice of Watches from the watch file at `path`. For each entry
|
||||
// in the watches file, it verifies the configuration. If an error is
|
||||
// encountered loading the file or verifying the configuration, it will be
|
||||
// returned.
|
||||
func Load(path string) ([]Watch, error) {
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
watches := []Watch{}
|
||||
err = yaml.Unmarshal(b, &watches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
watchesMap := make(map[schema.GroupVersionKind]Watch)
|
||||
for i, w := range watches {
|
||||
if err := verifyGVK(w.GroupVersionKind); err != nil {
|
||||
return nil, fmt.Errorf("invalid GVK: %s: %w", w.GroupVersionKind, err)
|
||||
}
|
||||
|
||||
cl, err := loader.Load(w.ChartPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid chart %s: %w", w.ChartPath, err)
|
||||
}
|
||||
w.Chart = cl
|
||||
w.OverrideValues = expandOverrideEnvs(w.OverrideValues)
|
||||
if w.WatchDependentResources == nil {
|
||||
trueVal := true
|
||||
w.WatchDependentResources = &trueVal
|
||||
}
|
||||
|
||||
if _, ok := watchesMap[w.GroupVersionKind]; ok {
|
||||
return nil, fmt.Errorf("duplicate GVK: %s", w.GroupVersionKind)
|
||||
}
|
||||
|
||||
watchesMap[w.GroupVersionKind] = w
|
||||
watches[i] = w
|
||||
}
|
||||
return watches, nil
|
||||
}
|
||||
|
||||
func expandOverrideEnvs(in map[string]string) map[string]string {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]string)
|
||||
for k, v := range in {
|
||||
out[k] = os.ExpandEnv(v)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user