cide 0.1.1 → 0.2.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 +4 -4
- data/.editorconfig +13 -0
- data/.rspec +1 -0
- data/.rubocop.yml +28 -11
- data/CHANGELOG.md +10 -0
- data/Gemfile.lock +52 -10
- data/README.md +10 -11
- data/Rakefile +3 -1
- data/cide.gemspec +6 -3
- data/lib/cide/build/config.rb +84 -0
- data/lib/cide/build/config_loader.rb +227 -0
- data/lib/cide/build.rb +2 -0
- data/lib/cide/cli.rb +195 -0
- data/lib/cide/constants.rb +12 -0
- data/lib/cide/default_cide.yml +9 -0
- data/lib/cide/docker.rb +9 -0
- data/lib/{cide_template.erb → cide/dockerfile_template.erb} +4 -15
- data/lib/{ssh_config → cide/ssh_config} +0 -0
- data/lib/cide.rb +2 -224
- data/spec/build_config_loader_spec.rb +116 -0
- data/spec/spec_helper.rb +1 -0
- metadata +63 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c84c09a6870e5146c1a42a86e03e1d01a2dc74aa
|
|
4
|
+
data.tar.gz: 4a4b246cc68b455bd26c8185c6dde9aaa1a6e8b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8e69cb3488d0a15e18ab4031083aa3c33c720757236d28135de981e0d98ea8dbeb95807d48a236b4617492551880049129cee649a49508288b7d9e22c0662dbf
|
|
7
|
+
data.tar.gz: 8b0a452b1d6a159f4362dc11674576d9c6ca34c9b6c56a0b1bdab0727b64ee07a6d13dfdcc2df93838d662048cb5817292ed5951e8d95170fe58ccbe6fc51dae
|
data/.editorconfig
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# EditorConfig is awesome: http://EditorConfig.org
|
|
2
|
+
|
|
3
|
+
# top-most EditorConfig file
|
|
4
|
+
root = true
|
|
5
|
+
|
|
6
|
+
# Unix-style newlines with a newline ending every file
|
|
7
|
+
[*]
|
|
8
|
+
end_of_line = lf
|
|
9
|
+
insert_final_newline = true
|
|
10
|
+
|
|
11
|
+
[*.rb]
|
|
12
|
+
indent_style = space
|
|
13
|
+
indent_size = 2
|
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--colour --format documentation -rspec_helper
|
data/.rubocop.yml
CHANGED
|
@@ -1,33 +1,50 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
Exclude:
|
|
3
|
+
- 'spec/**/*'
|
|
4
|
+
|
|
5
|
+
# Rubocop is not smart enough
|
|
6
|
+
Metrics/AbcSize:
|
|
7
|
+
Max: 100
|
|
8
|
+
|
|
1
9
|
# CIDE::CLI is essentially a big dispatcher, no need to break it
|
|
2
10
|
# in smaller chunks.
|
|
3
11
|
Metrics/ClassLength:
|
|
4
12
|
Max: 200
|
|
5
13
|
|
|
14
|
+
# Offense count: 28
|
|
15
|
+
Metrics/CyclomaticComplexity:
|
|
16
|
+
Max: 15
|
|
17
|
+
|
|
6
18
|
# CIDE::CLI methods can be read top to bottom. No need to factor out
|
|
7
19
|
# functionality unless it can be shared.
|
|
8
20
|
Metrics/MethodLength:
|
|
9
21
|
Max: 60
|
|
10
22
|
|
|
23
|
+
Metrics/PerceivedComplexity:
|
|
24
|
+
Max: 20
|
|
25
|
+
|
|
11
26
|
# Don't align stuff vertically, bad for diffing
|
|
12
27
|
Style/AlignParameters:
|
|
13
28
|
EnforcedStyle: with_fixed_indentation
|
|
14
29
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
EnforcedStyleForMultiline: comma
|
|
30
|
+
# Don't obsess over missing documentation for now
|
|
31
|
+
Style/Documentation:
|
|
32
|
+
Enabled: false
|
|
19
33
|
|
|
20
|
-
#
|
|
21
|
-
Style/
|
|
34
|
+
# Don't agree with rubocop here
|
|
35
|
+
Style/MultilineOperationIndentation:
|
|
22
36
|
Enabled: false
|
|
23
37
|
|
|
24
38
|
# Prefering the short style
|
|
25
39
|
Style/PerlBackrefs:
|
|
26
40
|
Enabled: false
|
|
27
41
|
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
|
|
42
|
+
# $? is not equivalent to $CHILD_STATUS
|
|
43
|
+
Style/SpecialGlobalVars:
|
|
44
|
+
Enabled: false
|
|
45
|
+
|
|
46
|
+
# Allows for easy diffing
|
|
47
|
+
# Keep them sorted alphabetically unless a meaningful order exists
|
|
48
|
+
Style/TrailingComma:
|
|
49
|
+
EnforcedStyleForMultiline: comma
|
|
31
50
|
|
|
32
|
-
Metrics/PerceivedComplexity:
|
|
33
|
-
Max: 20
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
|
|
2
|
+
0.2.0 / 2015-04-15
|
|
3
|
+
==================
|
|
4
|
+
|
|
5
|
+
* NEW: .cide.yml schema loader
|
|
6
|
+
* NEW: Support for linked containers
|
|
7
|
+
* NEW: Much better build output
|
|
8
|
+
* CHANGE: Docker version: 1.5.0+ is required
|
|
9
|
+
* FIX: Avoid name clashes with projects who have a Dockerfile already
|
|
10
|
+
* FIX: cide --export
|
|
11
|
+
|
|
2
12
|
0.1.1 / 2015-01-27
|
|
3
13
|
==================
|
|
4
14
|
|
data/Gemfile.lock
CHANGED
|
@@ -2,34 +2,76 @@ PATH
|
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
4
|
cide (0.1.1)
|
|
5
|
-
thor
|
|
5
|
+
thor (~> 0.19.0)
|
|
6
|
+
virtus (~> 1.0.0)
|
|
6
7
|
|
|
7
8
|
GEM
|
|
8
9
|
remote: https://rubygems.org/
|
|
9
10
|
specs:
|
|
11
|
+
activesupport (4.2.1)
|
|
12
|
+
i18n (~> 0.7)
|
|
13
|
+
json (~> 1.7, >= 1.7.7)
|
|
14
|
+
minitest (~> 5.1)
|
|
15
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
|
16
|
+
tzinfo (~> 1.1)
|
|
10
17
|
ast (2.0.0)
|
|
11
18
|
astrolabe (1.3.0)
|
|
12
19
|
parser (>= 2.2.0.pre.3, < 3.0)
|
|
13
|
-
|
|
20
|
+
axiom-types (0.1.1)
|
|
21
|
+
descendants_tracker (~> 0.0.4)
|
|
22
|
+
ice_nine (~> 0.11.0)
|
|
23
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
|
24
|
+
coercible (1.0.0)
|
|
25
|
+
descendants_tracker (~> 0.0.1)
|
|
26
|
+
descendants_tracker (0.0.4)
|
|
27
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
|
28
|
+
diff-lcs (1.2.5)
|
|
29
|
+
equalizer (0.0.11)
|
|
30
|
+
i18n (0.7.0)
|
|
31
|
+
ice_nine (0.11.1)
|
|
32
|
+
json (1.8.1)
|
|
33
|
+
minitest (5.4.3)
|
|
34
|
+
parser (2.2.0.3)
|
|
14
35
|
ast (>= 1.1, < 3.0)
|
|
15
|
-
|
|
16
|
-
powerpack (0.0.9)
|
|
36
|
+
powerpack (0.1.0)
|
|
17
37
|
rainbow (2.0.0)
|
|
18
|
-
rake (10.
|
|
19
|
-
|
|
38
|
+
rake (10.4.2)
|
|
39
|
+
rspec (3.2.0)
|
|
40
|
+
rspec-core (~> 3.2.0)
|
|
41
|
+
rspec-expectations (~> 3.2.0)
|
|
42
|
+
rspec-mocks (~> 3.2.0)
|
|
43
|
+
rspec-core (3.2.3)
|
|
44
|
+
rspec-support (~> 3.2.0)
|
|
45
|
+
rspec-expectations (3.2.1)
|
|
46
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
47
|
+
rspec-support (~> 3.2.0)
|
|
48
|
+
rspec-mocks (3.2.1)
|
|
49
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
50
|
+
rspec-support (~> 3.2.0)
|
|
51
|
+
rspec-support (3.2.2)
|
|
52
|
+
rubocop (0.30.0)
|
|
20
53
|
astrolabe (~> 1.3)
|
|
21
|
-
parser (>= 2.2.0.
|
|
22
|
-
powerpack (~> 0.
|
|
54
|
+
parser (>= 2.2.0.1, < 3.0)
|
|
55
|
+
powerpack (~> 0.1)
|
|
23
56
|
rainbow (>= 1.99.1, < 3.0)
|
|
24
57
|
ruby-progressbar (~> 1.4)
|
|
25
|
-
ruby-progressbar (1.
|
|
26
|
-
slop (3.6.0)
|
|
58
|
+
ruby-progressbar (1.7.5)
|
|
27
59
|
thor (0.19.1)
|
|
60
|
+
thread_safe (0.3.5)
|
|
61
|
+
tzinfo (1.2.2)
|
|
62
|
+
thread_safe (~> 0.1)
|
|
63
|
+
virtus (1.0.5)
|
|
64
|
+
axiom-types (~> 0.1)
|
|
65
|
+
coercible (~> 1.0)
|
|
66
|
+
descendants_tracker (~> 0.0, >= 0.0.3)
|
|
67
|
+
equalizer (~> 0.0, >= 0.0.9)
|
|
28
68
|
|
|
29
69
|
PLATFORMS
|
|
30
70
|
ruby
|
|
31
71
|
|
|
32
72
|
DEPENDENCIES
|
|
73
|
+
activesupport
|
|
33
74
|
cide!
|
|
34
75
|
rake
|
|
76
|
+
rspec
|
|
35
77
|
rubocop
|
data/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Example
|
|
|
20
20
|
`.cide.yml`
|
|
21
21
|
```
|
|
22
22
|
---
|
|
23
|
-
|
|
23
|
+
from: "ruby:2.1"
|
|
24
24
|
as_root:
|
|
25
25
|
- apt-get update -qy && apt-get install -qy libxml2-dev
|
|
26
26
|
command: bundle && bundle exec rspec
|
|
@@ -32,35 +32,34 @@ Features
|
|
|
32
32
|
* straighforward to use, just run `cide` inside of your project
|
|
33
33
|
* works on OSX with boot2docker
|
|
34
34
|
* integrates easily with jenkins or other CI systems
|
|
35
|
+
* can run with linked containers
|
|
35
36
|
|
|
36
37
|
Limitations
|
|
37
38
|
-----------
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
doesn't allow referencing files outside of the directory (even with a symlink)
|
|
40
|
+
Docker version 1.5.0+ is required
|
|
41
41
|
|
|
42
42
|
Installation
|
|
43
43
|
------------
|
|
44
44
|
|
|
45
45
|
Install docker: https://docs.docker.com/installation/#installation
|
|
46
46
|
|
|
47
|
-
```
|
|
47
|
+
```sh
|
|
48
48
|
gem install cide
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
OSX docker install:
|
|
52
|
-
```
|
|
52
|
+
```sh
|
|
53
53
|
brew install boot2docker
|
|
54
54
|
boot2docker init
|
|
55
55
|
boot2docker up
|
|
56
56
|
# cide auto-detects boot2docker on OSX
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
Similar projects
|
|
60
|
+
----------------
|
|
61
|
+
|
|
62
|
+
* [Docker Compose](https://docs.docker.com/compose/) - Docker development environment
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
* schema validation
|
|
64
|
-
* find a way to import SSH keys
|
|
65
|
-
* cleaner output. on error is just outputs a backtrace
|
|
64
|
+
Open an issue if a project is missing.
|
|
66
65
|
|
data/Rakefile
CHANGED
data/cide.gemspec
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Gem::Specification.new do |s|
|
|
4
4
|
s.name = 'cide'
|
|
5
|
-
s.version = '0.
|
|
5
|
+
s.version = '0.2.0'
|
|
6
6
|
s.authors = ['zimbatm']
|
|
7
7
|
s.email = ['zimbatm@zimbatm.com']
|
|
8
8
|
s.summary = 'CI docker runner'
|
|
@@ -13,14 +13,17 @@ DESC
|
|
|
13
13
|
s.homepage = 'https://github.com/zimbatm/cide'
|
|
14
14
|
s.license = 'MIT'
|
|
15
15
|
|
|
16
|
-
s.executables
|
|
16
|
+
s.executables = ['cide']
|
|
17
17
|
s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
|
18
18
|
s.test_files = `git ls-files spec`.split($INPUT_RECORD_SEPARATOR)
|
|
19
19
|
s.require_paths = ['lib']
|
|
20
20
|
|
|
21
21
|
s.required_ruby_version = '>= 1.9.3'
|
|
22
22
|
|
|
23
|
-
s.add_runtime_dependency 'thor'
|
|
23
|
+
s.add_runtime_dependency 'thor', '~> 0.19.0'
|
|
24
|
+
s.add_runtime_dependency 'virtus', '~> 1.0.0'
|
|
24
25
|
s.add_development_dependency 'rake'
|
|
25
26
|
s.add_development_dependency 'rubocop'
|
|
27
|
+
s.add_development_dependency 'rspec'
|
|
28
|
+
s.add_development_dependency 'activesupport'
|
|
26
29
|
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require 'virtus'
|
|
2
|
+
|
|
3
|
+
require 'erb'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
|
|
6
|
+
require 'cide/build/config_loader'
|
|
7
|
+
|
|
8
|
+
module CIDE
|
|
9
|
+
module Build
|
|
10
|
+
class Config
|
|
11
|
+
DOCKERFILE_TEMPLATE =
|
|
12
|
+
File.expand_path('../../dockerfile_template.erb', __FILE__)
|
|
13
|
+
|
|
14
|
+
module NiceInspect
|
|
15
|
+
def inspect
|
|
16
|
+
out = []
|
|
17
|
+
attributes.each_pair do |key, value|
|
|
18
|
+
out << "#{key}=#{value.inspect}"
|
|
19
|
+
end
|
|
20
|
+
"<#{out.join(' ')}>"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Step
|
|
25
|
+
include Virtus.model(strict: true)
|
|
26
|
+
include NiceInspect
|
|
27
|
+
attribute :add, Array[String], default: []
|
|
28
|
+
attribute :forward_env, Array[String], default: []
|
|
29
|
+
attribute :run, Array[String], default: []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Link
|
|
33
|
+
include Virtus.model
|
|
34
|
+
include NiceInspect
|
|
35
|
+
attribute :name, String
|
|
36
|
+
attribute :image, String
|
|
37
|
+
attribute :env, Hash[String, String]
|
|
38
|
+
attribute :run, String
|
|
39
|
+
# Container ID added after the fact
|
|
40
|
+
attr_accessor :id
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
include Virtus.model(strict: true)
|
|
44
|
+
include NiceInspect
|
|
45
|
+
attribute :from, String, default: 'ubuntu'
|
|
46
|
+
attribute :as_root, Array[String], default: []
|
|
47
|
+
attribute :use_ssh, Boolean, default: false
|
|
48
|
+
attribute :before, Step, required: false
|
|
49
|
+
attribute :forward_env, Array[String], default: []
|
|
50
|
+
attribute :export_dir, String, required: false
|
|
51
|
+
attribute :links, Array[Link], default: []
|
|
52
|
+
attribute :run, String, default: 'script/ci'
|
|
53
|
+
|
|
54
|
+
attr_reader :warnings, :errors
|
|
55
|
+
|
|
56
|
+
def initialize(*)
|
|
57
|
+
super
|
|
58
|
+
@warnings = []
|
|
59
|
+
@errors = []
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def to_dockerfile
|
|
63
|
+
ERB.new(File.read(DOCKERFILE_TEMPLATE), nil, '<>-').result(binding)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.load_file(file_path, output = $stderr)
|
|
67
|
+
obj = new
|
|
68
|
+
loader = ConfigLoader.new(obj)
|
|
69
|
+
loader.load YAML.load_file(file_path)
|
|
70
|
+
|
|
71
|
+
obj.warnings.each do |warn|
|
|
72
|
+
output.puts "WARN: #{warn}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
obj.errors.each do |error|
|
|
76
|
+
output.puts "ERROR: #{error}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
return obj if obj.errors.empty?
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
module CIDE
|
|
2
|
+
module Build
|
|
3
|
+
class ConfigLoader
|
|
4
|
+
class Path
|
|
5
|
+
attr_reader :to_s
|
|
6
|
+
def initialize(str)
|
|
7
|
+
@to_s = str.to_s
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def append(value)
|
|
11
|
+
self.class.new(
|
|
12
|
+
@to_s + (value.is_a?(Integer) ? "[#{value}]" : ".#{value}"),
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(config)
|
|
18
|
+
@config = config
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def load(data)
|
|
22
|
+
data.each_pair do |key, value|
|
|
23
|
+
key = key.to_s
|
|
24
|
+
path = Path.new(key)
|
|
25
|
+
case key
|
|
26
|
+
when 'from', 'image' then
|
|
27
|
+
wanted_key(path, 'from', key)
|
|
28
|
+
@config.from = expect_string(path, value)
|
|
29
|
+
when 'as_root' then
|
|
30
|
+
@config.as_root = expect_array(path, value)
|
|
31
|
+
when 'use_ssh' then
|
|
32
|
+
@config.use_ssh = expect_boolean(path, value)
|
|
33
|
+
when 'before' then
|
|
34
|
+
@config.before = maybe_step(path, value)
|
|
35
|
+
when 'forward_env' then
|
|
36
|
+
@config.forward_env = expect_array(path, value)
|
|
37
|
+
when 'export_dir' then
|
|
38
|
+
@config.export_dir = maybe_string(path, value)
|
|
39
|
+
when 'link', 'links' then
|
|
40
|
+
@config.links = expect_links(path, value)
|
|
41
|
+
when 'run', 'command' then
|
|
42
|
+
wanted_key(path, 'run', key)
|
|
43
|
+
@config.run = expect_string(path, value)
|
|
44
|
+
else
|
|
45
|
+
unknown_key(path)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
@config
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
protected
|
|
52
|
+
|
|
53
|
+
def wanted_key(path, wanted_key, key)
|
|
54
|
+
return if key == wanted_key
|
|
55
|
+
@config.warnings <<
|
|
56
|
+
"#{path} is deprecated. use '#{wanted_key}' instead."
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def unknown_key(path)
|
|
60
|
+
@config.warnings << "Unknown key #{path}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def type_error(path, wanted_type, value)
|
|
64
|
+
@config.errors <<
|
|
65
|
+
"expected #{path} to be a #{wanted_type} but got a #{value.class}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def expect_string(path, value)
|
|
69
|
+
case value
|
|
70
|
+
when String, Symbol, Integer
|
|
71
|
+
value.to_s
|
|
72
|
+
else
|
|
73
|
+
type_error(path, 'string', value)
|
|
74
|
+
''
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def maybe_string(path, value)
|
|
79
|
+
case value
|
|
80
|
+
when String, Symbol
|
|
81
|
+
value.to_s
|
|
82
|
+
when nil
|
|
83
|
+
nil
|
|
84
|
+
else
|
|
85
|
+
type_error(path, 'string or nil', value)
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def maybe_step(path, value)
|
|
91
|
+
case value
|
|
92
|
+
when String, Symbol, Array then
|
|
93
|
+
load_step(path, run: value)
|
|
94
|
+
when Hash then
|
|
95
|
+
load_step(path, value)
|
|
96
|
+
when nil then
|
|
97
|
+
nil
|
|
98
|
+
else
|
|
99
|
+
type_error(path, 'string, array, hash or nil', value)
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def load_step(path, data)
|
|
105
|
+
step = Config::Step.new
|
|
106
|
+
data.each_pair do |key, value|
|
|
107
|
+
key = key.to_s
|
|
108
|
+
path_ = path.append(key)
|
|
109
|
+
case key
|
|
110
|
+
when 'run' then
|
|
111
|
+
step.run = expect_array(path_, value)
|
|
112
|
+
when 'forward_env' then
|
|
113
|
+
step.forward_env = expect_array(path_, value)
|
|
114
|
+
when 'add' then
|
|
115
|
+
step.add = expect_array(path_, value)
|
|
116
|
+
else
|
|
117
|
+
unknown_key(path_)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
step
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def expect_links(path, value)
|
|
124
|
+
array = []
|
|
125
|
+
case value
|
|
126
|
+
when String, Symbol, Hash then
|
|
127
|
+
array << expect_link(path, value)
|
|
128
|
+
when Array then
|
|
129
|
+
value.compact.each_with_index do |value_, i|
|
|
130
|
+
array << expect_link(path.append(i), value_)
|
|
131
|
+
end
|
|
132
|
+
when nil then
|
|
133
|
+
# nothing to do
|
|
134
|
+
else
|
|
135
|
+
type_error(path, 'string, array of links, hash or nil', value)
|
|
136
|
+
end
|
|
137
|
+
array.compact
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def expect_link(path, value)
|
|
141
|
+
case value
|
|
142
|
+
when String, Symbol then
|
|
143
|
+
load_link(path, name: value, image: value)
|
|
144
|
+
when Hash
|
|
145
|
+
load_link(path, value)
|
|
146
|
+
else
|
|
147
|
+
type_error(path, 'string or hash expected', value)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def load_link(path, data)
|
|
152
|
+
link = Config::Link.new
|
|
153
|
+
data.each_pair do |key, value|
|
|
154
|
+
key = key.to_s
|
|
155
|
+
path_ = path.append(key)
|
|
156
|
+
case key
|
|
157
|
+
when 'name' then
|
|
158
|
+
link.name = expect_string(path_, value)
|
|
159
|
+
when 'image', 'from' then
|
|
160
|
+
wanted_key(path_, 'image', key)
|
|
161
|
+
link.image = expect_string(path_, value)
|
|
162
|
+
when 'env' then
|
|
163
|
+
link.env = expect_env(path_, value)
|
|
164
|
+
when 'run' then
|
|
165
|
+
link.run = maybe_string(path_, value)
|
|
166
|
+
else
|
|
167
|
+
unknown_key(path_)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
link.name ||= link.image
|
|
171
|
+
link.image ||= link.name
|
|
172
|
+
if link.name.nil?
|
|
173
|
+
type_error(
|
|
174
|
+
path,
|
|
175
|
+
'expected hash to either declare the name or image',
|
|
176
|
+
data,
|
|
177
|
+
)
|
|
178
|
+
return nil
|
|
179
|
+
end
|
|
180
|
+
link
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def expect_boolean(path, value)
|
|
184
|
+
case value
|
|
185
|
+
when true then
|
|
186
|
+
true
|
|
187
|
+
when false then
|
|
188
|
+
false
|
|
189
|
+
else
|
|
190
|
+
type_error(path, 'boolean', value)
|
|
191
|
+
false
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def expect_array(path, value)
|
|
196
|
+
array = []
|
|
197
|
+
case value
|
|
198
|
+
when Array then
|
|
199
|
+
value.compact.each_with_index do |value_, i|
|
|
200
|
+
array << expect_string(path.append(i), value_)
|
|
201
|
+
end
|
|
202
|
+
when String, Symbol then
|
|
203
|
+
array << value.to_s
|
|
204
|
+
when nil then
|
|
205
|
+
# nothing to do
|
|
206
|
+
else
|
|
207
|
+
type_error(path, 'array of string, string or nil', value)
|
|
208
|
+
end
|
|
209
|
+
array.compact
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def expect_env(path, value)
|
|
213
|
+
case value
|
|
214
|
+
when Hash then
|
|
215
|
+
hash = {}
|
|
216
|
+
value.each_pair do |key, value_|
|
|
217
|
+
hash[key.to_s] = expect_string(path.append(key.to_s), value_)
|
|
218
|
+
end
|
|
219
|
+
hash
|
|
220
|
+
else
|
|
221
|
+
type_error(path, 'hash', value)
|
|
222
|
+
{}
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
data/lib/cide/build.rb
ADDED
data/lib/cide/cli.rb
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
require 'cide/constants'
|
|
2
|
+
require 'cide/docker'
|
|
3
|
+
require 'cide/build'
|
|
4
|
+
|
|
5
|
+
require 'thor'
|
|
6
|
+
|
|
7
|
+
require 'json'
|
|
8
|
+
require 'time'
|
|
9
|
+
|
|
10
|
+
module CIDE
|
|
11
|
+
# Command-line option-parsing and execution for cide
|
|
12
|
+
class CLI < Thor
|
|
13
|
+
include CIDE::Docker
|
|
14
|
+
include Thor::Actions
|
|
15
|
+
add_runtime_options!
|
|
16
|
+
|
|
17
|
+
default_command 'build'
|
|
18
|
+
|
|
19
|
+
desc 'build', 'Builds an image and executes the run script'
|
|
20
|
+
|
|
21
|
+
method_option 'name',
|
|
22
|
+
desc: 'Name of the build',
|
|
23
|
+
aliases: %w(n t),
|
|
24
|
+
default: File.basename(Dir.pwd)
|
|
25
|
+
|
|
26
|
+
method_option 'export',
|
|
27
|
+
desc: 'Whenever to export artifacts',
|
|
28
|
+
type: :boolean,
|
|
29
|
+
default: nil
|
|
30
|
+
|
|
31
|
+
method_option 'export_dir',
|
|
32
|
+
desc: 'Change the ouput directory on the host',
|
|
33
|
+
aliases: %w(o host_export_dir),
|
|
34
|
+
default: nil
|
|
35
|
+
|
|
36
|
+
method_option 'run',
|
|
37
|
+
desc: 'Override the script to run',
|
|
38
|
+
aliases: ['r'],
|
|
39
|
+
default: nil
|
|
40
|
+
|
|
41
|
+
method_option 'ssh_key',
|
|
42
|
+
desc: 'Path to a ssh key to import into the docker image',
|
|
43
|
+
aliases: ['s'],
|
|
44
|
+
default: '~/.ssh/id_rsa'
|
|
45
|
+
|
|
46
|
+
def build
|
|
47
|
+
containers = []
|
|
48
|
+
|
|
49
|
+
setup_docker
|
|
50
|
+
|
|
51
|
+
## Config ##
|
|
52
|
+
banner 'Config'
|
|
53
|
+
build = Build::Config.load_file CONFIG_FILE
|
|
54
|
+
exit 1 if build.nil?
|
|
55
|
+
export_dir = options.export_dir || File.dirname(build.export_dir)
|
|
56
|
+
build.run = options.run if options.run
|
|
57
|
+
name = CIDE::Docker.id options.name
|
|
58
|
+
tag = "cide/#{name}"
|
|
59
|
+
say_status :config, build.inspect
|
|
60
|
+
|
|
61
|
+
## Build ##
|
|
62
|
+
banner 'Build'
|
|
63
|
+
if build.use_ssh
|
|
64
|
+
unless File.exist?(options.ssh_key)
|
|
65
|
+
fail ArgumentError, "SSH key #{options.ssh_key} not found"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
create_tmp_file SSH_CONFIG_FILE, File.read(SSH_CONFIG_PATH)
|
|
69
|
+
create_tmp_file TEMP_SSH_KEY, File.read(build.ssh_key)
|
|
70
|
+
end
|
|
71
|
+
create_tmp_file DOCKERFILE, build.to_dockerfile
|
|
72
|
+
docker :build, '--force-rm', '--pull', '-f', DOCKERFILE, '-t', tag, '.'
|
|
73
|
+
|
|
74
|
+
## CI ##
|
|
75
|
+
banner 'Run'
|
|
76
|
+
build.links.each do |link|
|
|
77
|
+
args = ['--detach']
|
|
78
|
+
link.env.each_pair do |key, value|
|
|
79
|
+
args.push('--env', [key, value].join('='))
|
|
80
|
+
end
|
|
81
|
+
args << link.image
|
|
82
|
+
args << link.run if link.run
|
|
83
|
+
link.id = docker(:run, *args, capture: true).strip
|
|
84
|
+
containers << link.id
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
run_options = ['--detach']
|
|
88
|
+
|
|
89
|
+
build.forward_env.each do |env|
|
|
90
|
+
run_options.push '--env', [env, ENV[env]].join('=')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
build.links.each do |link|
|
|
94
|
+
run_options.push '--link', [link.id, link.name].join(':')
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
run_options.push tag
|
|
98
|
+
run_options.push build.run
|
|
99
|
+
|
|
100
|
+
id = docker(:run, *run_options, capture: true).strip
|
|
101
|
+
containers << id
|
|
102
|
+
docker(:attach, id)
|
|
103
|
+
|
|
104
|
+
## Export ##
|
|
105
|
+
return unless options.export
|
|
106
|
+
banner 'Export'
|
|
107
|
+
fail 'export flag set but no export_dir given' if build.export_dir.nil?
|
|
108
|
+
|
|
109
|
+
guest_export_dir = File.expand_path(build.export_dir, CIDE_SRC_DIR)
|
|
110
|
+
host_export_dir = File.expand_path(export_dir, Dir.pwd)
|
|
111
|
+
docker :cp, [id, guest_export_dir].join(':'), host_export_dir
|
|
112
|
+
rescue Docker::Error => ex
|
|
113
|
+
exit ex.exitstatus
|
|
114
|
+
ensure
|
|
115
|
+
# Shutdown old containers
|
|
116
|
+
unless containers.empty?
|
|
117
|
+
docker :rm, '--force', *containers.reverse,
|
|
118
|
+
verbose: false,
|
|
119
|
+
capture: true
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
desc 'clean', 'Removes old containers'
|
|
124
|
+
method_option 'days',
|
|
125
|
+
desc: 'Number of days to keep the images',
|
|
126
|
+
default: 7,
|
|
127
|
+
type: :numeric
|
|
128
|
+
method_option 'count',
|
|
129
|
+
desc: 'Maximum number of images to keep',
|
|
130
|
+
default: 10,
|
|
131
|
+
type: :numeric
|
|
132
|
+
def clean
|
|
133
|
+
setup_docker
|
|
134
|
+
|
|
135
|
+
days_to_keep = options[:days]
|
|
136
|
+
max_images = options[:count]
|
|
137
|
+
|
|
138
|
+
x = docker('images', '--no-trunc', capture: true)
|
|
139
|
+
iter = x.lines.each
|
|
140
|
+
iter.next
|
|
141
|
+
cide_image_ids = iter
|
|
142
|
+
.map { |line| line.split(/\s+/) }
|
|
143
|
+
.select { |line| line[0] =~ %r{^cide/} || line[0] == '<none>' }
|
|
144
|
+
.map { |line| line[2] }
|
|
145
|
+
|
|
146
|
+
if cide_image_ids.empty?
|
|
147
|
+
puts 'No images found to be cleaned'
|
|
148
|
+
return
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
x = docker('inspect', *cide_image_ids, capture: true)
|
|
152
|
+
cide_images = JSON.parse(x.strip)
|
|
153
|
+
.each { |image| image['Created'] = Time.iso8601(image['Created']) }
|
|
154
|
+
.sort { |a, b| a['Created'] <=> b['Created'] }
|
|
155
|
+
|
|
156
|
+
if cide_images.size > max_images
|
|
157
|
+
old_cide_images = cide_images[0..-max_images]
|
|
158
|
+
.map { |image| image['Id'] }
|
|
159
|
+
else
|
|
160
|
+
old_times = Time.now - (days_to_keep * 24 * 60 * 60)
|
|
161
|
+
old_cide_images = cide_images
|
|
162
|
+
.select { |image| image['Created'] < old_times }
|
|
163
|
+
.map { |image| image['Id'] }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
if old_cide_images.empty?
|
|
167
|
+
puts 'No images found to be cleaned'
|
|
168
|
+
return
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
docker('rmi', *old_cide_images)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
desc 'init', "Creates a blank #{CONFIG_FILE} into the project"
|
|
175
|
+
def init
|
|
176
|
+
puts "Creating #{CONFIG_FILE} with default values"
|
|
177
|
+
create_file CONFIG_FILE, File.read(DEFAULT_CIDEFILE)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
private
|
|
181
|
+
|
|
182
|
+
def create_tmp_file(destination, *args, &block)
|
|
183
|
+
create_file(destination, *args, &block)
|
|
184
|
+
at_exit do
|
|
185
|
+
remove_file(destination, verbose: false)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
LINE_SIZE = 78.0
|
|
190
|
+
def banner(text)
|
|
191
|
+
pad = (LINE_SIZE - text.size - 4) / 2
|
|
192
|
+
puts '=' * pad.floor + "[ #{text} ]" + '=' * pad.ceil
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module CIDE
|
|
2
|
+
dir = File.expand_path('..', __FILE__)
|
|
3
|
+
DOCKERFILE = 'Dockerfile.cide'
|
|
4
|
+
DEFAULT_CIDEFILE = File.join(dir, 'default_cide.yml')
|
|
5
|
+
TEMP_SSH_KEY = 'id_rsa.tmp'
|
|
6
|
+
SSH_CONFIG_FILE = 'ssh_config'
|
|
7
|
+
SSH_CONFIG_PATH = File.join(dir, SSH_CONFIG_FILE)
|
|
8
|
+
CONFIG_FILE = '.cide.yml'
|
|
9
|
+
CIDE_DIR = '/cide'
|
|
10
|
+
CIDE_SRC_DIR = File.join(CIDE_DIR, 'src')
|
|
11
|
+
CIDE_SSH_DIR = File.join(CIDE_DIR, '.ssh')
|
|
12
|
+
end
|
data/lib/cide/docker.rb
CHANGED
|
@@ -17,6 +17,8 @@ module CIDE
|
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
class VersionError < StandardError; end
|
|
21
|
+
|
|
20
22
|
def docker(*args, **opts)
|
|
21
23
|
setup_docker
|
|
22
24
|
|
|
@@ -42,6 +44,13 @@ module CIDE
|
|
|
42
44
|
.lines
|
|
43
45
|
.grep(/export (\w+)=(.*)/) { ENV[$1] = $2.strip }
|
|
44
46
|
end
|
|
47
|
+
|
|
48
|
+
# Check docker version
|
|
49
|
+
unless `docker version 2>/dev/null` =~ /Client version: ([^\s]+)/
|
|
50
|
+
fail VersionError, 'Unknown docker version'
|
|
51
|
+
end
|
|
52
|
+
fail VersionError, "Docker version #{$1} too old" if $1 < '1.5.0'
|
|
53
|
+
|
|
45
54
|
true
|
|
46
55
|
)
|
|
47
56
|
end
|
|
@@ -26,16 +26,16 @@ RUN chown -R cide:cide <%= CIDE_DIR %>
|
|
|
26
26
|
|
|
27
27
|
# Before
|
|
28
28
|
|
|
29
|
-
<% if before
|
|
30
|
-
<% before
|
|
29
|
+
<% if before -%>
|
|
30
|
+
<% before.forward_env.each do |key| -%>
|
|
31
31
|
ENV <%= key %> <%= ENV[key] %>
|
|
32
32
|
<% end %>
|
|
33
|
-
<% before
|
|
33
|
+
<% before.add.each do |file| -%>
|
|
34
34
|
ADD <%= file %> <%= File.expand_path(file, CIDE_SRC_DIR) %>
|
|
35
35
|
<% end %>
|
|
36
36
|
RUN chown -R cide:cide <%= CIDE_DIR %>
|
|
37
37
|
USER cide
|
|
38
|
-
RUN <%= before
|
|
38
|
+
RUN <%= before.run %>
|
|
39
39
|
USER root
|
|
40
40
|
<% end -%>
|
|
41
41
|
|
|
@@ -43,14 +43,3 @@ USER root
|
|
|
43
43
|
|
|
44
44
|
ADD . <%= CIDE_SRC_DIR %>
|
|
45
45
|
RUN chown -R cide:cide <%= CIDE_DIR %>
|
|
46
|
-
|
|
47
|
-
# ENV
|
|
48
|
-
|
|
49
|
-
<% forward_env.to_a.each do |key| -%>
|
|
50
|
-
ENV <%= key %> <%= ENV[key] %>
|
|
51
|
-
<% end %>
|
|
52
|
-
|
|
53
|
-
# Test !
|
|
54
|
-
|
|
55
|
-
USER cide
|
|
56
|
-
RUN <%= run %>
|
|
File without changes
|
data/lib/cide.rb
CHANGED
|
@@ -1,229 +1,7 @@
|
|
|
1
|
-
require 'erb'
|
|
2
|
-
require 'json'
|
|
3
|
-
require 'optparse'
|
|
4
|
-
require 'time'
|
|
5
|
-
require 'yaml'
|
|
6
|
-
|
|
7
|
-
require 'thor'
|
|
8
|
-
|
|
9
|
-
require 'cide/docker'
|
|
10
|
-
|
|
11
1
|
# CIDE is a Continuous Integration Docker Environment runner
|
|
12
2
|
#
|
|
13
3
|
# The juicy bits are defined in CIDE::CLI
|
|
14
4
|
module CIDE
|
|
15
|
-
DIR = File.expand_path('..', __FILE__)
|
|
16
|
-
DOCKERFILE = 'Dockerfile'
|
|
17
|
-
TEMP_SSH_KEY = 'id_rsa.tmp'
|
|
18
|
-
DOCKERFILE_TEMPLATE = File.join(DIR, 'cide_template.erb')
|
|
19
|
-
SSH_CONFIG_FILE = 'ssh_config'
|
|
20
|
-
SSH_CONFIG_PATH = File.join(DIR, SSH_CONFIG_FILE)
|
|
21
|
-
CONFIG_FILE = '.cide.yml'
|
|
22
|
-
|
|
23
|
-
CIDE_DIR = '/cide'
|
|
24
|
-
CIDE_SRC_DIR = File.join(CIDE_DIR, '/src')
|
|
25
|
-
CIDE_SSH_DIR = File.join(CIDE_DIR, '/.ssh')
|
|
26
|
-
|
|
27
|
-
def self.struct(opts = {}, &block)
|
|
28
|
-
Class.new(Struct.new(*opts.keys), &block).new(*opts.values)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
DefaultConfig = struct(
|
|
32
|
-
name: nil,
|
|
33
|
-
from: 'ubuntu',
|
|
34
|
-
as_root: [],
|
|
35
|
-
forward_env: [],
|
|
36
|
-
before: {},
|
|
37
|
-
export: false,
|
|
38
|
-
export_dir: './artifacts',
|
|
39
|
-
host_export_dir: nil,
|
|
40
|
-
run: 'script/ci',
|
|
41
|
-
use_ssh: false,
|
|
42
|
-
ssh_key: '~/.ssh/id_rsa',
|
|
43
|
-
) do
|
|
44
|
-
|
|
45
|
-
alias_method :image=, :from=
|
|
46
|
-
alias_method :command=, :run=
|
|
47
|
-
|
|
48
|
-
def name=(str)
|
|
49
|
-
super CIDE::Docker.id(str)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def ssh_key_path
|
|
53
|
-
File.expand_path(ssh_key)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def to_dockerfile
|
|
57
|
-
ERB.new(File.read(DOCKERFILE_TEMPLATE), nil, '<>-').result(binding)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def merge!(opts = {})
|
|
61
|
-
opts.each_pair { |k, v| public_send("#{k}=", v) }
|
|
62
|
-
self
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def merge(opts = {})
|
|
66
|
-
dup.merge!(opts)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def to_yaml
|
|
70
|
-
members.each_with_object({}) do |k, obj|
|
|
71
|
-
v = self[k]
|
|
72
|
-
obj[k.to_s] = v unless v.nil?
|
|
73
|
-
end.to_yaml
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Command-line option-parsing and execution for cide
|
|
78
|
-
class CLI < Thor
|
|
79
|
-
include CIDE::Docker
|
|
80
|
-
include Thor::Actions
|
|
81
|
-
add_runtime_options!
|
|
82
|
-
|
|
83
|
-
default_command 'build'
|
|
84
|
-
|
|
85
|
-
desc 'build', 'Builds an image and executes the run script'
|
|
86
|
-
|
|
87
|
-
method_option 'name',
|
|
88
|
-
desc: 'Name of the build',
|
|
89
|
-
aliases: %w(n t),
|
|
90
|
-
default: nil
|
|
91
|
-
|
|
92
|
-
method_option 'host_export_dir',
|
|
93
|
-
desc: 'Output directory on host to put build artefacts in',
|
|
94
|
-
aliases: ['o'],
|
|
95
|
-
default: nil
|
|
96
|
-
|
|
97
|
-
method_option 'export',
|
|
98
|
-
desc: 'Are we expecting to export artifacts',
|
|
99
|
-
type: :boolean,
|
|
100
|
-
default: nil
|
|
101
|
-
|
|
102
|
-
method_option 'run',
|
|
103
|
-
desc: 'The script to run',
|
|
104
|
-
aliases: ['r'],
|
|
105
|
-
default: nil
|
|
106
|
-
|
|
107
|
-
method_option 'ssh_key',
|
|
108
|
-
desc: 'Path to a ssh key to import into the docker image',
|
|
109
|
-
aliases: ['s'],
|
|
110
|
-
default: '~/.ssh/id_rsa'
|
|
111
|
-
|
|
112
|
-
def build
|
|
113
|
-
setup_docker
|
|
114
|
-
|
|
115
|
-
config = DefaultConfig.merge YAML.load_file(CONFIG_FILE)
|
|
116
|
-
options.each_pair do |k, v|
|
|
117
|
-
config[k] = v unless v.nil?
|
|
118
|
-
end
|
|
119
|
-
config.name ||= File.basename(Dir.pwd)
|
|
120
|
-
config.host_export_dir ||= config.export_dir
|
|
121
|
-
|
|
122
|
-
tag = "cide/#{config.name}"
|
|
123
|
-
|
|
124
|
-
if config.use_ssh
|
|
125
|
-
unless File.exist?(config.ssh_key_path)
|
|
126
|
-
fail MalformattedArgumentError, "SSH key #{config.ssh_key} not found"
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
create_tmp_file SSH_CONFIG_FILE, File.read(SSH_CONFIG_PATH)
|
|
130
|
-
create_tmp_file TEMP_SSH_KEY, File.read(config.ssh_key_path)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
say_status :config, config.to_h
|
|
134
|
-
|
|
135
|
-
create_tmp_file DOCKERFILE, config.to_dockerfile
|
|
136
|
-
|
|
137
|
-
docker :build, '--force-rm', '-t', tag, '.'
|
|
138
|
-
|
|
139
|
-
return unless config.export
|
|
140
|
-
|
|
141
|
-
unless config.export_dir
|
|
142
|
-
fail 'Fail: export flag set but no export dir given'
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
id = docker(:run, '-d', tag, true, capture: true).strip
|
|
146
|
-
begin
|
|
147
|
-
guest_export_dir = File.expand_path(config.export_dir, CIDE_SRC_DIR)
|
|
148
|
-
|
|
149
|
-
host_export_dir = File.expand_path(
|
|
150
|
-
config.host_export_dir || config.export_dir,
|
|
151
|
-
Dir.pwd,
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
docker :cp, [id, guest_export_dir].join(':'), host_export_dir
|
|
155
|
-
|
|
156
|
-
ensure
|
|
157
|
-
docker :rm, '-f', id
|
|
158
|
-
end
|
|
159
|
-
rescue Docker::Error => ex
|
|
160
|
-
exit ex.exitstatus
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
desc 'clean', 'Removes old containers'
|
|
164
|
-
method_option 'days',
|
|
165
|
-
desc: 'Number of days to keep the images',
|
|
166
|
-
default: 7,
|
|
167
|
-
type: :numeric
|
|
168
|
-
method_option 'count',
|
|
169
|
-
desc: 'Maximum number of images to keep',
|
|
170
|
-
default: 10,
|
|
171
|
-
type: :numeric
|
|
172
|
-
def clean
|
|
173
|
-
setup_docker
|
|
174
|
-
|
|
175
|
-
days_to_keep = options[:days]
|
|
176
|
-
max_images = options[:count]
|
|
177
|
-
|
|
178
|
-
x = docker('images', '--no-trunc', capture: true)
|
|
179
|
-
iter = x.lines.each
|
|
180
|
-
iter.next
|
|
181
|
-
cide_image_ids = iter
|
|
182
|
-
.map { |line| line.split(/\s+/) }
|
|
183
|
-
.select { |line| line[0] =~ /^cide\// || line[0] == '<none>' }
|
|
184
|
-
.map { |line| line[2] }
|
|
185
|
-
|
|
186
|
-
if cide_image_ids.empty?
|
|
187
|
-
puts 'No images found to be cleaned'
|
|
188
|
-
return
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
x = docker('inspect', *cide_image_ids, capture: true)
|
|
192
|
-
cide_images = JSON.parse(x.strip)
|
|
193
|
-
.each { |image| image['Created'] = Time.iso8601(image['Created']) }
|
|
194
|
-
.sort { |a, b| a['Created'] <=> b['Created'] }
|
|
195
|
-
|
|
196
|
-
if cide_images.size > max_images
|
|
197
|
-
old_cide_images = cide_images[0..-max_images]
|
|
198
|
-
.map { |image| image['Id'] }
|
|
199
|
-
else
|
|
200
|
-
old_times = Time.now - (days_to_keep * 24 * 60 * 60)
|
|
201
|
-
old_cide_images = cide_images
|
|
202
|
-
.select { |image| image['Created'] < old_times }
|
|
203
|
-
.map { |image| image['Id'] }
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
if old_cide_images.empty?
|
|
207
|
-
puts 'No images found to be cleaned'
|
|
208
|
-
return
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
docker('rmi', *old_cide_images)
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
desc 'init', "Creates a blank #{CONFIG_FILE} into the project"
|
|
215
|
-
def init
|
|
216
|
-
puts "Creating #{CONFIG_FILE} with default values"
|
|
217
|
-
create_file CONFIG_FILE, DefaultConfig.to_yaml
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
private
|
|
221
|
-
|
|
222
|
-
def create_tmp_file(destination, *args, &block)
|
|
223
|
-
create_file(destination, *args, &block)
|
|
224
|
-
at_exit do
|
|
225
|
-
remove_file(destination, verbose: false)
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
end
|
|
229
5
|
end
|
|
6
|
+
|
|
7
|
+
require 'cide/cli'
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
require "cide/build"
|
|
2
|
+
require "stringio"
|
|
3
|
+
require "ostruct"
|
|
4
|
+
require "active_support/json"
|
|
5
|
+
|
|
6
|
+
describe "CIDE::Build::Config::Loader" do
|
|
7
|
+
before do
|
|
8
|
+
@config = CIDE::Build::Config.new
|
|
9
|
+
@loader = CIDE::Build::ConfigLoader.new(@config)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
default_config = {
|
|
13
|
+
"from" => "ubuntu",
|
|
14
|
+
"as_root" => [],
|
|
15
|
+
"use_ssh" => false,
|
|
16
|
+
"before" => nil,
|
|
17
|
+
"forward_env" => [],
|
|
18
|
+
"export_dir" => nil,
|
|
19
|
+
"links" => [],
|
|
20
|
+
"run" => "script/ci",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
it "works - empty config" do
|
|
24
|
+
@loader.load({})
|
|
25
|
+
expect(@config.as_json).to eq(default_config)
|
|
26
|
+
expect(@config.warnings).to eq([])
|
|
27
|
+
expect(@config.errors).to eq([])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "works2 - full config" do
|
|
31
|
+
full_config = {
|
|
32
|
+
"from" => "god",
|
|
33
|
+
"as_root" => ["one", "two"],
|
|
34
|
+
"use_ssh" => true,
|
|
35
|
+
"before" => {
|
|
36
|
+
"add" => ["zzz", "yyy"],
|
|
37
|
+
"forward_env" => ["HOME"],
|
|
38
|
+
"run" => ["a", "b"],
|
|
39
|
+
},
|
|
40
|
+
"forward_env" => ["PWD"],
|
|
41
|
+
"links" => [
|
|
42
|
+
{"name" => "redis", "image" => "redis2:foo", "env" => {"HOME" => "/"}, "run" => "redis-server"}
|
|
43
|
+
],
|
|
44
|
+
"export_dir" => "./artifacts",
|
|
45
|
+
"run" => "do/something",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@loader.load(full_config)
|
|
49
|
+
|
|
50
|
+
expect(@config.as_json).to eq(default_config.merge(full_config))
|
|
51
|
+
expect(@config.warnings).to eq([])
|
|
52
|
+
expect(@config.errors).to eq([])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "coerces things around" do
|
|
56
|
+
@loader.load(
|
|
57
|
+
as_root: "xxxxx",
|
|
58
|
+
before: :zzzzz,
|
|
59
|
+
links: ["mysql", {image: "redis", env: {PATH: "/bin"}}, nil],
|
|
60
|
+
forward_env: ["HOME", nil, 555]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
expect(@config.to_h.as_json).to eq(default_config.merge(
|
|
64
|
+
"as_root" => ["xxxxx"],
|
|
65
|
+
"before" => {
|
|
66
|
+
"add" => [],
|
|
67
|
+
"forward_env" => [],
|
|
68
|
+
"run" => ["zzzzz"],
|
|
69
|
+
},
|
|
70
|
+
"links" => [
|
|
71
|
+
{"name" => "mysql", "image" => "mysql", "run" => nil, "env" => {}},
|
|
72
|
+
{"name" => "redis", "image" => "redis", "run" => nil, "env" => {"PATH" => "/bin"}},
|
|
73
|
+
],
|
|
74
|
+
"forward_env" => ["HOME", "555"],
|
|
75
|
+
))
|
|
76
|
+
expect(@config.warnings).to eq([])
|
|
77
|
+
expect(@config.errors).to eq([])
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "notifies deprecations" do
|
|
81
|
+
@loader.load(
|
|
82
|
+
image: "foo",
|
|
83
|
+
command: "lol",
|
|
84
|
+
zzz: 4,
|
|
85
|
+
)
|
|
86
|
+
expect(@config.as_json).to eq(default_config.merge(
|
|
87
|
+
"from" => "foo",
|
|
88
|
+
"run" => "lol",
|
|
89
|
+
))
|
|
90
|
+
expect(@config.warnings).to eq([
|
|
91
|
+
"image is deprecated. use 'from' instead.",
|
|
92
|
+
"command is deprecated. use 'run' instead.",
|
|
93
|
+
"Unknown key zzz",
|
|
94
|
+
])
|
|
95
|
+
expect(@config.errors).to eq([])
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "reports type errors" do
|
|
99
|
+
@loader.load(
|
|
100
|
+
as_root: ["aaa", Time.now],
|
|
101
|
+
forward_env: {},
|
|
102
|
+
links: {},
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
expect(@config.to_h.as_json).to eq(default_config.merge(
|
|
106
|
+
"as_root" => ["aaa", ""],
|
|
107
|
+
))
|
|
108
|
+
expect(@config.warnings).to eq([])
|
|
109
|
+
expect(@config.errors).to eq([
|
|
110
|
+
"expected as_root[1] to be a string but got a Time",
|
|
111
|
+
"expected forward_env to be a array of string, string or nil but got a Hash",
|
|
112
|
+
"expected links to be a expected hash to either declare the name or image but got a Hash",
|
|
113
|
+
])
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
metadata
CHANGED
|
@@ -1,23 +1,51 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cide
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- zimbatm
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2015-
|
|
11
|
+
date: 2015-04-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: thor
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.19.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.19.0
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: virtus
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 1.0.0
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 1.0.0
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
|
16
44
|
requirements:
|
|
17
45
|
- - ">="
|
|
18
46
|
- !ruby/object:Gem::Version
|
|
19
47
|
version: '0'
|
|
20
|
-
type: :
|
|
48
|
+
type: :development
|
|
21
49
|
prerelease: false
|
|
22
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
51
|
requirements:
|
|
@@ -25,7 +53,7 @@ dependencies:
|
|
|
25
53
|
- !ruby/object:Gem::Version
|
|
26
54
|
version: '0'
|
|
27
55
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
56
|
+
name: rubocop
|
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
|
30
58
|
requirements:
|
|
31
59
|
- - ">="
|
|
@@ -39,7 +67,21 @@ dependencies:
|
|
|
39
67
|
- !ruby/object:Gem::Version
|
|
40
68
|
version: '0'
|
|
41
69
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
70
|
+
name: rspec
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: activesupport
|
|
43
85
|
requirement: !ruby/object:Gem::Requirement
|
|
44
86
|
requirements:
|
|
45
87
|
- - ">="
|
|
@@ -63,7 +105,9 @@ extensions: []
|
|
|
63
105
|
extra_rdoc_files: []
|
|
64
106
|
files:
|
|
65
107
|
- ".cide.yml"
|
|
108
|
+
- ".editorconfig"
|
|
66
109
|
- ".gitignore"
|
|
110
|
+
- ".rspec"
|
|
67
111
|
- ".rubocop.yml"
|
|
68
112
|
- ".travis.yml"
|
|
69
113
|
- CHANGELOG.md
|
|
@@ -76,9 +120,17 @@ files:
|
|
|
76
120
|
- cide
|
|
77
121
|
- cide.gemspec
|
|
78
122
|
- lib/cide.rb
|
|
123
|
+
- lib/cide/build.rb
|
|
124
|
+
- lib/cide/build/config.rb
|
|
125
|
+
- lib/cide/build/config_loader.rb
|
|
126
|
+
- lib/cide/cli.rb
|
|
127
|
+
- lib/cide/constants.rb
|
|
128
|
+
- lib/cide/default_cide.yml
|
|
79
129
|
- lib/cide/docker.rb
|
|
80
|
-
- lib/
|
|
81
|
-
- lib/ssh_config
|
|
130
|
+
- lib/cide/dockerfile_template.erb
|
|
131
|
+
- lib/cide/ssh_config
|
|
132
|
+
- spec/build_config_loader_spec.rb
|
|
133
|
+
- spec/spec_helper.rb
|
|
82
134
|
homepage: https://github.com/zimbatm/cide
|
|
83
135
|
licenses:
|
|
84
136
|
- MIT
|
|
@@ -99,8 +151,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
99
151
|
version: '0'
|
|
100
152
|
requirements: []
|
|
101
153
|
rubyforge_project:
|
|
102
|
-
rubygems_version: 2.4.
|
|
154
|
+
rubygems_version: 2.4.6
|
|
103
155
|
signing_key:
|
|
104
156
|
specification_version: 4
|
|
105
157
|
summary: CI docker runner
|
|
106
|
-
test_files:
|
|
158
|
+
test_files:
|
|
159
|
+
- spec/build_config_loader_spec.rb
|
|
160
|
+
- spec/spec_helper.rb
|