diff --git a/rd-api-client/src/main/java/org/rundeck/client/api/RundeckApi.java b/rd-api-client/src/main/java/org/rundeck/client/api/RundeckApi.java index e5821c7e..d7508fbe 100644 --- a/rd-api-client/src/main/java/org/rundeck/client/api/RundeckApi.java +++ b/rd-api-client/src/main/java/org/rundeck/client/api/RundeckApi.java @@ -388,7 +388,29 @@ Call exportProjectDownload( ); /** - * Export project archive (<=v18) + * Import project archive (<=v18) + * + * @param project project + * + * @return archive response + */ + @Headers("Accept: application/json") + @PUT("project/{project}/import") + Call importProjectArchive( + @Path("project") String project, + @Query("jobUuidOption") String jobUuidOption, + @Query("importExecutions") Boolean importExecutions, + @Query("importConfig") Boolean importConfig, + @Query("importACL") Boolean importACL, + @Query("importScm") Boolean importScm, + @Query("importWebhooks") Boolean importWebhooks, + @Query("whkRegenAuthTokens") Boolean whkRegenAuthTokens, + @Query("importNodesSources") Boolean importNodesSources, + @Body RequestBody body + ); + + /** + * Import project archive (<=v18) * * @param project project * @@ -406,6 +428,7 @@ Call importProjectArchive( @Query("importWebhooks") Boolean importWebhooks, @Query("whkRegenAuthTokens") Boolean whkRegenAuthTokens, @Query("importNodesSources") Boolean importNodesSources, + @QueryMap Map params, @Body RequestBody body ); diff --git a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/projects/Archives.java b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/projects/Archives.java index 4291a760..e0739ba9 100644 --- a/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/projects/Archives.java +++ b/rd-cli-tool/src/main/java/org/rundeck/client/tool/commands/projects/Archives.java @@ -37,9 +37,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.function.BooleanSupplier; /** @@ -51,7 +49,7 @@ public class Archives extends BaseCommand { @Getter @Setter static class BaseOptions extends ProjectRequiredNameOptions{ - @CommandLine.Option(names = {"-f", "--file"}, description = "Output file path", required = true) + @CommandLine.Option(names = {"-f", "--file"}, description = "Output/Import file path", required = true) @Getter private File file; } @@ -100,6 +98,19 @@ static class ArchiveImportOpts extends BaseOptions{ "import errors are treated as failures.") boolean strict; + + @CommandLine.Option( + names = {"--component", "-I"}, + arity = "0..*", + description = "Enable named import components, such as project-tours (enterprise)") + Set components; + + @CommandLine.Option( + names = {"--options", "-O"}, + arity = "0..*", + description = "Set options for enabled components, in the form name.key=value") + Map componentOptions; + } @CommandLine.Command(description = "Import a project archive", name = "import") @@ -116,6 +127,17 @@ public boolean importArchive(@CommandLine.Mixin ArchiveImportOpts opts) throws I } RequestBody body = RequestBody.create(input, Client.MEDIA_TYPE_ZIP); + Map extraCompOpts = new HashMap<>(); + if (opts.components != null && opts.components.size() > 0) { + for (String component : opts.components) { + extraCompOpts.put("importComponents." + component, "true"); + } + } + if (opts.componentOptions != null && opts.componentOptions.size() > 0) { + for (Map.Entry stringStringEntry : opts.componentOptions.entrySet()) { + extraCompOpts.put("importOpts." + stringStringEntry.getKey(), stringStringEntry.getValue()); + } + } String project = validate(opts); ProjectImportStatus status = apiCall(api -> api.importProjectArchive( project, @@ -127,6 +149,7 @@ public boolean importArchive(@CommandLine.Mixin ArchiveImportOpts opts) throws I opts.isIncludeWebhooks(), opts.isWhkRegenAuthTokens(), opts.isIncludeNodeSources(), + extraCompOpts, body )); boolean anyerror = false; diff --git a/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/projects/ArchivesSpec.groovy b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/projects/ArchivesSpec.groovy new file mode 100644 index 00000000..aacf0576 --- /dev/null +++ b/rd-cli-tool/src/test/groovy/org/rundeck/client/tool/commands/projects/ArchivesSpec.groovy @@ -0,0 +1,80 @@ +package org.rundeck.client.tool.commands.projects + +import groovy.transform.CompileStatic +import okhttp3.ResponseBody +import org.rundeck.client.api.RundeckApi +import org.rundeck.client.api.model.ProjectImportStatus +import org.rundeck.client.tool.CommandOutput +import org.rundeck.client.tool.RdApp +import org.rundeck.client.tool.commands.RdToolImpl +import org.rundeck.client.tool.options.ProjectRequiredNameOptions +import org.rundeck.client.util.Client +import org.rundeck.client.util.RdClientConfig +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.converter.jackson.JacksonConverterFactory +import retrofit2.mock.Calls +import spock.lang.Specification + +class ArchivesSpec extends Specification { + File tempFile + + def setup() { + tempFile = File.createTempFile('ArchivesSpec-test', 'zip') + } + + def cleanup() { + if (tempFile.exists()) { + tempFile.delete() + } + } + + def "import component options set in url"() { + + def api = Mock(RundeckApi) + + def retrofit = new Retrofit.Builder() + .addConverterFactory(JacksonConverterFactory.create()) + .baseUrl('http://example.com/fake/').build() + def out = Mock(CommandOutput) + def client = new Client(api, retrofit, null, null, 18, true, null) + + def rdapp = Mock(RdApp) { + getClient() >> client + getAppConfig() >> Mock(RdClientConfig) + } + def rdTool = new RdToolImpl(rdapp) + + def sut = new Archives() + sut.rdOutput = out + sut.rdTool = rdTool + def opts = new Archives.ArchiveImportOpts() + opts.components = ['test-comp'].toSet() + opts.componentOptions = ['test-comp.key': 'value'] + opts.file = tempFile + opts.project = 'Aproj' + + + when: + def result = sut.importArchive(opts) + + then: + 1 * api.importProjectArchive( + 'Aproj', + _, + _, + _, + _, + _, + _, + _, + _, + [ + 'importComponents.test-comp': 'true', + 'importOpts.test-comp.key' : 'value', + ], + _ + ) >> Calls.response(new ProjectImportStatus()) + 0 * api._(*_) + } +}