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]>
203 lines
7.7 KiB
Go
203 lines
7.7 KiB
Go
package managedfields_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
arv1 "k8s.io/api/admissionregistration/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"sigs.k8s.io/yaml"
|
|
|
|
"github.com/hanzoai/cd/gitops-engine/pkg/utils/kube/scheme"
|
|
|
|
"github.com/hanzoai/cd/util/cd/managedfields"
|
|
"github.com/hanzoai/cd/util/cd/testdata"
|
|
)
|
|
|
|
func TestNormalize(t *testing.T) {
|
|
t.Parallel()
|
|
parser := scheme.StaticParser()
|
|
t.Run("will remove conflicting fields if managed by trusted managers", func(t *testing.T) {
|
|
// given
|
|
t.Parallel()
|
|
desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
|
|
liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
|
|
trustedManagers := []string{"kube-controller-manager", "revision-history-manager"}
|
|
pt := parser.Type("io.k8s.api.apps.v1.Deployment")
|
|
|
|
// when
|
|
liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers, &pt)
|
|
|
|
// then
|
|
require.NoError(t, err)
|
|
require.NotNil(t, liveResult)
|
|
require.NotNil(t, desiredResult)
|
|
desiredReplicas, ok, err := unstructured.NestedFloat64(desiredResult.Object, "spec", "replicas")
|
|
assert.False(t, ok)
|
|
require.NoError(t, err)
|
|
liveReplicas, ok, err := unstructured.NestedFloat64(liveResult.Object, "spec", "replicas")
|
|
assert.False(t, ok)
|
|
require.NoError(t, err)
|
|
assert.Zero(t, desiredReplicas)
|
|
assert.Zero(t, liveReplicas)
|
|
liveRevisionHistory, ok, err := unstructured.NestedFloat64(liveResult.Object, "spec", "revisionHistoryLimit")
|
|
assert.False(t, ok)
|
|
require.NoError(t, err)
|
|
desiredRevisionHistory, ok, err := unstructured.NestedFloat64(desiredResult.Object, "spec", "revisionHistoryLimit")
|
|
assert.False(t, ok)
|
|
require.NoError(t, err)
|
|
assert.Zero(t, desiredRevisionHistory)
|
|
assert.Zero(t, liveRevisionHistory)
|
|
})
|
|
t.Run("will keep conflicting fields if not from trusted manager", func(t *testing.T) {
|
|
// given
|
|
t.Parallel()
|
|
desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
|
|
liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
|
|
trustedManagers := []string{"another-manager"}
|
|
pt := parser.Type("io.k8s.api.apps.v1.Deployment")
|
|
|
|
// when
|
|
liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers, &pt)
|
|
|
|
// then
|
|
require.NoError(t, err)
|
|
validateNestedFloat64(t, float64(3), desiredResult, "spec", "replicas")
|
|
validateNestedFloat64(t, float64(1), desiredResult, "spec", "revisionHistoryLimit")
|
|
validateNestedFloat64(t, float64(2), liveResult, "spec", "replicas")
|
|
validateNestedFloat64(t, float64(3), liveResult, "spec", "revisionHistoryLimit")
|
|
})
|
|
t.Run("no-op if live state is nil", func(t *testing.T) {
|
|
// given
|
|
t.Parallel()
|
|
desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
|
|
trustedManagers := []string{"kube-controller-manager"}
|
|
pt := parser.Type("io.k8s.api.apps.v1.Deployment")
|
|
|
|
// when
|
|
liveResult, desiredResult, err := managedfields.Normalize(nil, desiredState, trustedManagers, &pt)
|
|
|
|
// then
|
|
require.NoError(t, err)
|
|
assert.Nil(t, liveResult)
|
|
assert.Nil(t, desiredResult)
|
|
validateNestedFloat64(t, float64(3), desiredState, "spec", "replicas")
|
|
validateNestedFloat64(t, float64(1), desiredState, "spec", "revisionHistoryLimit")
|
|
})
|
|
t.Run("no-op if desired state is nil", func(t *testing.T) {
|
|
// given
|
|
t.Parallel()
|
|
liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
|
|
trustedManagers := []string{"kube-controller-manager"}
|
|
pt := parser.Type("io.k8s.api.apps.v1.Deployment")
|
|
|
|
// when
|
|
liveResult, desiredResult, err := managedfields.Normalize(liveState, nil, trustedManagers, &pt)
|
|
|
|
// then
|
|
require.NoError(t, err)
|
|
assert.Nil(t, liveResult)
|
|
assert.Nil(t, desiredResult)
|
|
validateNestedFloat64(t, float64(2), liveState, "spec", "replicas")
|
|
validateNestedFloat64(t, float64(3), liveState, "spec", "revisionHistoryLimit")
|
|
})
|
|
t.Run("no-op if trusted manager list is empty", func(t *testing.T) {
|
|
// given
|
|
t.Parallel()
|
|
desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
|
|
liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
|
|
pt := parser.Type("io.k8s.api.apps.v1.Deployment")
|
|
|
|
// when
|
|
liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, []string{}, &pt)
|
|
|
|
// then
|
|
require.NoError(t, err)
|
|
assert.Nil(t, liveResult)
|
|
assert.Nil(t, desiredResult)
|
|
validateNestedFloat64(t, float64(3), desiredState, "spec", "replicas")
|
|
validateNestedFloat64(t, float64(1), desiredState, "spec", "revisionHistoryLimit")
|
|
validateNestedFloat64(t, float64(2), liveState, "spec", "replicas")
|
|
validateNestedFloat64(t, float64(3), liveState, "spec", "revisionHistoryLimit")
|
|
})
|
|
t.Run("will normalize successfully inside a list", func(t *testing.T) {
|
|
// given
|
|
t.Parallel()
|
|
desiredState := StrToUnstructured(testdata.DesiredValidatingWebhookYaml)
|
|
liveState := StrToUnstructured(testdata.LiveValidatingWebhookYaml)
|
|
trustedManagers := []string{"external-secrets"}
|
|
pt := parser.Type("io.k8s.api.admissionregistration.v1.ValidatingWebhookConfiguration")
|
|
|
|
// when
|
|
liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers, &pt)
|
|
|
|
// then
|
|
require.NoError(t, err)
|
|
require.NotNil(t, liveResult)
|
|
require.NotNil(t, desiredResult)
|
|
|
|
var vwcLive arv1.ValidatingWebhookConfiguration
|
|
err = runtime.DefaultUnstructuredConverter.FromUnstructured(liveResult.Object, &vwcLive)
|
|
require.NoError(t, err)
|
|
assert.Len(t, vwcLive.Webhooks, 1)
|
|
assert.Empty(t, string(vwcLive.Webhooks[0].ClientConfig.CABundle))
|
|
|
|
var vwcConfig arv1.ValidatingWebhookConfiguration
|
|
err = runtime.DefaultUnstructuredConverter.FromUnstructured(desiredResult.Object, &vwcConfig)
|
|
require.NoError(t, err)
|
|
assert.Len(t, vwcConfig.Webhooks, 1)
|
|
assert.Empty(t, string(vwcConfig.Webhooks[0].ClientConfig.CABundle))
|
|
})
|
|
t.Run("does not fail if object fails validation schema", func(t *testing.T) {
|
|
t.Parallel()
|
|
desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
|
|
require.NoError(t, unstructured.SetNestedField(desiredState.Object, "spec", "hello", "world"))
|
|
liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
|
|
|
|
pt := parser.Type("io.k8s.api.apps.v1.Deployment")
|
|
|
|
_, _, err := managedfields.Normalize(liveState, desiredState, []string{}, &pt)
|
|
require.NoError(t, err)
|
|
})
|
|
t.Run("does not panic when pt is nil", func(t *testing.T) {
|
|
// ResolveParseableType can return nil when a CRD's GVK isn't in the
|
|
// cluster's openapi schema; Normalize must fall back to the deduced
|
|
// type rather than dereferencing nil.
|
|
t.Parallel()
|
|
desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
|
|
liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
|
|
trustedManagers := []string{"kube-controller-manager"}
|
|
|
|
liveResult, desiredResult, err := managedfields.Normalize(liveState, desiredState, trustedManagers, nil)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, liveResult)
|
|
require.NotNil(t, desiredResult)
|
|
})
|
|
}
|
|
|
|
func validateNestedFloat64(t *testing.T, expected float64, obj *unstructured.Unstructured, fields ...string) {
|
|
t.Helper()
|
|
current := getNestedFloat64(t, obj, fields...)
|
|
assert.InEpsilon(t, expected, current, 0.0001)
|
|
}
|
|
|
|
func getNestedFloat64(t *testing.T, obj *unstructured.Unstructured, fields ...string) float64 {
|
|
t.Helper()
|
|
current, ok, err := unstructured.NestedFloat64(obj.Object, fields...)
|
|
assert.True(t, ok, "nested field not found")
|
|
require.NoError(t, err)
|
|
return current
|
|
}
|
|
|
|
func StrToUnstructured(jsonStr string) *unstructured.Unstructured {
|
|
obj := make(map[string]any)
|
|
err := yaml.Unmarshal([]byte(jsonStr), &obj)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return &unstructured.Unstructured{Object: obj}
|
|
}
|