From 7d0fe1f04ed285e7e5cf825a305114b3e981c2f8 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 16 Jul 2016 18:39:58 +0200 Subject: [PATCH] Add implementation of manual actions --- app/controllers/projects/builds_controller.rb | 9 +++++ app/models/ci/build.rb | 24 +++++++++++++ app/models/ci/pipeline.rb | 4 +++ app/models/deployment.rb | 4 +++ app/views/projects/ci/builds/_build.html.haml | 13 +++++-- .../projects/ci/pipelines/_pipeline.html.haml | 34 +++++++++++++------ .../deployments/_deployment.html.haml | 2 ++ .../projects/deployments/_playable.html.haml | 12 +++++++ .../environments/_environment.html.haml | 4 +++ config/routes.rb | 1 + lib/ci/gitlab_ci_yaml_processor.rb | 4 +-- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- 12 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 app/views/projects/deployments/_playable.html.haml diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index d7513d75f01..597cfa93712 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -57,6 +57,15 @@ class Projects::BuildsController < Projects::ApplicationController redirect_to build_path(build) end + def play + unless @build.playable? + return render_404 + end + + build = @build.play(current_user) + redirect_to build_path(build) + end + def cancel @build.cancel redirect_to build_path(@build) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index ffac3a22efc..81feca0088f 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -15,6 +15,7 @@ module Ci scope :with_artifacts, ->() { where.not(artifacts_file: nil) } scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } + scope :manual_actions, ->() { where(when: :manual).without_created } mount_uploader :artifacts_file, ArtifactUploader mount_uploader :artifacts_metadata, ArtifactUploader @@ -91,6 +92,29 @@ module Ci end end + def manual? + self.when == 'manual' + end + + def playable_actions + pipeline.playable_actions + end + + def playable? + project.builds_enabled? && commands.present? && manual? + end + + def play(current_user = nil) + if skipped? + # We can run skipped build + new_build.user = current_user + new_build.queue + else + # Otherwise we need to create a duplicate + Ci::Build.retry(self, current_user) + end + end + def retryable? project.builds_enabled? && commands.present? && complete? end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index b468434866b..5aefe1d1694 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -65,6 +65,10 @@ module Ci !tag? end + def playable_actions + builds.manual_actions.latest + end + def retryable? builds.latest.any? do |build| build.failed? && build.retryable? diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 520026c18dd..44dfb67552e 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -32,4 +32,8 @@ class Deployment < ActiveRecord::Base def keep_around_commit project.repository.keep_around(self.sha) end + + def playable_actions + deployable.try(:playable_actions) + end end diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index e1b42b2cfa5..efedafefc38 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -39,6 +39,8 @@ %span.label.label-danger allowed to fail - if defined?(retried) && retried %span.label.label-warning retried + - if build.manual? + %span.label.label-info manual - if defined?(runner) && runner @@ -79,6 +81,11 @@ - if build.active? = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do = icon('remove', class: 'cred') - - elsif defined?(allow_retry) && allow_retry && build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do - = icon('repeat') + - elsif defined?(allow_retry) && allow_retry + - if build.retryable? + = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do + = icon('repeat') + - elsif build.playable? + = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do + = icon('play') + diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 0557d384e33..1285fce4930 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -57,18 +57,30 @@ %td.pipeline-actions .controls.hidden-xs.pull-right - artifacts = pipeline.builds.latest.select { |b| b.artifacts? } - - if artifacts.present? - .inline - .btn-group - %a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'} - = icon("download") - %b.caret - %ul.dropdown-menu.dropdown-menu-align-right - - artifacts.each do |build| + - playable = pipeline.playable_actions + - if artifacts.present? || playable.any? + .btn-group.inline + - if playable.any? + .btn-group + %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} + = icon("play") + %b.caret + %ul.dropdown-menu.dropdown-menu-align-right %li - = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do - = icon("download") - %span Download '#{build.name}' artifacts + = link_to play_namespace_project_build_path(@project.namespace, @project, build), rel: 'nofollow' do + = icon("play") + %span= playable.name.titleize + - if artifacts.present? + .btn-group + %a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'} + = icon("download") + %b.caret + %ul.dropdown-menu.dropdown-menu-align-right + - artifacts.each do |build| + %li + = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do + = icon("download") + %span Download '#{build.name}' artifacts - if can?(current_user, :update_pipeline, @project) .cancel-retry-btns.inline diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index d08dd92f1f6..e430d195ea6 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -21,3 +21,5 @@ Retry - else Rollback + + = render 'playable', deployment: deployment diff --git a/app/views/projects/deployments/_playable.html.haml b/app/views/projects/deployments/_playable.html.haml new file mode 100644 index 00000000000..02e4fb7974e --- /dev/null +++ b/app/views/projects/deployments/_playable.html.haml @@ -0,0 +1,12 @@ +- playable = deployment.playable_actions +- if playable.any? + .btn-group.inline + .btn-group + %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} + = icon("play") + %b.caret + %ul.dropdown-menu.dropdown-menu-align-right + %li + = link_to play_namespace_project_build_path(@project.namespace, @project, build), rel: 'nofollow' do + = icon("play") + %span= playable.name.titleize diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index eafa246d05f..8e517196e27 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -15,3 +15,7 @@ %td - if last_deployment #{time_ago_with_tooltip(last_deployment.created_at)} + + %td + - if can?(current_user, :create_deployment, last_deployment) && last_deployment.deployable + = render 'projects/deployments/playable', deployment: last_deployment diff --git a/config/routes.rb b/config/routes.rb index 3160fd767b8..be651d8903f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -750,6 +750,7 @@ Rails.application.routes.draw do get :status post :cancel post :retry + post :play post :erase get :trace get :raw diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index a48dc542b14..41449d720b3 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -194,8 +194,8 @@ module Ci raise ValidationError, "#{name} job: allow_failure parameter should be an boolean" end - if job[:when] && !job[:when].in?(%w[on_success on_failure always]) - raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always" + if job[:when] && !job[:when].in?(%w[on_success on_failure always manual]) + raise ValidationError, "#{name} job: when parameter should be on_success, on_failure, always or manual" end if job[:environment] && !validate_environment(job[:environment]) diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index ad6587b4c25..d20fd4ab7dd 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1141,7 +1141,7 @@ EOT config = YAML.dump({ rspec: { script: "test", when: 1 } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure, always or manual") end it "returns errors if job artifacts:name is not an a string" do