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]>
452 lines
16 KiB
Go
452 lines
16 KiB
Go
package cd
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/hanzoai/cd/util/kube"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gopkg.in/yaml.v2"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
|
|
"github.com/hanzoai/cd/common"
|
|
"github.com/hanzoai/cd/pkg/apis/application/v1alpha1"
|
|
)
|
|
|
|
func TestSetAppInstanceLabel(t *testing.T) {
|
|
t.Parallel()
|
|
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
|
|
require.NoError(t, err)
|
|
|
|
var obj unstructured.Unstructured
|
|
err = yaml.Unmarshal(yamlBytes, &obj)
|
|
require.NoError(t, err)
|
|
|
|
resourceTracking := NewResourceTracking()
|
|
|
|
err = resourceTracking.SetAppInstance(&obj, common.LabelKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodLabel, "")
|
|
require.NoError(t, err)
|
|
app := resourceTracking.GetAppName(&obj, common.LabelKeyAppInstance, v1alpha1.TrackingMethodLabel, "")
|
|
assert.Equal(t, "my-app", app)
|
|
}
|
|
|
|
func TestSetAppInstanceAnnotation(t *testing.T) {
|
|
t.Parallel()
|
|
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
|
|
require.NoError(t, err)
|
|
|
|
var obj unstructured.Unstructured
|
|
err = yaml.Unmarshal(yamlBytes, &obj)
|
|
require.NoError(t, err)
|
|
|
|
resourceTracking := NewResourceTracking()
|
|
|
|
err = resourceTracking.SetAppInstance(&obj, common.AnnotationKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodAnnotation, "")
|
|
require.NoError(t, err)
|
|
|
|
app := resourceTracking.GetAppName(&obj, common.AnnotationKeyAppInstance, v1alpha1.TrackingMethodAnnotation, "")
|
|
assert.Equal(t, "my-app", app)
|
|
}
|
|
|
|
func TestSetAppInstanceAnnotationAndLabel(t *testing.T) {
|
|
t.Parallel()
|
|
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
|
|
require.NoError(t, err)
|
|
var obj unstructured.Unstructured
|
|
err = yaml.Unmarshal(yamlBytes, &obj)
|
|
require.NoError(t, err)
|
|
|
|
resourceTracking := NewResourceTracking()
|
|
|
|
err = resourceTracking.SetAppInstance(&obj, common.LabelKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodAnnotationAndLabel, "")
|
|
require.NoError(t, err)
|
|
|
|
app := resourceTracking.GetAppName(&obj, common.LabelKeyAppInstance, v1alpha1.TrackingMethodAnnotationAndLabel, "")
|
|
assert.Equal(t, "my-app", app)
|
|
}
|
|
|
|
func TestSetAppInstanceAnnotationAndLabelLongName(t *testing.T) {
|
|
t.Parallel()
|
|
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
|
|
require.NoError(t, err)
|
|
var obj unstructured.Unstructured
|
|
err = yaml.Unmarshal(yamlBytes, &obj)
|
|
require.NoError(t, err)
|
|
|
|
resourceTracking := NewResourceTracking()
|
|
|
|
err = resourceTracking.SetAppInstance(&obj, common.LabelKeyAppInstance, "my-app-with-an-extremely-long-name-that-is-over-sixty-three-characters", "", v1alpha1.TrackingMethodAnnotationAndLabel, "")
|
|
require.NoError(t, err)
|
|
|
|
// the annotation should still work, so the name from GetAppName should not be truncated
|
|
app := resourceTracking.GetAppName(&obj, common.LabelKeyAppInstance, v1alpha1.TrackingMethodAnnotationAndLabel, "")
|
|
assert.Equal(t, "my-app-with-an-extremely-long-name-that-is-over-sixty-three-characters", app)
|
|
|
|
// the label should be truncated to 63 characters
|
|
assert.Equal(t, "my-app-with-an-extremely-long-name-that-is-over-sixty-three-cha", obj.GetLabels()[common.LabelKeyAppInstance])
|
|
}
|
|
|
|
func TestSetAppInstanceAnnotationAndLabelLongNameBadEnding(t *testing.T) {
|
|
t.Parallel()
|
|
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
|
|
require.NoError(t, err)
|
|
var obj unstructured.Unstructured
|
|
err = yaml.Unmarshal(yamlBytes, &obj)
|
|
require.NoError(t, err)
|
|
|
|
resourceTracking := NewResourceTracking()
|
|
|
|
err = resourceTracking.SetAppInstance(&obj, common.LabelKeyAppInstance, "the-very-suspicious-name-with-precisely-sixty-three-characters-with-hyphen", "", v1alpha1.TrackingMethodAnnotationAndLabel, "")
|
|
require.NoError(t, err)
|
|
|
|
// the annotation should still work, so the name from GetAppName should not be truncated
|
|
app := resourceTracking.GetAppName(&obj, common.LabelKeyAppInstance, v1alpha1.TrackingMethodAnnotationAndLabel, "")
|
|
assert.Equal(t, "the-very-suspicious-name-with-precisely-sixty-three-characters-with-hyphen", app)
|
|
|
|
// the label should be truncated to 63 characters, AND the hyphen should be removed
|
|
assert.Equal(t, "the-very-suspicious-name-with-precisely-sixty-three-characters", obj.GetLabels()[common.LabelKeyAppInstance])
|
|
}
|
|
|
|
func TestSetAppInstanceAnnotationAndLabelOutOfBounds(t *testing.T) {
|
|
t.Parallel()
|
|
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
|
|
require.NoError(t, err)
|
|
var obj unstructured.Unstructured
|
|
err = yaml.Unmarshal(yamlBytes, &obj)
|
|
require.NoError(t, err)
|
|
|
|
resourceTracking := NewResourceTracking()
|
|
|
|
err = resourceTracking.SetAppInstance(&obj, common.LabelKeyAppInstance, "----------------------------------------------------------------", "", v1alpha1.TrackingMethodAnnotationAndLabel, "")
|
|
// this should error because it can't truncate to a valid value
|
|
assert.EqualError(t, err, "failed to set app instance label: unable to truncate label to not end with a special character")
|
|
}
|
|
|
|
func TestTruncateLabel_Short(t *testing.T) {
|
|
t.Parallel()
|
|
got, err := TruncateLabel("my-app")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "my-app", got)
|
|
}
|
|
|
|
func TestTruncateLabel_AtLimit(t *testing.T) {
|
|
t.Parallel()
|
|
val := "the-very-suspicious-name-with-precisely-sixty-three-characters0"
|
|
require.Len(t, val, LabelMaxLength)
|
|
got, err := TruncateLabel(val)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, val, got)
|
|
}
|
|
|
|
func TestTruncateLabel_LongName(t *testing.T) {
|
|
t.Parallel()
|
|
got, err := TruncateLabel("my-app-with-an-extremely-long-name-that-is-over-sixty-three-characters")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "my-app-with-an-extremely-long-name-that-is-over-sixty-three-cha", got)
|
|
assert.LessOrEqual(t, len(got), LabelMaxLength)
|
|
}
|
|
|
|
func TestTruncateLabel_TrailingSpecialCharsStripped(t *testing.T) {
|
|
t.Parallel()
|
|
got, err := TruncateLabel("the-very-suspicious-name-with-precisely-sixty-three-characters-with-hyphen")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "the-very-suspicious-name-with-precisely-sixty-three-characters", got)
|
|
}
|
|
|
|
func TestTruncateLabel_AllSpecialChars(t *testing.T) {
|
|
t.Parallel()
|
|
_, err := TruncateLabel("----------------------------------------------------------------")
|
|
assert.EqualError(t, err, "unable to truncate label to not end with a special character")
|
|
}
|
|
|
|
func TestRemoveAppInstance_LabelOnly(t *testing.T) {
|
|
t.Parallel()
|
|
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
|
|
require.NoError(t, err)
|
|
var obj unstructured.Unstructured
|
|
err = yaml.Unmarshal(yamlBytes, &obj)
|
|
require.NoError(t, err)
|
|
|
|
rt := NewResourceTracking()
|
|
|
|
err = rt.SetAppInstance(&obj, common.LabelKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodLabel, "")
|
|
require.NoError(t, err)
|
|
|
|
err = rt.RemoveAppInstance(&obj, string(v1alpha1.TrackingMethodLabel))
|
|
require.NoError(t, err)
|
|
|
|
_, exists := obj.GetLabels()[common.LabelKeyAppInstance]
|
|
assert.False(t, exists)
|
|
}
|
|
|
|
func TestRemoveAppInstance_AnnotationOnly(t *testing.T) {
|
|
t.Parallel()
|
|
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
|
|
require.NoError(t, err)
|
|
|
|
var obj unstructured.Unstructured
|
|
err = yaml.Unmarshal(yamlBytes, &obj)
|
|
require.NoError(t, err)
|
|
|
|
rt := NewResourceTracking()
|
|
|
|
err = rt.SetAppInstance(&obj, common.AnnotationKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodAnnotation, "")
|
|
require.NoError(t, err)
|
|
|
|
err = rt.RemoveAppInstance(&obj, string(v1alpha1.TrackingMethodAnnotation))
|
|
require.NoError(t, err)
|
|
|
|
annotations := obj.GetAnnotations()
|
|
assert.NotContains(t, annotations, common.AnnotationKeyAppInstance)
|
|
assert.NotContains(t, annotations, common.AnnotationInstallationID)
|
|
assert.NotContains(t, annotations, v1alpha1.TrackingMethodAnnotation)
|
|
}
|
|
|
|
func TestRemoveAppInstance_AnnotationAndLabel(t *testing.T) {
|
|
t.Parallel()
|
|
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
|
|
require.NoError(t, err)
|
|
|
|
var obj unstructured.Unstructured
|
|
err = yaml.Unmarshal(yamlBytes, &obj)
|
|
require.NoError(t, err)
|
|
|
|
rt := NewResourceTracking()
|
|
|
|
err = rt.SetAppInstance(&obj, common.LabelKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodAnnotationAndLabel, "")
|
|
require.NoError(t, err)
|
|
|
|
err = rt.RemoveAppInstance(&obj, string(v1alpha1.TrackingMethodAnnotationAndLabel))
|
|
require.NoError(t, err)
|
|
|
|
assert.NotContains(t, obj.GetAnnotations(), common.AnnotationKeyAppInstance)
|
|
assert.NotContains(t, obj.GetAnnotations(), common.AnnotationInstallationID)
|
|
assert.NotContains(t, obj.GetLabels(), common.LabelKeyAppInstance)
|
|
}
|
|
|
|
func TestRemoveAppInstance_DefaultCase(t *testing.T) {
|
|
t.Parallel()
|
|
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
|
|
require.NoError(t, err)
|
|
|
|
var obj unstructured.Unstructured
|
|
err = yaml.Unmarshal(yamlBytes, &obj)
|
|
require.NoError(t, err)
|
|
|
|
// Add a label manually to verify if this custom label exists at the end
|
|
obj.SetLabels(map[string]string{
|
|
"my-custom-label": "keep-me",
|
|
})
|
|
|
|
rt := NewResourceTracking()
|
|
|
|
err = rt.SetAppInstance(&obj, common.AnnotationKeyAppInstance, "my-app", "", "", "")
|
|
require.NoError(t, err)
|
|
|
|
err = rt.RemoveAppInstance(&obj, "unknown-method")
|
|
require.NoError(t, err)
|
|
|
|
assert.NotContains(t, obj.GetAnnotations(), common.AnnotationKeyAppInstance)
|
|
assert.NotContains(t, obj.GetAnnotations(), common.AnnotationInstallationID)
|
|
|
|
// Argo CD app-instance label was never added, so it shouldn't exist
|
|
_, cdLabelExists := obj.GetLabels()[common.LabelKeyAppInstance]
|
|
assert.False(t, cdLabelExists)
|
|
// Custom label should still exist
|
|
assert.Equal(t, "keep-me", obj.GetLabels()["my-custom-label"])
|
|
}
|
|
|
|
func TestRemoveAppInstance_AnnotationAndLabel_LongName(t *testing.T) {
|
|
t.Parallel()
|
|
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
|
|
require.NoError(t, err)
|
|
|
|
var obj unstructured.Unstructured
|
|
err = yaml.Unmarshal(yamlBytes, &obj)
|
|
require.NoError(t, err)
|
|
|
|
rt := NewResourceTracking()
|
|
|
|
longName := "my-app-with-an-extremely-long-name-that-is-over-sixty-three-characters"
|
|
err = rt.SetAppInstance(&obj, common.LabelKeyAppInstance, longName, "", v1alpha1.TrackingMethodAnnotationAndLabel, "")
|
|
require.NoError(t, err)
|
|
|
|
err = rt.RemoveAppInstance(&obj, string(v1alpha1.TrackingMethodAnnotationAndLabel))
|
|
require.NoError(t, err)
|
|
|
|
assert.NotContains(t, obj.GetAnnotations(), common.AnnotationKeyAppInstance)
|
|
assert.NotContains(t, obj.GetLabels(), common.LabelKeyAppInstance)
|
|
}
|
|
|
|
func TestSetAppInstanceAnnotationNotFound(t *testing.T) {
|
|
t.Parallel()
|
|
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
|
|
require.NoError(t, err)
|
|
|
|
var obj unstructured.Unstructured
|
|
err = yaml.Unmarshal(yamlBytes, &obj)
|
|
require.NoError(t, err)
|
|
|
|
resourceTracking := NewResourceTracking()
|
|
|
|
app := resourceTracking.GetAppName(&obj, common.LabelKeyAppInstance, v1alpha1.TrackingMethodAnnotation, "")
|
|
assert.Empty(t, app)
|
|
}
|
|
|
|
func TestParseAppInstanceValue(t *testing.T) {
|
|
t.Parallel()
|
|
resourceTracking := NewResourceTracking()
|
|
appInstanceValue, err := resourceTracking.ParseAppInstanceValue("app:<group>/<kind>:<namespace>/<name>")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "app", appInstanceValue.ApplicationName)
|
|
assert.Equal(t, "<group>", appInstanceValue.Group)
|
|
assert.Equal(t, "<kind>", appInstanceValue.Kind)
|
|
assert.Equal(t, "<namespace>", appInstanceValue.Namespace)
|
|
assert.Equal(t, "<name>", appInstanceValue.Name)
|
|
}
|
|
|
|
func TestParseAppInstanceValueColon(t *testing.T) {
|
|
t.Parallel()
|
|
resourceTracking := NewResourceTracking()
|
|
appInstanceValue, err := resourceTracking.ParseAppInstanceValue("app:<group>/<kind>:<namespace>/<name>:<colon>")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "app", appInstanceValue.ApplicationName)
|
|
assert.Equal(t, "<group>", appInstanceValue.Group)
|
|
assert.Equal(t, "<kind>", appInstanceValue.Kind)
|
|
assert.Equal(t, "<namespace>", appInstanceValue.Namespace)
|
|
assert.Equal(t, "<name>:<colon>", appInstanceValue.Name)
|
|
}
|
|
|
|
func TestParseAppInstanceValueWrongFormat1(t *testing.T) {
|
|
t.Parallel()
|
|
resourceTracking := NewResourceTracking()
|
|
_, err := resourceTracking.ParseAppInstanceValue("app")
|
|
require.ErrorIs(t, err, ErrWrongResourceTrackingFormat)
|
|
}
|
|
|
|
func TestParseAppInstanceValueWrongFormat2(t *testing.T) {
|
|
t.Parallel()
|
|
resourceTracking := NewResourceTracking()
|
|
_, err := resourceTracking.ParseAppInstanceValue("app;group/kind/ns")
|
|
require.ErrorIs(t, err, ErrWrongResourceTrackingFormat)
|
|
}
|
|
|
|
func TestParseAppInstanceValueCorrectFormat(t *testing.T) {
|
|
t.Parallel()
|
|
resourceTracking := NewResourceTracking()
|
|
_, err := resourceTracking.ParseAppInstanceValue("app:group/kind:test/ns")
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func sampleResource(t *testing.T) *unstructured.Unstructured {
|
|
t.Helper()
|
|
yamlBytes, err := os.ReadFile("testdata/svc.yaml")
|
|
require.NoError(t, err)
|
|
var obj *unstructured.Unstructured
|
|
err = yaml.Unmarshal(yamlBytes, &obj)
|
|
require.NoError(t, err)
|
|
return obj
|
|
}
|
|
|
|
func TestResourceIdNormalizer_Normalize(t *testing.T) {
|
|
t.Parallel()
|
|
rt := NewResourceTracking()
|
|
|
|
// live object is a resource that has old style tracking label
|
|
liveObj := sampleResource(t)
|
|
err := rt.SetAppInstance(liveObj, common.LabelKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodLabel, "")
|
|
require.NoError(t, err)
|
|
|
|
// config object is a resource that has new style tracking annotation
|
|
configObj := sampleResource(t)
|
|
err = rt.SetAppInstance(configObj, common.AnnotationKeyAppInstance, "my-app2", "", v1alpha1.TrackingMethodAnnotation, "")
|
|
require.NoError(t, err)
|
|
|
|
_ = rt.Normalize(configObj, liveObj, common.LabelKeyAppInstance, string(v1alpha1.TrackingMethodAnnotation))
|
|
|
|
// the normalization should affect add the new style annotation and drop old tracking label from live object
|
|
annotation, err := kube.GetAppInstanceAnnotation(configObj, common.AnnotationKeyAppInstance)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, liveObj.GetAnnotations()[common.AnnotationKeyAppInstance], annotation)
|
|
_, hasOldLabel := liveObj.GetLabels()[common.LabelKeyAppInstance]
|
|
assert.False(t, hasOldLabel)
|
|
}
|
|
|
|
func TestResourceIdNormalizer_NormalizeCRD(t *testing.T) {
|
|
t.Parallel()
|
|
rt := NewResourceTracking()
|
|
|
|
// live object is a CRD resource
|
|
liveObj := &unstructured.Unstructured{
|
|
Object: map[string]any{
|
|
"apiVersion": "apiextensions.k8s.io/v1",
|
|
"kind": "CustomResourceDefinition",
|
|
"metadata": map[string]any{
|
|
"name": "crontabs.stable.example.com",
|
|
"labels": map[string]any{
|
|
common.LabelKeyAppInstance: "my-app",
|
|
},
|
|
},
|
|
"spec": map[string]any{
|
|
"group": "stable.example.com",
|
|
"scope": "Namespaced",
|
|
},
|
|
},
|
|
}
|
|
|
|
// config object is a CRD resource
|
|
configObj := &unstructured.Unstructured{
|
|
Object: map[string]any{
|
|
"apiVersion": "apiextensions.k8s.io/v1",
|
|
"kind": "CustomResourceDefinition",
|
|
"metadata": map[string]any{
|
|
"name": "crontabs.stable.example.com",
|
|
"labels": map[string]any{
|
|
common.LabelKeyAppInstance: "my-app",
|
|
},
|
|
},
|
|
"spec": map[string]any{
|
|
"group": "stable.example.com",
|
|
"scope": "Namespaced",
|
|
},
|
|
},
|
|
}
|
|
|
|
require.NoError(t, rt.Normalize(configObj, liveObj, common.LabelKeyAppInstance, string(v1alpha1.TrackingMethodAnnotation)))
|
|
// the normalization should not apply any changes to the live object
|
|
require.NotContains(t, liveObj.GetAnnotations(), common.AnnotationKeyAppInstance)
|
|
}
|
|
|
|
func TestResourceIdNormalizer_Normalize_ConfigHasOldLabel(t *testing.T) {
|
|
t.Parallel()
|
|
rt := NewResourceTracking()
|
|
|
|
// live object is a resource that has old style tracking label
|
|
liveObj := sampleResource(t)
|
|
err := rt.SetAppInstance(liveObj, common.LabelKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodLabel, "")
|
|
require.NoError(t, err)
|
|
|
|
// config object is a resource that has new style tracking annotation
|
|
configObj := sampleResource(t)
|
|
err = rt.SetAppInstance(configObj, common.AnnotationKeyAppInstance, "my-app2", "", v1alpha1.TrackingMethodAnnotation, "")
|
|
require.NoError(t, err)
|
|
err = rt.SetAppInstance(configObj, common.LabelKeyAppInstance, "my-app", "", v1alpha1.TrackingMethodLabel, "")
|
|
require.NoError(t, err)
|
|
|
|
_ = rt.Normalize(configObj, liveObj, common.LabelKeyAppInstance, string(v1alpha1.TrackingMethodAnnotation))
|
|
|
|
// the normalization should affect add the new style annotation and drop old tracking label from live object
|
|
annotation, err := kube.GetAppInstanceAnnotation(configObj, common.AnnotationKeyAppInstance)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, liveObj.GetAnnotations()[common.AnnotationKeyAppInstance], annotation)
|
|
_, hasOldLabel := liveObj.GetLabels()[common.LabelKeyAppInstance]
|
|
assert.True(t, hasOldLabel)
|
|
}
|
|
|
|
func TestIsOldTrackingMethod(t *testing.T) {
|
|
t.Parallel()
|
|
assert.True(t, IsOldTrackingMethod(string(v1alpha1.TrackingMethodLabel)))
|
|
}
|