Skip to content

Commit

Permalink
Merge pull request #540 from rundeck/enh/jobs-json-format
Browse files Browse the repository at this point in the history
RUN-2064: Add jobs json format
  • Loading branch information
gschueler authored Nov 22, 2023
2 parents 276ae46 + 77a6dbc commit 6458240
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import lombok.Getter;
import lombok.Setter;
import org.rundeck.client.tool.CommandOutput;
import okhttp3.MediaType;
import org.rundeck.client.tool.extension.BaseCommand;
import picocli.CommandLine;
import org.rundeck.client.api.model.scheduler.ForecastJobItem;
Expand All @@ -41,7 +41,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
Expand Down Expand Up @@ -157,7 +156,7 @@ public int purge(@CommandLine.Mixin Purge options,
return 0;
}

@CommandLine.Command(description = "Load Job definitions from a file in XML or YAML format.")
@CommandLine.Command(description = "Load Job definitions from a file in XML, YAML or JSON format.")
public int load(
@CommandLine.Mixin JobLoadOptions options,
@CommandLine.Mixin JobFileOptions fileOptions,
Expand All @@ -171,10 +170,15 @@ public int load(
if (!input.canRead() || !input.isFile()) {
throw new InputError(String.format("File is not readable or does not exist: %s", input));
}

MediaType mediaType = Client.MEDIA_TYPE_XML;
if (fileOptions.getFormat() == JobFileOptions.Format.yaml) {
mediaType = Client.MEDIA_TYPE_YAML;
} else if (fileOptions.getFormat() == JobFileOptions.Format.json) {
mediaType = Client.MEDIA_TYPE_JSON;
}
RequestBody requestBody = RequestBody.create(
input,
fileOptions.getFormat() == JobFileOptions.Format.xml ? Client.MEDIA_TYPE_XML : Client.MEDIA_TYPE_YAML
mediaType
);

String project = getRdTool().projectOrEnv(projectNameOptions);
Expand All @@ -188,17 +192,17 @@ public int load(

List<JobLoadItem> failed = importResult.getFailed();

printLoadResult(importResult.getSucceeded(), "Succeeded", getRdOutput(), verboseOption.isVerbose());
printLoadResult(importResult.getSkipped(), "Skipped", getRdOutput(), verboseOption.isVerbose());
printLoadResult(failed, "Failed", getRdOutput(), verboseOption.isVerbose());
printLoadResult(importResult.getSucceeded(), "Succeeded", verboseOption.isVerbose());
printLoadResult(importResult.getSkipped(), "Skipped", verboseOption.isVerbose());
printLoadResult(failed, "Failed", verboseOption.isVerbose());

return (failed == null || failed.isEmpty()) ? 0 : 1;
}

private void printLoadResult(
final List<JobLoadItem> list,
final String title,
CommandOutput output, final boolean isVerbose
final boolean isVerbose
) {
if (null != list && !list.isEmpty()) {
getRdOutput().info(String.format("%d Jobs %s:%n", list.size(), title));
Expand Down Expand Up @@ -240,9 +244,27 @@ public void list(
));
}
try (ResponseBody body = body1) {
if ((jobFileOptions.getFormat() != JobFileOptions.Format.yaml ||
!ServiceClient.hasAnyMediaType(body.contentType(), Client.MEDIA_TYPE_YAML, Client.MEDIA_TYPE_TEXT_YAML)) &&
!ServiceClient.hasAnyMediaType(body.contentType(), Client.MEDIA_TYPE_XML, Client.MEDIA_TYPE_TEXT_XML)) {
if ((
jobFileOptions.getFormat() == JobFileOptions.Format.yaml
&& !ServiceClient.hasAnyMediaType(
body.contentType(),
Client.MEDIA_TYPE_YAML,
Client.MEDIA_TYPE_TEXT_YAML
)
) || (
jobFileOptions.getFormat() == JobFileOptions.Format.json
&& !ServiceClient.hasAnyMediaType(
body.contentType(),
Client.MEDIA_TYPE_JSON
)
) || (
jobFileOptions.getFormat() == JobFileOptions.Format.xml
&& !ServiceClient.hasAnyMediaType(
body.contentType(),
Client.MEDIA_TYPE_XML,
Client.MEDIA_TYPE_TEXT_XML
)
)) {

throw new IllegalStateException("Unexpected response format: " + body.contentType());
}
Expand Down Expand Up @@ -386,7 +408,7 @@ public static String[] splitJobNameParts(final String job) {
int i = job.lastIndexOf('/');
String group = job.substring(0, i);
String name = job.substring(i + 1);
if ("".equals(group.trim())) {
if (group.trim().isEmpty()) {
group = null;
}
return new String[]{group, name};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public boolean isFile() {

public enum Format {
xml,
yaml
yaml,
json
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.rundeck.client.tool.commands

import okhttp3.RequestBody
import org.rundeck.client.api.model.BulkToggleJobExecutionResponse
import org.rundeck.client.api.model.BulkToggleJobScheduleResponse
import org.rundeck.client.api.model.DeleteJob
Expand Down Expand Up @@ -148,6 +149,84 @@ class JobsSpec extends Specification {
null | null | 'a' | 'b/c'
}

def "job list write to file with format #format"() {
given:
def api = Mock(RundeckApi)
RdTool rdTool = setupMock(api)
def out = Mock(CommandOutput)
Jobs command = new Jobs()
command.rdTool = rdTool
command.rdOutput = out

def opts = new JobListOptions()
opts.project = 'ProjectName'
opts.setJob(job)
opts.setGroup(group)



def fileOptions = new JobFileOptions(
format: format,
file: tempFile
)
when:
command.list(new JobOutputFormatOption(), fileOptions, opts)

then:
1 * api.exportJobs('ProjectName', job, group, null, null, format.toString()) >>
Calls.response(ResponseBody.create( 'abc',MediaType.parse(contentType)))
0 * api._(*_)
tempFile.exists()
tempFile.text == 'abc'

where:
job = 'a'
group = 'b/c'
format | contentType
JobFileOptions.Format.xml | 'application/xml'
JobFileOptions.Format.xml | 'text/xml'
JobFileOptions.Format.json | 'application/json'
JobFileOptions.Format.yaml | 'application/yaml'
JobFileOptions.Format.yaml | 'text/yaml'
}
def "job list write to file with incorrect response format causes error #format"() {
given:
def api = Mock(RundeckApi)
RdTool rdTool = setupMock(api)
def out = Mock(CommandOutput)
Jobs command = new Jobs()
command.rdTool = rdTool
command.rdOutput = out

def opts = new JobListOptions()
opts.project = 'ProjectName'
opts.setJob(job)
opts.setGroup(group)



def fileOptions = new JobFileOptions(
format: format,
file: tempFile
)
when:
command.list(new JobOutputFormatOption(), fileOptions, opts)

then:
1 * api.exportJobs('ProjectName', job, group, null, null, format.toString()) >>
Calls.response(ResponseBody.create( 'abc',MediaType.parse(contentType)))
0 * api._(*_)
IllegalStateException e = thrown()

where:
job = 'a'
group = 'b/c'
format | contentType
JobFileOptions.Format.xml | 'application/yaml'
JobFileOptions.Format.json | 'application/xml'
JobFileOptions.Format.yaml | 'text/json'
}

@Unroll
def "jobs #action behavior"() {
given:
Expand Down Expand Up @@ -400,7 +479,7 @@ class JobsSpec extends Specification {
def "job load success"() {
given:
def opts = new JobFileOptions()
opts.format= JobFileOptions.Format.yaml
opts.format= format
opts.file=tempFile


Expand All @@ -415,14 +494,22 @@ class JobsSpec extends Specification {
def result = command.load(new JobLoadOptions(), opts,new ProjectNameOptions(project:'ProjectName'),new VerboseOption())

then:
1 * api.loadJobs('ProjectName', _, 'yaml', _, _) >>
1 * api.loadJobs('ProjectName', { RequestBody body->
body.contentType()==expectedType
}, format.toString(), _, _) >>
Calls.response(new ImportResult(succeeded: [new JobLoadItem( id:'jobid',name: 'Job Name')], skipped: [], failed: []))
0 * api._(*_)
1 * out.info('1 Jobs Succeeded:\n')
1 * out.output(['jobid Job Name'])
0 * out._(*_)
result == 0

where:
format | expectedType
JobFileOptions.Format.yaml | Client.MEDIA_TYPE_YAML
JobFileOptions.Format.xml | Client.MEDIA_TYPE_XML
JobFileOptions.Format.json | Client.MEDIA_TYPE_JSON

}

def "job load with errors verbose output"() {
Expand Down

0 comments on commit 6458240

Please sign in to comment.