From c1918fb10b333593837c15bf4a6fa161ca502b4b Mon Sep 17 00:00:00 2001
From: Grzegorz Bizon <grzesiek.bizon@gmail.com>
Date: Mon, 17 Jul 2017 12:05:07 +0200
Subject: [PATCH] Add a new `retry` CI/CD configuration keyword

---
 lib/ci/gitlab_ci_yaml_processor.rb           |  3 +-
 lib/gitlab/ci/config/entry/job.rb            | 10 +++--
 spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 22 +++++++++++
 spec/lib/gitlab/ci/config/entry/job_spec.rb  | 39 ++++++++++++++++++++
 4 files changed, 70 insertions(+), 4 deletions(-)

diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index cf3a0336792..3a4911b23b0 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -83,7 +83,8 @@ module Ci
           before_script: job[:before_script],
           script: job[:script],
           after_script: job[:after_script],
-          environment: job[:environment]
+          environment: job[:environment],
+          retry: job[:retry]
         }.compact }
     end
 
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 176301bcca1..5f1144894b5 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -11,7 +11,7 @@ module Gitlab
 
           ALLOWED_KEYS = %i[tags script only except type image services allow_failure
                             type stage when artifacts cache dependencies before_script
-                            after_script variables environment coverage].freeze
+                            after_script variables environment coverage retry].freeze
 
           validations do
             validates :config, allowed_keys: ALLOWED_KEYS
@@ -23,6 +23,9 @@ module Gitlab
             with_options allow_nil: true do
               validates :tags, array_of_strings: true
               validates :allow_failure, boolean: true
+              validates :retry, numericality: { only_integer: true,
+                                                greater_than_or_equal_to: 0,
+                                                less_than: 10 }
               validates :when,
                 inclusion: { in: %w[on_success on_failure always manual],
                              message: 'should be on_success, on_failure, ' \
@@ -76,9 +79,9 @@ module Gitlab
 
           helpers :before_script, :script, :stage, :type, :after_script,
                   :cache, :image, :services, :only, :except, :variables,
-                  :artifacts, :commands, :environment, :coverage
+                  :artifacts, :commands, :environment, :coverage, :retry
 
-          attributes :script, :tags, :allow_failure, :when, :dependencies
+          attributes :script, :tags, :allow_failure, :when, :dependencies, :retry
 
           def compose!(deps = nil)
             super do
@@ -142,6 +145,7 @@ module Gitlab
               environment: environment_defined? ? environment_value : nil,
               environment_name: environment_defined? ? environment_value[:name] : nil,
               coverage: coverage_defined? ? coverage_value : nil,
+              retry: retry_defined? ? retry_value.to_i : nil,
               artifacts: artifacts_value,
               after_script: after_script_value,
               ignore: ignored? }
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index ea79389e67e..e50f799a6e9 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -32,6 +32,28 @@ module Ci
         end
       end
 
+      describe 'retry entry' do
+        context 'when retry count is specified' do
+          let(:config) do
+            YAML.dump(rspec: { script: 'rspec', retry: 3 })
+          end
+
+          it 'includes retry count in build options attribute' do
+            expect(subject[:options]).to include(retry: 3)
+          end
+        end
+
+        context 'when retry count is not specified' do
+          let(:config) do
+            YAML.dump(rspec: { script: 'rspec' })
+          end
+
+          it 'does not persist retry count in the database' do
+            expect(subject[:options]).not_to have_key(:retry)
+          end
+        end
+      end
+
       describe 'allow failure entry' do
         context 'when job is a manual action' do
           context 'when allow_failure is defined' do
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index c5cad887b64..f8ed59a3a44 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -80,6 +80,45 @@ describe Gitlab::Ci::Config::Entry::Job do
           expect(entry.errors).to include "job script can't be blank"
         end
       end
+
+      context 'when retry value is not correct' do
+        context 'when it is not a numeric value' do
+          let(:config) { { retry: true } }
+
+          it 'returns error about invalid type' do
+            expect(entry).not_to be_valid
+            expect(entry.errors).to include 'job retry is not a number'
+          end
+        end
+
+        context 'when it is lower than zero' do
+          let(:config) { { retry: -1 } }
+
+          it 'returns error about value too low' do
+            expect(entry).not_to be_valid
+            expect(entry.errors)
+              .to include 'job retry must be greater than or equal to 0'
+          end
+        end
+
+        context 'when it is not an integer' do
+          let(:config) { { retry: 1.5 } }
+
+          it 'returns error about wrong value' do
+            expect(entry).not_to be_valid
+            expect(entry.errors).to include 'job retry must be an integer'
+          end
+        end
+
+        context 'when the value is too high' do
+          let(:config) { { retry: 10 } }
+
+          it 'returns error about value too high' do
+            expect(entry).not_to be_valid
+            expect(entry.errors).to include 'job retry must be less than 10'
+          end
+        end
+      end
     end
   end
 
-- 
2.30.9