Skip to content
This repository has been archived by the owner on Mar 21, 2022. It is now read-only.

add RegistryAuthSupplier interface #759

Merged
merged 8 commits into from
May 23, 2017

Conversation

mattnworb
Copy link
Member

This completes #749 - David and I worked on this together in-person.

The new RegistryAuthSupplier interface replaces the existing RegistryAuth support in DefaultDockerClient so that users can customize the authentication behavior much more closely. On each operation that requires authentication, DefaultDockerClient will invoke the appropriate method on the RegistryAuthSupplier instance, allowing the implementation to supply up-to-date authentication information/tokens.

This change is inspired by our attempts to integrate with Google Container Registry, where ~/.docker/config.json file contains short-lived access tokens that expire shortly after creation. This means that a long-lived DefaultDockerClient instance cannot store static RegistryAuth information from construction-time.

Also included is an implementation of RegistryAuthSupplier that refreshes the access tokens supplied by gcloud docker -a prior to each authFor(..) operation, as well as an implementation of RegistryAuthSupplier that returns static data.

Closes #740.

@mattnworb
Copy link
Member Author

@davidxia @mavenraven

one thing we decided to punt on for the moment is customizing the GCR RegistryAuthSupplier to be able to do something like gcloud --account <accountname> docker ... to allow the user to choose which account/credentials to use. What is here now uses the "default" credentialed account in gcloud, which seems to static to rely on long-term. We'll come back to this soon, in a new PR.

@mattnworb mattnworb force-pushed the mattbrown/registry-auth-supplier branch 3 times, most recently from c5c2236 to 8fea305 Compare May 22, 2017 20:59
import com.spotify.docker.client.messages.RegistryConfigs;

