Skip to content

Commit

Permalink
Add support for App resources having a dependency on HelmReleases (#1267
Browse files Browse the repository at this point in the history
)

* Add support for App resources having a dependency on HelmReleases

* Update k8s dependencies to the latest patch version

* Update k8smetadata to the latest patch version

* Upgrade controller-runtime to the latest patch

* Update ClusterRole to read HelmReleases
  • Loading branch information
nprokopic authored Apr 23, 2024
1 parent d5e5953 commit 016c465
Show file tree
Hide file tree
Showing 10 changed files with 575 additions and 53 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project's packages adheres to [Semantic Versioning](http://semver.org/s

## [Unreleased]

### Added

- Add support for App resources having a dependency on HelmReleases.

## [6.10.3] - 2024-01-29

### Fixed
Expand Down
20 changes: 10 additions & 10 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/giantswarm/errors v0.3.0
github.com/giantswarm/helmclient/v4 v4.11.2
github.com/giantswarm/k8sclient/v7 v7.0.1
github.com/giantswarm/k8smetadata v0.22.0
github.com/giantswarm/k8smetadata v0.24.0
github.com/giantswarm/kubeconfig/v4 v4.1.0
github.com/giantswarm/microendpoint v1.0.0
github.com/giantswarm/microerror v0.4.1
Expand All @@ -27,11 +27,11 @@ require (
github.com/prometheus/client_golang v1.19.0
github.com/spf13/afero v1.11.0
github.com/spf13/viper v1.18.2
k8s.io/api v0.26.1
k8s.io/apiextensions-apiserver v0.26.1
k8s.io/apimachinery v0.26.1
k8s.io/client-go v0.26.1
sigs.k8s.io/controller-runtime v0.14.4
k8s.io/api v0.26.15
k8s.io/apiextensions-apiserver v0.26.15
k8s.io/apimachinery v0.26.15
k8s.io/client-go v0.26.15
sigs.k8s.io/controller-runtime v0.14.7
sigs.k8s.io/yaml v1.4.0
)

