diff --git a/acceptance/bundle/resources/secrets/remove-expire-time/script b/acceptance/bundle/resources/secrets/remove-expire-time/script index 1659dfb2fee..d22f67aa06b 100644 --- a/acceptance/bundle/resources/secrets/remove-expire-time/script +++ b/acceptance/bundle/resources/secrets/remove-expire-time/script @@ -13,13 +13,16 @@ read_state.py secrets secret1 name expire_time title "Remove expire_time and re-deploy" trace update_file.py databricks.yml ' expire_time: "2030-01-01T00:00:00Z"' "" trace $CLI bundle plan --var secret_value=secret-value -trace $CLI bundle plan --var secret_value=secret-value -o json | nostamp | jq '.plan."resources.secrets.secret1" | {action, changes}' > out.plan.$DATABRICKS_BUNDLE_ENGINE.json +# Project to expire_time (the field this test is about): the full changes set now carries +# output-only fields whose remote values differ between the testserver and a real workspace +# (metastore_id, comment, timestamps), which is noise here and breaks the Cloud run. +trace $CLI bundle plan --var secret_value=secret-value -o json | nostamp | jq '.plan."resources.secrets.secret1" | {action, changes: (.changes | {expire_time, owner, value})}' > out.plan.$DATABRICKS_BUNDLE_ENGINE.json trace $CLI bundle deploy --var secret_value=secret-value trace print_requests.py //unity-catalog read_state.py secrets secret1 name expire_time title "Plan again to record any drift the removal leaves behind" trace $CLI bundle plan --var secret_value=secret-value -trace $CLI bundle plan --var secret_value=secret-value -o json | nostamp | jq '.plan."resources.secrets.secret1" | {action, changes}' > out.plan.drift.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle plan --var secret_value=secret-value -o json | nostamp | jq '.plan."resources.secrets.secret1" | {action, changes: (.changes | {expire_time, owner, value})}' > out.plan.drift.$DATABRICKS_BUNDLE_ENGINE.json rm -f "$OUT_REQUESTS" diff --git a/acceptance/bundle/resources/secrets/update-expire-time/script b/acceptance/bundle/resources/secrets/update-expire-time/script index fe58d8c30c0..5114aaa644c 100644 --- a/acceptance/bundle/resources/secrets/update-expire-time/script +++ b/acceptance/bundle/resources/secrets/update-expire-time/script @@ -13,7 +13,10 @@ read_state.py secrets secret1 name expire_time title "Change expire_time and re-deploy" trace update_file.py databricks.yml "2030-01-01T00:00:00Z" "2031-06-15T12:00:00Z" trace $CLI bundle plan --var secret_value=secret-value -trace $CLI bundle plan --var secret_value=secret-value -o json | nostamp | jq '.plan."resources.secrets.secret1" | {action, changes}' > out.plan.$DATABRICKS_BUNDLE_ENGINE.json +# Project to expire_time (the field this test is about): the full changes set now carries +# output-only fields whose remote values differ between the testserver and a real workspace +# (metastore_id, comment, timestamps), which is noise here and breaks the Cloud run. +trace $CLI bundle plan --var secret_value=secret-value -o json | nostamp | jq '.plan."resources.secrets.secret1" | {action, changes: (.changes | {expire_time, owner, value})}' > out.plan.$DATABRICKS_BUNDLE_ENGINE.json trace $CLI bundle deploy --var secret_value=secret-value trace print_requests.py //unity-catalog read_state.py secrets secret1 name expire_time diff --git a/acceptance/bundle/resources/secrets/update-value/output.txt b/acceptance/bundle/resources/secrets/update-value/output.txt index 214af4d4c15..8f12f1e007a 100644 --- a/acceptance/bundle/resources/secrets/update-value/output.txt +++ b/acceptance/bundle/resources/secrets/update-value/output.txt @@ -75,11 +75,51 @@ secrets secret1 catalog_name='main' schema_name='default' name='test_secret' com "value": "[redacted]" }, "changes": { + "create_time": { + "action": "skip", + "reason": "spec:output_only", + "remote": "[TIMESTAMP]" + }, + "created_by": { + "action": "skip", + "reason": "spec:output_only", + "remote": "[USERNAME]" + }, + "effective_owner": { + "action": "skip", + "reason": "spec:output_only", + "remote": "[USERNAME]" + }, + "effective_value": { + "action": "skip", + "reason": "spec:output_only", + "remote": "[redacted]" + }, + "full_name": { + "action": "skip", + "reason": "spec:output_only", + "remote": "main.default.test_secret" + }, + "metastore_id": { + "action": "skip", + "reason": "spec:output_only", + "remote": "test-metastore-id" + }, "owner": { "action": "skip", "reason": "spec:input_only", "remote": "[USERNAME]" }, + "update_time": { + "action": "skip", + "reason": "spec:output_only", + "remote": "[TIMESTAMP]" + }, + "updated_by": { + "action": "skip", + "reason": "spec:output_only", + "remote": "[USERNAME]" + }, "value": { "action": "update", "old": "", diff --git a/bundle/direct/dresources/secret.go b/bundle/direct/dresources/secret.go index 5e6e01b0f95..2db0a334f82 100644 --- a/bundle/direct/dresources/secret.go +++ b/bundle/direct/dresources/secret.go @@ -45,35 +45,30 @@ func (*ResourceSecret) PrepareState(input *resources.Secret) *catalog.Secret { } } -func (*ResourceSecret) RemapState(remote *catalog.Secret) *catalog.Secret { - return &catalog.Secret{ - CatalogName: remote.CatalogName, - SchemaName: remote.SchemaName, - Name: remote.Name, - Value: remote.EffectiveValue, - Comment: remote.Comment, - ExpireTime: remote.ExpireTime, - Owner: remote.EffectiveOwner, - CreateTime: nil, - CreatedBy: "", - EffectiveOwner: "", - EffectiveValue: "", - FullName: "", - MetastoreId: "", - UpdateTime: nil, - UpdatedBy: "", - ForceSendFields: utils.FilterFields[catalog.Secret](nil), - } +// remapSecretRemote surfaces the resolved value and owner (the API returns them under +// effective_value/effective_owner) under value/owner so remote state matches the config +// shape — without this, the persisted value is empty and every plan re-reports an update. +// The rest of the secret, including the output-only metadata, is left intact and handled +// by ignore_remote_changes, so no information is discarded. It runs wherever remote state +// is produced (DoRead/DoCreate/DoUpdate), so no RemapState hook is needed. +func remapSecretRemote(remote *catalog.Secret) *catalog.Secret { + remote.Value = remote.EffectiveValue + remote.Owner = remote.EffectiveOwner + return remote } -// DoRead fetches the secret by full name. IncludeValue is set so RemapState can +// DoRead fetches the secret by full name. IncludeValue is set so remapSecretRemote can // recover the stored value from EffectiveValue. func (r *ResourceSecret) DoRead(ctx context.Context, id string) (*catalog.Secret, error) { - return r.client.SecretsUc.GetSecret(ctx, catalog.GetSecretRequest{ + remote, err := r.client.SecretsUc.GetSecret(ctx, catalog.GetSecretRequest{ FullName: id, IncludeValue: true, ForceSendFields: nil, }) + if err != nil { + return nil, err + } + return remapSecretRemote(remote), nil } // DoCreate creates a new UC secret. @@ -84,7 +79,7 @@ func (r *ResourceSecret) DoCreate(ctx context.Context, state *catalog.Secret) (s if err != nil || response == nil { return "", nil, err } - return response.FullName, response, nil + return response.FullName, remapSecretRemote(response), nil } // comment is force-sent on update so that clearing it in config clears it on the secret. The @@ -106,7 +101,7 @@ func (r *ResourceSecret) DoUpdate(ctx context.Context, id string, state *catalog if err != nil { return nil, err } - return response, nil + return remapSecretRemote(response), nil } // DoDelete deletes the secret.