Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modules section for dip #173

Merged
merged 1 commit into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,92 @@ services:

The container will run using the same user ID as your host machine.

### Modules

Modules are defined as array in `modules` section of dip.yml, modules are stored in `.dip` subdirectory of dip.yml directory.

The main purpose of modules is to improve maintainability for a group of projects.
Imagine having multiple gems which are managed with dip, each of them has the same commands, so to change one command in dip you need to update all gems individualy.

With `modules` you can define a group of modules for dip.

For example having setup as this:

```yml
# ./dip.yml
modules:
- sasts
- rails

...
```

```yml
# ./.dip/sasts.yml
interaction:
brakeman:
description: Check brakeman sast
command: docker run ...
```

```yml
# ./.dip/rails.yml
interaction:
annotate:
description: Run annotate command
service: backend
command: bundle exec annotate
```

Will be expanded to:

```yml
# resultant configuration
interaction:
brakeman:
description: Check brakeman sast
command: docker run ...
annotate:
description: Run annotate command
service: backend
command: bundle exec annotate
```

Imagine `.dip` to be a submodule so it can be managed only in one place.

If you want to override module command, you can redefine it in dip.yml

```yml
# ./dip.yml
modules:
- sasts

interaction:
brakeman:
description: Check brakeman sast
command: docker run another-image ...
```

```yml
# ./.dip/sasts.yml
interaction:
brakeman:
description: Check brakeman sast
command: docker run some-image ...
```

Will be expanded to:

```yml
# resultant configuration
interaction:
brakeman:
description: Check brakeman sast
command: docker run another-image ...
```

Nested modules are not supported.

### dip run

Run commands defined within the `interaction` section of dip.yml
Expand Down
30 changes: 28 additions & 2 deletions lib/dip/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ def exist?
file_path&.exist?
end

def modules_dir
file_path.dirname / ".dip"
end

private

attr_reader :override
Expand Down Expand Up @@ -90,6 +94,10 @@ def file_path
finder.file_path
end

def module_file(filename)
finder.modules_dir / "#{filename}.yml"
end

def exist?
finder.exist?
end
Expand Down Expand Up @@ -125,10 +133,28 @@ def config
"Please upgrade your dip!"
end

base_config = {}

if (modules = config[:modules])
raise Dip::Error, "Modules should be specified as array" unless modules.is_a?(Array)

modules.each do |m|
file = module_file(m)
raise Dip::Error, "Could not find module `#{m}`" unless file.exist?

module_config = self.class.load_yaml(file)
raise Dip::Error, "Nested modules are not supported" if module_config[:modules]

base_config.deep_merge!(module_config)
end
end

base_config.deep_merge!(config)

override_finder = ConfigFinder.new(work_dir, override: true)
config.deep_merge!(self.class.load_yaml(override_finder.file_path)) if override_finder.exist?
base_config.deep_merge!(self.class.load_yaml(override_finder.file_path)) if override_finder.exist?

@config = CONFIG_DEFAULTS.merge(config)
@config = CONFIG_DEFAULTS.merge(base_config)
end

def config_missing_error(config_key)
Expand Down
3 changes: 3 additions & 0 deletions spec/fixtures/modules/.dip/first.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interaction:
test_app:
service: test_backend
3 changes: 3 additions & 0 deletions spec/fixtures/modules/.dip/last.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interaction:
test_app:
service: test_frontend
3 changes: 3 additions & 0 deletions spec/fixtures/modules/.dip/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interaction:
app:
service: backend
17 changes: 17 additions & 0 deletions spec/fixtures/modules/dip.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: '2'

modules:
- first
- last
- test

environment:
FOO: bar

compose:
files:
- docker-compose.yml

interaction:
app1:
service: frontend
4 changes: 4 additions & 0 deletions spec/fixtures/unknown_module/dip.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
version: '2'

modules:
- unknown
24 changes: 24 additions & 0 deletions spec/lib/dip/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,30 @@
end
end

context "when config has modules", :env do
let(:env) { {"DIP_FILE" => fixture_path("modules", "dip.yml")} }

it "expands modules to main config" do
expect(subject.interaction[:app][:service]).to eq "backend"
end

it "merges modules to main config" do
expect(subject.interaction[:app1][:service]).to eq "frontend"
end

it "overrides first defined module with the last one" do
expect(subject.interaction[:test_app][:service]).to eq "test_frontend"
end
end

context "when config has unknown module", :env do
let(:env) { {"DIP_FILE" => fixture_path("unknown_module", "dip.yml")} }

it "raises and error" do
expect { subject.interaction }.to raise_error(Dip::Error, /Could not find module/)
end
end

context "when config located two levels higher and overridden at one level higher", :env do
subject { described_class.new(fixture_path("cascade", "sub_a", "sub_b")) }

Expand Down
Loading