CloudyScripts 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/LICENSE ADDED
@@ -0,0 +1,4 @@
1
+ == CloudyScriptLibrary
2
+
3
+ sdsds
4
+ Put appropriate LICENSE for your project here.
data/README ADDED
@@ -0,0 +1 @@
1
+ Scripts to facilitate programming for infrastructure clouds
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ #
2
+ # To change this template, choose Tools | Templates
3
+ # and open the template in the editor.
4
+
5
+
6
+ require 'rubygems'
7
+ require 'rake'
8
+ require 'rake/clean'
9
+ require 'rake/gempackagetask'
10
+ require 'rake/rdoctask'
11
+ require 'rake/testtask'
12
+
13
+ spec = Gem::Specification.new do |s|
14
+ s.name = 'CloudyScripts'
15
+ s.version = '0.0.1'
16
+ s.has_rdoc = true
17
+ s.extra_rdoc_files = ['README', 'LICENSE']
18
+ s.summary = 'Scripts to facilitate programming for infrastructure clouds.'
19
+ s.description = s.summary
20
+ s.homepage = "http://elastic-security.com"
21
+ s.rubyforge_project = "cloudyscripts"
22
+ s.author = 'Matthias Jung'
23
+ s.email = 'matthias.jung@gmail.com'
24
+ # s.executables = ['your_executable_here']
25
+ s.files = %w(LICENSE README Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
26
+ s.require_path = "lib"
27
+ s.bindir = "bin"
28
+ s.has_rdoc = true
29
+ s.add_dependency("amazon-ec2")
30
+ s.add_dependency("net-ssh")
31
+ end
32
+
33
+ Rake::GemPackageTask.new(spec) do |p|
34
+ p.gem_spec = spec
35
+ p.need_tar = true
36
+ p.need_zip = true
37
+ end
38
+
39
+ Rake::RDocTask.new do |rdoc|
40
+ files =['README', 'LICENSE', 'lib/**/*.rb']
41
+ rdoc.rdoc_files.add(files)
42
+ rdoc.main = "README" # page to start on
43
+ rdoc.title = "CloudyScripts Docs"
44
+ rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
45
+ rdoc.options << '--line-numbers'
46
+ end
47
+
48
+ Rake::TestTask.new do |t|
49
+ t.test_files = FileList['test/**/*.rb']
50
+ end
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'net/ssh'
3
+ require 'AWS'
4
+
5
+ require "../test/mock/mocked_ec2_api"
6
+ require "../test/mock/mocked_remote_command_handler"
7
+
8
+ require "scripts/ec2/dm_encrypt"
9
+
10
+ rch = MockedRemoteCommandHandler.new
11
+ ec2 = MockedEc2Api.new
12
+
13
+ params = {
14
+ :remote_command_handler => rch,
15
+ :ec2_api_handler => ec2,
16
+ :password => "password",
17
+ :ip_address => "127.0.0.1",
18
+ :ssh_key_file => "/Users/mats/.ssh",
19
+ :device => "/dev/sdh",
20
+ :device_name => "device-vol-i-12345"
21
+ }
22
+ script = DmEncrypt.new(params)
23
+ script.start_script()
24
+ if script.get_execution_result[:failed] == nil || script.get_execution_result[:failed]
25
+ puts "script failed: #{script.get_execution_result[:failure_reason]}"
26
+ end
27
+
28
+ puts "done"
@@ -0,0 +1,172 @@
1
+ require 'lib/ssh_api'
2
+
3
+ class DmCryptHelper
4
+
5
+ def set_ssh(ssh_session)
6
+ @ssh_session = ssh_session
7
+ end
8
+
9
+ def install()
10
+ #TODO: dm-crypt seems to be installed automatically
11
+ true
12
+ end
13
+
14
+ def tools_installed?()
15
+ @ssh_session.exec! "which dmsetup" do |ch, stream, data|
16
+ if stream == :stderr
17
+ return false
18
+ end
19
+ end
20
+ @ssh_session.exec! "which cryptsetup" do |ch, stream, data|
21
+ if stream == :stderr
22
+ return false
23
+ end
24
+ end
25
+ #TODO: check also that "/dev/mapper /dev/mapper/control" exist
26
+ true
27
+ end
28
+
29
+ def encrypt_storage(name, password, device, path)
30
+ # first: check if a file in /dev/mapper exists
31
+ if SshApi.file_exists?(@ssh_session, "/dev/mapper/dm-#{name}")
32
+ mapper_exists = true
33
+ else
34
+ mapper_exists = false
35
+ end
36
+ puts "mapper exists = #{mapper_exists}"
37
+ exec_string = "cryptsetup create dm-#{name} #{device}"
38
+ if !mapper_exists
39
+ #mapper does not exist, create it
40
+ channel = @ssh_session.open_channel do |ch|
41
+ ch.send_data("#{password}\n")
42
+ puts "execute #{exec_string}"
43
+ ch.exec exec_string do |ch, success|
44
+ puts "success = #{success}"
45
+ if !success
46
+ err = "Failed during creation of encrypted partition"
47
+ #puts "#{err}: #{data}"
48
+ raise Exception.new(err)
49
+ end
50
+ end
51
+ end
52
+ channel.wait
53
+ end
54
+ # now mapper is created
55
+ # second: check if pvscan sucessful
56
+ pv_exists = false
57
+ @ssh_session.exec! "/sbin/pvscan" do |ch, stream, data|
58
+ if stream == :stdout
59
+ if data.include?("vg-#{name}")
60
+ pv_exists = true
61
+ else
62
+ pv_exists = false
63
+ end
64
+ end
65
+ end
66
+ if !pv_exists
67
+ exec_string = "pvcreate /dev/mapper/dm-#{name}"
68
+ puts "pv does not exist - execute: #{exec_string}"
69
+ #private volume does not exist, create it
70
+ channel = @ssh_session.open_channel do |ch|
71
+ ch.send_data("y\n")
72
+ ch.exec exec_string do |ch, success|
73
+ puts "success = #{success}"
74
+ if !success
75
+ err = "Failed during creation of physical volume"
76
+ #puts "#{err}: #{data}"
77
+ raise Exception.new(err)
78
+ end
79
+ end
80
+ end
81
+ channel.wait
82
+ end
83
+ # third: check if vgscan successful
84
+ vg_exists = false
85
+ @ssh_session.exec! "/sbin/vgscan" do |ch, stream, data|
86
+ if stream == :stdout
87
+ if data.include?("vg-#{name}")
88
+ vg_exists = true
89
+ else
90
+ vg_exists = false
91
+ end
92
+ end
93
+ end
94
+ if !vg_exists
95
+ exec_string = "vgcreate vg-#{name} /dev/mapper/dm-#{name}"
96
+ puts "vg_exists == false; execute #{exec_string}"
97
+ @ssh_session.exec! exec_string do |ch, stream, data|
98
+ if stream == :stderr && !data.blank?
99
+ err = "Failed during creation of volume group"
100
+ puts "#{err}: #{data}"
101
+ raise Exception.new(err)
102
+ end
103
+ end
104
+ #exec_string = "lvcreate -n lv-#{name} -L#{size_in_mb.to_s}M vg-#{name}"
105
+ exec_string = "lvcreate -n lv-#{name} -l100%FREE vg-#{name}"
106
+ puts "execute #{exec_string}"
107
+ @ssh_session.exec! exec_string do |ch, stream, data|
108
+ if stream == :stderr && !data.blank?
109
+ err = "Failed during creation of logical volume"
110
+ puts "#{err}: #{data}"
111
+ raise Exception.new(err)
112
+ end
113
+ end
114
+ exec_string = "mkfs -t ext3 /dev/vg-#{name}/lv-#{name}"
115
+ puts "execute #{exec_string}"
116
+ @ssh_session.exec! exec_string #do |ch, stream, data|
117
+ #if stream == :stderr && !data.blank?
118
+ #err = "Failed during creation of file-system"
119
+ #puts "#{err}: #{data}"
120
+ #raise Exception.new(err)
121
+ #end
122
+ #end
123
+ if !SshApi.file_exists?(@ssh_session,"/dev/vg-#{name}/lv-#{name}")
124
+ err = "Missing file: /dev/vg-#{name}/lv-#{name}"
125
+ raise Exception.new(err)
126
+ end
127
+ else
128
+ exec_string = "/sbin/vgchange -a y vg-#{name}"
129
+ puts "vg_exists == true; execute #{exec_string}"
130
+ @ssh_session.exec! exec_string do |ch, stream, data| #TODO: the right size instead L2G!
131
+ if stream == :stderr && !data.blank?
132
+ err = "Failed during re-activation of volume group"
133
+ puts "#{err}: #{data}"
134
+ raise Exception.new(err)
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ def test_storage_encryption(password, mount_point, path)
141
+ raise Exception.new("not yet implemented")
142
+ end
143
+
144
+ def undo_encryption(name, path)
145
+ exec_string = "umount #{path}"
146
+ puts "going to execute #{exec_string}"
147
+ @ssh_session.exec! exec_string do |ch, stream, data|
148
+ puts "returns #{data}"
149
+ end
150
+ exec_string = "lvremove --verbose vg-#{name} -f" #[with confirmation?]
151
+ puts "going to execute #{exec_string}"
152
+ @ssh_session.exec! exec_string do |ch, stream, data|
153
+ puts "returns #{data}"
154
+ end
155
+ exec_string = "vgremove vg-#{name}"
156
+ puts "going to execute #{exec_string}"
157
+ @ssh_session.exec! exec_string do |ch, stream, data|
158
+ puts "returns #{data}"
159
+ end
160
+ exec_string = "pvremove /dev/mapper/dm-#{name}"
161
+ puts "going to execute #{exec_string}"
162
+ @ssh_session.exec! exec_string do |ch, stream, data|
163
+ puts "returns #{data}"
164
+ end
165
+ exec_string = "cryptsetup remove dm-#{name}"
166
+ puts "going to execute #{exec_string}"
167
+ @ssh_session.exec! exec_string do |ch, stream, data|
168
+ puts "returns #{data}"
169
+ end
170
+ end
171
+
172
+ end
@@ -0,0 +1,113 @@
1
+ require 'rubygems'
2
+ require 'net/ssh'
3
+
4
+ class RemoteCommandHandler
5
+ def initialize
6
+ @crypto = DmCryptHelper.new #TODO: instantiate helpers for different tools
7
+ end
8
+
9
+ def self.file_exists?(ssh_session, path)
10
+ result = true
11
+ ssh_session.exec!("ls #{path}") do |ch, stream, data|
12
+ if stream == :stderr
13
+ result = false
14
+ end
15
+ end
16
+ result
17
+ end
18
+
19
+ def connect(ip, keyfile)
20
+ @ssh_session = Net::SSH.start(ip, 'root', :keys => [keyfile])
21
+ @crypto.set_ssh(@ssh_session)
22
+ end
23
+
24
+ def disconnect
25
+ @ssh_session.close
26
+ end
27
+
28
+ def install(software_package)
29
+ @crypto.install()
30
+ end
31
+
32
+ def tools_installed?(software_package)
33
+ @crypto.tools_installed?
34
+ end
35
+
36
+ def encrypt_storage(name, password, device, path)
37
+ @crypto.encrypt_storage(name, password, device, path)
38
+ end
39
+
40
+ def storage_encrypted?(password, device, path)
41
+ drive_mounted?(path) #TODO: must at least also check the name
42
+ end
43
+
44
+ # Checks if the drive on path is mounted
45
+ def drive_mounted?(path)
46
+ #check if drive mounted
47
+ drive_found = false
48
+ @ssh_session.exec! "mount" do |ch, stream, data|
49
+ if stream == :stdout
50
+ puts "mount command produces the following data: #{data}\n---------------"
51
+ if data.include?("on #{path} type")
52
+ drive_found = true
53
+ else
54
+ puts "not mounted: #{data}"
55
+ end
56
+ end
57
+ end
58
+ if drive_found
59
+ return SshApi.file_exists?(@ssh_session, path)
60
+ else
61
+ puts "not mounted (since #{path} non-existing)"
62
+ false
63
+ end
64
+ end
65
+
66
+ # Checks if the drive on path is mounted with the specific device
67
+ def drive_mounted_as?(device, path)
68
+ #check if drive mounted
69
+ drive_mounted = false
70
+ @ssh_session.exec! "mount" do |ch, stream, data|
71
+ if stream == :stdout
72
+ if data.include?("#{device} on #{path} type")
73
+ drive_mounted = true
74
+ else
75
+ puts "not mounted: #{data}"
76
+ end
77
+ end
78
+ end
79
+ drive_mounted
80
+ end
81
+
82
+ def activate_encrypted_volume(name, path)
83
+ drive_mounted = drive_mounted?(path)
84
+ puts "drive #{path} mounted? #{drive_mounted}"
85
+ if !drive_mounted
86
+ @ssh_session.exec! "mkdir #{path}"
87
+ exec_string = "mount /dev/vg-#{name}/lv-#{name} #{path}"
88
+ puts "drive not mounted; execute: #{exec_string}"
89
+ @ssh_session.exec! "mount /dev/vg-#{name}/lv-#{name} #{path}" do |ch, stream, data|
90
+ if stream == :stderr && !data.blank?
91
+ err = "Failed during mounting encrypted device"
92
+ puts "#{err}: #{data}"
93
+ puts "mount /dev/vg-#{name}/lv-#{name} #{path}"
94
+ raise Exception.new(err)
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ def undo_encryption(name, path)
101
+ @crypto.undo_encryption(name, path)
102
+ end
103
+
104
+ def umount(path)
105
+ exec_string = "umount #{path}"
106
+ puts "going to execute #{exec_string}"
107
+ @ssh_session.exec! exec_string do |ch, stream, data|
108
+ puts "ssh_api.umount: returns #{data}"
109
+ end
110
+ !drive_mounted?(path)
111
+ end
112
+
113
+ end
@@ -0,0 +1,69 @@
1
+ # Implements a little state-machine.
2
+ # Usage: for every state you need, extend this class.
3
+ # The method enter() must be implemented for every state you code and
4
+ # return another state.
5
+ class ScriptExecutionState
6
+ # context information for the state (hash)
7
+ attr_reader :context
8
+
9
+ def initialize(context)
10
+ @context = context
11
+ end
12
+
13
+ # Start the state machine using this state as initial state.
14
+ def start_state_machine
15
+ @current_state = self
16
+ puts "start state machine with #{@current_state.inspect}"
17
+ while !@current_state.done? && !@current_state.failed?
18
+ begin
19
+ @current_state = @current_state.enter()
20
+ rescue Exception => e
21
+ @current_state = FailedState.new(@context, e.to_s, @current_state)
22
+ puts "Exception: #{e}"
23
+ puts "#{e.backtrace.join("\n")}"
24
+ end
25
+ end
26
+ @current_state
27
+ end
28
+
29
+ # Returns the state that is reached after execution.
30
+ def end_state
31
+ @current_state
32
+ end
33
+
34
+ # To be implemented. Executes the code for this state.
35
+ def enter
36
+ raise Exception.new("TaskExecutionState is abstract")
37
+ end
38
+
39
+ # To be implemented. Indicates if the final state is reached.
40
+ def done?
41
+ false
42
+ end
43
+
44
+ # To be implemented. Indicates if the final state is a failure state.
45
+ def failed?
46
+ false
47
+ end
48
+
49
+ def to_s
50
+ s = self.class.to_s
51
+ s.sub(/.*\:\:/,'')
52
+ end
53
+
54
+ class FailedState < ScriptExecutionState
55
+ attr_accessor :failure_reason, :from_state
56
+ def initialize(context, failure_reason, from_state)
57
+ super(context)
58
+ @failure_reason = failure_reason
59
+ @from_state = from_state
60
+ end
61
+ def done?
62
+ true
63
+ end
64
+ def failed?
65
+ true
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,182 @@
1
+ require "help/script_execution_state"
2
+ require "scripts/ec2/ec2_script"
3
+
4
+ # Encrypts an EC2 Storage
5
+ class DmEncrypt < Ec2Script
6
+ def initialize(input_params)
7
+ super(input_params)
8
+ end
9
+
10
+ # Input parameters
11
+ # aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
12
+ # aws_secret_key => the Amazon AWS Secret Key
13
+ # ip_address => IP Address of the machine to connect to
14
+ # ssh_key_file => Path of the keyfile used to connect to the machine
15
+ # device => Path of the device to encrypt
16
+ # device_name => Name of the Device to encrypt
17
+ # storage_path => Path on which the encrypted device is mounted
18
+ # remote_command_handler => object that allows to connect via ssh and execute commands
19
+ # ec2_api_handler => object that allows to access the EC2 API
20
+ # password => password used for encryption
21
+ #
22
+ def initialize(input_params)
23
+ super(input_params)
24
+ @result = {:done => false}
25
+ end
26
+
27
+ # Executes the script.
28
+ def start_script
29
+ begin
30
+ current_state = DmEncryptState.load_state(@input_params)
31
+ end_state = current_state.start_state_machine()
32
+ if end_state.failed?
33
+ @result[:failed] = true
34
+ @result[:failure_reason] = current_state.end_state.failure_reason
35
+ @result[:end_state] = current_state.end_state
36
+ else
37
+ @result[:failed] = false
38
+ end
39
+ rescue Exception => e
40
+ puts "exception during encryption: #{e}"
41
+ puts e.backtrace.join("\n")
42
+ err = e.to_s
43
+ err += " (in #{current_state.end_state.to_s})" unless current_state.blank?
44
+ @result[:failed] = true
45
+ @result[:failure_reason] = err
46
+ @result[:end_state] = current_state.end_state unless current_state.blank?
47
+ ensure
48
+ begin
49
+ @input_params[:remote_command_handler].disconnect
50
+ rescue Exception => e2
51
+ puts "rescue disconnect: #{e2}"
52
+ end
53
+ end
54
+
55
+ #
56
+ @result[:done] = true
57
+ end
58
+
59
+ # Returns a hash with the following information:
60
+ # :done => if execution is done
61
+ #
62
+ def get_execution_result
63
+ @result
64
+ end
65
+
66
+ private
67
+
68
+ # Here begins the state machine implementation
69
+
70
+ class DmEncryptState < ScriptExecutionState
71
+
72
+ def self.load_state(context)
73
+ InitialState.new(context)
74
+ end
75
+ end
76
+
77
+ # Starting state. Tries to connect via ssh.
78
+ class InitialState < DmEncryptState
79
+ def enter
80
+ connect()
81
+ end
82
+
83
+ private
84
+
85
+ def connect()
86
+ puts "InitialState.connect"
87
+ @context[:remote_command_handler].connect(@context[:ip_address], @context[:ssh_key_file])
88
+ ConnectedState.new(@context)
89
+ end
90
+ end
91
+
92
+ # Connected via SSH. Tries to install dm-encrypt.#TODO: depends on OS
93
+ class ConnectedState < DmEncryptState
94
+ def enter
95
+ install_tools()
96
+ end
97
+
98
+ private
99
+ def install_tools
100
+ puts "ConnectedState.install_tools"
101
+ if !tools_installed?
102
+ @context[:remote_command_handler].install("dm-crypt") #TODO: constant somewhere? admin parameter?
103
+ end
104
+ if tools_installed?
105
+ puts "system says that tools are installed"
106
+ ToolInstalledState.new(@context)
107
+ else
108
+ FailedState.new(@context, "Installation of Tools failed", ConnectedState.new(@context))
109
+ end
110
+ end
111
+
112
+ def tools_installed?
113
+ if @context[:remote_command_handler].tools_installed?("dm-crypt")
114
+ true
115
+ else
116
+ false
117
+ end
118
+ end
119
+ end
120
+
121
+ # Connected and Tools installed. Start encryption.
122
+ class ToolInstalledState < DmEncryptState
123
+ def enter
124
+ create_encrypted_volume()
125
+ end
126
+
127
+ private
128
+ def create_encrypted_volume
129
+ puts "ToolInstalledState.create_encrypted_volume"
130
+ #first check if the drive is not yet mounted by someone else
131
+ if @context[:remote_command_handler].drive_mounted?(@context[:storage_path])
132
+ if !@context[:remote_command_handler].drive_mounted_as?(calc_device_name(), @context[:storage_path])
133
+ raise Exception.new("Drive is already used by another device")
134
+ end
135
+ end
136
+ #
137
+ @context[:remote_command_handler].encrypt_storage(@context[:device_name],
138
+ @context[:password], @context[:device], @context[:storage_path])
139
+ VolumeCreatedState.new(@context)
140
+ end
141
+
142
+ def calc_device_name
143
+ dev = @context[:device_name].gsub(/[-]/,"--")
144
+ "/dev/mapper/vg--#{dev}-lv--#{dev}"
145
+ end
146
+
147
+ end
148
+
149
+ class VolumeCreatedState < DmEncryptState
150
+ def enter
151
+ mount_and_activate()
152
+ end
153
+
154
+ private
155
+ def mount_and_activate
156
+ puts "VolumeCreatedState.mount_and_activate"
157
+ @context[:remote_command_handler].activate_encrypted_volume(@context[:device_name],@context[:storage_path])
158
+ MountedAndActivatedState.new(@context)
159
+ end
160
+ end
161
+
162
+ class MountedAndActivatedState < DmEncryptState
163
+ def enter
164
+ cleanup()
165
+ end
166
+
167
+ private
168
+ def cleanup()
169
+ puts "MountedAndActivatedState.cleanup"
170
+ @context[:remote_command_handler].disconnect()
171
+ DoneState.new(@context)
172
+ end
173
+
174
+ end
175
+
176
+ class DoneState < DmEncryptState
177
+ def done?
178
+ true
179
+ end
180
+ end
181
+
182
+ end
@@ -0,0 +1,12 @@
1
+ # Base class for any script on EC2.
2
+ class Ec2Script
3
+ # Input parameters
4
+ # aws_access_key => the Amazon AWS Access Key (see Your Account -> Security Credentials)
5
+ # aws_secret_key => the Amazon AWS Secret Key
6
+ #
7
+ def initialize(input_params)
8
+ @input_params = input_params
9
+ end
10
+
11
+ end
12
+
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: CloudyScripts
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Matthias Jung
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-11 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: amazon-ec2
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: net-ssh
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: Scripts to facilitate programming for infrastructure clouds.
36
+ email: matthias.jung@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README
43
+ - LICENSE
44
+ files:
45
+ - LICENSE
46
+ - README
47
+ - Rakefile
48
+ - lib/cloudyscripts.rb
49
+ - lib/help/dm_crypt_helper.rb
50
+ - lib/help/remote_command_handler.rb
51
+ - lib/help/script_execution_state.rb
52
+ - lib/scripts/ec2/dm_encrypt.rb
53
+ - lib/scripts/ec2/ec2_script.rb
54
+ has_rdoc: true
55
+ homepage: http://elastic-security.com
56
+ licenses: []
57
+
58
+ post_install_message:
59
+ rdoc_options: []
60
+
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ requirements: []
76
+
77
+ rubyforge_project: cloudyscripts
78
+ rubygems_version: 1.3.5
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: Scripts to facilitate programming for infrastructure clouds.
82
+ test_files: []
83
+