From 6c704fd99e1fd5a86dd2eccc8b192a009546cc96 Mon Sep 17 00:00:00 2001
From: Grzegorz Bizon <grzesiek.bizon@gmail.com>
Date: Sat, 6 Aug 2016 16:45:02 +0200
Subject: [PATCH 1/9] Inject dependencies into each CI config entry node

---
 lib/gitlab/ci/config/node/configurable.rb |  2 +-
 lib/gitlab/ci/config/node/entry.rb        | 11 +++++++----
 lib/gitlab/ci/config/node/global.rb       |  6 +++++-
 lib/gitlab/ci/config/node/job.rb          |  2 +-
 lib/gitlab/ci/config/node/jobs.rb         |  2 +-
 5 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb
index 2de82d40c9d..fb3604edac2 100644
--- a/lib/gitlab/ci/config/node/configurable.rb
+++ b/lib/gitlab/ci/config/node/configurable.rb
@@ -25,7 +25,7 @@ module Gitlab
 
           private
 
-          def compose!
+          def compose!(_deps)
             self.class.nodes.each do |key, factory|
               factory
                 .value(@config[key])
diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb
index 0c782c422b5..40ffa85d6da 100644
--- a/lib/gitlab/ci/config/node/entry.rb
+++ b/lib/gitlab/ci/config/node/entry.rb
@@ -20,11 +20,14 @@ module Gitlab
             @validator.validate(:new)
           end
 
-          def process!
+          def process!(deps = nil)
             return unless valid?
 
-            compose!
-            descendants.each(&:process!)
+            compose!(deps)
+
+            descendants.each do |entry|
+              entry.process!(deps)
+            end
           end
 
           def leaf?
@@ -76,7 +79,7 @@ module Gitlab
 
           private
 
-          def compose!
+          def compose!(_deps)
           end
         end
       end
diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb
index ccd539fb003..4d65309cf44 100644
--- a/lib/gitlab/ci/config/node/global.rb
+++ b/lib/gitlab/ci/config/node/global.rb
@@ -36,9 +36,13 @@ module Gitlab
           helpers :before_script, :image, :services, :after_script,
                   :variables, :stages, :types, :cache, :jobs
 
+          def process!(_deps = nil)
+            super(self)
+          end
+
           private
 
-          def compose!
+          def compose!(_deps)
             super
 
             compose_jobs!
diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb
index e84737acbb9..55f34b446e5 100644
--- a/lib/gitlab/ci/config/node/job.rb
+++ b/lib/gitlab/ci/config/node/job.rb
@@ -107,7 +107,7 @@ module Gitlab
               after_script: after_script }
           end
 
-          def compose!
+          def compose!(_deps)
             super
 
             if type_defined? && !stage_defined?
diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb
index 51683c82ceb..a2a8554ed19 100644
--- a/lib/gitlab/ci/config/node/jobs.rb
+++ b/lib/gitlab/ci/config/node/jobs.rb
@@ -28,7 +28,7 @@ module Gitlab
 
           private
 
-          def compose!
+          def compose!(_deps)
             @config.each do |name, config|
               node = hidden?(name) ? Node::HiddenJob : Node::Job
 
-- 
GitLab


From 700078e8e43626a9be4fd752e35cd27ac526b410 Mon Sep 17 00:00:00 2001
From: Grzegorz Bizon <grzesiek.bizon@gmail.com>
Date: Sat, 6 Aug 2016 17:12:59 +0200
Subject: [PATCH 2/9] Rename unspecified and undefined CI config nodes

---
 lib/gitlab/ci/config/node/factory.rb          |  8 ++--
 lib/gitlab/ci/config/node/null.rb             | 34 ---------------
 lib/gitlab/ci/config/node/undefined.rb        | 27 ++++++++++--
 lib/gitlab/ci/config/node/unspecified.rb      | 19 +++++++++
 .../lib/gitlab/ci/config/node/factory_spec.rb |  3 +-
 spec/lib/gitlab/ci/config/node/global_spec.rb |  4 +-
 spec/lib/gitlab/ci/config/node/null_spec.rb   | 41 -------------------
 .../gitlab/ci/config/node/undefined_spec.rb   | 33 +++++++++------
 .../gitlab/ci/config/node/unspecified_spec.rb | 32 +++++++++++++++
 9 files changed, 103 insertions(+), 98 deletions(-)
 delete mode 100644 lib/gitlab/ci/config/node/null.rb
 create mode 100644 lib/gitlab/ci/config/node/unspecified.rb
 delete mode 100644 spec/lib/gitlab/ci/config/node/null_spec.rb
 create mode 100644 spec/lib/gitlab/ci/config/node/unspecified_spec.rb

diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb
index 707b052e6a8..5387f29ad59 100644
--- a/lib/gitlab/ci/config/node/factory.rb
+++ b/lib/gitlab/ci/config/node/factory.rb
@@ -37,8 +37,8 @@ module Gitlab
             # See issue #18775.
             #
             if @value.nil?
-              Node::Undefined.new(
-                fabricate_undefined
+              Node::Unspecified.new(
+                fabricate_unspecified
               )
             else
               fabricate(@node, @value)
@@ -47,13 +47,13 @@ module Gitlab
 
           private
 
-          def fabricate_undefined
+          def fabricate_unspecified
             ##
             # If node has a default value we fabricate concrete node
             # with default value.
             #
             if @node.default.nil?
-              fabricate(Node::Null)
+              fabricate(Node::Undefined)
             else
               fabricate(@node, @node.default)
             end
diff --git a/lib/gitlab/ci/config/node/null.rb b/lib/gitlab/ci/config/node/null.rb
deleted file mode 100644
index 88a5f53f13c..00000000000
--- a/lib/gitlab/ci/config/node/null.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module Gitlab
-  module Ci
-    class Config
-      module Node
-        ##
-        # This class represents an undefined node.
-        #
-        # Implements the Null Object pattern.
-        #
-        class Null < Entry
-          def value
-            nil
-          end
-
-          def valid?
-            true
-          end
-
-          def errors
-            []
-          end
-
-          def specified?
-            false
-          end
-
-          def relevant?
-            false
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb
index 45fef8c3ae5..33e78023539 100644
--- a/lib/gitlab/ci/config/node/undefined.rb
+++ b/lib/gitlab/ci/config/node/undefined.rb
@@ -3,15 +3,34 @@ module Gitlab
     class Config
       module Node
         ##
-        # This class represents an unspecified entry node.
+        # This class represents an undefined node.
         #
-        # It decorates original entry adding method that indicates it is
-        # unspecified.
+        # Implements the Null Object pattern.
         #
-        class Undefined < SimpleDelegator
+        class Undefined < Entry
+          def initialize(*)
+            super(nil)
+          end
+
+          def value
+            nil
+          end
+
+          def valid?
+            true
+          end
+
+          def errors
+            []
+          end
+
           def specified?
             false
           end
+
+          def relevant?
+            false
+          end
         end
       end
     end
diff --git a/lib/gitlab/ci/config/node/unspecified.rb b/lib/gitlab/ci/config/node/unspecified.rb
new file mode 100644
index 00000000000..a7d1f6131b8
--- /dev/null
+++ b/lib/gitlab/ci/config/node/unspecified.rb
@@ -0,0 +1,19 @@
+module Gitlab
+  module Ci
+    class Config
+      module Node
+        ##
+        # This class represents an unspecified entry node.
+        #
+        # It decorates original entry adding method that indicates it is
+        # unspecified.
+        #
+        class Unspecified < SimpleDelegator
+          def specified?
+            false
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb
index d26185ba585..a699089c563 100644
--- a/spec/lib/gitlab/ci/config/node/factory_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb
@@ -65,7 +65,8 @@ describe Gitlab::Ci::Config::Node::Factory do
           .value(nil)
           .create!
 
-        expect(entry).to be_an_instance_of Gitlab::Ci::Config::Node::Undefined
+        expect(entry)
+          .to be_an_instance_of Gitlab::Ci::Config::Node::Unspecified
       end
     end
 
diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb
index 2f87d270b36..dfe75bd09e5 100644
--- a/spec/lib/gitlab/ci/config/node/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/global_spec.rb
@@ -156,9 +156,9 @@ describe Gitlab::Ci::Config::Node::Global do
           expect(global.descendants.count).to eq 8
         end
 
-        it 'contains undefined nodes' do
+        it 'contains unspecified nodes' do
           expect(global.descendants.first)
-            .to be_an_instance_of Gitlab::Ci::Config::Node::Undefined
+            .to be_an_instance_of Gitlab::Ci::Config::Node::Unspecified
         end
       end
 
diff --git a/spec/lib/gitlab/ci/config/node/null_spec.rb b/spec/lib/gitlab/ci/config/node/null_spec.rb
deleted file mode 100644
index 1ab5478dcfa..00000000000
--- a/spec/lib/gitlab/ci/config/node/null_spec.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Ci::Config::Node::Null do
-  let(:null) { described_class.new(nil) }
-
-  describe '#leaf?' do
-    it 'is leaf node' do
-      expect(null).to be_leaf
-    end
-  end
-
-  describe '#valid?' do
-    it 'is always valid' do
-      expect(null).to be_valid
-    end
-  end
-
-  describe '#errors' do
-    it 'is does not contain errors' do
-      expect(null.errors).to be_empty
-    end
-  end
-
-  describe '#value' do
-    it 'returns nil' do
-      expect(null.value).to eq nil
-    end
-  end
-
-  describe '#relevant?' do
-    it 'is not relevant' do
-      expect(null.relevant?).to eq false
-    end
-  end
-
-  describe '#specified?' do
-    it 'is not defined' do
-      expect(null.specified?).to eq false
-    end
-  end
-end
diff --git a/spec/lib/gitlab/ci/config/node/undefined_spec.rb b/spec/lib/gitlab/ci/config/node/undefined_spec.rb
index 2d43e1c1a9d..6bde8602963 100644
--- a/spec/lib/gitlab/ci/config/node/undefined_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/undefined_spec.rb
@@ -1,32 +1,41 @@
 require 'spec_helper'
 
 describe Gitlab::Ci::Config::Node::Undefined do
-  let(:undefined) { described_class.new(entry) }
-  let(:entry) { spy('Entry') }
+  let(:entry) { described_class.new }
+
+  describe '#leaf?' do
+    it 'is leaf node' do
+      expect(entry).to be_leaf
+    end
+  end
 
   describe '#valid?' do
-    it 'delegates method to entry' do
-      expect(undefined.valid).to eq entry
+    it 'is always valid' do
+      expect(entry).to be_valid
     end
   end
 
   describe '#errors' do
-    it 'delegates method to entry' do
-      expect(undefined.errors).to eq entry
+    it 'is does not contain errors' do
+      expect(entry.errors).to be_empty
     end
   end
 
   describe '#value' do
-    it 'delegates method to entry' do
-      expect(undefined.value).to eq entry
+    it 'returns nil' do
+      expect(entry.value).to eq nil
     end
   end
 
-  describe '#specified?' do
-    it 'is always false' do
-      allow(entry).to receive(:specified?).and_return(true)
+  describe '#relevant?' do
+    it 'is not relevant' do
+      expect(entry.relevant?).to eq false
+    end
+  end
 
-      expect(undefined.specified?).to be false
+  describe '#specified?' do
+    it 'is not defined' do
+      expect(entry.specified?).to eq false
     end
   end
 end
diff --git a/spec/lib/gitlab/ci/config/node/unspecified_spec.rb b/spec/lib/gitlab/ci/config/node/unspecified_spec.rb
new file mode 100644
index 00000000000..ba3ceef24ce
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/unspecified_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Unspecified do
+  let(:unspecified) { described_class.new(entry) }
+  let(:entry) { spy('Entry') }
+
+  describe '#valid?' do
+    it 'delegates method to entry' do
+      expect(unspecified.valid?).to eq entry
+    end
+  end
+
+  describe '#errors' do
+    it 'delegates method to entry' do
+      expect(unspecified.errors).to eq entry
+    end
+  end
+
+  describe '#value' do
+    it 'delegates method to entry' do
+      expect(unspecified.value).to eq entry
+    end
+  end
+
+  describe '#specified?' do
+    it 'is always false' do
+      allow(entry).to receive(:specified?).and_return(true)
+
+      expect(unspecified.specified?).to be false
+    end
+  end
+end
-- 
GitLab


From eaf211c2e393a2eeb5999b9ffec17112cdd52a0c Mon Sep 17 00:00:00 2001
From: Grzegorz Bizon <grzesiek.bizon@gmail.com>
Date: Thu, 25 Aug 2016 13:49:15 +0200
Subject: [PATCH 3/9] Expose compose method in the ci config entry nodes

---
 lib/gitlab/ci/config.rb                   |  2 +-
 lib/gitlab/ci/config/node/configurable.rb | 10 ++++++--
 lib/gitlab/ci/config/node/entry.rb        | 17 ++++++--------
 lib/gitlab/ci/config/node/global.rb       | 14 ++++--------
 lib/gitlab/ci/config/node/job.rb          | 20 ++++++++--------
 lib/gitlab/ci/config/node/jobs.rb         | 28 +++++++++++++----------
 6 files changed, 47 insertions(+), 44 deletions(-)

diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index ae82c0db3f1..bbfa6cf7d05 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -14,7 +14,7 @@ module Gitlab
         @config = Loader.new(config).load!
 
         @global = Node::Global.new(@config)
-        @global.process!
+        @global.compose!
       end
 
       def valid?
diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb
index fb3604edac2..530f95a6251 100644
--- a/lib/gitlab/ci/config/node/configurable.rb
+++ b/lib/gitlab/ci/config/node/configurable.rb
@@ -23,9 +23,9 @@ module Gitlab
             end
           end
 
-          private
+          def compose!(deps)
+            return unless valid?
 
-          def compose!(_deps)
             self.class.nodes.each do |key, factory|
               factory
                 .value(@config[key])
@@ -33,6 +33,12 @@ module Gitlab
 
               @entries[key] = factory.create!
             end
+
+            yield if block_given?
+
+            @entries.each_value do |entry|
+              entry.compose!(deps)
+            end
           end
 
           class_methods do
diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb
index 40ffa85d6da..e02f4bf0a05 100644
--- a/lib/gitlab/ci/config/node/entry.rb
+++ b/lib/gitlab/ci/config/node/entry.rb
@@ -20,14 +20,16 @@ module Gitlab
             @validator.validate(:new)
           end
 
+          # Temporary method
+          #
           def process!(deps = nil)
-            return unless valid?
-
             compose!(deps)
+          end
 
-            descendants.each do |entry|
-              entry.process!(deps)
-            end
+          def compose!(deps = nil)
+            return unless valid?
+
+            yield if block_given?
           end
 
           def leaf?
@@ -76,11 +78,6 @@ module Gitlab
           def self.validator
             Validator
           end
-
-          private
-
-          def compose!(_deps)
-          end
         end
       end
     end
diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb
index 4d65309cf44..2a2943c9288 100644
--- a/lib/gitlab/ci/config/node/global.rb
+++ b/lib/gitlab/ci/config/node/global.rb
@@ -36,19 +36,15 @@ module Gitlab
           helpers :before_script, :image, :services, :after_script,
                   :variables, :stages, :types, :cache, :jobs
 
-          def process!(_deps = nil)
-            super(self)
+          def compose!(_deps = nil)
+            super(self) do
+              compose_jobs!
+              compose_deprecated_entries!
+            end
           end
 
           private
 
-          def compose!(_deps)
-            super
-
-            compose_jobs!
-            compose_deprecated_entries!
-          end
-
           def compose_jobs!
             factory = Node::Factory.new(Node::Jobs)
               .value(@config.except(*self.class.nodes.keys))
diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb
index 55f34b446e5..9317bb40f50 100644
--- a/lib/gitlab/ci/config/node/job.rb
+++ b/lib/gitlab/ci/config/node/job.rb
@@ -82,6 +82,16 @@ module Gitlab
                   :cache, :image, :services, :only, :except, :variables,
                   :artifacts
 
+          def compose!(deps)
+            super do
+              if type_defined? && !stage_defined?
+                @entries[:stage] = @entries[:type]
+              end
+
+              @entries.delete(:type)
+            end
+          end
+
           def name
             @metadata[:name]
           end
@@ -106,16 +116,6 @@ module Gitlab
               artifacts: artifacts,
               after_script: after_script }
           end
