rudy 0.4.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. data/CHANGES.txt +54 -30
  2. data/README.rdoc +100 -12
  3. data/Rakefile +103 -8
  4. data/Rudyfile +119 -0
  5. data/bin/ird +175 -0
  6. data/bin/rudy +259 -156
  7. data/bin/rudy-ec2 +228 -95
  8. data/bin/rudy-s3 +76 -0
  9. data/bin/rudy-sdb +67 -0
  10. data/lib/annoy.rb +270 -0
  11. data/lib/console.rb +30 -9
  12. data/lib/escape.rb +305 -0
  13. data/lib/rudy.rb +151 -182
  14. data/lib/rudy/aws.rb +56 -49
  15. data/lib/rudy/aws/ec2.rb +47 -292
  16. data/lib/rudy/aws/ec2/address.rb +157 -0
  17. data/lib/rudy/aws/ec2/group.rb +301 -0
  18. data/lib/rudy/aws/ec2/image.rb +168 -0
  19. data/lib/rudy/aws/ec2/instance.rb +434 -0
  20. data/lib/rudy/aws/ec2/keypair.rb +104 -0
  21. data/lib/rudy/aws/ec2/snapshot.rb +98 -0
  22. data/lib/rudy/aws/ec2/volume.rb +230 -0
  23. data/lib/rudy/aws/ec2/zone.rb +77 -0
  24. data/lib/rudy/aws/s3.rb +54 -0
  25. data/lib/rudy/aws/sdb.rb +298 -0
  26. data/lib/rudy/aws/sdb/error.rb +46 -0
  27. data/lib/rudy/{metadata/backup.rb → backup.rb} +26 -51
  28. data/lib/rudy/cli.rb +157 -0
  29. data/lib/rudy/cli/aws/ec2/addresses.rb +105 -0
  30. data/lib/rudy/cli/aws/ec2/candy.rb +208 -0
  31. data/lib/rudy/cli/aws/ec2/groups.rb +121 -0
  32. data/lib/rudy/cli/aws/ec2/images.rb +196 -0
  33. data/lib/rudy/cli/aws/ec2/instances.rb +194 -0
  34. data/lib/rudy/cli/aws/ec2/keypairs.rb +53 -0
  35. data/lib/rudy/cli/aws/ec2/snapshots.rb +49 -0
  36. data/lib/rudy/cli/aws/ec2/volumes.rb +104 -0
  37. data/lib/rudy/cli/aws/ec2/zones.rb +22 -0
  38. data/lib/rudy/cli/aws/s3/buckets.rb +50 -0
  39. data/lib/rudy/cli/aws/s3/store.rb +22 -0
  40. data/lib/rudy/cli/aws/sdb/domains.rb +41 -0
  41. data/lib/rudy/cli/candy.rb +8 -0
  42. data/lib/rudy/{command → cli}/config.rb +34 -24
  43. data/lib/rudy/cli/disks.rb +35 -0
  44. data/lib/rudy/cli/machines.rb +94 -0
  45. data/lib/rudy/cli/routines.rb +57 -0
  46. data/lib/rudy/config.rb +77 -72
  47. data/lib/rudy/config/objects.rb +29 -0
  48. data/lib/rudy/disks.rb +248 -0
  49. data/lib/rudy/global.rb +121 -0
  50. data/lib/rudy/huxtable.rb +340 -0
  51. data/lib/rudy/machines.rb +245 -0
  52. data/lib/rudy/metadata.rb +123 -13
  53. data/lib/rudy/routines.rb +47 -0
  54. data/lib/rudy/routines/helpers/diskhelper.rb +101 -0
  55. data/lib/rudy/routines/helpers/scripthelper.rb +91 -0
  56. data/lib/rudy/routines/release.rb +34 -0
  57. data/lib/rudy/routines/shutdown.rb +57 -0
  58. data/lib/rudy/routines/startup.rb +58 -0
  59. data/lib/rudy/scm/svn.rb +1 -1
  60. data/lib/rudy/utils.rb +322 -4
  61. data/lib/storable.rb +26 -17
  62. data/lib/sysinfo.rb +274 -0
  63. data/lib/tryouts.rb +6 -13
  64. data/rudy.gemspec +128 -42
  65. data/support/randomize-root-password +45 -0
  66. data/support/rudy-ec2-startup +9 -9
  67. data/support/update-ec2-ami-tools +20 -0
  68. data/test/05_config/00_setup_test.rb +20 -0
  69. data/test/05_config/30_machines_test.rb +69 -0
  70. data/test/20_sdb/00_setup_test.rb +16 -0
  71. data/test/20_sdb/10_domains_test.rb +115 -0
  72. data/test/25_ec2/00_setup_test.rb +29 -0
  73. data/test/25_ec2/10_keypairs_test.rb +41 -0
  74. data/test/25_ec2/20_groups_test.rb +131 -0
  75. data/test/25_ec2/30_addresses_test.rb +38 -0
  76. data/test/25_ec2/40_volumes_test.rb +49 -0
  77. data/test/25_ec2/50_snapshots_test.rb +74 -0
  78. data/test/26_ec2_instances/00_setup_test.rb +28 -0
  79. data/test/26_ec2_instances/10_instances_test.rb +83 -0
  80. data/test/26_ec2_instances/50_images_test.rb +13 -0
  81. data/test/30_sdb_metadata/00_setup_test.rb +21 -0
  82. data/test/30_sdb_metadata/10_disks_test.rb +109 -0
  83. data/test/30_sdb_metadata/20_backups_test.rb +102 -0
  84. data/test/coverage.txt +51 -0
  85. data/test/helper.rb +36 -0
  86. data/vendor/highline-1.5.1/CHANGELOG +222 -0
  87. data/vendor/highline-1.5.1/INSTALL +35 -0
  88. data/vendor/highline-1.5.1/LICENSE +7 -0
  89. data/vendor/highline-1.5.1/README +63 -0
  90. data/vendor/highline-1.5.1/Rakefile +82 -0
  91. data/vendor/highline-1.5.1/TODO +6 -0
  92. data/vendor/highline-1.5.1/examples/ansi_colors.rb +38 -0
  93. data/vendor/highline-1.5.1/examples/asking_for_arrays.rb +18 -0
  94. data/vendor/highline-1.5.1/examples/basic_usage.rb +75 -0
  95. data/vendor/highline-1.5.1/examples/color_scheme.rb +32 -0
  96. data/vendor/highline-1.5.1/examples/limit.rb +12 -0
  97. data/vendor/highline-1.5.1/examples/menus.rb +65 -0
  98. data/vendor/highline-1.5.1/examples/overwrite.rb +19 -0
  99. data/vendor/highline-1.5.1/examples/page_and_wrap.rb +322 -0
  100. data/vendor/highline-1.5.1/examples/password.rb +7 -0
  101. data/vendor/highline-1.5.1/examples/trapping_eof.rb +22 -0
  102. data/vendor/highline-1.5.1/examples/using_readline.rb +17 -0
  103. data/vendor/highline-1.5.1/lib/highline.rb +758 -0
  104. data/vendor/highline-1.5.1/lib/highline/color_scheme.rb +120 -0
  105. data/vendor/highline-1.5.1/lib/highline/compatibility.rb +17 -0
  106. data/vendor/highline-1.5.1/lib/highline/import.rb +43 -0
  107. data/vendor/highline-1.5.1/lib/highline/menu.rb +395 -0
  108. data/vendor/highline-1.5.1/lib/highline/question.rb +463 -0
  109. data/vendor/highline-1.5.1/lib/highline/system_extensions.rb +193 -0
  110. data/vendor/highline-1.5.1/setup.rb +1360 -0
  111. data/vendor/highline-1.5.1/test/tc_color_scheme.rb +56 -0
  112. data/vendor/highline-1.5.1/test/tc_highline.rb +823 -0
  113. data/vendor/highline-1.5.1/test/tc_import.rb +54 -0
  114. data/vendor/highline-1.5.1/test/tc_menu.rb +429 -0
  115. data/vendor/highline-1.5.1/test/ts_all.rb +15 -0
  116. metadata +141 -38
  117. data/lib/aws_sdb.rb +0 -3
  118. data/lib/aws_sdb/error.rb +0 -42
  119. data/lib/aws_sdb/service.rb +0 -215
  120. data/lib/rudy/aws/simpledb.rb +0 -53
  121. data/lib/rudy/command/addresses.rb +0 -46
  122. data/lib/rudy/command/backups.rb +0 -175
  123. data/lib/rudy/command/base.rb +0 -841
  124. data/lib/rudy/command/deploy.rb +0 -12
  125. data/lib/rudy/command/disks.rb +0 -213
  126. data/lib/rudy/command/environment.rb +0 -73
  127. data/lib/rudy/command/groups.rb +0 -61
  128. data/lib/rudy/command/images.rb +0 -91
  129. data/lib/rudy/command/instances.rb +0 -85
  130. data/lib/rudy/command/machines.rb +0 -161
  131. data/lib/rudy/command/metadata.rb +0 -41
  132. data/lib/rudy/command/release.rb +0 -174
  133. data/lib/rudy/command/volumes.rb +0 -66
  134. data/lib/rudy/metadata/disk.rb +0 -138
  135. data/tryouts/console_tryout.rb +0 -91
