diff --git a/src/main/java/org/rundeck/client/api/RundeckApi.java b/src/main/java/org/rundeck/client/api/RundeckApi.java index 05b4bee8..90b004f1 100644 --- a/src/main/java/org/rundeck/client/api/RundeckApi.java +++ b/src/main/java/org/rundeck/client/api/RundeckApi.java @@ -25,7 +25,9 @@ public interface RundeckApi { Call> listJobs( @Path("project") String project, @Query("jobFilter") String jobName, - @Query("groupPath") String groupPath + @Query("groupPath") String groupPath, + @Query("jobExactFilter") String jobNameExact, + @Query("groupPathExact") String groupPathExact ); @Headers("Accept: application/json") @@ -52,6 +54,8 @@ Call exportJobs( @Path("project") String project, @Query("jobFilter") String jobName, @Query("groupPath") String groupPath, + @Query("jobExactFilter") String jobNameExact, + @Query("groupPathExact") String groupPathExact, @Query("format") String format ); diff --git a/src/main/java/org/rundeck/client/tool/commands/Jobs.java b/src/main/java/org/rundeck/client/tool/commands/Jobs.java index fb7acd22..0e6c4b0b 100644 --- a/src/main/java/org/rundeck/client/tool/commands/Jobs.java +++ b/src/main/java/org/rundeck/client/tool/commands/Jobs.java @@ -64,7 +64,9 @@ public boolean purge(Purge options, CommandOutput output) throws IOException, In listCall = getClient().getService().listJobs( projectOrEnv(options), options.getJob(), - options.getGroup() + options.getGroup(), + options.getJobExact(), + options.getGroupExact() ); List body = getClient().checkError(listCall); for (JobItem jobItem : body) { @@ -77,6 +79,11 @@ public boolean purge(Purge options, CommandOutput output) throws IOException, In } if (!options.isConfirm()) { //request confirmation + if (null == System.console()) { + output.error("No user interaction available. Use --confirm to confirm purge without user interaction"); + output.warning(String.format("Not deleting %d jobs", ids.size())); + return false; + } String s = System.console().readLine("Really delete %d Jobs? (y/N) ", ids.size()); if (!"y".equals(s)) { @@ -172,6 +179,8 @@ public void list(ListOpts options, CommandOutput output) throws IOException, Inp project, options.getJob(), options.getGroup(), + options.getJobExact(), + options.getGroupExact(), options.getFormat() ); } @@ -206,7 +215,9 @@ public void list(ListOpts options, CommandOutput output) throws IOException, Inp listCall = getClient().getService().listJobs( project, options.getJob(), - options.getGroup() + options.getGroup(), + options.getJobExact(), + options.getGroupExact() ); } List body = getClient().checkError(listCall); diff --git a/src/main/java/org/rundeck/client/tool/commands/Run.java b/src/main/java/org/rundeck/client/tool/commands/Run.java index fd86e50d..4e682429 100644 --- a/src/main/java/org/rundeck/client/tool/commands/Run.java +++ b/src/main/java/org/rundeck/client/tool/commands/Run.java @@ -39,7 +39,13 @@ public boolean run(RunBaseOptions options, CommandOutput out) throws IOException } String job = options.getJob(); String[] parts = Jobs.splitJobNameParts(job); - Call> listCall = getClient().getService().listJobs(projectOrEnv(options), parts[1], parts[0]); + Call> listCall = getClient().getService().listJobs( + projectOrEnv(options), + null, + null, + parts[1], + parts[0] + ); List jobItems = getClient().checkError(listCall); if (jobItems.size() != 1) { out.error(String.format("Could not find a unique job with name: %s%n", job)); diff --git a/src/main/java/org/rundeck/client/tool/options/JobListOptions.java b/src/main/java/org/rundeck/client/tool/options/JobListOptions.java index aea7159d..93b4a5bf 100644 --- a/src/main/java/org/rundeck/client/tool/options/JobListOptions.java +++ b/src/main/java/org/rundeck/client/tool/options/JobListOptions.java @@ -10,16 +10,26 @@ public interface JobListOptions extends JobBaseOptions{ - @Option(shortName = "j", longName = "job", description = "Job name") + @Option(shortName = "j", longName = "job", description = "Job name filter") String getJob(); boolean isJob(); - @Option(shortName = "g", longName = "group", description = "Job Group") + @Option(shortName = "g", longName = "group", description = "Job Group filter") String getGroup(); boolean isGroup(); + @Option(shortName = "J", longName = "jobxact", description = "Exact Job name") + String getJobExact(); + + boolean isJobExact(); + + @Option(shortName = "G", longName = "groupxact", description = "Exact Job Group") + String getGroupExact(); + + boolean isGroupExact(); + @Option(shortName = "i", longName = "idlist", description = "Comma separated list of Job IDs") String getIdlist(); diff --git a/src/test/groovy/org/rundeck/client/tool/commands/JobsSpec.groovy b/src/test/groovy/org/rundeck/client/tool/commands/JobsSpec.groovy new file mode 100644 index 00000000..0c6c6c9d --- /dev/null +++ b/src/test/groovy/org/rundeck/client/tool/commands/JobsSpec.groovy @@ -0,0 +1,141 @@ +package org.rundeck.client.tool.commands + +import com.simplifyops.toolbelt.CommandOutput +import okhttp3.MediaType +import okhttp3.ResponseBody +import org.rundeck.client.api.RundeckApi +import org.rundeck.client.api.model.DeleteJobsResult +import org.rundeck.client.api.model.JobItem +import org.rundeck.client.util.Client +import retrofit2.Response +import retrofit2.Retrofit +import retrofit2.mock.Calls +import spock.lang.Specification + +/** + * @author greg + * @since 12/13/16 + */ +class JobsSpec extends Specification { + File tempFile = File.createTempFile('data', 'out') + + def setup() { + tempFile = File.createTempFile('data', 'out') + } + + def cleanup() { + if (tempFile.exists()) { + tempFile.delete() + } + } + + def "job list with input parameters"() { + given: + def api = Mock(RundeckApi) + + def opts = Mock(Jobs.ListOpts) { + isJob() >> true + isProject() >> true + getProject() >> 'ProjectName' + getJobExact() >> jobexact + getGroupExact() >> groupexact + getJob() >> job + getGroup() >> group + } + def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() + def client = new Client(api, retrofit, 17) + def hasclient = Mock(ApiCommand.HasClient) { + getClient() >> client + } + Jobs jobs = new Jobs(hasclient) + def out = Mock(CommandOutput) + when: + jobs.list(opts, out) + + then: + 1 * api.listJobs('ProjectName', job, group, jobexact, groupexact) >> + Calls.response([new JobItem(id: 'fakeid')]) + 0 * api._(*_) + + where: + job | group | jobexact | groupexact + 'a' | 'b/c' | null | null + null | null | 'a' | 'b/c' + } + + def "job list write to file with input parameters"() { + given: + def api = Mock(RundeckApi) + def opts = Mock(Jobs.ListOpts) { + isJob() >> true + isProject() >> true + getProject() >> 'ProjectName' + getJobExact() >> jobexact + getGroupExact() >> groupexact + getJob() >> job + getGroup() >> group + isFile() >> true + getFormat() >> 'xml' + getFile() >> tempFile + } + def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() + def client = new Client(api, retrofit, 17) + def hasclient = Mock(ApiCommand.HasClient) { + getClient() >> client + } + Jobs jobs = new Jobs(hasclient) + def out = Mock(CommandOutput) + when: + jobs.list(opts, out) + + then: + 1 * api.exportJobs('ProjectName', job, group, jobexact, groupexact, 'xml') >> + Calls.response(ResponseBody.create(MediaType.parse('application/xml'), 'abc')) + 0 * api._(*_) + tempFile.exists() + tempFile.text == 'abc' + + where: + job | group | jobexact | groupexact + 'a' | 'b/c' | null | null + null | null | 'a' | 'b/c' + } + + + def "job purge with input parameters"() { + given: + def api = Mock(RundeckApi) + + def opts = Mock(Jobs.Purge) { + isJob() >> true + isProject() >> true + getProject() >> 'ProjectName' + getJobExact() >> jobexact + getGroupExact() >> groupexact + getJob() >> job + getGroup() >> group + isConfirm() >> true + } + def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() + def client = new Client(api, retrofit, 17) + def hasclient = Mock(ApiCommand.HasClient) { + getClient() >> client + } + Jobs jobs = new Jobs(hasclient) + def out = Mock(CommandOutput) + when: + def result = jobs.purge(opts, out) + + then: + 1 * api.listJobs('ProjectName', job, group, jobexact, groupexact) >> + Calls.response([new JobItem(id: 'fakeid')]) + 1 * api.deleteJobs(['fakeid']) >> Calls.response(new DeleteJobsResult(allsuccessful: true)) + 0 * api._(*_) + result + + where: + job | group | jobexact | groupexact + 'a' | 'b/c' | null | null + null | null | 'a' | 'b/c' + } +} diff --git a/src/test/groovy/org/rundeck/client/tool/commands/RunSpec.groovy b/src/test/groovy/org/rundeck/client/tool/commands/RunSpec.groovy new file mode 100644 index 00000000..967c19f9 --- /dev/null +++ b/src/test/groovy/org/rundeck/client/tool/commands/RunSpec.groovy @@ -0,0 +1,47 @@ +package org.rundeck.client.tool.commands + +import com.simplifyops.toolbelt.CommandOutput +import org.rundeck.client.api.RundeckApi +import org.rundeck.client.api.model.Execution +import org.rundeck.client.api.model.JobItem +import org.rundeck.client.tool.options.RunBaseOptions +import org.rundeck.client.util.Client +import retrofit2.Retrofit +import retrofit2.mock.Calls +import spock.lang.Specification + +/** + * @author greg + * @since 12/13/16 + */ +class RunSpec extends Specification { + def "run command -j queries for exact job name and group"() { + + given: + def api = Mock(RundeckApi) + + def opts = Mock(RunBaseOptions) { + isJob() >> true + isProject() >> true + getProject() >> 'ProjectName' + getJob() >> 'a group/path/a job' + } + def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build() + def client = new Client(api, retrofit, 17) + def hasclient = Mock(ApiCommand.HasClient) { + getClient() >> client + } + Run run = new Run(hasclient) + def out = Mock(CommandOutput) + when: + def result = run.run(opts, out) + + then: + 1 * api.listJobs('ProjectName', null, null, 'a job', 'a group/path') >> + Calls.response([new JobItem(id: 'fakeid')]) + 1 * api.runJob('fakeid', null, null, null, null) >> Calls.response(new Execution(id: 123, description: '')) + 0 * api._(*_) + result + + } +}