-
Notifications
You must be signed in to change notification settings - Fork 8
/
HelmServiceUtil.go
243 lines (221 loc) · 7.65 KB
/
HelmServiceUtil.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
package util
import (
"encoding/binary"
"errors"
"fmt"
"github.com/davecgh/go-spew/spew"
k8sCommonBean "github.com/devtron-labs/common-lib/utils/k8s/commonBean"
"github.com/devtron-labs/common-lib/utils/k8s/health"
"github.com/devtron-labs/kubelink/bean"
"hash"
"hash/fnv"
"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/storage/driver"
coreV1 "k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/rand"
)
type ExtraNodeInfo struct {
// UpdateRevision is only used for StatefulSets, if not empty, indicates the version of the StatefulSet used to generate Pods in the sequence
UpdateRevision string
ResourceNetworkingInfo *bean.ResourceNetworkingInfo
RolloutCurrentPodHash string
}
// GetAppId returns AppID by logic cluster_id|namespace|release_name
func GetAppId(clusterId int32, release *release.Release) string {
return fmt.Sprintf("%d|%s|%s", clusterId, release.Namespace, release.Name)
}
func GetMessageFromReleaseStatus(releaseStatus release.Status) string {
switch releaseStatus {
case release.StatusUnknown:
return "The release is in an uncertain state"
case release.StatusDeployed:
return "The release has been pushed to Kubernetes"
case release.StatusUninstalled:
return "The release has been uninstalled from Kubernetes"
case release.StatusSuperseded:
return "The release object is outdated and a newer one exists"
case release.StatusFailed:
return "The release was not successfully deployed"
case release.StatusUninstalling:
return "The release uninstall operation is underway"
case release.StatusPendingInstall:
return "The release install operation is underway"
case release.StatusPendingUpgrade:
return "The release upgrade operation is underway"
case release.StatusPendingRollback:
return "The release rollback operation is underway"
default:
fmt.Println("un handled release status", releaseStatus)
}
return ""
}
// app health is worst of the nodes health
// or if app status is healthy then check for hibernation status
func BuildAppHealthStatus(nodes []*bean.ResourceNode) *bean.HealthStatusCode {
appHealthStatus := bean.HealthStatusHealthy
isAppFullyHibernated := true
var isAppPartiallyHibernated bool
var isAnyNodeCanByHibernated bool
for _, node := range nodes {
if node.IsHook {
continue
}
nodeHealth := node.Health
if node.CanBeHibernated {
isAnyNodeCanByHibernated = true
if !node.IsHibernated {
isAppFullyHibernated = false
} else {
isAppPartiallyHibernated = true
}
}
if nodeHealth == nil {
continue
}
if health.IsWorseStatus(health.HealthStatusCode(appHealthStatus), health.HealthStatusCode(nodeHealth.Status)) {
appHealthStatus = nodeHealth.Status
}
}
// override hibernate status on app level if status is healthy and hibernation done
if appHealthStatus == bean.HealthStatusHealthy && isAnyNodeCanByHibernated {
if isAppFullyHibernated {
appHealthStatus = bean.HealthStatusHibernated
} else if isAppPartiallyHibernated {
appHealthStatus = bean.HealthStatusPartiallyHibernated
}
}
return &appHealthStatus
}
func GetAppStatusOnBasisOfHealthyNonHealthy(healthStatusArray []*bean.HealthStatus) *bean.HealthStatusCode {
appHealthStatus := bean.HealthStatusHealthy
for _, node := range healthStatusArray {
nodeHealth := node
if nodeHealth == nil {
continue
}
//if any node's health is worse than healthy then we break the loop and return
if health.IsWorseStatus(health.HealthStatusCode(appHealthStatus), health.HealthStatusCode(nodeHealth.Status)) {
appHealthStatus = nodeHealth.Status
break
}
}
return &appHealthStatus
}
// DeepHashObject writes specified object to hash using the spew library
// which follows pointers and prints actual values of the nested objects
// ensuring the hash does not change when a pointer changes.
func DeepHashObject(hasher hash.Hash, objectToWrite interface{}) {
hasher.Reset()
printer := spew.ConfigState{
Indent: " ",
SortKeys: true,
DisableMethods: true,
SpewKeys: true,
}
_, err := printer.Fprintf(hasher, "%#v", objectToWrite)
if err != nil {
fmt.Println(err)
}
}
func ComputePodHash(template *coreV1.PodTemplateSpec, collisionCount *int32) string {
podTemplateSpecHasher := fnv.New32a()
DeepHashObject(podTemplateSpecHasher, *template)
// Add collisionCount in the hash if it exists.
if collisionCount != nil {
collisionCountBytes := make([]byte, 8)
binary.LittleEndian.PutUint32(collisionCountBytes, uint32(*collisionCount))
_, err := podTemplateSpecHasher.Write(collisionCountBytes)
if err != nil {
fmt.Println(err)
}
}
return rand.SafeEncodeString(fmt.Sprint(podTemplateSpecHasher.Sum32()))
}
func ConvertToV1Deployment(nodeObj map[string]interface{}) (*v1beta1.Deployment, error) {
deploymentObj := v1beta1.Deployment{}
err := runtime.DefaultUnstructuredConverter.FromUnstructured(nodeObj, &deploymentObj)
if err != nil {
return nil, err
}
return &deploymentObj, nil
}
func ConvertToV1ReplicaSet(nodeObj map[string]interface{}) (*v1beta1.ReplicaSet, error) {
replicaSetObj := v1beta1.ReplicaSet{}
err := runtime.DefaultUnstructuredConverter.FromUnstructured(nodeObj, &replicaSetObj)
if err != nil {
return nil, err
}
return &replicaSetObj, nil
}
func GetReplicaSetPodHash(replicasetObj *v1beta1.ReplicaSet, collisionCount *int32) string {
labels := make(map[string]string)
for k, v := range replicasetObj.Spec.Template.Labels {
if k != "pod-template-hash" {
labels[k] = v
}
}
replicasetObj.Spec.Template.Labels = labels
podHash := ComputePodHash(&replicasetObj.Spec.Template, collisionCount)
return podHash
}
func GetRolloutPodTemplateHash(replicasetNode *bean.ResourceNode) string {
if rolloutPodTemplateHash, ok := replicasetNode.NetworkingInfo.Labels["rollouts-pod-template-hash"]; ok {
return rolloutPodTemplateHash
}
return ""
}
func GetRolloutPodHash(rollout map[string]interface{}) string {
if s, ok := rollout["status"]; ok {
if sm, ok := s.(map[string]interface{}); ok {
if cph, ok := sm["currentPodHash"]; ok {
if cphs, ok := cph.(string); ok {
return cphs
}
}
}
}
return ""
}
func GetHookMetadata(manifest *unstructured.Unstructured) (bool, string) {
annotations, found, _ := unstructured.NestedStringMap(manifest.Object, "metadata", "annotations")
if found {
if hookType, ok := annotations[release.HookAnnotation]; ok {
return true, hookType
}
}
return false, ""
}
func AddSelectiveInfoInResourceNode(resourceNode *bean.ResourceNode, gvk schema.GroupVersionKind, obj map[string]interface{}) {
if gvk.Kind == k8sCommonBean.StatefulSetKind {
resourceNode.UpdateRevision = GetUpdateRevisionForStatefulSet(obj)
}
if gvk.Kind == k8sCommonBean.DeploymentKind {
deployment, _ := ConvertToV1Deployment(obj)
if deployment == nil {
return
}
deploymentPodHash := ComputePodHash(&deployment.Spec.Template, deployment.Status.CollisionCount)
resourceNode.DeploymentPodHash = deploymentPodHash
resourceNode.DeploymentCollisionCount = deployment.Status.CollisionCount
}
if gvk.Kind == k8sCommonBean.K8sClusterResourceRolloutKind {
rolloutPodHash, found, _ := unstructured.NestedString(obj, "status", "currentPodHash")
if found {
resourceNode.RolloutCurrentPodHash = rolloutPodHash
}
}
}
func GetUpdateRevisionForStatefulSet(obj map[string]interface{}) string {
updateRevisionFromManifest, found, _ := unstructured.NestedString(obj, "status", "updateRevision")
if found {
return updateRevisionFromManifest
}
return ""
}
func IsReleaseNotFoundError(err error) bool {
return errors.Is(err, driver.ErrReleaseNotFound) || errors.Is(err, driver.ErrNoDeployedReleases)
}