From cf7da039bedcad5163ce9deedccc94206d4c485a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 29 Apr 2016 15:14:38 +0200 Subject: commit status --- .../projects/environments_controller.rb | 17 +++++++ app/helpers/gitlab_routing_helper.rb | 4 ++ app/models/ci/pipeline.rb | 4 ++ .../projects/environments/_environment.html.haml | 58 ++++++++++++++++++++++ .../projects/environments/_header_title.html.haml | 1 + app/views/projects/environments/index.html.haml | 22 ++++++++ app/views/projects/environments/show.html.haml | 30 +++++++++++ config/routes.rb | 2 + 8 files changed, 138 insertions(+) create mode 100644 app/controllers/projects/environments_controller.rb create mode 100644 app/views/projects/environments/_environment.html.haml create mode 100644 app/views/projects/environments/_header_title.html.haml create mode 100644 app/views/projects/environments/index.html.haml create mode 100644 app/views/projects/environments/show.html.haml diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb new file mode 100644 index 00000000000..f5af24ed217 --- /dev/null +++ b/app/controllers/projects/environments_controller.rb @@ -0,0 +1,17 @@ +class Projects::EnvironmentsController < Projects::ApplicationController + layout 'project' + + def index + @environments = project.builds.where.not(environment: nil).pluck(:environment).uniq + @environments = @environments.map { |env| build_for_env(env) }.compact + end + + 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 + + def build_for_env(environment) + project.builds.success.order(id: :desc).find_by(environment: environment) + end +end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 2ce2d4e694f..aae6b5d0d38 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -29,6 +29,10 @@ module GitlabRoutingHelper namespace_project_pipelines_path(project.namespace, project, *args) end + def project_environments_path(project, *args) + namespace_project_environments_path(project.namespace, project, *args) + end + def project_builds_path(project, *args) namespace_project_builds_path(project.namespace, project, *args) end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 9b5b46f4928..85d9e0856d1 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -161,6 +161,10 @@ module Ci git_commit_message =~ /(\[ci skip\])/ if git_commit_message end + def environments + builds.where.not(environment: nil).success.pluck(:environment).uniq + end + private def update_state diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml new file mode 100644 index 00000000000..e3216aea6cd --- /dev/null +++ b/app/views/projects/environments/_environment.html.haml @@ -0,0 +1,58 @@ +%tr.commit + - commit = build.commit + - status = build.status + + %td + %strong + = link_to build.environment, namespace_project_environment_path(@project.namespace, @project, build.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 + %div.branch-commit + - if commit.ref + = link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref), class: "monospace" + · + = link_to commit.short_sha, namespace_project_commit_path(@project.namespace, @project, commit.sha), class: "commit-id monospace" + + %p + %span + - 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 + - if build.started_at && build.finished_at + %p + %i.fa.fa-clock-o +   + #{duration_in_words(build.finished_at, build.started_at)} + - if build.finished_at + %p + %i.fa.fa-calendar +   + #{time_ago_with_tooltip(build.finished_at)} + + %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} diff --git a/app/views/projects/environments/_header_title.html.haml b/app/views/projects/environments/_header_title.html.haml new file mode 100644 index 00000000000..e056fccad5d --- /dev/null +++ b/app/views/projects/environments/_header_title.html.haml @@ -0,0 +1 @@ +- header_title project_title(@project, "Environments", project_environments_path(@project)) diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml new file mode 100644 index 00000000000..e94bc97be9d --- /dev/null +++ b/app/views/projects/environments/index.html.haml @@ -0,0 +1,22 @@ +- page_title "Environments" += render "header_title" + +.gray-content-block + Environments for this project + +%ul.content-list + - if @environments.blank? + %li + .nothing-here-block No environments to show + - else + .table-holder + %table.table.builds + %tbody + %th Environment + %th Pipeline ID + %th Build ID + %th Changes + %th + %th + - @environments.each do |build| + = render "environment", build: build diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml new file mode 100644 index 00000000000..ce2d9cf7d71 --- /dev/null +++ b/app/views/projects/environments/show.html.haml @@ -0,0 +1,30 @@ +- page_title "Environments" + += render "header_title" + +.gray-content-block + Latest deployments for + %strong + = @environment + +%ul.content-list + - 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 @builds, theme: 'gitlab' diff --git a/config/routes.rb b/config/routes.rb index 95fbe7dd9df..6b8402c40dd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -704,6 +704,8 @@ Rails.application.routes.draw do end end + resources :environments, only: [:index, :show] + resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do collection do post :cancel_all -- cgit v1.2.1 From 907c0e6796b69f9577c147dd489cf55748c749ac Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 10 Jun 2016 23:36:54 +0200 Subject: Added initial version of deployments --- app/controllers/projects/builds_controller.rb | 2 +- app/controllers/projects/commit_controller.rb | 2 +- .../projects/environments_controller.rb | 14 +++-- app/helpers/projects_helper.rb | 4 ++ app/models/ability.rb | 12 +++- app/models/ci/build.rb | 13 +++-- app/models/deployment.rb | 25 ++++++++ app/models/environment.rb | 11 ++++ app/models/project.rb | 2 + app/services/ci/create_builds_service.rb | 3 +- app/services/create_deployment_service.rb | 45 ++++++++++++++ .../projects/deployments/_deployment.html.haml | 32 ++++++++++ .../projects/environments/_environment.html.haml | 68 +++++++--------------- app/views/projects/environments/index.html.haml | 38 ++++++------ app/views/projects/environments/show.html.haml | 46 +++++++-------- app/views/projects/pipelines/_head.html.haml | 6 ++ db/migrate/20160610204157_add_deployments.rb | 27 +++++++++ db/migrate/20160610204158_add_environments.rb | 17 ++++++ .../20160610211845_add_environment_to_builds.rb | 10 ++++ db/schema.rb | 35 ++++++++++- lib/api/builds.rb | 2 +- lib/ci/gitlab_ci_yaml_processor.rb | 8 ++- 22 files changed, 311 insertions(+), 111 deletions(-) create mode 100644 app/models/deployment.rb create mode 100644 app/models/environment.rb create mode 100644 app/services/create_deployment_service.rb create mode 100644 app/views/projects/deployments/_deployment.html.haml create mode 100644 db/migrate/20160610204157_add_deployments.rb create mode 100644 db/migrate/20160610204158_add_environments.rb create mode 100644 db/migrate/20160610211845_add_environment_to_builds.rb diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 14c82826342..ef3051d7519 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -51,7 +51,7 @@ class Projects::BuildsController < Projects::ApplicationController return render_404 end - build = Ci::Build.retry(@build) + build = Ci::Build.retry(@build, current_user) redirect_to build_path(build) end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 20637fa46fe..6751737d15e 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -46,7 +46,7 @@ class Projects::CommitController < Projects::ApplicationController def retry_builds ci_builds.latest.failed.each do |build| if build.retryable? - Ci::Build.retry(build) + Ci::Build.retry(build, current_user) end end diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index f5af24ed217..722954a6b78 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -1,17 +1,19 @@ class Projects::EnvironmentsController < Projects::ApplicationController layout 'project' + before_action :authorize_read_environment! + before_action :environment, only: [:show] def index - @environments = project.builds.where.not(environment: nil).pluck(:environment).uniq - @environments = @environments.map { |env| build_for_env(env) }.compact + @environments = project.environments end 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 - def build_for_env(environment) - project.builds.success.order(id: :desc).find_by(environment: environment) + private + + def environment + @environment ||= project.environments.find(params[:id].to_s) + @environment || render_404 end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 5e5d170a9f3..2ad7520b63a 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -156,6 +156,10 @@ module ProjectsHelper nav_tabs << :container_registry end + if can?(current_user, :read_environment, project) + nav_tabs << :environments + end + if can?(current_user, :admin_project, project) nav_tabs << :settings end diff --git a/app/models/ability.rb b/app/models/ability.rb index 44515550d9e..747f250ff4f 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -228,6 +228,8 @@ class Ability :read_build, :read_container_image, :read_pipeline, + :read_environment, + :read_deployment ] end @@ -246,6 +248,10 @@ class Ability :push_code, :create_container_image, :update_container_image, + :create_environment, + :update_environment, + :create_deployment, + :update_deployment, ] end @@ -273,7 +279,9 @@ class Ability :admin_commit_status, :admin_build, :admin_container_image, - :admin_pipeline + :admin_pipeline, + :admin_environment, + :admin_deployment ] end @@ -317,6 +325,8 @@ class Ability unless project.builds_enabled rules += named_abilities('build') rules += named_abilities('pipeline') + rules += named_abilities('environment') + rules += named_abilities('deployment') end unless project.container_registry_enabled diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 6a64ca451f7..60202525727 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -38,7 +38,7 @@ module Ci new_build.save end - def retry(build) + def retry(build, user = nil) new_build = Ci::Build.new(status: 'pending') new_build.ref = build.ref new_build.tag = build.tag @@ -52,6 +52,7 @@ module Ci new_build.stage = build.stage new_build.stage_idx = build.stage_idx new_build.trigger_request = build.trigger_request + new_build.user = user new_build.save MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build) new_build @@ -73,6 +74,12 @@ module Ci build.update_coverage build.execute_hooks 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 def retryable? @@ -83,10 +90,6 @@ module Ci !self.pipeline.statuses.latest.include?(self) end - def retry - Ci::Build.retry(self) - end - def depends_on_builds # Get builds of the same type latest_builds = self.pipeline.builds.latest diff --git a/app/models/deployment.rb b/app/models/deployment.rb new file mode 100644 index 00000000000..7cdfc740441 --- /dev/null +++ b/app/models/deployment.rb @@ -0,0 +1,25 @@ +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 diff --git a/app/models/environment.rb b/app/models/environment.rb new file mode 100644 index 00000000000..623404ba634 --- /dev/null +++ b/app/models/environment.rb @@ -0,0 +1,11 @@ +class Environment < ActiveRecord::Base + belongs_to :project + + has_many :deployments + + validates_presence_of :name + + def last_deployment + deployments.last + end +end diff --git a/app/models/project.rb b/app/models/project.rb index e2f7ffe493c..be714ea41fd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -125,6 +125,8 @@ class Project < ActiveRecord::Base 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 :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 diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb index 64bcdac5c65..3a74ae094e8 100644 --- a/app/services/ci/create_builds_service.rb +++ b/app/services/ci/create_builds_service.rb @@ -29,7 +29,8 @@ module Ci :options, :allow_failure, :stage, - :stage_idx) + :stage_idx, + :environment) build_attrs.merge!(ref: @pipeline.ref, tag: @pipeline.tag, diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb new file mode 100644 index 00000000000..f745471913f --- /dev/null +++ b/app/services/create_deployment_service.rb @@ -0,0 +1,45 @@ +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 diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml new file mode 100644 index 00000000000..363c394d6d3 --- /dev/null +++ b/app/views/projects/deployments/_deployment.html.haml @@ -0,0 +1,32 @@ +%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" + · + = 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 +   + #{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' diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index e3216aea6cd..a4c88fface2 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -1,58 +1,32 @@ -%tr.commit - - commit = build.commit - - status = build.status +- last_deployment = environment.last_deployment +%tr.environment %td %strong - = link_to build.environment, namespace_project_environment_path(@project.namespace, @project, build.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} + = link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment), class: "monospace" %td - %div.branch-commit - - if commit.ref - = link_to commit.ref, namespace_project_commits_path(@project.namespace, @project, commit.ref), class: "monospace" - · - = link_to commit.short_sha, namespace_project_commit_path(@project.namespace, @project, commit.sha), class: "commit-id monospace" + - if last_deployment + %div.branch-commit + - if last_deployment.ref + = link_to last.ref, namespace_project_commits_path(@project.namespace, @project, last_deployment.ref), class: "monospace" + · + = 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 - %span - - 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 + No deployments yet %td - - if build.started_at && build.finished_at - %p - %i.fa.fa-clock-o -   - #{duration_in_words(build.finished_at, build.started_at)} - - if build.finished_at - %p - %i.fa.fa-calendar -   - #{time_ago_with_tooltip(build.finished_at)} + %p + %i.fa.fa-calendar +   + #{time_ago_with_tooltip(last_deployment.created_at)} %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} diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index e94bc97be9d..40d35ef3881 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -1,22 +1,22 @@ +- @no_container = true - page_title "Environments" -= render "header_title" += render "projects/pipelines/head" -.gray-content-block - Environments for this project +%div{ class: (container_class) } + .gray-content-block + Environments for this project -%ul.content-list - - if @environments.blank? - %li - .nothing-here-block No environments to show - - else - .table-holder - %table.table.builds - %tbody - %th Environment - %th Pipeline ID - %th Build ID - %th Changes - %th - %th - - @environments.each do |build| - = render "environment", build: build + %ul.content-list + - if @environments.blank? + %li + .nothing-here-block No environments to show + - else + .table-holder + %table.table.builds + %tbody + %th Environment + %th Last deployment + %th Date + %th + - @environments.each do |environment| + = render 'environment', environment: environment diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index ce2d9cf7d71..de5e686044f 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -1,30 +1,26 @@ +- @no_container = true - 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 - Latest deployments for - %strong - = @environment + %ul.content-list + - if @deployments.blank? + %li + .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 - - 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 @deployments - = render @builds, commit_sha: true, ref: true, allow_retry: true - - = paginate @builds, theme: 'gitlab' + = paginate @deployments, theme: 'gitlab' diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index f278d4e0538..3562d91dfbd 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -13,3 +13,9 @@ %span Builds %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 diff --git a/db/migrate/20160610204157_add_deployments.rb b/db/migrate/20160610204157_add_deployments.rb new file mode 100644 index 00000000000..c93d3bf64d3 --- /dev/null +++ b/db/migrate/20160610204157_add_deployments.rb @@ -0,0 +1,27 @@ +# 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 diff --git a/db/migrate/20160610204158_add_environments.rb b/db/migrate/20160610204158_add_environments.rb new file mode 100644 index 00000000000..8311fd39b01 --- /dev/null +++ b/db/migrate/20160610204158_add_environments.rb @@ -0,0 +1,17 @@ +# 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 diff --git a/db/migrate/20160610211845_add_environment_to_builds.rb b/db/migrate/20160610211845_add_environment_to_builds.rb new file mode 100644 index 00000000000..990e445ac55 --- /dev/null +++ b/db/migrate/20160610211845_add_environment_to_builds.rb @@ -0,0 +1,10 @@ +# 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 diff --git a/db/schema.rb b/db/schema.rb index aac327797e7..63df5efb879 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # 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 enable_extension "plpgsql" @@ -144,9 +144,9 @@ ActiveRecord::Schema.define(version: 20160608155312) do t.text "commands" t.integer "job_id" t.string "name" - t.boolean "deploy", default: false + t.boolean "deploy", default: false t.text "options" - t.boolean "allow_failure", default: false, null: false + t.boolean "allow_failure", default: false, null: false t.string "stage" t.integer "trigger_request_id" t.integer "stage_idx" @@ -161,6 +161,7 @@ ActiveRecord::Schema.define(version: 20160608155312) do t.text "artifacts_metadata" t.integer "erased_by_id" t.datetime "erased_at" + t.string "environment" 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 @@ -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 + 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| t.integer "user_id", null: false t.string "email", null: false @@ -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", ["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| t.string "target_type" t.integer "target_id" diff --git a/lib/api/builds.rb b/lib/api/builds.rb index 0ff8fa74a84..6bf59afab53 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -142,7 +142,7 @@ module API return not_found!(build) unless build 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, user_can_download_artifacts: can?(current_user, :read_build, user_project) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 130f5b0892e..5aacb59dc5c 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -7,7 +7,8 @@ module Ci ALLOWED_YAML_KEYS = [:before_script, :after_script, :image, :services, :types, :stages, :variables, :cache] ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when, :artifacts, :cache, - :dependencies, :before_script, :after_script, :variables] + :dependencies, :before_script, :after_script, :variables, + :environment] attr_reader :before_script, :after_script, :image, :services, :path, :cache @@ -85,6 +86,7 @@ module Ci except: job[:except], allow_failure: job[:allow_failure] || false, when: job[:when] || 'on_success', + environment: job[:environment], options: { image: job[:image] || @image, services: job[:services] || @services, @@ -203,6 +205,10 @@ module Ci 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" end + + if job[:environment] && !validate_string(job[:environment]) + raise ValidationError, "#{name} job: environment should be a string" + end end def validate_job_script!(name, job) -- cgit v1.2.1 From 4f00b93ddd07d8a31a04b37dbe150340e84ccfd8 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 11 Jun 2016 00:15:53 +0200 Subject: Add deployment views --- .../projects/environments_controller.rb | 29 +++++++++++++++++++++- app/services/create_deployment_service.rb | 11 ++------ .../projects/deployments/_deployment.html.haml | 14 +++++------ .../projects/environments/_environment.html.haml | 9 +++---- app/views/projects/environments/index.html.haml | 11 +++++--- app/views/projects/environments/new.html.haml | 15 +++++++++++ app/views/projects/environments/show.html.haml | 15 +++++++---- app/views/projects/pipelines/_head.html.haml | 1 + config/routes.rb | 2 +- 9 files changed, 74 insertions(+), 33 deletions(-) create mode 100644 app/views/projects/environments/new.html.haml diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 722954a6b78..c6a9a0a403a 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -1,17 +1,44 @@ class Projects::EnvironmentsController < Projects::ApplicationController layout 'project' before_action :authorize_read_environment! - before_action :environment, only: [:show] + before_action :environment, only: [:show, :destroy] def index @environments = project.environments end def show + @deployments = environment.deployments.order(id: :desc).page(params[:page]).per(30) + end + + def new + @environment = project.environments.new + end + + def create + @environment = project.environments.create(create_params) + unless @environment.persisted? + render 'new' + return + end + + redirect_to namespace_project_environment_path(project.namespace, project, @environment) + end + + def destroy + if @environment.destroy + redirect_to namespace_project_environments_path(project.namespace, project), notice: 'Environment was successfully removed.' + else + redirect_to namespace_project_environments_path(project.namespace, project), alert: 'Failed to remove environment.' + end end private + def create_params + params.require(:environment).permit(:name) + end + def environment @environment ||= project.environments.find(params[:id].to_s) @environment || render_404 diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index f745471913f..7408ec367f6 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -2,7 +2,8 @@ require_relative 'base_service' class CreateDeploymentService < BaseService def execute(deployable) - environment = find_or_create_environment(params[:environment]) + environment = find_environment(params[:environment]) + return error('no environment') unless environmnet deployment = create_deployment(environment, deployable) if deployment.persisted? @@ -14,14 +15,6 @@ class CreateDeploymentService < BaseService 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 diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index 363c394d6d3..539c297cad3 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -1,11 +1,11 @@ %tr.deployment %td - %strong= "##{environment.id}" + %strong= "##{deployment.iid}" %td %div.branch-commit - if deployment.ref - = link_to last.ref, namespace_project_commits_path(@project.namespace, @project, deployment.ref), class: "monospace" + = link_to deployment.ref, namespace_project_commits_path(@project.namespace, @project, deployment.ref), class: "monospace" · = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-id monospace" @@ -18,15 +18,13 @@ %td - if deployment.deployable - = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: "monospace" do + = link_to namespace_project_build_path(@project.namespace, @project, deployment.deployable), class: "monospace" do = "#{deployment.deployable.name} (##{deployment.deployable.id})" %td - %p - %i.fa.fa-calendar -   - #{time_ago_with_tooltip(deployment.created_at)} + #{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' + .pull-right + = link_to 'Retry', retry_namespace_project_build_path(@project.namespace, @project, deployment.deployable), method: :post, class: 'btn btn-build' diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index a4c88fface2..16d04832e1a 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -9,7 +9,7 @@ - if last_deployment %div.branch-commit - if last_deployment.ref - = link_to last.ref, namespace_project_commits_path(@project.namespace, @project, last_deployment.ref), class: "monospace" + = link_to last_deployment.ref, namespace_project_commits_path(@project.namespace, @project, last_deployment.ref), class: "monospace" · = link_to last_deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, last_deployment.sha), class: "commit-id monospace" @@ -24,9 +24,8 @@ No deployments yet %td - %p - %i.fa.fa-calendar -   - #{time_ago_with_tooltip(last_deployment.created_at)} + - if last_deployment + %p + #{time_ago_with_tooltip(last_deployment.created_at)} %td diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 40d35ef3881..2da8d068e9f 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -3,16 +3,19 @@ = render "projects/pipelines/head" %div{ class: (container_class) } - .gray-content-block - Environments for this project + - if can?(current_user, :create_environment, @project) + .top-area + .nav-controls + = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do + New environment - %ul.content-list + %ul.content-list.environments - if @environments.blank? %li .nothing-here-block No environments to show - else .table-holder - %table.table.builds + %table.table %tbody %th Environment %th Last deployment diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml new file mode 100644 index 00000000000..5e8bc596f1e --- /dev/null +++ b/app/views/projects/environments/new.html.haml @@ -0,0 +1,15 @@ +- page_title "New Environment" + +%h3.page-title + New Environment +%hr + += form_for @environment, url: namespace_project_environments_path(@project.namespace, @project), html: { id: "new-environment-form", class: "form-horizontal js-new-environment-form js-requires-input" } do |f| + = form_errors(@environment) + .form-group + = f.label :ref, 'Name', class: 'control-label' + .col-sm-10 + = f.text_field :name, required: true, tabindex: 2, class: 'form-control' + .form-actions + = f.submit 'Create', class: 'btn btn-create', tabindex: 3 + = link_to 'Cancel', namespace_project_environments_path(@project.namespace, @project), class: 'btn btn-cancel' diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index de5e686044f..dc07ad1a769 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -3,21 +3,26 @@ = render "projects/pipelines/head" %div{ class: (container_class) } - .gray-content-block - Latest deployments for - %strong= @environment.name + .top-area + .col-md-9 + %h3= @environment.name.titleize + + .col-md-3 + .nav-controls + = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post %ul.content-list - if @deployments.blank? %li - .nothing-here-block No deployment for specific environment + .nothing-here-block No deployments for #{@environment.name} - else .table-holder %table.table.builds %thead %tr + %th ID %th Commit - %th Context + %th Build %th Date %th diff --git a/app/views/projects/pipelines/_head.html.haml b/app/views/projects/pipelines/_head.html.haml index 3562d91dfbd..8374cb4223d 100644 --- a/app/views/projects/pipelines/_head.html.haml +++ b/app/views/projects/pipelines/_head.html.haml @@ -19,3 +19,4 @@ = link_to project_environments_path(@project), title: 'Environments', class: 'shortcuts-environments' do %span Environments + %span.badge.count.environments_counter= number_with_delimiter(@project.environments.count) diff --git a/config/routes.rb b/config/routes.rb index 6b8402c40dd..d50e2535e75 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -704,7 +704,7 @@ Rails.application.routes.draw do end end - resources :environments, only: [:index, :show] + resources :environments, only: [:index, :show, :new, :create, :destroy] resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do collection do -- cgit v1.2.1 From 672aec4a2da36d9ee755156be4a907f4b8d96347 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 13 Jun 2016 15:00:51 +0100 Subject: Improved views --- .../projects/environments/_environment.html.haml | 4 +-- app/views/projects/environments/index.html.haml | 27 ++++++++--------- app/views/projects/environments/new.html.haml | 24 +++++++-------- app/views/projects/environments/show.html.haml | 35 +++++++++++----------- 4 files changed, 44 insertions(+), 46 deletions(-) diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index 16d04832e1a..5ca57bd153d 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -3,7 +3,7 @@ %tr.environment %td %strong - = link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment), class: "monospace" + = link_to environment.name, namespace_project_environment_path(@project.namespace, @project, environment) %td - if last_deployment @@ -27,5 +27,3 @@ - if last_deployment %p #{time_ago_with_tooltip(last_deployment.created_at)} - - %td diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 2da8d068e9f..4a445a157ec 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -9,17 +9,16 @@ = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do New environment - %ul.content-list.environments - - if @environments.blank? - %li - .nothing-here-block No environments to show - - else - .table-holder - %table.table - %tbody - %th Environment - %th Last deployment - %th Date - %th - - @environments.each do |environment| - = render 'environment', environment: environment + - if @environments.blank? + %ul.content-list.environments + %li.nothing-here-block + No environments to show + - else + .table-holder + %table.table + %tbody + %th Environment + %th Last deployment + %th Date + - @environments.each do |environment| + = render 'environment', environment: environment diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index 5e8bc596f1e..c7abac6e49f 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -1,15 +1,15 @@ - page_title "New Environment" += render "projects/pipelines/head" -%h3.page-title - New Environment -%hr +.row.prepend-top-default.append-bottom-default + .col-lg-3 + %h4.prepend-top-0 + New Environment -= form_for @environment, url: namespace_project_environments_path(@project.namespace, @project), html: { id: "new-environment-form", class: "form-horizontal js-new-environment-form js-requires-input" } do |f| - = form_errors(@environment) - .form-group - = f.label :ref, 'Name', class: 'control-label' - .col-sm-10 - = f.text_field :name, required: true, tabindex: 2, class: 'form-control' - .form-actions - = f.submit 'Create', class: 'btn btn-create', tabindex: 3 - = link_to 'Cancel', namespace_project_environments_path(@project.namespace, @project), class: 'btn btn-cancel' + = form_for @environment, url: namespace_project_environments_path(@project.namespace, @project), html: { id: "new-environment-form", class: "col-lg-9 js-new-environment-form js-requires-input" } do |f| + = form_errors(@environment) + .form-group + = f.label :ref, 'Environment name', class: 'label-light' + = f.text_field :name, required: true, class: 'form-control' + = f.submit 'Create environment', class: 'btn btn-create' + = link_to "Cancel", namespace_project_environments_path(@project.namespace, @project), class: "btn btn-cancel" diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index dc07ad1a769..f5e30d75b42 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -9,23 +9,24 @@ .col-md-3 .nav-controls - = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post + = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :delete - %ul.content-list - - if @deployments.blank? - %li - .nothing-here-block No deployments for #{@environment.name} - - else - .table-holder - %table.table.builds - %thead - %tr - %th ID - %th Commit - %th Build - %th Date - %th + - if @deployments.blank? + %ul.content-list + %li.nothing-here-block + No deployments for + %strong= @environment.name + - else + .table-holder + %table.table.builds + %thead + %tr + %th ID + %th Commit + %th Build + %th Date + %th - = render @deployments + = render @deployments - = paginate @deployments, theme: 'gitlab' + = paginate @deployments, theme: 'gitlab' -- cgit v1.2.1 From 3656a6edf37f9e24e6c080223cbfddff464e7962 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 14 Jun 2016 13:04:10 +0200 Subject: Make retry action on pipeline to save a user --- app/controllers/projects/pipelines_controller.rb | 2 +- app/models/ci/pipeline.rb | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index cac440ae53e..127bd1a4318 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -32,7 +32,7 @@ class Projects::PipelinesController < Projects::ApplicationController end def retry - pipeline.retry_failed + pipeline.retry_failed(current_user) redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project) end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 85d9e0856d1..4bbfb4cc806 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -76,8 +76,10 @@ module Ci builds.running_or_pending.each(&:cancel) end - def retry_failed - builds.latest.failed.select(&:retryable?).each(&:retry) + def retry_failed(user) + builds.latest.failed.select(&:retryable?).each do |build| + Ci::Build.retry(build, user) + end end def latest? -- cgit v1.2.1 From e8f09f02bf8b0053f276a8e5ce0bdd18c621a1a3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 14 Jun 2016 13:04:21 +0200 Subject: Validate environment name with regex --- app/models/environment.rb | 6 ++- lib/ci/gitlab_ci_yaml_processor.rb | 8 +++- lib/gitlab/regex.rb | 8 ++++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 66 +++++++++++++++++++++++++--- 4 files changed, 78 insertions(+), 10 deletions(-) diff --git a/app/models/environment.rb b/app/models/environment.rb index 623404ba634..b29cca8fbe2 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -3,7 +3,11 @@ class Environment < ActiveRecord::Base has_many :deployments - validates_presence_of :name + validates :name, + presence: true, + length: { within: 0..255 }, + format: { with: Gitlab::Regex.environment_name_regex, + message: Gitlab::Regex.environment_name_regex_message } def last_deployment deployments.last diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 66f1bcea4ff..b19ce4aaff9 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -214,8 +214,8 @@ module Ci raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always" end - if job[:environment] && !validate_string(job[:environment]) - raise ValidationError, "#{name} job: environment should be a string" + if job[:environment] && !validate_environment(job[:environment]) + raise ValidationError, "#{name} job: environment parameter #{Gitlab::Regex.environment_name_regex_message}" end end @@ -322,6 +322,10 @@ module Ci value.in?([true, false]) end + def validate_environment(value) + value.is_a?(String) && value =~ Gitlab::Regex.environment_name_regex + end + def process?(only_params, except_params, ref, tag, trigger_request) if only_params.present? return false unless matching?(only_params, ref, tag, trigger_request) diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 1cbd6d945a0..c84c68f96f6 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -100,5 +100,13 @@ module Gitlab def container_registry_reference_regex git_reference_regex end + + def environment_name_regex + @environment_name_regex ||= /\A[a-zA-Z0-9_-]+\z/.freeze + end + + def environment_name_regex_message + "can contain only letters, digits, '-' and '_'." + end end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 304290d6608..530aa79955a 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -26,7 +26,8 @@ module Ci tag_list: [], options: {}, allow_failure: false, - when: "on_success" + when: "on_success", + environment: nil, }) end @@ -387,7 +388,8 @@ module Ci services: ["mysql"] }, allow_failure: false, - when: "on_success" + when: "on_success", + environment: nil, }) end @@ -415,7 +417,8 @@ module Ci services: ["postgresql"] }, allow_failure: false, - when: "on_success" + when: "on_success", + environment: nil, }) end end @@ -599,7 +602,8 @@ module Ci } }, when: "on_success", - allow_failure: false + allow_failure: false, + environment: nil, }) end @@ -621,6 +625,51 @@ module Ci end end + describe '#environment' do + let(:config) do + { + deploy_to_production: { stage: 'deploy', script: 'test', environment: environment } + } + end + + let(:processor) { GitlabCiYamlProcessor.new(YAML.dump(config)) } + let(:builds) { processor.builds_for_stage_and_ref('deploy', 'master') } + + context 'when a production environment is specified' do + let(:environment) { 'production' } + + it 'does return production' do + expect(builds.size).to eq(1) + expect(builds.first[:environment]).to eq(environment) + end + end + + context 'when no environment is specified' do + let(:environment) { nil } + + it 'does return nil environment' do + expect(builds.size).to eq(1) + expect(builds.first[:environment]).to be_nil + end + end + + context 'is not a string' do + let(:environment) { 1 } + + it 'raises error' do + expect { builds }.to raise_error("deploy_to_production job: environment parameter #{Gitlab::Regex.environment_name_regex_message}") + end + end + + context 'is not a valid string' do + let(:environment) { 'production staging' } + + it 'raises error' do + expect { builds }.to raise_error("deploy_to_production job: environment parameter #{Gitlab::Regex.environment_name_regex_message}") + end + end + end + describe "Dependencies" do let(:config) do { @@ -682,7 +731,8 @@ module Ci tag_list: [], options: {}, when: "on_success", - allow_failure: false + allow_failure: false, + environment: nil, }) end end @@ -727,7 +777,8 @@ module Ci tag_list: [], options: {}, when: "on_success", - allow_failure: false + allow_failure: false, + environment: nil, }) expect(subject.second).to eq({ except: nil, @@ -739,7 +790,8 @@ module Ci tag_list: [], options: {}, when: "on_success", - allow_failure: false + allow_failure: false, + environment: nil, }) end end -- cgit v1.2.1 From 3ade826065f38e3734090cf34fbfc28b68ba79d0 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 14 Jun 2016 13:51:12 +0200 Subject: Add specs for models and services --- app/models/deployment.rb | 2 + app/models/environment.rb | 4 ++ app/services/create_deployment_service.rb | 40 +++++++---------- db/migrate/20160610204157_add_deployments.rb | 4 +- db/schema.rb | 4 +- doc/permissions/permissions.md | 2 + spec/factories/deployments.rb | 12 +++++ spec/factories/environments.rb | 7 +++ spec/models/deployment_spec.rb | 17 +++++++ spec/models/environment_spec.rb | 14 ++++++ spec/models/project_spec.rb | 2 + spec/services/create_deployment_service_spec.rb | 59 +++++++++++++++++++++++++ 12 files changed, 139 insertions(+), 28 deletions(-) create mode 100644 spec/factories/deployments.rb create mode 100644 spec/factories/environments.rb create mode 100644 spec/models/deployment_spec.rb create mode 100644 spec/models/environment_spec.rb create mode 100644 spec/services/create_deployment_service_spec.rb diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 7cdfc740441..44a0a7fdd10 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -8,6 +8,8 @@ class Deployment < ActiveRecord::Base validates_presence_of :sha validates_presence_of :ref + validates_associated :project + validates_associated :environment delegate :name, to: :environment, prefix: true diff --git a/app/models/environment.rb b/app/models/environment.rb index b29cca8fbe2..3eab137718e 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -9,6 +9,10 @@ class Environment < ActiveRecord::Base format: { with: Gitlab::Regex.environment_name_regex, message: Gitlab::Regex.environment_name_regex_message } + validates_uniqueness_of :name, scope: :project_id + + validates_associated :project + def last_deployment deployments.last end diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 7408ec367f6..eec1773073e 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -1,38 +1,30 @@ require_relative 'base_service' class CreateDeploymentService < BaseService - def execute(deployable) - environment = find_environment(params[:environment]) - return error('no environment') unless environmnet + def execute(deployable = nil) + environment = create_or_find_environment(params[:environment]) - deployment = create_deployment(environment, deployable) - if deployment.persisted? - success(deployment) - else - error(deployment.errors) - end + project.deployments.create( + environment: environment, + ref: params[:ref], + tag: params[:tag], + sha: params[:sha], + user: current_user, + deployable: deployable, + ) end private - def find_environment(environment) - project.environments.find_by(name: environment) + def create_or_find_environment(environment) + find_environment(environment) || create_environment(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, - ) + def create_environment(environment) + project.environments.create(name: environment) end - def success(deployment) - out = super() - out[:deployment] = deployment - out + def find_environment(environment) + project.environments.find_by(name: environment) end end diff --git a/db/migrate/20160610204157_add_deployments.rb b/db/migrate/20160610204157_add_deployments.rb index c93d3bf64d3..557b78f91e1 100644 --- a/db/migrate/20160610204157_add_deployments.rb +++ b/db/migrate/20160610204157_add_deployments.rb @@ -13,8 +13,8 @@ class AddDeployments < ActiveRecord::Migration t.boolean :tag t.string :sha t.integer :user_id - t.integer :deployable_id, null: false - t.string :deployable_type, null: false + t.integer :deployable_id + t.string :deployable_type t.datetime :created_at t.datetime :updated_at end diff --git a/db/schema.rb b/db/schema.rb index cd6c087c847..51a6044f99c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -390,8 +390,8 @@ ActiveRecord::Schema.define(version: 20160610301627) do t.boolean "tag" t.string "sha" t.integer "user_id" - t.integer "deployable_id", null: false - t.string "deployable_type", null: false + t.integer "deployable_id" + t.string "deployable_type" t.datetime "created_at" t.datetime "updated_at" end diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index b76ce31cbad..666dcfafd03 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -28,6 +28,7 @@ documentation](../workflow/add-user/add-user.md). | Manage labels | | ✓ | ✓ | ✓ | ✓ | | See a commit status | | ✓ | ✓ | ✓ | ✓ | | See a container registry | | ✓ | ✓ | ✓ | ✓ | +| See a environments | | ✓ | ✓ | ✓ | ✓ | | Manage merge requests | | | ✓ | ✓ | ✓ | | Create new merge request | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ | @@ -40,6 +41,7 @@ documentation](../workflow/add-user/add-user.md). | Create or update commit status | | | ✓ | ✓ | ✓ | | Update a container registry | | | ✓ | ✓ | ✓ | | Remove a container registry image | | | ✓ | ✓ | ✓ | +| Manage environments | | | ✓ | ✓ | ✓ | | Create new milestones | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ | diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb new file mode 100644 index 00000000000..f335a111a7d --- /dev/null +++ b/spec/factories/deployments.rb @@ -0,0 +1,12 @@ +FactoryGirl.define do + factory :deployment, class: Deployment do + sha '97de212e80737a608d939f648d959671fb0a0142' + ref 'master' + + environment factory: :environment + + after(:build) do |deployment, evaluator| + deployment.project = deployment.environment.project + end + end +end diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb new file mode 100644 index 00000000000..07265c26ca3 --- /dev/null +++ b/spec/factories/environments.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :environment, class: Environment do + sequence(:name) { |n| "environment#{n}" } + + project factory: :empty_project + end +end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb new file mode 100644 index 00000000000..b273018707f --- /dev/null +++ b/spec/models/deployment_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Deployment, models: true do + subject { build(:deployment) } + + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:environment) } + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:deployable) } + + it { is_expected.to delegate_method(:name).to(:environment).with_prefix } + it { is_expected.to delegate_method(:commit).to(:project) } + it { is_expected.to delegate_method(:commit_title).to(:commit).as(:try) } + + it { is_expected.to validate_presence_of(:ref) } + it { is_expected.to validate_presence_of(:sha) } +end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb new file mode 100644 index 00000000000..7629af6a570 --- /dev/null +++ b/spec/models/environment_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe Environment, models: true do + let(:environment) { create(:environment) } + + it { is_expected.to belong_to(:project) } + it { is_expected.to have_many(:deployments) } + + it { is_expected.to delegate_method(:last_deployment).to(:deployments).as(:last) } + + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) } + it { is_expected.to validate_length_of(:name).is_within(0..255) } +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index de8815f5a38..1f626ff2647 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -28,6 +28,8 @@ describe Project, models: true do it { is_expected.to have_many(:runners) } it { is_expected.to have_many(:variables) } it { is_expected.to have_many(:triggers) } + it { is_expected.to have_many(:environments).dependent(:destroy) } + it { is_expected.to have_many(:deployments).dependent(:destroy) } it { is_expected.to have_many(:todos).dependent(:destroy) } end diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb new file mode 100644 index 00000000000..76f3e0ac9ff --- /dev/null +++ b/spec/services/create_deployment_service_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe CreateDeploymentService, services: true do + let(:build) { create(:ci_build) } + let(:project) { build.project } + let(:user) { create(:user) } + + let(:service) { described_class.new(project, user, params) } + + describe '#execute' do + let(:params) do + { environment: 'production', + ref: 'master', + sha: build.sha, + } + end + + subject { service.execute } + + context 'when no environments exist' do + it 'does create a new environment' do + expect { subject }.to change { Environment.count }.by(1) + end + + it 'does create a deployment' do + expect(subject).to be_persisted + end + end + + context 'when environment exist' do + before { create(:environment, project: project, name: 'production') } + + it 'does not create a new environment' do + expect { subject }.not_to change { Environment.count } + end + + it 'does create a deployment' do + expect(subject).to be_persisted + end + end + + context 'for environment with invalid name' do + let(:params) do + { environment: 'name with spaces', + ref: 'master', + sha: build.sha, + } + end + + it 'does not create a new environment' do + expect { subject }.not_to change { Environment.count } + end + + it 'does not create a deployment' do + expect(subject).not_to be_persisted + end + end + end +end -- cgit v1.2.1 From e129f66d9e597f43f7a85243ddedf3de0fc4946a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 14 Jun 2016 14:43:16 +0200 Subject: Add gitlab-ci.yml documentation for environments --- doc/ci/yaml/README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 0707555e393..0546fa50f1c 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -28,6 +28,7 @@ If you want a quick introduction to GitLab CI, follow our - [only and except](#only-and-except) - [tags](#tags) - [when](#when) + - [environment](#environment) - [artifacts](#artifacts) - [artifacts:name](#artifacts-name) - [artifacts:when](#artifacts-when) @@ -353,6 +354,7 @@ job_name: | cache | no | Define list of files that should be cached between subsequent runs | | before_script | no | Override a set of commands that are executed before build | | after_script | no | Override a set of commands that are executed after build | +| environment | no | Defines a name of environment to which deployment is done by this build | ### script @@ -524,6 +526,31 @@ The above script will: 1. Execute `cleanup_build_job` only when `build_job` fails 2. Always execute `cleanup_job` as the last step in pipeline. +### environment + +>**Note:** +Introduced in GitLab v8.9.0. + +`environment` is used to define that job does deployment to specific environment. +This allows to easily track all deployments to your environments straight from GitLab. + +If `environment` is specified and no environment under that name does exist a new one will be created automatically. + +The `environment` name must contain only letters, digits, '-' and '_'. + +--- + +**Example configurations** + +``` +deploy to production: + stage: deploy + script: git push production HEAD:master + environment: production +``` + +The `deploy to production` job will be marked as doing deployment to `production` environment. + ### artifacts >**Notes:** -- cgit v1.2.1 From bb6f246790fb3a6b85ab2fd9341566557da64a23 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 14 Jun 2016 14:43:45 +0200 Subject: Authorize environments controller actions --- .../projects/environments_controller.rb | 2 + app/views/projects/environments/new.html.haml | 2 +- app/views/projects/environments/show.html.haml | 3 +- .../security/project/public_access_spec.rb | 43 ++++++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index c6a9a0a403a..4f8dadd6adf 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -1,6 +1,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController layout 'project' before_action :authorize_read_environment! + before_action :authorize_create_environment!, only: [:new, :create] + before_action :authorize_update_environment!, only: [:destroy] before_action :environment, only: [:show, :destroy] def index diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index c7abac6e49f..ade41d9de2d 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -9,7 +9,7 @@ = form_for @environment, url: namespace_project_environments_path(@project.namespace, @project), html: { id: "new-environment-form", class: "col-lg-9 js-new-environment-form js-requires-input" } do |f| = form_errors(@environment) .form-group - = f.label :ref, 'Environment name', class: 'label-light' + = f.label :name, 'Environment name', class: 'label-light' = f.text_field :name, required: true, class: 'form-control' = f.submit 'Create environment', class: 'btn btn-create' = link_to "Cancel", namespace_project_environments_path(@project.namespace, @project), class: "btn btn-cancel" diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index f5e30d75b42..1d39bef9427 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -9,7 +9,8 @@ .col-md-3 .nav-controls - = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :delete + - if can?(current_user, :update_environment, @project) + = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :delete - if @deployments.blank? %ul.content-list diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index c5f741709ad..f6c6687e162 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -175,6 +175,49 @@ describe "Public Project Access", feature: true do end end + describe "GET /:project_path/environments" do + subject { namespace_project_environments_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/environments/:id" do + let(:environment) { create(:environment, project: project) } + subject { namespace_project_environments_path(project.namespace, project, environment) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + + describe "GET /:project_path/environments/new" do + subject { new_namespace_project_environment_path(project.namespace, project) } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :external } + it { is_expected.to be_denied_for :visitor } + end + describe "GET /:project_path/blob" do let(:commit) { project.repository.commit } -- cgit v1.2.1 From 6209b60c96d8b380ac184d83647c3c8b0b026cac Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 14 Jun 2016 14:44:09 +0200 Subject: Properly create a new deployment after build success --- app/models/ci/build.rb | 8 ++- spec/services/create_deployment_service_spec.rb | 66 +++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 60202525727..9215ad36547 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -75,9 +75,13 @@ module Ci build.execute_hooks end - after_transition any: :success do |build| + after_transition any => [:success] do |build| if build.environment.present? - CreateDeploymentService.new(build.project, build.user, environment: build.environment).execute(build) + service = CreateDeploymentService.new(build.project, build.user, + environment: build.environment, + sha: build.sha, ref: build.ref, + tag: build.tag) + service.execute(build) end end end diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index 76f3e0ac9ff..b6ae3505379 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -1,8 +1,7 @@ require 'spec_helper' describe CreateDeploymentService, services: true do - let(:build) { create(:ci_build) } - let(:project) { build.project } + let(:project) { create(:empty_project) } let(:user) { create(:user) } let(:service) { described_class.new(project, user, params) } @@ -11,7 +10,7 @@ describe CreateDeploymentService, services: true do let(:params) do { environment: 'production', ref: 'master', - sha: build.sha, + sha: '97de212e80737a608d939f648d959671fb0a0142', } end @@ -43,7 +42,7 @@ describe CreateDeploymentService, services: true do let(:params) do { environment: 'name with spaces', ref: 'master', - sha: build.sha, + sha: '97de212e80737a608d939f648d959671fb0a0142', } end @@ -56,4 +55,63 @@ describe CreateDeploymentService, services: true do end end end + + describe 'processing of builds' do + let(:environment) { nil } + + shared_examples 'does not create environment and deployment' do + it 'does not create a new environment' do + expect { subject }.not_to change { Environment.count } + end + + it 'does not create a new deployment' do + expect { subject }.not_to change { Deployment.count } + end + + it 'does not call a service' do + expect_any_instance_of(described_class).not_to receive(:execute) + subject + end + end + + shared_examples 'does create environment and deployment' do + it 'does create a new environment' do + expect { subject }.to change { Environment.count }.by(1) + end + + it 'does create a new deployment' do + expect { subject }.to change { Deployment.count }.by(1) + end + + it 'does call a service' do + expect_any_instance_of(described_class).to receive(:execute) + subject + end + end + + context 'without environment specified' do + let(:build) { create(:ci_build, project: project) } + + it_behaves_like 'does not create environment and deployment' do + subject { build.success } + end + end + + context 'when environment is specified' do + let(:pipeline) { create(:ci_pipeline, project: project) } + let(:build) { create(:ci_build, pipeline: pipeline, environment: 'production') } + + context 'when build succeeds' do + it_behaves_like 'does create environment and deployment' do + subject { build.success } + end + end + + context 'when build fails' do + it_behaves_like 'does not create environment and deployment' do + subject { build.drop } + end + end + end + end end -- cgit v1.2.1 From 30877effb15d8a3eccc13925549a4c97de93c58e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 14 Jun 2016 14:47:00 +0200 Subject: Test environment controller specs --- app/models/deployment.rb | 4 + .../projects/deployments/_deployment.html.haml | 6 +- spec/features/environments_spec.rb | 159 +++++++++++++++++++++ 3 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 spec/features/environments_spec.rb diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 44a0a7fdd10..32799ee27e6 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -24,4 +24,8 @@ class Deployment < ActiveRecord::Base def short_sha Commit::truncate_sha(sha) end + + def last? + self == environment.last_deployment + end end diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index 539c297cad3..1ac17af8b58 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -27,4 +27,8 @@ %td - if can?(current_user, :update_deployment, @project) && deployment.deployable .pull-right - = link_to 'Retry', retry_namespace_project_build_path(@project.namespace, @project, deployment.deployable), method: :post, class: 'btn btn-build' + = link_to retry_namespace_project_build_path(@project.namespace, @project, deployment.deployable), method: :post, class: 'btn btn-build' do + - if deployment.last? + Retry + - else + Rollback diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb new file mode 100644 index 00000000000..b73bb30e216 --- /dev/null +++ b/spec/features/environments_spec.rb @@ -0,0 +1,159 @@ +require 'spec_helper' + +describe 'Environments' do + include GitlabRoutingHelper + + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + let(:role) { :developer } + + before do + login_as(user) + project.team << [user, role] + end + + describe 'GET /:project/environments' do + subject { visit namespace_project_environments_path(project.namespace, project) } + + context 'without environments' do + it 'does show no environments' do + subject + + expect(page).to have_content('No environments to show') + end + end + + context 'with environments' do + let!(:environment) { create(:environment, project: project) } + + it 'does show environment name' do + subject + + expect(page).to have_link(environment.name) + end + + context 'without deployments' do + it 'does show no deployments' do + subject + + expect(page).to have_content('No deployments yet') + end + end + + context 'with deployments' do + let!(:deployment) { create(:deployment, environment: environment) } + + it 'does show deployment SHA' do + subject + + expect(page).to have_link(deployment.short_sha) + end + end + end + + it 'does have a New environment button' do + subject + + expect(page).to have_link('New environment') + end + end + + describe 'GET /:project/environments/:id' do + let(:environment) { create(:environment, project: project) } + + subject { visit namespace_project_environment_path(project.namespace, project, environment) } + + context 'without deployments' do + it 'does show no deployments' do + subject + + expect(page).to have_content('No deployments for') + end + end + + context 'with deployments' do + let!(:deployment) { create(:deployment, environment: environment) } + + before { subject } + + it 'does show deployment SHA' do + expect(page).to have_link(deployment.short_sha) + end + + it 'does not show a retry button for deployment without build' do + expect(page).not_to have_link('Retry') + end + + context 'with build' do + let(:build) { create(:ci_build, project: project) } + let(:deployment) { create(:deployment, environment: environment, deployable: build) } + + it 'does show build name' do + expect(page).to have_link("#{build.name} (##{build.id})") + end + + it 'does show retry button' do + expect(page).to have_link('Retry') + end + end + end + end + + describe 'POST /:project/environments' do + before { visit namespace_project_environments_path(project.namespace, project) } + + context 'when logged as developer' do + before { click_link 'New environment' } + + context 'for valid name' do + before do + fill_in('Environment name', with: 'production') + click_on 'Create environment' + end + + it 'does create a new pipeline' do + expect(page).to have_content('production') + end + end + + context 'for invalid name' do + before do + fill_in('Environment name', with: 'name with spaces') + click_on 'Create environment' + end + + it { expect(page).to have_content('Name can contain only letters') } + end + end + + context 'when logged as reporter' do + let(:role) { :reporter } + + it 'does not have a New environment link' do + expect(page).not_to have_link('New environment') + end + end + end + + describe 'DELETE /:project/environments/:id' do + let(:environment) { create(:environment, project: project) } + + before { visit namespace_project_environment_path(project.namespace, project, environment) } + + context 'when logged as developer' do + before { click_link 'Destroy' } + + it 'does not have environment' do + expect(page).not_to have_link(environment.name) + end + end + + context 'when logged as reporter' do + let(:role) { :reporter } + + it 'does not have a Destroy link' do + expect(page).not_to have_link('Destroy') + end + end + end +end -- cgit v1.2.1 From dc41a933f4f9a79e7160e38f248d33d7beb99bb6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 14 Jun 2016 16:11:28 +0200 Subject: Update scss to make the views look nicer --- app/assets/stylesheets/pages/environments.scss | 5 +++++ app/views/projects/deployments/_deployment.html.haml | 2 +- app/views/projects/environments/_environment.html.haml | 7 +++---- app/views/projects/environments/index.html.haml | 2 +- app/views/projects/environments/show.html.haml | 6 +++--- 5 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 app/assets/stylesheets/pages/environments.scss diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss new file mode 100644 index 00000000000..e160d676e35 --- /dev/null +++ b/app/assets/stylesheets/pages/environments.scss @@ -0,0 +1,5 @@ +.environments { + .commit-title { + margin: 0; + } +} diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index 1ac17af8b58..28c003d22a8 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -9,7 +9,7 @@ · = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-id monospace" - %p + %p.commit-title %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" diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index 5ca57bd153d..c2e6d11f941 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -13,17 +13,16 @@ · = link_to last_deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, last_deployment.sha), class: "commit-id monospace" - %p + %p.commit-title %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.commit-title No deployments yet %td - if last_deployment - %p - #{time_ago_with_tooltip(last_deployment.created_at)} + #{time_ago_with_tooltip(last_deployment.created_at)} diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 4a445a157ec..fa1046bbe1a 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -15,7 +15,7 @@ No environments to show - else .table-holder - %table.table + %table.table.environments %tbody %th Environment %th Last deployment diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 1d39bef9427..6454101004a 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -5,7 +5,7 @@ %div{ class: (container_class) } .top-area .col-md-9 - %h3= @environment.name.titleize + %h3.page-title= @environment.name.titleize .col-md-3 .nav-controls @@ -13,13 +13,13 @@ = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :delete - if @deployments.blank? - %ul.content-list + %ul.content-list.environments %li.nothing-here-block No deployments for %strong= @environment.name - else .table-holder - %table.table.builds + %table.table.environments %thead %tr %th ID -- cgit v1.2.1 From 14a02a6a95353948d00f8f973b35b80ac06f4599 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 14 Jun 2016 18:34:48 +0200 Subject: Improve design after review --- app/controllers/projects/environments_controller.rb | 17 +++++++++-------- app/models/ability.rb | 16 +++++++++++++--- app/models/ci/build.rb | 3 ++- app/models/deployment.rb | 10 +++++----- app/models/environment.rb | 5 ++--- app/services/create_deployment_service.rb | 20 ++++---------------- app/views/layouts/nav/_project.html.haml | 2 +- app/views/projects/deployments/_commit.html.haml | 12 ++++++++++++ app/views/projects/deployments/_deployment.html.haml | 17 +++-------------- .../projects/environments/_environment.html.haml | 13 +------------ app/views/projects/environments/index.html.haml | 3 +-- app/views/projects/environments/new.html.haml | 3 ++- app/views/projects/environments/show.html.haml | 2 +- doc/permissions/permissions.md | 5 +++-- 14 files changed, 59 insertions(+), 69 deletions(-) create mode 100644 app/views/projects/deployments/_commit.html.haml diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 4f8dadd6adf..1f9f676c63b 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -19,20 +19,22 @@ class Projects::EnvironmentsController < Projects::ApplicationController def create @environment = project.environments.create(create_params) - unless @environment.persisted? + + if @environment.persisted? + redirect_to namespace_project_environment_path(project.namespace, project, @environment) + else render 'new' - return end - - redirect_to namespace_project_environment_path(project.namespace, project, @environment) end def destroy if @environment.destroy - redirect_to namespace_project_environments_path(project.namespace, project), notice: 'Environment was successfully removed.' + flash[:notice] = 'Environment was successfully removed.' else - redirect_to namespace_project_environments_path(project.namespace, project), alert: 'Failed to remove environment.' + flash[:alert] = 'Failed to remove environment.' end + + redirect_to namespace_project_environments_path(project.namespace, project) end private @@ -42,7 +44,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController end def environment - @environment ||= project.environments.find(params[:id].to_s) - @environment || render_404 + @environment ||= project.environments.find_by!(id: params[:id]) end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 93905abbee8..32e45674682 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -18,6 +18,8 @@ class Ability when Namespace then namespace_abilities(user, subject) when GroupMember then group_member_abilities(user, subject) when ProjectMember then project_member_abilities(user, subject) + when Deployment then deployment_abilities(user, subject) + when Environment then environment_abilities(user, subject) when User then user_abilities else [] end.concat(global_abilities(user)) @@ -249,9 +251,7 @@ class Ability :create_container_image, :update_container_image, :create_environment, - :update_environment, - :create_deployment, - :update_deployment, + :create_deployment ] end @@ -269,6 +269,8 @@ class Ability @project_master_rules ||= project_dev_rules + [ :push_code_to_protected_branches, :update_project_snippet, + :update_environment, + :update_deployment, :admin_milestone, :admin_project_snippet, :admin_project_member, @@ -525,6 +527,14 @@ class Ability project_abilities(user, subject.project) end + def deployment_abilities(user, subject) + project_abilities(user, subject.project) + end + + def environment_abilities(user, subject) + project_abilities(user, subject.project) + end + private def restricted_public_level? diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index ac039a3b148..764d8e4e136 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -81,7 +81,8 @@ module Ci if build.environment.present? service = CreateDeploymentService.new(build.project, build.user, environment: build.environment, - sha: build.sha, ref: build.ref, + sha: build.sha, + ref: build.ref, tag: build.tag) service.execute(build) end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 32799ee27e6..d9006b70e30 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -6,10 +6,10 @@ class Deployment < ActiveRecord::Base belongs_to :user belongs_to :deployable, polymorphic: true - validates_presence_of :sha - validates_presence_of :ref - validates_associated :project - validates_associated :environment + validates :sha, presence: true + validates :ref, presence: true + validates :project, associated: true + validates :environment, associated: true delegate :name, to: :environment, prefix: true @@ -22,7 +22,7 @@ class Deployment < ActiveRecord::Base end def short_sha - Commit::truncate_sha(sha) + Commit.truncate_sha(sha) end def last? diff --git a/app/models/environment.rb b/app/models/environment.rb index 3eab137718e..ac6f8c81e01 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -5,13 +5,12 @@ class Environment < ActiveRecord::Base validates :name, presence: true, + uniqueness: { scope: :project_id }, length: { within: 0..255 }, format: { with: Gitlab::Regex.environment_name_regex, message: Gitlab::Regex.environment_name_regex_message } - validates_uniqueness_of :name, scope: :project_id - - validates_associated :project + validates :project, associated: true def last_deployment deployments.last diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index eec1773073e..efeb9df9527 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -2,7 +2,9 @@ require_relative 'base_service' class CreateDeploymentService < BaseService def execute(deployable = nil) - environment = create_or_find_environment(params[:environment]) + environment = project.environments.find_or_create_by( + name: params[:environment] + ) project.deployments.create( environment: environment, @@ -10,21 +12,7 @@ class CreateDeploymentService < BaseService tag: params[:tag], sha: params[:sha], user: current_user, - deployable: deployable, + deployable: deployable ) end - - private - - def create_or_find_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 end diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 0ac44b084a9..32a91afab8d 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -40,7 +40,7 @@ Code - if project_nav_tab? :pipelines - = nav_link(controller: :pipelines) do + = nav_link(controller: [:pipelines, :builds, :environments]) do = link_to project_pipelines_path(@project), title: 'Pipelines', class: 'shortcuts-pipelines' do %span Pipelines diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml new file mode 100644 index 00000000000..0f9d9512d88 --- /dev/null +++ b/app/views/projects/deployments/_commit.html.haml @@ -0,0 +1,12 @@ +%div.branch-commit + - if deployment.ref + = link_to deployment.ref, namespace_project_commits_path(@project.namespace, @project, deployment.ref), class: "monospace" + · + = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-id monospace" + + %p.commit-title + %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 diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index 28c003d22a8..f065f28c6ee 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -3,29 +3,18 @@ %strong= "##{deployment.iid}" %td - %div.branch-commit - - if deployment.ref - = link_to deployment.ref, namespace_project_commits_path(@project.namespace, @project, deployment.ref), class: "monospace" - · - = link_to deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, deployment.sha), class: "commit-id monospace" - - %p.commit-title - %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 + = render 'projects/deployments/commit', deployment: deployment %td - if deployment.deployable - = link_to namespace_project_build_path(@project.namespace, @project, deployment.deployable), class: "monospace" do + = link_to namespace_project_build_path(@project.namespace, @project, deployment.deployable) do = "#{deployment.deployable.name} (##{deployment.deployable.id})" %td #{time_ago_with_tooltip(deployment.created_at)} %td - - if can?(current_user, :update_deployment, @project) && deployment.deployable + - if can?(current_user, :update_deployment, deployment) && deployment.deployable .pull-right = link_to retry_namespace_project_build_path(@project.namespace, @project, deployment.deployable), method: :post, class: 'btn btn-build' do - if deployment.last? diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml index c2e6d11f941..eafa246d05f 100644 --- a/app/views/projects/environments/_environment.html.haml +++ b/app/views/projects/environments/_environment.html.haml @@ -7,18 +7,7 @@ %td - if last_deployment - %div.branch-commit - - if last_deployment.ref - = link_to last_deployment.ref, namespace_project_commits_path(@project.namespace, @project, last_deployment.ref), class: "monospace" - · - = link_to last_deployment.short_sha, namespace_project_commit_path(@project.namespace, @project, last_deployment.sha), class: "commit-id monospace" - - %p.commit-title - %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 + = render 'projects/deployments/commit', deployment: last_deployment - else %p.commit-title No deployments yet diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index fa1046bbe1a..ae9e77e7d89 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -20,5 +20,4 @@ %th Environment %th Last deployment %th Date - - @environments.each do |environment| - = render 'environment', environment: environment + = render @environments diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index ade41d9de2d..533f624c4e2 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -1,3 +1,4 @@ +- @no_container = true - page_title "New Environment" = render "projects/pipelines/head" @@ -6,7 +7,7 @@ %h4.prepend-top-0 New Environment - = form_for @environment, url: namespace_project_environments_path(@project.namespace, @project), html: { id: "new-environment-form", class: "col-lg-9 js-new-environment-form js-requires-input" } do |f| + = form_for @environment, url: namespace_project_environments_path(@project.namespace, @project), html: { class: "col-lg-9" } do |f| = form_errors(@environment) .form-group = f.label :name, 'Environment name', class: 'label-light' diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 6454101004a..b41b1651a81 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -9,7 +9,7 @@ .col-md-3 .nav-controls - - if can?(current_user, :update_environment, @project) + - if can?(current_user, :update_environment, @environment) = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :delete - if @deployments.blank? diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index 666dcfafd03..963b35de3a0 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -28,7 +28,7 @@ documentation](../workflow/add-user/add-user.md). | Manage labels | | ✓ | ✓ | ✓ | ✓ | | See a commit status | | ✓ | ✓ | ✓ | ✓ | | See a container registry | | ✓ | ✓ | ✓ | ✓ | -| See a environments | | ✓ | ✓ | ✓ | ✓ | +| See environments | | ✓ | ✓ | ✓ | ✓ | | Manage merge requests | | | ✓ | ✓ | ✓ | | Create new merge request | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ | @@ -41,7 +41,7 @@ documentation](../workflow/add-user/add-user.md). | Create or update commit status | | | ✓ | ✓ | ✓ | | Update a container registry | | | ✓ | ✓ | ✓ | | Remove a container registry image | | | ✓ | ✓ | ✓ | -| Manage environments | | | ✓ | ✓ | ✓ | +| Create new environments | | | ✓ | ✓ | ✓ | | Create new milestones | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ | @@ -54,6 +54,7 @@ documentation](../workflow/add-user/add-user.md). | Manage runners | | | | ✓ | ✓ | | Manage build triggers | | | | ✓ | ✓ | | Manage variables | | | | ✓ | ✓ | +| Delete environments | | | | ✓ | ✓ | | Switch visibility level | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ | | Remove project | | | | | ✓ | -- cgit v1.2.1 From 2541e50d7ce64bb402d06dc9d75567b78282c7b7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 15 Jun 2016 12:03:49 +0200 Subject: Improve validations --- app/models/deployment.rb | 6 ++-- app/models/environment.rb | 4 +-- db/migrate/20160610204157_add_deployments.rb | 12 ++++---- db/migrate/20160610204158_add_environments.rb | 2 +- db/schema.rb | 40 +++++++++++++-------------- spec/factories/deployments.rb | 1 + 6 files changed, 31 insertions(+), 34 deletions(-) diff --git a/app/models/deployment.rb b/app/models/deployment.rb index d9006b70e30..cda922080cb 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -1,15 +1,13 @@ class Deployment < ActiveRecord::Base include InternalId - belongs_to :project - belongs_to :environment + belongs_to :project, required: true + belongs_to :environment, required: true belongs_to :user belongs_to :deployable, polymorphic: true validates :sha, presence: true validates :ref, presence: true - validates :project, associated: true - validates :environment, associated: true delegate :name, to: :environment, prefix: true diff --git a/app/models/environment.rb b/app/models/environment.rb index ac6f8c81e01..7986a2529df 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -1,5 +1,5 @@ class Environment < ActiveRecord::Base - belongs_to :project + belongs_to :project, required: true has_many :deployments @@ -10,8 +10,6 @@ class Environment < ActiveRecord::Base format: { with: Gitlab::Regex.environment_name_regex, message: Gitlab::Regex.environment_name_regex_message } - validates :project, associated: true - def last_deployment deployments.last end diff --git a/db/migrate/20160610204157_add_deployments.rb b/db/migrate/20160610204157_add_deployments.rb index 557b78f91e1..cfa842daa6d 100644 --- a/db/migrate/20160610204157_add_deployments.rb +++ b/db/migrate/20160610204157_add_deployments.rb @@ -6,12 +6,12 @@ class AddDeployments < ActiveRecord::Migration 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 :iid, null: false + t.integer :project_id, null: false + t.integer :environment_id, null: false + t.string :ref, null: false + t.boolean :tag, null: false + t.string :sha, null: false t.integer :user_id t.integer :deployable_id t.string :deployable_type diff --git a/db/migrate/20160610204158_add_environments.rb b/db/migrate/20160610204158_add_environments.rb index 8311fd39b01..e1c71d173c4 100644 --- a/db/migrate/20160610204158_add_environments.rb +++ b/db/migrate/20160610204158_add_environments.rb @@ -6,7 +6,7 @@ class AddEnvironments < ActiveRecord::Migration def change create_table :environments, force: true do |t| - t.integer :project_id + t.integer :project_id, null: false t.string :name, null: false t.datetime :created_at t.datetime :updated_at diff --git a/db/schema.rb b/db/schema.rb index 388b259277a..3ac64e888ee 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -384,12 +384,12 @@ ActiveRecord::Schema.define(version: 20160610301627) do 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 "iid", null: false + t.integer "project_id", null: false + t.integer "environment_id", null: false + t.string "ref", null: false + t.boolean "tag", null: false + t.string "sha", null: false t.integer "user_id" t.integer "deployable_id" t.string "deployable_type" @@ -413,7 +413,7 @@ ActiveRecord::Schema.define(version: 20160610301627) do 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.integer "project_id", null: false t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" @@ -777,37 +777,37 @@ ActiveRecord::Schema.define(version: 20160610301627) do t.datetime "created_at" t.datetime "updated_at" t.integer "creator_id" - t.boolean "issues_enabled", default: true, null: false - t.boolean "merge_requests_enabled", default: true, null: false - t.boolean "wiki_enabled", default: true, null: false + t.boolean "issues_enabled", default: true, null: false + t.boolean "merge_requests_enabled", default: true, null: false + t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" - t.boolean "snippets_enabled", default: true, null: false + t.boolean "snippets_enabled", default: true, null: false t.datetime "last_activity_at" t.string "import_url" - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", default: false, null: false + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false t.string "avatar" t.string "import_status" t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false + t.integer "star_count", default: 0, null: false t.string "import_type" t.string "import_source" t.integer "commit_count", default: 0 t.text "import_error" t.integer "ci_id" - t.boolean "builds_enabled", default: true, null: false - t.boolean "shared_runners_enabled", default: true, null: false + t.boolean "builds_enabled", default: true, null: false + t.boolean "shared_runners_enabled", default: true, null: false t.string "runners_token" t.string "build_coverage_regex" - t.boolean "build_allow_git_fetch", default: true, null: false - t.integer "build_timeout", default: 3600, null: false + t.boolean "build_allow_git_fetch", default: true, null: false + t.integer "build_timeout", default: 3600, null: false t.boolean "pending_delete", default: false - t.boolean "public_builds", default: true, null: false + t.boolean "public_builds", default: true, null: false t.integer "pushes_since_gc", default: 0 t.boolean "last_repository_check_failed" t.datetime "last_repository_check_at" t.boolean "container_registry_enabled" - t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false + t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false t.boolean "has_external_issue_tracker" end diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb index f335a111a7d..82591604fcb 100644 --- a/spec/factories/deployments.rb +++ b/spec/factories/deployments.rb @@ -2,6 +2,7 @@ FactoryGirl.define do factory :deployment, class: Deployment do sha '97de212e80737a608d939f648d959671fb0a0142' ref 'master' + tag false environment factory: :environment -- cgit v1.2.1 From 00526440092bb82beb86b87376dd1ea6178bf05f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 15 Jun 2016 12:07:06 +0200 Subject: Improve forms and specs --- app/controllers/projects/environments_controller.rb | 4 ++-- app/models/ability.rb | 4 ++-- app/views/projects/environments/_form.html.haml | 7 +++++++ app/views/projects/environments/new.html.haml | 13 +++---------- app/views/projects/environments/show.html.haml | 2 +- spec/features/environments_spec.rb | 12 +++++++----- 6 files changed, 22 insertions(+), 20 deletions(-) create mode 100644 app/views/projects/environments/_form.html.haml diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 1f9f676c63b..4b433796161 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -10,7 +10,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController end def show - @deployments = environment.deployments.order(id: :desc).page(params[:page]).per(30) + @deployments = environment.deployments.order(id: :desc).page(params[:page]) end def new @@ -44,6 +44,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController end def environment - @environment ||= project.environments.find_by!(id: params[:id]) + @environment ||= project.environments.find(params[:id]) end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 32e45674682..734b152605b 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -251,7 +251,8 @@ class Ability :create_container_image, :update_container_image, :create_environment, - :create_deployment + :create_deployment, + :update_deployment ] end @@ -270,7 +271,6 @@ class Ability :push_code_to_protected_branches, :update_project_snippet, :update_environment, - :update_deployment, :admin_milestone, :admin_project_snippet, :admin_project_member, diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml new file mode 100644 index 00000000000..c07f4bd510c --- /dev/null +++ b/app/views/projects/environments/_form.html.haml @@ -0,0 +1,7 @@ += form_for @environment, url: namespace_project_environments_path(@project.namespace, @project), html: { class: 'col-lg-9' } do |f| + = form_errors(@environment) + .form-group + = f.label :name, 'Name', class: 'label-light' + = f.text_field :name, required: true, class: 'form-control' + = f.submit 'Create environment', class: 'btn btn-create' + = link_to 'Cancel', namespace_project_environments_path(@project.namespace, @project), class: 'btn btn-cancel' diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index 533f624c4e2..54465828ba9 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -1,16 +1,9 @@ -- @no_container = true -- page_title "New Environment" -= render "projects/pipelines/head" +- page_title 'New Environment' .row.prepend-top-default.append-bottom-default .col-lg-3 %h4.prepend-top-0 New Environment + %p Environments allow you to track deployments of your application - = form_for @environment, url: namespace_project_environments_path(@project.namespace, @project), html: { class: "col-lg-9" } do |f| - = form_errors(@environment) - .form-group - = f.label :name, 'Environment name', class: 'label-light' - = f.text_field :name, required: true, class: 'form-control' - = f.submit 'Create environment', class: 'btn btn-create' - = link_to "Cancel", namespace_project_environments_path(@project.namespace, @project), class: "btn btn-cancel" + = render 'form' diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index b41b1651a81..069b77b5adf 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -10,7 +10,7 @@ .col-md-3 .nav-controls - if can?(current_user, :update_environment, @environment) - = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :delete + = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete - if @deployments.blank? %ul.content-list.environments diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index b73bb30e216..8002b793986 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -107,7 +107,7 @@ describe 'Environments' do context 'for valid name' do before do - fill_in('Environment name', with: 'production') + fill_in('Name', with: 'production') click_on 'Create environment' end @@ -118,7 +118,7 @@ describe 'Environments' do context 'for invalid name' do before do - fill_in('Environment name', with: 'name with spaces') + fill_in('Name', with: 'name with spaces') click_on 'Create environment' end @@ -140,7 +140,9 @@ describe 'Environments' do before { visit namespace_project_environment_path(project.namespace, project, environment) } - context 'when logged as developer' do + context 'when logged as master' do + let(:role) { :master } + before { click_link 'Destroy' } it 'does not have environment' do @@ -148,8 +150,8 @@ describe 'Environments' do end end - context 'when logged as reporter' do - let(:role) { :reporter } + context 'when logged as developer' do + let(:role) { :developer } it 'does not have a Destroy link' do expect(page).not_to have_link('Destroy') -- cgit v1.2.1 From 18fd2ccb8b9b60e2acd6782a4160f85d3ee6c95f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 15 Jun 2016 12:12:26 +0200 Subject: Improve cyclomatic of ability::allowed --- app/models/ability.rb | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 734b152605b..8d76e8efa13 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -9,7 +9,6 @@ class Ability when CommitStatus then commit_status_abilities(user, subject) when Project then project_abilities(user, subject) when Issue then issue_abilities(user, subject) - when ExternalIssue then external_issue_abilities(user, subject) when Note then note_abilities(user, subject) when ProjectSnippet then project_snippet_abilities(user, subject) when PersonalSnippet then personal_snippet_abilities(user, subject) @@ -18,9 +17,8 @@ class Ability when Namespace then namespace_abilities(user, subject) when GroupMember then group_member_abilities(user, subject) when ProjectMember then project_member_abilities(user, subject) - when Deployment then deployment_abilities(user, subject) - when Environment then environment_abilities(user, subject) when User then user_abilities + when ExternalIssue, Deployment, Environment then project_abilities(user, subject.project) else [] end.concat(global_abilities(user)) end @@ -523,18 +521,6 @@ class Ability end end - def external_issue_abilities(user, subject) - project_abilities(user, subject.project) - end - - def deployment_abilities(user, subject) - project_abilities(user, subject.project) - end - - def environment_abilities(user, subject) - project_abilities(user, subject.project) - end - private def restricted_public_level? -- cgit v1.2.1 From 32a400aa14a0f2b2245251cb831fdc688917b4c1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 15 Jun 2016 12:24:47 +0200 Subject: Make environments_spec more feature-spec --- spec/features/environments_spec.rb | 113 ++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 57 deletions(-) diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 8002b793986..40fea5211e9 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -1,109 +1,105 @@ require 'spec_helper' -describe 'Environments' do - include GitlabRoutingHelper +feature 'Environments', feature: true do + given(:project) { create(:empty_project) } + given(:user) { create(:user) } + given(:role) { :developer } - let(:project) { create(:empty_project) } - let(:user) { create(:user) } - let(:role) { :developer } - - before do + background do login_as(user) project.team << [user, role] end - describe 'GET /:project/environments' do - subject { visit namespace_project_environments_path(project.namespace, project) } + describe 'when showing environments' do + given!(:environment) { } + given!(:deployment) { } - context 'without environments' do - it 'does show no environments' do - subject + before do + visit namespace_project_environments_path(project.namespace, project) + end + context 'without environments' do + scenario 'does show no environments' do expect(page).to have_content('No environments to show') end end context 'with environments' do - let!(:environment) { create(:environment, project: project) } - - it 'does show environment name' do - subject + given(:environment) { create(:environment, project: project) } + scenario 'does show environment name' do expect(page).to have_link(environment.name) end context 'without deployments' do - it 'does show no deployments' do - subject - + scenario 'does show no deployments' do expect(page).to have_content('No deployments yet') end end context 'with deployments' do - let!(:deployment) { create(:deployment, environment: environment) } - - it 'does show deployment SHA' do - subject + given(:deployment) { create(:deployment, environment: environment) } + scenario 'does show deployment SHA' do expect(page).to have_link(deployment.short_sha) end end end - it 'does have a New environment button' do - subject - + scenario 'does have a New environment button' do expect(page).to have_link('New environment') end end - describe 'GET /:project/environments/:id' do - let(:environment) { create(:environment, project: project) } + describe 'when showing the environment' do + given(:environment) { create(:environment, project: project) } + given!(:deployment) { } - subject { visit namespace_project_environment_path(project.namespace, project, environment) } + before do + visit namespace_project_environment_path(project.namespace, project, environment) + end context 'without deployments' do - it 'does show no deployments' do - subject - + scenario 'does show no deployments' do expect(page).to have_content('No deployments for') end end context 'with deployments' do - let!(:deployment) { create(:deployment, environment: environment) } + given(:deployment) { create(:deployment, environment: environment) } - before { subject } - - it 'does show deployment SHA' do + scenario 'does show deployment SHA' do expect(page).to have_link(deployment.short_sha) end - it 'does not show a retry button for deployment without build' do + scenario 'does not show a retry button for deployment without build' do expect(page).not_to have_link('Retry') end context 'with build' do - let(:build) { create(:ci_build, project: project) } - let(:deployment) { create(:deployment, environment: environment, deployable: build) } + given(:build) { create(:ci_build, project: project) } + given(:deployment) { create(:deployment, environment: environment, deployable: build) } - it 'does show build name' do + scenario 'does show build name' do expect(page).to have_link("#{build.name} (##{build.id})") end - it 'does show retry button' do + scenario 'does show retry button' do expect(page).to have_link('Retry') end end end end - describe 'POST /:project/environments' do - before { visit namespace_project_environments_path(project.namespace, project) } + describe 'when creating a new environment' do + before do + visit namespace_project_environments_path(project.namespace, project) + end context 'when logged as developer' do - before { click_link 'New environment' } + before do + click_link 'New environment' + end context 'for valid name' do before do @@ -111,7 +107,7 @@ describe 'Environments' do click_on 'Create environment' end - it 'does create a new pipeline' do + scenario 'does create a new pipeline' do expect(page).to have_content('production') end end @@ -122,38 +118,41 @@ describe 'Environments' do click_on 'Create environment' end - it { expect(page).to have_content('Name can contain only letters') } + scenario 'does show errors' do + expect(page).to have_content('Name can contain only letters') + end end end context 'when logged as reporter' do - let(:role) { :reporter } + given(:role) { :reporter } - it 'does not have a New environment link' do + scenario 'does not have a New environment link' do expect(page).not_to have_link('New environment') end end end - describe 'DELETE /:project/environments/:id' do - let(:environment) { create(:environment, project: project) } + describe 'when deleting existing environment' do + given(:environment) { create(:environment, project: project) } - before { visit namespace_project_environment_path(project.namespace, project, environment) } + before do + visit namespace_project_environment_path(project.namespace, project, environment) + end context 'when logged as master' do - let(:role) { :master } - - before { click_link 'Destroy' } + given(:role) { :master } - it 'does not have environment' do + scenario 'does delete environment' do + click_link 'Destroy' expect(page).not_to have_link(environment.name) end end context 'when logged as developer' do - let(:role) { :developer } + given(:role) { :developer } - it 'does not have a Destroy link' do + scenario 'does not have a Destroy link' do expect(page).not_to have_link('Destroy') end end -- cgit v1.2.1 From 2bed8db99567292bd619ddd9ec8158f1ed7b54e6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 15 Jun 2016 12:24:53 +0200 Subject: Add CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index e71a154d1d5..77fee01f66f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 8.9.0 (unreleased) - Fix 404 page when viewing TODOs that contain milestones or labels in different projects - Redesign navigation for project pages - Fix groups API to list only user's accessible projects + - Add Environments and Deployments - Redesign account and email confirmation emails - `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix - Bump nokogiri to 1.6.8 -- cgit v1.2.1 From eb26755d63dbe3b4c32230a2ec8730a0d889f292 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 15 Jun 2016 12:56:02 +0200 Subject: Create_deployment ability is need to create retry or rollback deployment --- app/models/ability.rb | 4 ++-- app/views/projects/deployments/_deployment.html.haml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 8d76e8efa13..ecf02a0ff6f 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -249,8 +249,7 @@ class Ability :create_container_image, :update_container_image, :create_environment, - :create_deployment, - :update_deployment + :create_deployment ] end @@ -269,6 +268,7 @@ class Ability :push_code_to_protected_branches, :update_project_snippet, :update_environment, + :update_deployment, :admin_milestone, :admin_project_snippet, :admin_project_member, diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml index f065f28c6ee..d08dd92f1f6 100644 --- a/app/views/projects/deployments/_deployment.html.haml +++ b/app/views/projects/deployments/_deployment.html.haml @@ -14,7 +14,7 @@ #{time_ago_with_tooltip(deployment.created_at)} %td - - if can?(current_user, :update_deployment, deployment) && deployment.deployable + - if can?(current_user, :create_deployment, deployment) && deployment.deployable .pull-right = link_to retry_namespace_project_build_path(@project.namespace, @project, deployment.deployable), method: :post, class: 'btn btn-build' do - if deployment.last? -- cgit v1.2.1 From 14433b341d5e8f0e55d984b478267f5df98f42ae Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 15 Jun 2016 13:00:13 +0200 Subject: Make `project_id` and `environment_id` nullable This is done to make belongs_to with required to properly validate association. Otherwise `ActiveRecord::StatementInvalid` is raised. --- db/migrate/20160610204157_add_deployments.rb | 4 ++-- db/schema.rb | 6 +++--- spec/services/create_deployment_service_spec.rb | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/db/migrate/20160610204157_add_deployments.rb b/db/migrate/20160610204157_add_deployments.rb index cfa842daa6d..a15f6c0ea6b 100644 --- a/db/migrate/20160610204157_add_deployments.rb +++ b/db/migrate/20160610204157_add_deployments.rb @@ -7,8 +7,8 @@ class AddDeployments < ActiveRecord::Migration def change create_table :deployments, force: true do |t| t.integer :iid, null: false - t.integer :project_id, null: false - t.integer :environment_id, null: false + t.integer :project_id + t.integer :environment_id t.string :ref, null: false t.boolean :tag, null: false t.string :sha, null: false diff --git a/db/schema.rb b/db/schema.rb index 3ac64e888ee..1e8d86d0aae 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -385,8 +385,8 @@ ActiveRecord::Schema.define(version: 20160610301627) do create_table "deployments", force: :cascade do |t| t.integer "iid", null: false - t.integer "project_id", null: false - t.integer "environment_id", null: false + t.integer "project_id" + t.integer "environment_id" t.string "ref", null: false t.boolean "tag", null: false t.string "sha", null: false @@ -413,7 +413,7 @@ ActiveRecord::Schema.define(version: 20160610301627) do add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree create_table "environments", force: :cascade do |t| - t.integer "project_id", null: false + t.integer "project_id" t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index b6ae3505379..654e441f3cd 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -10,6 +10,7 @@ describe CreateDeploymentService, services: true do let(:params) do { environment: 'production', ref: 'master', + tag: false, sha: '97de212e80737a608d939f648d959671fb0a0142', } end @@ -42,6 +43,7 @@ describe CreateDeploymentService, services: true do let(:params) do { environment: 'name with spaces', ref: 'master', + tag: false, sha: '97de212e80737a608d939f648d959671fb0a0142', } end -- cgit v1.2.1 From 9e487100b5e7ee7e226121fa353060d4e3dda8d4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 15 Jun 2016 14:05:10 +0200 Subject: Validate project and environment instead of only requiring --- app/models/deployment.rb | 4 +- db/migrate/20160610204157_add_deployments.rb | 4 +- db/schema.rb | 471 +-------------------------- 3 files changed, 6 insertions(+), 473 deletions(-) diff --git a/app/models/deployment.rb b/app/models/deployment.rb index cda922080cb..030648470ee 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -1,8 +1,8 @@ class Deployment < ActiveRecord::Base include InternalId - belongs_to :project, required: true - belongs_to :environment, required: true + belongs_to :project, validate: true + belongs_to :environment, validate: true belongs_to :user belongs_to :deployable, polymorphic: true diff --git a/db/migrate/20160610204157_add_deployments.rb b/db/migrate/20160610204157_add_deployments.rb index a15f6c0ea6b..cfa842daa6d 100644 --- a/db/migrate/20160610204157_add_deployments.rb +++ b/db/migrate/20160610204157_add_deployments.rb @@ -7,8 +7,8 @@ class AddDeployments < ActiveRecord::Migration def change create_table :deployments, force: true do |t| t.integer :iid, null: false - t.integer :project_id - t.integer :environment_id + t.integer :project_id, null: false + t.integer :environment_id, null: false t.string :ref, null: false t.boolean :tag, null: false t.string :sha, null: false diff --git a/db/schema.rb b/db/schema.rb index 1e8d86d0aae..603ad8a29e9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -385,8 +385,8 @@ ActiveRecord::Schema.define(version: 20160610301627) do create_table "deployments", force: :cascade do |t| t.integer "iid", null: false - t.integer "project_id" - t.integer "environment_id" + t.integer "project_id", null: false + t.integer "environment_id", null: false t.string "ref", null: false t.boolean "tag", null: false t.string "sha", null: false @@ -628,470 +628,3 @@ ActiveRecord::Schema.define(version: 20160610301627) do add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} - create_table "milestones", force: :cascade do |t| - t.string "title", null: false - t.integer "project_id", null: false - t.text "description" - t.date "due_date" - t.datetime "created_at" - t.datetime "updated_at" - t.string "state" - t.integer "iid" - end - - add_index "milestones", ["created_at", "id"], name: "index_milestones_on_created_at_and_id", using: :btree - add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} - add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree - add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree - add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree - add_index "milestones", ["title"], name: "index_milestones_on_title", using: :btree - add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} - - create_table "namespaces", force: :cascade do |t| - t.string "name", null: false - t.string "path", null: false - t.integer "owner_id" - t.datetime "created_at" - t.datetime "updated_at" - t.string "type" - t.string "description", default: "", null: false - t.string "avatar" - t.boolean "share_with_group_lock", default: false - t.integer "visibility_level", default: 20, null: false - end - - add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree - add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree - add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} - add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree - add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree - add_index "namespaces", ["path"], name: "index_namespaces_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"} - add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree - add_index "namespaces", ["visibility_level"], name: "index_namespaces_on_visibility_level", using: :btree - - create_table "notes", force: :cascade do |t| - t.text "note" - t.string "noteable_type" - t.integer "author_id" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "project_id" - t.string "attachment" - t.string "line_code" - t.string "commit_id" - t.integer "noteable_id" - t.boolean "system", default: false, null: false - t.text "st_diff" - t.integer "updated_by_id" - t.string "type" - end - - add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree - add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree - add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree - add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree - add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree - add_index "notes", ["note"], name: "index_notes_on_note_trigram", using: :gin, opclasses: {"note"=>"gin_trgm_ops"} - add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree - add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree - add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree - add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree - add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree - - create_table "notification_settings", force: :cascade do |t| - t.integer "user_id", null: false - t.integer "source_id" - t.string "source_type" - t.integer "level", default: 0, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree - add_index "notification_settings", ["user_id", "source_id", "source_type"], name: "index_notifications_on_user_id_and_source_id_and_source_type", unique: true, using: :btree - add_index "notification_settings", ["user_id"], name: "index_notification_settings_on_user_id", using: :btree - - create_table "oauth_access_grants", force: :cascade do |t| - t.integer "resource_owner_id", null: false - t.integer "application_id", null: false - t.string "token", null: false - t.integer "expires_in", null: false - t.text "redirect_uri", null: false - t.datetime "created_at", null: false - t.datetime "revoked_at" - t.string "scopes" - end - - add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree - - create_table "oauth_access_tokens", force: :cascade do |t| - t.integer "resource_owner_id" - t.integer "application_id" - t.string "token", null: false - t.string "refresh_token" - t.integer "expires_in" - t.datetime "revoked_at" - t.datetime "created_at", null: false - t.string "scopes" - end - - add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree - add_index "oauth_access_tokens", ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id", using: :btree - add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree - - create_table "oauth_applications", force: :cascade do |t| - t.string "name", null: false - t.string "uid", null: false - t.string "secret", null: false - t.text "redirect_uri", null: false - t.string "scopes", default: "", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.integer "owner_id" - t.string "owner_type" - end - - add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree - add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree - - create_table "project_group_links", force: :cascade do |t| - t.integer "project_id", null: false - t.integer "group_id", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.integer "group_access", default: 30, null: false - end - - create_table "project_import_data", force: :cascade do |t| - t.integer "project_id" - t.text "data" - t.text "encrypted_credentials" - t.string "encrypted_credentials_iv" - t.string "encrypted_credentials_salt" - end - - create_table "projects", force: :cascade do |t| - t.string "name" - t.string "path" - t.text "description" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "creator_id" - t.boolean "issues_enabled", default: true, null: false - t.boolean "merge_requests_enabled", default: true, null: false - t.boolean "wiki_enabled", default: true, null: false - t.integer "namespace_id" - t.boolean "snippets_enabled", default: true, null: false - t.datetime "last_activity_at" - t.string "import_url" - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", default: false, null: false - t.string "avatar" - t.string "import_status" - t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false - t.string "import_type" - t.string "import_source" - t.integer "commit_count", default: 0 - t.text "import_error" - t.integer "ci_id" - t.boolean "builds_enabled", default: true, null: false - t.boolean "shared_runners_enabled", default: true, null: false - t.string "runners_token" - t.string "build_coverage_regex" - t.boolean "build_allow_git_fetch", default: true, null: false - t.integer "build_timeout", default: 3600, null: false - t.boolean "pending_delete", default: false - t.boolean "public_builds", default: true, null: false - t.integer "pushes_since_gc", default: 0 - t.boolean "last_repository_check_failed" - t.datetime "last_repository_check_at" - t.boolean "container_registry_enabled" - t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false - t.boolean "has_external_issue_tracker" - end - - add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree - add_index "projects", ["builds_enabled"], name: "index_projects_on_builds_enabled", using: :btree - add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree - add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree - add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree - add_index "projects", ["description"], name: "index_projects_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} - add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree - add_index "projects", ["last_repository_check_failed"], name: "index_projects_on_last_repository_check_failed", using: :btree - add_index "projects", ["name"], name: "index_projects_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} - add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree - add_index "projects", ["path"], name: "index_projects_on_path", using: :btree - add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"} - add_index "projects", ["pending_delete"], name: "index_projects_on_pending_delete", using: :btree - add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree - add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree - add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree - - create_table "protected_branches", force: :cascade do |t| - t.integer "project_id", null: false - t.string "name", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.boolean "developers_can_push", default: false, null: false - end - - add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree - - create_table "releases", force: :cascade do |t| - t.string "tag" - t.text "description" - t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" - end - - add_index "releases", ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree - add_index "releases", ["project_id"], name: "index_releases_on_project_id", using: :btree - - create_table "sent_notifications", force: :cascade do |t| - t.integer "project_id" - t.integer "noteable_id" - t.string "noteable_type" - t.integer "recipient_id" - t.string "commit_id" - t.string "reply_key", null: false - t.string "line_code" - end - - add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree - - create_table "services", force: :cascade do |t| - t.string "type" - t.string "title" - t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" - t.boolean "active", default: false, null: false - t.text "properties" - t.boolean "template", default: false - t.boolean "push_events", default: true - t.boolean "issues_events", default: true - t.boolean "merge_requests_events", default: true - t.boolean "tag_push_events", default: true - t.boolean "note_events", default: true, null: false - t.boolean "build_events", default: false, null: false - t.string "category", default: "common", null: false - t.boolean "default", default: false - t.boolean "wiki_page_events", default: true - end - - add_index "services", ["category"], name: "index_services_on_category", using: :btree - add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree - add_index "services", ["default"], name: "index_services_on_default", using: :btree - add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree - add_index "services", ["template"], name: "index_services_on_template", using: :btree - - create_table "snippets", force: :cascade do |t| - t.string "title" - t.text "content" - t.integer "author_id", null: false - t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" - t.string "file_name" - t.string "type" - t.integer "visibility_level", default: 0, null: false - end - - add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree - add_index "snippets", ["created_at", "id"], name: "index_snippets_on_created_at_and_id", using: :btree - add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree - add_index "snippets", ["file_name"], name: "index_snippets_on_file_name_trigram", using: :gin, opclasses: {"file_name"=>"gin_trgm_ops"} - add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree - add_index "snippets", ["title"], name: "index_snippets_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} - add_index "snippets", ["updated_at"], name: "index_snippets_on_updated_at", using: :btree - add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree - - create_table "spam_logs", force: :cascade do |t| - t.integer "user_id" - t.string "source_ip" - t.string "user_agent" - t.boolean "via_api" - t.integer "project_id" - t.string "noteable_type" - t.string "title" - t.text "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - create_table "subscriptions", force: :cascade do |t| - t.integer "user_id" - t.integer "subscribable_id" - t.string "subscribable_type" - t.boolean "subscribed" - t.datetime "created_at" - t.datetime "updated_at" - end - - add_index "subscriptions", ["subscribable_id", "subscribable_type", "user_id"], name: "subscriptions_user_id_and_ref_fields", unique: true, using: :btree - - create_table "taggings", force: :cascade do |t| - t.integer "tag_id" - t.integer "taggable_id" - t.string "taggable_type" - t.integer "tagger_id" - t.string "tagger_type" - t.string "context" - t.datetime "created_at" - end - - add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true, using: :btree - add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree - - create_table "tags", force: :cascade do |t| - t.string "name" - t.integer "taggings_count", default: 0 - end - - add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree - - create_table "todos", force: :cascade do |t| - t.integer "user_id", null: false - t.integer "project_id", null: false - t.integer "target_id" - t.string "target_type", null: false - t.integer "author_id" - t.integer "action", null: false - t.string "state", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.integer "note_id" - t.string "commit_id" - end - - add_index "todos", ["author_id"], name: "index_todos_on_author_id", using: :btree - add_index "todos", ["commit_id"], name: "index_todos_on_commit_id", using: :btree - add_index "todos", ["note_id"], name: "index_todos_on_note_id", using: :btree - add_index "todos", ["project_id"], name: "index_todos_on_project_id", using: :btree - add_index "todos", ["state"], name: "index_todos_on_state", using: :btree - add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree - add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree - - create_table "u2f_registrations", force: :cascade do |t| - t.text "certificate" - t.string "key_handle" - t.string "public_key" - t.integer "counter" - t.integer "user_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - add_index "u2f_registrations", ["key_handle"], name: "index_u2f_registrations_on_key_handle", using: :btree - add_index "u2f_registrations", ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree - - create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at" - t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 - t.datetime "current_sign_in_at" - t.datetime "last_sign_in_at" - t.string "current_sign_in_ip" - t.string "last_sign_in_ip" - t.datetime "created_at" - t.datetime "updated_at" - t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false - t.string "authentication_token" - t.integer "theme_id", default: 1, null: false - t.string "bio" - t.integer "failed_attempts", default: 0 - t.datetime "locked_at" - t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false - t.string "state" - t.integer "color_scheme_id", default: 1, null: false - t.datetime "password_expires_at" - t.integer "created_by_id" - t.datetime "last_credential_check_at" - t.string "avatar" - t.string "confirmation_token" - t.datetime "confirmed_at" - t.datetime "confirmation_sent_at" - t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false - t.string "notification_email" - t.boolean "hide_no_password", default: false - t.boolean "password_automatically_set", default: false - t.string "location" - t.string "encrypted_otp_secret" - t.string "encrypted_otp_secret_iv" - t.string "encrypted_otp_secret_salt" - t.boolean "otp_required_for_login", default: false, null: false - t.text "otp_backup_codes" - t.string "public_email", default: "", null: false - t.integer "dashboard", default: 0 - t.integer "project_view", default: 0 - t.integer "consumed_timestep" - t.integer "layout", default: 0 - t.boolean "hide_project_limit", default: false - t.string "unlock_token" - t.datetime "otp_grace_period_started_at" - t.boolean "ldap_email", default: false, null: false - t.boolean "external", default: false - end - - add_index "users", ["admin"], name: "index_users_on_admin", using: :btree - add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree - add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree - add_index "users", ["created_at", "id"], name: "index_users_on_created_at_and_id", using: :btree - add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree - add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree - add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"} - add_index "users", ["name"], name: "index_users_on_name", using: :btree - add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} - add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree - add_index "users", ["state"], name: "index_users_on_state", using: :btree - add_index "users", ["username"], name: "index_users_on_username", using: :btree - add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"} - - create_table "users_star_projects", force: :cascade do |t| - t.integer "project_id", null: false - t.integer "user_id", null: false - t.datetime "created_at" - t.datetime "updated_at" - end - - add_index "users_star_projects", ["project_id"], name: "index_users_star_projects_on_project_id", using: :btree - add_index "users_star_projects", ["user_id", "project_id"], name: "index_users_star_projects_on_user_id_and_project_id", unique: true, using: :btree - add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree - - create_table "web_hooks", force: :cascade do |t| - t.string "url", limit: 2000 - t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" - t.string "type", default: "ProjectHook" - t.integer "service_id" - t.boolean "push_events", default: true, null: false - t.boolean "issues_events", default: false, null: false - t.boolean "merge_requests_events", default: false, null: false - t.boolean "tag_push_events", default: false - t.boolean "note_events", default: false, null: false - t.boolean "enable_ssl_verification", default: true - t.boolean "build_events", default: false, null: false - t.boolean "wiki_page_events", default: false, null: false - t.string "token" - end - - add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree - add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree - - add_foreign_key "u2f_registrations", "users" -end -- cgit v1.2.1 From a4dc5f79bf281127fe5e4ace36ac4f7664701e0b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 15 Jun 2016 14:05:26 +0200 Subject: Make project_id, iid unique for deployments --- db/migrate/20160610204157_add_deployments.rb | 2 +- db/schema.rb | 469 ++++++++++++++++++++++++++- 2 files changed, 469 insertions(+), 2 deletions(-) diff --git a/db/migrate/20160610204157_add_deployments.rb b/db/migrate/20160610204157_add_deployments.rb index cfa842daa6d..cb144ea8a6d 100644 --- a/db/migrate/20160610204157_add_deployments.rb +++ b/db/migrate/20160610204157_add_deployments.rb @@ -20,7 +20,7 @@ class AddDeployments < ActiveRecord::Migration end add_index :deployments, :project_id - add_index :deployments, [:project_id, :iid] + add_index :deployments, [:project_id, :iid], unique: true add_index :deployments, [:project_id, :environment_id] add_index :deployments, [:project_id, :environment_id, :iid] end diff --git a/db/schema.rb b/db/schema.rb index 603ad8a29e9..c5259b00efc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -399,7 +399,7 @@ ActiveRecord::Schema.define(version: 20160610301627) do 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", "iid"], name: "index_deployments_on_project_id_and_iid", unique: true, using: :btree add_index "deployments", ["project_id"], name: "index_deployments_on_project_id", using: :btree create_table "emails", force: :cascade do |t| @@ -628,3 +628,470 @@ ActiveRecord::Schema.define(version: 20160610301627) do add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} + create_table "milestones", force: :cascade do |t| + t.string "title", null: false + t.integer "project_id", null: false + t.text "description" + t.date "due_date" + t.datetime "created_at" + t.datetime "updated_at" + t.string "state" + t.integer "iid" + end + + add_index "milestones", ["created_at", "id"], name: "index_milestones_on_created_at_and_id", using: :btree + add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} + add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree + add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree + add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree + add_index "milestones", ["title"], name: "index_milestones_on_title", using: :btree + add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} + + create_table "namespaces", force: :cascade do |t| + t.string "name", null: false + t.string "path", null: false + t.integer "owner_id" + t.datetime "created_at" + t.datetime "updated_at" + t.string "type" + t.string "description", default: "", null: false + t.string "avatar" + t.boolean "share_with_group_lock", default: false + t.integer "visibility_level", default: 20, null: false + end + + add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree + add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree + add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} + add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree + add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree + add_index "namespaces", ["path"], name: "index_namespaces_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"} + add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree + add_index "namespaces", ["visibility_level"], name: "index_namespaces_on_visibility_level", using: :btree + + create_table "notes", force: :cascade do |t| + t.text "note" + t.string "noteable_type" + t.integer "author_id" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "project_id" + t.string "attachment" + t.string "line_code" + t.string "commit_id" + t.integer "noteable_id" + t.boolean "system", default: false, null: false + t.text "st_diff" + t.integer "updated_by_id" + t.string "type" + end + + add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree + add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree + add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree + add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree + add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree + add_index "notes", ["note"], name: "index_notes_on_note_trigram", using: :gin, opclasses: {"note"=>"gin_trgm_ops"} + add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree + add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree + add_index "notes", ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree + add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree + add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree + + create_table "notification_settings", force: :cascade do |t| + t.integer "user_id", null: false + t.integer "source_id" + t.string "source_type" + t.integer "level", default: 0, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree + add_index "notification_settings", ["user_id", "source_id", "source_type"], name: "index_notifications_on_user_id_and_source_id_and_source_type", unique: true, using: :btree + add_index "notification_settings", ["user_id"], name: "index_notification_settings_on_user_id", using: :btree + + create_table "oauth_access_grants", force: :cascade do |t| + t.integer "resource_owner_id", null: false + t.integer "application_id", null: false + t.string "token", null: false + t.integer "expires_in", null: false + t.text "redirect_uri", null: false + t.datetime "created_at", null: false + t.datetime "revoked_at" + t.string "scopes" + end + + add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree + + create_table "oauth_access_tokens", force: :cascade do |t| + t.integer "resource_owner_id" + t.integer "application_id" + t.string "token", null: false + t.string "refresh_token" + t.integer "expires_in" + t.datetime "revoked_at" + t.datetime "created_at", null: false + t.string "scopes" + end + + add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree + add_index "oauth_access_tokens", ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id", using: :btree + add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree + + create_table "oauth_applications", force: :cascade do |t| + t.string "name", null: false + t.string "uid", null: false + t.string "secret", null: false + t.text "redirect_uri", null: false + t.string "scopes", default: "", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.integer "owner_id" + t.string "owner_type" + end + + add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree + add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree + + create_table "project_group_links", force: :cascade do |t| + t.integer "project_id", null: false + t.integer "group_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.integer "group_access", default: 30, null: false + end + + create_table "project_import_data", force: :cascade do |t| + t.integer "project_id" + t.text "data" + t.text "encrypted_credentials" + t.string "encrypted_credentials_iv" + t.string "encrypted_credentials_salt" + end + + create_table "projects", force: :cascade do |t| + t.string "name" + t.string "path" + t.text "description" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "creator_id" + t.boolean "issues_enabled", default: true, null: false + t.boolean "merge_requests_enabled", default: true, null: false + t.boolean "wiki_enabled", default: true, null: false + t.integer "namespace_id" + t.boolean "snippets_enabled", default: true, null: false + t.datetime "last_activity_at" + t.string "import_url" + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false + t.string "avatar" + t.string "import_status" + t.float "repository_size", default: 0.0 + t.integer "star_count", default: 0, null: false + t.string "import_type" + t.string "import_source" + t.integer "commit_count", default: 0 + t.text "import_error" + t.integer "ci_id" + t.boolean "builds_enabled", default: true, null: false + t.boolean "shared_runners_enabled", default: true, null: false + t.string "runners_token" + t.string "build_coverage_regex" + t.boolean "build_allow_git_fetch", default: true, null: false + t.integer "build_timeout", default: 3600, null: false + t.boolean "pending_delete", default: false + t.boolean "public_builds", default: true, null: false + t.integer "pushes_since_gc", default: 0 + t.boolean "last_repository_check_failed" + t.datetime "last_repository_check_at" + t.boolean "container_registry_enabled" + t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false + t.boolean "has_external_issue_tracker" + end + + add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree + add_index "projects", ["builds_enabled"], name: "index_projects_on_builds_enabled", using: :btree + add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree + add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree + add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree + add_index "projects", ["description"], name: "index_projects_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} + add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree + add_index "projects", ["last_repository_check_failed"], name: "index_projects_on_last_repository_check_failed", using: :btree + add_index "projects", ["name"], name: "index_projects_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} + add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree + add_index "projects", ["path"], name: "index_projects_on_path", using: :btree + add_index "projects", ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"} + add_index "projects", ["pending_delete"], name: "index_projects_on_pending_delete", using: :btree + add_index "projects", ["runners_token"], name: "index_projects_on_runners_token", using: :btree + add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree + add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree + + create_table "protected_branches", force: :cascade do |t| + t.integer "project_id", null: false + t.string "name", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "developers_can_push", default: false, null: false + end + + add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree + + create_table "releases", force: :cascade do |t| + t.string "tag" + t.text "description" + t.integer "project_id" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "releases", ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree + add_index "releases", ["project_id"], name: "index_releases_on_project_id", using: :btree + + create_table "sent_notifications", force: :cascade do |t| + t.integer "project_id" + t.integer "noteable_id" + t.string "noteable_type" + t.integer "recipient_id" + t.string "commit_id" + t.string "reply_key", null: false + t.string "line_code" + end + + add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree + + create_table "services", force: :cascade do |t| + t.string "type" + t.string "title" + t.integer "project_id" + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "active", default: false, null: false + t.text "properties" + t.boolean "template", default: false + t.boolean "push_events", default: true + t.boolean "issues_events", default: true + t.boolean "merge_requests_events", default: true + t.boolean "tag_push_events", default: true + t.boolean "note_events", default: true, null: false + t.boolean "build_events", default: false, null: false + t.string "category", default: "common", null: false + t.boolean "default", default: false + t.boolean "wiki_page_events", default: true + end + + add_index "services", ["category"], name: "index_services_on_category", using: :btree + add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree + add_index "services", ["default"], name: "index_services_on_default", using: :btree + add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree + add_index "services", ["template"], name: "index_services_on_template", using: :btree + + create_table "snippets", force: :cascade do |t| + t.string "title" + t.text "content" + t.integer "author_id", null: false + t.integer "project_id" + t.datetime "created_at" + t.datetime "updated_at" + t.string "file_name" + t.string "type" + t.integer "visibility_level", default: 0, null: false + end + + add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree + add_index "snippets", ["created_at", "id"], name: "index_snippets_on_created_at_and_id", using: :btree + add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree + add_index "snippets", ["file_name"], name: "index_snippets_on_file_name_trigram", using: :gin, opclasses: {"file_name"=>"gin_trgm_ops"} + add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree + add_index "snippets", ["title"], name: "index_snippets_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} + add_index "snippets", ["updated_at"], name: "index_snippets_on_updated_at", using: :btree + add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree + + create_table "spam_logs", force: :cascade do |t| + t.integer "user_id" + t.string "source_ip" + t.string "user_agent" + t.boolean "via_api" + t.integer "project_id" + t.string "noteable_type" + t.string "title" + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "subscriptions", force: :cascade do |t| + t.integer "user_id" + t.integer "subscribable_id" + t.string "subscribable_type" + t.boolean "subscribed" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "subscriptions", ["subscribable_id", "subscribable_type", "user_id"], name: "subscriptions_user_id_and_ref_fields", unique: true, using: :btree + + create_table "taggings", force: :cascade do |t| + t.integer "tag_id" + t.integer "taggable_id" + t.string "taggable_type" + t.integer "tagger_id" + t.string "tagger_type" + t.string "context" + t.datetime "created_at" + end + + add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true, using: :btree + add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree + + create_table "tags", force: :cascade do |t| + t.string "name" + t.integer "taggings_count", default: 0 + end + + add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree + + create_table "todos", force: :cascade do |t| + t.integer "user_id", null: false + t.integer "project_id", null: false + t.integer "target_id" + t.string "target_type", null: false + t.integer "author_id" + t.integer "action", null: false + t.string "state", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.integer "note_id" + t.string "commit_id" + end + + add_index "todos", ["author_id"], name: "index_todos_on_author_id", using: :btree + add_index "todos", ["commit_id"], name: "index_todos_on_commit_id", using: :btree + add_index "todos", ["note_id"], name: "index_todos_on_note_id", using: :btree + add_index "todos", ["project_id"], name: "index_todos_on_project_id", using: :btree + add_index "todos", ["state"], name: "index_todos_on_state", using: :btree + add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree + add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree + + create_table "u2f_registrations", force: :cascade do |t| + t.text "certificate" + t.string "key_handle" + t.string "public_key" + t.integer "counter" + t.integer "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "u2f_registrations", ["key_handle"], name: "index_u2f_registrations_on_key_handle", using: :btree + add_index "u2f_registrations", ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree + + create_table "users", force: :cascade do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.integer "sign_in_count", default: 0 + t.datetime "current_sign_in_at" + t.datetime "last_sign_in_at" + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.datetime "created_at" + t.datetime "updated_at" + t.string "name" + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false + t.string "authentication_token" + t.integer "theme_id", default: 1, null: false + t.string "bio" + t.integer "failed_attempts", default: 0 + t.datetime "locked_at" + t.string "username" + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false + t.string "state" + t.integer "color_scheme_id", default: 1, null: false + t.datetime "password_expires_at" + t.integer "created_by_id" + t.datetime "last_credential_check_at" + t.string "avatar" + t.string "confirmation_token" + t.datetime "confirmed_at" + t.datetime "confirmation_sent_at" + t.string "unconfirmed_email" + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false + t.string "notification_email" + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false + t.string "location" + t.string "encrypted_otp_secret" + t.string "encrypted_otp_secret_iv" + t.string "encrypted_otp_secret_salt" + t.boolean "otp_required_for_login", default: false, null: false + t.text "otp_backup_codes" + t.string "public_email", default: "", null: false + t.integer "dashboard", default: 0 + t.integer "project_view", default: 0 + t.integer "consumed_timestep" + t.integer "layout", default: 0 + t.boolean "hide_project_limit", default: false + t.string "unlock_token" + t.datetime "otp_grace_period_started_at" + t.boolean "ldap_email", default: false, null: false + t.boolean "external", default: false + end + + add_index "users", ["admin"], name: "index_users_on_admin", using: :btree + add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree + add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree + add_index "users", ["created_at", "id"], name: "index_users_on_created_at_and_id", using: :btree + add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree + add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree + add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"} + add_index "users", ["name"], name: "index_users_on_name", using: :btree + add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} + add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree + add_index "users", ["state"], name: "index_users_on_state", using: :btree + add_index "users", ["username"], name: "index_users_on_username", using: :btree + add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"} + + create_table "users_star_projects", force: :cascade do |t| + t.integer "project_id", null: false + t.integer "user_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "users_star_projects", ["project_id"], name: "index_users_star_projects_on_project_id", using: :btree + add_index "users_star_projects", ["user_id", "project_id"], name: "index_users_star_projects_on_user_id_and_project_id", unique: true, using: :btree + add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree + + create_table "web_hooks", force: :cascade do |t| + t.string "url", limit: 2000 + t.integer "project_id" + t.datetime "created_at" + t.datetime "updated_at" + t.string "type", default: "ProjectHook" + t.integer "service_id" + t.boolean "push_events", default: true, null: false + t.boolean "issues_events", default: false, null: false + t.boolean "merge_requests_events", default: false, null: false + t.boolean "tag_push_events", default: false + t.boolean "note_events", default: false, null: false + t.boolean "enable_ssl_verification", default: true + t.boolean "build_events", default: false, null: false + t.boolean "wiki_page_events", default: false, null: false + t.string "token" + end + + add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree + add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree + + add_foreign_key "u2f_registrations", "users" +end -- cgit v1.2.1 From 6ace6d940a90e70f89392c3be7d9e538b6cec04c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 15 Jun 2016 15:09:50 +0200 Subject: Use validate and required for environment and project --- app/models/deployment.rb | 4 ++-- app/models/environment.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 030648470ee..e498ca96e3c 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -1,8 +1,8 @@ class Deployment < ActiveRecord::Base include InternalId - belongs_to :project, validate: true - belongs_to :environment, validate: true + belongs_to :project, required: true, validate: true + belongs_to :environment, required: true, validate: true belongs_to :user belongs_to :deployable, polymorphic: true diff --git a/app/models/environment.rb b/app/models/environment.rb index 7986a2529df..ac3a571a1f3 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -1,5 +1,5 @@ class Environment < ActiveRecord::Base - belongs_to :project, required: true + belongs_to :project, required: true, validate: true has_many :deployments -- cgit v1.2.1