Skip to content

Commit c937de3

Browse files
authored
Pending upgrade release fix (#495)
Signed-off-by: Mikhail Scherba <mikhail.scherba@flant.com>
1 parent 94b1313 commit c937de3

12 files changed

Lines changed: 115 additions & 43 deletions

File tree

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
FROM --platform=${TARGETPLATFORM:-linux/amd64} flant/jq:b6be13d5-musl as libjq
33

44
# Go builder.
5-
FROM --platform=${TARGETPLATFORM:-linux/amd64} golang:1.19-alpine3.16 AS builder
5+
FROM --platform=${TARGETPLATFORM:-linux/amd64} golang:1.20-alpine3.16 AS builder
66

77
ARG appVersion=latest
88
RUN apk --no-cache add git ca-certificates gcc musl-dev libc-dev binutils-gold
@@ -18,7 +18,7 @@ ADD . /app
1818
# Clone shell-operator to get frameworks
1919
RUN git clone https://github.com/flant/shell-operator shell-operator-clone && \
2020
cd shell-operator-clone && \
21-
git checkout v1.1.3
21+
git checkout v1.4.10
2222

2323
RUN shellOpVer=$(go list -m all | grep shell-operator | cut -d' ' -f 2-) \
2424
CGO_ENABLED=1 \

cmd/addon-operator/main.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package main
33
import (
44
"context"
55
"fmt"
6-
"math/rand"
76
"os"
87
"strings"
98
"syscall"
@@ -66,9 +65,6 @@ func main() {
6665
func start(_ *kingpin.ParseContext) error {
6766
sh_app.AppStartMessage = fmt.Sprintf("%s %s, shell-operator %s", app.AppName, app.Version, sh_app.Version)
6867

69-
// Init rand generator.
70-
rand.Seed(time.Now().UnixNano())
71-
7268
ctx := context.Background()
7369

7470
operator := addon_operator.NewAddonOperator(ctx)

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/flant/addon-operator
22

3-
go 1.19
3+
go 1.20
44

55
require (
66
github.com/davecgh/go-spew v1.1.1

pkg/addon-operator/operator.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,28 +1091,34 @@ func (op *AddonOperator) StartModuleManagerEventHandler() {
10911091
default:
10921092
}
10931093

1094-
case absentResourcesEvent := <-op.HelmResourcesManager.Ch():
1094+
case HelmReleaseStatusEvent := <-op.HelmResourcesManager.Ch():
10951095
logLabels := map[string]string{
10961096
"event.id": uuid.Must(uuid.NewV4()).String(),
1097-
"module": absentResourcesEvent.ModuleName,
1097+
"module": HelmReleaseStatusEvent.ModuleName,
10981098
}
10991099
eventLogEntry := logEntry.WithFields(utils.LabelsToLogFields(logLabels))
11001100

11011101
// Do not add ModuleRun task if it is already queued.
1102-
hasTask := QueueHasPendingModuleRunTask(op.engine.TaskQueues.GetMain(), absentResourcesEvent.ModuleName)
1102+
hasTask := QueueHasPendingModuleRunTask(op.engine.TaskQueues.GetMain(), HelmReleaseStatusEvent.ModuleName)
1103+
eventDescription := "AbsentHelmResourcesDetected"
1104+
additionalDescription := fmt.Sprintf("%d absent module resources", len(HelmReleaseStatusEvent.Absent))
1105+
if HelmReleaseStatusEvent.UnexpectedStatus {
1106+
eventDescription = "HelmReleaseUnexpectedStatus"
1107+
additionalDescription = "unexpected helm release status"
1108+
}
1109+
11031110
if !hasTask {
11041111
newTask := sh_task.NewTask(task.ModuleRun).
11051112
WithLogLabels(logLabels).
11061113
WithQueueName("main").
11071114
WithMetadata(task.HookMetadata{
1108-
EventDescription: "DetectAbsentHelmResources",
1109-
ModuleName: absentResourcesEvent.ModuleName,
1115+
EventDescription: eventDescription,
1116+
ModuleName: HelmReleaseStatusEvent.ModuleName,
11101117
})
11111118
op.engine.TaskQueues.GetMain().AddLast(newTask.WithQueuedAt(time.Now()))
1112-
taskAddDescription := fmt.Sprintf("got %d absent module resources, append", len(absentResourcesEvent.Absent))
1113-
op.logTaskAdd(logEntry, taskAddDescription, newTask)
1119+
op.logTaskAdd(logEntry, fmt.Sprintf("detected %s, append", additionalDescription), newTask)
11141120
} else {
1115-
eventLogEntry.WithField("task.flow", "noop").Infof("Got %d absent module resources, ModuleRun task already queued", len(absentResourcesEvent.Absent))
1121+
eventLogEntry.WithField("task.flow", "noop").Infof("Detected %s, ModuleRun task already queued", additionalDescription)
11161122
}
11171123
}
11181124
}

pkg/helm/helm3/helm3.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ func (h *Helm3Client) DeleteRelease(releaseName string) (err error) {
197197
return fmt.Errorf("helm uninstall %s invocation error: %v\n%v %v", releaseName, err, stdout, stderr)
198198
}
199199

200+
h.LogEntry.Debugf("helm release %s deleted", releaseName)
200201
return
201202
}
202203

pkg/helm/helm3lib/helm3lib.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package helm3lib
22

33
import (
4+
"context"
45
"fmt"
56
"os"
67
"sort"
@@ -15,7 +16,10 @@ import (
1516
"helm.sh/helm/v3/pkg/cli"
1617
"helm.sh/helm/v3/pkg/release"
1718
"helm.sh/helm/v3/pkg/releaseutil"
19+
"helm.sh/helm/v3/pkg/storage"
1820
"helm.sh/helm/v3/pkg/storage/driver"
21+
"k8s.io/apimachinery/pkg/api/errors"
22+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1923
"k8s.io/cli-runtime/pkg/genericclioptions"
2024
"k8s.io/client-go/rest"
2125

@@ -141,7 +145,7 @@ func (h *LibClient) UpgradeRelease(releaseName string, chartName string, valuesP
141145
}
142146
return h.upgradeRelease(releaseName, chartName, valuesPaths, setValues, namespace)
143147
}
144-
148+
h.LogEntry.Debugf("helm release %s upgraded", releaseName)
145149
return nil
146150
}
147151

@@ -209,7 +213,34 @@ func (h *LibClient) upgradeRelease(releaseName string, chartName string, valuesP
209213
nsReleaseName := fmt.Sprintf("%s/%s", latestRelease.Namespace, latestRelease.Name)
210214
h.LogEntry.Debugf("Latest release '%s': revision: %d has status: %s", nsReleaseName, latestRelease.Version, latestRelease.Info.Status)
211215
if latestRelease.Info.Status.IsPending() {
212-
h.rollbackLatestRelease(releases)
216+
objectName := fmt.Sprintf("%s.%s.v%d", storage.HelmStorageType, latestRelease.Name, latestRelease.Version)
217+
kubeClient, err := actionConfig.KubernetesClientSet()
218+
if err != nil {
219+
return fmt.Errorf("couldn't get kubernetes client set: %w", err)
220+
}
221+
// switch between storage types (memory, sql, secrets, configmaps) - with secrets and configmaps we can deal a bit more straightforward than doing a rollback
222+
switch actionConfig.Releases.Name() {
223+
case driver.ConfigMapsDriverName:
224+
h.LogEntry.Debugf("ConfigMap for helm revision %d of release %s in status %s, driver %s: will be deleted", latestRelease.Version, nsReleaseName, latestRelease.Info.Status, driver.ConfigMapsDriverName)
225+
err := kubeClient.CoreV1().ConfigMaps(latestRelease.Namespace).Delete(context.TODO(), objectName, metav1.DeleteOptions{})
226+
if err != nil && !errors.IsNotFound(err) {
227+
return fmt.Errorf("couldn't delete configmap %s of release %s: %w", objectName, nsReleaseName, err)
228+
}
229+
h.LogEntry.Debugf("ConfigMap %s was deleted", objectName)
230+
231+
case driver.SecretsDriverName:
232+
h.LogEntry.Debugf("Secret for helm revision %d of release %s in status %s, driver %s: will be deleted", latestRelease.Version, nsReleaseName, latestRelease.Info.Status, driver.SecretsDriverName)
233+
err := kubeClient.CoreV1().Secrets(latestRelease.Namespace).Delete(context.TODO(), objectName, metav1.DeleteOptions{})
234+
if err != nil && !errors.IsNotFound(err) {
235+
return fmt.Errorf("couldn't delete secret %s of release %s: %w", objectName, nsReleaseName, err)
236+
}
237+
h.LogEntry.Debugf("Secret %s was deleted", objectName)
238+
239+
default:
240+
// memory and sql storages a bit more trickier - doing a rollback is justified
241+
h.LogEntry.Debugf("Helm revision %d of release %s in status %s, driver %s: will be rolledback", latestRelease.Version, nsReleaseName, latestRelease.Info.Status, actionConfig.Releases.Name())
242+
h.rollbackLatestRelease(releases)
243+
}
213244
}
214245
}
215246

@@ -271,6 +302,7 @@ func (h *LibClient) DeleteRelease(releaseName string) error {
271302
return fmt.Errorf("helm uninstall %s invocation error: %v\n", releaseName, err)
272303
}
273304

305+
h.LogEntry.Debugf("helm release %s deleted", releaseName)
274306
return nil
275307
}
276308

@@ -288,6 +320,8 @@ func (h *LibClient) IsReleaseExists(releaseName string) (bool, error) {
288320
// ListReleasesNames returns list of release names.
289321
func (h *LibClient) ListReleasesNames() ([]string, error) {
290322
l := action.NewList(actionConfig)
323+
// list all releases regardless of their state
324+
l.StateMask = action.ListAll
291325
list, err := l.Run()
292326
if err != nil {
293327
return nil, fmt.Errorf("helm list failed: %s", err)

pkg/helm_resources_manager/helm_resources_manager.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ type HelmResourcesManager interface {
1818
StopMonitors()
1919
PauseMonitors()
2020
ResumeMonitors()
21-
StartMonitor(moduleName string, manifests []manifest.Manifest, defaultNamespace string)
21+
StartMonitor(moduleName string, manifests []manifest.Manifest, defaultNamespace string, LastReleaseStatus func(releaseName string) (revision string, status string, err error))
2222
HasMonitor(moduleName string) bool
2323
StopMonitor(moduleName string)
2424
PauseMonitor(moduleName string)
2525
ResumeMonitor(moduleName string)
2626
AbsentResources(moduleName string) ([]manifest.Manifest, error)
2727
GetMonitor(moduleName string) *ResourcesMonitor
2828
GetAbsentResources(templates []manifest.Manifest, defaultNamespace string) ([]manifest.Manifest, error)
29-
Ch() chan AbsentResourcesEvent
29+
Ch() chan ReleaseStatusEvent
3030
}
3131

3232
type helmResourcesManager struct {
@@ -39,14 +39,14 @@ type helmResourcesManager struct {
3939

4040
monitors map[string]*ResourcesMonitor
4141

42-
eventCh chan AbsentResourcesEvent
42+
eventCh chan ReleaseStatusEvent
4343
}
4444

4545
var _ HelmResourcesManager = &helmResourcesManager{}
4646

4747
func NewHelmResourcesManager() HelmResourcesManager {
4848
return &helmResourcesManager{
49-
eventCh: make(chan AbsentResourcesEvent),
49+
eventCh: make(chan ReleaseStatusEvent),
5050
monitors: make(map[string]*ResourcesMonitor),
5151
}
5252
}
@@ -69,11 +69,11 @@ func (hm *helmResourcesManager) Stop() {
6969
}
7070
}
7171

72-
func (hm *helmResourcesManager) Ch() chan AbsentResourcesEvent {
72+
func (hm *helmResourcesManager) Ch() chan ReleaseStatusEvent {
7373
return hm.eventCh
7474
}
7575

76-
func (hm *helmResourcesManager) StartMonitor(moduleName string, manifests []manifest.Manifest, defaultNamespace string) {
76+
func (hm *helmResourcesManager) StartMonitor(moduleName string, manifests []manifest.Manifest, defaultNamespace string, lastReleaseStatus func(releaseName string) (revision string, status string, err error)) {
7777
log.Debugf("Start helm resources monitor for '%s'", moduleName)
7878
hm.StopMonitor(moduleName)
7979

@@ -83,20 +83,22 @@ func (hm *helmResourcesManager) StartMonitor(moduleName string, manifests []mani
8383
rm.WithModuleName(moduleName)
8484
rm.WithManifests(manifests)
8585
rm.WithDefaultNamespace(defaultNamespace)
86+
rm.WithStatusGetter(lastReleaseStatus)
8687
rm.WithAbsentCb(hm.absentResourcesCallback)
8788

8889
hm.monitors[moduleName] = rm
8990
rm.Start()
9091
}
9192

92-
func (hm *helmResourcesManager) absentResourcesCallback(moduleName string, absent []manifest.Manifest, defaultNs string) {
93+
func (hm *helmResourcesManager) absentResourcesCallback(moduleName string, unexpectedStatus bool, absent []manifest.Manifest, defaultNs string) {
9394
log.Debugf("Detect absent resources for %s", moduleName)
9495
for _, m := range absent {
9596
log.Debugf("%s/%s/%s", m.Namespace(defaultNs), m.Kind(), m.Name())
9697
}
97-
hm.eventCh <- AbsentResourcesEvent{
98-
ModuleName: moduleName,
99-
Absent: absent,
98+
hm.eventCh <- ReleaseStatusEvent{
99+
ModuleName: moduleName,
100+
Absent: absent,
101+
UnexpectedStatus: unexpectedStatus,
100102
}
101103
}
102104

pkg/helm_resources_manager/resources_monitor.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ type ResourcesMonitor struct {
3131
kubeClient *klient.Client
3232
logLabels map[string]string
3333

34-
absentCb func(moduleName string, absent []manifest.Manifest, defaultNs string)
34+
absentCb func(moduleName string, unexpectedStatus bool, absent []manifest.Manifest, defaultNs string)
35+
36+
helmStatusGetter func(releaseName string) (revision string, status string, err error)
3537
}
3638

3739
func NewResourcesMonitor() *ResourcesMonitor {
@@ -73,11 +75,15 @@ func (r *ResourcesMonitor) WithManifests(manifests []manifest.Manifest) {
7375
r.manifests = manifests
7476
}
7577

76-
func (r *ResourcesMonitor) WithAbsentCb(cb func(string, []manifest.Manifest, string)) {
78+
func (r *ResourcesMonitor) WithAbsentCb(cb func(string, bool, []manifest.Manifest, string)) {
7779
r.absentCb = cb
7880
}
7981

80-
// Start creates a timer and check if all manifests are present in cluster.
82+
func (r *ResourcesMonitor) WithStatusGetter(lastReleaseStatus func(releaseName string) (revision string, status string, err error)) {
83+
r.helmStatusGetter = lastReleaseStatus
84+
}
85+
86+
// Start creates a timer and check if all deployed manifests are present in the cluster.
8187
func (r *ResourcesMonitor) Start() {
8288
logEntry := log.WithFields(utils.LabelsToLogFields(r.logLabels)).
8389
WithField("operator.component", "HelmResourceMonitor")
@@ -92,6 +98,19 @@ func (r *ResourcesMonitor) Start() {
9298
if r.paused {
9399
continue
94100
}
101+
// Check release status
102+
status, err := r.GetHelmReleaseStatus(r.moduleName)
103+
if err != nil {
104+
logEntry.Errorf("Cannot get helm release status: %s", err)
105+
}
106+
107+
if status != "deployed" {
108+
logEntry.Debugf("Helm release %s is in unexpected status: %s", r.moduleName, status)
109+
if r.absentCb != nil {
110+
r.absentCb(r.moduleName, true, []manifest.Manifest{}, r.defaultNamespace)
111+
}
112+
}
113+
95114
// Check resources
96115
absent, err := r.AbsentResources()
97116
if err != nil {
@@ -101,7 +120,7 @@ func (r *ResourcesMonitor) Start() {
101120
if len(absent) > 0 {
102121
logEntry.Debug("Absent resources detected")
103122
if r.absentCb != nil {
104-
r.absentCb(r.moduleName, absent, r.defaultNamespace)
123+
r.absentCb(r.moduleName, false, absent, r.defaultNamespace)
105124
}
106125
} else {
107126
logEntry.Debug("No absent resources detected")
@@ -115,6 +134,18 @@ func (r *ResourcesMonitor) Start() {
115134
}()
116135
}
117136

137+
// GetHelmReleaseStatus returns last release status
138+
func (r *ResourcesMonitor) GetHelmReleaseStatus(moduleName string) (string, error) {
139+
logEntry := log.WithFields(utils.LabelsToLogFields(r.logLabels)).
140+
WithField("operator.component", "HelmResourceMonitor")
141+
revision, status, err := r.helmStatusGetter(moduleName)
142+
if err != nil {
143+
return "", err
144+
}
145+
logEntry.Debugf("Helm release %s, revision %s, status: %s", moduleName, revision, status)
146+
return status, nil
147+
}
148+
118149
// Pause prevent execution of absent callback
119150
func (r *ResourcesMonitor) Pause() {
120151
r.paused = true

pkg/helm_resources_manager/test/mock/mock.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (h *MockHelmResourcesManager) PauseMonitors() {}
2727

2828
func (h *MockHelmResourcesManager) ResumeMonitors() {}
2929

30-
func (h *MockHelmResourcesManager) StartMonitor(_ string, _ []manifest.Manifest, _ string) {
30+
func (h *MockHelmResourcesManager) StartMonitor(_ string, _ []manifest.Manifest, _ string, _ func(string) (revision string, status string, err error)) {
3131
}
3232

3333
func (h *MockHelmResourcesManager) HasMonitor(_ string) bool {
@@ -49,6 +49,6 @@ func (h *MockHelmResourcesManager) GetAbsentResources(_ []manifest.Manifest, _ s
4949
return nil, nil
5050
}
5151

52-
func (h *MockHelmResourcesManager) Ch() chan AbsentResourcesEvent {
52+
func (h *MockHelmResourcesManager) Ch() chan ReleaseStatusEvent {
5353
return nil
5454
}

pkg/helm_resources_manager/types/types.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package types
22

33
import "github.com/flant/kube-client/manifest"
44

5-
type AbsentResourcesEvent struct {
6-
ModuleName string
7-
Absent []manifest.Manifest
5+
type ReleaseStatusEvent struct {
6+
ModuleName string
7+
Absent []manifest.Manifest
8+
UnexpectedStatus bool
89
}

0 commit comments

Comments
 (0)