aeolus-image 0.0.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/Rakefile +62 -0
- data/bin/aeolus-image +6 -0
- data/examples/aeolus-cli +9 -0
- data/examples/custom_repo.tdl +18 -0
- data/examples/image_description.xml +3 -0
- data/examples/tdl.rng +207 -0
- data/lib/base_command.rb +134 -0
- data/lib/build_command.rb +68 -0
- data/lib/config_parser.rb +212 -0
- data/lib/delete_command.rb +9 -0
- data/lib/import_command.rb +44 -0
- data/lib/list_command.rb +141 -0
- data/lib/push_command.rb +72 -0
- data/man/aeolus-image-build.1 +36 -0
- data/man/aeolus-image-import.1 +57 -0
- data/man/aeolus-image-list.1 +80 -0
- data/man/aeolus-image-push.1 +40 -0
- data/man/aeolus-image.1 +16 -0
- data/spec/base_command_spec.rb +76 -0
- data/spec/build_command_spec.rb +63 -0
- data/spec/config_parser_spec.rb +82 -0
- data/spec/fixtures/invalid_template.tdl +18 -0
- data/spec/fixtures/valid_template.tdl +18 -0
- data/spec/import_command_spec.rb +43 -0
- data/spec/list_command_spec.rb +21 -0
- data/spec/push_command_spec.rb +56 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +52 -0
- metadata +155 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'imagefactory'
|
2
|
+
|
3
|
+
module Aeolus
|
4
|
+
module Image
|
5
|
+
class BuildCommand < BaseCommand
|
6
|
+
attr_accessor :console
|
7
|
+
def initialize(opts={}, logger=nil)
|
8
|
+
super(opts, logger)
|
9
|
+
default = {
|
10
|
+
:template_str => '',
|
11
|
+
:template => '',
|
12
|
+
:target => [],
|
13
|
+
:image => '',
|
14
|
+
:build => ''
|
15
|
+
}
|
16
|
+
@options = default.merge(@options)
|
17
|
+
@console = ImageFactoryConsole.new()
|
18
|
+
@console.start
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
if combo_implemented?
|
23
|
+
@options[:template_str] = read_file(@options[:template])
|
24
|
+
if @options[:template_str].nil?
|
25
|
+
puts "Cannot find specified file"
|
26
|
+
quit(1)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Validate XML against TDL Schema
|
30
|
+
errors = validate_xml_document(File.dirname(__FILE__) + "/../examples/tdl.rng", @options[:template_str])
|
31
|
+
if errors.length > 0
|
32
|
+
puts "ERROR: The given Template does not conform to the TDL Schema, see below for specific details:"
|
33
|
+
errors.each do |error|
|
34
|
+
puts "- " + error.message
|
35
|
+
end
|
36
|
+
quit(1)
|
37
|
+
end
|
38
|
+
|
39
|
+
#This is a temporary hack in case the agent doesn't show up on bus immediately
|
40
|
+
sleep(5)
|
41
|
+
@console.build(@options[:template_str], @options[:target], @options[:image], @options[:build]).each do |adaptor|
|
42
|
+
puts ""
|
43
|
+
puts "Target Image: #{adaptor.image_id}"
|
44
|
+
puts "Image: #{adaptor.image}"
|
45
|
+
puts "Build: #{adaptor.build}"
|
46
|
+
puts "Status: #{adaptor.status}"
|
47
|
+
puts "Percent Complete: #{adaptor.percent_complete}"
|
48
|
+
end
|
49
|
+
quit(0)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def combo_implemented?
|
54
|
+
if @options[:template].empty? || @options[:target].empty?
|
55
|
+
puts "This combination of parameters is not currently supported"
|
56
|
+
quit(1)
|
57
|
+
end
|
58
|
+
true
|
59
|
+
end
|
60
|
+
|
61
|
+
def quit(code)
|
62
|
+
@console.shutdown
|
63
|
+
super
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'logger'
|
3
|
+
require 'base_command'
|
4
|
+
require 'list_command'
|
5
|
+
require 'build_command'
|
6
|
+
require 'push_command'
|
7
|
+
require 'import_command'
|
8
|
+
require 'delete_command'
|
9
|
+
|
10
|
+
module Aeolus
|
11
|
+
module Image
|
12
|
+
class ConfigParser
|
13
|
+
COMMANDS = %w(list build push import delete)
|
14
|
+
attr_accessor :options, :command, :args
|
15
|
+
|
16
|
+
def initialize(argv)
|
17
|
+
@args = argv
|
18
|
+
# Default options
|
19
|
+
@options = {}
|
20
|
+
parse
|
21
|
+
end
|
22
|
+
|
23
|
+
def process
|
24
|
+
# Check for command, then call appropriate Optionparser and initiate
|
25
|
+
# call to that class.
|
26
|
+
@command = @args.shift
|
27
|
+
# Eventually get the config file from user dir if it exists.
|
28
|
+
# File.expand_path("~")
|
29
|
+
if COMMANDS.include?(@command)
|
30
|
+
self.send(@command.to_sym)
|
31
|
+
else
|
32
|
+
@args << "-h"
|
33
|
+
puts "Valid command required: \n\n"
|
34
|
+
parse
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def parse
|
40
|
+
@optparse ||= OptionParser.new do|opts|
|
41
|
+
opts.banner = "Usage: aeolus-image [#{COMMANDS.join('|')}] [general options] [command options]"
|
42
|
+
|
43
|
+
opts.separator ""
|
44
|
+
opts.separator "General options:"
|
45
|
+
opts.on('-u', '--user USERNAME', 'Conductor username') do |user|
|
46
|
+
@options[:user] = user
|
47
|
+
end
|
48
|
+
opts.on('-w', '--password PASSWORD', 'Conductor password') do |pw|
|
49
|
+
@options[:password] = pw
|
50
|
+
end
|
51
|
+
opts.on('-d', '--id ID', 'id for a given object') do |id|
|
52
|
+
@options[:id] = id
|
53
|
+
end
|
54
|
+
opts.on('-r', '--description NAME', 'description (e.g. "<image><name>MyImage</name></image>" or "/home/user/myImage.xml")') do |description|
|
55
|
+
@options[:description] = description
|
56
|
+
end
|
57
|
+
opts.on('-r', '--provider NAME1,NAME2', Array,'name of specific provider (ie ec2-us-east1)') do |name|
|
58
|
+
@options[:provider] = name
|
59
|
+
end
|
60
|
+
opts.on('-I', '--image ID', 'ID of the base image, can be used in build and push commands, see examples') do |id|
|
61
|
+
@options[:image] = id
|
62
|
+
end
|
63
|
+
opts.on('-T', '--target TARGET1,TARGET2', Array, 'provider type (ec2, rackspace, rhevm, etc)') do |name|
|
64
|
+
@options[:target] = name
|
65
|
+
end
|
66
|
+
opts.on('-d', '--daemon', 'run as a background process') do
|
67
|
+
@options[:subcommand] = :images
|
68
|
+
end
|
69
|
+
opts.on( '-h', '--help', 'Get usage information for this tool') do
|
70
|
+
puts opts
|
71
|
+
exit(0)
|
72
|
+
end
|
73
|
+
|
74
|
+
opts.separator ""
|
75
|
+
opts.separator "List options:"
|
76
|
+
opts.on('-i', '--images', 'Retrieve a list of images') do
|
77
|
+
@options[:subcommand] = :images
|
78
|
+
end
|
79
|
+
opts.on('-b', '--builds ID', 'Retrieve the builds of an image') do |id|
|
80
|
+
@options[:subcommand] = :builds
|
81
|
+
@options[:id] = id
|
82
|
+
end
|
83
|
+
opts.on('-t', '--targetimages ID', 'Retrieve the target images from a build') do |id|
|
84
|
+
@options[:subcommand] = :targetimages
|
85
|
+
@options[:id] = id
|
86
|
+
end
|
87
|
+
opts.on('-P', '--providerimages ID', 'Retrieve the provider images from a target image') do |id|
|
88
|
+
@options[:subcommand] = :targetimages
|
89
|
+
@options[:id] = id
|
90
|
+
end
|
91
|
+
opts.on('-g', '--targets', 'Retrieve the values available for the --target parameter') do
|
92
|
+
@options[:subcommand] = :targets
|
93
|
+
end
|
94
|
+
opts.on('-p', '--providers', 'Retrieve the values available for the --provider parameter') do
|
95
|
+
@options[:subcommand] = :providers
|
96
|
+
end
|
97
|
+
opts.on('-a', '--accounts', 'Retrieve the values available for the --account parameter') do
|
98
|
+
@options[:subcommand] = :accounts
|
99
|
+
end
|
100
|
+
|
101
|
+
opts.separator ""
|
102
|
+
opts.separator "Build options:"
|
103
|
+
opts.on('-e', '--template FILE', 'path to file that contains template xml') do |file|
|
104
|
+
@options[:template] = file
|
105
|
+
end
|
106
|
+
|
107
|
+
opts.separator ""
|
108
|
+
opts.separator "Push options:"
|
109
|
+
opts.on('-B', '--build ID', 'push all target images for a build, to same providers as previously') do |id|
|
110
|
+
@options[:build] = id
|
111
|
+
end
|
112
|
+
opts.on('-A', '--account NAME', 'name of specific provider account to use for push') do |name|
|
113
|
+
@options[:account] = name
|
114
|
+
end
|
115
|
+
|
116
|
+
opts.separator ""
|
117
|
+
opts.separator "Delete options:"
|
118
|
+
opts.on('-m', '--targetimage ID', 'delete target image and its provider images') do |id|
|
119
|
+
@options[:targetimage] = id
|
120
|
+
end
|
121
|
+
opts.on('-D', '--providerimage ID', 'delete provider image') do |id|
|
122
|
+
@options[:providerimage] = id
|
123
|
+
end
|
124
|
+
|
125
|
+
opts.separator ""
|
126
|
+
opts.separator "List Examples:"
|
127
|
+
opts.separator "aeolus-image list --images # list available images"
|
128
|
+
opts.separator "aeolus-image list --builds $image_id # list the builds of an image"
|
129
|
+
opts.separator "aeolus-image list --targetimages $build_id # list the target images from a build"
|
130
|
+
opts.separator "aeolus-image list --targets # list the values available for the --target parameter"
|
131
|
+
opts.separator "aeolus-image list --providers # list the values available for the --provider parameter"
|
132
|
+
opts.separator "aeolus-image list --accounts # list the values available for the --account parameter"
|
133
|
+
|
134
|
+
opts.separator ""
|
135
|
+
opts.separator "Build examples:"
|
136
|
+
opts.separator "aeolus-image build --target ec2 --template my.tmpl # build a new image for ec2 from the template"
|
137
|
+
opts.separator "aeolus-image build --image $image_id # (NOT IMPLEMENTED) rebuild the image template and targets from latest build"
|
138
|
+
opts.separator %q{aeolus-image build --target ec2,rackspace \ # rebuild the image with a new template and set of targets
|
139
|
+
--image $image_i \
|
140
|
+
--template my.tmpl}
|
141
|
+
|
142
|
+
opts.separator ""
|
143
|
+
opts.separator "Push examples:"
|
144
|
+
opts.separator "aeolus-image push --provider ec2-us-east-1 --id $image_id # initial push of an image build via image id to the specified provider"
|
145
|
+
opts.separator "aeolus-image push --build $build_id # push an image build via build id to the specified provider"
|
146
|
+
opts.separator "aeolus-image push --account $provider_account --build $build_id # (NOT IMPLEMENTED) ditto, using a specific provider account"
|
147
|
+
opts.separator "aeolus-image push --image $image_id # (NOT IMPLEMENTED) push all the target images for the latest build"
|
148
|
+
|
149
|
+
opts.separator ""
|
150
|
+
opts.separator "Import examples:"
|
151
|
+
opts.separator "aeolus-image import --provider ec2-us-east-1 --target ec2 --id $ami_id # import an AMI from the specified provider"
|
152
|
+
opts.separator "aeolus-image import --provider ec2-us-east-1 --target ec2 --id $ami_id --description '<image><name>My Image</name></image>' # import an AMI from the specified provider"
|
153
|
+
opts.separator "aeolus-image import --provider ec2-us-east-1 --target ec2 --id $ami_id --description <path_to_xml_file> # import an AMI from the specified provider"
|
154
|
+
|
155
|
+
opts.separator ""
|
156
|
+
opts.separator "Delete examples: (DELETE CURRENTLY NOT IMPLEMENTED) "
|
157
|
+
opts.separator "aeolus-image delete --build $build_id # deletes a build, updating latest/parent references as appropriate"
|
158
|
+
opts.separator "aeolus-image delete --targetimage $target_image # deletes a target image and its provider images"
|
159
|
+
opts.separator "aeolus-image delete --providerimage $provider_image # deletes a provider image"
|
160
|
+
end
|
161
|
+
|
162
|
+
begin
|
163
|
+
@optparse.parse!(@args)
|
164
|
+
rescue OptionParser::InvalidOption
|
165
|
+
puts "Warning: Invalid option"
|
166
|
+
exit(1)
|
167
|
+
rescue OptionParser::MissingArgument => e
|
168
|
+
puts "Warning, #{e.message}"
|
169
|
+
exit(1)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# TODO: Remove all this boilerplate and replace with some metaprogramming,
|
174
|
+
# perhaps method_missing
|
175
|
+
def list
|
176
|
+
# TODO: Instantiate and call object matching command type, for example:
|
177
|
+
# l = ListCommand.new(@options)
|
178
|
+
# Each Command will call it's own internal method depending on the contents of the hash.
|
179
|
+
# For the list example above, that object would call a method 'images' based on the item
|
180
|
+
# @options[:subcommand] being :images, so internally that class may do something like:
|
181
|
+
# self.send(@options[:subcommand])
|
182
|
+
if @options[:subcommand].nil?
|
183
|
+
# TODO: Pull out Print Usage into seporate method, and print
|
184
|
+
puts "Could not find subcommand for list, run `./aeolus-image --help` for usage instructions"
|
185
|
+
exit(1)
|
186
|
+
else
|
187
|
+
list_command = ListCommand.new(@options)
|
188
|
+
list_command.send(@options[:subcommand])
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def build
|
193
|
+
b = BuildCommand.new(@options)
|
194
|
+
b.run
|
195
|
+
end
|
196
|
+
|
197
|
+
def push
|
198
|
+
b = PushCommand.new(@options)
|
199
|
+
b.run
|
200
|
+
end
|
201
|
+
|
202
|
+
def import
|
203
|
+
import_command = ImportCommand.new(@options)
|
204
|
+
import_command.import_image
|
205
|
+
end
|
206
|
+
|
207
|
+
def delete
|
208
|
+
"Not implemented"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Aeolus
|
2
|
+
module Image
|
3
|
+
class ImportCommand < BaseCommand
|
4
|
+
def initialize(opts={}, logger=nil)
|
5
|
+
super(opts, logger)
|
6
|
+
default = {
|
7
|
+
:image => '',
|
8
|
+
:build => '',
|
9
|
+
:id => '',
|
10
|
+
:description => '<image><name>' + @options[:id] + '</name></image>',
|
11
|
+
:target => '',
|
12
|
+
:provider => ''
|
13
|
+
}
|
14
|
+
@options = default.merge(@options)
|
15
|
+
@console = ImageFactoryConsole.new()
|
16
|
+
@console.start
|
17
|
+
end
|
18
|
+
|
19
|
+
def import_image
|
20
|
+
description = read_file(@options[:description])
|
21
|
+
if !description.nil?
|
22
|
+
@options[:description] = description
|
23
|
+
end
|
24
|
+
# TODO: Validate Description XML
|
25
|
+
|
26
|
+
#This is a temporary hack in case the agent doesn't show up on bus
|
27
|
+
#immediately
|
28
|
+
sleep(5)
|
29
|
+
import_map = @console.import_image(@options[:image], @options[:build], @options[:id], @options[:description], @options[:target].first, @options[:provider].first)
|
30
|
+
puts ""
|
31
|
+
puts "Target Image: " + import_map['target_image']
|
32
|
+
puts "Image: " + import_map['image']
|
33
|
+
puts "Build: " + import_map['build']
|
34
|
+
puts "Provider Image: " + import_map['provider_image']
|
35
|
+
quit(0)
|
36
|
+
end
|
37
|
+
|
38
|
+
def quit(code)
|
39
|
+
@console.shutdown
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/list_command.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
module Aeolus
|
2
|
+
module Image
|
3
|
+
class ListCommand < BaseCommand
|
4
|
+
def initialize(opts={}, logger=nil)
|
5
|
+
super(opts, logger)
|
6
|
+
end
|
7
|
+
|
8
|
+
def images
|
9
|
+
images = [["IMAGE ID", "LASTEST PUSHED BUILD", "NAME", "TARGET", "OS", "OS VERSION", "ARCH", "DESCRIPTION"]]
|
10
|
+
doc = Nokogiri::XML iwhd['/target_images'].get
|
11
|
+
# Check for any invalid data in iwhd
|
12
|
+
invalid_images = []
|
13
|
+
doc.xpath("/objects/object/key").each do |targetimage|
|
14
|
+
begin
|
15
|
+
build = iwhd["/target_images/" + targetimage.text + "/build"].get
|
16
|
+
image = iwhd["/builds/" + build + "/image"].get
|
17
|
+
|
18
|
+
if template_info = get_template_info(image, targetimage.text)
|
19
|
+
images << [image] + [lastest_pushed(image)] + template_info
|
20
|
+
else
|
21
|
+
images << [image] + [lastest_pushed(image)] +[get_image_name(image), iwhd["/target_images/" + targetimage + "/target"].get, "", "", "", ""]
|
22
|
+
end
|
23
|
+
rescue
|
24
|
+
invalid_images << targetimage.text
|
25
|
+
end
|
26
|
+
end
|
27
|
+
format_print(images)
|
28
|
+
|
29
|
+
unless invalid_images.empty?
|
30
|
+
puts "\nN.B. following images were not listed, aeolus-image encountered some invalid data in iwhd:"
|
31
|
+
puts invalid_images.join "\n"
|
32
|
+
end
|
33
|
+
quit(0)
|
34
|
+
end
|
35
|
+
|
36
|
+
def builds
|
37
|
+
doc = Nokogiri::XML iwhd['/builds'].get
|
38
|
+
doc.xpath("/objects/object/key").each do |build|
|
39
|
+
if iwhd['/builds/' + build.text + "/image"].get == @options[:id]
|
40
|
+
puts build.text
|
41
|
+
end
|
42
|
+
end
|
43
|
+
quit(0)
|
44
|
+
end
|
45
|
+
|
46
|
+
def targetimages
|
47
|
+
doc = Nokogiri::XML iwhd['/target_images'].get
|
48
|
+
doc.xpath("/objects/object/key").each do |target_image|
|
49
|
+
begin
|
50
|
+
if iwhd['/target_images/' + target_image.text + "/build"].get == @options[:id]
|
51
|
+
puts target_image.text
|
52
|
+
end
|
53
|
+
rescue RestClient::ResourceNotFound
|
54
|
+
end
|
55
|
+
end
|
56
|
+
quit(0)
|
57
|
+
end
|
58
|
+
|
59
|
+
def targets
|
60
|
+
targets = [["NAME", "TARGET CODE"]]
|
61
|
+
targets << ["Mock", "mock"]
|
62
|
+
targets << ["Amazon EC2", "ec2"]
|
63
|
+
targets << ["RHEV-M", "rhevm"]
|
64
|
+
targets << ["VMware vSphere", "vsphere"]
|
65
|
+
targets << ["Condor Cloud", "condor_cloud"]
|
66
|
+
format_print(targets)
|
67
|
+
quit(0)
|
68
|
+
end
|
69
|
+
|
70
|
+
def providers
|
71
|
+
print_values = [["NAME", "TYPE", "URL"]]
|
72
|
+
|
73
|
+
doc = Nokogiri::XML conductor['/providers'].get
|
74
|
+
doc.xpath("/providers/provider").each do |provider|
|
75
|
+
print_values << [provider.xpath("name").text, provider.xpath("provider_type").text, provider.xpath("url").text]
|
76
|
+
end
|
77
|
+
|
78
|
+
format_print(print_values)
|
79
|
+
quit(0)
|
80
|
+
end
|
81
|
+
|
82
|
+
def accounts
|
83
|
+
print_values = [["NAME", "PROVIDER", "PROVIDER TYPE"]]
|
84
|
+
doc = Nokogiri::XML conductor['/provider_accounts/'].get
|
85
|
+
doc.xpath("/provider_accounts/provider_account").each do |account|
|
86
|
+
print_values << [account.xpath("name").text, account.xpath("provider").text, account.xpath("provider_type").text]
|
87
|
+
end
|
88
|
+
|
89
|
+
format_print(print_values)
|
90
|
+
quit(0)
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
# Takes a 2D array of strings and neatly prints them to STDOUT
|
95
|
+
def format_print(print_values)
|
96
|
+
widths = Array.new(print_values[0].size, 0)
|
97
|
+
print_values.each do |print_value|
|
98
|
+
widths = widths.zip(print_value).map! {|width, value| value.length > width ? value.length : width }
|
99
|
+
end
|
100
|
+
|
101
|
+
print_values.each do |print_value|
|
102
|
+
widths.zip(print_value) do |width, value|
|
103
|
+
printf("%-#{width + 5}s", value)
|
104
|
+
end
|
105
|
+
puts ""
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def get_template_info(image, targetimage)
|
110
|
+
begin
|
111
|
+
template = Nokogiri::XML iwhd["/templates/" + iwhd["/target_images/" + targetimage + "/template"].get].get
|
112
|
+
[template.xpath("/template/name").text,
|
113
|
+
iwhd["/target_images/" + targetimage + "/target"].get,
|
114
|
+
template.xpath("/template/os/name").text,
|
115
|
+
template.xpath("/template/os/version").text,
|
116
|
+
template.xpath("/template/os/arch").text,
|
117
|
+
template.xpath("/template/description").text]
|
118
|
+
rescue
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def get_image_name(image)
|
123
|
+
begin
|
124
|
+
template_xml = Nokogiri::XML iwhd["images/" + image].get
|
125
|
+
template_xml.xpath("/image/name").text
|
126
|
+
rescue
|
127
|
+
""
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def lastest_pushed(image)
|
132
|
+
begin
|
133
|
+
build = iwhd["/images/" + image + "/latest_build"].get
|
134
|
+
build.nil? ? "" : build
|
135
|
+
rescue
|
136
|
+
""
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|