Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #69 from rundeck/rundeck-cli-68
Fix #68 improve projects list output
  • Loading branch information
gschueler committed Feb 2, 2017
2 parents 35cb5ad + c79bfee commit 9ae691c
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 7 deletions.
7 changes: 7 additions & 0 deletions src/main/java/org/rundeck/client/api/RundeckApi.java
Expand Up @@ -90,6 +90,13 @@ Call<DeleteJobsResult> deleteJobs(
@GET("projects")
Call<List<ProjectItem>> listProjects();

/**
* @see <a href="http://rundeck.org/docs/api/#getting-project-info">API</a>
*/
@Headers("Accept: application/json")
@GET("project/{project}")
Call<ProjectItem> getProjectInfo(@Path("project") String project);

/**
*
* @see <a href="http://rundeck.org/docs/api/index.html#listing-resources">api</a>
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/org/rundeck/client/api/model/ProjectItem.java
Expand Up @@ -3,6 +3,8 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
Expand Down Expand Up @@ -49,6 +51,22 @@ public void setConfig(Map<String, String> config) {
this.config = config;
}

public Map<Object, Object> toBasicMap() {

HashMap<Object, Object> detail = new LinkedHashMap<>();
detail.put("name", getName());
detail.put("description", description != null && !"".equals(description.trim()) ? description : "");
detail.put("url", getUrl());
return detail;
}
public Map<Object, Object> toMap() {

HashMap<Object, Object> detail = new LinkedHashMap<>(toBasicMap());
if (null != getConfig()) {
detail.put("config", getConfig());
}
return detail;
}
public String toBasicString() {
return String.format(
"%s%s",
Expand Down
64 changes: 57 additions & 7 deletions src/main/java/org/rundeck/client/tool/commands/Projects.java
Expand Up @@ -12,14 +12,15 @@
import org.rundeck.client.tool.commands.projects.ACLs;
import org.rundeck.client.tool.commands.projects.Readme;
import org.rundeck.client.tool.commands.projects.SCM;
import org.rundeck.client.tool.options.OptionUtil;
import org.rundeck.client.tool.options.ProjectCreateOptions;
import org.rundeck.client.tool.options.ProjectNameOptions;
import org.rundeck.client.tool.options.*;
import org.rundeck.client.util.Format;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;


Expand All @@ -41,11 +42,60 @@ public List<Object> getSubCommands() {
);
}

@Command(isDefault = true, description = "List all projects. (no options.)")
public void list(CommandOutput output) throws IOException, InputError {
interface ProjectResultOptions extends ProjectListFormatOptions, VerboseOption {

}

@CommandLineInterface(application = "list") interface ProjectListOpts extends ProjectResultOptions {

}

@Command(isDefault = true, description = "List all projects.")
public void list(ProjectListOpts opts, CommandOutput output) throws IOException, InputError {
List<ProjectItem> body = apiCall(RundeckApi::listProjects);
output.info(String.format("%d Projects:%n", body.size()));
output.output(body.stream().map(ProjectItem::toBasicString).collect(Collectors.toList()));
if (!opts.isOutputFormat()) {
output.info(String.format("%d Projects:%n", body.size()));
}

outputProjectList(opts, output, body, ProjectItem::getName, ProjectItem::toMap);
}

@CommandLineInterface(application = "info") interface ProjectInfoOpts extends ProjectResultOptions {

@Option(shortName = "p", longName = "project", description = "Project name")
String getProject();

}

@Command(isDefault = true,
description = "Get info about a project. Use -v/--verbose to output all available config data, or use " +
"-%/--outformat for selective data.")
public void info(ProjectInfoOpts opts, CommandOutput output) throws IOException, InputError {
ProjectItem body = apiCall(api -> api.getProjectInfo(opts.getProject()));

outputProjectList(opts, output, Collections.singletonList(body), ProjectItem::toBasicMap, ProjectItem::toMap);
}

private void outputProjectList(
final ProjectResultOptions options,
final CommandOutput output,
final List<ProjectItem> body,
final Function<ProjectItem, Object> basicOutput,
final Function<ProjectItem, Map<Object, Object>> verboseOutput
)
{
final Function<ProjectItem, ?> outformat;
if (options.isVerbose()) {
output.output(body.stream().map(verboseOutput).collect(Collectors.toList()));
return;
}
if (options.isOutputFormat()) {
outformat = Format.formatter(options.getOutputFormat(), ProjectItem::toMap, "%", "");
} else {
outformat = basicOutput;
}

output.output(body.stream().map(outformat).collect(Collectors.toList()));
}

@CommandLineInterface(application = "delete") interface ProjectDelete extends ProjectNameOptions {
Expand Down
@@ -0,0 +1,19 @@
package org.rundeck.client.tool.options;

import com.lexicalscope.jewel.cli.Option;

/**
* @author greg
* @since 2/2/17
*/
public interface ProjectListFormatOptions {

@Option(shortName = "%",
longName = "outformat",
description = "Output format specifier for project info. You can use \"%key\" where key is one of: " +
"name, description, url, config, config.KEY. E.g. \"%name: " +
"%description\".")
String getOutputFormat();

boolean isOutputFormat();
}
@@ -0,0 +1,58 @@
package org.rundeck.client.tool.commands

import com.simplifyops.toolbelt.CommandOutput
import org.rundeck.client.api.RundeckApi
import org.rundeck.client.api.model.ProjectItem
import org.rundeck.client.api.model.ScheduledJobItem
import org.rundeck.client.tool.RdApp
import org.rundeck.client.util.Client
import retrofit2.Retrofit
import retrofit2.mock.Calls
import spock.lang.Specification

/**
* @author greg
* @since 2/2/17
*/
class ProjectsSpec extends Specification {

def "projects list outformat"() {
given:

def api = Mock(RundeckApi)
def opts = Mock(Projects.ProjectListOpts) {
getOutputFormat() >> outFormat
isOutputFormat() >> (outFormat != null)
}

def retrofit = new Retrofit.Builder().baseUrl('http://example.com/fake/').build()
def client = new Client(api, retrofit, 18)
def hasclient = Mock(RdApp) {
getClient() >> client
}
Projects projects = new Projects(hasclient)
def out = Mock(CommandOutput)

when:
projects.list(opts, out)

then:
1 * api.listProjects() >>
Calls.response(
[new ProjectItem(name: 'abc', description: '123', config: [xyz: '993']), new ProjectItem(
name: 'def',
description: ''
)]
)
1 * out.output(result)


where:
outFormat | result
null | ['abc', 'def']
'%name' | ['abc', 'def']
'%name/%description' | ['abc/123', 'def/']
'%name/%config' | ['abc/{xyz=993}', 'def/']
'%name/%config.xyz' | ['abc/993', 'def/']
}
}

0 comments on commit 9ae691c

Please sign in to comment.