Expand Down Expand Up @@ -80,7 +80,7 @@ require (
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
Expand Down Expand Up @@ -160,16 +160,16 @@ require (
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/resty.v1 v1.12.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
helm.sh/helm/v3 v3.10.3 // indirect
k8s.io/apiserver v0.26.1 // indirect
k8s.io/apiserver v0.26.15 // indirect
k8s.io/cli-runtime v0.25.2 // indirect
k8s.io/component-base v0.26.1 // indirect
k8s.io/component-base v0.26.15 // indirect
k8s.io/klog/v2 v2.90.0 // indirect
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
k8s.io/kubectl v0.25.2 // indirect
Expand Down
36 changes: 18 additions & 18 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,8 @@ github.com/giantswarm/helmclient/v4 v4.11.2 h1:QRp5wq4TWZgc1yjKFQiiGGtS2obvKxtJr
github.com/giantswarm/helmclient/v4 v4.11.2/go.mod h1:4JA/1SpZBeHOsXyDxooRltxLFNJuYmRIa2Cx7dK2Yoc=
github.com/giantswarm/k8sclient/v7 v7.0.1 h1:UmRwgsw5Uda27tpIblPo7nWjp/nq5qwqxEPHWcvzsHk=
github.com/giantswarm/k8sclient/v7 v7.0.1/go.mod h1:zJTXammjLHSiukMIO4+a6eUDgzj/lJxEXFZ22mC0WXc=
github.com/giantswarm/k8smetadata v0.22.0 h1:hTDM61G/vbyCPTo16bz3tTb+/Jg77kkEcUWKj6qZP4o=
github.com/giantswarm/k8smetadata v0.22.0/go.mod h1:QiQAyaZnwco1U0lENLF0Kp4bSN4dIPwIlHWEvUo3ES8=
github.com/giantswarm/k8smetadata v0.24.0 h1:mAIgH4W06qx8X5rV9QEtJhCJLn8DMXfTfNVZi5ROp4c=
github.com/giantswarm/k8smetadata v0.24.0/go.mod h1:QiQAyaZnwco1U0lENLF0Kp4bSN4dIPwIlHWEvUo3ES8=
github.com/giantswarm/kubeconfig/v4 v4.1.0 h1:Ziq60FDlbigxRVJ47wLTdF3fx/Ao8gppe4CPmnEO6HM=
github.com/giantswarm/kubeconfig/v4 v4.1.0/go.mod h1:cc01+1DLSnZh8csr33fPIWY/C97bXKMAL5k0Y1jpyYQ=
github.com/giantswarm/microendpoint v1.0.0 h1:vW6VXPaWXBPZc9Q99FgPcjYprAKLItufKFcydBH47Xc=
Expand Down Expand Up @@ -372,8 +372,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
Expand Down Expand Up @@ -1348,22 +1348,22 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.18.9/go.mod h1:9u/h6sUh6FxfErv7QqetX1EB3yBMIYOBXzdcf0Gf0rc=
k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ=
k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg=
k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI=
k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM=
k8s.io/api v0.26.15 h1:tjMERUjIwkq+2UtPZL5ZbSsLkpxUv4gXWZfV5lQl+Og=
k8s.io/api v0.26.15/go.mod h1:CtWOrFl8VLCTLolRlhbBxo4fy83tjCLEtYa5pMubIe0=
k8s.io/apiextensions-apiserver v0.26.15 h1:QePn6+5mihx8sXQLaOXzvF4XPv2RGGj8Pv+O4P75GPU=
k8s.io/apiextensions-apiserver v0.26.15/go.mod h1:PbhgN0XidyF+9vCTUmNgVFK0MMEYqlHLZ4AJeBfiNMo=
k8s.io/apimachinery v0.18.9/go.mod h1:PF5taHbXgTEJLU+xMypMmYTXTWPJ5LaW8bfsisxnEXk=
k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ=
k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74=
k8s.io/apiserver v0.26.1 h1:6vmnAqCDO194SVCPU3MU8NcDgSqsUA62tBUSWrFXhsc=
k8s.io/apiserver v0.26.1/go.mod h1:wr75z634Cv+sifswE9HlAo5FQ7UoUauIICRlOE+5dCg=
k8s.io/apimachinery v0.26.15 h1:GPxeERYBSqSZlj3xIkX4L6mBjzZ9q8JPnJ+Vj15qe+g=
k8s.io/apimachinery v0.26.15/go.mod h1:O/uIhIOWuy6ndHqQ6qbkjD7OgeMhVtlk8+Z66ZcmJQc=
k8s.io/apiserver v0.26.15 h1:9sV2i7+A+/+YjQw9DMf7XTgbUdxtOKeTkluU7VhlD6Y=
k8s.io/apiserver v0.26.15/go.mod h1:dLnCqVroGkCKYNobv9Nm1Ot8GzapzMOKltAlkOlzv8o=
k8s.io/cli-runtime v0.25.2 h1:XOx+SKRjBpYMLY/J292BHTkmyDffl/qOx3YSuFZkTuc=
k8s.io/cli-runtime v0.25.2/go.mod h1:OQx3+/0st6x5YpkkJQlEWLC73V0wHsOFMC1/roxV8Oc=
k8s.io/client-go v0.18.9/go.mod h1:UjkEetDmr40P9NX0Ok3Idt08FCf2I4mIHgjFsot77uY=
k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU=
k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE=
k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4=
k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU=
k8s.io/client-go v0.26.15 h1:A2Yav2v+VZQfpEsf5ESFp2Lqq5XACKBDrwkG+jEtOg0=
k8s.io/client-go v0.26.15/go.mod h1:KJs7snLEyKPlypqTQG/ngcaqE6h3/6qTvVHDViRL+iI=
k8s.io/component-base v0.26.15 h1:32XJyv5fo/lbDZhYU1HyISXTgdSUkbW5cO4DhfR6Y/8=
k8s.io/component-base v0.26.15/go.mod h1:9V+nBzUtTNtRuYfYmQQEhuKrjhL80i2l6F2H2qUsHAI=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
Expand All @@ -1383,8 +1383,8 @@ oras.land/oras-go v1.2.1/go.mod h1:3N11Z5E3c4ZzOjroCl1RtAdB4yNAYl7A27j2SVf913A=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/controller-runtime v0.14.4 h1:Kd/Qgx5pd2XUL08eOV2vwIq3L9GhIbJ5Nxengbd4/0M=
sigs.k8s.io/controller-runtime v0.14.4/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0=
sigs.k8s.io/controller-runtime v0.14.7 h1:Vrnm2vk9ZFlRkXATHz0W0wXcqNl7kPat8q2JyxVy0Q8=
sigs.k8s.io/controller-runtime v0.14.7/go.mod h1:ErTs3SJCOujNUnTz4AS+uh8hp6DHMo1gj6fFndJT1X8=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM=
Expand Down
7 changes: 7 additions & 0 deletions helm/app-operator/templates/rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ rules:
- appcatalogentries
verbs:
- "*"
- apiGroups:
- helm.toolkit.fluxcd.io
resources:
- helmreleases
verbs:
- get
- list
- apiGroups:
- ""
resources:
Expand Down
94 changes: 90 additions & 4 deletions service/controller/app/resource/chart/desired.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"github.com/giantswarm/operatorkit/v8/pkg/controller/context/resourcecanceledcontext"
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/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -31,10 +33,11 @@ import (
const (
chartPullFailedStatus = "chart-pull-failed"

annotationChartOperatorPause = "chart-operator.giantswarm.io/paused"
annotationChartOperatorPauseReason = "app-operator.giantswarm.io/pause-reason"
annotationChartOperatorPauseStarted = "app-operator.giantswarm.io/pause-ts"
annotationChartOperatorDependsOn = "app-operator.giantswarm.io/depends-on"
annotationChartOperatorPause = "chart-operator.giantswarm.io/paused"
annotationChartOperatorPauseReason = "app-operator.giantswarm.io/pause-reason"
annotationChartOperatorPauseStarted = "app-operator.giantswarm.io/pause-ts"
annotationChartOperatorDependsOn = "app-operator.giantswarm.io/depends-on"
annotationChartOperatorDependsOnHelmRelease = "app-operator.giantswarm.io/depends-on-helmrelease"
)

func (r *Resource) GetDesiredState(ctx context.Context, obj interface{}) (interface{}, error) {
Expand Down Expand Up @@ -159,6 +162,44 @@ func (r *Resource) checkDependencies(ctx context.Context, app v1alpha1.App) ([]s
}
}

// Get a list of installed and up-to-date HelmReleases in the same namespace.
helmReleaseGVR := schema.GroupVersionResource{
Group: "helm.toolkit.fluxcd.io",
Version: "v2beta1",
Resource: "helmreleases",
}
dependsOnHelmReleaseValue, ok := app.Annotations[annotationChartOperatorDependsOnHelmRelease]
if ok && dependsOnHelmReleaseValue != "" {
helmReleases, err := r.dynamicClient.Resource(helmReleaseGVR).Namespace(app.Namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, microerror.Mask(err)
}

for _, helmRelease := range helmReleases.Items {
desiredVersion, err := getUnstructuredProperty[string](helmRelease, "spec.chart.spec.version")
if err != nil {
return nil, microerror.Mask(err)
}
lastAppliedRevision, err := getUnstructuredProperty[string](helmRelease, "status.lastAppliedRevision")
if err != nil {
return nil, microerror.Mask(err)
}
isReady := false
conditions, err := getUnstructuredProperty[[]interface{}](helmRelease, "status.conditions")
if err != nil {
return nil, microerror.Mask(err)
}
for _, conditionRaw := range conditions {
condition := conditionRaw.(map[string]interface{})
if strings.ToLower(condition["type"].(string)) == "ready" && strings.ToLower(condition["status"].(string)) == "true" {
isReady = true
}
}

installedApps[helmRelease.GetName()] = isReady && desiredVersion == lastAppliedRevision
}
}

// Get a list of dependencies that are not installed.
dependenciesNotInstalled := make([]string, 0)
{
Expand Down Expand Up @@ -546,3 +587,48 @@ func setStatus(cc *controllercontext.Context, err error) {
addStatusToContext(cc, err.Error(), status.UnknownError)
}
}

// getUnstructuredProperty returns the unstructured object's property from the specified path.
func getUnstructuredProperty[T interface{}](o unstructured.Unstructured, propertyPath string) (T, error) {
var result T

// trim ".", so e.g. ".x.y.z" becomes "x.y.z"
propertyPath = strings.Trim(propertyPath, ".")

// e.g. for propertyPath "x.y.z", here we get ["x", "y", "z"]
propertyNames := strings.Split(propertyPath, ".")

// e.g. propertyPath "x.y.z" has a depth of 3
if len(propertyNames) == 0 {
return result, nil
}

var ok bool
property := o.UnstructuredContent()
for i, propertyName := range propertyNames {
if i < len(propertyNames)-1 {
// we are reading a parent property, e.g. if we want "x.y.z", here we read "x" or "x.y"
propertyRaw, foundProperty := property[propertyName]
if !foundProperty {
return result, nil
}
property, ok = propertyRaw.(map[string]interface{})
if !ok {
return result, microerror.Maskf(propertyNotFoundError, "trying to get property '%s' from the unstructured object, but property '%s' is of type %T and not an object", propertyPath, propertyName, propertyRaw)
}
continue
}

// we are reading desired property of type T at path "x.y.z" (this is the last loop iteration)
if property[propertyName] != nil {
result, ok = property[propertyName].(T)
if !ok {
// Returns error only when the property is set to some non-nil value. When the value is nil,
// the empty value of the desired type will be returned.
return result, microerror.Maskf(wrongTypeError, "property at path %s is of type %T, expected type %T", propertyPath, property[propertyName], result)
}
}
}

return result, nil
}
Loading

0 comments on commit 016c465

Please sign in to comment.