-
-          def compose!(_deps)
-            super
-
-            if type_defined? && !stage_defined?
-              @entries[:stage] = @entries[:type]
-            end
-
-            @entries.delete(:type)
-          end
         end
       end
     end
diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb
index a2a8554ed19..45865825e46 100644
--- a/lib/gitlab/ci/config/node/jobs.rb
+++ b/lib/gitlab/ci/config/node/jobs.rb
@@ -26,19 +26,23 @@ module Gitlab
             name.to_s.start_with?('.')
           end
 
-          private
-
-          def compose!(_deps)
-            @config.each do |name, config|
-              node = hidden?(name) ? Node::HiddenJob : Node::Job
-
-              factory = Node::Factory.new(node)
-                .value(config || {})
-                .metadata(name: name)
-                .with(key: name, parent: self,
-                      description: "#{name} job definition.")
+          def compose!(deps = nil)
+            super do
+              @config.each do |name, config|
+                node = hidden?(name) ? Node::HiddenJob : Node::Job
+
+                factory = Node::Factory.new(node)
+                  .value(config || {})
+                  .metadata(name: name)
+                  .with(key: name, parent: self,
+                        description: "#{name} job definition.")
+
+                @entries[name] = factory.create!
+              end
 
-              @entries[name] = factory.create!
+              @entries.each_value do |entry|
+                entry.compose!(deps)
+              end
             end
           end
         end
