dklet 0.1.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.
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: []