From 732e1bbf514a65893ebb381850884385e69b9ae1 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 17 May 2018 11:33:39 -0400 Subject: [PATCH 1/6] [JENKINS-51395] At least reproduced problem in test. --- pom.xml | 10 ++- .../workflow/RegistryEndpointStepTest.java | 61 +++++++++++-------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/pom.xml b/pom.xml index d1bc7dae2..dc0f263d2 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ 8 2.9 2.12 + 2.25 @@ -55,7 +56,7 @@ org.jenkins-ci.plugins.workflow workflow-cps - 2.25 + ${workflow-cps-plugin.version} org.jenkins-ci.plugins.workflow @@ -101,6 +102,13 @@ tests test + + org.jenkins-ci.plugins.workflow + workflow-cps + ${workflow-cps-plugin.version} + tests + test + org.jenkins-ci.plugins.workflow workflow-job diff --git a/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java b/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java index eda6e270f..b0aa510dd 100644 --- a/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java @@ -21,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package org.jenkinsci.plugins.docker.workflow; import com.cloudbees.plugins.credentials.CredentialsProvider; @@ -32,41 +33,47 @@ import java.util.Map; import java.util.TreeMap; import org.jenkinsci.plugins.docker.commons.credentials.DockerRegistryEndpoint; +import org.jenkinsci.plugins.structs.describable.DescribableModel; +import org.jenkinsci.plugins.workflow.cps.SnippetizerTester; import org.jenkinsci.plugins.workflow.steps.StepConfigTester; -import org.jenkinsci.plugins.workflow.structs.DescribableHelper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import org.junit.ClassRule; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; -import org.junit.runners.model.Statement; -import org.jvnet.hudson.test.BuildWatcher; -import org.jvnet.hudson.test.RestartableJenkinsRule; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; public class RegistryEndpointStepTest { - @ClassRule public static BuildWatcher buildWatcher = new BuildWatcher(); - @Rule public RestartableJenkinsRule story = new RestartableJenkinsRule(); + @Rule public JenkinsRule r = new JenkinsRule(); - @Test public void configRoundTrip() { - story.addStep(new Statement() { - @Override public void evaluate() throws Throwable { - IdCredentials registryCredentials = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, "registryCreds", null, "me", "pass"); - CredentialsProvider.lookupStores(story.j.jenkins).iterator().next().addCredentials(Domain.global(), registryCredentials); - StepConfigTester sct = new StepConfigTester(story.j); - Map registryConfig = new TreeMap(); - registryConfig.put("url", "https://docker.my.com/"); - registryConfig.put("credentialsId", registryCredentials.getId()); - Map config = Collections.singletonMap("registry", registryConfig); - RegistryEndpointStep step = DescribableHelper.instantiate(RegistryEndpointStep.class, config); - step = sct.configRoundTrip(step); - DockerRegistryEndpoint registry = step.getRegistry(); - assertNotNull(registry); - assertEquals("https://docker.my.com/", registry.getUrl()); - assertEquals(registryCredentials.getId(), registry.getCredentialsId()); - assertEquals(config, DescribableHelper.uninstantiate(step)); - } - }); + @Ignore("fails to run withDockerRegistry without registry: prefix, which is what Snippetizer offers") + @Issue("JENKINS-51395") + @Test public void configRoundTrip() throws Exception { + SnippetizerTester st = new SnippetizerTester(r); + RegistryEndpointStep step = new RegistryEndpointStep(new DockerRegistryEndpoint("https://myreg/", null)); + st.assertRoundTrip(step, "withDockerRegistry([url: 'https://myreg/']) {\n // some block\n}"); + step = new RegistryEndpointStep(new DockerRegistryEndpoint(null, "hubcreds")); + st.assertRoundTrip(step, "withDockerRegistry([credentialsId: 'hubcreds']) {\n // some block\n}"); + step = new RegistryEndpointStep(new DockerRegistryEndpoint("https://myreg/", "mycreds")); + step.setToolName("ce"); + st.assertRoundTrip(step, "withDockerRegistry(registry: [credentialsId: 'mycreds', url: 'https://myreg/'], toolName: 'ce') {\n // some block\n}"); + IdCredentials registryCredentials = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, "registryCreds", null, "me", "pass"); + CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), registryCredentials); + StepConfigTester sct = new StepConfigTester(r); + // TODO use of DescribableModel here is gratuitous; the rest should just test the UI + Map registryConfig = new TreeMap<>(); + registryConfig.put("url", "https://docker.my.com/"); + registryConfig.put("credentialsId", registryCredentials.getId()); + Map config = Collections.singletonMap("registry", registryConfig); + step = new DescribableModel<>(RegistryEndpointStep.class).instantiate(config); + step = sct.configRoundTrip(step); + DockerRegistryEndpoint registry = step.getRegistry(); + assertNotNull(registry); + assertEquals("https://docker.my.com/", registry.getUrl()); + assertEquals(registryCredentials.getId(), registry.getCredentialsId()); + assertEquals(config, new DescribableModel<>(RegistryEndpointStep.class).uninstantiate(step)); } -} \ No newline at end of file +} From 8c463ba72f69c0c0e1fca60c11fb5e88e3dbe0e4 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 17 May 2018 12:36:00 -0400 Subject: [PATCH 2/6] [JENKINS-51395] Switching recommended syntax to inline arguments, while still supporting the old syntax. --- .../docker/workflow/RegistryEndpointStep.java | 31 ++++++++- .../plugins/docker/workflow/Docker.groovy | 2 +- .../workflow/RegistryEndpointStepTest.java | 65 ++++++++++--------- 3 files changed, 67 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java b/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java index d74ecfdd7..46d24dcd2 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java @@ -32,19 +32,24 @@ import hudson.model.Node; import hudson.model.TaskListener; import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import org.jenkinsci.plugins.docker.commons.credentials.DockerRegistryEndpoint; import org.jenkinsci.plugins.docker.commons.credentials.KeyMaterialFactory; import org.jenkinsci.plugins.docker.commons.tools.DockerTool; +import org.jenkinsci.plugins.structs.describable.UninstantiatedDescribable; import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl; import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl; +import org.jenkinsci.plugins.workflow.steps.Step; import org.jenkinsci.plugins.workflow.steps.StepContextParameter; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; public class RegistryEndpointStep extends AbstractStepImpl { - + private final @Nonnull DockerRegistryEndpoint registry; private @CheckForNull String toolName; @@ -105,6 +110,30 @@ public DescriptorImpl() { return true; } + @Override + public UninstantiatedDescribable uninstantiate(Step step) throws UnsupportedOperationException { + RegistryEndpointStep s = (RegistryEndpointStep) step; + Map args = new TreeMap<>(); + args.put("url", s.registry.getUrl()); + args.put("credentialsId", s.registry.getCredentialsId()); + args.put("toolName", s.toolName); + args.values().removeAll(Collections.singleton(null)); + return new UninstantiatedDescribable(args); + } + + @Override + public Step newInstance(Map arguments) throws Exception { + if (arguments.containsKey("url") || arguments.containsKey("credentialsId")) { + if (arguments.containsKey("registry")) { + throw new IllegalArgumentException("cannot mix url/credentialsId with registry"); + } + arguments.put("registry", new DockerRegistryEndpoint((String) arguments.remove("url"), (String) arguments.remove("credentialsId"))); + } else if (!arguments.containsKey("registry")) { + throw new IllegalArgumentException("must specify url/credentialsId (or registry)"); + } + return super.newInstance(arguments); + } + } } diff --git a/src/main/resources/org/jenkinsci/plugins/docker/workflow/Docker.groovy b/src/main/resources/org/jenkinsci/plugins/docker/workflow/Docker.groovy index 45159f9f1..97dad6030 100644 --- a/src/main/resources/org/jenkinsci/plugins/docker/workflow/Docker.groovy +++ b/src/main/resources/org/jenkinsci/plugins/docker/workflow/Docker.groovy @@ -37,7 +37,7 @@ class Docker implements Serializable { public V withRegistry(String url, String credentialsId = null, Closure body) { node { script.withEnv(["DOCKER_REGISTRY_URL=${url}"]) { - script.withDockerRegistry(registry: [url: url, credentialsId: credentialsId], toolName: script.env.DOCKER_TOOL_NAME) { + script.withDockerRegistry(url: url, credentialsId: credentialsId, toolName: script.env.DOCKER_TOOL_NAME) { body() } } diff --git a/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java b/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java index b0aa510dd..660ba9bb4 100644 --- a/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java @@ -29,16 +29,13 @@ import com.cloudbees.plugins.credentials.common.IdCredentials; import com.cloudbees.plugins.credentials.domains.Domain; import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; import org.jenkinsci.plugins.docker.commons.credentials.DockerRegistryEndpoint; -import org.jenkinsci.plugins.structs.describable.DescribableModel; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.cps.SnippetizerTester; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.steps.StepConfigTester; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; @@ -48,32 +45,42 @@ public class RegistryEndpointStepTest { @Rule public JenkinsRule r = new JenkinsRule(); - @Ignore("fails to run withDockerRegistry without registry: prefix, which is what Snippetizer offers") @Issue("JENKINS-51395") @Test public void configRoundTrip() throws Exception { - SnippetizerTester st = new SnippetizerTester(r); - RegistryEndpointStep step = new RegistryEndpointStep(new DockerRegistryEndpoint("https://myreg/", null)); - st.assertRoundTrip(step, "withDockerRegistry([url: 'https://myreg/']) {\n // some block\n}"); - step = new RegistryEndpointStep(new DockerRegistryEndpoint(null, "hubcreds")); - st.assertRoundTrip(step, "withDockerRegistry([credentialsId: 'hubcreds']) {\n // some block\n}"); - step = new RegistryEndpointStep(new DockerRegistryEndpoint("https://myreg/", "mycreds")); - step.setToolName("ce"); - st.assertRoundTrip(step, "withDockerRegistry(registry: [credentialsId: 'mycreds', url: 'https://myreg/'], toolName: 'ce') {\n // some block\n}"); - IdCredentials registryCredentials = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, "registryCreds", null, "me", "pass"); - CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), registryCredentials); - StepConfigTester sct = new StepConfigTester(r); - // TODO use of DescribableModel here is gratuitous; the rest should just test the UI - Map registryConfig = new TreeMap<>(); - registryConfig.put("url", "https://docker.my.com/"); - registryConfig.put("credentialsId", registryCredentials.getId()); - Map config = Collections.singletonMap("registry", registryConfig); - step = new DescribableModel<>(RegistryEndpointStep.class).instantiate(config); - step = sct.configRoundTrip(step); - DockerRegistryEndpoint registry = step.getRegistry(); - assertNotNull(registry); - assertEquals("https://docker.my.com/", registry.getUrl()); - assertEquals(registryCredentials.getId(), registry.getCredentialsId()); - assertEquals(config, new DescribableModel<>(RegistryEndpointStep.class).uninstantiate(step)); + { // Recommended syntax. + SnippetizerTester st = new SnippetizerTester(r); + RegistryEndpointStep step = new RegistryEndpointStep(new DockerRegistryEndpoint("https://myreg/", null)); + st.assertRoundTrip(step, "withDockerRegistry(url: 'https://myreg/') {\n // some block\n}"); + step = new RegistryEndpointStep(new DockerRegistryEndpoint(null, "hubcreds")); + st.assertRoundTrip(step, "withDockerRegistry(credentialsId: 'hubcreds') {\n // some block\n}"); + step = new RegistryEndpointStep(new DockerRegistryEndpoint("https://myreg/", "mycreds")); + step.setToolName("ce"); + st.assertRoundTrip(step, "withDockerRegistry(credentialsId: 'mycreds', toolName: 'ce', url: 'https://myreg/') {\n // some block\n}"); + } + { // Older syntax. + WorkflowJob p = r.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition("node {withDockerRegistry(registry: [url: 'https://docker.my.com/'], toolName: 'irrelevant') {}}", true)); + r.buildAndAssertSuccess(p); + p.setDefinition(new CpsFlowDefinition("node {withDockerRegistry(registry: [url: 'https://docker.my.com/']) {}}", true)); + r.buildAndAssertSuccess(p); + p.setDefinition(new CpsFlowDefinition("node {withDockerRegistry([url: 'https://docker.my.com/']) {}}", true)); + r.buildAndAssertSuccess(p); + // and new, just in case SnippetizerTester is faking it: + p.setDefinition(new CpsFlowDefinition("node {withDockerRegistry(url: 'https://docker.my.com/') {}}", true)); + r.buildAndAssertSuccess(p); + } + { // UI form. + IdCredentials registryCredentials = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, "registryCreds", null, "me", "pass"); + CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), registryCredentials); + StepConfigTester sct = new StepConfigTester(r); + RegistryEndpointStep step = new RegistryEndpointStep(new DockerRegistryEndpoint("https://docker.my.com/", "registryCreds")); + step = sct.configRoundTrip(step); + DockerRegistryEndpoint registry = step.getRegistry(); + assertNotNull(registry); + assertEquals("https://docker.my.com/", registry.getUrl()); + assertEquals("registryCreds", registry.getCredentialsId()); + // TODO check toolName + } } } From ce736d313df0beb8b667e5eaf4ba4b8494f75a87 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 17 May 2018 12:39:42 -0400 Subject: [PATCH 3/6] =?UTF-8?q?Also=20noticed=20that=20snippet=20generatio?= =?UTF-8?q?n=20was=20inappropriate=20for=20using=20the=20=E2=80=9Cdefault?= =?UTF-8?q?=E2=80=9D=20Docker=20tool.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/docker/workflow/RegistryEndpointStep.java | 3 ++- .../plugins/docker/workflow/RegistryEndpointStepTest.java | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java b/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java index 46d24dcd2..0eb5e3395 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java @@ -28,6 +28,7 @@ import hudson.Extension; import hudson.FilePath; import hudson.Launcher; +import hudson.Util; import hudson.model.Job; import hudson.model.Node; import hudson.model.TaskListener; @@ -67,7 +68,7 @@ public String getToolName() { } @DataBoundSetter public void setToolName(String toolName) { - this.toolName = toolName; + this.toolName = Util.fixEmpty(toolName); } public static class Execution extends AbstractEndpointStepExecution { diff --git a/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java b/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java index 660ba9bb4..201eb8199 100644 --- a/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java @@ -50,6 +50,7 @@ public class RegistryEndpointStepTest { { // Recommended syntax. SnippetizerTester st = new SnippetizerTester(r); RegistryEndpointStep step = new RegistryEndpointStep(new DockerRegistryEndpoint("https://myreg/", null)); + step.setToolName(""); st.assertRoundTrip(step, "withDockerRegistry(url: 'https://myreg/') {\n // some block\n}"); step = new RegistryEndpointStep(new DockerRegistryEndpoint(null, "hubcreds")); st.assertRoundTrip(step, "withDockerRegistry(credentialsId: 'hubcreds') {\n // some block\n}"); From c9ae684ab42cf5df3471c271abf7bf7981a70ab0 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 17 May 2018 12:42:18 -0400 Subject: [PATCH 4/6] Whitespace changes to minimize diff. --- .../plugins/docker/workflow/RegistryEndpointStep.java | 8 +++----- .../plugins/docker/workflow/RegistryEndpointStepTest.java | 3 +-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java b/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java index 0eb5e3395..d271f4b52 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java @@ -50,7 +50,7 @@ import org.kohsuke.stapler.DataBoundSetter; public class RegistryEndpointStep extends AbstractStepImpl { - + private final @Nonnull DockerRegistryEndpoint registry; private @CheckForNull String toolName; @@ -111,8 +111,7 @@ public DescriptorImpl() { return true; } - @Override - public UninstantiatedDescribable uninstantiate(Step step) throws UnsupportedOperationException { + @Override public UninstantiatedDescribable uninstantiate(Step step) throws UnsupportedOperationException { RegistryEndpointStep s = (RegistryEndpointStep) step; Map args = new TreeMap<>(); args.put("url", s.registry.getUrl()); @@ -122,8 +121,7 @@ public UninstantiatedDescribable uninstantiate(Step step) throws UnsupportedOper return new UninstantiatedDescribable(args); } - @Override - public Step newInstance(Map arguments) throws Exception { + @Override public Step newInstance(Map arguments) throws Exception { if (arguments.containsKey("url") || arguments.containsKey("credentialsId")) { if (arguments.containsKey("registry")) { throw new IllegalArgumentException("cannot mix url/credentialsId with registry"); diff --git a/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java b/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java index 201eb8199..1e5590444 100644 --- a/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStepTest.java @@ -21,7 +21,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - package org.jenkinsci.plugins.docker.workflow; import com.cloudbees.plugins.credentials.CredentialsProvider; @@ -84,4 +83,4 @@ public class RegistryEndpointStepTest { } } -} +} \ No newline at end of file From 15c0acc356cf0da426eaac646928576ab697adf3 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 17 May 2018 12:45:12 -0400 Subject: [PATCH 5/6] Noting that usability of ServerEndpointStep could benefit from a similar change, though it is not needed for a bug fix. --- .../jenkinsci/plugins/docker/workflow/ServerEndpointStep.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/jenkinsci/plugins/docker/workflow/ServerEndpointStep.java b/src/main/java/org/jenkinsci/plugins/docker/workflow/ServerEndpointStep.java index 4bbe854bb..8ddf52e15 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/ServerEndpointStep.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/ServerEndpointStep.java @@ -85,6 +85,8 @@ public DescriptorImpl() { return true; } + // TODO allow DockerServerEndpoint fields to be inlined, as in RegistryEndpointStep, so Docker.groovy can say simply: script.withDockerServer(uri: uri, credentialsId: credentialsId) {…} + } } From ebdfe94ae89d6396d4ba2b8b7331333ad2f14141 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 17 May 2018 13:35:45 -0400 Subject: [PATCH 6/6] @svanoort points out that it is safer to copy the arguments map before mutating it. --- .../jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java b/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java index d271f4b52..47ced2eb5 100644 --- a/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java +++ b/src/main/java/org/jenkinsci/plugins/docker/workflow/RegistryEndpointStep.java @@ -34,6 +34,7 @@ import hudson.model.TaskListener; import java.io.IOException; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import javax.annotation.CheckForNull; @@ -122,6 +123,7 @@ public DescriptorImpl() { } @Override public Step newInstance(Map arguments) throws Exception { + arguments = new HashMap<>(arguments); if (arguments.containsKey("url") || arguments.containsKey("credentialsId")) { if (arguments.containsKey("registry")) { throw new IllegalArgumentException("cannot mix url/credentialsId with registry");