Skip to content

Commit 016c465

Browse files
authored
Add support for App resources having a dependency on HelmReleases (#1267)
* 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
1 parent d5e5953 commit 016c465

File tree

10 files changed

+575
-53
lines changed

10 files changed

+575
-53
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project's packages adheres to [Semantic Versioning](http://semver.org/s
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Add support for App resources having a dependency on HelmReleases.
13+
1014
## [6.10.3] - 2024-01-29
1115

1216
### Fixed

go.mod

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ require (
1313
github.com/giantswarm/errors v0.3.0
1414
github.com/giantswarm/helmclient/v4 v4.11.2
1515
github.com/giantswarm/k8sclient/v7 v7.0.1
16-
github.com/giantswarm/k8smetadata v0.22.0
16+
github.com/giantswarm/k8smetadata v0.24.0
1717
github.com/giantswarm/kubeconfig/v4 v4.1.0
1818
github.com/giantswarm/microendpoint v1.0.0
1919
github.com/giantswarm/microerror v0.4.1
@@ -27,11 +27,11 @@ require (
2727
github.com/prometheus/client_golang v1.19.0
2828
github.com/spf13/afero v1.11.0
2929
github.com/spf13/viper v1.18.2
30-
k8s.io/api v0.26.1
31-
k8s.io/apiextensions-apiserver v0.26.1
32-
k8s.io/apimachinery v0.26.1
33-
k8s.io/client-go v0.26.1
34-
sigs.k8s.io/controller-runtime v0.14.4
30+
k8s.io/api v0.26.15
31+
k8s.io/apiextensions-apiserver v0.26.15
32+
k8s.io/apimachinery v0.26.15
33+
k8s.io/client-go v0.26.15
34+
sigs.k8s.io/controller-runtime v0.14.7
3535
sigs.k8s.io/yaml v1.4.0
3636
)
3737

@@ -80,7 +80,7 @@ require (
8080
github.com/gobwas/glob v0.2.3 // indirect
8181
github.com/gogo/protobuf v1.3.2 // indirect
8282
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
83-
github.com/golang/protobuf v1.5.3 // indirect
83+
github.com/golang/protobuf v1.5.4 // indirect
8484
github.com/google/btree v1.0.1 // indirect
8585
github.com/google/gnostic v0.6.9 // indirect
8686
github.com/google/gofuzz v1.2.0 // indirect
@@ -160,16 +160,16 @@ require (
160160
google.golang.org/appengine v1.6.7 // indirect
161161
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
162162
google.golang.org/grpc v1.59.0 // indirect
163-
google.golang.org/protobuf v1.32.0 // indirect
163+
google.golang.org/protobuf v1.33.0 // indirect
164164
gopkg.in/inf.v0 v0.9.1 // indirect
165165
gopkg.in/ini.v1 v1.67.0 // indirect
166166
gopkg.in/resty.v1 v1.12.0 // indirect
167167
gopkg.in/yaml.v2 v2.4.0 // indirect
168168
gopkg.in/yaml.v3 v3.0.1 // indirect
169169
helm.sh/helm/v3 v3.10.3 // indirect
170-
k8s.io/apiserver v0.26.1 // indirect
170+
k8s.io/apiserver v0.26.15 // indirect
171171
k8s.io/cli-runtime v0.25.2 // indirect
172-
k8s.io/component-base v0.26.1 // indirect
172+
k8s.io/component-base v0.26.15 // indirect
173173
k8s.io/klog/v2 v2.90.0 // indirect
174174
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
175175
k8s.io/kubectl v0.25.2 // indirect

go.sum

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,8 @@ github.com/giantswarm/helmclient/v4 v4.11.2 h1:QRp5wq4TWZgc1yjKFQiiGGtS2obvKxtJr
256256
github.com/giantswarm/helmclient/v4 v4.11.2/go.mod h1:4JA/1SpZBeHOsXyDxooRltxLFNJuYmRIa2Cx7dK2Yoc=
257257
github.com/giantswarm/k8sclient/v7 v7.0.1 h1:UmRwgsw5Uda27tpIblPo7nWjp/nq5qwqxEPHWcvzsHk=
258258
github.com/giantswarm/k8sclient/v7 v7.0.1/go.mod h1:zJTXammjLHSiukMIO4+a6eUDgzj/lJxEXFZ22mC0WXc=
259-
github.com/giantswarm/k8smetadata v0.22.0 h1:hTDM61G/vbyCPTo16bz3tTb+/Jg77kkEcUWKj6qZP4o=
260-
github.com/giantswarm/k8smetadata v0.22.0/go.mod h1:QiQAyaZnwco1U0lENLF0Kp4bSN4dIPwIlHWEvUo3ES8=
259+
github.com/giantswarm/k8smetadata v0.24.0 h1:mAIgH4W06qx8X5rV9QEtJhCJLn8DMXfTfNVZi5ROp4c=
260+
github.com/giantswarm/k8smetadata v0.24.0/go.mod h1:QiQAyaZnwco1U0lENLF0Kp4bSN4dIPwIlHWEvUo3ES8=
261261
github.com/giantswarm/kubeconfig/v4 v4.1.0 h1:Ziq60FDlbigxRVJ47wLTdF3fx/Ao8gppe4CPmnEO6HM=
262262
github.com/giantswarm/kubeconfig/v4 v4.1.0/go.mod h1:cc01+1DLSnZh8csr33fPIWY/C97bXKMAL5k0Y1jpyYQ=
263263
github.com/giantswarm/microendpoint v1.0.0 h1:vW6VXPaWXBPZc9Q99FgPcjYprAKLItufKFcydBH47Xc=
@@ -372,8 +372,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
372372
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
373373
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
374374
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
375-
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
376-
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
375+
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
376+
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
377377
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
378378
github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k=
379379
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -1348,22 +1348,22 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
13481348
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
13491349
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
13501350
k8s.io/api v0.18.9/go.mod h1:9u/h6sUh6FxfErv7QqetX1EB3yBMIYOBXzdcf0Gf0rc=
1351-
k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ=
1352-
k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg=
1353-
k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI=
1354-
k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM=
1351+
k8s.io/api v0.26.15 h1:tjMERUjIwkq+2UtPZL5ZbSsLkpxUv4gXWZfV5lQl+Og=
1352+
k8s.io/api v0.26.15/go.mod h1:CtWOrFl8VLCTLolRlhbBxo4fy83tjCLEtYa5pMubIe0=
1353+
k8s.io/apiextensions-apiserver v0.26.15 h1:QePn6+5mihx8sXQLaOXzvF4XPv2RGGj8Pv+O4P75GPU=
1354+
k8s.io/apiextensions-apiserver v0.26.15/go.mod h1:PbhgN0XidyF+9vCTUmNgVFK0MMEYqlHLZ4AJeBfiNMo=
13551355
k8s.io/apimachinery v0.18.9/go.mod h1:PF5taHbXgTEJLU+xMypMmYTXTWPJ5LaW8bfsisxnEXk=
1356-
k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ=
1357-
k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74=
1358-
k8s.io/apiserver v0.26.1 h1:6vmnAqCDO194SVCPU3MU8NcDgSqsUA62tBUSWrFXhsc=
1359-
k8s.io/apiserver v0.26.1/go.mod h1:wr75z634Cv+sifswE9HlAo5FQ7UoUauIICRlOE+5dCg=
1356+
k8s.io/apimachinery v0.26.15 h1:GPxeERYBSqSZlj3xIkX4L6mBjzZ9q8JPnJ+Vj15qe+g=
1357+
k8s.io/apimachinery v0.26.15/go.mod h1:O/uIhIOWuy6ndHqQ6qbkjD7OgeMhVtlk8+Z66ZcmJQc=
1358+
k8s.io/apiserver v0.26.15 h1:9sV2i7+A+/+YjQw9DMf7XTgbUdxtOKeTkluU7VhlD6Y=
1359+
k8s.io/apiserver v0.26.15/go.mod h1:dLnCqVroGkCKYNobv9Nm1Ot8GzapzMOKltAlkOlzv8o=
13601360
k8s.io/cli-runtime v0.25.2 h1:XOx+SKRjBpYMLY/J292BHTkmyDffl/qOx3YSuFZkTuc=
13611361
k8s.io/cli-runtime v0.25.2/go.mod h1:OQx3+/0st6x5YpkkJQlEWLC73V0wHsOFMC1/roxV8Oc=
13621362
k8s.io/client-go v0.18.9/go.mod h1:UjkEetDmr40P9NX0Ok3Idt08FCf2I4mIHgjFsot77uY=
1363-
k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU=
1364-
k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE=
1365-
k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4=
1366-
k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU=
1363+
k8s.io/client-go v0.26.15 h1:A2Yav2v+VZQfpEsf5ESFp2Lqq5XACKBDrwkG+jEtOg0=
1364+
k8s.io/client-go v0.26.15/go.mod h1:KJs7snLEyKPlypqTQG/ngcaqE6h3/6qTvVHDViRL+iI=
1365+
k8s.io/component-base v0.26.15 h1:32XJyv5fo/lbDZhYU1HyISXTgdSUkbW5cO4DhfR6Y/8=
1366+
k8s.io/component-base v0.26.15/go.mod h1:9V+nBzUtTNtRuYfYmQQEhuKrjhL80i2l6F2H2qUsHAI=
13671367
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
13681368
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
13691369
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
@@ -1383,8 +1383,8 @@ oras.land/oras-go v1.2.1/go.mod h1:3N11Z5E3c4ZzOjroCl1RtAdB4yNAYl7A27j2SVf913A=
13831383
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
13841384
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
13851385
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
1386-
sigs.k8s.io/controller-runtime v0.14.4 h1:Kd/Qgx5pd2XUL08eOV2vwIq3L9GhIbJ5Nxengbd4/0M=
1387-
sigs.k8s.io/controller-runtime v0.14.4/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0=
1386+
sigs.k8s.io/controller-runtime v0.14.7 h1:Vrnm2vk9ZFlRkXATHz0W0wXcqNl7kPat8q2JyxVy0Q8=
1387+
sigs.k8s.io/controller-runtime v0.14.7/go.mod h1:ErTs3SJCOujNUnTz4AS+uh8hp6DHMo1gj6fFndJT1X8=
13881388
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
13891389
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
13901390
sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM=

helm/app-operator/templates/rbac.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ rules:
5858
- appcatalogentries
5959
verbs:
6060
- "*"
61+
- apiGroups:
62+
- helm.toolkit.fluxcd.io
63+
resources:
64+
- helmreleases
65+
verbs:
66+
- get
67+
- list
6168
- apiGroups:
6269
- ""
6370
resources:

service/controller/app/resource/chart/desired.go

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"github.com/giantswarm/operatorkit/v8/pkg/controller/context/resourcecanceledcontext"
1919
apierrors "k8s.io/apimachinery/pkg/api/errors"
2020
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
22+
"k8s.io/apimachinery/pkg/runtime/schema"
2123
"k8s.io/apimachinery/pkg/types"
2224
"k8s.io/client-go/kubernetes"
2325
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -31,10 +33,11 @@ import (
3133
const (
3234
chartPullFailedStatus = "chart-pull-failed"
3335

34-
annotationChartOperatorPause = "chart-operator.giantswarm.io/paused"
35-
annotationChartOperatorPauseReason = "app-operator.giantswarm.io/pause-reason"
36-
annotationChartOperatorPauseStarted = "app-operator.giantswarm.io/pause-ts"
37-
annotationChartOperatorDependsOn = "app-operator.giantswarm.io/depends-on"
36+
annotationChartOperatorPause = "chart-operator.giantswarm.io/paused"
37+
annotationChartOperatorPauseReason = "app-operator.giantswarm.io/pause-reason"
38+
annotationChartOperatorPauseStarted = "app-operator.giantswarm.io/pause-ts"
39+
annotationChartOperatorDependsOn = "app-operator.giantswarm.io/depends-on"
40+
annotationChartOperatorDependsOnHelmRelease = "app-operator.giantswarm.io/depends-on-helmrelease"
3841
)
3942

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

165+
// Get a list of installed and up-to-date HelmReleases in the same namespace.
166+
helmReleaseGVR := schema.GroupVersionResource{
167+
Group: "helm.toolkit.fluxcd.io",
168+
Version: "v2beta1",
169+
Resource: "helmreleases",
170+
}
171+
dependsOnHelmReleaseValue, ok := app.Annotations[annotationChartOperatorDependsOnHelmRelease]
172+
if ok && dependsOnHelmReleaseValue != "" {
173+
helmReleases, err := r.dynamicClient.Resource(helmReleaseGVR).Namespace(app.Namespace).List(ctx, metav1.ListOptions{})
174+
if err != nil {
175+
return nil, microerror.Mask(err)
176+
}
177+
178+
for _, helmRelease := range helmReleases.Items {
179+
desiredVersion, err := getUnstructuredProperty[string](helmRelease, "spec.chart.spec.version")
180+
if err != nil {
181+
return nil, microerror.Mask(err)
182+
}
183+
lastAppliedRevision, err := getUnstructuredProperty[string](helmRelease, "status.lastAppliedRevision")
184+
if err != nil {
185+
return nil, microerror.Mask(err)
186+
}
187+
isReady := false
188+
conditions, err := getUnstructuredProperty[[]interface{}](helmRelease, "status.conditions")
189+
if err != nil {
190+
return nil, microerror.Mask(err)
191+
}
192+
for _, conditionRaw := range conditions {
193+
condition := conditionRaw.(map[string]interface{})
194+
if strings.ToLower(condition["type"].(string)) == "ready" && strings.ToLower(condition["status"].(string)) == "true" {
195+
isReady = true
196+
}
197+
}
198+
199+
installedApps[helmRelease.GetName()] = isReady && desiredVersion == lastAppliedRevision
200+
}
201+
}
202+
162203
// Get a list of dependencies that are not installed.
163204
dependenciesNotInstalled := make([]string, 0)
164205
{
@@ -546,3 +587,48 @@ func setStatus(cc *controllercontext.Context, err error) {
546587
addStatusToContext(cc, err.Error(), status.UnknownError)
547588
}
548589
}
590+
591+
// getUnstructuredProperty returns the unstructured object's property from the specified path.
592+
func getUnstructuredProperty[T interface{}](o unstructured.Unstructured, propertyPath string) (T, error) {
593+
var result T
594+
595+
// trim ".", so e.g. ".x.y.z" becomes "x.y.z"
596+
propertyPath = strings.Trim(propertyPath, ".")
597+
598+
// e.g. for propertyPath "x.y.z", here we get ["x", "y", "z"]
599+
propertyNames := strings.Split(propertyPath, ".")
600+
601+
// e.g. propertyPath "x.y.z" has a depth of 3
602+
if len(propertyNames) == 0 {
603+
return result, nil
604+
}
605+
606+
var ok bool
607+
property := o.UnstructuredContent()
608+
for i, propertyName := range propertyNames {
609+
if i < len(propertyNames)-1 {
610+
// we are reading a parent property, e.g. if we want "x.y.z", here we read "x" or "x.y"
611+
propertyRaw, foundProperty := property[propertyName]
612+
if !foundProperty {
613+
return result, nil
614+
}
615+
property, ok = propertyRaw.(map[string]interface{})
616+
if !ok {
617+
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)
618+
}
619+
continue
620+
}
621+
622+
// we are reading desired property of type T at path "x.y.z" (this is the last loop iteration)
623+
if property[propertyName] != nil {
624+
result, ok = property[propertyName].(T)
625+
if !ok {
626+
// Returns error only when the property is set to some non-nil value. When the value is nil,
627+
// the empty value of the desired type will be returned.
628+
return result, microerror.Maskf(wrongTypeError, "property at path %s is of type %T, expected type %T", propertyPath, property[propertyName], result)
629+
}
630+
}
631+
}
632+
633+
return result, nil
634+
}

0 commit comments

Comments
 (0)