@@ -0,0 +1,98 @@
1
+
2
+
3
+ module Rudy::AWS
4
+ module EC2
5
+ class Snapshot < Storable
6
+ @@sformat = "%s <- %10s; %s"
7
+
8
+ field :awsid
9
+ field :progress
10
+ field :created
11
+ field :volid
12
+ field :status
13
+
14
+ def liner_note
15
+ t = Time.parse(@created)
16
+ info = t.strftime("%Y-%m-%d %H:%M:%S")
17
+ "%s (%s)" % [(self.awsid || '').bright, info]
18
+ end
19
+
20
+ def to_s(with_title=false)
21
+ @@sformat % [liner_note, @volid, @status]
22
+ end
23
+
24
+ def completed?
25
+ self.status && self.status == 'completed'
26
+ end
27
+
28
+ end
29
+
30
+ class Snapshots
31
+ include Rudy::AWS::ObjectBase
32
+ include Rudy::AWS::EC2::Base
33
+
34
+ def list(snap_id=[])
35
+ snapshots = list_as_hash(snap_id)
36
+ if snapshots
37
+ snapshots = snapshots.values.sort { |a,b| a.created <=> b.created }
38
+ end
39
+ snapshots
40
+ end
41
+ def list_as_hash(snap_id=[])
42
+ snap_id = [snap_id].flatten.compact
43
+ slist = @ec2.describe_snapshots(:snapshot_id => snap_id)
44
+ return unless slist['snapshotSet'].is_a?(Hash)
45
+ snapshots = {}
46
+ slist['snapshotSet']['item'].each do |snap|
47
+ kp = self.class.from_hash(snap)
48
+ snapshots[kp.awsid] = kp
49
+ end
50
+ snapshots
51
+ end
52
+
53
+ def create(vol_id)
54
+ vol_id = (vol_id.is_a?(Rudy::AWS::EC2::Volume)) ? vol_id.awsid : vol_id
55
+ shash = @ec2.create_snapshot(:volume_id => vol_id)
56
+ snap = Snapshots.from_hash(shash)
57
+ snap
58
+ end
59
+
60
+ def destroy(snap_id)
61
+ ret = @ec2.delete_snapshot(:snapshot_id => snap_id)
62
+ (ret && ret['return'] == 'true')
63
+ end
64
+
65
+
66
+ def Snapshots.from_hash(h)
67
+ #snapshotSet:
68
+ # item:
69
+ # - snapshotId: snap-5493653d
70
+ # volumeId: vol-0836d761
71
+ # status: completed
72
+ # startTime: "2009-03-29T20:15:10.000Z"
73
+ # progress: 100%
74
+ vol = Rudy::AWS::EC2::Snapshot.new
75
+ vol.awsid = h['snapshotId']
76
+ vol.volid = h['volumeId']
77
+ vol.created = h['startTime']
78
+ vol.progress = h['progress']
79
+ vol.status = h['status']
80
+ vol
81
+ end
82
+
83
+ def any?
84
+ !(list_as_hash || {}).empty?
85
+ end
86
+
87
+ def get(snap_id)
88
+ list(snap_id).first || nil
89
+ end
90
+
91
+ def exists?(id)
92
+ !get(snap_id).nil?
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,230 @@
1
+
2
+
3
+ module Rudy::AWS
4
+ module EC2
5
+ class Volume < Storable
6
+ @@sformat = "%s %10s;%4sGB; %s " # cram the terabyte
7
+
8
+ field :awsid
9
+ field :status
10
+ field :size
11
+ field :snapid
12
+ field :zone
13
+ field :create_time
14
+ field :attach_time
15
+ field :instid
16
+ field :device
17
+
18
+ def liner_note
19
+ info = attached? ? "attached to #{@instid}" : @status
20
+ "%s (%s)" % [(self.awsid || '').bright, info]
21
+ end
22
+
23
+ def to_s(with_title=false)
24
+ line = @@sformat % [liner_note, @zone, @size, @device]
25
+ line << " <- %s" % [@snapid] if @snapid
26
+ line
27
+ end
28
+
29
+ def inspect
30
+ lines = [liner_note]
31
+ field_names.each do |n|
32
+ lines << sprintf(" %12s: %s", n, self.send(n)) if self.send(n)
33
+ end
34
+ lines.join($/)
35
+ end
36
+
37
+ # Alias for status
38
+ def state
39
+ status
40
+ end
41
+
42
+ def available?; (status && status == "available"); end
43
+ def creating?; (status && status == "creating"); end
44
+ def deleting?; (status && status == "deleting"); end
45
+ def attached?; (status && status == "attached"); end
46
+ def in_use?; (status && status == "in-use"); end
47
+
48
+ end
49
+
50
+
51
+ class Volumes
52
+ include Rudy::AWS::ObjectBase
53
+ include Rudy::AWS::EC2::Base
54
+
55
+ unless defined?(KNOWN_STATES)
56
+ KNOWN_STATES = [:available, :creating, :deleting, :attached, :detaching].freeze
57
+ end
58
+
59
+ # * +size+ the number of GB
60
+ def create(size, zone, snapid=nil)
61
+ opts = {
62
+ :availability_zone => zone.to_s,
63
+ :size => (size || 1).to_s
64
+ }
65
+
66
+ opts[:snapshot_id] = snapid if snapid
67
+
68
+ # "status"=>"creating",
69
+ # "size"=>"1",
70
+ # "snapshotId"=>nil,
71
+ # "requestId"=>"d42ff744-48b5-4f47-a3f0-7aba57a13eb9",
72
+ # "availabilityZone"=>"us-east-1b",
73
+ # "createTime"=>"2009-03-17T20:10:48.000Z",
74
+ # "volumeId"=>"vol-48826421"
75
+ vol = execute_request({}) { @ec2.create_volume(opts) }
76
+
77
+ # TODO: use a waiter?
78
+ #Rudy.waiter(1, 30) do
79
+ # ret = @@ec2.volumes.available?(volume.awsid)
80
+ #end
81
+
82
+ reqid = vol['requestId']
83
+ Volumes.from_hash(vol) || nil
84
+ end
85
+
86
+ def destroy(vol_id)
87
+ vol_id = Volumes.get_vol_id(vol_id)
88
+ raise VolumeNotAvailable, vol_id unless available?(vol_id)
89
+ ret = execute_request({}) { @ec2.delete_volume(:volume_id => vol_id) }
90
+ (ret['return'] == 'true')
91
+ end
92
+
93
+ def attach(vol_id, inst_id, device)
94
+ vol_id = Volumes.get_vol_id(vol_id)
95
+ inst_id = inst_id.is_a?(Rudy::AWS::EC2::Instance) ? inst_id.awsid : inst_id
96
+ raise NoVolumeID unless vol_id
97
+ raise VolumeAlreadyAttached, vol_id if attached?(vol_id)
98
+ raise NoInstanceID unless inst_id
99
+ raise NoDevice unless device
100
+
101
+ opts = {
102
+ :volume_id => vol_id,
103
+ :instance_id => inst_id,
104
+ :device => device
105
+ }
106
+ ret = execute_request(false) { @ec2.attach_volume(opts) }
107
+ (ret['status'] == 'attaching')
108
+ end
109
+
110
+ def detach(vol_id)
111
+ vol_id = Volumes.get_vol_id(vol_id)
112
+ raise NoVolumeID unless vol_id
113
+ raise VolumeNotAttached, vol_id unless attached?(vol_id)
114
+ ret = execute_request({}) { @ec2.detach_volume(:volume_id => vol_id) }
115
+ (ret['status'] == 'detaching')
116
+ end
117
+
118
+
119
+ def list(state=nil, vol_id=[])
120
+ list_as_hash(state, vol_id).values
121
+ end
122
+
123
+ def list_as_hash(state=nil, vol_id=[])
124
+ state &&= state.to_sym
125
+ state = nil if state == :any
126
+ # A nil state is fine, but we don't want an unknown one!
127
+ raise UnknownState, state if state && !Volumes.known_state?(state)
128
+
129
+ opts = {
130
+ :volume_id => vol_id ? [vol_id].flatten : []
131
+ }
132
+
133
+ vlist = execute_request({}) { @ec2.describe_volumes(opts) }
134
+
135
+ volumes = {}
136
+ return volumes unless vlist['volumeSet'].is_a?(Hash)
137
+ vlist['volumeSet']['item'].each do |vol|
138
+ v = Volumes.from_hash(vol)
139
+ next if state && v.state != state.to_s
140
+ volumes[v.awsid] = v
141
+ end
142
+ volumes
143
+ end
144
+
145
+ def any?(state=nil,vol_id=[])
146
+ !list(state, vol_id).nil?
147
+ end
148
+
149
+ def exists?(vol_id)
150
+ vol_id = Volumes.get_vol_id(vol_id)
151
+ vol = get(vol_id)
152
+ !vol.nil?
153
+ end
154
+
155
+ def get(vol_id)
156
+ vol_id = Volumes.get_vol_id(vol_id)
157
+ list(:any, vol_id).first || nil
158
+ end
159
+
160
+ # deleting?, available?, etc...
161
+ %w[deleting available attached in-use].each do |state|
162
+ define_method("#{state.tr('-', '_')}?") do |vol_id|
163
+ vol_id = Volumes.get_vol_id(vol_id)
164
+ return false unless vol_id
165
+ vol = get(vol_id)
166
+ (vol && vol.status == state)
167
+ end
168
+ end
169
+
170
+ # Creates a Rudy::AWS::EC2::Volume object from:
171
+ #
172
+ # volumeSet:
173
+ # item:
174
+ # - status: available
175
+ # size: "1"
176
+ # snapshotId:
177
+ # availabilityZone: us-east-1b
178
+ # attachmentSet:
179
+ # createTime: "2009-03-17T20:10:48.000Z"
180
+ # volumeId: vol-48826421
181
+ # attachmentSet:
182
+ # item:
183
+ # - attachTime: "2009-03-17T21:49:54.000Z"
184
+ # status: attached
185
+ # device: /dev/sdh
186
+ # instanceId: i-956af3fc
187
+ # volumeId: vol-48826421
188
+ #
189
+ # requestId: 8fc30e5b-a9c3-4fe0-a979-0f71e639a7c7
190
+ #
191
+ def self.from_hash(h)
192
+ vol = Rudy::AWS::EC2::Volume.new
193
+ vol.status = h['status']
194
+ vol.size = h['size']
195
+ vol.snapid = h['snapshotId']
196
+ vol.zone = h['availabilityZone']
197
+ vol.awsid = h['volumeId']
198
+ vol.create_time = h['createTime']
199
+ if h['attachmentSet'].is_a?(Hash)
200
+ item = h['attachmentSet']['item'].first
201
+ vol.status = item['status'] # Overwrite "available status". Possibly a bad idea.
202
+ vol.device = item['device']
203
+ vol.attach_time = item['attachTime']
204
+ vol.instid = item['instanceId']
205
+ end
206
+ vol
207
+ end
208
+
209
+ # * +vol_id+ is a String or Rudy::AWS::EC2::Volume
210
+ # Returns the volume ID
211
+ def self.get_vol_id(vol_id)
212
+ (vol_id.is_a?(Rudy::AWS::EC2::Volume)) ? vol_id.awsid : vol_id
213
+ end
214
+
215
+ # Is +state+ a known EC2 volume state? See: KNOWN_STATES
216
+ def self.known_state?(state)
217
+ return false unless state
218
+ state &&= state.to_sym
219
+ KNOWN_STATES.member?(state)
220
+ end
221
+
222
+ end
223
+ end
224
+ end
225
+
226
+
227
+ class Rudy::AWS::EC2::Volumes
228
+
229
+
230
+ end
@@ -0,0 +1,77 @@
1
+
2
+ module Rudy::AWS
3
+ module EC2
4
+
5
+ class Zone < Storable
6
+
7
+ field :name
8
+ field :region
9
+ field :state
10
+
11
+ def liner_note
12
+ "%-10s %9s %s" % [self.name, self.region, self.state]
13
+ end
14
+
15
+ def to_s(titles=false)
16
+ str = titles ? "%-20s %s#{$/}" % ['name', 'region', 'state'] : ""
17
+ str << liner_note
18
+ end
19
+
20
+ end
21
+
22
+ class Zones
23
+ include Rudy::AWS::ObjectBase
24
+ include Rudy::AWS::EC2::Base
25
+
26
+ def list(*names)
27
+ zones = list_as_hash(names)
28
+ zones &&= zones.values
29
+ zones
30
+ end
31
+
32
+ def list_as_hash(*names)
33
+ names = names.flatten
34
+ zlist = @ec2.describe_availability_zones(:zone_name => names)
35
+ return unless zlist['availabilityZoneInfo'].is_a?(Hash)
36
+ zones = {}
37
+ zlist['availabilityZoneInfo']['item'].each do |zhash|
38
+ zon = Zones.from_hash(zhash)
39
+ zones[zon.name] = zon
40
+ end
41
+ zones
42
+ end
43
+
44
+ def self.from_hash(h)
45
+ zone = Rudy::AWS::EC2::Zone.new
46
+ zone.name = h['zoneName']
47
+ zone.region = h['regionName']
48
+ zone.state = h['zoneState']
49
+ zone
50
+ end
51
+
52
+ def any?
53
+ zones = list || []
54
+ !zones.empty?
55
+ end
56
+
57
+ def get(name)
58
+ zones = list(name) || []
59
+ return if zones.empty?
60
+ zones.first
61
+ end
62
+
63
+ def zone?(name)
64
+ begin
65
+ kp = get(name)
66
+ kp.is_a?(Rudy::AWS::EC2::Zone)
67
+ rescue => ex
68
+ false
69
+ end
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
76
+
77
+
@@ -1,3 +1,57 @@
1
1
 