-- 
GitLab


From 4f837f6690970bde315b8c9d76190ed45f15e49e Mon Sep 17 00:00:00 2001
From: Grzegorz Bizon <grzesiek.bizon@gmail.com>
Date: Thu, 25 Aug 2016 13:58:46 +0200
Subject: [PATCH 4/9] Remove temporary stub method from ci config nodes

---
 lib/gitlab/ci/config/node/configurable.rb     |  2 +-
 lib/gitlab/ci/config/node/entry.rb            |  6 -----
 lib/gitlab/ci/config/node/job.rb              |  2 +-
 spec/lib/gitlab/ci/config/node/cache_spec.rb  |  2 +-
 spec/lib/gitlab/ci/config/node/global_spec.rb | 26 ++++++++++++-------
 spec/lib/gitlab/ci/config/node/job_spec.rb    |  2 +-
 spec/lib/gitlab/ci/config/node/jobs_spec.rb   |  6 ++---
 spec/lib/gitlab/ci/config/node/script_spec.rb |  4 +--
 8 files changed, 24 insertions(+), 26 deletions(-)

diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb
index 530f95a6251..6b7ab2fdaf2 100644
--- a/lib/gitlab/ci/config/node/configurable.rb
+++ b/lib/gitlab/ci/config/node/configurable.rb
@@ -23,7 +23,7 @@ module Gitlab
             end
           end
 
-          def compose!(deps)
+          def compose!(deps = nil)
             return unless valid?
 
             self.class.nodes.each do |key, factory|
diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb
index e02f4bf0a05..907d25a8c49 100644
--- a/lib/gitlab/ci/config/node/entry.rb
+++ b/lib/gitlab/ci/config/node/entry.rb
@@ -20,12 +20,6 @@ module Gitlab
             @validator.validate(:new)
           end
 
-          # Temporary method
-          #
-          def process!(deps = nil)
-            compose!(deps)
-          end
-
           def compose!(deps = nil)
             return unless valid?
 
diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb
index 9317bb40f50..3e6666d751f 100644
--- a/lib/gitlab/ci/config/node/job.rb
+++ b/lib/gitlab/ci/config/node/job.rb
@@ -82,7 +82,7 @@ module Gitlab
                   :cache, :image, :services, :only, :except, :variables,
                   :artifacts
 
-          def compose!(deps)
+          def compose!(deps = nil)
             super do
               if type_defined? && !stage_defined?
                 @entries[:stage] = @entries[:type]
