cloudstack_cloner 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 60dd8f4ce7db03e2c028187255f2e92868e67de6
4
+ data.tar.gz: e2dbeb3fcfdd1016bebca529754f0f442cf3a9c8
5
+ SHA512:
6
+ metadata.gz: 82a48a9715c7ca16483a81cd81afd713165c0f8c13621e811c57f10a28a78cede4d20faae6ef3b50f734962b93fb31962712106a3955becaacd50cb7386feffc
7
+ data.tar.gz: 9ddcc880e8c8edc4503ab70691f289c6e5a5ceaaa880460c146c4d5a6a1b84d3a9e34f455bbfc938d4154a1f8ad4850cac8de56193075799a8585b666f3c13a7
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cloudstack_cloner.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 niwo
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # CloudstackCloner
2
+
3
+ Automated CloudStack VM cloning and copying and attaching of existing data disks.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'cloudstack_cloner'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install cloudstack_cloner
20
+
21
+ ## Usage
22
+
23
+
24
+ ### Example
25
+
26
+ ```bash
27
+ cloudstack_cloner clone --virtual_machine test01 --clone-name test-clone --project Playground --data-volumes test -e prod --offering 2cpu_2gb
28
+ ```
29
+
30
+ ## Contributing
31
+
32
+ 1. Fork it ( https://github.com/[my-github-username]/cloudstack_cloner/fork )
33
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
34
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
35
+ 4. Push to the branch (`git push origin my-new-feature`)
36
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'cloudstack_cloner'
4
+
5
+ CloudstackCloner::Cli.start(ARGV)
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cloudstack_cloner/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cloudstack_cloner"
8
+ spec.version = CloudstackCloner::VERSION
9
+ spec.authors = ["niwo"]
10
+ spec.email = ["nik.wolfgramm@gmail.com"]
11
+ spec.summary = %q{Automated CloudStack VM cloning}
12
+ spec.description = %q{Automated CloudStack VM cloning}
13
+ spec.homepage = "https://github.com/swisstxt/cloudstack_cloner"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+
24
+ spec.add_dependency('thor', '~> 0.19.1')
25
+ spec.add_dependency('cloudstack_client', '~> 1.0.4')
26
+ end
@@ -0,0 +1,48 @@
1
+ require "thor"
2
+
3
+ module CloudstackCloner
4
+ class Cli < Thor
5
+ include Thor::Actions
6
+ include CloudstackCloner::OptionResolver
7
+ include CloudstackCloner::Helper
8
+
9
+ package_name "cloudstack_cloner"
10
+
11
+ class_option :config_file,
12
+ default: File.join(Dir.home, '.cloudstack-cli.yml'),
13
+ aliases: '-c',
14
+ desc: 'Location of your cloudstack-cli configuration file'
15
+
16
+ class_option :env,
17
+ aliases: '-e',
18
+ desc: 'Environment to use'
19
+
20
+ desc "version", "Print cloudstack-cloner version number"
21
+ def version
22
+ say "cloudstack-cloner version #{CloudstackCloner::VERSION}"
23
+ end
24
+
25
+ desc "clone", "Clone a virtual machine"
26
+ option :virtual_machine,
27
+ desc: "name of the vm to clone",
28
+ required: true
29
+ option :project,
30
+ desc: "name of project"
31
+ option :clone_name,
32
+ desc: "name of the new vm",
33
+ required: true
34
+ option :offering,
35
+ desc: "name of the compute offering for the new vm"
36
+ option :data_volumes,
37
+ desc: "names of data volumes to attach",
38
+ type: :array
39
+ def clone
40
+ opts = options.dup
41
+ opts = resolve_project(opts)
42
+ opts = resolve_virtual_machine(opts)
43
+ opts = resolve_compute_offering(opts)
44
+ clone_vm(opts)
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,135 @@
1
+ require "yaml"
2
+ require "cloudstack_cloner/option_resolver"
3
+
4
+ module CloudstackCloner
5
+ module Helper
6
+
7
+ def clone_vm(opts)
8
+ vm = client.list_virtual_machines(
9
+ opts.merge(listall: true, name: opts[:virtual_machine])
10
+ ).first
11
+
12
+ if vm["state"] == "Running"
13
+ say "Failure: ", :red
14
+ say "VM #{vm["name"]} has to be stopped in order to create a template."
15
+ exit 1
16
+ end
17
+
18
+ data_volumes = opts[:data_volumes].map do |disk|
19
+ unless volume = client.list_volumes(
20
+ name: disk,
21
+ listall: true,
22
+ type: "DATADISK",
23
+ project_id: opts[:project_id]
24
+ ).first
25
+ say "Failure: ", :red
26
+ say "Volume #{disk} not found."
27
+ exit 1
28
+ end
29
+ volume
30
+ end
31
+
32
+ volume = client.list_volumes(opts.merge(listall: true, type: "root")).first
33
+
34
+ templ_name = "#{vm["name"]}-#{Time.now.strftime("%F")}"
35
+
36
+ if template = client.list_templates(
37
+ name: templ_name,
38
+ listall: true,
39
+ projectid: opts[:project_id],
40
+ templatefilter: "self"
41
+ ).first
42
+ say "Template #{templ_name} already exists.", :green
43
+ else
44
+ say "Create template from volume #{volume["name"]} ", :yellow
45
+ template = client.create_template(
46
+ name: templ_name,
47
+ displaytext: templ_name,
48
+ ostypeid: vm["guestosid"],
49
+ volumeid: volume["id"]
50
+ )["template"]
51
+ say " [OK]", :green
52
+ end
53
+
54
+ say "Creating VM from template #{template["name"]} ", :yellow
55
+ clone = client.deploy_virtual_machine(
56
+ name: opts[:clone_name],
57
+ displaytext: opts[:clone_name],
58
+ templateid: template["id"],
59
+ serviceofferingid: opts[:service_offering_id] || vm["serviceofferingid"],
60
+ networkids: vm["networkids"],
61
+ zoneid: vm["zoneid"],
62
+ projectid: opts[:project_id]
63
+ )["virtualmachine"]
64
+ say " [OK]", :green
65
+
66
+
67
+ data_volumes.each do |volume|
68
+ say "Creating snapshot for volume #{volume["name"]} ", :yellow
69
+ snapshot = client.create_snapshot(volumeid: volume["id"])["snapshot"]
70
+ say " [OK]", :green
71
+
72
+ say "Creating clone of volume #{volume["name"]} ", :yellow
73
+ volume = client.create_volume(
74
+ name: "#{volume["name"]}_#{opts[:clone_name]}",
75
+ snapshot_id: snapshot["id"],
76
+ projectid: opts[:project_id]
77
+ )["volume"]
78
+ say " [OK]", :green
79
+
80
+ say "Attach clone of volume #{volume["name"]} to VM #{clone["name"]} ", :yellow
81
+ client.attach_volume(
82
+ id: volume["id"],
83
+ virtualmachineid: clone["id"]
84
+ )
85
+ say " [OK]", :green
86
+
87
+ say "Delete snapshot of volume #{volume["name"]} ", :yellow
88
+ volume = client.delete_snapshot(id: snapshot["id"])
89
+ say " [OK]", :green
90
+ end
91
+
92
+ end
93
+
94
+ private
95
+
96
+ def client
97
+ @config ||= load_configuration(options[:config_file], options[:env]).first
98
+ @client ||= CloudstackClient::Client.new(
99
+ @config[:url],
100
+ @config[:api_key],
101
+ @config[:secret_key],
102
+ options
103
+ )
104
+ end
105
+
106
+ def load_configuration(config_file, env)
107
+ unless File.exists?(config_file)
108
+ puts "Configuration file #{config_file} not found."
109
+ puts "Please run 'cloudstack-cli environment add' to create one."
110
+ exit 1
111
+ end
112
+
113
+ begin
114
+ config = YAML::load(IO.read(config_file))
115
+ rescue
116
+ puts "Can't load configuration from file #{config_file}."
117
+ exit 1
118
+ end
119
+
120
+ if env ||= config[:default]
121
+ unless config = config[env]
122
+ puts "Can't find environment #{env}."
123
+ exit 1
124
+ end
125
+ end
126
+
127
+ unless config.key?(:url) && config.key?(:api_key) && config.key?(:secret_key)
128
+ puts "The environment #{env || '\'-\''} contains no valid data."
129
+ exit 1
130
+ end
131
+ return config, env
132
+ end
133
+
134
+ end
135
+ end
@@ -0,0 +1,185 @@
1
+ module CloudstackCloner
2
+ module OptionResolver
3
+
4
+ def vm_options_to_params(options = options)
5
+ resolve_zone(options)
6
+ resolve_project(options)
7
+ resolve_compute_offering(options)
8
+ resolve_template(options)
9
+ resolve_disk_offering(options)
10
+ resolve_iso(options)
11
+ unless options[:template_id]
12
+ say "Error: Template or ISO is required.", :red
13
+ exit 1
14
+ end
15
+ resolve_networks(options)
16
+ end
17
+
18
+ def resolve_zone(options = options)
19
+ if options[:zone]
20
+ zones = client.list_zones
21
+ zone = zones.find {|z| z['name'] == options[:zone] }
22
+ if !zone
23
+ msg = options[:zone] ? "Zone '#{options[:zone]}' is invalid." : "No zone found."
24
+ say "Error: #{msg}", :red
25
+ exit 1
26
+ end
27
+ options[:zone_id] = zone['id']
28
+ end
29
+ options
30
+ end
31
+
32
+ def resolve_domain(options = options)
33
+ if options[:domain]
34
+ if domain = client.list_domains(name: options[:domain]).first
35
+ options[:domain_id] = domain['id']
36
+ else
37
+ say "Error: Domain #{options[:domain]} not found.", :red
38
+ exit 1
39
+ end
40
+ end
41
+ options
42
+ end
43
+
44
+ def resolve_project(options = options)
45
+ if options[:project]
46
+ if %w(ALL -1).include? options[:project]
47
+ options[:project_id] = "-1"
48
+ elsif project = client.list_projects(name: options[:project], listall: true).first
49
+ options[:project_id] = project['id']
50
+ else
51
+ say "Error: Project #{options[:project]} not found.", :red
52
+ exit 1
53
+ end
54
+ end
55
+ options
56
+ end
57
+
58
+ def resolve_account(options = options)
59
+ if options[:account]
60
+ if account = client.list_accounts(name: options[:account], listall: true).first
61
+ options[:account_id] = account['id']
62
+ options[:domain_id] = account['domainid']
63
+ else
64
+ say "Error: Account #{options[:account]} not found.", :red
65
+ exit 1
66
+ end
67
+ end
68
+ options
69
+ end
70
+
71
+ def resolve_networks(options = options)
72
+ networks = []
73
+ available_networks = network = client.list_networks(
74
+ zone_id: options[:zone_id],
75
+ project_id: options[:project_id]
76
+ )
77
+ if options[:networks]
78
+ options[:networks].each do |name|
79
+ unless network = available_networks.find { |n| n['name'] == name }
80
+ say "Error: Network '#{name}' not found.", :red
81
+ exit 1
82
+ end
83
+ networks << network['id'] rescue nil
84
+ end
85
+ end
86
+ networks.compact!
87
+ if networks.empty?
88
+ #unless default_network = client.list_networks(project_id: options[:project_id]).find {
89
+ # |n| n['isdefault'] == true }
90
+ unless default_network = client.list_networks(project_id: options[:project_id]).first
91
+ say "Error: No default network found.", :red
92
+ exit 1
93
+ end
94
+ networks << available_networks.first['id'] rescue nil
95
+ end
96
+ options[:network_ids] = networks.join(',')
97
+ options
98
+ end
99
+
100
+ def resolve_iso(options = options)
101
+ if options[:iso]
102
+ unless iso = client.list_isos(
103
+ name: options[:iso],
104
+ project_id: options[:project_id]
105
+ ).first
106
+ say "Error: Iso '#{options[:iso]}' is invalid.", :red
107
+ exit 1
108
+ end
109
+ unless options[:diskoffering_id]
110
+ say "Error: a disk offering is required when using iso.", :red
111
+ exit 1
112
+ end
113
+ options[:template_id] = iso['id']
114
+ options['hypervisor'] = (options[:hypervisor] || 'vmware')
115
+ end
116
+ options
117
+ end
118
+
119
+ def resolve_template(options = options)
120
+ if options[:template]
121
+ if template = client.list_templates(
122
+ name: options[:template],
123
+ template_filter: "executable",
124
+ project_id: options[:project_id]
125
+ ).first
126
+ options[:template_id] = template['id']
127
+ else
128
+ say "Error: Template #{options[:template]} not found.", :red
129
+ exit 1
130
+ end
131
+ end
132
+ options
133
+ end
134
+
135
+ def resolve_compute_offering(options = options)
136
+ if options[:offering]
137
+ if offering = client.list_service_offerings(name: options[:offering]).first
138
+ options[:service_offering_id] = offering['id']
139
+ else
140
+ say "Error: Offering #{options[:offering]} not found.", :red
141
+ exit 1
142
+ end
143
+ end
144
+ options
145
+ end
146
+
147
+ def resolve_disk_offering(options = options)
148
+ if options[:disk_offering]
149
+ unless disk_offering = client.list_disk_offerings(name: options[:disk_offering]).first
150
+ say "Error: Disk offering '#{options[:disk_offering]}' not found.", :red
151
+ exit 1
152
+ end
153
+ options[:disk_offering_id] = disk_offering['id']
154
+ end
155
+ options
156
+ end
157
+
158
+ def resolve_virtual_machine(options = options.dup)
159
+ if options[:virtual_machine]
160
+ args = { name: options[:virtual_machine], listall: true }
161
+ args[:project_id] = options[:project_id]
162
+ unless vm = client.list_virtual_machines(args).first
163
+ say "Error: VM '#{options[:virtual_machine]}' not found.", :red
164
+ exit 1
165
+ end
166
+ options[:virtual_machine_id] = vm['id']
167
+ end
168
+ options
169
+ end
170
+
171
+ def resolve_snapshot(options = options)
172
+ if options[:snapshot]
173
+ args = { name: options[:snapshot], listall: true }
174
+ args[:project_id] = options[:project_id]
175
+ unless snapshot = client.list_snapshots(args).first
176
+ say "Error: Snapshot '#{options[:snapshot]}' not found.", :red
177
+ exit 1
178
+ end
179
+ options[:snapshot_id] = snapshot['id']
180
+ end
181
+ options
182
+ end
183
+
184
+ end
185
+ end
@@ -0,0 +1,3 @@
1
+ module CloudstackCloner
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,5 @@
1
+ require "cloudstack_client"
2
+
3
+ require "cloudstack_cloner/version"
4
+ require "cloudstack_cloner/helper"
5
+ require "cloudstack_cloner/cli"
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloudstack_cloner
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - niwo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.19.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.19.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: cloudstack_client
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.4
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.0.4
69
+ description: Automated CloudStack VM cloning
70
+ email:
71
+ - nik.wolfgramm@gmail.com
72
+ executables:
73
+ - cloudstack_cloner
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - bin/cloudstack_cloner
83
+ - cloudstack_cloner.gemspec
84
+ - lib/cloudstack_cloner.rb
85
+ - lib/cloudstack_cloner/cli.rb
86
+ - lib/cloudstack_cloner/helper.rb
87
+ - lib/cloudstack_cloner/option_resolver.rb
88
+ - lib/cloudstack_cloner/version.rb
89
+ homepage: https://github.com/swisstxt/cloudstack_cloner
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.2.2
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Automated CloudStack VM cloning
113
+ test_files: []