diff --git a/cmd/ai_sync.go b/cmd/ai_sync.go index cddbacdfd..39cdd6489 100644 --- a/cmd/ai_sync.go +++ b/cmd/ai_sync.go @@ -36,8 +36,11 @@ func executeAiSync(cmd *cobra.Command, _ []string) error { injectmanagedByAIDeckTag(targetContent) + // No on-disk decK state file to mock-parse here — targetContent is + // converted from AI Gateway source files via ai2kong, a different format + // entirely. return syncContent(ctx, targetContent, false, aiSyncParallelism, 0, - aiSyncWorkspace, aiSyncJSONOutput, ApplyTypeFull) + aiSyncWorkspace, aiSyncJSONOutput, ApplyTypeFull, nil) } // buildAiSyncTargetContent reads every AI Gateway source referenced by diff --git a/cmd/common.go b/cmd/common.go index b390c4cd9..b7e375d13 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -204,7 +204,27 @@ func syncMain(ctx context.Context, filenames []string, dry bool, parallelism, if err != nil { return err } - return syncContent(ctx, targetContent, dry, parallelism, delay, workspace, enableJSONOutput, applyType) + + // Build the per-entity secret field map from a mock-rendered parse of the + // same files (env var references left as their bare name instead of the + // real value). This lets diff masking target exactly the fields that were + // actually templated, rather than scanning values. Skipped entirely when + // --no-mask-values is set, since masking won't run anyway. + var secretMap file.SecretMap + if !noMaskValues { + mockContent, err := file.GetMockContentFromFiles(filenames, file.EnvVarsSkip) + if err != nil { + _ = fmt.Errorf("error getting mock content for secret field detection: %w", err) + secretMap = nil + } else { + secretMap = file.BuildSecretMap(mockContent) + } + + fmt.Println(secretMap) + + } + + return syncContent(ctx, targetContent, dry, parallelism, delay, workspace, enableJSONOutput, applyType, secretMap) } // initJSONOutput resets the shared JSON output report at the start of a command. @@ -229,8 +249,13 @@ func initJSONOutput() { // reused by commands (such as `ai sync`) that build their target content // in-memory rather than reading it from files. Callers are responsible for // calling initJSONOutput when JSON output is enabled. +// +// secretMap is optional (nil is fine) — callers that don't have an +// on-disk state file to mock-parse (e.g. `ai sync`, whose target content is +// converted from a different source format) simply pass nil, and diff +// masking falls back to value-based masking for every entity. func syncContent(ctx context.Context, targetContent *file.Content, dry bool, parallelism, - delay int, workspace string, enableJSONOutput bool, applyType ApplyType, + delay int, workspace string, enableJSONOutput bool, applyType ApplyType, secretMap file.SecretMap, ) error { err := validateSkipConsumersWithLookupTags(targetContent) if err != nil { @@ -518,7 +543,7 @@ func syncContent(ctx context.Context, targetContent *file.Content, dry bool, par totalOps, err := performDiff( ctx, currentState, targetState, dry, parallelism, delay, kongClient, mode == modeKonnect, - enableJSONOutput, applyType, + enableJSONOutput, applyType, secretMap, ) if err != nil { if enableJSONOutput { @@ -793,7 +818,7 @@ func isAIGatewayInstance(ctx context.Context, client *kong.Client) (bool, error) func performDiff(ctx context.Context, currentState, targetState *state.KongState, dry bool, parallelism int, delay int, client *kong.Client, isKonnect bool, - enableJSONOutput bool, applyType ApplyType, + enableJSONOutput bool, applyType ApplyType, secretMap file.SecretMap, ) (int, error) { shouldSkipDeletes := applyType == ApplyTypePartial @@ -811,6 +836,7 @@ func performDiff(ctx context.Context, currentState, targetState *state.KongState if err != nil { return 0, err } + s.SetSecretMap(secretMap) stats, errs, changes := s.Solve(ctx, parallelism, dry, enableJSONOutput) totalOps := stats.CreateOps.Count() + stats.UpdateOps.Count() + stats.DeleteOps.Count() diff --git a/cmd/common_test.go b/cmd/common_test.go index a668e2be7..976363b0e 100644 --- a/cmd/common_test.go +++ b/cmd/common_test.go @@ -145,6 +145,7 @@ func TestPerformDiff_JSONOutput(t *testing.T) { false, // isKonnect true, // enabled Json output ApplyTypeFull, + nil, // secretMap (not needed for this test) ) require.NoError(t, err) diff --git a/cmd/gateway_reset.go b/cmd/gateway_reset.go index 16e5a8be0..3f92b2eae 100644 --- a/cmd/gateway_reset.go +++ b/cmd/gateway_reset.go @@ -167,7 +167,9 @@ func performReset(ctx context.Context, workspaces []string, isKonnect bool) erro return err } // Perform the diff/reset - _, err = performDiff(ctx, currentState, targetState, false, 10, 0, client, isKonnect, resetJSONOutput, ApplyTypeFull) + // gateway reset has no state file — target is an empty new state, so + // there is nothing to mock-parse; secretMap is nil. + _, err = performDiff(ctx, currentState, targetState, false, 10, 0, client, isKonnect, resetJSONOutput, ApplyTypeFull, nil) if err != nil { return fmt.Errorf("resetting workspace '%s': %w", ws, err) } diff --git a/go.mod b/go.mod index 335ef7b9c..013aa09fd 100644 --- a/go.mod +++ b/go.mod @@ -15,8 +15,9 @@ require ( github.com/ettle/strcase v0.2.0 github.com/fatih/color v1.19.0 github.com/google/go-cmp v0.7.0 + github.com/google/uuid v1.6.0 github.com/kong/go-apiops v0.4.5 - github.com/kong/go-database-reconciler v1.42.1 + github.com/kong/go-database-reconciler v1.42.1-0.20260818055204-eef511ff3df8 github.com/kong/go-kong v0.77.0 github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/cobra v1.10.2 @@ -24,6 +25,7 @@ require ( github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 golang.org/x/sync v0.22.0 + gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.35.4 k8s.io/apiextensions-apiserver v0.33.1 k8s.io/apimachinery v0.35.4 @@ -79,7 +81,6 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/go-querystring v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/go-memdb v1.3.5 // indirect @@ -139,7 +140,6 @@ require ( golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/cli-runtime v0.31.0 // indirect k8s.io/component-base v0.33.1 // indirect k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect @@ -220,7 +220,7 @@ require ( golang.org/x/tools v0.48.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + gopkg.in/yaml.v3 v3.0.1 k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect k8s.io/utils v0.0.0-20260108192941-914a6e750570 // indirect diff --git a/go.sum b/go.sum index 60b1301a4..1bbae5870 100644 --- a/go.sum +++ b/go.sum @@ -256,8 +256,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kong/go-apiops v0.4.5 h1:9b41aJ5LKlVR+RaspviTE6nBD9oZRTltw+tRA2k9k7g= github.com/kong/go-apiops v0.4.5/go.mod h1:Xt99d90LallLVwYJAGaufiNbBdsK0KKboe7gR4Ryths= -github.com/kong/go-database-reconciler v1.42.1 h1:1F4XhhJcnK5ixmeeZ973N4pjIkmnf9xlIejdq6+TZMs= -github.com/kong/go-database-reconciler v1.42.1/go.mod h1:AYYSt3TBXL8QMa1IZwqj4BhSxKMkFXama5ObtgWPoMc= +github.com/kong/go-database-reconciler v1.42.1-0.20260818055204-eef511ff3df8 h1:ZgViLnSg+jfQ41yUdnHQgRJjNtn7OypnDgx/00Mh7n4= +github.com/kong/go-database-reconciler v1.42.1-0.20260818055204-eef511ff3df8/go.mod h1:AYYSt3TBXL8QMa1IZwqj4BhSxKMkFXama5ObtgWPoMc= github.com/kong/go-kong v0.77.0 h1:bANx78/pE+kbKnL1ssa24Si4xbyBjhkUxwTOI+MrnnI= github.com/kong/go-kong v0.77.0/go.mod h1:Wx5aTcMjyUnIF94M5NYFWb/EnuEkqB5STrWvybFSYYQ= github.com/kong/go-slugify v1.0.0 h1:vCFAyf2sdoSlBtLcrmDWUFn0ohlpKiKvQfXZkO5vSKY=