mirror of
https://github.com/hanzoai/deploy.git
synced 2026-10-07 08:44:50 +00:00
go.mod still declared `module github.com/hanzoai/deploy` after the repo was renamed. A Go module path is not cosmetic — it is the resolvable location. So `go get github.com/hanzoai/cd` could not work, and anything importing this module had to spell a name that no longer exists at that address. The build log of v3.6.0 is where it surfaced: the compiler was still emitting github.com/hanzoai/deploy/cmd/cd/commands. 3541 import paths across 690 .go, 19 .proto, 4 .yaml, 3 .sh, 2 .mod, Procfile and Makefile. Plus the 62 ghcr.io/hanzoai/deploy image references, moved to ghcr.io/hanzoai/cd to match hanzo.yml's build target — a distinct string from the module path, replaced separately so neither could clobber the other. go mod tidy clean, CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./... clean. Co-authored-by: Hanzo Dev <[email protected]>
200 lines
6.9 KiB
Go
200 lines
6.9 KiB
Go
package kube
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
|
|
"github.com/hanzoai/cd/gitops-engine/pkg/utils/kube"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
"github.com/hanzoai/cd/common"
|
|
)
|
|
|
|
var resourceNamePattern = regexp.MustCompile("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$")
|
|
|
|
// IsValidResourceName returns true if given string a valid Kubernetes resource name
|
|
func IsValidResourceName(name string) bool {
|
|
return len(name) < 64 && resourceNamePattern.MatchString(name)
|
|
}
|
|
|
|
// SetAppInstanceLabel the recommended app.kubernetes.io/instance label against an unstructured object
|
|
// Uses the legacy labeling if environment variable is set
|
|
func SetAppInstanceLabel(target *unstructured.Unstructured, key, val string) error {
|
|
labels, err := nestedNullableStringMap(target.Object, "metadata", "labels")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get labels from target object %s %s/%s: %w", target.GroupVersionKind().String(), target.GetNamespace(), target.GetName(), err)
|
|
}
|
|
if labels == nil {
|
|
labels = make(map[string]string)
|
|
}
|
|
labels[key] = val
|
|
target.SetLabels(labels)
|
|
if key != common.LabelKeyLegacyApplicationName {
|
|
// we no longer label the pod template sub resources in v0.11
|
|
return nil
|
|
}
|
|
|
|
gvk := schema.FromAPIVersionAndKind(target.GetAPIVersion(), target.GetKind())
|
|
// special case for deployment and job types: make sure that derived replicaset, and pod has
|
|
// the application label
|
|
switch gvk.Group {
|
|
case "apps", "extensions":
|
|
switch gvk.Kind {
|
|
case kube.DeploymentKind, kube.ReplicaSetKind, kube.StatefulSetKind, kube.DaemonSetKind:
|
|
templateLabels, ok, err := unstructured.NestedMap(target.UnstructuredContent(), "spec", "template", "metadata", "labels")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ok || templateLabels == nil {
|
|
templateLabels = make(map[string]any)
|
|
}
|
|
templateLabels[key] = val
|
|
err = unstructured.SetNestedMap(target.UnstructuredContent(), templateLabels, "spec", "template", "metadata", "labels")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// The following is a workaround for issue #335. In API version extensions/v1beta1 or
|
|
// apps/v1beta1, if a spec omits spec.selector then k8s will default the
|
|
// spec.selector.matchLabels to match spec.template.metadata.labels. This means Hanzo CD
|
|
// labels can potentially make their way into spec.selector.matchLabels, which is a bad
|
|
// thing. The following logic prevents this behavior.
|
|
switch target.GetAPIVersion() {
|
|
case "apps/v1beta1", "extensions/v1beta1":
|
|
selector, _, err := unstructured.NestedMap(target.UnstructuredContent(), "spec", "selector")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(selector) == 0 {
|
|
// If we get here, user did not set spec.selector in their manifest. We do not want
|
|
// our Hanzo CD labels to get defaulted by kubernetes, so we explicitly set the labels
|
|
// for them (minus the Hanzo CD labels).
|
|
delete(templateLabels, key)
|
|
err = unstructured.SetNestedMap(target.UnstructuredContent(), templateLabels, "spec", "selector", "matchLabels")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case "batch":
|
|
if gvk.Kind == kube.JobKind {
|
|
templateLabels, ok, err := unstructured.NestedMap(target.UnstructuredContent(), "spec", "template", "metadata", "labels")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !ok || templateLabels == nil {
|
|
templateLabels = make(map[string]any)
|
|
}
|
|
templateLabels[key] = val
|
|
err = unstructured.SetNestedMap(target.UnstructuredContent(), templateLabels, "spec", "template", "metadata", "labels")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetAppInstanceAnnotation the recommended app.kubernetes.io/instance annotation against an unstructured object
|
|
// Uses the legacy labeling if environment variable is set
|
|
func SetAppInstanceAnnotation(target *unstructured.Unstructured, key, val string) error {
|
|
annotations, err := nestedNullableStringMap(target.Object, "metadata", "annotations")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get annotations from target object %s %s/%s: %w", target.GroupVersionKind().String(), target.GetNamespace(), target.GetName(), err)
|
|
}
|
|
|
|
if annotations == nil {
|
|
annotations = make(map[string]string)
|
|
}
|
|
annotations[key] = val
|
|
target.SetAnnotations(annotations)
|
|
return nil
|
|
}
|
|
|
|
// GetAppInstanceAnnotation returns the application instance name from annotation
|
|
func GetAppInstanceAnnotation(un *unstructured.Unstructured, key string) (string, error) {
|
|
annotations, err := nestedNullableStringMap(un.Object, "metadata", "annotations")
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get annotations from target object %s %s/%s: %w", un.GroupVersionKind().String(), un.GetNamespace(), un.GetName(), err)
|
|
}
|
|
if annotations != nil {
|
|
return annotations[key], nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
// GetAppInstanceLabel returns the application instance name from labels
|
|
func GetAppInstanceLabel(un *unstructured.Unstructured, key string) (string, error) {
|
|
labels, err := nestedNullableStringMap(un.Object, "metadata", "labels")
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get labels for %s %s/%s: %w", un.GroupVersionKind().String(), un.GetNamespace(), un.GetName(), err)
|
|
}
|
|
if labels != nil {
|
|
return labels[key], nil
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
// RemoveLabel removes label with the specified name
|
|
func RemoveLabel(un *unstructured.Unstructured, key string) error {
|
|
labels, err := nestedNullableStringMap(un.Object, "metadata", "labels")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get labels for %s %s/%s: %w", un.GroupVersionKind().String(), un.GetNamespace(), un.GetName(), err)
|
|
}
|
|
if labels == nil {
|
|
return nil
|
|
}
|
|
|
|
for k := range labels {
|
|
if k == key {
|
|
delete(labels, k)
|
|
if len(labels) == 0 {
|
|
un.SetLabels(nil)
|
|
} else {
|
|
un.SetLabels(labels)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveAnnotation removes annotation with the specified name
|
|
func RemoveAnnotation(un *unstructured.Unstructured, key string) error {
|
|
annotations, err := nestedNullableStringMap(un.Object, "metadata", "annotations")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get annotations for %s %s/%s: %w", un.GroupVersionKind().String(), un.GetNamespace(), un.GetName(), err)
|
|
}
|
|
if annotations == nil {
|
|
return nil
|
|
}
|
|
|
|
for k := range annotations {
|
|
if k == key {
|
|
delete(annotations, k)
|
|
if len(annotations) == 0 {
|
|
un.SetAnnotations(nil)
|
|
} else {
|
|
un.SetAnnotations(annotations)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// nestedNullableStringMap returns a copy of map[string]string value of a nested field.
|
|
// Returns false if value is not found and an error if not one of map[string]any or nil, or contains non-string values in the map.
|
|
func nestedNullableStringMap(obj map[string]any, fields ...string) (map[string]string, error) {
|
|
var m map[string]string
|
|
val, found, err := unstructured.NestedFieldNoCopy(obj, fields...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if found && val != nil {
|
|
m, _, err = unstructured.NestedStringMap(obj, fields...)
|
|
}
|
|
return m, err
|
|
}
|