2
2
  module Rudy::AWS
3
+ class S3
4
+
5
+ def initialize(access_key=nil, secret_key=nil, region=nil, debug=nil)
6
+
7
+ url ||= 'http://sdb.amazonaws.com'
8
+ # There is a bug with passing :server to EC2::Base.new so
9
+ # we'll use the environment variable for now.
10
+ #if region && Rudy::AWS.valid_region?(region)
11
+ # "#{region}.sdb.amazonaws.com"
12
+ #end
13
+
14
+ @access_key_id = access_key || ENV['AWS_ACCESS_KEY'] || ENV['AMAZON_ACCESS_KEY_ID']
15
+ @secret_access_key = secret_key || ENV['AWS_SECRET_KEY'] || ENV['AMAZON_SECRET_ACCESS_KEY']
16
+ @base_url = url
17
+ @debug = debug || StringIO.new
18
+
19
+
20
+ AWS::S3::Base.establish_connection!(
21
+ :access_key_id => @access_key_id,
22
+ :secret_access_key => @secret_access_key
23
+ )
24
+
25
+ end
26
+
27
+ def list_buckets
28
+ ::AWS::S3::Service.buckets
29
+ end
30
+
31
+ def create_bucket(name)
32
+ ::AWS::S3::Bucket.create(name)
33
+ end
34
+
35
+ def destroy_bucket(name)
36
+ ::AWS::S3::Bucket.delete(name)
37
+ end
38
+
39
+ def find_bucket(name)
40
+ ::AWS::S3::Bucket.delete(name)
41
+ end
42
+
43
+ def list_bucket_objects(name)
44
+ ::AWS::S3::Bucket.objects(name)
45
+ end
46
+
47
+ #def store(path, bucket)
48
+ # fname = File.basename(path)
49
+ # S3Object.store(fname, open(path), bucket)
50
+ #end
51
+
52
+ def bucket_exists?(name)
53
+ b = find_bucket(name)
54
+ !b.nil?
55
+ end
56
+ end
3
57
  end