cyclid-lxd-plugin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []