djin 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e44393cb2f47b9b3a68d2ce59ae997e5e1d6b252e68778293b5e62a7600374b
4
- data.tar.gz: 5c02ed7dc956357e31b07c6ade84c5f660cb26469f7434ea0128b42f0ebec74f
3
+ metadata.gz: 06daa5a16dcd5adf9e7ff60d976689fbe7e146cf58f708b97d3fea8de2113599
4
+ data.tar.gz: e36aad9524a35db424096893fc2f817306c25160b8afea4fb19bd7c1951053fc
5
5
  SHA512:
6
- metadata.gz: 1d8d9dbd993757586e35679c12e1d03b98c940e5bca58e6ab2b3b8955c84c78b2fad6ae2053a0430b85aa580217a8540b3196ab3489c0ae76c4426e712142822
7
- data.tar.gz: be25638d743e46c62a452891298003d3dd93905ea38b6bbe51420d44549ba001785f7b9a631cc4e4b83c61ffa541688420df9bd9339023b1cb86747ad2ae87c4
6
+ metadata.gz: 071abc594f4ee1ac16f3983550abba863fb22594afba6c2a0d1b54b92888feb8eda51f55445d6562f061f065b5da12b55e4a964dbe863e33227fb3e421e13cab
7
+ data.tar.gz: 118d467194cbcad067c942e692794ac48afbcd4295183971e9a3a3c3ef41aec4ec2d693da83e7f86686e70cdfe9369458ae0eee319258b3dd8d3eb54f0761893
@@ -1,25 +1,37 @@
1
1
  name: Ruby
2
2
 
3
- on: [push]
3
+ on: [push, workflow_dispatch]
4
4
 
5
5
  jobs:
6
6
  build:
7
7
  runs-on: ubuntu-16.04
8
8
  strategy:
9
9
  matrix:
10
- ruby: [ '2.4', '2.5', '2.6' ]
10
+ ruby: [ '2.5', '2.6', '2.7' ]
11
11
  name: Ruby ${{ matrix.ruby }}
12
12
  steps:
13
13
  - uses: actions/checkout@v2
14
- - uses: actions/setup-ruby@v1
15
- with:
16
- ruby-version: ${{ matrix.ruby }}
17
- - name: Install Gems
14
+
15
+ - name: Setup Code Climate test-reporter
18
16
  run: |
19
- gem install bundler
20
- bundle install --jobs 4 --retry 3
17
+ curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
18
+ chmod +x ./cc-test-reporter
19
+ ./cc-test-reporter before-build
20
+
21
+ - name: Build
22
+ run: |
23
+ docker-compose -p djin_ruby_${{ matrix.ruby }} build --build-arg ${{ matrix.ruby }} app
24
+
21
25
  - name: Run tests
22
- run: bundle exec rake
26
+ run: |
27
+ docker-compose -p djin_ruby_${{ matrix.ruby }} run --rm app rspec
28
+ env:
29
+ TMP_TEST_FILE_FOLDER: '/tmp'
30
+
31
+ - name: Publish code coverage
32
+ run: |
33
+ # TODO: Move to script
34
+ docker-compose -p djin_ruby_${{ matrix.ruby }} run --rm app 'export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}" && ./cc-test-reporter after-build -r ${{secrets.CC_TEST_REPORTER_ID}}'
23
35
 
24
36
  lint:
25
37
  runs-on: ubuntu-16.04
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  .byebug_history
10
+ .irb_history
10
11
 
11
12
  # rspec failure tracking
12
13
  .rspec_status
data/.irbrc ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ IRB.conf[:SAVE_HISTORY] = 200
4
+ IRB.conf[:HISTORY_FILE] = '~/.irb_history'
@@ -3,6 +3,11 @@ inherit_from: .rubocop_todo.yml
3
3
  Style/Documentation:
4
4
  Enabled: false
5
5
 
6
+ Style/BlockDelimiters:
7
+ Exclude:
8
+ - spec/**/*
9
+
6
10
  Metrics/BlockLength:
7
11
  Exclude:
8
12
  - spec/**/*
13
+ - djin.gemspec
@@ -1,3 +1,7 @@
1
+ ## 0.11.0 - 14/01/2021
2
+ * [FEATURE] Remote Configs
3
+ * [TECH] Adds Test Coverage
4
+
1
5
  ## 0.10.0 - 08/10/2020
2
6
  * [FEATURE] -f command option
3
7
 
data/Dockerfile CHANGED
@@ -1,4 +1,5 @@
1
- FROM ruby:2.6.5-alpine AS builder
1
+ ARG RUBY_VERSION
2
+ FROM ruby:${RUBY_VERSION:-2.6.5}-alpine AS builder
2
3
 
3
4
  ENV BUILD_PACKAGES build-base git
4
5
 
@@ -13,17 +14,23 @@ COPY djin.gemspec Gemfile Gemfile.lock ./
13
14
 
14
15
  COPY lib/djin/version.rb lib/djin/version.rb
15
16
 
16
- RUN gem install bundler -v 2.0.2
17
+ RUN gem install bundler -v 2.1.4
17
18
 
18
19
  RUN bundle install
19
20
 
21
+ FROM builder AS dev
22
+
23
+ WORKDIR /usr/src/djin
24
+
25
+ COPY . .
26
+
20
27
  FROM ruby:2.6.5-alpine
21
28
 
22
29
  WORKDIR /usr/src/djin
23
30
 
24
31
  COPY --from=builder /usr/local/bundle/ /usr/local/bundle
25
32
 
26
- RUN gem install bundler -v 2.0.2
33
+ RUN gem install bundler -v 2.1.4
27
34
 
28
35
  ENV DEPENDENCIES git
29
36
 
data/Gemfile CHANGED
@@ -2,5 +2,9 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ group :test do
6
+ gem 'factory_bot'
7
+ end
8
+
5
9
  # Specify your gem's dependencies in djin.gemspec
6
10
  gemspec
@@ -1,40 +1,46 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- djin (0.10.0)
4
+ djin (0.11.0)
5
5
  dry-cli (~> 0.6.0)
6
6
  dry-equalizer (~> 0.3.0)
7
7
  dry-struct (~> 1.3.0)
8
8
  dry-validation (~> 1.5.1)
9
+ git (~> 1.8.1)
9
10
  mustache (~> 1.1.1)
10
11
  vseries (~> 0.1.0)
11
12
 
12
13
  GEM
13
14
  remote: https://rubygems.org/
14
15
  specs:
16
+ activesupport (6.1.1)
17
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
+ i18n (>= 1.6, < 2)
19
+ minitest (>= 5.1)
20
+ tzinfo (~> 2.0)
21
+ zeitwerk (~> 2.3)
15
22
  ast (2.4.1)
16
23
  byebug (11.1.1)
17
24
  concurrent-ruby (1.1.7)
18
25
  diff-lcs (1.3)
26
+ docile (1.3.2)
19
27
  dry-cli (0.6.0)
20
28
  concurrent-ruby (~> 1.0)
21
- dry-configurable (0.11.6)
29
+ dry-configurable (0.12.0)
22
30
  concurrent-ruby (~> 1.0)
23
- dry-core (~> 0.4, >= 0.4.7)
24
- dry-equalizer (~> 0.2)
31
+ dry-core (~> 0.5, >= 0.5.0)
25
32
  dry-container (0.7.2)
26
33
  concurrent-ruby (~> 1.0)
27
34
  dry-configurable (~> 0.1, >= 0.1.3)
28
- dry-core (0.4.9)
35
+ dry-core (0.5.0)
29
36
  concurrent-ruby (~> 1.0)
30
37
  dry-equalizer (0.3.0)
31
38
  dry-inflector (0.2.0)
32
39
  dry-initializer (3.0.4)
33
- dry-logic (1.0.8)
40
+ dry-logic (1.1.0)
34
41
  concurrent-ruby (~> 1.0)
35
- dry-core (~> 0.2)
36
- dry-equalizer (~> 0.2)
37
- dry-schema (1.5.5)
42
+ dry-core (~> 0.5, >= 0.5)
43
+ dry-schema (1.5.6)
38
44
  concurrent-ruby (~> 1.0)
39
45
  dry-configurable (~> 0.8, >= 0.8.3)
40
46
  dry-core (~> 0.4)
@@ -61,13 +67,22 @@ GEM
61
67
  dry-equalizer (~> 0.2)
62
68
  dry-initializer (~> 3.0)
63
69
  dry-schema (~> 1.5, >= 1.5.2)
70
+ factory_bot (6.1.0)
71
+ activesupport (>= 5.0.0)
72
+ git (1.8.1)
73
+ rchardet (~> 1.8)
74
+ i18n (1.8.7)
75
+ concurrent-ruby (~> 1.0)
64
76
  ice_nine (0.11.2)
77
+ json (2.3.1)
78
+ minitest (5.14.3)
65
79
  mustache (1.1.1)
66
80
  parallel (1.19.2)
67
81
  parser (2.7.1.4)
68
82
  ast (~> 2.4.1)
69
83
  rainbow (3.0.0)
70
84
  rake (13.0.1)
85
+ rchardet (1.8.0)
71
86
  regexp_parser (1.7.1)
72
87
  rexml (3.2.4)
73
88
  rspec (3.9.0)
@@ -95,8 +110,16 @@ GEM
95
110
  rubocop-ast (0.0.3)
96
111
  parser (>= 2.7.0.1)
97
112
  ruby-progressbar (1.10.1)
113
+ simplecov (0.17.1)
114
+ docile (~> 1.1)
115
+ json (>= 1.8, < 3)
116
+ simplecov-html (~> 0.10.0)
117
+ simplecov-html (0.10.2)
118
+ tzinfo (2.0.4)
119
+ concurrent-ruby (~> 1.0)
98
120
  unicode-display_width (1.7.0)
99
121
  vseries (0.1.1)
122
+ zeitwerk (2.4.2)
100
123
 
101
124
  PLATFORMS
102
125
  ruby
@@ -105,9 +128,11 @@ DEPENDENCIES
105
128
  bundler (~> 2.0)
106
129
  byebug
107
130
  djin!
131
+ factory_bot
108
132
  rake (~> 13.0)
109
133
  rspec (~> 3.0)
110
134
  rubocop
135
+ simplecov (~> 0.17.0)
111
136
 
112
137
  BUNDLED WITH
113
138
  2.1.4
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  ![](https://github.com/catks/djin/workflows/Ruby/badge.svg?branch=master)
4
4
  [![Maintainability](https://api.codeclimate.com/v1/badges/824a2e78399813543212/maintainability)](https://codeclimate.com/github/catks/djin/maintainability)
5
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/824a2e78399813543212/test_coverage)](https://codeclimate.com/github/catks/djin/test_coverage)
5
6
 
6
7
  Djin is a make-like utility for docker containers
7
8
 
@@ -13,7 +14,7 @@ Djin is distributed as a Ruby Gem, to install simple run:
13
14
 
14
15
  ### With Rbenv
15
16
 
16
- If you use Rbenv you can install djin only once and create a alias in your .basrc, .zshrc, etc:
17
+ If you use Rbenv you can install djin only once and create an alias in your .basrc, .zshrc, etc:
17
18
 
18
19
  #### ZSH
19
20
  $ RBENV_VERSION=$(rbenv global) gem install djin && echo "alias djin='RBENV_VERSION=$(rbenv global) djin'" >> ~/.zshrc
@@ -26,7 +27,7 @@ If you use Rbenv you can install djin only once and create a alias in your .basr
26
27
  To use djin first you need to create a djin.yml file:
27
28
 
28
29
  ```yaml
29
- djin_version: '0.10.0'
30
+ djin_version: '0.11.0'
30
31
 
31
32
  tasks:
32
33
  # With a docker image
@@ -53,12 +54,12 @@ You can also set task dependencies with depends_on option:
53
54
 
54
55
 
55
56
  ```yaml
56
- djin_version: '0.10.0'
57
+ djin_version: '0.11.0'
57
58
 
58
59
  _default_run_options: &default_run_options
59
60
  options: "--rm"
60
61
 
61
- tasks:
62
+ tasks:
62
63
  "db:create":
63
64
  docker-compose:
64
65
  service: app
@@ -82,7 +83,7 @@ tasks:
82
83
  Or mix local commands and docker/docker-compose commands:
83
84
 
84
85
  ```yaml
85
- djin_version: '0.10.0'
86
+ djin_version: '0.11.0'
86
87
 
87
88
  _default_run_options: &default_run_options
88
89
  options: "--rm"
@@ -121,7 +122,7 @@ After that you can run `djin {{task_name}}`, like `djin script` or `djin test`
121
122
  You can also use environment variables using the '{{YOUR_ENV_HERE}}' syntax, like so:
122
123
 
123
124
  ```yaml
124
- djin_version: '0.10.0'
125
+ djin_version: '0.11.0'
125
126
 
126
127
  _default_run_options: &default_run_options
127
128
  options: "--rm"
@@ -138,7 +139,7 @@ tasks:
138
139
 
139
140
  Or define some variables to use in multiple locations
140
141
  ```yaml
141
- djin_version: '0.10.0'
142
+ djin_version: '0.11.0'
142
143
 
143
144
  _default_run_options: &default_run_options
144
145
  options: "--rm"
@@ -162,7 +163,7 @@ tasks:
162
163
  It's also possible to pass custom arguments to the command, which means is possible to make a djin task act like the command itself:
163
164
 
164
165
  ```yaml
165
- djin_version: '0.10.0'
166
+ djin_version: '0.11.0'
166
167
 
167
168
  _default_run_options: &default_run_options
168
169
  options: "--rm"
@@ -179,16 +180,16 @@ tasks:
179
180
 
180
181
  ```
181
182
 
182
- With that you can pass custom args after `--`, eg: `djin rubocop -- --parallel`, which wil make djin runs `rubocop --parallel` inside the service `app`.
183
+ With that, you can pass custom args after `--`, eg: `djin rubocop -- --parallel`, which will make djin runs `rubocop --parallel` inside the service `app`.
183
184
 
184
185
  Under the hood djin uses [Mustache](https://mustache.github.io/), so you can use other features like conditionals: `{{#IS_ENABLE}} Enabled {{/IS_ENABLE}}` (for args use the `args?`, eg: `{{#args?}} {{args}} --and-other-thing{{/args?}}`), to see more more options you can access this [Link](https://mustache.github.io/mustache.5.html)
185
186
 
186
187
  ### Reusing tasks
187
188
 
188
- If you have multiple tasks with similar behaviour and with small differences you can use the `include` keyword, so this:
189
+ If you have multiple tasks with similar behavior and with small differences you can use the `include` keyword, so this:
189
190
 
190
191
  ```yaml
191
- djin_version: '0.10.0'
192
+ djin_version: '0.11.0'
192
193
 
193
194
  tasks:
194
195
  "host1:ssh":
@@ -227,7 +228,7 @@ can become this:
227
228
 
228
229
  ```yaml
229
230
  # djin.yml
230
- djin_version: '0.10.0'
231
+ djin_version: '0.11.0'
231
232
 
232
233
  include:
233
234
  - file: '.djin/server_tasks.yml'
@@ -249,7 +250,7 @@ include:
249
250
 
250
251
  ```yaml
251
252
  # .djin/server_tasks.yml
252
- djin_version: '0.10.0'
253
+ djin_version: '0.11.0'
253
254
 
254
255
  tasks:
255
256
  "{{namespace}}:ssh":
@@ -266,11 +267,30 @@ tasks:
266
267
  local:
267
268
  run:
268
269
  - ssh -t {{ssh_user}}@{{host}} tail -f /var/log/my_log
269
- ```
270
+ ```
271
+
272
+ You can also reuse tasks in some git repository, to do that you need to declare a git source and optionally a version:
273
+
274
+ ```yaml
275
+ djin_version: '0.11.0'
276
+
277
+ include:
278
+ - git: 'https://github.com/catks/djin.git'
279
+ version: 'master'
280
+ file: 'examples/djin_lib/test.yml'
281
+ context:
282
+ variables:
283
+ namespace: 'remote:'
284
+
285
+ ```
286
+
287
+ After that run `djin remote-config fetch` to fetch the repo and you can start using the tasks (All remote repos are cloned in `~/.djin/remote`)
288
+
289
+ See `djin remote-config` to learn more.
270
290
 
271
291
  ### Loading custom files
272
292
 
273
- You can also specify a file to be read by djin with `-f`, eg:
293
+ You can also specify a file to be read by djin with `-f`, eg:
274
294
 
275
295
  ```bash
276
296
  djin -f my_file.yml # Returns the help for all tasks in my_file
@@ -298,7 +318,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
298
318
  1. Enable multiple -f options to merge configuration between files
299
319
  2. Option to export tasks to Makefile
300
320
  3. djin-export docker image to create and sync makefiles
301
- 4. include key option to add tasks in git repositories files (maybe with a local cache)
321
+ 4. include a key option to add tasks in git repositories files (maybe with a local cache)
302
322
 
303
323
  ## Contributing
304
324
 
data/Vertofile CHANGED
@@ -1,4 +1,4 @@
1
- verto_version '0.8.0'
1
+ verto_version '0.10.0'
2
2
 
3
3
  config {
4
4
  version.prefix = 'v' # Adds a version_prefix
@@ -12,20 +12,10 @@ context(branch('master')) {
12
12
  }
13
13
 
14
14
  before_tag_creation {
15
- version_changes = sh(
16
- %q#git log --oneline --decorate | grep -B 100 -m 1 "tag:" | grep "pull request" | awk '{print $1}' | xargs git show --format='%b' | grep -v Approved | grep -v "^$" | grep -E "^[[:space:]]*\[.*\]" | sed 's/^[[:space:]]*\(.*\)/ * \1/'#, output: false
17
- ).output
18
-
19
- puts "---------------------------"
20
- version_changes = "## #{new_version} - #{Time.now.strftime('%d/%m/%Y')}\n#{version_changes}\n"
21
- exit unless confirm("Create new Realease?\n" \
22
- "---------------------------\n" \
23
- "#{version_changes}" \
24
- "---------------------------\n"
25
- )
26
-
27
- # CHANGELOG
28
- file('CHANGELOG.md').prepend(version_changes)
15
+ update_changelog(with: :merged_pull_requests_with_bracketed_labels,
16
+ confirmation: true,
17
+ filename: 'CHANGELOG.md')
18
+
29
19
  git!('add CHANGELOG.md')
30
20
 
31
21
  files_to_change_version_once = %w[lib/djin/version.rb djin.yml] + Dir['examples/**/*.yml'] + Dir['spec/support/fixtures/**/*.yml']
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  # Specify which files should be added to the gem when it is released.
22
22
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
23
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|docker)/}) }
25
25
  end
26
26
  spec.bindir = 'exe'
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.add_dependency 'dry-equalizer', '~> 0.3.0'
32
32
  spec.add_dependency 'dry-struct', '~> 1.3.0'
33
33
  spec.add_dependency 'dry-validation', '~> 1.5.1'
34
+ spec.add_dependency 'git', '~> 1.8.1'
34
35
  spec.add_dependency 'mustache', '~> 1.1.1'
35
36
  spec.add_dependency 'vseries', '~> 0.1.0'
36
37
  spec.add_development_dependency 'bundler', '~> 2.0'
@@ -38,4 +39,5 @@ Gem::Specification.new do |spec|
38
39
  spec.add_development_dependency 'rake', '~> 13.0'
39
40
  spec.add_development_dependency 'rspec', '~> 3.0'
40
41
  spec.add_development_dependency 'rubocop'
42
+ spec.add_development_dependency 'simplecov', '~> 0.17.0'
41
43
  end
data/djin.yml CHANGED
@@ -1,4 +1,4 @@
1
- djin_version: '0.10.0'
1
+ djin_version: '0.11.0'
2
2
 
3
3
  _default_run_options: &default_run_options
4
4
  options: "--rm --entrypoint=''"
@@ -9,11 +9,21 @@ tasks:
9
9
  docker-compose:
10
10
  service: app
11
11
  run:
12
- commands: "cd /usr/src/djin && rspec {{args}}"
12
+ commands: "rspec {{args}}"
13
13
  <<: *default_run_options
14
14
  aliases:
15
15
  - rspec
16
16
 
17
+ lint:
18
+ description: Lint
19
+ docker-compose:
20
+ service: app
21
+ run:
22
+ commands: "rubocop {{args}}"
23
+ <<: *default_run_options
24
+ aliases:
25
+ - rubocop
26
+
17
27
  sh:
18
28
  description: Enter app service shell
19
29
  docker-compose:
@@ -1,6 +1,20 @@
1
- version: '3'
1
+ version: "3.9"
2
+
2
3
  services:
3
4
  app:
4
- build: .
5
+ build:
6
+ context: .
7
+ target: dev
8
+ entrypoint: 'sh docker-entrypoint.sh'
9
+ command: 'djin'
10
+ tty: true
11
+ stdin_open: true
5
12
  volumes:
6
13
  - .:/usr/src/djin
14
+ depends_on:
15
+ - gitserver
16
+
17
+ gitserver:
18
+ image: catks/gitserver-http:0.1.0
19
+ volumes:
20
+ - ./docker/git_server/repos/:/var/lib/initial/
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+
3
+ set -e
4
+
5
+ bundle check || bundle install
6
+
7
+ exec bundle exec "$@"
@@ -1,5 +1,5 @@
1
1
  ---
2
- djin_version: '0.10.0'
2
+ djin_version: '0.11.0'
3
3
 
4
4
  include:
5
5
  - file: 'djin_lib/test.yml'
@@ -1,4 +1,4 @@
1
- djin_version: '0.10.0'
1
+ djin_version: '0.11.0'
2
2
 
3
3
  _default_run_options: &default_run_options
4
4
  options: "--rm --entrypoint=''"
@@ -1,4 +1,4 @@
1
- djin_version: '0.10.0'
1
+ djin_version: '0.11.0'
2
2
 
3
3
  include:
4
4
  - file: '.djin/server_tasks.yml'
@@ -0,0 +1,9 @@
1
+ djin_version: '0.11.0'
2
+
3
+ include:
4
+ - git: 'https://gitserver/myrepo.git'
5
+ version: 'master'
6
+ file: 'examples/djin_lib/test.yml'
7
+ context:
8
+ variables:
9
+ namespace: 'remote:'
@@ -9,22 +9,26 @@ require 'vseries'
9
9
  require 'dry/cli'
10
10
  require 'mustache'
11
11
  require 'optparse'
12
+ require 'git'
12
13
  require 'djin/extensions/hash_extensions'
13
14
  require 'djin/extensions/object_extensions'
14
15
  require 'djin/entities/types'
15
16
  require 'djin/entities/task'
16
- require 'djin/entities/file_config'
17
+ require 'djin/entities/include_config.rb'
18
+ require 'djin/entities/main_config'
17
19
  require 'djin/interpreter/base_command_builder'
18
20
  require 'djin/interpreter/docker_command_builder'
19
21
  require 'djin/interpreter/docker_compose_command_builder'
20
22
  require 'djin/interpreter/local_command_builder'
21
23
  require 'djin/interpreter'
24
+ require 'djin/include_resolver'
22
25
  require 'djin/config_loader'
23
26
  require 'djin/executor'
24
27
  require 'djin/root_cli_parser'
25
28
  require 'djin/cli'
26
29
  require 'djin/task_contract'
27
30
  require 'djin/repositories/task_repository'
31
+ require 'djin/repositories/remote_config_repository'
28
32
  require 'djin/memory_cache'
29
33
 
30
34
  module Djin
@@ -32,34 +36,66 @@ module Djin
32
36
 
33
37
  using Djin::ObjectExtensions
34
38
 
35
- def self.load_tasks!(*file_paths)
36
- files = file_paths.presence || RootCliParser.parse![:files] || ['djin.yml']
39
+ class << self
40
+ def load_tasks!(*file_paths)
41
+ files = file_paths.presence || RootCliParser.parse![:files] || ['djin.yml']
37
42
 
38
- file_config = ConfigLoader.load_files!(*files)
43
+ file_config = ConfigLoader.load_files!(*files)
39
44
 
40
- # TODO: Make all tasks be under 'tasks' key, passing only the tasks here
41
- tasks = Interpreter.load!(file_config)
45
+ # TODO: Make all tasks be under 'tasks' key, passing only the tasks here
46
+ tasks = Interpreter.load!(file_config)
42
47
 
43
- @task_repository = TaskRepository.new(tasks)
44
- CLI.load_tasks!(tasks)
45
- rescue Djin::Interpreter::InvalidConfigurationError => e
46
- error_name = e.class.name.split('::').last
47
- abort("[#{error_name}] #{e.message}")
48
- end
48
+ @task_repository = TaskRepository.new(tasks)
49
49
 
50
- def self.tasks
51
- task_repository.all
52
- end
50
+ remote_configs = file_config.include_configs.select { |f| f.type == :remote }
51
+ @remote_config_repository = RemoteConfigRepository.new(remote_configs)
53
52
 
54
- def self.task_repository
55
- @task_repository ||= TaskRepository.new
56
- end
53
+ CLI.load_tasks!(tasks)
54
+ rescue Djin::Interpreter::InvalidConfigurationError => e
55
+ error_name = e.class.name.split('::').last
56
+ abort("[#{error_name}] #{e.message}")
57
+ end
57
58
 
58
- def self.cache
59
- @cache ||= MemoryCache.new
60
- end
59
+ def tasks
60
+ task_repository.all
61
+ end
62
+
63
+ def task_repository
64
+ @task_repository ||= TaskRepository.new
65
+ end
66
+
67
+ def remote_config_repository
68
+ @remote_config_repository ||= RemoteConfigRepository.new
69
+ end
70
+
71
+ def cache
72
+ @cache ||= MemoryCache.new
73
+ end
74
+
75
+ def root_path
76
+ Pathname.new File.expand_path(__dir__ + '/..')
77
+ end
78
+
79
+ def warn(message, type: 'WARNING')
80
+ stderr.puts "[#{type}] #{message}"
81
+ end
82
+
83
+ def warn_once(message, type: 'WARNING')
84
+ return if warnings.include?(message)
85
+
86
+ warn(message, type: type)
87
+
88
+ warnings << message
89
+ end
90
+
91
+ def stderr
92
+ $stderr
93
+ end
94
+
95
+ private
61
96
 
62
- def self.root_path
63
- Pathname.new File.expand_path(File.dirname(__FILE__) + '/..')
97
+ def warnings
98
+ @warnings ||= []
99
+ end
64
100
  end
65
101
  end
@@ -37,7 +37,33 @@ module Djin
37
37
  end
38
38
  end
39
39
 
40
+ module RemoteConfig
41
+ class Fetch < Dry::CLI::Command
42
+ desc 'Fetchs missing remote configs'
43
+
44
+ def call(*)
45
+ Djin.remote_config_repository.fetch_all
46
+ end
47
+ end
48
+
49
+ class Clear < Dry::CLI::Command
50
+ desc 'clear downloaded remote configs'
51
+ option :all,
52
+ type: :boolean,
53
+ default: false,
54
+ desc: 'Remove all remote configs, not only the ones referenced in the current djin file'
55
+
56
+ def call(all:)
57
+ return Djin.remote_config_repository.clear_all if all
58
+
59
+ Djin.remote_config_repository.clear
60
+ end
61
+ end
62
+ end
63
+
40
64
  register '-f', File, aliases: ['--file']
41
65
  register '--version', Version, aliases: ['-v']
66
+ register 'remote-config fetch', RemoteConfig::Fetch
67
+ register 'remote-config clear', RemoteConfig::Clear
42
68
  end
43
69
  end
@@ -4,11 +4,13 @@ module Djin
4
4
  # TODO: Refactor this class to be the Interpreter
5
5
  # class and use the current interpreter as
6
6
  # a TaskLoader
7
+
8
+ # rubocop:disable Metrics/ClassLength
7
9
  class ConfigLoader
8
10
  using Djin::HashExtensions
9
11
  RESERVED_WORDS = %w[djin_version variables tasks include].freeze
10
12
 
11
- # CHange Base Error
13
+ # Change Base Error
12
14
  FileNotFoundError = Class.new(Interpreter::InvalidConfigurationError)
13
15
 
14
16
  def self.load_files!(*files, runtime_config: {}, base_directory: '.')
@@ -25,7 +27,7 @@ module Djin
25
27
  @base_directory = Pathname.new(base_directory)
26
28
  @template_file = @base_directory.join(template_file_path)
27
29
 
28
- raise FileNotFoundError, "File '#{@template_file}' not found" unless @template_file.exist?
30
+ file_not_found!(@template_file) unless @template_file.exist?
29
31
 
30
32
  @template_file_content = Djin.cache.fetch(@template_file.realpath.to_s) { @template_file.read }
31
33
  @runtime_config = runtime_config
@@ -33,6 +35,7 @@ module Djin
33
35
 
34
36
  def load!
35
37
  validate_version!
38
+ validate_missing_config!
36
39
 
37
40
  file_config
38
41
  end
@@ -40,11 +43,12 @@ module Djin
40
43
  private
41
44
 
42
45
  def file_config
43
- FileConfig.new(
46
+ MainConfig.new(
44
47
  djin_version: version,
45
48
  variables: variables,
46
49
  tasks: tasks,
47
- raw_tasks: raw_tasks
50
+ raw_tasks: raw_tasks,
51
+ include_configs: @include_configs || []
48
52
  )
49
53
  end
50
54
 
@@ -66,8 +70,11 @@ module Djin
66
70
  end
67
71
 
68
72
  def legacy_tasks
69
- warn '[DEPRECATED] Root tasks are deprecated and will be removed in Djin 1.0.0,' \
70
- ' put the tasks under \'tasks\' keyword'
73
+ Djin.warn_once(
74
+ 'Root tasks are deprecated and will be removed in Djin 1.0.0,' \
75
+ ' put the tasks under \'tasks\' keyword',
76
+ type: 'DEPRECATED'
77
+ )
71
78
 
72
79
  rendered_djin_config.except(*RESERVED_WORDS).reject { |task| task.start_with?('_') }
73
80
  end
@@ -94,11 +101,37 @@ module Djin
94
101
  included_config.raw_tasks
95
102
  end
96
103
 
104
+ # TODO: Rename method
97
105
  def included_config
98
- @included_config ||= raw_djin_config['include']&.map do |tasks_reference|
99
- ConfigLoader.load!(tasks_reference['file'], base_directory: @template_file.dirname,
100
- runtime_config: tasks_reference['context'] || {})
101
- end&.reduce(:deep_merge)
106
+ @included_config ||= begin
107
+ present_include_configs&.map do |present_include|
108
+ ConfigLoader.load!(present_include.file, base_directory: @template_file.dirname,
109
+ # TODO: Rename to context_config
110
+ runtime_config: present_include.context)
111
+ end&.reduce(:deep_merge)
112
+ end
113
+ end
114
+
115
+ def present_include_configs
116
+ include_configs&.select(&:present?)
117
+ end
118
+
119
+ def missing_include_configs
120
+ include_configs&.select(&:missing?)
121
+ end
122
+
123
+ # TODO: Refactor to move include methods to a specific IncludeConfigLoader, maybe rename IncludeResolver
124
+ def include_configs
125
+ @include_configs ||= begin
126
+ # TODO: Rename the resolved variables
127
+ resolver = IncludeResolver.new(base_directory: @template_file.dirname)
128
+
129
+ include_djin_config = raw_djin_config['include'] || []
130
+
131
+ include_djin_config.map do |include_config|
132
+ resolver.call(include_config)
133
+ end
134
+ end
102
135
  end
103
136
 
104
137
  def args
@@ -142,5 +175,26 @@ module Djin
142
175
 
143
176
  raise Interpreter::VersionNotSupportedError, "Version #{version} is not supported, use #{Djin::VERSION} or higher"
144
177
  end
178
+
179
+ def validate_missing_config!
180
+ missing_include_configs.each do |ic|
181
+ file_not_found!(ic.full_path) if ic.type == :local
182
+
183
+ missing_file_remote_error = "#{ic.git} exists but is missing %s," \
184
+ 'if the file exists in upstream run djin remote-config fetch to fix'
185
+
186
+ file_not_found!(ic.full_path, missing_file_remote_error) if ic.type == :remote && ic.repository_fetched?
187
+
188
+ if ic.type == :remote
189
+ Djin.warn_once "Missing #{ic.git} with version '#{ic.version}', " \
190
+ 'run `djin remote-config fetch` to fetch the config'
191
+ end
192
+ end
193
+ end
194
+
195
+ def file_not_found!(filename, message = "File '%s' not found")
196
+ raise FileNotFoundError, message % filename
197
+ end
145
198
  end
199
+ # rubocop:enable Metrics/ClassLength
146
200
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ class IncludeConfig < Dry::Struct
5
+ attribute :file, Types::String
6
+ attribute :base_directory, Types::String
7
+ attribute :context, Types::Hash.default({}.freeze)
8
+ attribute :git, Types::String.optional.default(nil)
9
+ attribute :version, Types::String.default('master')
10
+ attribute :missing, Types::Bool.optional.default(nil)
11
+
12
+ using Djin::ObjectExtensions
13
+
14
+ include Dry::Equalizer(:git, :version, :file, :context)
15
+
16
+ def type
17
+ @type ||= git.present? ? :remote : :local
18
+ end
19
+
20
+ def present?
21
+ !missing?
22
+ end
23
+
24
+ def missing?
25
+ missing
26
+ end
27
+
28
+ def full_path
29
+ base_directory_pathname.join(file).expand_path
30
+ end
31
+
32
+ def repository_fetched?
33
+ @repository_fetched ||= base_directory_pathname.join(folder_name).exist?
34
+ end
35
+
36
+ # TODO: Rethink
37
+ def folder_name
38
+ @folder_name ||= "#{git.split('/').last.chomp('.git')}@#{version}"
39
+ end
40
+
41
+ private
42
+
43
+ def base_directory_pathname
44
+ @base_directory_pathname ||= Pathname.new(base_directory)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ module IncludeConfig
5
+ class Base < Dry::Struct
6
+ attribute :file, Types::String
7
+ attribute :context, Types::Hash.default({}.freeze)
8
+
9
+ include Dry::Equalizer(:file, :context)
10
+
11
+ def type
12
+ raise NotImplementedError
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ module IncludeConfig
5
+ class Local < Dry::Struct
6
+ attribute :file, Types::String
7
+ attribute :context, Types::Hash.default({}.freeze)
8
+
9
+ include Dry::Equalizer(:file, :context)
10
+
11
+ def type
12
+ :local
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Djin
4
- class FileConfig < Dry::Struct
4
+ class MainConfig < Dry::Struct
5
5
  using HashExtensions
6
6
 
7
7
  attribute :djin_version, Types::String
@@ -9,6 +9,7 @@ module Djin
9
9
  attribute :tasks, Types::Hash
10
10
  attribute :raw_tasks, Types::Hash
11
11
  # TODO: Add env and args
12
+ attribute :include_configs, Types::Array.of(Djin::IncludeConfig).default([].freeze)
12
13
 
13
14
  include Dry::Equalizer(:djin_version, :variables, :tasks, :raw_tasks)
14
15
 
@@ -19,13 +20,13 @@ module Djin
19
20
  def merge(file_config)
20
21
  merged_hash = to_h.merge(file_config.to_h)
21
22
 
22
- FileConfig.new(merged_hash)
23
+ MainConfig.new(merged_hash)
23
24
  end
24
25
 
25
26
  def deep_merge(file_config)
26
27
  merged_hash = to_h.deep_merge(file_config.to_h)
27
28
 
28
- FileConfig.new(merged_hash)
29
+ MainConfig.new(merged_hash)
29
30
  end
30
31
  end
31
32
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ class IncludeResolver
5
+ using ObjectExtensions
6
+ using HashExtensions
7
+
8
+ def initialize(base_directory: '.', remote_directory: '~/.djin/remote', entity_class: Djin::IncludeConfig)
9
+ # TODO: Use chain of responsability
10
+ @base_directory = Pathname.new(base_directory)
11
+ @remote_directory = Pathname.new(remote_directory)
12
+ @entity_class = entity_class
13
+ end
14
+
15
+ def call(params)
16
+ include_config_params = remote_handler(params)
17
+ include_config_params ||= local_handler(params)
18
+
19
+ build_entity(include_config_params)
20
+ end
21
+
22
+ private
23
+
24
+ def remote_handler(params)
25
+ return if params['git'].blank?
26
+
27
+ version = params['version'] || 'master'
28
+ # TODO: Extract RemoteConfig git_folder in IncludeConfig to another place and use it here
29
+ # Maybe create a optional git_folder attribute and fill it in here?
30
+ git_folder = "#{params['git'].split('/').last.chomp('.git')}@#{version}"
31
+
32
+ # TODO: Use RemoteConfigRepository
33
+ remote_file = @remote_directory.join(git_folder).join(params['file']).expand_path
34
+
35
+ missing = !remote_file.exist?
36
+
37
+ params.merge(missing: missing, file: remote_file.to_s, base_directory: @remote_directory.expand_path.to_s)
38
+ end
39
+
40
+ def local_handler(params)
41
+ # TODO: Mark not existing files as missing and handle all the missing files
42
+ missing = !@base_directory.join(params['file']).exist?
43
+ params.merge(missing: missing, base_directory: @base_directory.expand_path.to_s)
44
+ end
45
+
46
+ def build_entity(params)
47
+ @entity_class.new(**params.symbolize_keys)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ class RemoteConfigRepository
5
+ attr_accessor :base_path
6
+
7
+ def initialize(remote_configs, base_path: Pathname.new('~/.djin/remote'), stderr: Djin.stderr)
8
+ @remote_configs = remote_configs
9
+ @base_path = base_path
10
+ @stderr = stderr
11
+ end
12
+
13
+ def add(remote_config)
14
+ remote_configs << remote_config
15
+ end
16
+
17
+ def find(**filters)
18
+ remote_configs.select do |remote_config|
19
+ filters.reduce(true) do |memo, (filter_key, filter_value)|
20
+ memo && remote_config.public_send(filter_key) == filter_value
21
+ end
22
+ end
23
+ end
24
+
25
+ def fetch_all
26
+ remote_configs.each do |rc|
27
+ git_folder = base_path.join(rc.folder_name).expand_path
28
+
29
+ # TODO: Extract STDEER Output, maybe publishing events and subscribing a observer for the logs.
30
+ stderr.puts "Remote Path: #{base_path.expand_path}"
31
+ git_repo = rc.missing? ? clone_repo(git_folder, rc) : fetch_repo(git_folder, rc)
32
+
33
+ stderr.puts "Checking out to '#{rc.version}'"
34
+ git_repo.checkout(rc.version)
35
+ git_repo.pull
36
+ end
37
+ end
38
+
39
+ def clear
40
+ remote_configs.each do |rc|
41
+ git_folder = base_path.join(rc.folder_name)
42
+
43
+ stderr.puts "Removing #{rc.folder_name} repository..."
44
+ `rm -rf #{git_folder}`
45
+ end
46
+ end
47
+
48
+ def clear_all
49
+ remove_remote_folder
50
+ end
51
+
52
+ def remote_configs
53
+ @remote_configs ||= []
54
+ end
55
+
56
+ private
57
+
58
+ attr_accessor :stderr
59
+
60
+ def remove_remote_folder
61
+ stderr.puts "Removing #{base_path}..."
62
+ `rm -rf #{base_path}`
63
+ end
64
+
65
+ def clone_repo(git_folder, remote_config)
66
+ stderr.puts "Missing #{remote_config.folder_name} repository, cloning in #{git_folder}"
67
+ Git.clone(remote_config.git, git_folder, progress: true)
68
+ end
69
+
70
+ def fetch_repo(git_folder, remote_config)
71
+ stderr.puts "#{remote_config.git} repository already cloned, fetching..."
72
+ git_repo = Git.open(git_folder)
73
+ git_repo.fetch
74
+
75
+ git_repo
76
+ end
77
+ end
78
+ end
@@ -22,6 +22,10 @@ module Djin
22
22
  opts.on('-h', '--help') do
23
23
  throw :root_cli_exit
24
24
  end
25
+
26
+ opts.on('--all') do
27
+ throw :root_cli_exit
28
+ end
25
29
  end.parse(args)
26
30
  end
27
31
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Djin
4
- VERSION = '0.10.0'
4
+ VERSION = '0.11.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: djin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Atkinson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-09 00:00:00.000000000 Z
11
+ date: 2021-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-cli
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 1.5.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: git
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.8.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.8.1
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: mustache
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -164,6 +178,20 @@ dependencies:
164
178
  - - ">="
165
179
  - !ruby/object:Gem::Version
166
180
  version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: simplecov
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 0.17.0
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 0.17.0
167
195
  description:
168
196
  email:
169
197
  - carlos.atks@gmail.com
@@ -174,6 +202,7 @@ extra_rdoc_files: []
174
202
  files:
175
203
  - ".github/workflows/ruby.yml"
176
204
  - ".gitignore"
205
+ - ".irbrc"
177
206
  - ".rspec"
178
207
  - ".rubocop.yml"
179
208
  - ".rubocop_todo.yml"
@@ -191,26 +220,33 @@ files:
191
220
  - djin.gemspec
192
221
  - djin.yml
193
222
  - docker-compose.yml
223
+ - docker-entrypoint.sh
194
224
  - examples/djin.yml
195
225
  - examples/djin_lib/test.yml
196
226
  - examples/local_tasks/.djin/server_tasks.yml
197
227
  - examples/local_tasks/djin.yml
228
+ - examples/remote_tasks/djin.yml
198
229
  - exe/djin
199
230
  - lib/djin.rb
200
231
  - lib/djin/cli.rb
201
232
  - lib/djin/config_loader.rb
202
- - lib/djin/entities/file_config.rb
233
+ - lib/djin/entities/include_config.rb
234
+ - lib/djin/entities/include_config/base.rb
235
+ - lib/djin/entities/include_config/local.rb
236
+ - lib/djin/entities/main_config.rb
203
237
  - lib/djin/entities/task.rb
204
238
  - lib/djin/entities/types.rb
205
239
  - lib/djin/executor.rb
206
240
  - lib/djin/extensions/hash_extensions.rb
207
241
  - lib/djin/extensions/object_extensions.rb
242
+ - lib/djin/include_resolver.rb
208
243
  - lib/djin/interpreter.rb
209
244
  - lib/djin/interpreter/base_command_builder.rb
210
245
  - lib/djin/interpreter/docker_command_builder.rb
211
246
  - lib/djin/interpreter/docker_compose_command_builder.rb
212
247
  - lib/djin/interpreter/local_command_builder.rb
213
248
  - lib/djin/memory_cache.rb
249
+ - lib/djin/repositories/remote_config_repository.rb
214
250
  - lib/djin/repositories/task_repository.rb
215
251
  - lib/djin/root_cli_parser.rb
216
252
  - lib/djin/task_contract.rb
@@ -234,8 +270,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
234
270
  - !ruby/object:Gem::Version
235
271
  version: '0'
236
272
  requirements: []
237
- rubyforge_project:
238
- rubygems_version: 2.7.6
273
+ rubygems_version: 3.0.3
239
274
  signing_key:
240
275
  specification_version: 4
241
276
  summary: djin is a make-like utility for docker containers