Commit 907c0e67 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Added initial version of deployments

parent cf7da039
...@@ -51,7 +51,7 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -51,7 +51,7 @@ class Projects::BuildsController < Projects::ApplicationController
return render_404 return render_404
end end
build = Ci::Build.retry(@build) build = Ci::Build.retry(@build, current_user)
redirect_to build_path(build) redirect_to build_path(build)
end end
......
...@@ -46,7 +46,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -46,7 +46,7 @@ class Projects::CommitController < Projects::ApplicationController
def retry_builds def retry_builds
ci_builds.latest.failed.each do |build| ci_builds.latest.failed.each do |build|
if build.retryable? if build.retryable?
Ci::Build.retry(build) Ci::Build.retry(build, current_user)
end end
end end
......
class Projects::EnvironmentsController < Projects::ApplicationController class Projects::EnvironmentsController < Projects::ApplicationController
layout 'project' layout 'project'
before_action :authorize_read_environment!
before_action :environment, only: [:show]
def index def index
@environments = project.builds.where.not(environment: nil).pluck(:environment).uniq @environments = project.environments
@environments = @environments.map { |env| build_for_env(env) }.compact
end end
def show def show
@environment = params[:id].to_s
@builds = project.builds.where.not(status: ["manual"]).where(environment: params[:id].to_s).order(id: :desc).page(params[:page]).per(30)
end end
def build_for_env(environment) private
project.builds.success.order(id: :desc).find_by(environment: environment)
def environment
@environment ||= project.environments.find(params[:id].to_s)
@environment || render_404
end end
end end
...@@ -156,6 +156,10 @@ module ProjectsHelper ...@@ -156,6 +156,10 @@ module ProjectsHelper
nav_tabs << :container_registry nav_tabs << :container_registry
end end
if can?(current_user, :read_environment, project)
nav_tabs << :environments
end
if can?(current_user, :admin_project, project) if can?(current_user, :admin_project, project)
nav_tabs << :settings nav_tabs << :settings
end end
......
...@@ -228,6 +228,8 @@ class Ability ...@@ -228,6 +228,8 @@ class Ability
:read_build, :read_build,
:read_container_image, :read_container_image,
:read_pipeline, :read_pipeline,
:read_environment,
:read_deployment
] ]
end end
...@@ -246,6 +248,10 @@ class Ability ...@@ -246,6 +248,10 @@ class Ability
:push_code, :push_code,
:create_container_image, :create_container_image,
:update_container_image, :update_container_image,
:create_environment,
:update_environment,
:create_deployment,
:update_deployment,
] ]
end end
...@@ -273,7 +279,9 @@ class Ability ...@@ -273,7 +279,9 @@ class Ability
:admin_commit_status, :admin_commit_status,
:admin_build, :admin_build,
:admin_container_image, :admin_container_image,
:admin_pipeline :admin_pipeline,
:admin_environment,
:admin_deployment
] ]
end end
...@@ -317,6 +325,8 @@ class Ability ...@@ -317,6 +325,8 @@ class Ability
unless project.builds_enabled unless project.builds_enabled
rules += named_abilities('build') rules += named_abilities('build')
rules += named_abilities('pipeline') rules += named_abilities('pipeline')
rules += named_abilities('environment')
rules += named_abilities('deployment')
end end
unless project.container_registry_enabled unless project.container_registry_enabled
......
...@@ -38,7 +38,7 @@ module Ci ...@@ -38,7 +38,7 @@ module Ci
new_build.save new_build.save
end end
def retry(build) def retry(build, user = nil)
new_build = Ci::Build.new(status: 'pending') new_build = Ci::Build.new(status: 'pending')
new_build.ref = build.ref new_build.ref = build.ref
new_build.tag = build.tag new_build.tag = build.tag
...@@ -52,6 +52,7 @@ module Ci ...@@ -52,6 +52,7 @@ module Ci
new_build.stage = build.stage new_build.stage = build.stage
new_build.stage_idx = build.stage_idx new_build.stage_idx = build.stage_idx
new_build.trigger_request = build.trigger_request new_build.trigger_request = build.trigger_request
new_build.user = user
new_build.save new_build.save
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build) MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
new_build new_build
...@@ -73,6 +74,12 @@ module Ci ...@@ -73,6 +74,12 @@ module Ci
build.update_coverage build.update_coverage
build.execute_hooks build.execute_hooks
end end
after_transition any: :success do |build|
if build.environment.present?
CreateDeploymentService.new(build.project, build.user, environment: build.environment).execute(build)
end
end
end end
def retryable? def retryable?
...@@ -83,10 +90,6 @@ module Ci ...@@ -83,10 +90,6 @@ module Ci
!self.pipeline.statuses.latest.include?(self) !self.pipeline.statuses.latest.include?(self)
end end
def retry
Ci::Build.retry(self)
end
def depends_on_builds def depends_on_builds
# Get builds of the same type # Get builds of the same type
latest_builds = self.pipeline.builds.latest latest_builds = self.pipeline.builds.latest
......
class Deployment < ActiveRecord::Base
include InternalId
belongs_to :project
belongs_to :environment
belongs_to :user
belongs_to :deployable, polymorphic: true
validates_presence_of :sha
validates_presence_of :ref
delegate :name, to: :environment, prefix: true
def commit
project.commit(sha)
end
def commit_title
commit.try(:title)
end
def short_sha
Commit::truncate_sha(sha)
end
end
class Environment < ActiveRecord::Base
belongs_to :project
has_many :deployments
validates_presence_of :name
def last_deployment
deployments.last
end
end
...@@ -125,6 +125,8 @@ class Project < ActiveRecord::Base ...@@ -125,6 +125,8 @@ class Project < ActiveRecord::Base
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
has_many :variables, dependent: :destroy, class_name: 'Ci::Variable', foreign_key: :gl_project_id has_many :variables, dependent: :destroy, class_name: 'Ci::Variable', foreign_key: :gl_project_id
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :gl_project_id has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :gl_project_id
has_many :environments, dependent: :destroy
has_many :deployments, dependent: :destroy
accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :variables, allow_destroy: true
......
...@@ -29,7 +29,8 @@ module Ci ...@@ -29,7 +29,8 @@ module Ci
:options, :options,
:allow_failure, :allow_failure,
:stage, :stage,
:stage_idx) :stage_idx,
:environment)
build_attrs.merge!(ref: @pipeline.ref, build_attrs.merge!(ref: @pipeline.ref,
tag: @pipeline.tag, tag: @pipeline.tag,
......
require_relative 'base_service'
class CreateDeploymentService < BaseService
def execute(deployable)
environment = find_or_create_environment(params[:environment])
deployment = create_deployment(environment, deployable)
if deployment.persisted?
success(deployment)
else
error(deployment.errors)
end
end
private
def find_or_create_environment(environment)
find_environment(environment) || create_environment(environment)
end
def create_environment(environment)
project.environments.create(name: environment)
end
def find_environment(environment)
project.environments.find_by(name: environment)
end
def create_deployment(environment, deployable)
environment.deployments.create(
project: project,
ref: build.ref,
tag: build.tag,
sha: build.sha,
user: current_user,
deployable: deployable,
)
end
def success(deployment)
out = super()
out[:deployment] = deployment
out
end
end
%tr.deployment
%td
%strong= "##{environment.id}"
%td
%div.branch-commit
- if deployment.ref
= link_to last.ref, namespace_project_commits_path(@project.namespace, @project, deployment.ref), class: "monospace"
&middot;
= link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-id monospace"
%p
%span
- if commit_title = deployment.commit_title
= link_to_gfm commit_title, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-row-message"
- else
Cant find HEAD commit for this branch
%td
- if deployment.deployable
= link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: "monospace" do
= "#{deployment.deployable.name} (##{deployment.deployable.id})"
%td
%p
%i.fa.fa-calendar
&nbsp;
#{time_ago_with_tooltip(deployment.created_at)}
%td
- if can?(current_user, :update_deployment, @project) && deployment.deployable
= link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable, :retry], method: :post, title: 'Retry', class: 'btn btn-build'
%tr.commit - last_deployment = environment.last_deployment
- commit = build.commit
- status = build.status
%tr.environment
%td %td
%strong %strong
= link_to build.environment, namespace_project_environment_path(@project.namespace, @project, build.environment), class: "monospace" = link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment), class: "monospace"
%td.commit-link
= link_to namespace_project_pipeline_path(@project.namespace, @project, commit.id), class: "ci-status ci-#{commit.status}" do
= ci_icon_for_status(commit.status)
%strong ##{commit.id}
%td.commit-link
= link_to namespace_project_build_path(@project.namespace, @project, build.id), class: "ci-status ci-#{build.status}" do
= ci_icon_for_status(build.status)
%strong ##{build.id}
%td %td
%div.branch-commit - if last_deployment
- if commit.ref %div.branch-commit
= link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref), class: "monospace" - if last_deployment.ref
&middot; = link_to last.ref, namespace_project_commits_path(@project.namespace, @project, last_deployment.ref), class: "monospace"
= link_to commit.short_sha, namespace_project_commit_path(@project.namespace, @project, commit.sha), class: "commit-id monospace" &middot;
= link_to last_deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, last_deployment.sha), class: "commit-id monospace"
%p
%span
- if commit_title = last_deployment.commit_title
= link_to_gfm commit_title, namespace_project_commit_path(@project.namespace, @project, last_deployment.sha), class: "commit-row-message"
- else
Cant find HEAD commit for this branch
- else
%p %p
%span No deployments yet
- if commit_data = commit.commit_data
= link_to_gfm commit_data.title, namespace_project_commit_path(@project.namespace, @project, commit_data.id), class: "commit-row-message"
- else
Cant find HEAD commit for this branch
%td %td
- if build.started_at && build.finished_at %p
%p %i.fa.fa-calendar
%i.fa.fa-clock-o &nbsp;
&nbsp; #{time_ago_with_tooltip(last_deployment.created_at)}
#{duration_in_words(build.finished_at, build.started_at)}
- if build.finished_at
%p
%i.fa.fa-calendar
&nbsp;
#{time_ago_with_tooltip(build.finished_at)}
%td %td
.controls.hidden-xs.pull-right
- manual = commit.builds.latest.manual_actions.to_a
- if manual.any?
.dropdown.inline
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
= icon('play')
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- manual.each do |manual_build|
%li
= link_to '#', rel: 'nofollow' do
%i.fa.fa-play
%span #{manual_build.name}
- @no_container = true
- page_title "Environments" - page_title "Environments"
= render "header_title" = render "projects/pipelines/head"
.gray-content-block %div{ class: (container_class) }
Environments for this project .gray-content-block
Environments for this project
%ul.content-list %ul.content-list
- if @environments.blank? - if @environments.blank?
%li %li
.nothing-here-block No environments to show .nothing-here-block No environments to show
- else - else
.table-holder .table-holder
%table.table.builds %table.table.builds
%tbody %tbody
%th Environment %th Environment
%th Pipeline ID %th Last deployment
%th Build ID %th Date
%th Changes %th
%th - @environments.each do |environment|
%th = render 'environment', environment: environment
- @environments.each do |build|
= render "environment", build: build
- @no_container = true
- page_title "Environments" - page_title "Environments"
= render "projects/pipelines/head"
= render "header_title" %div{ class: (container_class) }
.gray-content-block
Latest deployments for
%strong= @environment.name
.gray-content-block %ul.content-list
Latest deployments for - if @deployments.blank?
%strong %li
= @environment .nothing-here-block No deployment for specific environment
- else
.table-holder
%table.table.builds
%thead
%tr
%th Commit
%th Context
%th Date
%th
%ul.content-list = render @deployments
- if @builds.blank?
%li
.nothing-here-block No builds to show for specific environment
- else
.table-holder
%table.table.builds
%thead
%tr
%th Status
%th Build ID
%th Commit
%th Ref
%th Name
%th Duration
%th Finished at
%th
= render @builds, commit_sha: true, ref: true, allow_retry: true = paginate @deployments, theme: 'gitlab'
= paginate @builds, theme: 'gitlab'
...@@ -13,3 +13,9 @@ ...@@ -13,3 +13,9 @@
%span %span
Builds Builds
%span.badge.count.builds_counter= number_with_delimiter(@project.running_or_pending_build_count) %span.badge.count.builds_counter= number_with_delimiter(@project.running_or_pending_build_count)
- if project_nav_tab? :environments
= nav_link(controller: %w(environments)) do
= link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do
%span
Environments
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddDeployments < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
def change
create_table :deployments, force: true do |t|
t.integer :iid
t.integer :project_id
t.integer :environment_id
t.string :ref
t.boolean :tag
t.string :sha
t.integer :user_id
t.integer :deployable_id, null: false
t.string :deployable_type, null: false
t.datetime :created_at
t.datetime :updated_at
end
add_index :deployments, :project_id
add_index :deployments, [:project_id, :iid]
add_index :deployments, [:project_id, :environment_id]
add_index :deployments, [:project_id, :environment_id, :iid]
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddEnvironments < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
def change
create_table :environments, force: true do |t|
t.integer :project_id
t.string :name, null: false
t.datetime :created_at
t.datetime :updated_at
end
add_index :environments, [:project_id, :name]
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddEnvironmentToBuilds < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
def change
add_column :ci_builds, :environment, :string
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160608155312) do ActiveRecord::Schema.define(version: 20160610211845) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -144,9 +144,9 @@ ActiveRecord::Schema.define(version: 20160608155312) do ...@@ -144,9 +144,9 @@ ActiveRecord::Schema.define(version: 20160608155312) do
t.text "commands" t.text "commands"
t.integer "job_id" t.integer "job_id"
t.string "name" t.string "name"
t.boolean "deploy", default: false t.boolean "deploy", default: false
t.text "options" t.text "options"
t.boolean "allow_failure", default: false, null: false t.boolean "allow_failure", default: false, null: false
t.string "stage" t.string "stage"
t.integer "trigger_request_id" t.integer "trigger_request_id"
t.integer "stage_idx" t.integer "stage_idx"
...@@ -161,6 +161,7 @@ ActiveRecord::Schema.define(version: 20160608155312) do ...@@ -161,6 +161,7 @@ ActiveRecord::Schema.define(version: 20160608155312) do
t.text "artifacts_metadata" t.text "artifacts_metadata"
t.integer "erased_by_id" t.integer "erased_by_id"
t.datetime "erased_at" t.datetime "erased_at"
t.string "environment"
end end
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
...@@ -381,6 +382,25 @@ ActiveRecord::Schema.define(version: 20160608155312) do ...@@ -381,6 +382,25 @@ ActiveRecord::Schema.define(version: 20160608155312) do
add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree
create_table "deployments", force: :cascade do |t|
t.integer "iid"
t.integer "project_id"
t.integer "environment_id"
t.string "ref"
t.boolean "tag"
t.string "sha"
t.integer "user_id"
t.integer "deployable_id", null: false
t.string "deployable_type", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "deployments", ["project_id", "environment_id", "iid"], name: "index_deployments_on_project_id_and_environment_id_and_iid", using: :btree
add_index "deployments", ["project_id", "environment_id"], name: "index_deployments_on_project_id_and_environment_id", using: :btree
add_index "deployments", ["project_id", "iid"], name: "index_deployments_on_project_id_and_iid", using: :btree
add_index "deployments", ["project_id"], name: "index_deployments_on_project_id", using: :btree
create_table "emails", force: :cascade do |t| create_table "emails", force: :cascade do |t|
t.integer "user_id", null: false t.integer "user_id", null: false
t.string "email", null: false t.string "email", null: false
...@@ -391,6 +411,15 @@ ActiveRecord::Schema.define(version: 20160608155312) do ...@@ -391,6 +411,15 @@ ActiveRecord::Schema.define(version: 20160608155312) do
add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree
add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree
create_table "environments", force: :cascade do |t|
t.integer "project_id"
t.string "name", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree
create_table "events", force: :cascade do |t| create_table "events", force: :cascade do |t|
t.string "target_type" t.string "target_type"
t.integer "target_id" t.integer "target_id"
......
...@@ -142,7 +142,7 @@ module API ...@@ -142,7 +142,7 @@ module API
return not_found!(build) unless build return not_found!(build) unless build
return forbidden!('Build is not retryable') unless build.retryable? return forbidden!('Build is not retryable') unless build.retryable?
build = Ci::Build.retry(build) build = Ci::Build.retry(build, current_user)
present build, with: Entities::Build, present build, with: Entities::Build,
user_can_download_artifacts: can?(current_user, :read_build, user_project) user_can_download_artifacts: can?(current_user, :read_build, user_project)
......
...@@ -7,7 +7,8 @@ module Ci ...@@ -7,7 +7,8 @@ module Ci
ALLOWED_YAML_KEYS = [:before_script, :after_script, :image, :services, :types, :stages, :variables, :cache] ALLOWED_YAML_KEYS = [:before_script, :after_script, :image, :services, :types, :stages, :variables, :cache]
ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services,
:allow_failure, :type, :stage, :when, :artifacts, :cache,