cloudstack_cloner 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +13 -11
- data/lib/cloudstack_cloner/cli.rb +6 -7
- data/lib/cloudstack_cloner/helper.rb +22 -10
- data/lib/cloudstack_cloner/option_resolver.rb +69 -102
- data/lib/cloudstack_cloner/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 416bf2f75c671c268cfd99cae1b6c2b7dae9ae2a
|
4
|
+
data.tar.gz: a4a4f82678ba62f2dea7a6ae1afc9e784825f27e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bbd4513764adb7d27d2303950818fd8aa26d44b47b9ca35ae5a02dd3da44d7736fd85924ad84520a9c9ab8fa7e20f1e5ca55949aa665bd1eb58fcebe4705c785
|
7
|
+
data.tar.gz: 647842bf094b20eec1f197ea4e4ae660b0be85439d3b244410524bbcfc629c541ed6d6d424c16bc013ead2a0bc43ebb85b918654a16cbe4a1eff38f6e5a5b47d
|
data/README.md
CHANGED
@@ -1,30 +1,32 @@
|
|
1
1
|
# CloudstackCloner
|
2
2
|
|
3
|
-
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/cloudstack_cloner.png)](http://badge.fury.io/rb/cloudstack_cloner)
|
4
|
+
|
5
|
+
Automated CloudStack VM cloning and copying/attaching of existing data disks.
|
6
|
+
CloudstackCloner uses [cloudstack_client](https://github.com/niwo/cloudstack_client) for CloudStack API communication.
|
4
7
|
|
5
8
|
## Installation
|
6
9
|
|
7
|
-
|
10
|
+
Install the Gem:
|
8
11
|
|
9
|
-
```
|
10
|
-
gem
|
12
|
+
```bash
|
13
|
+
$ gem install cloudstack_cloner
|
11
14
|
```
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
$ bundle
|
16
|
-
|
17
|
-
Or install it yourself as:
|
16
|
+
## Configuration
|
18
17
|
|
19
|
-
|
18
|
+
A [cloudstack-cli](https://github.com/niwo/cloudstack-cli) style configuration file is used for setting up URL, keys and secrets of your CloudStack API connection.
|
20
19
|
|
21
20
|
## Usage
|
22
21
|
|
22
|
+
### Preconditions
|
23
|
+
* The machine to be cloned has to be in "Stopped" state.
|
24
|
+
* The data disks to be copied have to be in state "Ready".
|
23
25
|
|
24
26
|
### Example
|
25
27
|
|
26
28
|
```bash
|
27
|
-
cloudstack_cloner clone --virtual_machine test01 --clone-name test-clone --project Playground --data-volumes test
|
29
|
+
$ cloudstack_cloner clone --virtual_machine test01 --clone-name test-clone --project Playground --data-volumes test-volume --offering 2cpu_2gb
|
28
30
|
```
|
29
31
|
|
30
32
|
## Contributing
|
@@ -24,21 +24,20 @@ module CloudstackCloner
|
|
24
24
|
|
25
25
|
desc "clone", "Clone a virtual machine"
|
26
26
|
option :virtual_machine,
|
27
|
-
desc: "
|
27
|
+
desc: "Name of the vm to clone",
|
28
28
|
required: true
|
29
29
|
option :project,
|
30
|
-
desc: "
|
30
|
+
desc: "Name of project"
|
31
31
|
option :clone_name,
|
32
|
-
desc: "
|
32
|
+
desc: "Name of the new vm",
|
33
33
|
required: true
|
34
34
|
option :offering,
|
35
|
-
desc: "
|
35
|
+
desc: "Name of the compute offering for the new vm"
|
36
36
|
option :data_volumes,
|
37
|
-
desc: "
|
37
|
+
desc: "Names of data volumes to attach, separated by space",
|
38
38
|
type: :array
|
39
39
|
def clone
|
40
|
-
opts = options.dup
|
41
|
-
opts = resolve_project(opts)
|
40
|
+
opts = resolve_project(options.dup)
|
42
41
|
opts = resolve_virtual_machine(opts)
|
43
42
|
opts = resolve_compute_offering(opts)
|
44
43
|
clone_vm(opts)
|
@@ -9,8 +9,16 @@ module CloudstackCloner
|
|
9
9
|
opts.merge(listall: true, name: opts[:virtual_machine])
|
10
10
|
).first
|
11
11
|
|
12
|
+
if client.list_virtual_machines(
|
13
|
+
opts.merge(listall: true, name: opts[:clone_name])
|
14
|
+
).size > 0
|
15
|
+
say_log "Failure: ", :red
|
16
|
+
say "VM with name #{opts[:clone_name]} already exists."
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
|
12
20
|
if vm["state"] == "Running"
|
13
|
-
|
21
|
+
say_log "Failure: ", :red
|
14
22
|
say "VM #{vm["name"]} has to be stopped in order to create a template."
|
15
23
|
exit 1
|
16
24
|
end
|
@@ -22,7 +30,7 @@ module CloudstackCloner
|
|
22
30
|
type: "DATADISK",
|
23
31
|
project_id: opts[:project_id]
|
24
32
|
).first
|
25
|
-
|
33
|
+
say_log "Failure: ", :red
|
26
34
|
say "Volume #{disk} not found."
|
27
35
|
exit 1
|
28
36
|
end
|
@@ -30,7 +38,6 @@ module CloudstackCloner
|
|
30
38
|
end
|
31
39
|
|
32
40
|
volume = client.list_volumes(opts.merge(listall: true, type: "root")).first
|
33
|
-
|
34
41
|
templ_name = "#{vm["name"]}-#{Time.now.strftime("%F")}"
|
35
42
|
|
36
43
|
if template = client.list_templates(
|
@@ -39,9 +46,9 @@ module CloudstackCloner
|
|
39
46
|
projectid: opts[:project_id],
|
40
47
|
templatefilter: "self"
|
41
48
|
).first
|
42
|
-
|
49
|
+
say_log "Template #{templ_name} already exists.", :green
|
43
50
|
else
|
44
|
-
|
51
|
+
say_log "Create template from volume #{volume["name"]} ", :yellow
|
45
52
|
template = client.create_template(
|
46
53
|
name: templ_name,
|
47
54
|
displaytext: templ_name,
|
@@ -51,7 +58,7 @@ module CloudstackCloner
|
|
51
58
|
say " [OK]", :green
|
52
59
|
end
|
53
60
|
|
54
|
-
|
61
|
+
say_log "Creating VM from template #{template["name"]} ", :yellow
|
55
62
|
clone = client.deploy_virtual_machine(
|
56
63
|
name: opts[:clone_name],
|
57
64
|
displaytext: opts[:clone_name],
|
@@ -65,11 +72,11 @@ module CloudstackCloner
|
|
65
72
|
|
66
73
|
|
67
74
|
data_volumes.each do |volume|
|
68
|
-
|
75
|
+
say_log "Creating snapshot for volume #{volume["name"]} ", :yellow
|
69
76
|
snapshot = client.create_snapshot(volumeid: volume["id"])["snapshot"]
|
70
77
|
say " [OK]", :green
|
71
78
|
|
72
|
-
|
79
|
+
say_log "Creating clone of volume #{volume["name"]} ", :yellow
|
73
80
|
volume = client.create_volume(
|
74
81
|
name: "#{volume["name"]}_#{opts[:clone_name]}",
|
75
82
|
snapshot_id: snapshot["id"],
|
@@ -77,14 +84,14 @@ module CloudstackCloner
|
|
77
84
|
)["volume"]
|
78
85
|
say " [OK]", :green
|
79
86
|
|
80
|
-
|
87
|
+
say_log "Attach clone of volume #{volume["name"]} to VM #{clone["name"]} ", :yellow
|
81
88
|
client.attach_volume(
|
82
89
|
id: volume["id"],
|
83
90
|
virtualmachineid: clone["id"]
|
84
91
|
)
|
85
92
|
say " [OK]", :green
|
86
93
|
|
87
|
-
|
94
|
+
say_log "Delete snapshot of volume #{volume["name"]} ", :yellow
|
88
95
|
volume = client.delete_snapshot(id: snapshot["id"])
|
89
96
|
say " [OK]", :green
|
90
97
|
end
|
@@ -93,6 +100,11 @@ module CloudstackCloner
|
|
93
100
|
|
94
101
|
private
|
95
102
|
|
103
|
+
def say_log(message, color = nil)
|
104
|
+
say "[#{Time.new.strftime("%F-%X")}] - "
|
105
|
+
say "#{message}", color
|
106
|
+
end
|
107
|
+
|
96
108
|
def client
|
97
109
|
@config ||= load_configuration(options[:config_file], options[:env]).first
|
98
110
|
@client ||= CloudstackClient::Client.new(
|
@@ -1,81 +1,67 @@
|
|
1
1
|
module CloudstackCloner
|
2
2
|
module OptionResolver
|
3
3
|
|
4
|
-
def
|
5
|
-
|
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]
|
4
|
+
def resolve_zone(opts)
|
5
|
+
if opts[:zone]
|
20
6
|
zones = client.list_zones
|
21
|
-
zone = zones.find {|z| z['name'] ==
|
7
|
+
zone = zones.find {|z| z['name'] == opts[:zone] }
|
22
8
|
if !zone
|
23
|
-
msg =
|
9
|
+
msg = opts[:zone] ? "Zone '#{opts[:zone]}' is invalid." : "No zone found."
|
24
10
|
say "Error: #{msg}", :red
|
25
11
|
exit 1
|
26
12
|
end
|
27
|
-
|
13
|
+
opts[:zone_id] = zone['id']
|
28
14
|
end
|
29
|
-
|
15
|
+
opts
|
30
16
|
end
|
31
17
|
|
32
|
-
def resolve_domain(
|
33
|
-
if
|
34
|
-
if domain = client.list_domains(name:
|
35
|
-
|
18
|
+
def resolve_domain(opts)
|
19
|
+
if opts[:domain]
|
20
|
+
if domain = client.list_domains(name: opts[:domain]).first
|
21
|
+
opts[:domain_id] = domain['id']
|
36
22
|
else
|
37
|
-
say "Error: Domain #{
|
23
|
+
say "Error: Domain #{opts[:domain]} not found.", :red
|
38
24
|
exit 1
|
39
25
|
end
|
40
26
|
end
|
41
|
-
|
27
|
+
opts
|
42
28
|
end
|
43
29
|
|
44
|
-
def resolve_project(
|
45
|
-
if
|
46
|
-
if %w(ALL -1).include?
|
47
|
-
|
48
|
-
elsif project = client.list_projects(name:
|
49
|
-
|
30
|
+
def resolve_project(opts)
|
31
|
+
if opts[:project]
|
32
|
+
if %w(ALL -1).include? opts[:project]
|
33
|
+
opts[:project_id] = "-1"
|
34
|
+
elsif project = client.list_projects(name: opts[:project], listall: true).first
|
35
|
+
opts[:project_id] = project['id']
|
50
36
|
else
|
51
|
-
say "Error: Project #{
|
37
|
+
say "Error: Project #{opts[:project]} not found.", :red
|
52
38
|
exit 1
|
53
39
|
end
|
54
40
|
end
|
55
|
-
|
41
|
+
opts
|
56
42
|
end
|
57
43
|
|
58
|
-
def resolve_account(
|
59
|
-
if
|
60
|
-
if account = client.list_accounts(name:
|
61
|
-
|
62
|
-
|
44
|
+
def resolve_account(opts)
|
45
|
+
if opts[:account]
|
46
|
+
if account = client.list_accounts(name: opts[:account], listall: true).first
|
47
|
+
opts[:account_id] = account['id']
|
48
|
+
opts[:domain_id] = account['domainid']
|
63
49
|
else
|
64
|
-
say "Error: Account #{
|
50
|
+
say "Error: Account #{opts[:account]} not found.", :red
|
65
51
|
exit 1
|
66
52
|
end
|
67
53
|
end
|
68
|
-
|
54
|
+
opts
|
69
55
|
end
|
70
56
|
|
71
|
-
def resolve_networks(
|
57
|
+
def resolve_networks(opts)
|
72
58
|
networks = []
|
73
59
|
available_networks = network = client.list_networks(
|
74
|
-
zone_id:
|
75
|
-
project_id:
|
60
|
+
zone_id: opts[:zone_id],
|
61
|
+
project_id: opts[:project_id]
|
76
62
|
)
|
77
|
-
if
|
78
|
-
|
63
|
+
if opts[:networks]
|
64
|
+
opts[:networks].each do |name|
|
79
65
|
unless network = available_networks.find { |n| n['name'] == name }
|
80
66
|
say "Error: Network '#{name}' not found.", :red
|
81
67
|
exit 1
|
@@ -85,100 +71,81 @@ module CloudstackCloner
|
|
85
71
|
end
|
86
72
|
networks.compact!
|
87
73
|
if networks.empty?
|
88
|
-
#unless default_network = client.list_networks(project_id:
|
74
|
+
#unless default_network = client.list_networks(project_id: opts[:project_id]).find {
|
89
75
|
# |n| n['isdefault'] == true }
|
90
|
-
unless default_network = client.list_networks(project_id:
|
76
|
+
unless default_network = client.list_networks(project_id: opts[:project_id]).first
|
91
77
|
say "Error: No default network found.", :red
|
92
78
|
exit 1
|
93
79
|
end
|
94
80
|
networks << available_networks.first['id'] rescue nil
|
95
81
|
end
|
96
|
-
|
97
|
-
|
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
|
82
|
+
opts[:network_ids] = networks.join(',')
|
83
|
+
opts
|
117
84
|
end
|
118
85
|
|
119
|
-
def resolve_template(
|
120
|
-
if
|
86
|
+
def resolve_template(opts)
|
87
|
+
if opts[:template]
|
121
88
|
if template = client.list_templates(
|
122
|
-
name:
|
89
|
+
name: opts[:template],
|
123
90
|
template_filter: "executable",
|
124
|
-
project_id:
|
91
|
+
project_id: opts[:project_id]
|
125
92
|
).first
|
126
|
-
|
93
|
+
opts[:template_id] = template['id']
|
127
94
|
else
|
128
|
-
say "Error: Template #{
|
95
|
+
say "Error: Template #{opts[:template]} not found.", :red
|
129
96
|
exit 1
|
130
97
|
end
|
131
98
|
end
|
132
|
-
|
99
|
+
opts
|
133
100
|
end
|
134
101
|
|
135
|
-
def resolve_compute_offering(
|
136
|
-
if
|
137
|
-
if offering = client.list_service_offerings(name:
|
138
|
-
|
102
|
+
def resolve_compute_offering(opts)
|
103
|
+
if opts[:offering]
|
104
|
+
if offering = client.list_service_offerings(name: opts[:offering]).first
|
105
|
+
opts[:service_offering_id] = offering['id']
|
139
106
|
else
|
140
|
-
say "Error: Offering #{
|
107
|
+
say "Error: Offering #{opts[:offering]} not found.", :red
|
141
108
|
exit 1
|
142
109
|
end
|
143
110
|
end
|
144
|
-
|
111
|
+
opts
|
145
112
|
end
|
146
113
|
|
147
|
-
def resolve_disk_offering(
|
148
|
-
if
|
149
|
-
unless disk_offering = client.list_disk_offerings(name:
|
150
|
-
say "Error: Disk offering '#{
|
114
|
+
def resolve_disk_offering(opts)
|
115
|
+
if opts[:disk_offering]
|
116
|
+
unless disk_offering = client.list_disk_offerings(name: opts[:disk_offering]).first
|
117
|
+
say "Error: Disk offering '#{opts[:disk_offering]}' not found.", :red
|
151
118
|
exit 1
|
152
119
|
end
|
153
|
-
|
120
|
+
opts[:disk_offering_id] = disk_offering['id']
|
154
121
|
end
|
155
|
-
|
122
|
+
opts
|
156
123
|
end
|
157
124
|
|
158
|
-
def resolve_virtual_machine(
|
159
|
-
if
|
160
|
-
args = { name:
|
161
|
-
args[:project_id] =
|
125
|
+
def resolve_virtual_machine(opts)
|
126
|
+
if opts[:virtual_machine]
|
127
|
+
args = { name: opts[:virtual_machine], listall: true }
|
128
|
+
args[:project_id] = opts[:project_id]
|
162
129
|
unless vm = client.list_virtual_machines(args).first
|
163
|
-
say "Error: VM '#{
|
130
|
+
say "Error: VM '#{opts[:virtual_machine]}' not found.", :red
|
164
131
|
exit 1
|
165
132
|
end
|
166
|
-
|
133
|
+
opts[:virtual_machine_id] = vm['id']
|
167
134
|
end
|
168
|
-
|
135
|
+
opts
|
169
136
|
end
|
170
137
|
|
171
|
-
def resolve_snapshot(
|
172
|
-
if
|
173
|
-
args = { name:
|
174
|
-
args[:project_id] =
|
138
|
+
def resolve_snapshot(opts)
|
139
|
+
if opts[:snapshot]
|
140
|
+
args = { name: opts[:snapshot], listall: true }
|
141
|
+
args[:project_id] = opts[:project_id]
|
175
142
|
unless snapshot = client.list_snapshots(args).first
|
176
|
-
say "Error: Snapshot '#{
|
143
|
+
say "Error: Snapshot '#{opts[:snapshot]}' not found.", :red
|
177
144
|
exit 1
|
178
145
|
end
|
179
|
-
|
146
|
+
opts[:snapshot_id] = snapshot['id']
|
180
147
|
end
|
181
|
-
|
148
|
+
opts
|
182
149
|
end
|
183
150
|
|
184
151
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cloudstack_cloner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- niwo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-07-
|
11
|
+
date: 2015-07-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -106,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
106
|
version: '0'
|
107
107
|
requirements: []
|
108
108
|
rubyforge_project:
|
109
|
-
rubygems_version: 2.
|
109
|
+
rubygems_version: 2.4.5
|
110
110
|
signing_key:
|
111
111
|
specification_version: 4
|
112
112
|
summary: Automated CloudStack VM cloning
|