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