diff --git a/spec/lib/gitlab/ci/config/node/cache_spec.rb b/spec/lib/gitlab/ci/config/node/cache_spec.rb
index 50f619ce26e..e251210949c 100644
--- a/spec/lib/gitlab/ci/config/node/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/cache_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Ci::Config::Node::Cache do
   let(:entry) { described_class.new(config) }
 
   describe 'validations' do
-    before { entry.process! }
+    before { entry.compose! }
 
     context 'when entry config value is correct' do
       let(:config) do
diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb
index dfe75bd09e5..e033b423fdd 100644
--- a/spec/lib/gitlab/ci/config/node/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/global_spec.rb
@@ -27,8 +27,8 @@ describe Gitlab::Ci::Config::Node::Global do
           spinach: { script: 'spinach' } }
       end
 
-      describe '#process!' do
-        before { global.process! }
+      describe '#compose!' do
+        before { global.compose! }
 
         it 'creates nodes hash' do
           expect(global.descendants).to be_an Array
@@ -59,7 +59,7 @@ describe Gitlab::Ci::Config::Node::Global do
         end
       end
 
-      context 'when not processed' do
+      context 'when not composed' do
         describe '#before_script' do
           it 'returns nil' do
             expect(global.before_script).to be nil
@@ -73,8 +73,8 @@ describe Gitlab::Ci::Config::Node::Global do
         end
       end
 
-      context 'when processed' do
-        before { global.process! }
+      context 'when composed' do
+        before { global.compose! }
 
         describe '#before_script' do
           it 'returns correct script' do
@@ -148,8 +148,11 @@ describe Gitlab::Ci::Config::Node::Global do
     end
 
     context 'when most of entires not defined' do
-      let(:hash) { { cache: { key: 'a' }, rspec: { script: %w[ls] } } }
-      before { global.process! }
+      before { global.compose! }
+
+      let(:hash) do
+        { cache: { key: 'a' }, rspec: { script: %w[ls] } }
+      end
 
       describe '#nodes' do
         it 'instantizes all nodes' do
@@ -188,8 +191,11 @@ describe Gitlab::Ci::Config::Node::Global do
     # details.
     #
     context 'when entires specified but not defined' do
-      let(:hash) { { variables: nil, rspec: { script: 'rspec' } } }
-      before { global.process! }
+      before { global.compose! }
+
+      let(:hash) do
+        { variables: nil, rspec: { script: 'rspec' } }
+      end
 
       describe '#variables' do
         it 'undefined entry returns a default value' do
@@ -200,7 +206,7 @@ describe Gitlab::Ci::Config::Node::Global do
   end
 
   context 'when hash is not valid' do
-    before { global.process! }
+    before { global.compose! }
 
     let(:hash) do
       { before_script: 'ls' }
diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb
index 1484fb60dd8..74aa616720e 100644
--- a/spec/lib/gitlab/ci/config/node/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/job_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
 describe Gitlab::Ci::Config::Node::Job do
   let(:entry) { described_class.new(config, name: :rspec) }
 
-  before { entry.process! }
+  before { entry.compose! }
 
   describe 'validations' do
     context 'when entry config value is correct' do
diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
index b8d9c70479c..4bf01835c0a 100644
--- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Ci::Config::Node::Jobs do
   let(:entry) { described_class.new(config) }
 
   describe 'validations' do
-    before { entry.process! }
+    before { entry.compose! }
 
     context 'when entry config value is correct' do
       let(:config) { { rspec: { script: 'rspec' } } }
@@ -47,8 +47,8 @@ describe Gitlab::Ci::Config::Node::Jobs do
     end
   end
 
