cyclid-digitalocean-plugin 0.1.0

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.
@@ -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: []