From 2a38eabf3875fd09bcd4a129639f77377288ab8d Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 9 Sep 2016 15:57:12 -0700 Subject: [PATCH 1/3] Add `rd jobs info -i ID` API v18 --- .../org/rundeck/client/api/RundeckApi.java | 12 +++++++- .../org/rundeck/client/api/model/JobItem.java | 27 ++++++++++++++++++ .../client/api/model/ScheduledJobItem.java | 28 ++++++++++++------- .../rundeck/client/tool/commands/Jobs.java | 14 ++++++++++ 4 files changed, 70 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/rundeck/client/api/RundeckApi.java b/src/main/java/org/rundeck/client/api/RundeckApi.java index 8296f1cc..d0c19bf1 100644 --- a/src/main/java/org/rundeck/client/api/RundeckApi.java +++ b/src/main/java/org/rundeck/client/api/RundeckApi.java @@ -33,6 +33,17 @@ Call> listJobs( @Query("idlist") String idlist ); + /** + * new api + * @param jobid + * @return + */ + @Headers("Accept: application/json") + @GET("job/{jobid}/info") + Call getJobInfo( + @Path("jobid") String jobid + ); + @GET("project/{project}/jobs/export") Call exportJobs( @@ -445,7 +456,6 @@ Call createToken( * Delete a Token * * @param id token - * */ @Headers("Accept: application/json") @DELETE("token/{id}") diff --git a/src/main/java/org/rundeck/client/api/model/JobItem.java b/src/main/java/org/rundeck/client/api/model/JobItem.java index 7745a318..d28b5470 100644 --- a/src/main/java/org/rundeck/client/api/model/JobItem.java +++ b/src/main/java/org/rundeck/client/api/model/JobItem.java @@ -5,6 +5,10 @@ import org.simpleframework.xml.Element; import org.simpleframework.xml.Root; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + /** * Created by greg on 3/28/16. */ @@ -25,6 +29,9 @@ public class JobItem { @Element(required = false) private String permalink; + @Attribute(required = false) + private Long averageDuration; + public String getId() { return id; } @@ -94,7 +101,27 @@ public String toString() { '}'; } + public Map toMap() { + HashMap map = new LinkedHashMap<>(); + map.put("id", getId()); + map.put("name", getName()); + map.put("group", getGroup()); + map.put("project", getProject()); + if(null!=getAverageDuration()){ + map.put("averageDuration", getAverageDuration()); + } + return map; + } + public String toBasicString() { return String.format("[%s] %s%s", id, group != null ? group + "/" : "", name); } + + public Long getAverageDuration() { + return averageDuration; + } + + public void setAverageDuration(Long averageDuration) { + this.averageDuration = averageDuration; + } } diff --git a/src/main/java/org/rundeck/client/api/model/ScheduledJobItem.java b/src/main/java/org/rundeck/client/api/model/ScheduledJobItem.java index f5e0b82b..7be56088 100644 --- a/src/main/java/org/rundeck/client/api/model/ScheduledJobItem.java +++ b/src/main/java/org/rundeck/client/api/model/ScheduledJobItem.java @@ -16,7 +16,7 @@ public class ScheduledJobItem extends JobItem { private boolean scheduled; private boolean scheduleEnabled; private boolean enabled; - private boolean serverOwner; + private Boolean serverOwner; public String getServerNodeUUID() { @@ -51,23 +51,31 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } - public boolean isServerOwner() { + public Boolean isServerOwner() { return serverOwner; } - public void setServerOwner(boolean serverOwner) { + public void setServerOwner(Boolean serverOwner) { this.serverOwner = serverOwner; } public Map toMap() { HashMap map = new LinkedHashMap<>(); - map.put("job", toBasicString()); - map.put("project", getProject()); - map.put("serverNodeUUID", getServerNodeUUID()); - map.put("scheduled", scheduled); - map.put("scheduleEnabled", scheduleEnabled); - map.put("enabled", enabled); - map.put("serverOwner", serverOwner); + + + HashMap detail = new LinkedHashMap<>(super.toMap()); + detail.put("scheduled", scheduled); + detail.put("scheduleEnabled", scheduleEnabled); + detail.put("enabled", enabled); + + map.put("job", detail); + + if (null != serverOwner && null != getServerNodeUUID()) { + HashMap schedule = new LinkedHashMap<>(); + schedule.put("serverNodeUUID", getServerNodeUUID()); + schedule.put("serverOwner", serverOwner); + map.put("scheduler", schedule); + } return map; } 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 4c058a22..d3830d1b 100644 --- a/src/main/java/org/rundeck/client/tool/commands/Jobs.java +++ b/src/main/java/org/rundeck/client/tool/commands/Jobs.java @@ -1,6 +1,7 @@ package org.rundeck.client.tool.commands; import com.lexicalscope.jewel.cli.CommandLineInterface; +import com.lexicalscope.jewel.cli.Option; import com.simplifyops.toolbelt.Command; import com.simplifyops.toolbelt.CommandOutput; import okhttp3.RequestBody; @@ -18,6 +19,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; /** * Created by greg on 3/28/16. @@ -182,6 +184,18 @@ public void list(ListOpts options, CommandOutput output) throws IOException { } } + @CommandLineInterface(application = "info") interface InfoOpts { + + @Option(shortName = "i", longName = "id", description = "Job ID") + String getId(); + } + + @Command(description = "Get info about a Job by ID (API v18)") + public void info(InfoOpts options, CommandOutput output) throws IOException { + ScheduledJobItem body = client.checkError(client.getService().getJobInfo(options.getId())); + output.output(body.toMap()); + } + /** * Split a job group/name into group then name parts * From 1fa888ede64ee9caa0a8472d6cf6899e1ccc0e17 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 21 Oct 2016 12:53:10 -0700 Subject: [PATCH 2/3] expose apiVersion used from Client --- src/main/java/org/rundeck/client/Rundeck.java | 22 ++++++++++++++++--- .../java/org/rundeck/client/util/Client.java | 8 ++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/rundeck/client/Rundeck.java b/src/main/java/org/rundeck/client/Rundeck.java index de67c7ef..36af7d77 100644 --- a/src/main/java/org/rundeck/client/Rundeck.java +++ b/src/main/java/org/rundeck/client/Rundeck.java @@ -20,7 +20,7 @@ */ public class Rundeck { public static final int API_VERS = 16; - public static final Pattern API_VERS_PATTERN = Pattern.compile("^(.*)(/api/\\d+/?)$"); + public static final Pattern API_VERS_PATTERN = Pattern.compile("^(.*)(/api/(\\d+)/?)$"); /** * Create a client using the specified, or default version @@ -79,6 +79,7 @@ public static Client client( { String appBaseUrl = buildBaseAppUrlForVersion(baseUrl); String base = buildApiUrlForVersion(baseUrl, apiVers); + int usedApiVers = apiVersionForUrl(baseUrl, apiVers); OkHttpClient.Builder callFactory = new OkHttpClient.Builder() .addInterceptor(new StaticHeaderInterceptor("X-Rundeck-Auth-Token", authToken)); @@ -109,7 +110,7 @@ public static Client client( )) .build(); - return new Client<>(build.create(RundeckApi.class), build); + return new Client<>(build.create(RundeckApi.class), build, usedApiVers); } /** @@ -134,6 +135,7 @@ public static Client client( String appBaseUrl = buildBaseAppUrlForVersion(baseUrl); String apiBaseUrl = buildApiUrlForVersion(baseUrl, apiVers); + int usedApiVers = apiVersionForUrl(baseUrl, apiVers); CookieManager cookieManager = new CookieManager(); cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); @@ -196,7 +198,7 @@ public static Client client( )) .build(); - return new Client<>(build.create(RundeckApi.class), build); + return new Client<>(build.create(RundeckApi.class), build, usedApiVers); } /** @@ -212,6 +214,20 @@ private static String buildApiUrlForVersion(String baseUrl, final int apiVers) { return normalizeUrlPath(baseUrl); } + /** + * @param baseUrl input url + * @param apiVers api VERSION to append if /api/VERS is not present + * + * @return URL for API by appending /api/VERS if it is not present + */ + private static int apiVersionForUrl(String baseUrl, final int apiVers) { + Matcher matcher = API_VERS_PATTERN.matcher(baseUrl); + if (matcher.matches()) { + return Integer.parseInt(matcher.group(3)); + } + return apiVers; + } + private static String normalizeUrlPath(String baseUrl) { if (!baseUrl.matches(".*/$")) { return baseUrl + "/"; diff --git a/src/main/java/org/rundeck/client/util/Client.java b/src/main/java/org/rundeck/client/util/Client.java index cd0a30f2..e9f691d7 100644 --- a/src/main/java/org/rundeck/client/util/Client.java +++ b/src/main/java/org/rundeck/client/util/Client.java @@ -36,10 +36,12 @@ public class Client { public static final String API_ERROR_API_VERSION_UNSUPPORTED = "api.error.api-version.unsupported"; private T service; private Retrofit retrofit; + private int apiVersion; - public Client(final T service, final Retrofit retrofit) { + public Client(final T service, final Retrofit retrofit, final int apiVersion) { this.service = service; this.retrofit = retrofit; + this.apiVersion = apiVersion; } /** @@ -202,4 +204,8 @@ public Retrofit getRetrofit() { public void setRetrofit(Retrofit retrofit) { this.retrofit = retrofit; } + + public int getApiVersion() { + return apiVersion; + } } From 4faced7da271ecc3d6f044aac61d59cd23350ada Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 21 Oct 2016 12:54:10 -0700 Subject: [PATCH 3/3] For job run, use json request for apiv18 --- .../org/rundeck/client/api/RundeckApi.java | 10 +++ .../rundeck/client/api/model/DateInfo.java | 14 +++- .../org/rundeck/client/api/model/JobRun.java | 71 +++++++++++++++++++ .../org/rundeck/client/tool/commands/Run.java | 57 +++++++++++++-- .../client/tool/options/RunBaseOptions.java | 11 ++- 5 files changed, 153 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/rundeck/client/api/model/JobRun.java diff --git a/src/main/java/org/rundeck/client/api/RundeckApi.java b/src/main/java/org/rundeck/client/api/RundeckApi.java index d0c19bf1..3bbc7bae 100644 --- a/src/main/java/org/rundeck/client/api/RundeckApi.java +++ b/src/main/java/org/rundeck/client/api/RundeckApi.java @@ -11,7 +11,9 @@ import java.io.File; import java.net.URL; +import java.util.Date; import java.util.List; +import java.util.Map; /** * Created by greg on 3/28/16. @@ -252,6 +254,14 @@ Call runJob( @Query("asUser") String user ); + @Headers("Accept: application/json") + @POST("job/{id}/executions") + Call runJob( + @Path("id") String id, + @Body JobRun jobRun + + ); + //key storage @Headers("Accept: application/json") diff --git a/src/main/java/org/rundeck/client/api/model/DateInfo.java b/src/main/java/org/rundeck/client/api/model/DateInfo.java index 79542c46..eba7932f 100644 --- a/src/main/java/org/rundeck/client/api/model/DateInfo.java +++ b/src/main/java/org/rundeck/client/api/model/DateInfo.java @@ -16,8 +16,18 @@ public class DateInfo { public String date; public long unixtime; - Date toDate() throws ParseException { - SimpleDateFormat asdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + public DateInfo(final String date) { + this.date = date; + } + + public DateInfo() { + } + + public Date toDate() throws ParseException { + return toDate("yyyy-MM-dd'T'HH:mm:ss'Z'"); + } + public Date toDate(final String format) throws ParseException { + SimpleDateFormat asdf = new SimpleDateFormat(format, Locale.US); return asdf.parse(date); } diff --git a/src/main/java/org/rundeck/client/api/model/JobRun.java b/src/main/java/org/rundeck/client/api/model/JobRun.java new file mode 100644 index 00000000..b3dedc63 --- /dev/null +++ b/src/main/java/org/rundeck/client/api/model/JobRun.java @@ -0,0 +1,71 @@ +package org.rundeck.client.api.model; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.Date; +import java.util.Map; + +/** + * Parameters to run a job + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(JsonInclude.Include.NON_NULL) +public class JobRun { + private String asUser; + private String argString; + private String loglevel; + private String filter; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssX") + private Date runAtTime; + private Map options; + + public String getAsUser() { + return asUser; + } + + public void setAsUser(String asUser) { + this.asUser = asUser; + } + + public String getArgString() { + return argString; + } + + public void setArgString(String argString) { + this.argString = argString; + } + + public String getLoglevel() { + return loglevel; + } + + public void setLoglevel(String loglevel) { + this.loglevel = loglevel; + } + + public String getFilter() { + return filter; + } + + public void setFilter(String filter) { + this.filter = filter; + } + + public Date getRunAtTime() { + return runAtTime; + } + + public void setRunAtTime(Date runAtTime) { + this.runAtTime = runAtTime; + } + + public Map getOptions() { + return options; + } + + public void setOptions(Map options) { + this.options = options; + } +} 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 6dff623b..278eda48 100644 --- a/src/main/java/org/rundeck/client/tool/commands/Run.java +++ b/src/main/java/org/rundeck/client/tool/commands/Run.java @@ -5,13 +5,17 @@ import org.rundeck.client.api.RundeckApi; import org.rundeck.client.api.model.Execution; import org.rundeck.client.api.model.JobItem; +import org.rundeck.client.api.model.JobRun; import org.rundeck.client.tool.options.RunBaseOptions; import org.rundeck.client.util.Client; import org.rundeck.client.util.Quoting; import retrofit2.Call; import java.io.IOException; +import java.text.ParseException; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Created by greg on 5/20/16. @@ -56,13 +60,52 @@ public boolean run(RunBaseOptions options, CommandOutput out) throws IOException throw new IllegalArgumentException("-j job or -i id is required"); } - Call executionListCall = client.getService().runJob( - jobId, - Quoting.joinStringQuoted(options.getCommandString()), - options.getLoglevel(), - options.getFilter(), - options.getUser() - ); + Call executionListCall; + if (client.getApiVersion() >= 18) { + JobRun request = new JobRun(); + request.setLoglevel(options.getLoglevel()); + request.setFilter(options.getFilter()); + request.setAsUser(options.getUser()); + List commandString = options.getCommandString(); + Map jobopts = new HashMap<>(); + String key = null; + if (null != commandString) { + for (String s : commandString) { + if (key == null && s.startsWith("-")) { + key = s.substring(1); + } else if (key != null) { + jobopts.put(key, s); + key = null; + } + } + } + if (key != null) { + throw new IllegalArgumentException( + String.format( + "Incorrect job options, expected: \"-%s value\", but saw only \"-%s\"", + key, + key + )); + } + + request.setOptions(jobopts); + if (null != options.getRunAtDate()) { + try { + request.setRunAtTime(options.getRunAtDate().toDate("yyyy-MM-dd'T'HH:mm:ssXX")); + } catch (ParseException e) { + throw new IllegalArgumentException("-@/--at date format is not valid", e); + } + } + executionListCall = client.getService().runJob(jobId, request); + } else { + executionListCall = client.getService().runJob( + jobId, + Quoting.joinStringQuoted(options.getCommandString()), + options.getLoglevel(), + options.getFilter(), + options.getUser() + ); + } Execution execution = client.checkError(executionListCall); out.output(String.format("Execution started: %s%n", execution.toBasicString())); diff --git a/src/main/java/org/rundeck/client/tool/options/RunBaseOptions.java b/src/main/java/org/rundeck/client/tool/options/RunBaseOptions.java index 8ce2d1d7..61859e75 100644 --- a/src/main/java/org/rundeck/client/tool/options/RunBaseOptions.java +++ b/src/main/java/org/rundeck/client/tool/options/RunBaseOptions.java @@ -3,7 +3,9 @@ import com.lexicalscope.jewel.cli.CommandLineInterface; import com.lexicalscope.jewel.cli.Option; import com.lexicalscope.jewel.cli.Unparsed; +import org.rundeck.client.api.model.DateInfo; +import java.util.Date; import java.util.List; /** @@ -40,7 +42,14 @@ public interface RunBaseOptions extends FollowOptions,OptionalProjectOptions { boolean isUser(); - @Unparsed(name = "-- -ARG VAL -ARG2 VAL", description = "Dispatch specified command string") + @Option(shortName = "@", + longName = "at", + description = "Run the job at the specified date/time. ISO8601 format (yyyy-MM-dd'T'HH:mm:ss'Z')") + DateInfo getRunAtDate(); + + boolean isRunAtDate(); + + @Unparsed(name = "-- -OPT VAL -OPT2 VAL", description = "Job options") List getCommandString(); }