Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow callers to set log output #309

Merged
merged 1 commit into from Feb 16, 2022
Merged

Conversation

imjasonh
Copy link
Contributor

@imjasonh imjasonh commented Feb 9, 2022

Signed-off-by: Jason Hall jasonhall@redhat.com

Description of changes:

Though this code is typically consumed as a CLI cred helper configured for use with the docker CLI, we've also found it to be really useful as a Go client for generating ECR credentials from the environment.

go-containerregistry, which is used by Tekton, Knative, ko, cosign, Kaniko, Buildpacks and more, provides an adapter between its auth-providing authn.Keychain interface, and Docker's credential.Helper interface, which you implement here. This means that these tools can (and some already do!) embed the cred helper's logic with:

kc := authn.NewKeychainFromHelper(ecr.ECRHelper{ClientFactory: api.DefaultClientFactory{}})

For example:

And this works great! 馃帀 These clients don't have to install the cred helper CLI and configure it using ~/.docker/config.json to pick up any available ECR credentials; they Just Work鈩笍.

Except...

Through this usage, this library logs a harmless error when we check whether a particular possibly-non-ECR registry can be used with this code. In its normal CLI usage, docker won't invoke the ecr-login cred helper unless it already knows the registry is ECR, so this logging is correct in identifying a weird situation. But in usage as a library, this leads to spammy, sometimes scary log messages, which we'd like to avoid.

I tried all manner of terrible hacks to disable os.Stderr, but due to how logrus works internally, and how this code uses it, it doesn't seem possible without making logrus-specific changes to all those downstream clients.

Instead, this PR proposes adding a func that's unused by the CLI code (its logic remains unchanged), to let library consumers set the log output for the scope of ecr-login's usage, which downstream clients can set to ioutil.Discard if they want. This is quite a bit less invasive than any alternative I came up with, and should impose almost no maintenance burden to you lovely maintainers 鉂わ笍

