cloudalign-cli 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +122 -0
- data/bin/cloudalign +86 -0
- data/lib/cloudalign/artifact.rb +24 -0
- data/lib/cloudalign/base_entity.rb +12 -0
- data/lib/cloudalign/client.rb +74 -0
- data/lib/cloudalign/config.rb +46 -0
- data/lib/cloudalign/file.rb +34 -0
- data/lib/cloudalign/project.rb +24 -0
- data/lib/cloudalign.rb +28 -0
- metadata +119 -0
data/README.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# Introduction
|
2
|
+
This is a Command Line Interface (CLI) to be used for accessing a remote CloudAlign system and interacting with it. This
|
3
|
+
tool is useful for creating scripts or other automated processes that one would need to interface with CloudAlign.
|
4
|
+
|
5
|
+
Also included with this tool is a simple Ruby library for talking to CloudAlign directly.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
### Dependencies
|
10
|
+
- Ruby 1.9.3 or greater
|
11
|
+
- RubyGems (http://www.rubygems.org ships with 1.9.x)
|
12
|
+
- A Unix like OS (Tested with Ubuntu 12 and Mac OSX)
|
13
|
+
|
14
|
+
### Setup
|
15
|
+
Simply install the CloudAlign CLI using GEM
|
16
|
+
<pre>
|
17
|
+
$ gem install cloudalign-cli
|
18
|
+
</pre>
|
19
|
+
|
20
|
+
### Configuration
|
21
|
+
Create a config file
|
22
|
+
<pre>
|
23
|
+
$ mkdir -p ~/cloudalign
|
24
|
+
$ vi ~/cloudalign/config.yml
|
25
|
+
</pre>
|
26
|
+
|
27
|
+
Finally, populate the config file with your credentials
|
28
|
+
<pre>
|
29
|
+
# File ~/cloudalign/config.yml
|
30
|
+
api_url: http://web1.dev.cloudalign.accetia.com
|
31
|
+
api_user: jmccaffrey
|
32
|
+
api_password: shhhitssecret
|
33
|
+
</pre>
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
### Supported Commands
|
38
|
+
Here are the supported operations with the current version of the CloudAlign Command Line Interface (CLI). Also listed
|
39
|
+
with each operation is a sample of how to use it.
|
40
|
+
|
41
|
+
#### Access Help Documentation
|
42
|
+
<pre>
|
43
|
+
$ cloudalign
|
44
|
+
Tasks:
|
45
|
+
cloudalign artifact <command> # Manipulate artifacts in the CloudAlign system
|
46
|
+
cloudalign help [TASK] # Describe available tasks or one specific task
|
47
|
+
cloudalign project <command> # Manipulate projects in the CloudAlign system
|
48
|
+
|
49
|
+
$ cloudalign artifact
|
50
|
+
cloudalign artifact align ARTIFACT # ...
|
51
|
+
cloudalign artifact compare ARTIFACT REFERENCE # ...
|
52
|
+
cloudalign artifact download ARTIFACT FILE # Downloads file FILE ...
|
53
|
+
cloudalign artifact help [COMMAND] # Describe subcommands...
|
54
|
+
cloudalign artifact list_files ARTIFACT # Lists all files in ...
|
55
|
+
|
56
|
+
$ cloudalign project
|
57
|
+
cloudalign project help [COMMAND] # Describe subcommands or...
|
58
|
+
cloudalign project list # Lists all viewable proj...
|
59
|
+
cloudalign project list_artifacts PROJECT # Lists all viewable arti...
|
60
|
+
cloudalign project upload_file PROJECT PATH # Uploads a file located ...
|
61
|
+
|
62
|
+
$ cloudalign project help list
|
63
|
+
Usage:
|
64
|
+
cloudalign list
|
65
|
+
|
66
|
+
Lists all viewable projects for your user
|
67
|
+
</pre>
|
68
|
+
|
69
|
+
#### List Projects
|
70
|
+
<pre>
|
71
|
+
$ cloudalign project list
|
72
|
+
+----+-----------------+
|
73
|
+
| id | name |
|
74
|
+
+----+-----------------+
|
75
|
+
| 1 | My Test Project |
|
76
|
+
+----+-----------------+
|
77
|
+
| 2 | asdf |
|
78
|
+
+----+-----------------+
|
79
|
+
|
80
|
+
</pre>
|
81
|
+
|
82
|
+
#### List Artifacts in a Project
|
83
|
+
<pre>
|
84
|
+
$ cloudalign project list_artifacts 2
|
85
|
+
+----+-----------+
|
86
|
+
| id | name |
|
87
|
+
+----+-----------+
|
88
|
+
| 20 | 40mb_file |
|
89
|
+
+----+-----------+
|
90
|
+
</pre>
|
91
|
+
|
92
|
+
#### List Files within an Artifact
|
93
|
+
<pre>
|
94
|
+
$ cloudalign artifact list_files 20
|
95
|
+
+-----+---------------+----------+--------+
|
96
|
+
| id | name | size | status |
|
97
|
+
+-----+---------------+----------+--------+
|
98
|
+
| 137 | 40mb_file.bin | 41943040 | READY |
|
99
|
+
+-----+---------------+----------+--------+
|
100
|
+
</pre>
|
101
|
+
|
102
|
+
#### Download a File
|
103
|
+
<pre>
|
104
|
+
$ cloudalign file download 20 137
|
105
|
+
Downloading file 40mb_file.bin from Artifact 20 to ./
|
106
|
+
...
|
107
|
+
Done
|
108
|
+
</pre>
|
109
|
+
|
110
|
+
#### Upload a File and Create Artifact
|
111
|
+
<pre>
|
112
|
+
$ cloudalign project upload 2 test.fastq
|
113
|
+
Uploading file test.fastq to project 'asdf'
|
114
|
+
...
|
115
|
+
Done
|
116
|
+
</pre>
|
117
|
+
|
118
|
+
#### Run BWA Alignment
|
119
|
+
<pre>
|
120
|
+
$ cloudalign artifact 20 align
|
121
|
+
Running alignment on artifact 'Test Artifact 123', use artifact list_files to see the status.
|
122
|
+
</pre>
|
data/bin/cloudalign
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift 'lib'
|
3
|
+
require "cloudalign"
|
4
|
+
require "thor"
|
5
|
+
require "thor/group"
|
6
|
+
require "pp"
|
7
|
+
require "formatador"
|
8
|
+
|
9
|
+
class ProjectTask < Thor
|
10
|
+
desc "list", "Lists all viewable projects for your user"
|
11
|
+
def list
|
12
|
+
projects = CloudAlign::Project.find_all.map do |p|
|
13
|
+
{
|
14
|
+
:id => p.id,
|
15
|
+
:name => p.name
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
Formatador.display_table(projects)
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "list_artifacts PROJECT", "Lists all viewable artifacts in <PROJECT>"
|
23
|
+
def list_artifacts(project_id)
|
24
|
+
artifacts = CloudAlign::Artifact.find_by_project(project_id).map do |a|
|
25
|
+
{
|
26
|
+
:id => a.id,
|
27
|
+
:name => a.name,
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
Formatador.display_table(artifacts)
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "upload_file PROJECT PATH", "Uploads a file located at PATH to the PROJECT in CloudAlign"
|
35
|
+
def upload_file(project_id, path)
|
36
|
+
project = CloudAlign::Project.find(project_id)
|
37
|
+
puts "Uploading file #{File.basename(path)} to project '#{project.name}'"
|
38
|
+
puts "..."
|
39
|
+
project.upload_file(path)
|
40
|
+
puts "Done"
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
class ArtifactTask < Thor
|
46
|
+
desc "list_files ARTIFACT", "Lists all files in <ARTIFACT>"
|
47
|
+
def list_files(artifact_id)
|
48
|
+
files = CloudAlign::File.find_by_artifact(artifact_id).map do |f|
|
49
|
+
{
|
50
|
+
:id => f.id,
|
51
|
+
:name => f.name,
|
52
|
+
:size => f.size,
|
53
|
+
:status => f.status
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
Formatador.display_table(files)
|
58
|
+
end
|
59
|
+
|
60
|
+
desc "download ARTIFACT FILE", "Downloads file FILE from the ARTIFACT in CloudAlign"
|
61
|
+
def download(artifact_id, file_id)
|
62
|
+
file = CloudAlign::File.find(artifact_id, file_id)
|
63
|
+
puts "Downloading file #{file.file_name} from Artifact #{file.artifact_id} to ./"
|
64
|
+
puts "..."
|
65
|
+
file.download
|
66
|
+
puts "Done"
|
67
|
+
end
|
68
|
+
|
69
|
+
desc "align ARTIFACT", "..."
|
70
|
+
def align(artifact_id)
|
71
|
+
artifact = CloudAlign::Artifact.find(artifact_id)
|
72
|
+
artifact.analyze(:bwa_alignment)
|
73
|
+
end
|
74
|
+
|
75
|
+
desc "compare ARTIFACT REFERENCE", "..."
|
76
|
+
def compare(artifact_id, reference)
|
77
|
+
puts "Not Implemented!"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class CLI < Thor
|
82
|
+
register(ProjectTask, 'project', 'project <command>', 'Manipulate projects in the CloudAlign system')
|
83
|
+
register(ArtifactTask, 'artifact', 'artifact <command>', 'Manipulate artifacts in the CloudAlign system')
|
84
|
+
end
|
85
|
+
|
86
|
+
CLI.start
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CloudAlign
|
2
|
+
class Artifact < BaseEntity
|
3
|
+
attr_accessor :name, :description
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def find_by_project(project_id)
|
8
|
+
Client.get_json("/projects/#{project_id}/artifacts").map do |row|
|
9
|
+
Artifact.new(row)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
def analyze(analyzer, options = {})
|
16
|
+
options[:analyzer] = analyzer
|
17
|
+
Client.post("/artifacts/#{@id}/analyze", options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def destroy
|
21
|
+
Client.delete("/artifacts/#{@id}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
class String
|
5
|
+
def starts_with?(prefix)
|
6
|
+
prefix = prefix.to_s
|
7
|
+
self[0, prefix.length] == prefix
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module CloudAlign
|
12
|
+
class Client
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def get(path)
|
16
|
+
RestClient.get(authorized_url(path))
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_json(path)
|
20
|
+
uri = URI.parse(path)
|
21
|
+
uri.path += '.json' unless uri.path.match(/\.json$|\/$/)
|
22
|
+
JSON.parse(get(uri.to_s))
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(path)
|
26
|
+
RestClient.delete(authorized_url(path))
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_file(path, output_path)
|
30
|
+
::File.open(output_path, 'w') do |out|
|
31
|
+
process_response = lambda do |response|
|
32
|
+
response.read_body do |chunk|
|
33
|
+
print "."
|
34
|
+
out.write chunk
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
RestClient::Request.execute(:method => :get, :url => authorized_url(path), :block_response => process_response)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def post(path, data)
|
43
|
+
RestClient.post(authorized_url(path), data)
|
44
|
+
end
|
45
|
+
|
46
|
+
def post_for_upload(path, data)
|
47
|
+
pp data
|
48
|
+
RestClient.post(authorized_url(path), data) do |response, request, result, &block|
|
49
|
+
if [301, 302, 303, 307].include? response.code
|
50
|
+
return get_json(response.headers[:location])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def authorized_url(path)
|
56
|
+
url = CloudAlign.config.get("api_url", "http://api.cloudalign.com").sub(/\/$/, '')
|
57
|
+
|
58
|
+
if path.starts_with?(url)
|
59
|
+
uri = URI.parse(path)
|
60
|
+
elsif path.starts_with?('/')
|
61
|
+
uri = URI.parse(url)
|
62
|
+
uri.path = path
|
63
|
+
else
|
64
|
+
return path
|
65
|
+
end
|
66
|
+
|
67
|
+
uri.user = CGI.escape(CloudAlign.config.get("api_user"))
|
68
|
+
uri.password = CGI.escape(CloudAlign.config.get("api_password"))
|
69
|
+
|
70
|
+
uri.to_s
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module CloudAlign
|
5
|
+
class Config
|
6
|
+
def initialize(path = nil)
|
7
|
+
@config = {}
|
8
|
+
load path
|
9
|
+
end
|
10
|
+
|
11
|
+
def set(key, value)
|
12
|
+
set_recursive(key, value, @config)
|
13
|
+
end
|
14
|
+
|
15
|
+
def get(key, default = nil)
|
16
|
+
p = @config
|
17
|
+
key.split('.').each do |part|
|
18
|
+
return default unless p.has_key? part
|
19
|
+
p = p[part]
|
20
|
+
end
|
21
|
+
|
22
|
+
p
|
23
|
+
end
|
24
|
+
|
25
|
+
def load(path = nil)
|
26
|
+
if path.nil?
|
27
|
+
config_paths = [Pathname.new(Dir.home).join('.cloudalign', 'config.yml').to_s]
|
28
|
+
path = config_paths.detect{|p| ::File.exists?(p)}
|
29
|
+
end
|
30
|
+
|
31
|
+
@config = YAML.load_file(path)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def set_recursive(key, value, array)
|
37
|
+
if key.include? '.'
|
38
|
+
(part, key) = key.split('.', 2)
|
39
|
+
array[part] = {} unless array.has_key? part
|
40
|
+
return set_recursive(key, value, array[part])
|
41
|
+
end
|
42
|
+
|
43
|
+
array[key] = value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module CloudAlign
|
2
|
+
class File < BaseEntity
|
3
|
+
attr_accessor :name, :storage_path, :size, :ready
|
4
|
+
|
5
|
+
def download(output_path = nil)
|
6
|
+
output_path = self.file_name if output_path.nil?
|
7
|
+
download_info = Client.get_json("/artifacts/#{@artifact_id}/files/#{@id}/download")
|
8
|
+
Client.get_file(download_info["download_url"], output_path)
|
9
|
+
end
|
10
|
+
|
11
|
+
def file_name
|
12
|
+
::File.basename(@storage_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def status
|
16
|
+
statuses = %w(READY BUILDING UPLOADING)
|
17
|
+
(@status >= 0 && @status < statuses.length) ? statuses[@status] : @status
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
|
22
|
+
def find(artifact_id, id)
|
23
|
+
File.new(Client.get_json("/artifacts/#{artifact_id}/files/#{id}"))
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_by_artifact(artifact_id)
|
27
|
+
Client.get_json("/artifacts/#{artifact_id}/files").map do |row|
|
28
|
+
File.new(row)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CloudAlign
|
2
|
+
class Project < BaseEntity
|
3
|
+
attr_accessor :name, :description
|
4
|
+
|
5
|
+
def self.find(id)
|
6
|
+
Project.new(Client.get_json("/projects/#{id}"))
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.find_all
|
10
|
+
Client.get_json("/projects").map do |row|
|
11
|
+
Project.new(row)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def upload_file(path)
|
16
|
+
post_data = Client.get_json("/projects/#{@id}/upload_file")
|
17
|
+
upload_url = post_data.delete("url")
|
18
|
+
post_data[:file] = ::File.new(path, 'rb')
|
19
|
+
|
20
|
+
Client.post_for_upload(upload_url, post_data)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
data/lib/cloudalign.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'logger'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module CloudAlign
|
6
|
+
autoload :BaseEntity, 'cloudalign/base_entity'
|
7
|
+
autoload :Client, 'cloudalign/client'
|
8
|
+
autoload :Project, 'cloudalign/project'
|
9
|
+
autoload :Artifact, 'cloudalign/artifact'
|
10
|
+
autoload :File, 'cloudalign/file'
|
11
|
+
autoload :Config, 'cloudalign/config'
|
12
|
+
|
13
|
+
API_VERSION = 1
|
14
|
+
|
15
|
+
class Error < StandardError; end
|
16
|
+
|
17
|
+
def self.logger
|
18
|
+
@logger ||= Logger.new(STDOUT)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.logger=(logger)
|
22
|
+
@logger = logger
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.config
|
26
|
+
@config ||= Config.new
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cloudalign-cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jonathan McCaffrey
|
9
|
+
- Jeffrey Biles
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-08-30 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: thor
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: rest-client
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: formatador
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: rspec
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
type: :runtime
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
description: Cloudalign brings the power of the cloud straight to your lab
|
80
|
+
email:
|
81
|
+
executables:
|
82
|
+
- cloudalign
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- bin/cloudalign
|
87
|
+
- lib/cloudalign/artifact.rb
|
88
|
+
- lib/cloudalign/base_entity.rb
|
89
|
+
- lib/cloudalign/client.rb
|
90
|
+
- lib/cloudalign/config.rb
|
91
|
+
- lib/cloudalign/file.rb
|
92
|
+
- lib/cloudalign/project.rb
|
93
|
+
- lib/cloudalign.rb
|
94
|
+
- README.md
|
95
|
+
homepage: http://rubygems.org/gems/cloudalign-cli
|
96
|
+
licenses: []
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project: cloudalign-cli
|
115
|
+
rubygems_version: 1.8.24
|
116
|
+
signing_key:
|
117
|
+
specification_version: 3
|
118
|
+
summary: Access cloudalign from your command line
|
119
|
+
test_files: []
|