cyclid-lxd-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.
- checksums.yaml +7 -0
- data/lib/cyclid/plugins/builder/lxd.rb +172 -0
- data/lib/cyclid/plugins/transport/lxdapi.rb +163 -0
- metadata +87 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 28191bf50cd5de65833791d6de820ca8e16b5e9e
|
4
|
+
data.tar.gz: fee02e95b7031694e032d00b13aa58136f338951
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 792ea700154e73b4ded74ec0e31fe9d4270044fcc8f0c987dfba61694d4647866b7b700b1f20a827a871fa547a10858b44588bb850bdf5b9bc71774c43431467
|
7
|
+
data.tar.gz: 10995f857e821416149dfe18907e46539f5d98f0d13a8426b7673a7371711085c940af54177dc467a2a61baa291d4af1ff25d5c5edb14be8f0f8733e13bbf377
|
@@ -0,0 +1,172 @@
|
|
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 'hyperkit'
|
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
|
+
# LXD build host
|
25
|
+
class LxdHost < BuildHost
|
26
|
+
# LXD is the only acceptable Transport
|
27
|
+
def transports
|
28
|
+
['lxdapi']
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# LXD builder. Uses the LXD REST API to create a build host container.
|
33
|
+
class Lxd < Builder
|
34
|
+
def initialize
|
35
|
+
@config = load_lxd_config(Cyclid.config.plugins)
|
36
|
+
@client = Hyperkit::Client.new(api_endpoint: @config[:api],
|
37
|
+
verify_ssl: @config[:verify_ssl],
|
38
|
+
client_cert: @config[:client_cert],
|
39
|
+
client_key: @config[:client_key])
|
40
|
+
end
|
41
|
+
|
42
|
+
# Create & return a build host
|
43
|
+
def get(args = {})
|
44
|
+
args.symbolize_keys!
|
45
|
+
|
46
|
+
Cyclid.logger.debug "lxd: args=#{args}"
|
47
|
+
|
48
|
+
# If there is one, split the 'os' into a 'distro' and 'release'
|
49
|
+
if args.key? :os
|
50
|
+
match = args[:os].match(/\A(\w*)_(.*)\Z/)
|
51
|
+
distro = match[1] if match
|
52
|
+
release = match[2] if match
|
53
|
+
else
|
54
|
+
# No OS was specified; use the default
|
55
|
+
# XXX Defaults should be configurable
|
56
|
+
distro = 'ubuntu'
|
57
|
+
release = 'trusty'
|
58
|
+
end
|
59
|
+
|
60
|
+
# Find the template fingerprint for the given distribution & release
|
61
|
+
image_alias = "#{distro}/#{release}"
|
62
|
+
fingerprint = find_or_create_image(image_alias)
|
63
|
+
Cyclid.logger.debug "fingerprint=#{fingerprint}"
|
64
|
+
|
65
|
+
# Create a new instance
|
66
|
+
name = create_name
|
67
|
+
create_container(name, fingerprint)
|
68
|
+
|
69
|
+
# Wait for the instance to settle
|
70
|
+
sleep 5
|
71
|
+
|
72
|
+
# Create a buildhost from the container details
|
73
|
+
buildhost = LxdHost.new(host: name,
|
74
|
+
name: name,
|
75
|
+
username: 'root',
|
76
|
+
workspace: '/root',
|
77
|
+
distro: distro,
|
78
|
+
release: release)
|
79
|
+
|
80
|
+
buildhost
|
81
|
+
end
|
82
|
+
|
83
|
+
# Destroy the build host
|
84
|
+
def release(_transport, buildhost)
|
85
|
+
name = buildhost[:host]
|
86
|
+
|
87
|
+
@client.stop_container(name)
|
88
|
+
wait_for_container(name, 'Stopped')
|
89
|
+
|
90
|
+
@client.delete_container(name)
|
91
|
+
rescue StandardError => ex
|
92
|
+
Cyclid.logger.error "LXD destroy timed out: #{ex}"
|
93
|
+
end
|
94
|
+
|
95
|
+
# Register this plugin
|
96
|
+
register_plugin 'lxd'
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# Load the config for the LXD Builder plugin and set defaults if they're not
|
101
|
+
# in the config
|
102
|
+
def load_lxd_config(config)
|
103
|
+
config.symbolize_keys!
|
104
|
+
|
105
|
+
lxd_config = config[:lxd] || {}
|
106
|
+
lxd_config.symbolize_keys!
|
107
|
+
Cyclid.logger.debug "config=#{lxd_config}"
|
108
|
+
|
109
|
+
raise 'the LXD API URL must be provided' \
|
110
|
+
unless lxd_config.key? :api
|
111
|
+
|
112
|
+
lxd_config[:client_cert] = File.join(%w(/ etc cyclid lxd_client.crt)) \
|
113
|
+
unless lxd_config.key? :client_cert
|
114
|
+
lxd_config[:client_key] = File.join(%w(/ etc cyclid lxd_client.key)) \
|
115
|
+
unless lxd_config.key? :client_key
|
116
|
+
|
117
|
+
lxd_config[:verify_ssl] = false \
|
118
|
+
unless lxd_config.key? :verify_ssl
|
119
|
+
lxd_config[:image_server] = 'https://images.linuxcontainers.org:8443' \
|
120
|
+
unless lxd_config.key? :image_server
|
121
|
+
lxd_config[:instance_name] = 'cyclid-build' \
|
122
|
+
unless lxd_config.key? :instance_name
|
123
|
+
|
124
|
+
lxd_config
|
125
|
+
end
|
126
|
+
|
127
|
+
def find_or_create_image(image_alias)
|
128
|
+
Cyclid.logger.debug "Create image #{image_alias}"
|
129
|
+
fingerprint = ''
|
130
|
+
begin
|
131
|
+
image = @client.image_by_alias(image_alias)
|
132
|
+
Cyclid.logger.debug "found image=#{image.inspect}"
|
133
|
+
fingerprint = image.fingerprint
|
134
|
+
rescue Hyperkit::NotFound
|
135
|
+
Cyclid.logger.debug "Downloading image for #{image_alias}"
|
136
|
+
image = @client.create_image_from_remote(@config[:image_server],
|
137
|
+
alias: image_alias,
|
138
|
+
protocol: 'simplestreams')
|
139
|
+
|
140
|
+
Cyclid.logger.debug "created image=#{image.inspect}"
|
141
|
+
fingerprint = image.metadata.fingerprint
|
142
|
+
@client.create_image_alias(fingerprint, image_alias)
|
143
|
+
end
|
144
|
+
|
145
|
+
fingerprint
|
146
|
+
end
|
147
|
+
|
148
|
+
def create_container(name, fingerprint)
|
149
|
+
Cyclid.logger.debug "Creating container #{name}"
|
150
|
+
@client.create_container(name, fingerprint: fingerprint)
|
151
|
+
@client.start_container(name)
|
152
|
+
|
153
|
+
wait_for_container(name, 'Running')
|
154
|
+
end
|
155
|
+
|
156
|
+
def wait_for_container(name, state)
|
157
|
+
29.times.each do |_t|
|
158
|
+
status = @client.container(name).status
|
159
|
+
break if status == state
|
160
|
+
Cyclid.logger.debug status
|
161
|
+
sleep 2
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def create_name
|
166
|
+
base = @config[:instance_name]
|
167
|
+
"#{base}-#{SecureRandom.hex(16)}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,163 @@
|
|
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 'hyperkit'
|
17
|
+
require 'websocket-client-simple'
|
18
|
+
|
19
|
+
# Top level module for the core Cyclid code.
|
20
|
+
module Cyclid
|
21
|
+
# Module for the Cyclid API
|
22
|
+
module API
|
23
|
+
# Module for Cyclid Plugins
|
24
|
+
module Plugins
|
25
|
+
# LXD based transport
|
26
|
+
class LxdApi < Transport
|
27
|
+
attr_reader :exit_code, :exit_signal
|
28
|
+
|
29
|
+
def initialize(args = {})
|
30
|
+
args.symbolize_keys!
|
31
|
+
|
32
|
+
Cyclid.logger.debug "args=#{args}"
|
33
|
+
|
34
|
+
# Container name & a log target are required
|
35
|
+
return false unless args.include?(:host) && \
|
36
|
+
args.include?(:log)
|
37
|
+
|
38
|
+
@name = args[:host]
|
39
|
+
@log = args[:log]
|
40
|
+
Cyclid.logger.debug "log=#{@log}"
|
41
|
+
|
42
|
+
@config = load_lxd_config(Cyclid.config.plugins)
|
43
|
+
@client = Hyperkit::Client.new(api_endpoint: @config[:api],
|
44
|
+
verify_ssl: @config[:verify_ssl],
|
45
|
+
client_cert: @config[:client_cert],
|
46
|
+
client_key: @config[:client_key])
|
47
|
+
|
48
|
+
# Grab some data from the context
|
49
|
+
ctx = args[:ctx]
|
50
|
+
@base_env = { 'HOME' => ctx[:workspace],
|
51
|
+
'TERM' => 'xterm-mono' }
|
52
|
+
end
|
53
|
+
|
54
|
+
# Execute a command via. the LXD API
|
55
|
+
def exec(cmd, path = nil)
|
56
|
+
command = build_command(cmd, path)
|
57
|
+
Cyclid.logger.debug "command=#{command}"
|
58
|
+
|
59
|
+
# Ensure some important variables are set, like HOME & TERM
|
60
|
+
env = @env || {}
|
61
|
+
env = env.merge @base_env
|
62
|
+
|
63
|
+
# Run the command...
|
64
|
+
rc = @client.execute_command(@name,
|
65
|
+
command,
|
66
|
+
environment: env,
|
67
|
+
wait_for_websocket: true,
|
68
|
+
interactive: true,
|
69
|
+
sync: false)
|
70
|
+
|
71
|
+
# ... and then connect a Websocket and read the output
|
72
|
+
operation = rc[:id]
|
73
|
+
ws_secret = rc[:metadata][:fds][:'0']
|
74
|
+
ws_url = "#{@config[:api]}/1.0/operations/#{operation}/websocket?secret=#{ws_secret}"
|
75
|
+
|
76
|
+
closed = false
|
77
|
+
log = @log
|
78
|
+
WebSocket::Client::Simple.connect ws_url do |ws|
|
79
|
+
ws.on :message do |msg|
|
80
|
+
close if msg.data.empty?
|
81
|
+
# Strip out any XTerm control characters and convert lint endings
|
82
|
+
data = msg.data.force_encoding('UTF-8')
|
83
|
+
.gsub(/\r\n/, "\n")
|
84
|
+
.gsub(/\r+/, "\n")
|
85
|
+
.gsub(/\033\[(.?\d+\D?|\w+)/, '')
|
86
|
+
|
87
|
+
log.write data
|
88
|
+
end
|
89
|
+
ws.on :open do
|
90
|
+
Cyclid.logger.debug 'websocket opened'
|
91
|
+
closed = false
|
92
|
+
end
|
93
|
+
ws.on :close do |e|
|
94
|
+
Cyclid.logger.debug "websocket closed: #{e}"
|
95
|
+
closed = true
|
96
|
+
end
|
97
|
+
ws.on :error do |e|
|
98
|
+
Cyclid.logger.debug "websocket error: #{e}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Wait until the Websocket thread has finished.
|
103
|
+
loop do
|
104
|
+
break if closed
|
105
|
+
sleep 1
|
106
|
+
end
|
107
|
+
|
108
|
+
# Get exit status
|
109
|
+
status = @client.operation(operation)
|
110
|
+
Cyclid.logger.debug "status=#{status.inspect}"
|
111
|
+
|
112
|
+
@exit_code = status[:metadata][:return]
|
113
|
+
@exit_code.zero? ? true : false
|
114
|
+
end
|
115
|
+
|
116
|
+
# Copy data from a local IO object to a remote file via. the API
|
117
|
+
def upload(io, path)
|
118
|
+
@client.push_file(io, @name, path)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Copy a data from remote file to a local IO object
|
122
|
+
def download(io, path)
|
123
|
+
@client.pull_file(@name, path, io)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Register this plugin
|
127
|
+
register_plugin 'lxdapi'
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
# Load the config for the LXD Builder plugin and set defaults if they're not
|
132
|
+
# in the config
|
133
|
+
def load_lxd_config(config)
|
134
|
+
config.symbolize_keys!
|
135
|
+
|
136
|
+
lxd_config = config[:lxd] || {}
|
137
|
+
lxd_config.symbolize_keys!
|
138
|
+
Cyclid.logger.debug "config=#{lxd_config}"
|
139
|
+
|
140
|
+
raise 'the LXD API URL must be provided' \
|
141
|
+
unless lxd_config.key? :api
|
142
|
+
|
143
|
+
lxd_config[:client_cert] = File.join(%w(/ etc cyclid lxd_client.crt)) \
|
144
|
+
unless lxd_config.key? :client_cert
|
145
|
+
lxd_config[:client_key] = File.join(%w(/ etc cyclid lxd_client.key)) \
|
146
|
+
unless lxd_config.key? :client_key
|
147
|
+
|
148
|
+
lxd_config[:verify_ssl] = false \
|
149
|
+
unless lxd_config.key? :verify_ssl
|
150
|
+
|
151
|
+
lxd_config
|
152
|
+
end
|
153
|
+
|
154
|
+
def build_command(cmd, path = nil)
|
155
|
+
command = []
|
156
|
+
command << "cd #{path}" if path
|
157
|
+
command << cmd
|
158
|
+
"sh -l -c '#{command.join(';')}'"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cyclid-lxd-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-12-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hyperkit
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: websocket-client-simple
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.3.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.3.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: cyclid
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.2'
|
55
|
+
description: Creates LXD container basedt build hosts
|
56
|
+
email: contact@cyclid.io
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- lib/cyclid/plugins/builder/lxd.rb
|
62
|
+
- lib/cyclid/plugins/transport/lxdapi.rb
|
63
|
+
homepage: https://cyclid.io
|
64
|
+
licenses:
|
65
|
+
- Apache-2.0
|
66
|
+
metadata: {}
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 2.5.1
|
84
|
+
signing_key:
|
85
|
+
specification_version: 4
|
86
|
+
summary: Cyclid LXD Builder & Transport plugin
|
87
|
+
test_files: []
|