kitchen-lxd_cli 0.1.0.beta → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 90f808190b07d50dd9404d55a80c7e5bdf23fcb6
4
- data.tar.gz: 6697d367bfd6ebcd472429ac3a63c156a65fc1bc
3
+ metadata.gz: 69c637b90cb10b7f5e7fbff0b2efd389f1d09c82
4
+ data.tar.gz: 2baf35c80ecd9804cb328b7ff2e0222a2a408a56
5
5
  SHA512:
6
- metadata.gz: 760fcae5bb6f14e0d6df9067160f7e0676dc826b6c43dcefb46773fc4902df2a37dbccf8fd8afef5679820f3f662c216977af6d37869cb2ee1a382f645373348
7
- data.tar.gz: 8217561471842586e3e39492cd6e348dd1c72913904653a30d5a8d620f4a19f0c444eb248b106f089a573a10c956ce67ce0800ca1ff600efb0ed2da86ef109d1
6
+ metadata.gz: c1a64dedb9835ac100b511e177f365d2d6276d657d3688bb50a551a3b138acce19ce1e8cbff63efcbcda12dcad299aa756d8012708bd1d9224007d881a01b5b1
7
+ data.tar.gz: 0f8665e9c1bfe62bf50021fa2520097618393764197619b0ffe8af0a94c44e25fb194ac434dfd716720587dca927072fc590442a21860d74af3553bbd6c3f833
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ *.swp
@@ -0,0 +1,29 @@
1
+ I, [2015-11-01T00:01:20.462600 #7769] INFO -- Kitchen: -----> Starting Kitchen (v1.4.2)
2
+ E, [2015-11-01T00:01:20.462938 #7769] ERROR -- Kitchen: ------Exception-------
3
+ E, [2015-11-01T00:01:20.462972 #7769] ERROR -- Kitchen: Class: Kitchen::UserError
4
+ E, [2015-11-01T00:01:20.462994 #7769] ERROR -- Kitchen: Message: Kitchen YAML file /home/braden/kitchen-lxd_cli/.kitchen.yml does not exist.
5
+ E, [2015-11-01T00:01:20.463013 #7769] ERROR -- Kitchen: ------Backtrace-------
6
+ E, [2015-11-01T00:01:20.463031 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/lib/kitchen/loader/yaml.rb:74:in `read'
7
+ E, [2015-11-01T00:01:20.463049 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/lib/kitchen/config.rb:145:in `data'
8
+ E, [2015-11-01T00:01:20.463068 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/lib/kitchen/config.rb:124:in `suites'
9
+ E, [2015-11-01T00:01:20.463086 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/lib/kitchen/config.rb:175:in `filter_instances'
10
+ E, [2015-11-01T00:01:20.463103 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/lib/kitchen/config.rb:134:in `build_instances'
11
+ E, [2015-11-01T00:01:20.463124 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/lib/kitchen/config.rb:110:in `instances'
12
+ E, [2015-11-01T00:01:20.463142 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/lib/kitchen/command.rb:115:in `filtered_instances'
13
+ E, [2015-11-01T00:01:20.463160 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/lib/kitchen/command.rb:145:in `parse_subcommand'
14
+ E, [2015-11-01T00:01:20.463202 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/lib/kitchen/command/action.rb:38:in `block in call'
15
+ E, [2015-11-01T00:01:20.463232 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/2.1.0/benchmark.rb:279:in `measure'
16
+ E, [2015-11-01T00:01:20.463250 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/lib/kitchen/command/action.rb:37:in `call'
17
+ E, [2015-11-01T00:01:20.463279 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/lib/kitchen/cli.rb:56:in `perform'
18
+ E, [2015-11-01T00:01:20.463298 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/lib/kitchen/cli.rb:171:in `block (2 levels) in <class:CLI>'
19
+ E, [2015-11-01T00:01:20.463317 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/thor-0.19.1/lib/thor/command.rb:27:in `run'
20
+ E, [2015-11-01T00:01:20.463336 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/thor-0.19.1/lib/thor/invocation.rb:126:in `invoke_command'
21
+ E, [2015-11-01T00:01:20.463366 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/lib/kitchen/cli.rb:308:in `invoke_task'
22
+ E, [2015-11-01T00:01:20.463387 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/thor-0.19.1/lib/thor.rb:359:in `dispatch'
23
+ E, [2015-11-01T00:01:20.463407 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/thor-0.19.1/lib/thor/base.rb:440:in `start'
24
+ E, [2015-11-01T00:01:20.463427 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/bin/kitchen:13:in `block in <top (required)>'
25
+ E, [2015-11-01T00:01:20.463447 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/lib/kitchen/errors.rb:154:in `with_friendly_errors'
26
+ E, [2015-11-01T00:01:20.463466 #7769] ERROR -- Kitchen: /opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/test-kitchen-1.4.2/bin/kitchen:13:in `<top (required)>'
27
+ E, [2015-11-01T00:01:20.463485 #7769] ERROR -- Kitchen: /opt/chefdk/bin/kitchen:14:in `load'
28
+ E, [2015-11-01T00:01:20.463523 #7769] ERROR -- Kitchen: /opt/chefdk/bin/kitchen:14:in `<main>'
29
+ E, [2015-11-01T00:01:20.463543 #7769] ERROR -- Kitchen: ----------------------
data/:w ADDED
@@ -0,0 +1,176 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Braden Wright (<braden.m.wright@gmail.com>)
4
+ #
5
+ # Copyright (C) 2015, Braden Wright
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'kitchen'
20
+
21
+ module Kitchen
22
+
23
+ module Driver
24
+
25
+ # LxdCli driver for Kitchen.
26
+ #
27
+ # @author Braden Wright <braden.m.wright@gmail.com>
28
+ # class LxdCli < Kitchen::Driver::SSHBase
29
+ class LxdCli < Kitchen::Driver::Base
30
+ kitchen_driver_api_version 2
31
+
32
+ default_config :public_key_path do
33
+ [
34
+ File.expand_path('~/.ssh/id_rsa.pub'),
35
+ File.expand_path('~/.ssh/id_dsa.pub'),
36
+ File.expand_path('~/.ssh/identity.pub'),
37
+ File.expand_path('~/.ssh/id_ecdsa.pub')
38
+ ].find { |path| File.exist?(path) }
39
+ end
40
+
41
+ required_config :public_key_path
42
+
43
+ def create(state)
44
+ if exists?
45
+ if running?
46
+ debug("#{instance.name} already exists, and is already running. Nothing to do")
47
+ else
48
+ debug("#{instance.name} already exists, starting instead")
49
+ run_lxc_command("start #{instance.name}")
50
+ end
51
+ else
52
+ image_name = create_image_if_missing
53
+ profile = "-p #{config[:profile]}" if config[:profile]
54
+ lxc_config = "-c #{config[:config]}" if config[:config]
55
+ run_lxc_command("launch #{image_name} #{instance.name} #{profile} #{lxc_config}")
56
+ end
57
+ ip_address(state)
58
+ setup_ssh_access
59
+
60
+ conn(state).execute("touch testing")
61
+ # instance.transport.connection(state).login_command #fails
62
+ end
63
+
64
+ def destroy(state)
65
+ if exists?
66
+ if running?
67
+ run_lxc_command("stop #{instance.name}")
68
+ else
69
+ debug("#{instance.name} isn't running, just destroying instead")
70
+ end
71
+ run_lxc_command("delete #{instance.name}")
72
+ else
73
+ debug("#{instance.name} doesn't exist. Nothing to do")
74
+ end
75
+ state.delete(:hostname)
76
+ end
77
+
78
+ private
79
+ def exists?
80
+ status = `lxc info #{instance.name} > /dev/null 2>&1 && echo $?`.chomp
81
+ if "#{status}" == "0"
82
+ debug("#{instance.name} exists")
83
+ return true
84
+ else
85
+ debug("#{instance.name} doesn't exist")
86
+ return false
87
+ end
88
+ end
89
+
90
+ def running?
91
+ status = `lxc info #{instance.name}`.match(/Status: ([a-zA-Z]+)[\n]/).captures[0].upcase
92
+ if status == "RUNNING"
93
+ debug("#{instance.name} is running")
94
+ return true
95
+ else
96
+ debug("#{instance.name} isn't running")
97
+ return false
98
+ end
99
+ end
100
+
101
+ def create_image_if_missing
102
+ image_name = config[:image_name] ||= instance.platform.name
103
+ status = `lxc image show #{image_name} > /dev/null 2>&1 && echo $?`.chomp
104
+ if "#{status}" == "0"
105
+ debug("Image #{image_name} exists")
106
+ else
107
+ debug("Image #{image_name} doesn't exist, creating now.")
108
+ image = get_ubuntu_image_info
109
+ image_os = config[:image_os] ||= image[:os]
110
+ image_release = config[:image_release] ||= image[:release]
111
+ debug("lxd-images import #{image_os} #{image_release} --alias #{image_name}")
112
+ run_local_command("lxd-images import #{image_os} #{image_release} --alias #{image_name}")
113
+ end
114
+ return image_name
115
+ end
116
+
117
+ def get_ubuntu_image_info
118
+ platform, release = instance.platform.name.split('-')
119
+ if platform.downcase == "ubuntu"
120
+ case release.downcase
121
+ when "14.04", "1404", "trusty", "", nil
122
+ image = { :os => platform, :release => "trusty" }
123
+ when "14.10", "1410", "utopic"
124
+ image = { :os => platform, :release => "utopic" }
125
+ when "15.04", "1504", "vivid"
126
+ image = { :os => platform, :release => "vivid" }
127
+ when "15.10", "1510", "wily"
128
+ image = { :os => platform, :release => "wily" }
129
+ when "16.04", "1604", "xenial"
130
+ image = { :os => platform, :release => "xenial" }
131
+ else
132
+ image = { :os => platform, :release => release }
133
+ end
134
+ else
135
+ image = { :os => platform, :release => release }
136
+ end
137
+ return image
138
+ end
139
+
140
+ def ip_address(state)
141
+ debug("start ip")
142
+ begin
143
+ lxc_info = `lxc info #{instance.name}`
144
+ end while (!lxc_info.match(/eth0:[\t]IPV[46][\t]([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*[\n]/))
145
+ conn(state).execute("-- ifconfig eth0 #{config[:ipv4]} up") if config[:ipv4]
146
+ # run_local_command("lxc exec #{instance.name} -- ifconfig eth0 #{config[:ipv4]} up") if config[:ipv4]
147
+ lxc_ip = config[:ipv4] ||= lxc_info.match(/eth0:[\t]IPV[46][\t]([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*[\n]/).captures[0].to_s
148
+ debug("found ip #{lxc_ip}")
149
+ state[:hostname] = lxc_ip
150
+ return lxc_ip
151
+ end
152
+
153
+ def setup_ssh_access
154
+ info("Copying public key from #{config[:public_key_path]} to #{instance.name}")
155
+ begin
156
+ sleep 0.3
157
+ status = `lxc file push #{config[:public_key_path]} #{instance.name}/root/.ssh/authorized_keys 2> /dev/null && echo $?`.chomp
158
+ end while ("#{status}" != "0")
159
+ end
160
+
161
+ def run_lxc_command(cmd)
162
+ # should be moved to transport and run_command used
163
+ #conn(state).run_lxc_command("stop #{instance.name}")
164
+ `lxc #{cmd}` if cmd
165
+ end
166
+
167
+ def run_local_command(cmd)
168
+ `#{cmd}` if cmd
169
+ end
170
+
171
+ def conn(state)
172
+ return instance.transport.connection(state)
173
+ end
174
+ end
175
+ end
176
+ end
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  A Test Kitchen Driver for LxdCli.
4
4
 
5
+ At this point would NOT recommend using in Production.
6
+
5
7
  ## <a name="overview"></a> Overview
6
8
 
7
9
  This is a test-kitchen driver for lxd, which controls lxc. I named it LxdCli because this is my first plugin and I wanted to leave LXD driver name in case a more extensive project is put together.
@@ -12,16 +14,43 @@ I started the project because I really like the idea of developing containers, b
12
14
 
13
15
  I AM VERY OPEN TO SUGGESTIONS/HELP. As I mentioned I haven't written a kitchen driver or published any ruby gems before so I was hesitant to even release it.
14
16
 
17
+ Minimal .kitchen.yml
15
18
  ```yaml
16
19
  ---
17
20
  driver:
18
21
  name: lxd_cli
19
- # optional: searches ~/.ssh/ by default for public key
20
- # public_key_path: "~/.ssh/id_rsa.pub"
21
22
 
22
23
  provisioner:
23
24
  name: chef_zero
24
- # name: nodes
25
+
26
+ platforms:
27
+ - name: ubuntu-14.04
28
+
29
+ suites:
30
+ - name: default
31
+ run_list:
32
+ attributes:
33
+ ```
34
+
35
+ All Options Shown .kitchen.yml
36
+ ```yaml
37
+ ---
38
+ driver:
39
+ name: lxd_cli
40
+ public_key_path: "/my/path/id_rsa.pub"
41
+ image_name: my-ubuntu-image
42
+ image_os: ubuntu
43
+ image_release: trusty
44
+ profile: my_lxc_profile
45
+ config: limits.memory=2G
46
+ domain_name: localdomain
47
+ dns_servers: ["8.8.8.8","8.8.4.4"]
48
+ ipv4: 10.0.3.99/24
49
+ ip_gateway: 10.0.3.1
50
+ stop_instead_of_destroy: true
51
+
52
+ provisioner:
53
+ name: chef_zero
25
54
 
26
55
  platforms:
27
56
  # Following 3 will all use the same image (LTS), but it will be named accordingly
@@ -32,28 +61,20 @@ platforms:
32
61
  - name: ubuntu-15.10
33
62
  - name: ubuntu-1510
34
63
  - name: ubuntu-wily
35
- ```
36
64
 
37
- As of now if you install lxd, and have a public key setup in ~/.ssh/ then you should be able to use plugin without manual intervention. I have only tested with ubuntu container. What does it do:
65
+ suites:
66
+ - name: web
67
+ driver_config:
68
+ ipv4: 10.0.3.31/24
69
+ run_list:
70
+ - name: db
71
+ driver_config:
72
+ ipv4: 10.0.3.32/24
73
+ run_list:
38
74
 
39
- Kitchen Create
40
-
41
- 1) Creates image if its missing. If a platform is specified LXD will check if an image with the same name exists, if not it will parse the platform name, and install the appropriate image. If the image exists it will use that image.
42
-
43
- 2) If container does not exists, creates container. Using the instance name as the container name, and using the image from previous step.
44
-
45
- 3) If container exists, starts it
46
-
47
- 4) copies public key from ~/.ssh/ to container /root/.ssh/
48
-
49
-
50
- Note: if you have troubles with the public key, you can always manually setup public key on container, and publish it to an image. If the image already exists it will not be download via test-kitchen.
51
-
52
- Kitchen Destroy
53
-
54
- 1) Stops container if its running
75
+ ```
55
76
 
56
- 2) Destroys container if it exists
77
+ As of now if you install lxd, and have a public key setup in ~/.ssh/ then you should be able to use plugin without manual intervention. I have only tested with Ubuntu containers. Good chance most things will work in other Platforms, but some networking config is Ubuntu/Debian specific (dns_servers, domain_name).
57
78
 
58
79
  LXD Links:
59
80
 
@@ -63,17 +84,91 @@ LXD Links:
63
84
 
64
85
  ## <a name="installation"></a> Installation and Setup
65
86
 
87
+ Must have LXD installed on OS, I've only tested on Ubuntu 15.10
88
+
89
+ Install on command line:
90
+ ```
91
+ gem install kitchen-lxd_cli
92
+ ```
93
+ or use bundler:
94
+ ```
95
+ gem "kitchen-lxd_cli"
96
+
97
+ gem "kitchen-lxd_cli", :github => "bradenwright/kitchen-lxd_cli"
98
+
99
+ gem "kitchen-lxd_cli", :path => "~/kitchen-lxd_cli"
100
+ ```
66
101
  Please read the [Driver usage][driver_usage] page for more details.
67
102
 
68
103
  ## <a name="config"></a> Configuration
69
104
 
70
- public_key_path config can be manual set or is derived by default based
105
+ Current config options:
106
+
107
+ * image_name
108
+ * image_os
109
+ * image_release
110
+ * profile
111
+ * config
112
+ * domain_name
113
+ * dns_servers
114
+ * ipv4
115
+ * ip_gateway
116
+ * stop_instead_of_destroy
117
+
118
+ public_key_path: /my/path/public_key_file
119
+
120
+ can be manual set otherwise is derived by default based
71
121
  ~/.ssh/ directory, specifically the setting is derived by searching for:
72
122
  - `~/.ssh/id_rsa.pub`
73
123
  - `~/.ssh/id_dsa.pub`
74
124
  - `~/.ssh/identity.pub`
75
125
  - `~/.ssh/id_ecdsa.pub`
76
126
 
127
+ The public key at this location is copied to /root/.ssh in the lxc container to allow password less login.
128
+
129
+ image_name:
130
+
131
+ Defaults to platform.name. Also note that if the image exists it will be used and will not be created. This allows a container to be published (image manually created).
132
+
133
+ image_os:
134
+
135
+ By default platform.name is split on "-" and the first element is used. E.G. platform.name = ubuntu-14.04 then image_os = ubuntu
136
+
137
+ image_release:
138
+
139
+ By default platform.name is split on "-" and the second element is used. E.G. platform.name = ubuntu-14.04 the image_release = 14.04 For ubuntu 14.04 is changed to release name, E.G. trusty, when image is being downloaded. For Ubuntu trusty, 14.04, 1404 will all result in the same image being used, it will just be named different depending. It may work for OS's other than ubuntu but have not tested
140
+
141
+ profile:
142
+
143
+ Default is Nil. See LXC documentation but a lxc profile can be specified. Which will be passed to "lxc init" command
144
+
145
+ config:
146
+
147
+ Default is Nil. See LXC documentation but a lxc config container key/value can be specified. [LXC Container Config Options](https://github.com/lxc/lxd/blob/master/specs/configuration.md#keyvalue-configuration-1) Again option is passed to "lxc launch" command.
148
+
149
+ domain_name:
150
+
151
+ Default is nil.
152
+
153
+ dns_server:
154
+
155
+ Default is nil. Which is used for dhcp, if a static ip is setup then dns servers need to be configured for chef to work. If a static ip is supplied and no dns_server is specified it will try to use the default gateway, google dns (e.g. 10.0.3.1, 8.8.8.8, 8.8.4.4). If a default gateway is not specified or can't be located then only google dns (8.8.8.8, 8.8.4.4) will be used. A hash of dns_servers may be specified instead.
156
+
157
+ LXC NETWORKING OPTIONS: LXC by default uses 10.0.3.2/24 with a gateway of 10.0.3.1. You may use any ip space you wish, but LXD/LXC install sets up an ethernet bridge on 10.0.3.0/24 so make sure whatever network you choose to use is configured and accessible. In Ubuntu 15.10, LXD/LXC 0.20 configuration for networking is located at /etc/default/lxc-net, another option other than static ips is to setup dhcp host/ip mappings to reserve ips (described in /etc/default/lxc-net file comments) but it didn't seem to be working for me. You can also configure DHCP scope, etc. I've been using static ips from 10.0.3.0/24 and those ips have worked without needing to make changes to LXD/LXC configuration, although I did change the DHCP scope to allow space for static ip addresses (just to make sure there wasn't accidentally overlap)
158
+
159
+ ipv4:
160
+
161
+ Allows for Static IP/CIDR to be set, currently netmask is not supported. E.g. 10.0.3.100/24
162
+
163
+ ip_gateway:
164
+
165
+ Allows for a default gateway to be set. If ipv4 is used ip_gateway default value is 10.0.3.1, if dhcp is used then default gateway is given via dhcp. Default gateway is also used as a dns_server if static ips are used and no dns server is specified.
166
+
167
+
168
+ stop_instead_of_destroy:
169
+
170
+ Default is false. Can be useful sometimes to keep machines intact. It allows kitchen destroy to stop container, kitchen create can be issued to start boxes if they are not running.
171
+
77
172
  ### <a name="config-require-chef-omnibus"></a> require\_chef\_omnibus
78
173
 
79
174
  Determines whether or not a Chef [Omnibus package][chef_omnibus_dl] will be
@@ -93,11 +188,12 @@ The default value is unset, or `nil`.
93
188
  ## <a name="roadmap"></a> Roadmap
94
189
  * Update/Clean README
95
190
  * Config option for remote host, remote user, etc. So lxc can be launched on a remote server, not just locally
96
- * Config option for adding/using lxc remote command
191
+ * Config option to add remote for lxc, so images can be downloaded from other locations. Currently would have to manually be done in LXD/LXC
97
192
  * Config option to publish container on verify
98
- * Config options for static ip, cpu, mem
99
193
  * Config option to install upstart (not used by default in containers)
100
194
  * Config option for proxy/cache
195
+ * Ability to set more than 1 config (key/value)
196
+ * Example chef cookbook which uses this driver to setup a multi-node web application
101
197
 
102
198
  ## <a name="development"></a> Development
103
199
 
@@ -123,9 +219,9 @@ Created and maintained by [Braden Wright][author] (<braden.m.wright@gmail.com>)
123
219
  Apache 2.0 (see [LICENSE][license])
124
220
 
125
221
 
126
- [author]: https://github.com/enter-github-user
127
- [issues]: https://github.com/enter-github-user/kitchen-lxd_cli/issues
128
- [license]: https://github.com/enter-github-user/kitchen-lxd_cli/blob/master/LICENSE
129
- [repo]: https://github.com/enter-github-user/kitchen-lxd_cli
222
+ [author]: https://github.com/bradenwright
223
+ [issues]: https://github.com/bradenwright/kitchen-lxd_cli/issues
224
+ [license]: https://github.com/bradenwright/kitchen-lxd_cli/blob/master/LICENSE
225
+ [repo]: https://github.com/bradenwright/kitchen-lxd_cli
130
226
  [driver_usage]: http://docs.kitchen-ci.org/drivers/usage
131
227
  [chef_omnibus_dl]: http://www.getchef.com/chef/install/
@@ -8,9 +8,9 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Kitchen::Driver::LXD_CLI_VERSION
9
9
  spec.authors = ['Braden Wright']
10
10
  spec.email = ['braden.m.wright@gmail.com']
11
- spec.description = %q{A Test Kitchen Driver for LxdCli}
11
+ spec.description = %q{A Test Kitchen Driver for LXD}
12
12
  spec.summary = spec.description
13
- spec.homepage = ''
13
+ spec.homepage = 'https://github.com/bradenwright/kitchen-lxd_cli'
14
14
  spec.license = 'Apache 2.0'
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
@@ -25,7 +25,9 @@ module Kitchen
25
25
  # LxdCli driver for Kitchen.
26
26
  #
27
27
  # @author Braden Wright <braden.m.wright@gmail.com>
28
- class LxdCli < Kitchen::Driver::SSHBase
28
+ class LxdCli < Kitchen::Driver::Base
29
+ kitchen_driver_api_version 2
30
+
29
31
  default_config :public_key_path do
30
32
  [
31
33
  File.expand_path('~/.ssh/id_rsa.pub'),
@@ -34,45 +36,46 @@ module Kitchen
34
36
  File.expand_path('~/.ssh/id_ecdsa.pub')
35
37
  ].find { |path| File.exist?(path) }
36
38
  end
39
+ default_config :stop_instead_of_destroy, false
40
+
41
+ required_config :public_key_path
37
42
 
38
43
  def create(state)
39
- if exists?
40
- if running?
41
- debug("#{instance.name} already exists, and is already running. Nothing to do")
42
- else
43
- debug("#{instance.name} already exists, starting instead")
44
- run_command("lxc start #{instance.name}")
45
- end
46
- else
47
- create_image_if_missing
48
- run_command("lxc launch #{instance.platform.name} #{instance.name}")
44
+ unless exists?
45
+ image_name = create_image_if_missing
46
+ profile = "-p #{config[:profile]}" if config[:profile]
47
+ lxc_config = "-c #{config[:config]}" if config[:config]
48
+ info("Initializing container #{instance.name}")
49
+ run_lxc_command("init #{image_name} #{instance.name} #{profile} #{lxc_config}")
49
50
  end
50
- ip_address(state)
51
+
52
+ config_and_start_container unless running?
53
+ configure_dns
54
+ lxc_ip = wait_for_ip_address
55
+ state[:hostname] = lxc_ip
51
56
  setup_ssh_access
52
57
  end
53
58
 
54
59
  def destroy(state)
55
60
  if exists?
56
61
  if running?
57
- run_command("lxc stop #{instance.name}")
58
- else
59
- debug("#{instance.name} isn't running, just destroying instead")
62
+ info("Stopping container #{instance.name}")
63
+ run_lxc_command("stop #{instance.name}")
60
64
  end
61
- run_command("lxc delete #{instance.name}")
62
- else
63
- debug("#{instance.name} doesn't exist. Nothing to do")
65
+ info("Deleting container #{instance.name}")
66
+ run_lxc_command("delete #{instance.name}") unless config[:stop_instead_of_destroy]
64
67
  end
65
68
  state.delete(:hostname)
66
69
  end
67
70
 
68
71
  private
69
72
  def exists?
70
- status = `lxc info #{instance.name} > /dev/null 2>&1 && echo $?`.chomp
71
- if "#{status}" == "0"
72
- debug("#{instance.name} exists")
73
+ `lxc info #{instance.name} > /dev/null 2>&1`
74
+ if $?.to_i == 0
75
+ debug("Container #{instance.name} exists")
73
76
  return true
74
77
  else
75
- debug("#{instance.name} doesn't exist")
78
+ debug("Container #{instance.name} doesn't exist")
76
79
  return false
77
80
  end
78
81
  end
@@ -80,29 +83,30 @@ module Kitchen
80
83
  def running?
81
84
  status = `lxc info #{instance.name}`.match(/Status: ([a-zA-Z]+)[\n]/).captures[0].upcase
82
85
  if status == "RUNNING"
83
- debug("#{instance.name} is running")
86
+ debug("Container #{instance.name} is running")
84
87
  return true
85
88
  else
86
- debug("#{instance.name} isn't running")
89
+ debug("Container #{instance.name} isn't running")
87
90
  return false
88
91
  end
89
92
  end
90
93
 
91
94
  def create_image_if_missing
92
- status = `lxc image show #{instance.name} > /dev/null 2>&1 && echo $?`.chomp
93
- if "#{status}" == "0"
94
- debug("Image #{instance.name} exists")
95
- return false
95
+ image_name = config[:image_name] ||= instance.platform.name
96
+ `lxc image show #{image_name} > /dev/null 2>&1`
97
+ if $?.to_i == 0
98
+ debug("Image #{image_name} exists")
96
99
  else
97
- debug("Image #{instance.name} doesn't exist, creating now.")
98
- image = get_ubuntu_image_info
99
- debug("lxd-images import #{image[:os]} #{image[:release]} --alias #{instance.platform.name}")
100
- run_command("lxd-images import #{image[:os]} #{image[:release]} --alias #{instance.platform.name}")
101
- return true
100
+ info("Image #{image_name} doesn't exist, creating now. May take a few minutes.")
101
+ image = get_image_info
102
+ image_os = config[:image_os] ||= image[:os]
103
+ image_release = config[:image_release] ||= image[:release]
104
+ run_local_command("lxd-images import #{image_os} #{image_release} --alias #{image_name}")
102
105
  end
106
+ return image_name
103
107
  end
104
108
 
105
- def get_ubuntu_image_info
109
+ def get_image_info
106
110
  platform, release = instance.platform.name.split('-')
107
111
  if platform.downcase == "ubuntu"
108
112
  case release.downcase
@@ -119,25 +123,163 @@ module Kitchen
119
123
  else
120
124
  image = { :os => platform, :release => release }
121
125
  end
122
- return image
126
+ else
127
+ image = { :os => platform, :release => release }
128
+ end
129
+ return image
130
+ end
131
+
132
+ def config_and_start_container
133
+ config[:ip_gateway] ||= "auto"
134
+ arg_disable_dhcp = ""
135
+ if config[:ipv4]
136
+ IO.popen("bash", "r+") do |p|
137
+ p.puts("echo -e \"lxc.network.type = veth\nlxc.network.name = eth0\nlxc.network.link = lxcbr0\nlxc.network.ipv4 = #{config[:ipv4]}\nlxc.network.ipv4.gateway = #{config[:ip_gateway]}\nlxc.network.flags = up\" | lxc config set #{instance.name} raw.lxc -")
138
+ p.puts("exit")
139
+ end
140
+ arg_disable_dhcp = "&& lxc exec #{instance.name} -- sed -i 's/dhcp/manual/g' /etc/network/interfaces.d/eth0.cfg"
141
+ end
142
+ # TODO: loop over/run all lxc config settings passed in or figure out how to use multiple with lxc init
143
+ info("Starting container #{instance.name}")
144
+ run_lxc_command("start #{instance.name} #{arg_disable_dhcp}")
145
+ end
146
+
147
+ def configure_dns
148
+ IO.popen("lxc exec #{instance.name} bash", "r+") do |p|
149
+ dns_servers = ""
150
+ config[:dns_servers].each do |dns_server|
151
+ dns_servers += "nameserver #{dns_server}\n"
152
+ end if config[:dns_servers]
153
+
154
+ case config[:ip_gateway]
155
+ when "auto", ""
156
+ dns_servers = "nameserver 8.8.8.8\nnameserver 8.8.4.4"
157
+ dns_servers = "nameserver 8.8.8.8\nnameserver 8.8.4.4"
158
+ else
159
+ dns_servers = "nameserver #{config[:ip_gateway]}\nnameserver 8.8.8.8\nnameserver 8.8.4.4"
160
+ end if config[:ipv4] && dns_servers.length == 0
161
+
162
+ if dns_servers.length > 0
163
+ wait_for_path("/etc/resolvconf/resolv.conf.d/base")
164
+ debug("Setting up the following dns servers via /etc/resolvconf/resolv.conf.d/base:")
165
+ debug(dns_servers.gsub("\n", ' '))
166
+ p.puts(" echo \"#{dns_servers.chomp}\" > /etc/resolvconf/resolv.conf.d/base")
167
+ p.puts("resolvconf -u")
168
+ end
169
+
170
+ debug("Setting up /etc/hosts")
171
+ if config[:domain_name]
172
+ # p.puts("echo -e \" dns-search #{config[:domain_name]}\" >> /etc/network/interfaces.d/eth0.cfg")
173
+ args_host = "#{instance.name}.#{config[:domain_name]} #{instance.name}"
174
+ end
175
+ args_host ||= "#{instance.name}"
176
+ wait_for_path("/etc/hosts")
177
+ p.puts("if grep -iq '127.0.1.1' /etc/hosts; then")
178
+ p.puts("sed -i 's/^127.0.1.1.*$/127.0.1.1\t#{args_host}/' /etc/hosts")
179
+ p.puts("else echo '#***** Setup by Kitchen-LxdCli driver *****#' >> /etc/hosts")
180
+ p.puts("echo -e '127.0.1.1\t#{args_host}' >> /etc/hosts; fi")
181
+ p.puts("exit")
123
182
  end
124
183
  end
184
+ =begin
185
+ def configure_ip_via_lxc_restart
186
+ debug("Configuring new ip address on eth0")
187
+
188
+ IO.popen("lxc exec #{instance.name} bash", "r+") do |p|
189
+ p.puts('echo -e "#############################################" > /etc/network/interfaces.d/eth0.cfg')
190
+ p.puts('echo -e "# DO NOT EDIT CONTROLLED BY KITCHEN-LXC_CLI #" >> /etc/network/interfaces.d/eth0.cfg')
191
+ p.puts('echo -e "#############################################" >> /etc/network/interfaces.d/eth0.cfg')
192
+ p.puts('echo -e "auto eth0" >> /etc/network/interfaces.d/eth0.cfg')
193
+ if config[:ipv4]
194
+ config[:ip_gateway] ||= "10.0.3.1"
195
+ config[:dns_servers] ||= [ "8.8.8.8", "8.8.4.4" ]
196
+ p.puts('echo -e " iface eth0 inet static" >> /etc/network/interfaces.d/eth0.cfg')
197
+ p.puts("echo -e \" address #{config[:ipv4]}\" >> /etc/network/interfaces.d/eth0.cfg")
198
+ else
199
+ p.puts('echo -e " iface eth0 inet dhcp" >> /etc/network/interfaces.d/eth0.cfg')
200
+ end
201
+ p.puts("echo -e \" gateway #{config[:ip_gateway]}\" >> /etc/network/interfaces.d/eth0.cfg") if config[:ip_gateway]
202
+ config[:dns_servers].each do |dns_server|
203
+ p.puts("echo -e \" dns-nameserver #{dns_server}\" >> /etc/network/interfaces.d/eth0.cfg")
204
+ end if config[:dns_servers]
205
+ if config[:domain_name]
206
+ p.puts("echo -e \" dns-search #{config[:domain_name]}\" >> /etc/network/interfaces.d/eth0.cfg")
207
+ end
208
+ p.puts("exit")
209
+ end
210
+ debug("Finished configuring new ip address, restarting #{instance.name} for settings to take effect")
211
+ debug_note_about_configuring_ip
212
+ wait_for_ip_address
213
+ sleep 3 # Was hanging more often than not whenever I lowered the sleep
214
+ debug("Restarting #{instance.name}")
215
+ run_lxc_command("restart #{instance.name}")
216
+ debug("Finished restarting #{instance.name} ip address should be configured")
217
+ end
218
+ =end
219
+
220
+ def setup_ssh_access
221
+ info("Setting up public key #{config[:public_key_path]} on #{instance.name}")
222
+ wait_for_path("/root/.ssh")
223
+
224
+ begin
225
+ debug("Uploading public key...")
226
+ `lxc file push #{config[:public_key_path]} #{instance.name}/root/.ssh/authorized_keys 2> /dev/null`
227
+ break if $?.to_i == 0
228
+ sleep 0.3
229
+ end while true
230
+
231
+ debug("Finished Copying public key from #{config[:public_key_path]} to #{instance.name}")
232
+ end
125
233
 
126
- def ip_address(state)
234
+ def wait_for_ip_address
235
+ info("Waiting for network to become ready")
127
236
  begin
128
- lxc_info = `lxc info #{instance.name}`
129
- end while (!lxc_info.match(/eth0:[\t]IPV[46][\t]([0-9.]+)[\n]/))
130
- lxc_ip = lxc_info.match(/eth0:[\t]IPV[46][\t]([0-9.]+)[\n]/).captures[0].to_s
131
- state[:hostname] = lxc_ip
237
+ lxc_info = `lxc info #{instance.name}`.match(/^[ ]+eth0:[\t]IPV4[\t]([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*$/)
238
+ debug("Still waiting for IP Address...")
239
+ lxc_ip = lxc_info.captures[0].to_s if lxc_info && lxc_info.captures
240
+ break if lxc_ip && lxc_ip.length > 7
241
+ sleep 0.3
242
+ end while true
243
+ debug("Found Ip Address #{lxc_ip}")
132
244
  return lxc_ip
133
245
  end
134
246
 
247
+ def wait_for_path(path)
248
+ begin
249
+ debug("Waiting for #{path} to become available...")
250
+ run_lxc_command("exec #{instance.name} -- ls #{path}")
251
+ break if $?.to_i == 0
252
+ sleep 0.3
253
+ end while true
254
+ debug("Found #{path}")
255
+ end
256
+
135
257
  def setup_ssh_access
136
- info("Copying public key from #{config[:public_key_path]} to #{instance.name}")
258
+ info("Setting up public key #{config[:public_key_path]} on #{instance.name}")
259
+ wait_for_path("/root/.ssh")
260
+
137
261
  begin
138
- sleep 1
139
- status = `lxc file push #{config[:public_key_path]} #{instance.name}/root/.ssh/authorized_keys 2> /dev/null && echo $?`.chomp
140
- end while ("#{status}" != "0")
262
+ debug("Uploading public key...")
263
+ `lxc file push #{config[:public_key_path]} #{instance.name}/root/.ssh/authorized_keys 2> /dev/null`
264
+ break if $?.to_i == 0
265
+ sleep 0.3
266
+ end while true
267
+
268
+ debug("Finished Copying public key from #{config[:public_key_path]} to #{instance.name}")
269
+ end
270
+
271
+ def run_lxc_command(cmd)
272
+ run_local_command("lxc #{cmd}") if cmd
273
+ end
274
+
275
+ def run_local_command(cmd)
276
+ debug("run_local_command ran: #{cmd}")
277
+ `#{cmd}` if cmd
278
+ debug("Command finished: #{$?.to_s}")
279
+ end
280
+
281
+ def debug_note_about_configuring_ip
282
+ debug("NOTE: Restarting seemed to be the only way I could get things to work. Tried lxc profiles, config options. Tried restart networking service but it didn't work, also tried passing command like ifconfig 10.0.3.x/24 eth0 up. Which set the ip but after container ran for a while, it would reset to dhcp address that had been assigned. Restarting container seems to be working, and is really fast. Open to better alternatives.")
141
283
  end
142
284
  end
143
285
  end
@@ -21,6 +21,6 @@ module Kitchen
21
21
  module Driver
22
22
 
23
23
  # Version string for LxdCli Kitchen driver
24
- LXD_CLI_VERSION = "0.1.0.beta"
24
+ LXD_CLI_VERSION = "0.1.0"
25
25
  end
26
26
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-lxd_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.beta
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Braden Wright
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-30 00:00:00.000000000 Z
11
+ date: 2015-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: test-kitchen
@@ -94,7 +94,7 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
- description: A Test Kitchen Driver for LxdCli
97
+ description: A Test Kitchen Driver for LXD
98
98
  email:
99
99
  - braden.m.wright@gmail.com
100
100
  executables: []
@@ -103,8 +103,10 @@ extra_rdoc_files: []
103
103
  files:
104
104
  - ".cane"
105
105
  - ".gitignore"
106
+ - ".kitchen/logs/kitchen.log"
106
107
  - ".tailor"
107
108
  - ".travis.yml"
109
+ - ":w"
108
110
  - CHANGELOG.md
109
111
  - Gemfile
110
112
  - LICENSE
@@ -113,7 +115,7 @@ files:
113
115
  - kitchen-lxd_cli.gemspec
114
116
  - lib/kitchen/driver/lxd_cli.rb
115
117
  - lib/kitchen/driver/lxd_cli_version.rb
116
- homepage: ''
118
+ homepage: https://github.com/bradenwright/kitchen-lxd_cli
117
119
  licenses:
118
120
  - Apache 2.0
119
121
  metadata: {}
@@ -128,14 +130,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
128
130
  version: '0'
129
131
  required_rubygems_version: !ruby/object:Gem::Requirement
130
132
  requirements:
131
- - - ">"
133
+ - - ">="
132
134
  - !ruby/object:Gem::Version
133
- version: 1.3.1
135
+ version: '0'
134
136
  requirements: []
135
137
  rubyforge_project:
136
138
  rubygems_version: 2.4.8
137
139
  signing_key:
138
140
  specification_version: 4
139
- summary: A Test Kitchen Driver for LxdCli
141
+ summary: A Test Kitchen Driver for LXD
140
142
  test_files: []
141
143
  has_rdoc: