cyclid-digitalocean-plugin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0eb1eb8572ab14e2bc8d035c4cae3c277ce812e0
4
+ data.tar.gz: c08f1446391ee32afdc64804e4dbf781da35bf26
5
+ SHA512:
6
+ metadata.gz: fb1d59fe2f3b574721f95945d15517ad2cabf8e50977ed5b07901d2e4fe96016ac2b4e24cd49e65f398a87c992aec1c9c1fdbe5bbf32a3a91b880d8510a87279
7
+ data.tar.gz: b7a079b37b81daa21147860e2fc1cb5f7758494a6f3eff6613edc067bfd5a9111c810cda2134471edf669e3785b1c8d873d975b7f842894cc2f560f7972e6198
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+ # Copyright 2016 Liqwyd Ltd.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'droplet_kit'
17
+
18
+ # Top level module for the core Cyclid code.
19
+ module Cyclid
20
+ # Module for the Cyclid API
21
+ module API
22
+ # Module for Cyclid Plugins
23
+ module Plugins
24
+ # Digitalocean build host
25
+ class DigitaloceanHost < BuildHost
26
+ # SSH is the only acceptable Transport
27
+ def transports
28
+ ['ssh']
29
+ end
30
+ end
31
+
32
+ # Digitalocean builder. Uses the Digitalocean API to obtain a build host instance.
33
+ class Digitalocean < Builder
34
+ def initialize
35
+ @config = load_digitalocean_config(Cyclid.config.plugins)
36
+ @client = DropletKit::Client.new(access_token: @config[:access_token])
37
+ end
38
+
39
+ # Create & return a build host
40
+ def get(args = {})
41
+ args.symbolize_keys!
42
+
43
+ Cyclid.logger.debug "digitalocean: args=#{args}"
44
+
45
+ # If there is one, split the 'os' into a 'distro' and 'release'
46
+ if args.key? :os
47
+ match = args[:os].match(/\A(\w*)_(.*)\Z/)
48
+ distro = match[1] if match
49
+ release = match[2] if match
50
+ else
51
+ # No OS was specified; use the default
52
+ # XXX Defaults should be configurable
53
+ distro = 'ubuntu'
54
+ release = 'trusty'
55
+ end
56
+
57
+ # Convert Debian & Ubuntu release codenames
58
+ release_version = if distro == 'ubuntu' or distro == 'debian'
59
+ codename_to_version(release)
60
+ else
61
+ release
62
+ end
63
+
64
+ begin
65
+ # Find, or create, the SSH key Cyclid can use
66
+ build_key = build_ssh_key
67
+
68
+ # Create the Droplet
69
+ droplet = DropletKit::Droplet.new(name: create_name,
70
+ region: @config[:region],
71
+ image: "#{distro}-#{release_version}-x64",
72
+ size: @config[:size],
73
+ ssh_keys: [build_key.id])
74
+ created = @client.droplets.create droplet
75
+
76
+ # Wait for the droplet to become 'active'
77
+ created = wait_for_droplet(created)
78
+
79
+ # Create a buildhost from the active Droplet details
80
+ buildhost = DigitaloceanHost.new(name: created.name,
81
+ host: created.networks.v4.first.ip_address,
82
+ id: created.id,
83
+ username: 'root',
84
+ workspace: '/root',
85
+ key: @config[:ssh_private_key],
86
+ distro: distro,
87
+ release: release)
88
+ rescue StandardError => ex
89
+ Cyclid.logger.error "couldn't get a build host from Digitalocean: #{ex}"
90
+ raise "digitalocean failed: #{ex}"
91
+ end
92
+
93
+ Cyclid.logger.debug "digitalocean buildhost=#{buildhost.inspect}"
94
+ return buildhost
95
+ end
96
+
97
+ # Destroy the build host
98
+ def release(_transport, buildhost)
99
+ @client.droplets.delete(id: buildhost[:id])
100
+ rescue StandardError => ex
101
+ Cyclid.logger.error "Digitalcoean destroy timed out: #{ex}"
102
+ end
103
+
104
+ # Register this plugin
105
+ register_plugin 'digitalocean'
106
+
107
+ private
108
+
109
+ # Load the config for the Digitalocean plugin and set defaults if they're not
110
+ # in the config
111
+ def load_digitalocean_config(config)
112
+ config.symbolize_keys!
113
+
114
+ do_config = config[:digitalocean] || {}
115
+ do_config.symbolize_keys!
116
+ Cyclid.logger.debug "config=#{do_config}"
117
+
118
+ raise 'the Digitalocean API access token must be provided' \
119
+ unless do_config.key? :access_token
120
+
121
+ # Set defaults
122
+ do_config[:region] = 'nyc1' unless do_config.key? :region
123
+ do_config[:size] = '512mb' unless do_config.key? :size
124
+ do_config[:ssh_private_key] = File.join(%w(/ etc cyclid id_rsa_build)) \
125
+ unless do_config.key? :ssh_private_key
126
+ do_config[:ssh_public_key] = File.join(%w(/ etc cyclid id_rsa_build.pub)) \
127
+ unless do_config.key? :ssh_public_key
128
+ do_config[:ssh_key_name] = 'cyclid-build' \
129
+ unless do_config.key? :ssh_key_name
130
+ do_config[:instance_name] = 'cyclid-build' \
131
+ unless do_config.key? :instance_name
132
+
133
+ return do_config
134
+ end
135
+
136
+ # Convert Ubuntu & Debian release codenames to Digitialocean approved release versions
137
+ def codename_to_version(release)
138
+ versions = { 'precise' => '12-04',
139
+ 'trusty' => '14-04',
140
+ 'xenial' => '16-04',
141
+ 'yakkety' => '16-10',
142
+ 'wheezy' => '7',
143
+ 'jessie' => '8' }
144
+
145
+ # No need to convert if it isn't a codename
146
+ return release if release =~ /\A\d*[-\d*]/
147
+
148
+ raise "don't know what version Ubuntu/Debian #{release} is" \
149
+ unless versions.key? release
150
+
151
+ return versions[release]
152
+ end
153
+
154
+ def build_ssh_key
155
+ # Find the build key, if it exists
156
+ build_key = nil
157
+ all_keys = @client.ssh_keys.all
158
+ all_keys.each do |key|
159
+ build_key = key if key.name == @config[:ssh_key_name]
160
+ end
161
+ Cyclid.logger.debug "build_key=#{build_key.inspect}"
162
+
163
+ # If the key doesn't exist, create it
164
+ if build_key.nil?
165
+ pubkey = File.read(@config[:ssh_public_key])
166
+ key = DropletKit::SSHKey.new(name: @config[:ssh_key_name],
167
+ public_key: pubkey)
168
+ build_key = @client.ssh_keys.create(key)
169
+ Cyclid.logger.debug "build_key=#{build_key.inspect}"
170
+ end
171
+
172
+ return build_key
173
+ end
174
+
175
+ def wait_for_droplet(created)
176
+ # Wait for the droplet to become active; wait a maximum of 1 minute,
177
+ # polling every 2 seconds.
178
+ Cyclid.logger.debug "Waiting for instance #{created.id} to become 'active'..."
179
+ 29.times do
180
+ created = @client.droplets.find(id: created.id.to_s)
181
+ break if created.status == 'active'
182
+
183
+ sleep 2
184
+ end
185
+ Cyclid.logger.debug "created=#{created.inspect}"
186
+
187
+ unless created.status == 'active'
188
+ @client.droplets.delete(id: created.id)
189
+
190
+ raise 'failed to create build host: did not become active within 1 minute. ' \
191
+ "Status is #{created.status}" \
192
+ end
193
+
194
+ return created
195
+ end
196
+
197
+ def create_name
198
+ base = @config[:instance_name]
199
+ "#{base}-#{SecureRandom.hex(16)}"
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cyclid-digitalocean-plugin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kristian Van Der Vliet
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: droplet_kit
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ description: Creates Digitalocean Droplet build hosts
28
+ email: contact@cyclid.io
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/cyclid/plugins/builder/digitalocean.rb
34
+ homepage: https://cyclid.io
35
+ licenses:
36
+ - Apache-2.0
37
+ metadata: {}
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 2.5.1
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: Cyclid Digitalocean plugin
58
+ test_files: []