This also slightly changes the API for instantiating an ECRHelper using NewECRHelper(), though AFAIK the CLI (and our users, whom I'll happily update) depends on that directly.

@@ -26,7 +27,20 @@ import (
var notImplemented = errors.New("not implemented")

type ECRHelper struct {
ClientFactory api.ClientFactory
clientFactory api.ClientFactory
logger *logrus.Logger
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using FieldLogger?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this give any flexibility to redirect/discard the output? I'm not very familiar with logrus personally, but it seems to only support log-writing and field-setting methods: https://pkg.go.dev/github.com/sirupsen/logrus#FieldLogger

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can either create a logrus.Logger and then set its Out as you did, or use https://github.com/sirupsen/logrus/blob/fdf1618bf7436ec3ee65753a6e2999c335e97221/hooks/test/test.go#L42.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. In either of those cases, my code has to be aware that logrus is involved, and take an explicit direct dependency on it.

go-containerregistry and many of its users don't depend on logrus directly today, and might only depend on it through ecr-login. Exposing that dependency through ecr-login's API means you can't change it in the future (if you want to) without breaking folks. By only exposing stdlib interfaces in the API, callers can keep a somewhat tidier dependency graph, and you retain some flexibility for the future. AFAIK nobody has expressed a desire to pass a different formatter.

Copy link
Contributor Author

@imjasonh imjasonh Feb 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an example, here's how I intend to use this affordance in ko to disable ecr-login's log message, without having to expose logrus as an implementation detail to ko.

https://github.com/google/ko/pull/586/files#diff-0924b0ef75727624e13d12e7f4b9362bbdb2b2e8ee8a9f5692f9be5bd8041f4fR46

If this PR is merged, I'd also make a similar change to tools like cosign, Tekton, Kaniko, etc., for the same reason.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. Sorry I was assuming you would be using logrus too.

ecr-login/ecr.go Outdated
}
}

func (self ECRHelper) WithLogOutput(w io.Writer) ECRHelper {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to use FieldLogger here as well. The caller may want to customize the logger (e.g. use JSON logging)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would tie ecr-login to logrus as its logging implementation via its exported API. I'm not sure how important that flexibility is, but currently, and with this change, ecr-login could change to another logging library under the hood, without callers being aware or needing to make any changes.

AFAIK, if this only supported WithFieldLogger, a caller that wanted to disable ecr-login's logging entirely would have to implement a discarding FieldLogger implementation, or else take a dependency on logrus and call logrus.New().SetOutput(ioutil.Discard).

If you think it would be a useful addition as a separate method, I'm happy to add it though.

@kzys
Copy link
Contributor

kzys commented Feb 14, 2022

How about using https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis instead of the Builder pattern?

So that most of clients can do NewECRHelper() without passing the default factory and we can make the logger output immutable while the lifecycle of this struct.

@imjasonh
Copy link
Contributor Author

imjasonh commented Feb 14, 2022

Yeah I can do that too if you'd like. One sec.

edit: Done, and updated ko to consume it: https://github.com/google/ko/pull/586/files#diff-0924b0ef75727624e13d12e7f4b9362bbdb2b2e8ee8a9f5692f9be5bd8041f4fL45

@kzys
Copy link
Contributor

kzys commented Feb 14, 2022

Looks good! Can you squash the commits to make the history tidy? Please keep Sign-off line. We need to have that on all commits (yeah, we should check that in GitHub Actions...).

Signed-off-by: Jason Hall <jasonhall@redhat.com>
@imjasonh
Copy link
Contributor Author

Friendly ping. I've squashed the commits, let me know if there's anything else you'd like to see.

@kzys kzys merged commit 3d7835a into awslabs:main Feb 16, 2022
imjasonh added a commit to imjasonh/amazon-ecr-credential-helper that referenced this pull request Feb 25, 2022
Reported in GoogleContainerTools/kaniko#1955

Changes in
awslabs#309 made it
possible for callers depending on this code as a Go library to redirect
log output to ioutil.Discard to avoid spurious log statements.

Unfortunately, because in that change the logger being used was the
shared global logger.StandardLogger(), redirecting its output to
ioutil.Discard to quiet ecr-login also disabled all logging using the
shared global logger (like Kaniko does).
imjasonh added a commit to imjasonh/amazon-ecr-credential-helper that referenced this pull request Feb 25, 2022
Reported in GoogleContainerTools/kaniko#1955

Changes in
awslabs#309 made it
possible for callers depending on this code as a Go library to redirect
log output to ioutil.Discard to avoid spurious log statements.

Unfortunately, because in that change the logger being used was the
shared global logger.StandardLogger(), redirecting its output to
ioutil.Discard to quiet ecr-login also disabled all logging using the
shared global logger (like Kaniko does).

Signed-off-by: Jason Hall <jasonhall@redhat.com>
imjasonh added a commit to imjasonh/amazon-ecr-credential-helper that referenced this pull request Feb 28, 2022
writes to the requested output , instead of changing the behavior of the
standard logrus logger.

Rename WithLogOutput to WithLogger to make this new behavior clearer.

Use non-global logger to avoid unexpected global log discarding

Reported in GoogleContainerTools/kaniko#1955

Changes in
awslabs#309 made it
possible for callers depending on this code as a Go library to redirect
log output to ioutil.Discard to avoid spurious log statements.

Unfortunately, because in that change the logger being used was the
shared global logger.StandardLogger(), redirecting its output to
ioutil.Discard to quiet ecr-login also disabled all logging using the
shared global logger (like Kaniko does).

Signed-off-by: Jason Hall <jasonhall@redhat.com>
halvards added a commit to halvards/skaffold that referenced this pull request Mar 15, 2022
Use a new logrus.Logger instance instead of the default logrus
singleton.

Relying on the default singleton is problematic when dependencies (or
transitive dependencies) also use logrus. When code paths in these
dependencies modify the default singleton, it also impacts Skaffold
logging.

I ran into this issue while trying to upgrade the `ko` dependency to
v0.10, and there was unwanted error-level log output messages from
`amazon-ecr-credential-helper`.

Context:
- ko-build/ko#586
- awslabs/amazon-ecr-credential-helper#309

This change also moves us closer to not leaking the logger
implementation dependency outside the `output/log` package.

Tracking: GoogleContainerTools#7131
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants