CloudyScripts 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+