-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ignore username/password changes in registryAuth config #1327
base: master
Are you sure you want to change the base?
Conversation
Changes to registry authentication credentials (username/password) should not trigger resource updates, while changes to registry addresses should. This applies to both raw config and JSON-encoded nested configuration. Key changes: - Added logic to ignore username/password updates in registryAuth arrays - Added handling for JSON-encoded nested configuration - Added tests to verify credential changes don't trigger diffs - Maintained address changes as a trigger for updates
Does the PR have any schema changes?Looking good! No breaking changes found. Maintainer note: consult the runbook for dealing with any breaking changes. |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #1327 +/- ##
======================================
Coverage 0.00% 0.00%
======================================
Files 3 3
Lines 147 147
======================================
Misses 147 147 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
provider/hybrid.go
Outdated
return nil, fmt.Errorf("error unwrapping new config: %w", err) | ||
} | ||
|
||
return dp.nativeProvider.DiffConfig(ctx, request) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this going to make it a no-op?
// DiffConfig diffs the configuration for this provider.
func (p *dockerNativeProvider) DiffConfig(context.Context, *rpc.DiffRequest) (*rpc.DiffResponse, error) {
return &rpc.DiffResponse{}, nil
}
provider/provider.go
Outdated
func (p *dockerNativeProvider) DiffConfig(context.Context, *rpc.DiffRequest) (*rpc.DiffResponse, error) { | ||
return &rpc.DiffResponse{}, nil | ||
func (p *dockerNativeProvider) DiffConfig(ctx context.Context, req *rpc.DiffRequest) (*rpc.DiffResponse, error) { | ||
return p.Diff(ctx, req) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh.
So to clarify. Prior to this change, provider configuration was handled by the bridge. The bridge default logic caused unwanted behavior: changes to usernames and passwords triggered cascading updates. To address the problem, given difficulty in customizing the bridge behavior to what is wanted, DiffConfig method is re-implemented entirely to use a custom method. |
I'd like to double-check that the bridge could not be reconfigured to give the desired behavior here quickly. Presumably this code was tripping up a force replace? |
provider/provider_test.go
Outdated
}), | ||
}, | ||
want: &rpc.DiffResponse{ | ||
Changes: rpc.DiffResponse_DIFF_NONE, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah so to clarify you want to completely hide the changes? Displaying the changes without causing a cascading replacement is undesirable? Why? Does not the user expect to see changes reflected after they are made?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those are settings that are likely to change on every run (e.g. for ECR). We’re already doing this for the Image
resource. This change aligns the bridged resources with that.
without this, drift detection wouldn’t work for most major clouds
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha so we want this as default behavior.
The issue is that the password and in turn the auth config block are marked as secret and that shows up as a replacement in the preview no matter what. We cannot un-secret the password because it’s sensitive. |
To be pedantic it seems to be an update and not a replacement:
|
All right given we want the new behavior, let's take the change, just let's talk a little bit about our options to keep the code a bit cleaner. Would it be possible to not reuse the Diff method for Docker Image as the DiffConfig implementation for the provider? These have different schemas and different purposes and are just accidentally similar. I think sharing this code reduces duplication but creates dense code that's harder to understand. Could we have some extra code instead that's simpler? If going down the path of implementing a custom diff method, one interesting hint can be seen here: pulumi-docker/provider/provider.go Line 304 in 962342e
Note the IgnoreKeyFunc perhaps it can take care of the ignored keys elegantly instead of post-processing ObjectDiff. Another possibility is to keep using the bridge implementation but do pre-processing or ask it to ignore keys e.g. func (dp dockerHybridProvider) DiffConfig(ctx context.Context, request *rpc.DiffRequest) (*rpc.DiffResponse, error) {
urn := resource.URN(request.GetUrn())
label := fmt.Sprintf("%s.DiffConfig(%s)", dp.name, urn)
logging.V(9).Infof("%s executing", label)
var err error
request.Olds, err = dp.unwrapJSONConfig(label, request.GetOlds())
if err != nil {
return nil, fmt.Errorf("error unwrapping old config: %w", err)
}
request.News, err = dp.unwrapJSONConfig(label, request.GetNews())
if err != nil {
return nil, fmt.Errorf("error unwrapping new config: %w", err)
}
request.IgnoreChanges = []string{"registryAuth"}
return dp.bridgedProvider.DiffConfig(ctx, request)
} This might not work but may be worth a quick try. There is also this https://github.com/pulumi/pulumi-terraform-bridge/blob/e2b32971aeb4b9ff1483d27f46dd09a9d515bff3/unstable/propertyvalue/ignore_changes.go#L24 that can pre-process the maps. |
Sorry, yeah what I wanted to say (and the PR description has it correct) is that it shows as an update for the provider and updates for all dependent resources. |
provider/provider.go
Outdated
@@ -339,20 +339,47 @@ func (p *dockerNativeProvider) Diff(_ context.Context, req *rpc.DiffRequest) (*r | |||
func diffUpdates(updates map[resource.PropertyKey]resource.ValueDiff) map[string]*rpc.PropertyDiff { | |||
updateDiff := map[string]*rpc.PropertyDiff{} | |||
for key, valueDiff := range updates { | |||
update := true | |||
// Include all the same updates by default. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sharing the diffUpdates function for cross-purposes is the most dense bit in this PR I think - it might be correct but this is pretty non-obivous code. I'd love if we had a separate version of this function for provider config specifically or even better if we didn't have to use this function to post-process updates but got the updates to compute as we want them in the first place.
Yeah, more than happy to give these other options a try. I’m mostly worried that we end up with two very similar functions that basically do the same thing. But let me quickly prototype so we can compare it side by side |
provider/provider_test.go
Outdated
|
||
got, err := p.Diff(context.TODO(), req) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("dockerNativeProvider.Diff() error = %v, wantErr %v", err, tt.wantErr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very confusing test suite we are testing dockerNativeProvider.Diff; the only job of dockerNativeProvider.Diff is to compute diffs for the Image resource. However we're testing it with registryAuth field that is not even part of the Image resource schema. I think we can keep the test suite but target
func (dp dockerHybridProvider) DiffConfig(ctx context.Context, request *rpc.DiffRequest) (*rpc.DiffResponse, error) {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, more reason to split out the methods!
I think that's good in my book. It's only accidentally similar. There's generic code for doing diffs, and there's specific distinct jobs for diffing provider config vs Image resource config, which have different concrete schemas. The schemas will evolve separately in the future. Slightly duplicative code seems the way to go. |
Capturing some slack convo that helped me understand the trade-offs here. This workaround does not do what the user wants because it persists password from state. This will get stale in 1hr or so after the token expires. import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as docker from "@pulumi/docker";
const creds = aws.ecr.getAuthorizationTokenOutput({});
export const dockerProvider = new docker.Provider("docker-provider", {
registryAuth: [
{
username: creds.apply((c) => c.userName),
password: creds.apply((c) => c.password),
address: creds.apply((c) => c.proxyEndpoint),
},
],
}, {ignoreChanges: ["registryAuth"]}); Instead, what we get with this PR is that the |
To clarify a bit more, the outputs of the provider in state will be frozen, but the input will still be updated. The input is what's passed to configure in the case of refresh/delete so this change doesn't break any of those workflows. |
Another approach to consider here, is to add support for configuring explicit providers using the stack configuration file. In this way, one could maintain the password as a stack configuration setting (which is updatable even for refresh/destroy), rather than as a Provider argument. |
I think this would generally be a great idea! Sadly it wouldn’t solve this particular problem. The issue here mostly surfaces in cases where the provider is configured with short-lived credentials retrieved by a function (e.g. Amazon ECR). |
@t0yv0 it took a bit of experimentation, but I managed to get a version working that uses ignoreChanges and minimal post processing on the DiffConfig response! |
This PR modifies how the Docker provider handles changes to registry authentication credentials. Previously, any changes to the registryAuth configuration would trigger resource updates. Now, changes to usernames and passwords are ignored while changes to registry addresses still trigger updates. This aligns the native
docker.Image
resource (which already ignored username/password changes) with the rest of the bridged resources.To handle nested json-encoded configuration (related to pulumi/pulumi#17667) the provider is leveraging the
UnfoldProperties
method from the bridge. This way it can transparently operate on the config without having to worry about the json encoding.Key changes:
Fixes #952