docker_brick 0.0.1

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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.project +17 -0
  4. data/Gemfile +11 -0
  5. data/LICENSE.txt +24 -0
  6. data/README.md +29 -0
  7. data/Rakefile +2 -0
  8. data/bin/brick +11 -0
  9. data/docker_brick.gemspec +29 -0
  10. data/lib/brick/application.rb +35 -0
  11. data/lib/brick/cli/build.rb +28 -0
  12. data/lib/brick/cli/core/subcommand_loader.rb +39 -0
  13. data/lib/brick/cli/help.rb +16 -0
  14. data/lib/brick/cli/project_new.rb +29 -0
  15. data/lib/brick/cli/run.rb +113 -0
  16. data/lib/brick/cli/service_new.rb +44 -0
  17. data/lib/brick/cli/up.rb +46 -0
  18. data/lib/brick/cli.rb +230 -0
  19. data/lib/brick/cli__validator.rb +28 -0
  20. data/lib/brick/config.rb +9 -0
  21. data/lib/brick/docker/docker_client.rb +160 -0
  22. data/lib/brick/generators/new_project_generator/templates/fig.yml +0 -0
  23. data/lib/brick/generators/new_project_generator.rb +51 -0
  24. data/lib/brick/generators/new_service_generator.rb +24 -0
  25. data/lib/brick/generators.rb +10 -0
  26. data/lib/brick/mixin/aliasing.rb +24 -0
  27. data/lib/brick/mixin/colors.rb +24 -0
  28. data/lib/brick/mixin/convert_to_class_name.rb +52 -0
  29. data/lib/brick/mixin/docker_support.rb +253 -0
  30. data/lib/brick/mixin/yaml_helper.rb +18 -0
  31. data/lib/brick/mixin.rb +8 -0
  32. data/lib/brick/models/project.rb +139 -0
  33. data/lib/brick/models/service.rb +276 -0
  34. data/lib/brick/models.rb +13 -0
  35. data/lib/brick/monkey_patches/cli.rb +24 -0
  36. data/lib/brick/monkey_patches/connection.rb +15 -0
  37. data/lib/brick/monkey_patches/docker_container.rb +28 -0
  38. data/lib/brick/monkey_patches/hash.rb +16 -0
  39. data/lib/brick/monkey_patches.rb +5 -0
  40. data/lib/brick/version.rb +4 -0
  41. data/lib/brick.rb +34 -0
  42. data/spec/brick_update.sh +7 -0
  43. data/spec/integration/brick/models/project_spec.rb +70 -0
  44. data/spec/projects/nc_test/fig.yml +14 -0
  45. data/spec/projects/nc_test/ncserver/Dockerfile +7 -0
  46. data/spec/projects/rails/myapp/Dockerfile +7 -0
  47. data/spec/projects/rails/myapp/Gemfile +2 -0
  48. data/spec/projects/rails/myapp/fig.yml +13 -0
  49. data/spec/shell/brick_update.sh +7 -0
  50. data/spec/spec_helper.rb +19 -0
  51. data/spec/unit/brick/mixin/docker_support.yml +33 -0
  52. data/spec/unit/brick/mixin/docker_support_spec.rb +66 -0
  53. data/spec/unit/brick/models/dockerfile/Dockerfile +7 -0
  54. data/spec/unit/brick/models/fig_build.yml +3 -0
  55. data/spec/unit/brick/models/fig_completed.yml +41 -0
  56. data/spec/unit/brick/models/fig_dependency.yml +19 -0
  57. data/spec/unit/brick/models/fig_single.yml +6 -0
  58. data/spec/unit/brick/models/fig_volumes.yml +10 -0
  59. data/spec/unit/brick/models/fig_volumes_from.yml +19 -0
  60. data/spec/unit/brick/models/project_spec.rb +70 -0
  61. data/spec/unit/brick/models/service_spec.rb +80 -0
  62. metadata +231 -0
