dklet 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9dd62d9e1e833237c0ad745846ebca53657d8d050930b299ad59cc15cee316c2
4
+ data.tar.gz: 41322b580ebeb1dfcd381a325be7b3ccb29ab2315b6e9434db7cffec1ea69926
5
+ SHA512:
6
+ metadata.gz: 2b317d7d44c67c7d8bae36858d86b681f84ebdc5a67c09f7dd2d3f24bf7569648620a432c9cd2916aa80ab7f56a339c14e72423899f59d2f05ec59bb9f75361b
7
+ data.tar.gz: fe49b4cb5e65ede0270e4a978fdecf1ca88258a77516767af54bba9cbd6ede77d1597b585144307b60390a8e09a311476b2cb0545eb8507a11bc7312c74203b3
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ pkg
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.1
5
+ before_install: gem install bundler -v 1.16.2
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at cao7113@hotmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ gemspec
6
+
7
+ gem 'byebug'
8
+
9
+ # https://github.com/hashicorp/vault-ruby
10
+ #gem "vault", "~> 0.12"
data/Gemfile.lock ADDED
@@ -0,0 +1,39 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ dklet (0.1.0)
5
+ thor
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ byebug (10.0.2)
11
+ diff-lcs (1.3)
12
+ rake (10.5.0)
13
+ rspec (3.7.0)
14
+ rspec-core (~> 3.7.0)
15
+ rspec-expectations (~> 3.7.0)
16
+ rspec-mocks (~> 3.7.0)
17
+ rspec-core (3.7.1)
18
+ rspec-support (~> 3.7.0)
19
+ rspec-expectations (3.7.0)
20
+ diff-lcs (>= 1.2.0, < 2.0)
21
+ rspec-support (~> 3.7.0)
22
+ rspec-mocks (3.7.0)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.7.0)
25
+ rspec-support (3.7.1)
26
+ thor (0.20.0)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ bundler (~> 1.16)
33
+ byebug
34
+ dklet!
35
+ rake (~> 10.0)
36
+ rspec (~> 3.0)
37
+
38
+ BUNDLED WITH
39
+ 1.16.2
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Ruijian Cao
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # Dklet
2
+
3
+ A practical dsl for daily container life
4
+
5
+ ## Installation
6
+
7
+ $ gem install dklet
8
+
9
+ ## Development
10
+
11
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
12
+
13
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
14
+
15
+ ## Contributing
16
+
17
+ Bug reports and pull requests are welcome on GitHub at https://github.com/cao7113/dklet. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
18
+
19
+ ## License
20
+
21
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
22
+
23
+ ## Code of Conduct
24
+
25
+ Everyone interacting in the Dklet project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/cao7113/dklet/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "dklet"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/dklet.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "dklet/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "dklet"
7
+ spec.version = Dklet::VERSION
8
+ spec.authors = ["Ruijian Cao"]
9
+ spec.email = ["cao7113@hotmail.com"]
10
+
11
+ spec.summary = %q{practical DSL for daily container life}
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/dailyops/kc/dklet"
14
+ spec.license = "MIT"
15
+
16
+ # Specify which files should be added to the gem when it is released.
17
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
18
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ end
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.16"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "rspec", "~> 3.0"
28
+
29
+ spec.add_dependency "thor" #, "~> 0.20.0"
30
+ end
data/exe/mkdklet ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ require 'dklet'
3
+
4
+ rbfile = ARGV[0]
5
+ abort "rbfile required!" if rbfile.nil?
6
+
7
+ rpath = Pathname(rbfile)
8
+ abort "rbfile #{rpath.to_s} existed!" if rpath.exist?
9
+
10
+ rpath.parent.mkpath
11
+ extra = ARGV[1..-1].join(' ')
12
+
13
+ handler = File.basename(__FILE__)
14
+ tmpl = Dklet.lib_path.join('template/dklet.erb').read
15
+
16
+ erb = ERB.new(tmpl, nil, '%<>')
17
+ result = erb.result(binding)
18
+ rpath.write(result)
19
+ rpath.chmod(0755)
20
+ puts "file: #{rpath} generated by #{handler}!"
21
+ exec "#{ENV['EDITOR'] || 'vi'} #{rpath}" if extra =~ /(-o|--open)/
data/exe/rundklet ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ file = ARGV[0]
4
+ abort "Error: require docklet file!" unless file
5
+ $PROGRAM_NAME = file
6
+ ARGV.shift
7
+
8
+ # Tricks to support env preset vars (high priority before register)
9
+ argv = ARGV
10
+ idx = argv.index('--env') || argv.index('-e')
11
+ if idx
12
+ ENV['APP_ENV'] ||= argv[idx+1]
13
+ end
14
+ idx = argv.index('--release') || argv.index('-r')
15
+ if idx
16
+ ENV['APP_RELEASE'] ||= argv[idx+1]
17
+ end
18
+
19
+ require "dklet"
20
+ extend Dklet::DSL
21
+ load file
22
+
23
+ # load tag-specific features, eg. rails_web
24
+ app_tags.each do |atag|
25
+ #puts "==loading #{atag}"
26
+ require "dklet/app_tags/#{atag}"
27
+ end
28
+
29
+ let_cli_magic_start! # make dockerize fun with ruby
@@ -0,0 +1,35 @@
1
+ custom_commands do
2
+ desc 'rails_boot', 'run rails boot script'
3
+ def rails_boot
4
+ #docker-compose exec website rails db:reset
5
+ system <<~Desc
6
+ docker exec #{ops_container} rails db:create 2>/dev/null
7
+ Desc
8
+ invoke :db_migrate
9
+ end
10
+
11
+ desc 'rails_console', 'run into rails console'
12
+ def rails_console
13
+ system <<~Desc
14
+ docker exec -it #{ops_container} rails console
15
+ Desc
16
+ end
17
+
18
+ desc 'db_migrate', 'run db migrate'
19
+ def db_migrate
20
+ system <<~Desc
21
+ docker exec #{ops_container} rails db:migrate
22
+ Desc
23
+ end
24
+
25
+ desc 'browse', 'open browser'
26
+ def browse(tp = :web)
27
+ domain = fetch("#{tp}_domain".to_sym)
28
+ return unless domain
29
+ doms = domain.split(',')
30
+ system <<~Desc
31
+ open http://#{doms.first}
32
+ Desc
33
+ end
34
+ map 'open' => 'browse'
35
+ end
data/lib/dklet/cli.rb ADDED
@@ -0,0 +1,357 @@
1
+ require "thor"
2
+ class DockletCLI < Thor
3
+ #include Thor::Actions
4
+ default_command :main
5
+
6
+ class_option :debug, type: :boolean, default: false, banner: 'in debug mode, more log'
7
+ class_option :dry, type: :boolean, default: false, banner: 'dry run'
8
+ class_option :quiet, type: :boolean, default: false, banner: 'keep quiet'
9
+ class_option :force, type: :boolean, default: false, banner: 'force do'
10
+ class_option :env, banner: 'app env', aliases: ['-e']
11
+ class_option :release, banner: 'what app release for', aliases: ['-r']
12
+
13
+ desc 'version', 'show dklet version'
14
+ def version
15
+ puts Dklet.version
16
+ end
17
+ map '-v' => "version"
18
+
19
+ desc 'main', 'main user entry'
20
+ option :preclean, type: :boolean, default: true, banner: 'clean before do anything'
21
+ option :build, type: :boolean, default: true, banner: 'build image'
22
+ def main
23
+ invoke_clean if options[:preclean] && task_opts(:main)[:preclean] != false
24
+
25
+ invoke_hooks_for(:main, type: :before)
26
+ invoke :build, [], {} if options[:build] && task_opts(:main)[:build] != false
27
+ invoke_hooks_for(:main)
28
+ end
29
+
30
+ desc 'console', 'get ruby console'
31
+ def console
32
+ pp registry
33
+ byebug
34
+ puts "=ok"
35
+ end
36
+
37
+ desc 'log [CONTAINER]', 'logs in container'
38
+ def log(cid = ops_container)
39
+ unless cid
40
+ container_missing
41
+ return
42
+ end
43
+ system <<~Desc
44
+ docker logs -t -f --details #{cid}
45
+ Desc
46
+ end
47
+
48
+ desc 'runsh [CONTAINER]', 'run into container'
49
+ option :cmd, banner: 'run command in container'
50
+ option :opts, banner: 'docker run options'
51
+ option :tmp, type: :boolean, default: false, banner: 'allow run tmp container'
52
+ def runsh(cid = ops_container)
53
+ tmprun = options[:tmp]
54
+ if tmprun
55
+ dkcmd = "docker run -t -d"
56
+ dkcmd += " --network #{netname}" if netname
57
+ dkcmd += " #{options[:opts]}" if options[:opts]
58
+ cid = `#{dkcmd} #{docker_image} sleep 3d`.chomp
59
+ puts "==run tmp container: #{cid}" unless options[:quiet]
60
+ end
61
+
62
+ abort "No container found!" unless cid
63
+
64
+ cmd = options[:cmd] || 'sh'
65
+ puts "run : #{cmd}" unless options[:quiet]
66
+
67
+ if cmd == 'sh' # simple case
68
+ cmds = <<~Desc
69
+ docker exec -it #{options[:opts]} #{cid} #{cmd}
70
+ Desc
71
+ else
72
+ tfile = Dklet::Util.tmpfile_for cmd
73
+ dst_file = "/tmp/dklet-#{File.basename(tfile)}-#{rand(10000)}"
74
+ # todo user permissions for pg
75
+ cmds = <<~Desc
76
+ docker cp --archive #{tfile} #{cid}:#{dst_file}
77
+ docker exec -it #{options[:opts]} #{cid} sh -c 'sh #{dst_file} && rm -f #{dst_file}'
78
+ Desc
79
+ end
80
+ puts cmds unless options[:quiet]
81
+ system cmds unless options[:dry]
82
+
83
+ if tmprun
84
+ system <<~Desc
85
+ docker rm -f #{cid}
86
+ Desc
87
+ end
88
+ end
89
+ map "sh" => :runsh
90
+
91
+ desc 'daemon', 'docker run in daemon'
92
+ option :opts, banner: 'run extra options'
93
+ def daemon
94
+ invoke :build, [], {}
95
+ system "docker run -d #{options[:opts]} #{docker_image}" unless options[:dry]
96
+ end
97
+
98
+ desc 'build', 'build image'
99
+ option :opts, banner: 'build extra options like --no-cache'
100
+ def build
101
+ return unless dockerfile
102
+
103
+ unless options[:dry]
104
+ invoke_hooks_for(:build, type: :before)
105
+ end
106
+
107
+ cmd = "docker build --tag #{docker_image}"
108
+ net = build_net
109
+ cmd += " --network #{net}" if net
110
+ cmd += " #{options[:opts]}" if options[:opts]
111
+
112
+ bpath = smart_build_context_path
113
+ cmd = if bpath
114
+ "#{cmd} --file #{dockerfile} #{bpath}"
115
+ else # nil stand for do not need build context
116
+ "cat #{dockerfile} | #{cmd} -"
117
+ end
118
+ puts "build command:\n #{cmd}" if options[:debug]
119
+
120
+ system cmd unless options[:dry]
121
+ end
122
+
123
+ desc 'note', 'display user notes'
124
+ def note
125
+ puts user_notes.join("\n")
126
+ end
127
+
128
+ desc 'clean', 'clean container artifacts'
129
+ # keep cache reused
130
+ option :image, type: :boolean, default: false, banner: 'clean user-derived images'
131
+ def clean
132
+ invoke_hooks_for(:clean, type: :before)
133
+
134
+ unless specfile # do not clean container if compose-file exists
135
+ cids = containers_for_release
136
+ unless cids.empty?
137
+ str_ids = cids.join(' ')
138
+ system <<~Desc
139
+ echo ==clean containers: #{str_ids}
140
+ docker rm --force #{str_ids}
141
+ Desc
142
+ end
143
+ end
144
+
145
+ invoke_hooks_for(:clean)
146
+
147
+ if options[:image] && dockerfile
148
+ system <<~Desc
149
+ echo ==clean image: #{docker_image}
150
+ docker rmi --force #{docker_image} 2>/dev/null
151
+ Desc
152
+ end
153
+ end
154
+
155
+ desc 'spec', 'display specs'
156
+ option :spec, type: :boolean, default: true, banner: 'show rendered specfile'
157
+ option :dockerfile, type: :boolean, default: true, banner: 'show Dockerfile'
158
+ def spec
159
+ if options[:spec] && specfile
160
+ puts File.read(specfile)
161
+ puts "# rendered at #{specfile}" if options[:debug]
162
+ end
163
+ if options[:dockerfile] && dockerfile
164
+ puts File.read(dockerfile)
165
+ puts "# Dockerfile at #{dockerfile} " if options[:debug]
166
+ end
167
+ end
168
+
169
+ desc 'image_name', 'display image name'
170
+ def image_name
171
+ puts docker_image
172
+ end
173
+
174
+ desc 'image', 'list related image'
175
+ def image
176
+ system "docker images #{docker_image}"
177
+ end
178
+
179
+ desc 'ps', 'ps related containers'
180
+ option :imaged, type: :boolean, default: false, banner: 'same image digest'
181
+ def ps
182
+ cmd = if options[:imaged]
183
+ "docker ps -f ancestor=#{docker_image} -a"
184
+ else
185
+ "docker ps #{container_filters_for_release} -a"
186
+ end
187
+ puts cmd if options[:debug]
188
+ system cmd unless options[:dry]
189
+ end
190
+
191
+ desc 'netup [NETNAME]', 'make networking'
192
+ def netup(net = netname)
193
+ return unless net
194
+ ensure_docker_net(net)
195
+ puts "network #{net} working"
196
+ end
197
+
198
+ desc 'netdown [NETNAME]', 'clean networking'
199
+ def netdown(net = netname)
200
+ puts "cleaning net: #{net}" if options[:debug]
201
+ return unless net
202
+ return unless find_net(net)
203
+
204
+ cids = containers_in_net(net)
205
+ binded = !cids.empty?
206
+ if binded
207
+ if options[:force] || yes?("#{cids.size} containers linked, FORCELY remove(y|n)?")
208
+ system "docker rm -f #{cids.join(' ')}"
209
+ binded = false
210
+ end
211
+ end
212
+ if binded
213
+ puts "#{net} has binded resources, skipped"
214
+ else
215
+ system "docker network rm #{net}"
216
+ puts "network #{net} cleaned"
217
+ end
218
+ end
219
+
220
+ desc 'netps [NETNAME]', 'ps in a network'
221
+ def netps(net = netname)
222
+ return unless net
223
+ system <<~Desc
224
+ docker ps -f network=#{net} -a
225
+ Desc
226
+ end
227
+
228
+ desc 'netls', 'list networks'
229
+ def netls()
230
+ system <<~Desc
231
+ docker network ls
232
+ Desc
233
+ end
234
+
235
+ desc 'comprun', 'compose run'
236
+ def comprun(*args)
237
+ cmd = <<~Desc
238
+ #{compose_cmd} #{args.join(' ')}
239
+ Desc
240
+ puts cmd if options[:debug]
241
+ system cmd unless options[:dry]
242
+ end
243
+
244
+ desc 'vols', 'ls volumes'
245
+ def vols
246
+ system <<~Desc
247
+ ls -l #{volumes_root}/
248
+ Desc
249
+ end
250
+
251
+ desc 'clear_app_volumes', 'clear app volumes'
252
+ def clear_app_volumes
253
+ if app_volumes.directory?
254
+ if options[:force] || yes?("Remove app volumes dir data?")
255
+ app_volumes.rmtree
256
+ end
257
+ end
258
+ end
259
+
260
+ desc 'inspect_info', 'inspect info'
261
+ option :image, type: :boolean, default: false, aliases: ['-i'], banner: 'inspect image'
262
+ option :container, type: :boolean, default: false, aliases: ['-c'], banner: 'inspect container'
263
+ def inspect_info
264
+ cmd = nil
265
+ if options[:image]
266
+ cmd = "docker inspect #{docker_image}"
267
+ elsif options[:container]
268
+ cid = containers_for_release.first || container_name || ops_container
269
+ cmd = "docker inspect #{cid}"
270
+ else
271
+ h = {
272
+ script: dklet_script,
273
+ script_path: script_path,
274
+ script_name: script_name,
275
+ appname: appname,
276
+ env: env,
277
+ release: app_release,
278
+ full_release_name: full_release_name,
279
+ container_name: container_name,
280
+ image: docker_image,
281
+ approot: approot,
282
+ build_root: build_root,
283
+ build_net: build_net,
284
+ release_labels: release_label_hash,
285
+ network: netname,
286
+ voluemes_root: volumes_root,
287
+ app_volumes: app_volumes,
288
+ domain: proxy_domain,
289
+ dsl_methods: dsl_methods,
290
+ registry: registry
291
+ }
292
+ pp h
293
+ end
294
+ system cmd if cmd
295
+ end
296
+ map 'inspect' => 'inspect_info'
297
+ map 'info' => 'inspect_info'
298
+
299
+ desc 'mock1', ''
300
+ def mock1(time)
301
+ puts "invoked at #{time}"
302
+ end
303
+
304
+ desc 'mock2', ''
305
+ def mock2
306
+ invoke :mock1, [Time.now]
307
+ puts 'first invoked'
308
+ invoke :mock1, [Time.now]
309
+ puts 'sencond invoked'
310
+
311
+ invoke2 :mock1, [Time.now], {}
312
+ puts 'third invoked'
313
+ invoke2 :mock1, [Time.now], {}
314
+ puts '4th invoked'
315
+ end
316
+
317
+ no_commands do
318
+ include Dklet::DSL
319
+
320
+ def invoke_clean
321
+ invoke :clean, [], {}
322
+ end
323
+
324
+ def invoke_hooks_for(name = :main, type: :after)
325
+ hooks_name = "#{name}_#{type}_hooks".to_sym
326
+ hooks = fetch(hooks_name)
327
+ if hooks && !hooks.empty?
328
+ hooks.each do |hook|
329
+ # eval by receiver dierectly
330
+ instance_eval &hook if hook.respond_to?(:call)
331
+ end
332
+ end
333
+ end
334
+
335
+ # https://github.com/erikhuda/thor/issues/73
336
+ def invoke2(task, args, options)
337
+ (klass, task) = Thor::Util.find_class_and_command_by_namespace(task)
338
+ klass.new.invoke(task, args, options)
339
+ end
340
+
341
+ def container_run(cmds, opts = nil)
342
+ cmds = cmds.join("\n") if cmds.is_a?(Array)
343
+ opts = (opts||{}).merge(cmd: cmds).merge(options.slice('quiet', 'dry'))
344
+ invoke :runsh, [], opts
345
+ end
346
+
347
+ # encapsulate run commands behaviors in system
348
+ def system_run(cmds, opts={})
349
+ unless options[:quiet]
350
+ puts cmds
351
+ end
352
+ unless options[:dry]
353
+ system cmds
354
+ end
355
+ end
356
+ end # of no_commands
357
+ end
data/lib/dklet/dsl.rb ADDED
@@ -0,0 +1,408 @@
1
+ module Dklet::DSL
2
+ class << self
3
+ def registry
4
+ @_registry ||= {}
5
+ end
6
+
7
+ def dsl_methods
8
+ @_dsl_methods ||= []
9
+ end
10
+
11
+ def dsl_method(mthd)
12
+ dsl_methods << mthd
13
+ define_method(mthd) do
14
+ fetch_with_default(mthd)
15
+ end
16
+ end
17
+ end
18
+
19
+ def registry
20
+ Dklet::DSL.registry
21
+ end
22
+
23
+ def add_dsl &blk
24
+ Dklet::DSL.module_eval &blk
25
+ end
26
+
27
+ def dsl_methods
28
+ Dklet::DSL.dsl_methods
29
+ end
30
+
31
+ def register(key, value)
32
+ registry[key] = value
33
+ end
34
+
35
+ def fetch(key)
36
+ val = registry[key]
37
+ if val && val.respond_to?(:call)
38
+ val = val.call
39
+ end
40
+ val
41
+ end
42
+
43
+ def fetch_with_default(key)
44
+ provided = fetch(key)
45
+ return provided if provided
46
+ mthd = "default_#{key}"
47
+ send(mthd) if respond_to?(mthd)
48
+ end
49
+
50
+ def register_docker_image(name)
51
+ register :docker_image, name
52
+ end
53
+
54
+ # release is not relevant
55
+ def default_docker_image
56
+ "#{env}/#{appname}:#{image_tag}"
57
+ end
58
+
59
+ def default_image_tag
60
+ "edge"
61
+ end
62
+
63
+ def default_image_labels
64
+ app_labels = image_label_hash.map{|k, v| [k,v].join('=') }.join(' ')
65
+ "maintainer=dailyops built_from=docklet #{app_labels}"
66
+ end
67
+
68
+ def image_label_hash
69
+ {
70
+ dklet_app: appname,
71
+ dklet_env: env
72
+ }
73
+ end
74
+
75
+ def release_label_hash
76
+ image_label_hash.merge(dklet_release: app_release)
77
+ end
78
+
79
+ # maybe from external image
80
+ def dkrun_cmd(labeled: true, opts: nil, named: false)
81
+ cmd = "docker run"
82
+ if labeled
83
+ release_labels = release_label_hash.map do |k, v|
84
+ "--label=#{k}=#{v}"
85
+ end.join(' ')
86
+ cmd += " #{release_labels}"
87
+ end
88
+ cmd += " --net #{netname}" if netname
89
+ cmd += " --name #{container_name}" if named
90
+ cmd += " #{opts}" if opts
91
+ cmd
92
+ end
93
+
94
+ def dktmprun(opts: nil)
95
+ cmd = dkrun_cmd(opts: "--rm -i #{opts}", labeled: false)
96
+ "#{cmd} #{docker_image}"
97
+ end
98
+
99
+ def container_filters_for_release
100
+ release_label_hash.map do |k, v|
101
+ "--filter label=#{k}=#{v}"
102
+ end.join(' ')
103
+ end
104
+
105
+ def containers_for_release
106
+ `docker ps -aq #{container_filters_for_release}`.split("\n")
107
+ end
108
+
109
+ # Note: if img1:t1 = img2:t2 points to same image hashid, they will be selected as same
110
+ def containers_for_image(img = docker_image)
111
+ `docker ps -aq -f ancestor=#{img}`.split("\n")
112
+ end
113
+
114
+ def containers_in_net(net = netname)
115
+ `docker ps -aq -f network=#{net}`.split("\n")
116
+ end
117
+
118
+ def dklet_script
119
+ Pathname($PROGRAM_NAME)
120
+ end
121
+
122
+ # 触发脚本所在(绝对)路径
123
+ def script_path
124
+ dklet_script.realdirpath.dirname
125
+ end
126
+
127
+ # use <parent_path_name>_<script_file_name> to ensure possible unique
128
+ def script_name # not file name
129
+ sname = fetch(:script_name)
130
+ return sname if sname
131
+ name = dklet_script.basename('.rb').to_s
132
+ pname = script_path.basename.to_s
133
+ "#{pname}_#{name}"
134
+ end
135
+
136
+ def set_file_for(name, str)
137
+ register name, Dklet::Util.tmpfile_for(str)
138
+ end
139
+
140
+ def file_for(name)
141
+ fetch(name)
142
+ end
143
+
144
+ def file_content_for(name)
145
+ fpath = fetch(name)
146
+ return unless fpath
147
+ File.read(fpath)
148
+ end
149
+
150
+ # todo
151
+ def dklet_config_for(name)
152
+ p = Pathname("/dkconf/#{full_release_name}")
153
+ p.mkpath unless p.directory?
154
+ p.join(name)
155
+ end
156
+
157
+ def rendered_file_for(name, locals: {}, in_binding: binding)
158
+ tmpl = file_content_for(name)
159
+ return unless tmpl
160
+ erb = ERB.new(tmpl, nil, '%<>')
161
+ rendered = erb.result(in_binding)
162
+ Dklet::Util.tmpfile_for(rendered)
163
+ end
164
+
165
+ # Dockerfile for image build
166
+ def write_dockerfile(str, path: nil)
167
+ set_file_for(:dockerfile, str)
168
+ register_build_root(path) if path
169
+ end
170
+
171
+ def raw_dockerfile
172
+ fetch(:dockerfile)
173
+ end
174
+
175
+ def dockerfile
176
+ rendered_file_for(:dockerfile)
177
+ end
178
+
179
+ # specfile for k8s resources spec manifest
180
+ def write_specfile(str)
181
+ set_file_for(:specfile, str)
182
+ end
183
+
184
+ def raw_specfile
185
+ fetch(:specfile)
186
+ end
187
+
188
+ ## rendered in current context
189
+ def specfile
190
+ rendered_file_for(:specfile)
191
+ end
192
+
193
+ def disable(key)
194
+ (registry[:disable] ||= {})[key] = true
195
+ end
196
+
197
+ def disabled?(key)
198
+ (registry[:disable] ||= {})[key]
199
+ end
200
+
201
+ # main dsl
202
+ def task(name = :main, opts={}, &blk)
203
+ type = opts.delete(:type) || :after
204
+ hooks_name = "#{name}_#{type}_hooks".to_sym
205
+ (registry[hooks_name] ||= []) << blk
206
+ task_opts(name).merge!(opts) unless opts.empty?
207
+ end
208
+
209
+ def before_task(name = :main, &blk)
210
+ task(name, type: :before, &blk)
211
+ end
212
+
213
+ def task_opts(name = :main)
214
+ key = "opts_for_task_#{name}".to_sym
215
+ registry[key] ||= {}
216
+ end
217
+
218
+ def let_cli_magic_start!
219
+ DockletCLI.start
220
+ end
221
+
222
+ def custom_commands &blk
223
+ DockletCLI.class_eval &blk
224
+ end
225
+
226
+ def add_note str
227
+ (registry[:user_notes] ||= []) << str
228
+ end
229
+
230
+ def user_notes
231
+ fetch(:user_notes)
232
+ end
233
+
234
+ # docker networking
235
+ def register_net(name = :dailyops, build: false)
236
+ register :netname, name
237
+ ensure_docker_net(name) if build
238
+ end
239
+
240
+ def netname
241
+ fetch(:netname)
242
+ end
243
+
244
+ def ensure_docker_net(name, driver: :bridge)
245
+ unless netid = find_net(name)
246
+ puts "create new network: #{name}"
247
+ netid = `docker network create #{name} --label #{label_pair(:name, name)} --driver=#{driver}`
248
+ end
249
+ netid
250
+ end
251
+
252
+ # use label (not name) filter to avoid str part match
253
+ def find_net(name)
254
+ cmd = "docker network ls -q --filter label=#{label_pair(:name, name)}"
255
+ netid = `#{cmd}`.chomp
256
+ return netid unless netid.empty?
257
+ nil
258
+ end
259
+
260
+ def label_key(key, prefix: true)
261
+ prefix ? "docklet.#{key}" : key
262
+ end
263
+
264
+ # key=value pair
265
+ def label_pair(key, val, prefix: true)
266
+ [label_key(key, prefix: prefix), val].join('=')
267
+ end
268
+
269
+ ## project name for docker-compose
270
+ def compose_name
271
+ "#{fetch(:compose_name) || appname}_#{env}"
272
+ end
273
+
274
+ # -f, --file
275
+ # -p, --project-name to altertive project name, eg. default net prefix
276
+ def compose_cmd
277
+ "docker-compose -f #{specfile} --project-name #{compose_name} --project-directory #{approot}"
278
+ end
279
+
280
+ def register_approot path
281
+ register_path(:approot, path)
282
+ end
283
+
284
+ def approot
285
+ fetch(:approot) || build_root || script_path
286
+ end
287
+
288
+ def appname
289
+ fetch(:appname) || script_name
290
+ end
291
+
292
+ # take into acccount: app, env, app_release
293
+ def full_release_name
294
+ [env, appname, app_release].compact.join('_')
295
+ end
296
+
297
+ # make path friendly
298
+ def release_path_name
299
+ full_release_name.gsub(/_/, '-')
300
+ end
301
+
302
+ def register_app_tag(tag)
303
+ app_tags << tag
304
+ end
305
+
306
+ def app_tags
307
+ registry[:app_tags] ||= []
308
+ end
309
+
310
+ def smart_build_context_path
311
+ # use explicitly specified, maybe nil
312
+ return build_root if registry.has_key?(:build_root)
313
+ # check build path dependent
314
+ body = File.read(dockerfile)
315
+ need_path = body =~ /^\s*(ADD|COPY)\s/i
316
+ script_path if need_path
317
+ end
318
+
319
+ def register_build_root path
320
+ register_path(:build_root, path)
321
+ end
322
+
323
+ def build_root
324
+ fetch(:build_root)
325
+ end
326
+
327
+ def register_build_net net
328
+ register(:build_net, net)
329
+ end
330
+
331
+ def build_net
332
+ fetch(:build_net)
333
+ end
334
+
335
+ def register_path key, path
336
+ path = Pathname(path) unless path.is_a?(Pathname)
337
+ register key, path
338
+ end
339
+
340
+ def register_ops(cid)
341
+ register :ops_container, cid
342
+ end
343
+
344
+ def default_ops_container
345
+ containers_for_release.first # || container_name
346
+ end
347
+
348
+ def default_container_name
349
+ full_release_name
350
+ end
351
+
352
+ def container_missing
353
+ puts "Not found container for image: #{docker_image}"
354
+ end
355
+
356
+ def register_default_env(str)
357
+ register :default_env, str
358
+ end
359
+
360
+ def env
361
+ ENV['APP_ENV'] || fetch(:default_env) || 'dev'
362
+ end
363
+
364
+ # 标识一次运行发布的用途, 如redis for hirails-only
365
+ def app_release
366
+ ENV['APP_RELEASE'] || 'default'
367
+ end
368
+
369
+ def volumes_root
370
+ vols_root = "#{ENV['HOME']}/DockerVolumes"
371
+ root = fetch(:volumes_root) || if File.directory?(vols_root)
372
+ # friendly to File sharing on Docker for Mac
373
+ vols_root
374
+ else
375
+ '~/docker-volumes'
376
+ end
377
+ proot = Pathname(root)
378
+ proot.mkpath unless proot.directory?
379
+ proot
380
+ end
381
+
382
+ def default_app_volumes
383
+ volumes_root.join(release_path_name)
384
+ end
385
+
386
+ ## top proxy domain part
387
+ def proxy_domain
388
+ ENV['LOCAL_DKLET_DOMAIN'] || 'lh'
389
+ end
390
+
391
+ def domain_for(*doms)
392
+ doms.map{|d| "#{d}.#{proxy_domain}" }.join(',')
393
+ end
394
+
395
+ # ref dklet/mac/
396
+ def host_domain_in_container
397
+ ENV['HOST_DOMAIN_IN_CONTAINER'] || 'host.dokcer.internal'
398
+ end
399
+ end
400
+
401
+ %i(
402
+ docker_image
403
+ image_tag
404
+ image_labels
405
+ container_name
406
+ ops_container
407
+ app_volumes
408
+ ).each{|m| Dklet::DSL.dsl_method(m) }
data/lib/dklet/util.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'tempfile'
2
+ require 'socket'
3
+
4
+ module Dklet::Util
5
+ module_function
6
+
7
+ def tmpfile_for(str, prefix: 'kc-tmp')
8
+ file = Tempfile.new(prefix)
9
+ file.write str
10
+ file.close # save to disk
11
+ # unlink清理问题:引用进程结束时自动删除?👍
12
+ file.path
13
+ end
14
+
15
+ def human_timestamp(t = Time.now)
16
+ t.strftime("%Y%m%d%H%M%S")
17
+ end
18
+
19
+ def host_ip
20
+ Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ module Dklet
2
+ VERSION = "0.1.1"
3
+
4
+ def self.version
5
+ VERSION
6
+ end
7
+ end
data/lib/dklet.rb ADDED
@@ -0,0 +1,15 @@
1
+ require "pathname"
2
+ require "erb"
3
+
4
+ module Dklet
5
+ class << self
6
+ def lib_path
7
+ Pathname(__dir__)
8
+ end
9
+ end
10
+ end
11
+
12
+ require "dklet/version"
13
+ require 'dklet/util'
14
+ require 'dklet/dsl'
15
+ require 'dklet/cli'
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env rundklet
2
+ add_note <<~Note
3
+ Note
4
+
5
+ # https://docs.docker.com/develop/develop-images/dockerfile_best-practices
6
+ write_dockerfile <<~Desc
7
+ FROM alpine:3.7
8
+ LABEL <%%=image_labels%>
9
+ Desc
10
+
11
+ task :main do
12
+ system_run <<~Desc
13
+ #{dkrun_cmd(named: true)} -d #{docker_image}
14
+ # #{compose_cmd} up -d
15
+ Desc
16
+ end
17
+
18
+ custom_commands do
19
+ desc 'try', 'try'
20
+ def try
21
+ system_run <<~Desc
22
+ #{dktmprun} echo hi container #{container_name}
23
+ Desc
24
+ end
25
+ end
26
+
27
+ # Generated with dklet version: <%=Dklet.version%>
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dklet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Ruijian Cao
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-10-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: thor
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: practical DSL for daily container life
70
+ email:
71
+ - cao7113@hotmail.com
72
+ executables:
73
+ - mkdklet
74
+ - rundklet
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".gitignore"
79
+ - ".rspec"
80
+ - ".travis.yml"
81
+ - CODE_OF_CONDUCT.md
82
+ - Gemfile
83
+ - Gemfile.lock
84
+ - LICENSE.txt
85
+ - README.md
86
+ - Rakefile
87
+ - bin/console
88
+ - bin/setup
89
+ - dklet.gemspec
90
+ - exe/mkdklet
91
+ - exe/rundklet
92
+ - lib/dklet.rb
93
+ - lib/dklet/app_tags/rails_web.rb
94
+ - lib/dklet/cli.rb
95
+ - lib/dklet/dsl.rb
96
+ - lib/dklet/util.rb
97
+ - lib/dklet/version.rb
98
+ - lib/template/dklet.erb
99
+ homepage: https://github.com/dailyops/kc/dklet
100
+ licenses:
101
+ - MIT
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 2.7.6
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: practical DSL for daily container life
123
+ test_files: []