/**
* Wraps a RegistryAuth with the RegisrtyAuthSupplier interface.

Choose a reason for hiding this comment

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

typo

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed, thanks

@mavenraven
Copy link

👍

@mattnworb mattnworb force-pushed the mattbrown/registry-auth-supplier branch 2 times, most recently from adf8d21 to b6d531d Compare May 23, 2017 13:40
@codecov-io
Copy link

codecov-io commented May 23, 2017

Codecov Report

Merging #759 into master will increase coverage by 0.32%.
The diff coverage is 69.42%.

@@             Coverage Diff              @@
##             master     #759      +/-   ##
============================================
+ Coverage     65.56%   65.89%   +0.32%     
- Complexity      656      672      +16     
============================================
  Files           153      158       +5     
  Lines          2878     2926      +48     
  Branches        335      335              
============================================
+ Hits           1887     1928      +41     
- Misses          844      850       +6     
- Partials        147      148       +1

@mattnworb mattnworb force-pushed the mattbrown/registry-auth-supplier branch from 2f67e16 to b43336d Compare May 23, 2017 16:53
Copy link
Contributor

@davidxia davidxia left a comment

Choose a reason for hiding this comment

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

Didn't take that close of a look at tests though.

}

return request(
GET,
InputStream.class,
resource,
resource.request(APPLICATION_JSON_TYPE).header("X-Registry-Auth", authHeader(registryAuth))
resource.request(APPLICATION_JSON_TYPE).header("X-Registry-Auth", authHeader(
registryAuthSupplier.authFor(images[0])))
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this endpoint need auth at all? Docs don't mention it.

Copy link
Member Author

Choose a reason for hiding this comment

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

I am not sure, but the existing code has it.

public Builder registryAuth(final RegistryAuth registryAuth) {
if (this.registryAuthSupplier != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Will we keep this logic or remove registryAuth() at some point?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd like to remove all the deprecated methods in a future release - see what I added to Changelog

new DockerConfigReader().defaultConfigPath());
}

public GoogleContainerRegistryAuthSupplier(DockerConfigReader dockerCfgReader,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: can we put each param on a new line?

private static final ObjectMapper MAPPER = ObjectMapperProvider.objectMapper();

/** Returns all RegistryConfig instances from the configuration file. */
public RegistryConfigs fromConfig(Path configPath) throws IOException {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not make these all static methods? Then you remove a call to the constructor at call sites.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think this was done to make it easy to mock it


private static final String DEFAULT_REGISTRY = "https://index.docker.io/v1/";
private static final String DUMMY_EMAIL = "1234@5678.com";

@SuppressWarnings("FieldCanBeLocal")
private static final ObjectMapper MAPPER =
Copy link
Contributor

Choose a reason for hiding this comment

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

unused

public abstract ImmutableMap<String, RegistryConfig> configs();

@AutoValue
public abstract static class RegistryConfig {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice. Why did I add these in the first place?!

David Morhovich and others added 7 commits May 23, 2017 15:16
Adds support for RegistryAuthSupplier. Client code can consume this using
DefaultDockerClient builder. Provides support for authenticating against GCR
to allow pushes/pulls from GCR.
the Build method in the Docker Remote API allows for passing *all* of
the RegistryAuth structs that the client knows about, this updates the
RegistryAuthSupplier interface to match that.
use the same method for parsing the config file in both code paths
and bump library version to 8.6.0 as significant new functionality is
added/deprecated here
testAuth and testBadAuth is failing on Docker 1.9 and 1.10 because
RegistryAuth.email is not set when we send it to the daemon
@mattnworb mattnworb force-pushed the mattbrown/registry-auth-supplier branch from 652ba48 to 9d65790 Compare May 23, 2017 19:18
}

public Builder registryAuthSupplier(final RegistryAuthSupplier registryAuthSupplier) {
if (this.registryAuthSupplier != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this check if(this.registryAuth != null)? Right now this would only get triggered if the user called builder().registryAuthSupplier(registryAuthSupplier).registryAuthSupplier(registryAuthSupplier).

Copy link
Member Author

Choose a reason for hiding this comment

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

Builder.registryAuth(RegistryAuth) is setting this.registryAuthSupplier to a non-null value

Copy link
Member Author

Choose a reason for hiding this comment

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

I would remove the registryAuth field from the Builder altogether but there is a registryAuth() getter for some reason

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@mattnworb mattnworb force-pushed the mattbrown/registry-auth-supplier branch from 9d65790 to 073ad23 Compare May 23, 2017 19:42
@mattnworb mattnworb merged commit f3233d1 into master May 23, 2017
@mattnworb mattnworb deleted the mattbrown/registry-auth-supplier branch May 23, 2017 20:22
try {
String registryName = "https://" + imageName.split("/")[0];
refresher.refresh();
return reader.fromConfig(configPath, registryName);
Copy link
Contributor

@davidxia davidxia May 23, 2017

Choose a reason for hiding this comment

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

I wanted to pull busybox:latest and push to gcr.io/myproject/busybox:latest and realized I have to use two DockerClients to do this or implement my own RegistryAuthSupplier that checks if the image name starts with gcr.io/.

    final DefaultDockerClient docker = DefaultDockerClient.fromEnv()
        .registryAuthSupplier(new GoogleContainerRegistryAuthSupplier())
        .build();
    docker.pull("busybox:latest");
    docker.tag("busybox:latest", "gcr.io/myproject/busybox:latest");
    docker.push("gcr.io/myproject/busybox:latest");

will throw

java.lang.IllegalArgumentException: serverAddress=https://busybox:latest does not appear in config file at /Users/dxia/.docker/config.json

	at com.spotify.docker.client.DockerConfigReader.parseDockerConfig(DockerConfigReader.java:81)
	at com.spotify.docker.client.DockerConfigReader.fromConfig(DockerConfigReader.java:51)
	at com.spotify.docker.client.gcr.GoogleContainerRegistryAuthSupplier.authFor(GoogleContainerRegistryAuthSupplier.java:58)
	at com.spotify.docker.client.DefaultDockerClient.pull(DefaultDockerClient.java:1196)
	at com.spotify.docker.client.DefaultDockerClient.pull(DefaultDockerClient.java:1190)
	at com.spotify.gcpprojectcreator.gcloud.IamPolicyHandlerTest.foo(IamPolicyHandlerTest.java:160)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Is this intended?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch, this is not intended.

I think this refactoring requires a few more changes before it is really complete and can be released. I wanted to merge what we have so far to avoid the PR growing gigantic. I'll fix this and I'm also close to being able to run this code locally in helios with GCR.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

integration with pushing/pulling images from Google Cloud Registry
5 participants