cloudstack_cloner 0.0.1 → 0.0.2
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 +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
|
+
[](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
|