diff --git a/README.md b/README.md index cd9cbd0..f9e2713 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/lib/dip/config.rb b/lib/dip/config.rb index 7e506b6..393af11 100644 --- a/lib/dip/config.rb +++ b/lib/dip/config.rb @@ -43,6 +43,10 @@ def exist? file_path&.exist? end + def modules_dir + file_path.dirname / ".dip" + end + private attr_reader :override @@ -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 @@ -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) diff --git a/spec/fixtures/modules/.dip/first.yml b/spec/fixtures/modules/.dip/first.yml new file mode 100644 index 0000000..cacd60d --- /dev/null +++ b/spec/fixtures/modules/.dip/first.yml @@ -0,0 +1,3 @@ +interaction: + test_app: + service: test_backend diff --git a/spec/fixtures/modules/.dip/last.yml b/spec/fixtures/modules/.dip/last.yml new file mode 100644 index 0000000..2fc667d --- /dev/null +++ b/spec/fixtures/modules/.dip/last.yml @@ -0,0 +1,3 @@ +interaction: + test_app: + service: test_frontend diff --git a/spec/fixtures/modules/.dip/test.yml b/spec/fixtures/modules/.dip/test.yml new file mode 100644 index 0000000..22f9324 --- /dev/null +++ b/spec/fixtures/modules/.dip/test.yml @@ -0,0 +1,3 @@ +interaction: + app: + service: backend diff --git a/spec/fixtures/modules/dip.yml b/spec/fixtures/modules/dip.yml new file mode 100644 index 0000000..f051bb2 --- /dev/null +++ b/spec/fixtures/modules/dip.yml @@ -0,0 +1,17 @@ +version: '2' + +modules: + - first + - last + - test + +environment: + FOO: bar + +compose: + files: + - docker-compose.yml + +interaction: + app1: + service: frontend diff --git a/spec/fixtures/unknown_module/dip.yml b/spec/fixtures/unknown_module/dip.yml new file mode 100644 index 0000000..8c9e794 --- /dev/null +++ b/spec/fixtures/unknown_module/dip.yml @@ -0,0 +1,4 @@ +version: '2' + +modules: + - unknown diff --git a/spec/lib/dip/config_spec.rb b/spec/lib/dip/config_spec.rb index 165a749..cdda41e 100644 --- a/spec/lib/dip/config_spec.rb +++ b/spec/lib/dip/config_spec.rb @@ -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")) }