-  context 'when valid job entries processed' do
-    before { entry.process! }
+  context 'when valid job entries composed' do
+    before { entry.compose! }
 
     let(:config) do
       { rspec: { script: 'rspec' },
diff --git a/spec/lib/gitlab/ci/config/node/script_spec.rb b/spec/lib/gitlab/ci/config/node/script_spec.rb
index ee7395362a9..219a7e981d3 100644
--- a/spec/lib/gitlab/ci/config/node/script_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/script_spec.rb
@@ -3,9 +3,7 @@ require 'spec_helper'
 describe Gitlab::Ci::Config::Node::Script do
   let(:entry) { described_class.new(config) }
 
-  describe '#process!' do
-    before { entry.process! }
-
+  describe 'validations' do
     context 'when entry config value is correct' do
       let(:config) { ['ls', 'pwd'] }
 
-- 
GitLab


From 30f58cf3924610564ca95b0ac17b69d272e74f5f Mon Sep 17 00:00:00 2001
From: Grzegorz Bizon <grzesiek.bizon@gmail.com>
Date: Thu, 25 Aug 2016 14:31:06 +0200
Subject: [PATCH 5/9] Add [] method for accessing ci entry dependencies

---
 lib/gitlab/ci/config/node/entry.rb            |  4 ++++
 spec/lib/gitlab/ci/config/node/global_spec.rb | 23 +++++++++++++++++++
 2 files changed, 27 insertions(+)

diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb
index 907d25a8c49..8717eabf81e 100644
--- a/lib/gitlab/ci/config/node/entry.rb
+++ b/lib/gitlab/ci/config/node/entry.rb
@@ -20,6 +20,10 @@ module Gitlab
             @validator.validate(:new)
           end
 
+          def [](key)
+            @entries[key] || Node::Undefined.new
+          end
+
           def compose!(deps = nil)
             return unless valid?
 
diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb
index e033b423fdd..4ff2320f1bf 100644
--- a/spec/lib/gitlab/ci/config/node/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/global_spec.rb
@@ -253,4 +253,27 @@ describe Gitlab::Ci::Config::Node::Global do
       expect(global.specified?).to be true
     end
   end
+
+  describe '#[]' do
+    before { global.compose! }
+
+    let(:hash) do
+      { cache: { key: 'a' }, rspec: { script: 'ls' } }
+    end
+
+    context 'when node exists' do
+      it 'returns correct entry' do
+        expect(global[:cache])
+          .to be_an_instance_of Gitlab::Ci::Config::Node::Cache
+        expect(global[:jobs][:rspec][:script].value).to eq ['ls']
+      end
+    end
+
+    context 'when node does not exist' do
+      it 'always return unspecified node' do
+        expect(global[:some][:unknown][:node])
+          .not_to be_specified
+      end
+    end
+  end
 end
-- 
GitLab


From 62f704c5ad421a538257917d75f68b3c698b9be0 Mon Sep 17 00:00:00 2001
From: Grzegorz Bizon <grzesiek.bizon@gmail.com>
Date: Mon, 29 Aug 2016 12:44:14 +0200
Subject: [PATCH 6/9] Make it possible to inherit global ci config nodes

---
 lib/gitlab/ci/config/node/job.rb              | 15 +++++++
 spec/lib/gitlab/ci/config/node/global_spec.rb | 30 ++++++++++---
 spec/lib/gitlab/ci/config/node/job_spec.rb    | 43 ++++++++++++++++++-
 3 files changed, 80 insertions(+), 8 deletions(-)

diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb
index 3e6666d751f..745f03ae4d5 100644
--- a/lib/gitlab/ci/config/node/job.rb
+++ b/lib/gitlab/ci/config/node/job.rb
@@ -90,6 +90,8 @@ module Gitlab
 
               @entries.delete(:type)
             end
+
+            inherit!(deps)
           end
 
           def name
@@ -102,6 +104,19 @@ module Gitlab
 
           private
 
+          def inherit!(deps)
+            return unless deps
+
+            self.class.nodes.each_key do |key|
+              global_entry = deps[key]
+              job_entry = @entries[key]
+
+              if global_entry.specified? && !job_entry.specified?
+                @entries[key] = global_entry
+              end
+            end
+          end
+
           def to_hash
             { name: name,
               before_script: before_script,
diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb
index 4ff2320f1bf..951f5f956d8 100644
--- a/spec/lib/gitlab/ci/config/node/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/global_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::Ci::Config::Node::Global do
   end
 
   context 'when hash is valid' do
-    context 'when all entries defined' do
+    context 'when some entries defined' do
       let(:hash) do
         { before_script: ['ls', 'pwd'],
           image: 'ruby:2.2',
@@ -24,7 +24,7 @@ describe Gitlab::Ci::Config::Node::Global do
           stages: ['build', 'pages'],
           cache: { key: 'k', untracked: true, paths: ['public/'] },
           rspec: { script: %w[rspec ls] },
-          spinach: { script: 'spinach' } }
+          spinach: { before_script: [], variables: {}, script: 'spinach' } }
       end
 
       describe '#compose!' do
@@ -76,6 +76,12 @@ describe Gitlab::Ci::Config::Node::Global do
       context 'when composed' do
         before { global.compose! }
 
+        describe '#errors' do
+          it 'has no errors' do
+            expect(global.errors).to be_empty
+          end
+        end
+
         describe '#before_script' do
           it 'returns correct script' do
             expect(global.before_script).to eq ['ls', 'pwd']
@@ -135,12 +141,24 @@ describe Gitlab::Ci::Config::Node::Global do
         describe '#jobs' do
           it 'returns jobs configuration' do
             expect(global.jobs).to eq(
-              rspec: { name: :rspec,
-                       script: %w[rspec ls],
-                       stage: 'test' },
+              rspec: { script: %w[rspec ls],
+                       name: :rspec,
+                       before_script: ['ls', 'pwd'],
+                       image: 'ruby:2.2',
+                       services: ['postgres:9.1', 'mysql:5.5'],
+                       stage: 'test',
+                       cache: { key: 'k', untracked: true, paths: ['public/'] },
+                       variables: { VAR: 'value' },
+                       after_script: ['make clean'] },
               spinach: { name: :spinach,
                          script: %w[spinach],
-                         stage: 'test' }
+                         before_script: [],
+                         image: 'ruby:2.2',
+                         services: ['postgres:9.1', 'mysql:5.5'],
+                         stage: 'test',
+                         cache: { key: 'k', untracked: true, paths: ['public/'] },
+                         variables: {},
+                         after_script: ['make clean'] },
             )
           end
         end
diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb
index 74aa616720e..f34a3786a0d 100644
--- a/spec/lib/gitlab/ci/config/node/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/job_spec.rb
@@ -3,9 +3,9 @@ require 'spec_helper'
 describe Gitlab::Ci::Config::Node::Job do
   let(:entry) { described_class.new(config, name: :rspec) }
 
-  before { entry.compose! }
-
   describe 'validations' do
+    before { entry.compose! }
+
     context 'when entry config value is correct' do
       let(:config) { { script: 'rspec' } }
 
@@ -60,6 +60,8 @@ describe Gitlab::Ci::Config::Node::Job do
   end
 
   describe '#value' do
+    before { entry.compose! }
+
     context 'when entry is correct' do
       let(:config) do
         { before_script: %w[ls pwd],
@@ -83,4 +85,41 @@ describe Gitlab::Ci::Config::Node::Job do
       expect(entry).to be_relevant
     end
   end
+
+  describe '#compose!' do
+    let(:unspecified) { double('unspecified', 'specified?' => false) }
+
+    let(:specified) do
+      double('specified', 'specified?' => true, value: 'specified')
+    end
+
+    let(:deps) { spy('deps', '[]' => unspecified) }
+
+    context 'when job config overrides global config' do
+      before { entry.compose!(deps) }
+
+      let(:config) do
+        { image: 'some_image', cache: { key: 'test' } }
+      end
+
+      it 'overrides global config' do
+        expect(entry[:image].value).to eq 'some_image'
+        expect(entry[:cache].value).to eq(key: 'test')
+      end
+    end
+
+    context 'when job config does not override global config' do
+      before do
+        allow(deps).to receive('[]').with(:image).and_return(specified)
+        entry.compose!(deps)
+      end
+
+      let(:config) { { script: 'ls', cache: { key: 'test' } } }
+
+      it 'uses config from global entry' do
+        expect(entry[:image].value).to eq 'specified'
+        expect(entry[:cache].value).to eq(key: 'test')
+      end
+    end
+  end
 end
-- 
GitLab


From bd807503d14f2592e2c89c361c6967866580e977 Mon Sep 17 00:00:00 2001
From: Grzegorz Bizon <grzesiek.bizon@gmail.com>
Date: Mon, 29 Aug 2016 13:10:57 +0200
Subject: [PATCH 7/9] Do not override job nodes in legacy ci config code

---
 lib/ci/gitlab_ci_yaml_processor.rb | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 47efd5bd9f2..5f1ba97214e 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -60,7 +60,7 @@ module Ci
         #  - before script behaves differently than after script
         #  - after script returns an array of commands
         #  - before script should be a concatenated command
-        commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"),
+        commands: [job[:before_script], job[:script]].flatten.compact.join("\n"),
         tag_list: job[:tags] || [],
         name: job[:name].to_s,
         allow_failure: job[:allow_failure] || false,
@@ -68,12 +68,12 @@ module Ci
         environment: job[:environment],
         yaml_variables: yaml_variables(name),
         options: {
-          image: job[:image] || @image,
-          services: job[:services] || @services,
+          image: job[:image],
+          services: job[:services],
           artifacts: job[:artifacts],
-          cache: job[:cache] || @cache,
+          cache: job[:cache],
           dependencies: job[:dependencies],
-          after_script: job[:after_script] || @after_script,
+          after_script: job[:after_script],
         }.compact
       }
     end
-- 
GitLab


From 1255205d896d070a6d8bcefa3774116263db84be Mon Sep 17 00:00:00 2001
From: Grzegorz Bizon <grzesiek.bizon@gmail.com>
Date: Mon, 29 Aug 2016 13:11:35 +0200
Subject: [PATCH 8/9] Add method that returns commands for ci job entry

---
 lib/ci/gitlab_ci_yaml_processor.rb            |  7 +--
 lib/gitlab/ci/config/node/job.rb              |  7 ++-
 spec/lib/gitlab/ci/config/node/global_spec.rb |  8 ++-
 spec/lib/gitlab/ci/config/node/job_spec.rb    | 57 ++++++++++++-------
 spec/lib/gitlab/ci/config/node/jobs_spec.rb   |  2 +
 5 files changed, 50 insertions(+), 31 deletions(-)

diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 5f1ba97214e..c00c2020b4f 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -55,12 +55,7 @@ module Ci
       {
         stage_idx: @stages.index(job[:stage]),
         stage: job[:stage],
-        ##
-        # Refactoring note:
-        #  - before script behaves differently than after script
-        #  - after script returns an array of commands
-        #  - before script should be a concatenated command
-        commands: [job[:before_script], job[:script]].flatten.compact.join("\n"),
+        commands: job[:commands],
         tag_list: job[:tags] || [],
         name: job[:name].to_s,
         allow_failure: job[:allow_failure] || false,
diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb
index 745f03ae4d5..0cbdf7619c0 100644
--- a/lib/gitlab/ci/config/node/job.rb
+++ b/lib/gitlab/ci/config/node/job.rb
@@ -80,7 +80,7 @@ module Gitlab
 
           helpers :before_script, :script, :stage, :type, :after_script,
                   :cache, :image, :services, :only, :except, :variables,
-                  :artifacts
+                  :artifacts, :commands
 
           def compose!(deps = nil)
             super do
@@ -102,6 +102,10 @@ module Gitlab
             @config.merge(to_hash.compact)
           end
 
+          def commands
+            (before_script_value.to_a + script_value.to_a).join("\n")
+          end
+
           private
 
           def inherit!(deps)
@@ -121,6 +125,7 @@ module Gitlab
             { name: name,
               before_script: before_script,
               script: script,
+              commands: commands,
               image: image,
               services: services,
               stage: stage,
diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb
index 951f5f956d8..12232ff7e2f 100644
--- a/spec/lib/gitlab/ci/config/node/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/global_spec.rb
@@ -141,9 +141,10 @@ describe Gitlab::Ci::Config::Node::Global do
         describe '#jobs' do
           it 'returns jobs configuration' do
             expect(global.jobs).to eq(
-              rspec: { script: %w[rspec ls],
-                       name: :rspec,
+              rspec: { name: :rspec,
+                       script: %w[rspec ls],
                        before_script: ['ls', 'pwd'],
+                       commands: "ls\npwd\nrspec\nls",
                        image: 'ruby:2.2',
                        services: ['postgres:9.1', 'mysql:5.5'],
                        stage: 'test',
@@ -151,8 +152,9 @@ describe Gitlab::Ci::Config::Node::Global do
                        variables: { VAR: 'value' },
                        after_script: ['make clean'] },
               spinach: { name: :spinach,
-                         script: %w[spinach],
                          before_script: [],
+                         script: %w[spinach],
+                         commands: 'spinach',
                          image: 'ruby:2.2',
                          services: ['postgres:9.1', 'mysql:5.5'],
                          stage: 'test',
diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb
index f34a3786a0d..c660ce39c46 100644
--- a/spec/lib/gitlab/ci/config/node/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/job_spec.rb
@@ -59,27 +59,6 @@ describe Gitlab::Ci::Config::Node::Job do
     end
   end
 
-  describe '#value' do
-    before { entry.compose! }
-
-    context 'when entry is correct' do
-      let(:config) do
-        { before_script: %w[ls pwd],
-          script: 'rspec',
-          after_script: %w[cleanup] }
-      end
-
-      it 'returns correct value' do
-        expect(entry.value)
-          .to eq(name: :rspec,
-                 before_script: %w[ls pwd],
-                 script: %w[rspec],
-                 stage: 'test',
-                 after_script: %w[cleanup])
-      end
-    end
-  end
-
   describe '#relevant?' do
     it 'is a relevant entry' do
       expect(entry).to be_relevant
@@ -122,4 +101,40 @@ describe Gitlab::Ci::Config::Node::Job do
       end
     end
   end
+
+  context 'when composed' do
+    before { entry.compose! }
+
+    describe '#value' do
+      before { entry.compose! }
+
+      context 'when entry is correct' do
+        let(:config) do
+          { before_script: %w[ls pwd],
+            script: 'rspec',
+            after_script: %w[cleanup] }
+        end
+
+        it 'returns correct value' do
+          expect(entry.value)
+            .to eq(name: :rspec,
+                   before_script: %w[ls pwd],
+                   script: %w[rspec],
+                   commands: "ls\npwd\nrspec",
+                   stage: 'test',
+                   after_script: %w[cleanup])
+        end
+      end
+    end
+
+    describe '#commands' do
+      let(:config) do
+        { before_script: %w[ls pwd], script: 'rspec' }
+      end
+
+      it 'returns a string of commands concatenated with new line character' do
+        expect(entry.commands).to eq "ls\npwd\nrspec"
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
index 4bf01835c0a..420d137270a 100644
--- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb
@@ -61,9 +61,11 @@ describe Gitlab::Ci::Config::Node::Jobs do
         expect(entry.value).to eq(
           rspec: { name: :rspec,
                    script: %w[rspec],
+                   commands: 'rspec',
                    stage: 'test' },
           spinach: { name: :spinach,
                      script: %w[spinach],
+                     commands: 'spinach',
                      stage: 'test' })
       end
     end
-- 
GitLab


From b3cd41b4014fa96780218f0f086de239731ec91a Mon Sep 17 00:00:00 2001
From: Grzegorz Bizon <grzesiek.bizon@gmail.com>
Date: Sat, 3 Sep 2016 09:31:02 +0200
Subject: [PATCH 9/9] Use double instead of spy in job config node specs

---
 spec/lib/gitlab/ci/config/node/job_spec.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb
index c660ce39c46..91f676dae03 100644
--- a/spec/lib/gitlab/ci/config/node/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/job_spec.rb
@@ -72,7 +72,7 @@ describe Gitlab::Ci::Config::Node::Job do
       double('specified', 'specified?' => true, value: 'specified')
     end
 
-    let(:deps) { spy('deps', '[]' => unspecified) }
+    let(:deps) { double('deps', '[]' => unspecified) }
 
     context 'when job config overrides global config' do
       before { entry.compose!(deps) }
-- 
GitLab