data/lib/brick/cli.rb ADDED
@@ -0,0 +1,230 @@
1
+ require 'mixlib/cli'
2
+ require 'brick/application'
3
+ require 'brick/monkey_patches/cli'
4
+ module Brick
5
+ class CLI
6
+
7
+ extend Brick::Mixin::ConvertToClassName
8
+ include Mixlib::CLI
9
+ #include Application
10
+
11
+
12
+ def self.logger
13
+ @@logger ||= Logger.new(STDOUT)
14
+ @@logger.level = Logger::INFO
15
+ @@logger.formatter = proc do |severity, datetime, progname, msg|
16
+ "#{msg}\n"
17
+ end
18
+ @@logger
19
+ end
20
+
21
+
22
+ def self.inherited(subclass)
23
+ unless subclass.unnamed?
24
+ subcommands[subclass.snake_case_name] = subclass
25
+ end
26
+ end
27
+
28
+ def self.unnamed?
29
+ name.nil? || name.empty?
30
+ end
31
+
32
+ # Run command line for the given +args+ (ARGV), adding +options+ to the list of
33
+ # CLI options that the subcommand knows how to handle.
34
+ # ===Arguments
35
+ # args::: usually ARGV
36
+ # options::: A Mixlib::CLI option parser hash. These +options+ are how
37
+ # subcommands know about global knife CLI options
38
+ def self.run(args, options={})
39
+ #configure brick for common attributes
40
+ #common_optparser.configure_brick
41
+ common_optparser.opt_parser.banner="Usage: brick SUBCOMMAND (options)"
42
+ CLI_Validator::validate
43
+ #logger.info "begin to run the comand #{args}"
44
+ load_commands
45
+ subcommand_class = subcommand_class_from(args)
46
+ subcommand_class.options = common_optparser.class.options.merge!(subcommand_class.options)
47
+ subcommand_class.load_deps
48
+ instance = subcommand_class.new(args)
49
+ #instance.configure_brick
50
+ instance.run_with_pretty_exceptions
51
+ end
52
+
53
+ def self.common_optparser
54
+ @@common_optparser ||= Application.new
55
+ end
56
+
57
+ def self.subcommand_class_from(args)
58
+ command_words = args.select {|arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ }
59
+
60
+ subcommand_class = nil
61
+
62
+ while ( !subcommand_class ) && ( !command_words.empty? )
63
+ snake_case_class_name = command_words.join("_")
64
+ unless subcommand_class = subcommands[snake_case_class_name]
65
+ command_words.pop
66
+ end
67
+ end
68
+ # see if we got the command as e.g., brick model create
69
+ subcommand_class ||= subcommands[args.first.gsub('-', '_')]
70
+ subcommand_class || subcommand_not_found!(args)
71
+ end
72
+
73
+ def self.subcommands
74
+ @@subcommands ||= {}
75
+ end
76
+
77
+ def self.subcommand_not_found!(args)
78
+ logger.fatal("Cannot find sub command for: '#{args.join(' ')}'")
79
+
80
+
81
+ list_commands
82
+
83
+
84
+ exit 10
85
+ end
86
+
87
+ def self.list_parameters
88
+ puts common_optparser.opt_parser.to_s
89
+ puts ""
90
+ end
91
+
92
+ # is given, only subcommands in that category are shown
93
+ def self.list_commands(preferred_category=nil)
94
+ load_commands
95
+
96
+ category_desc = preferred_category ? preferred_category + " " : ''
97
+ logger.info "Available #{category_desc}subcommands: (for details, brick SUB-COMMAND --help)\n\n"
98
+
99
+ if preferred_category && subcommands_by_category.key?(preferred_category)
100
+ commands_to_show = {preferred_category => subcommands_by_category[preferred_category]}
101
+ else
102
+ commands_to_show = subcommands_by_category
103
+ end
104
+
105
+ commands_to_show.sort.each do |category, commands|
106
+ next if category =~ /deprecated/i
107
+ logger.info "** #{category.upcase} COMMANDS **"
108
+ commands.sort.each do |command|
109
+ logger.info subcommands[command].banner if subcommands[command]
110
+ end
111
+ logger.info
112
+ end
113
+ end
114
+
115
+ def self.subcommands_by_category
116
+ unless @subcommands_by_category
117
+ @subcommands_by_category = Hash.new { |hash, key| hash[key] = [] }
118
+ subcommands.each do |snake_cased, klass|
119
+ @subcommands_by_category[klass.subcommand_category] << snake_cased
120
+ end
121
+ end
122
+ @subcommands_by_category
123
+ end
124
+
125
+ def self.snake_case_name
126
+ convert_to_snake_case(name.split('::').last) unless unnamed?
127
+ end
128
+
129
+ def self.load_commands
130
+ @commands_loaded ||= subcommand_loader.load_commands
131
+ end
132
+
133
+
134
+ def self.subcommand_loader
135
+ @subcommand_loader ||= CLI::SubcommandLoader.new
136
+ end
137
+
138
+ #Add catergory
139
+ def self.category(new_category)
140
+ @category = new_category
141
+ end
142
+
143
+ def self.subcommand_category
144
+ @category || snake_case_name.split('_').first unless unnamed?
145
+ end
146
+
147
+ def self.dependency_loaders
148
+ @dependency_loaders ||= []
149
+ end
150
+
151
+ def self.deps(&block)
152
+ dependency_loaders << block
153
+ end
154
+
155
+ def self.load_deps
156
+ dependency_loaders.each do |dep_loader|
157
+ dep_loader.call
158
+ end
159
+ end
160
+
161
+ def initialize(argv=[])
162
+
163
+ super()
164
+ command_name_words = self.class.snake_case_name.split('_')
165
+
166
+ # Mixlib::CLI ignores the embedded name_args
167
+ @cmd_args = ARGV.dup - command_name_words
168
+
169
+ @cmd_args = parse_options @cmd_args
170
+ @cmd_args.delete(command_name_words.join('-'))
171
+ #@name_args.reject! { |name_arg| command_name_words.delete(name_arg) }
172
+
173
+
174
+ Brick::Config.merge!(config)
175
+
176
+ project_name = ::Brick::Config[:project]
177
+
178
+ config_file = ::Brick::Config[:config_file]
179
+
180
+ project_dir = File.dirname(config_file)
181
+
182
+ ::Brick::Config[:project_dir] = project_dir
183
+ =begin
184
+ if config[:help]
185
+ logger.info opt_parser
186
+ exit 1
187
+ end
188
+ =end
189
+
190
+ end
191
+
192
+
193
+ def run_with_pretty_exceptions
194
+ unless self.respond_to?(:run)
195
+ logger.error "You need to add a #run method to your brick command before you can use it"
196
+ end
197
+ run
198
+ rescue Exception => e
199
+ raise
200
+ humanize_exception(e)
201
+ exit 100
202
+ end
203
+
204
+ def humanize_exception(e)
205
+ case e
206
+ when SystemExit
207
+ raise # make sure exit passes through.
208
+ when Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError
209
+ logger.error "Network Error: #{e.message}"
210
+ logger.info "Check your network settings"
211
+ else
212
+ logger.error "#{e.class.name}: #{e.message}"
213
+ end
214
+ end
215
+
216
+ def show_usage
217
+ puts( self.opt_parser.to_s)
218
+ end
219
+
220
+ # def configure_brick
221
+ # begin
222
+ # parse_options
223
+ # rescue => e
224
+ # ::Brick::CLI::logger.error e.message
225
+ # exit 1
226
+ # end
227
+ # Brick::Config.merge!(config)
228
+ # end
229
+ end
230
+ end
@@ -0,0 +1,28 @@
1
+ module Brick
2
+ class CLI_Validator
3
+ NO_COMMAND_GIVEN = "You need to pass a sub-command (e.g., brick SUB-COMMAND)\n"
4
+
5
+ def self.validate
6
+ if no_command_given?
7
+ print_help_and_exit(1, NO_COMMAND_GIVEN)
8
+ end
9
+
10
+ end
11
+
12
+ def self.no_subcommand_given?
13
+ ARGV[0] =~ /^-/
14
+ end
15
+
16
+ def self.no_command_given?
17
+ ARGV.empty?
18
+ end
19
+
20
+ def self.print_help_and_exit(exitcode=1, fatal_message=nil)
21
+ Brick::CLI::logger.error(fatal_message) if fatal_message
22
+ Brick::CLI.list_parameters
23
+ Brick::CLI.list_commands
24
+ exit exitcode
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ require 'mixlib/cli'
2
+ require 'mixlib/config'
3
+ module Brick
4
+ class Config
5
+ extend ::Mixlib::Config
6
+
7
+
8
+ end
9
+ end
@@ -0,0 +1,160 @@
1
+ require 'monitor'
2
+ require "docker"
3
+ require 'json'
4
+ require 'uri'
5
+ #require 'byebug'
6
+ module Brick
7
+ module Docker
8
+
9
+ end
10
+ end
11
+
12
+ module Brick
13
+ module Docker
14
+ class DockerClient
15
+ include Brick::Mixin::DockerSupport
16
+
17
+ @@default_client = nil
18
+
19
+ @@lock = Monitor.new
20
+
21
+ to_aliase_methods = [:run]
22
+
23
+ attr_accessor :base_url, :connection
24
+
25
+ @@connection_pool = Hash.new
26
+
27
+ @base_url = ENV['DOCKER_URL']
28
+
29
+ def self.connection base_url
30
+
31
+ conn = nil
32
+
33
+ @@lock.synchronize do
34
+ conn ||= @@connection_pool[base_url.to_sym]
35
+
36
+ if(conn.nil?)
37
+ conn = ::Docker::Connection.new(base_url, {})
38
+ @@connection_pool[base_url.to_sym] = @connection
39
+ end
40
+ end
41
+
42
+ conn
43
+ end
44
+
45
+ def initialize(options={})
46
+
47
+ unless(options[:base_url].nil?)
48
+ @base_url = options[:base_url]
49
+ end
50
+
51
+
52
+ @base_url ||= 'unix:///var/run/docker.sock'
53
+
54
+
55
+ @connection= self.class.connection @base_url
56
+
57
+ puts "#{__method__} #{__LINE__} @connection=#{@connection}"
58
+ end
59
+
60
+ def self.default
61
+ @@default_client ||= DockerClient.new
62
+ #puts "client=#{@@default_client}"
63
+ return @@default_client
64
+ end
65
+
66
+ def create config_hash, name=nil
67
+
68
+ docker_hash= create_config(config_hash)
69
+
70
+ docker_hash['name'] = name unless name.nil?
71
+
72
+ begin
73
+ container = ::Docker::Container.create(docker_hash.dup, connection)
74
+ #get full information of the container
75
+ container = ::Docker::Container.get(container.id,connection)
76
+ rescue ::Docker::Error::NotFoundError => exception
77
+ if exception.message.include? 'No such image'
78
+ ::Docker::Image.create({'fromImage'=> config_hash['image']},{}, connection)
79
+ container = ::Docker::Container.create(docker_hash.dup, connection)
80
+ #get full information of the container
81
+ container = ::Docker::Container.get(container.id,connection)
82
+ else
83
+ raise exception
84
+ end
85
+ return container
86
+ end
87
+ end
88
+
89
+
90
+ #if the container is already existed, reuse it
91
+ #if the container is not started, start it
92
+ def run config_hash, name=nil
93
+ #byebug
94
+ container = get_container_by_name name
95
+
96
+
97
+ if container.nil?
98
+ container = create config_hash, name
99
+ else
100
+ Brick::CLI::logger.info "container #{name} has already existed."
101
+ end
102
+
103
+ Brick::CLI::logger.debug "container #{container}."
104
+
105
+
106
+ unless container.is_running?
107
+ container.start(start_config(config_hash))
108
+ else
109
+ Brick::CLI::logger.info "container #{name} is #{container.info["Status"]}"
110
+ end
111
+
112
+ container
113
+ end
114
+
115
+ def get_container_by_name name=nil
116
+
117
+ container = nil
118
+
119
+ unless name.nil?
120
+ container = ::Docker::Container.search_by_name name, connection
121
+ end
122
+
123
+ container.first
124
+
125
+ end
126
+
127
+ def get_container_by_id id
128
+ ::Docker::Container.get(id,connection)
129
+ end
130
+
131
+
132
+ def build_from_dir options={}
133
+
134
+ image_name = options[:image_name]
135
+
136
+ dockerfile_path = options[:build_dir]
137
+
138
+ project_dir = options[:project_dir]
139
+
140
+ no_cache = options[:no_cache]
141
+
142
+ dockerfile_path = determine_dockerfile_path(dockerfile_path, project_dir)
143
+
144
+
145
+ image = ::Docker::Image.build_from_dir(dockerfile_path, {"t"=>image_name, "nocache" =>no_cache }) {
146
+ |chunk| h1 = ::JSON.parse(chunk);
147
+
148
+ value = h1.values[0].to_s;
149
+
150
+ puts(::URI.unescape(value))
151
+
152
+ }
153
+
154
+
155
+ image
156
+ end
157
+
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,51 @@
1
+ require "thor/group"
2
+ require "fileutils"
3
+
4
+ module Brick::Generators
5
+ class NewProjectGenerator < Thor::Group
6
+ include Thor::Actions
7
+
8
+ argument :working_dir
9
+ argument :project_name
10
+
11
+ attr_accessor :project_root
12
+
13
+ def self.source_root
14
+ File.join(File.dirname(__FILE__), "new_project_generator", "templates")
15
+ end
16
+
17
+ def create_root
18
+ self.destination_root = File.expand_path(working_dir)
19
+ empty_directory(project_name)
20
+ self.project_root=File.join(working_dir,project_name)
21
+ FileUtils.cd(project_root)
22
+ end
23
+
24
+ def fig_file
25
+ copy_file("fig.yml","#{project_name}/fig.yml")
26
+ puts "the project is created at #{project_root}"
27
+ end
28
+
29
+ def init_git
30
+ puts "Initializing git repo in #{project_root}"
31
+ FileUtils.cd(project_root)
32
+ git :init
33
+ git :add => "."
34
+ git :commit => "-m 'Initial commit'"
35
+
36
+ end
37
+
38
+ private
39
+
40
+ def git(commands={})
41
+ if commands.is_a?(Symbol)
42
+ `git #{commands}`
43
+ else
44
+ commands.each do |cmd, options|
45
+ `git #{cmd} #{options}`
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,24 @@
1
+ require "thor/group"
2
+
3
+ module Brick::Generators
4
+ class NewServiceGenerator < Thor::Group
5
+ argument :working_dir
6
+
7
+
8
+
9
+ def create_a
10
+ puts "a"
11
+ 'a'
12
+ end
13
+
14
+ def create_b
15
+ puts "b"
16
+ 'b'
17
+ end
18
+
19
+ def create_c
20
+ puts "c"
21
+ 'c'
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ module Brick
2
+ module Generators
3
+
4
+ end
5
+ end
6
+
7
+
8
+ require 'brick/generators/new_project_generator'
9
+
10
+ require 'brick/generators/new_service_generator'
@@ -0,0 +1,24 @@
1
+ module Brick::Mixin::Aliasing
2
+
3
+ def alias_method_chain(target, feature)
4
+ # Strip out punctuation on predicates or bang methods since
5
+ # e.g. target?_without_feature is not a valid method name.
6
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
7
+ yield(aliased_target, punctuation) if block_given?
8
+
9
+ with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
10
+ without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
11
+
12
+ alias_method without_method, target
13
+ alias_method target, with_method
14
+
15
+ case
16
+ when public_method_defined?(without_method)
17
+ public target
18
+ when protected_method_defined?(without_method)
19
+ protected target
20
+ when private_method_defined?(without_method)
21
+ private target
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ require 'set'
2
+ require 'colorize'
3
+
4
+ module Brick::Mixin::Colors
5
+ COLORS = [:blue, :red, :yellow, :light_red, :green, :light_green, :light_yellow, :black, :light_black, :light_blue, :magenta, :light_magenta, :cyan, :light_cyan, :white, :light_white]
6
+
7
+ @@current = -1
8
+
9
+ def determine_color
10
+ if $stdout.isatty
11
+ @@current = (@@current+1)%COLORS.size
12
+ end
13
+ end
14
+
15
+ def color_generator str
16
+
17
+ if $stdout.isatty
18
+
19
+ str=str.colorize(COLORS[@@current])
20
+ end
21
+
22
+ str
23
+ end
24
+ end
@@ -0,0 +1,52 @@
1
+ module Brick
2
+ module Mixin
3
+ end
4
+ end
5
+
6
+
7
+ module Brick
8
+ module Mixin
9
+ module ConvertToClassName
10
+ extend self
11
+
12
+ def convert_to_class_name(str)
13
+ str = str.dup
14
+ str.gsub!(/[^A-Za-z0-9_]/,'_')
15
+ rname = nil
16
+ regexp = %r{^(.+?)(_(.+))?$}
17
+
18
+ mn = str.match(regexp)
19
+ if mn
20
+ rname = mn[1].capitalize
21
+
22
+ while mn && mn[3]
23
+ mn = mn[3].match(regexp)
24
+ rname << mn[1].capitalize if mn
25
+ end
26
+ end
27
+
28
+ rname
29
+ end
30
+
31
+ def convert_to_snake_case(str, namespace=nil)
32
+ str = str.dup
33
+ str.sub!(/^#{namespace}(\:\:)?/, '') if namespace
34
+ str.gsub!(/[A-Z]/) {|s| "_" + s}
35
+ str.downcase!
36
+ str.sub!(/^\_/, "")
37
+ str
38
+ end
39
+
40
+ def snake_case_basename(str)
41
+ with_namespace = convert_to_snake_case(str)
42
+ with_namespace.split("::").last.sub(/^_/, '')
43
+ end
44
+
45
+ def filename_to_qualified_string(base, filename)
46
+ file_base = File.basename(filename, ".rb")
47
+ base.to_s + (file_base == 'default' ? '' : "_#{file_base}")
48
+ end
49
+
50
+ end
51
+ end
52
+ end