Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.dockerjava.api.command;

import com.github.dockerjava.api.model.AuthConfig;
import com.github.dockerjava.api.model.ServiceSpec;

import javax.annotation.CheckForNull;
Expand All @@ -19,6 +20,12 @@ public interface UpdateServiceCmd extends SyncDockerCmd<Void> {

UpdateServiceCmd withServiceSpec(ServiceSpec serviceSpec);

@CheckForNull
AuthConfig getAuthConfig();

@Nonnull
UpdateServiceCmd withAuthConfig(@Nonnull AuthConfig authConfig);

@CheckForNull
Long getVersion();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.github.dockerjava.api.command.UpdateServiceCmd;
import com.github.dockerjava.api.exception.NotFoundException;
import com.github.dockerjava.api.model.AuthConfig;
import com.github.dockerjava.api.model.ServiceSpec;
import com.github.dockerjava.core.RemoteApiVersion;
import org.apache.commons.lang3.builder.EqualsBuilder;
Expand All @@ -11,6 +12,7 @@

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.util.Objects;

/**
* @since {@link RemoteApiVersion#VERSION_1_24}
Expand All @@ -28,6 +30,13 @@ public class UpdateServiceCmdImpl extends AbstrDockerCmd<UpdateServiceCmd, Void>
*/
private ServiceSpec serviceSpec;

/**
* Registry authentication sent with the service update request.
*
* @since {@link RemoteApiVersion#VERSION_1_24}
*/
private AuthConfig authConfig;

/**
* @since 1.24
*/
Expand Down Expand Up @@ -71,6 +80,25 @@ public UpdateServiceCmd withServiceSpec(ServiceSpec serviceSpec) {
return this;
}

/**
* @see #authConfig
*/
@Override
@CheckForNull
public AuthConfig getAuthConfig() {
return authConfig;
}

/**
* @see #authConfig
*/
@Override
@Nonnull
public UpdateServiceCmd withAuthConfig(@Nonnull AuthConfig authConfig) {
this.authConfig = Objects.requireNonNull(authConfig, "authConfig was not specified");
return this;
}

/**
* @see #version
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.github.dockerjava.api.command.UpdateServiceCmd;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.InvocationBuilder;
import com.github.dockerjava.core.MediaType;
import com.github.dockerjava.core.WebTarget;
import org.slf4j.Logger;
Expand All @@ -27,8 +28,10 @@ protected Void execute(UpdateServiceCmd command) {
.queryParam("version", command.getVersion());

LOGGER.trace("POST: {}", webResource);
InvocationBuilder builder = resourceWithOptionalAuthConfig(command.getAuthConfig(), webResource.request())
.accept(MediaType.APPLICATION_JSON);
try {
webResource.request().accept(MediaType.APPLICATION_JSON).post(command.getServiceSpec()).close();
builder.post(command.getServiceSpec()).close();
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,23 @@
import com.github.dockerjava.api.model.ServiceModeConfig;
import com.github.dockerjava.api.model.ServiceReplicatedModeOptions;
import com.github.dockerjava.api.model.ServiceSpec;
import com.github.dockerjava.api.model.Task;
import com.github.dockerjava.api.model.TaskSpec;
import com.github.dockerjava.api.model.TaskState;
import com.github.dockerjava.junit.PrivateRegistryRule;
import com.google.common.collect.Lists;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.github.dockerjava.core.DockerRule.DEFAULT_IMAGE;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.startsWith;

public class UpdateSwarmServiceIT extends SwarmCmdIT {
@Test
Expand Down Expand Up @@ -46,4 +53,45 @@ public void testUpdateServiceReplicate() {
assertThat(updateService.getSpec().getMode().getReplicated().getReplicas(), is(2L));
});
}

@Test
public void testUpdateServiceWithRegistryAuth() throws InterruptedException {
DockerClient dockerClient = startSwarm();
ServiceSpec serviceSpec = new ServiceSpec()
.withName("authenticated-worker")
.withMode(new ServiceModeConfig().withReplicated(new ServiceReplicatedModeOptions().withReplicas(1)))
.withTaskTemplate(new TaskSpec().withContainerSpec(
new ContainerSpec().withImage(DEFAULT_IMAGE).withArgs(Arrays.asList("sleep", "3600"))));
String serviceId = dockerClient.createServiceCmd(serviceSpec).exec().getId();

await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> {
List<Task> tasks = dockerClient.listTasksCmd()
.withServiceFilter(serviceId)
.withStateFilter(TaskState.RUNNING)
.exec();
assertThat(tasks, hasSize(1));
});

try (PrivateRegistryRule registry = new PrivateRegistryRule(dockerClient)) {
registry.start();
String privateImage = registry.createPrivateImage("update-service");
Service service = dockerClient.inspectServiceCmd(serviceId).exec();
ServiceSpec updatedServiceSpec = service.getSpec();
updatedServiceSpec.getTaskTemplate().getContainerSpec().withImage(privateImage);

dockerClient.updateServiceCmd(serviceId, updatedServiceSpec)
.withVersion(service.getVersion().getIndex())
.withAuthConfig(registry.getAuthConfig())
.exec();

await().atMost(60, TimeUnit.SECONDS).untilAsserted(() -> {
List<Task> tasks = dockerClient.listTasksCmd()
.withServiceFilter(serviceId)
.withStateFilter(TaskState.RUNNING)
.exec();
assertThat(tasks, hasSize(1));
assertThat(tasks.get(0).getSpec().getContainerSpec().getImage(), startsWith(privateImage));
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.github.dockerjava.core.exec;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.model.AuthConfig;
import com.github.dockerjava.api.model.ServiceSpec;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.DockerClientImpl;
import com.github.dockerjava.transport.DockerHttpClient;
import com.google.common.io.BaseEncoding;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;

public class UpdateServiceCmdExecTest {

private static final String REGISTRY_AUTH_HEADER = "X-Registry-Auth";

@Test
public void sendsRegistryAuthHeader() throws Exception {
AtomicReference<DockerHttpClient.Request> request = new AtomicReference<>();
DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder().build();
DockerClient dockerClient = DockerClientImpl.getInstance(config, new CapturingDockerHttpClient(request));
AuthConfig authConfig = new AuthConfig()
.withUsername("user")
.withPassword("password")
.withRegistryAddress("registry.example.com");

dockerClient.updateServiceCmd("service-id", new ServiceSpec())
.withVersion(1L)
.withAuthConfig(authConfig)
.exec();

String encodedAuth = request.get().headers().get(REGISTRY_AUTH_HEADER);
assertThat(encodedAuth, notNullValue());
byte[] decodedAuth = BaseEncoding.base64Url().decode(encodedAuth);
AuthConfig sentAuth = config.getObjectMapper().readValue(decodedAuth, AuthConfig.class);
assertThat(sentAuth, is(authConfig));
}

@Test
public void omitsRegistryAuthHeaderWhenNotConfigured() {
AtomicReference<DockerHttpClient.Request> request = new AtomicReference<>();
DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder().build();
DockerClient dockerClient = DockerClientImpl.getInstance(config, new CapturingDockerHttpClient(request));

dockerClient.updateServiceCmd("service-id", new ServiceSpec())
.withVersion(1L)
.exec();

assertThat(request.get().headers().get(REGISTRY_AUTH_HEADER), nullValue());
}

private static class CapturingDockerHttpClient implements DockerHttpClient {

private final AtomicReference<Request> request;

CapturingDockerHttpClient(AtomicReference<Request> request) {
this.request = request;
}

@Override
public Response execute(Request request) {
this.request.set(request);
return new EmptyResponse();
}

@Override
public void close() throws IOException {
}
}

private static class EmptyResponse implements DockerHttpClient.Response {

@Override
public int getStatusCode() {
return 200;
}

@Override
public Map<String, List<String>> getHeaders() {
return Collections.emptyMap();
}

@Override
public InputStream getBody() {
return new ByteArrayInputStream(new byte[0]);
}

@Override
public void close() {
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.junit.rules.ExternalResource;

import java.io.File;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static com.github.dockerjava.api.model.HostConfig.newHostConfig;
Expand All @@ -20,7 +21,7 @@
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;

public class PrivateRegistryRule extends ExternalResource {
public class PrivateRegistryRule extends ExternalResource implements AutoCloseable {

private final DockerClient dockerClient;

Expand All @@ -29,7 +30,11 @@ public class PrivateRegistryRule extends ExternalResource {
private String containerId;

public PrivateRegistryRule() {
this.dockerClient = CmdIT.createDockerClient(DockerRule.config(null));
this(CmdIT.createDockerClient(DockerRule.config(null)));
}

public PrivateRegistryRule(DockerClient dockerClient) {
this.dockerClient = Objects.requireNonNull(dockerClient);
}

public AuthConfig getAuthConfig() {
Expand Down Expand Up @@ -59,12 +64,20 @@ public String createTestImage(String tagName) {
return imgName + ":" + tagName;
}

public void start() throws InterruptedException {
startRegistry();
}

/**
* Starts a local test registry when it is not already started and returns the auth configuration for it
* This method is synchronized so that only the first invocation starts the registry
*/
@Override
protected void before() throws Throwable {
protected void before() throws InterruptedException {
startRegistry();
}

private void startRegistry() throws InterruptedException {

int port = 5050;

Expand Down Expand Up @@ -116,4 +129,9 @@ protected void after() {
.exec();
}
}

@Override
public void close() {
after();
}
}