Commit f88659d0 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch 'zj-auto-devops-table' into 'master'

Implement the implied CI/CD config for AutoDevOps

Closes #34777

See merge request !13923
parents 45510326 12ddc28f
...@@ -35,6 +35,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -35,6 +35,7 @@ document.addEventListener('DOMContentLoaded', () => {
propsData: { propsData: {
endpoint: pipelineTableViewEl.dataset.endpoint, endpoint: pipelineTableViewEl.dataset.endpoint,
helpPagePath: pipelineTableViewEl.dataset.helpPagePath, helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
autoDevopsHelpPath: pipelineTableViewEl.dataset.helpAutoDevopsPath,
}, },
}).$mount(); }).$mount();
pipelineTableViewEl.appendChild(table.$el); pipelineTableViewEl.appendChild(table.$el);
......
...@@ -13,6 +13,10 @@ ...@@ -13,6 +13,10 @@
type: String, type: String,
required: true, required: true,
}, },
autoDevopsHelpPath: {
type: String,
required: true,
},
}, },
mixins: [ mixins: [
pipelinesMixin, pipelinesMixin,
...@@ -95,6 +99,7 @@ ...@@ -95,6 +99,7 @@
<pipelines-table-component <pipelines-table-component
:pipelines="state.pipelines" :pipelines="state.pipelines"
:update-graph-dropdown="updateGraphDropdown" :update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsHelpPath"
/> />
</div> </div>
</div> </div>
......
...@@ -243,6 +243,7 @@ import bp from './breakpoints'; ...@@ -243,6 +243,7 @@ import bp from './breakpoints';
propsData: { propsData: {
endpoint: pipelineTableViewEl.dataset.endpoint, endpoint: pipelineTableViewEl.dataset.endpoint,
helpPagePath: pipelineTableViewEl.dataset.helpPagePath, helpPagePath: pipelineTableViewEl.dataset.helpPagePath,
autoDevopsHelpPath: pipelineTableViewEl.dataset.helpAutoDevopsPath,
}, },
}).$mount(); }).$mount();
......
<script> <script>
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import popover from '../../vue_shared/directives/popover';
export default { export default {
props: { props: {
pipeline: { pipeline: {
type: Object, type: Object,
required: true, required: true,
}, },
autoDevopsHelpPath: {
type: String,
required: true,
},
}, },
components: { components: {
userAvatarLink, userAvatarLink,
}, },
directives: { directives: {
tooltip, tooltip,
popover,
}, },
computed: { computed: {
user() { user() {
return this.pipeline.user; return this.pipeline.user;
}, },
popoverOptions() {
return {
html: true,
delay: { hide: 600 },
trigger: 'hover',
placement: 'top',
title: '<div class="autodevops-title">This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b></div>',
content: `<a class="autodevops-link" href="${this.autoDevopsHelpPath}" target="_blank" rel="noopener noreferrer nofollow">Learn more about Auto DevOps</a>`,
};
},
}, },
}; };
</script> </script>
<template> <template>
<div class="table-section section-15 hidden-xs hidden-sm"> <div class="table-section section-15 hidden-xs hidden-sm pipeline-tags">
<a <a
:href="pipeline.path" :href="pipeline.path"
class="js-pipeline-url-link"> class="js-pipeline-url-link">
...@@ -57,6 +73,13 @@ export default { ...@@ -57,6 +73,13 @@ export default {
:title="pipeline.yaml_errors"> :title="pipeline.yaml_errors">
yaml invalid yaml invalid
</span> </span>
<a
v-if="pipeline.flags.auto_devops"
class="js-pipeline-url-autodevops label label-info autodevops-badge"
v-popover="popoverOptions"
role="button">
Auto DevOps
</a>
<span <span
v-if="pipeline.flags.stuck" v-if="pipeline.flags.stuck"
class="js-pipeline-url-stuck label label-warning"> class="js-pipeline-url-stuck label label-warning">
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
endpoint: pipelinesData.endpoint, endpoint: pipelinesData.endpoint,
cssClass: pipelinesData.cssClass, cssClass: pipelinesData.cssClass,
helpPagePath: pipelinesData.helpPagePath, helpPagePath: pipelinesData.helpPagePath,
autoDevopsPath: pipelinesData.helpAutoDevopsPath,
newPipelinePath: pipelinesData.newPipelinePath, newPipelinePath: pipelinesData.newPipelinePath,
canCreatePipeline: pipelinesData.canCreatePipeline, canCreatePipeline: pipelinesData.canCreatePipeline,
allPath: pipelinesData.allPath, allPath: pipelinesData.allPath,
...@@ -200,6 +201,7 @@ ...@@ -200,6 +201,7 @@
<pipelines-table-component <pipelines-table-component
:pipelines="state.pipelines" :pipelines="state.pipelines"
:update-graph-dropdown="updateGraphDropdown" :update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsPath"
/> />
</div> </div>
......
...@@ -17,6 +17,10 @@ ...@@ -17,6 +17,10 @@
required: false, required: false,
default: false, default: false,
}, },
autoDevopsHelpPath: {
type: String,
required: true,
},
}, },
components: { components: {
pipelinesTableRowComponent, pipelinesTableRowComponent,
...@@ -54,6 +58,7 @@ ...@@ -54,6 +58,7 @@
:key="model.id" :key="model.id"
:pipeline="model" :pipeline="model"
:update-graph-dropdown="updateGraphDropdown" :update-graph-dropdown="updateGraphDropdown"
:auto-devops-help-path="autoDevopsHelpPath"
/> />
</div> </div>
</template> </template>
...@@ -25,6 +25,10 @@ export default { ...@@ -25,6 +25,10 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
autoDevopsHelpPath: {
type: String,
required: true,
},
}, },
components: { components: {
asyncButtonComponent, asyncButtonComponent,
...@@ -218,7 +222,10 @@ export default { ...@@ -218,7 +222,10 @@ export default {
</div> </div>
</div> </div>
<pipeline-url :pipeline="pipeline" /> <pipeline-url
:pipeline="pipeline"
:auto-devops-help-path="autoDevopsHelpPath"
/>
<div class="table-section section-25"> <div class="table-section section-25">
<div <div
......
/**
* Helper to user bootstrap popover in vue.js.
* Follow docs for html attributes: https://getbootstrap.com/docs/3.3/javascript/#static-popover
*
* @example
* import popover from 'vue_shared/directives/popover.js';
* {
* directives: [popover]
* }
* <a v-popover="{options}">popover</a>
*/
export default {
bind(el, binding) {
$(el).popover(binding.value);
},
unbind(el) {
$(el).popover('destroy');
},
};
...@@ -226,6 +226,14 @@ ...@@ -226,6 +226,14 @@
vertical-align: baseline; vertical-align: baseline;
} }
a.autodevops-badge {
color: $white-light;
}
a.autodevops-link {
color: $gl-link-color;
}
.commit-row-description { .commit-row-description {
font-size: 14px; font-size: 14px;
padding: 10px 15px; padding: 10px 15px;
......
...@@ -202,6 +202,10 @@ ...@@ -202,6 +202,10 @@
.btn-group.open .dropdown-toggle { .btn-group.open .dropdown-toggle {
box-shadow: none; box-shadow: none;
} }
.pipeline-tags .label-container {
white-space: normal;
}
} }
.stage-cell { .stage-cell {
...@@ -932,3 +936,8 @@ button.mini-pipeline-graph-dropdown-toggle { ...@@ -932,3 +936,8 @@ button.mini-pipeline-graph-dropdown-toggle {
.pipelines-container .top-area .nav-controls > .btn:last-child { .pipelines-container .top-area .nav-controls > .btn:last-child {
float: none; float: none;
} }
.autodevops-title {
font-weight: $gl-font-weight-normal;
line-height: 1.5;
}
...@@ -6,7 +6,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController ...@@ -6,7 +6,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
end end
def update def update
if @project.update_attributes(update_params) if @project.update(update_params)
flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated." flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
redirect_to project_settings_ci_cd_path(@project) redirect_to project_settings_ci_cd_path(@project)
else else
...@@ -16,14 +16,12 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController ...@@ -16,14 +16,12 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
private private
def create_params
params.require(:pipeline).permit(:ref)
end
def update_params def update_params
params.require(:project).permit( params.require(:project).permit(
:runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, :runners_token, :builds_enabled, :build_allow_git_fetch,
:public_builds, :auto_cancel_pending_pipelines, :ci_config_path :build_timeout_in_minutes, :build_coverage_regex, :public_builds,
:auto_cancel_pending_pipelines, :ci_config_path,
auto_devops_attributes: [:id, :domain, :enabled]
) )
end end
end end
...@@ -8,6 +8,7 @@ module Projects ...@@ -8,6 +8,7 @@ module Projects
define_secret_variables define_secret_variables
define_triggers_variables define_triggers_variables
define_badges_variables define_badges_variables
define_auto_devops_variables
end end
private private
...@@ -42,6 +43,10 @@ module Projects ...@@ -42,6 +43,10 @@ module Projects
badge.new(@project, @ref).metadata badge.new(@project, @ref).metadata
end end
end end
def define_auto_devops_variables
@auto_devops = @project.auto_devops || ProjectAutoDevops.new
end
end end
end end
end end
...@@ -115,6 +115,7 @@ module ApplicationSettingsHelper ...@@ -115,6 +115,7 @@ module ApplicationSettingsHelper
:after_sign_up_text, :after_sign_up_text,
:akismet_api_key, :akismet_api_key,
:akismet_enabled, :akismet_enabled,
:auto_devops_enabled,
:clientside_sentry_dsn, :clientside_sentry_dsn,
:clientside_sentry_enabled, :clientside_sentry_enabled,
:container_registry_token_expire_delay, :container_registry_token_expire_delay,
......
...@@ -216,6 +216,7 @@ module Ci ...@@ -216,6 +216,7 @@ module Ci
variables += runner.predefined_variables if runner variables += runner.predefined_variables if runner
variables += project.container_registry_variables variables += project.container_registry_variables
variables += project.deployment_variables if has_environment? variables += project.deployment_variables if has_environment?
variables += project.auto_devops_variables
variables += yaml_variables variables += yaml_variables
variables += user_variables variables += user_variables
variables += project.group.secret_variables_for(ref, project).map(&:to_runner_variable) if project.group variables += project.group.secret_variables_for(ref, project).map(&:to_runner_variable) if project.group
......
...@@ -38,6 +38,7 @@ module Ci ...@@ -38,6 +38,7 @@ module Ci
validates :status, presence: { unless: :importing? } validates :status, presence: { unless: :importing? }
validate :valid_commit_sha, unless: :importing? validate :valid_commit_sha, unless: :importing?
after_initialize :set_config_source, if: :new_record?
after_create :keep_around_commits, unless: :importing? after_create :keep_around_commits, unless: :importing?
enum source: { enum source: {
...@@ -50,6 +51,12 @@ module Ci ...@@ -50,6 +51,12 @@ module Ci
external: 6 external: 6
} }
enum config_source: {
unknown_source: nil,
repository_source: 1,
auto_devops_source: 2
}
state_machine :status, initial: :created do state_machine :status, initial: :created do
event :enqueue do event :enqueue do
transition created: :pending transition created: :pending
...@@ -316,6 +323,14 @@ module Ci ...@@ -316,6 +323,14 @@ module Ci
builds.latest.failed_but_allowed.any? builds.latest.failed_but_allowed.any?
end end
def set_config_source
if ci_yaml_from_repo
self.config_source = :repository_source
elsif implied_ci_yaml_file
self.config_source = :auto_devops_source
end
end
def config_processor def config_processor
return unless ci_yaml_file return unless ci_yaml_file
return @config_processor if defined?(@config_processor) return @config_processor if defined?(@config_processor)
...@@ -342,11 +357,17 @@ module Ci ...@@ -342,11 +357,17 @@ module Ci
def ci_yaml_file def ci_yaml_file
return @ci_yaml_file if defined?(@ci_yaml_file) return @ci_yaml_file if defined?(@ci_yaml_file)
@ci_yaml_file = begin @ci_yaml_file =
project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path) if auto_devops_source?
rescue Rugged::ReferenceError, GRPC::NotFound, GRPC::Internal implied_ci_yaml_file
self.yaml_errors = else
"Failed to load CI/CD config file at #{ci_yaml_file_path}" ci_yaml_from_repo
end
if @ci_yaml_file
@ci_yaml_file
else
self.yaml_errors = "Failed to load CI/CD config file for #{sha}"
nil nil
end end
end end
...@@ -434,6 +455,23 @@ module Ci ...@@ -434,6 +455,23 @@ module Ci
private private
def ci_yaml_from_repo
return unless project
return unless sha
project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path)
rescue GRPC::NotFound, Rugged::ReferenceError, GRPC::Internal
nil
end
def implied_ci_yaml_file
return unless project
if project.auto_devops_enabled?
Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content
end
end
def pipeline_data def pipeline_data
Gitlab::DataBuilder::Pipeline.build(self) Gitlab::DataBuilder::Pipeline.build(self)
end end
......
...@@ -187,9 +187,12 @@ class Project < ActiveRecord::Base ...@@ -187,9 +187,12 @@ class Project < ActiveRecord::Base
has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner' has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
has_one :auto_devops, class_name: 'ProjectAutoDevops'
accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature accepts_nested_attributes_for :project_feature
accepts_nested_attributes_for :import_data accepts_nested_attributes_for :import_data
accepts_nested_attributes_for :auto_devops
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true delegate :members, to: :team, prefix: true
...@@ -466,6 +469,14 @@ class Project < ActiveRecord::Base ...@@ -466,6 +469,14 @@ class Project < ActiveRecord::Base
self[:lfs_enabled] && Gitlab.config.lfs.enabled self[:lfs_enabled] && Gitlab.config.lfs.enabled
end end
def auto_devops_enabled?
if auto_devops&.enabled.nil?
current_application_settings.auto_devops_enabled?
else
auto_devops.enabled?
end
end
def repository_storage_path def repository_storage_path
Gitlab.config.repositories.storages[repository_storage].try(:[], 'path') Gitlab.config.repositories.storages[repository_storage].try(:[], 'path')
end end
...@@ -1378,6 +1389,10 @@ class Project < ActiveRecord::Base ...@@ -1378,6 +1389,10 @@ class Project < ActiveRecord::Base
Gitlab::Utils.slugify(full_path.to_s) Gitlab::Utils.slugify(full_path.to_s)
end end
def has_ci?
repository.gitlab_ci_yml || auto_devops_enabled?
end
def predefined_variables def predefined_variables
[ [
{ key: 'CI_PROJECT_ID', value: id.to_s, public: true }, { key: 'CI_PROJECT_ID', value: id.to_s, public: true },
...@@ -1423,6 +1438,12 @@ class Project < ActiveRecord::Base ...@@ -1423,6 +1438,12 @@ class Project < ActiveRecord::Base
deployment_service.predefined_variables deployment_service.predefined_variables
end end
def auto_devops_variables
return [] unless auto_devops_enabled?
auto_devops&.variables || []
end
def append_or_update_attribute(name, value) def append_or_update_attribute(name, value)
old_values = public_send(name.to_s) # rubocop:disable GitlabSecurity/PublicSend old_values = public_send(name.to_s) # rubocop:disable GitlabSecurity/PublicSend
......
class ProjectAutoDevops < ActiveRecord::Base
belongs_to :project
validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true }
def variables
variables = []
variables << { key: 'AUTO_DEVOPS_DOMAIN', value: domain, public: true } if domain.present?
variables
end
end
...@@ -16,6 +16,7 @@ class PipelineEntity < Grape::Entity ...@@ -16,6 +16,7 @@ class PipelineEntity < Grape::Entity
expose :flags do expose :flags do
expose :latest?, as: :latest expose :latest?, as: :latest
expose :stuck?, as: :stuck expose :stuck?, as: :stuck
expose :auto_devops_source?, as: :auto_devops
expose :has_yaml_errors?, as: :yaml_errors expose :has_yaml_errors?, as: :yaml_errors
expose :can_retry?, as: :retryable expose :can_retry?, as: :retryable
expose :can_cancel?, as: :cancelable expose :can_cancel?, as: :cancelable
......
...@@ -226,7 +226,17 @@ ...@@ -226,7 +226,17 @@
.help-block 0 for unlimited .help-block 0 for unlimited
%fieldset %fieldset
%legend Continuous Integration %legend Continuous Integration and Deployment
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :auto_devops_enabled do
= f.check_box :auto_devops_enabled
Enabled Auto DevOps (Beta) for projects by default
.help-block
It will automatically build, test, and deploy applications based on a predefined CI/CD configuration
= link_to icon('question-circle'), help_page_path('topics/autodevops/index.md')
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
.checkbox .checkbox
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
#commit-pipeline-table-view{ data: { disable_initialization: disable_initialization, #commit-pipeline-table-view{ data: { disable_initialization: disable_initialization,
endpoint: endpoint, endpoint: endpoint,
"help-page-path" => help_page_path('ci/quick_start/README'), "help-page-path" => help_page_path('ci/quick_start/README'),
"help-auto-devops-path" => help_page_path('topics/autodevops/index.md'),
} } } }
- content_for :page_specific_javascripts do - content_for :page_specific_javascripts do
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json), #pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json),
"css-class" => container_class, "css-class" => container_class,
"help-page-path" => help_page_path('ci/quick_start/README'), "help-page-path" => help_page_path('ci/quick_start/README'),
"help-auto-devops-path" => help_page_path('topics/autodevops/index.md'),
"new-pipeline-path" => new_project_pipeline_path(@project), "new-pipeline-path" => new_project_pipeline_path(@project),
"can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s, "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s,
"all-path" => project_pipelines_path(@project), "all-path" => project_pipelines_path(@project),
...@@ -13,7 +14,7 @@ ...@@ -13,7 +14,7 @@
"finished-path" => project_pipelines_path(@project, scope: :finished), "finished-path" => project_pipelines_path(@project, scope: :finished),
"branches-path" => project_pipelines_path(@project, scope: :branches), "branches-path" => project_pipelines_path(@project, scope: :branches),
"tags-path" => project_pipelines_path(@project, scope: :tags), "tags-path" => project_pipelines_path(@project, scope: :tags),
"has-ci" => @repository.gitlab_ci_yml, "has-ci" => @project.has_ci?,
"ci-lint-path" => ci_lint_path } } "ci-lint-path" => ci_lint_path } }
= page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('common_vue')
......
...@@ -2,10 +2,41 @@ ...@@ -2,10 +2,41 @@
.col-lg-12 .col-lg-12
= form_for @project, url: project_pipelines_settings_path(@project) do |f| = form_for @project, url: project_pipelines_settings_path(@project) do |f|
%fieldset.builds-feature %fieldset.builds-feature
- unless @repository.gitlab_ci_yml
.form-group .form-group
%p Pipelines need to be configured before you can begin using Continuous Integration. %p Pipelines need to have Auto DevOps enabled or have a .gitlab-ci.yml configured before you can begin using Continuous Integration and Delivery.
= link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info' %h5 Auto DevOps (Beta)
%p
Auto DevOps will automatically build, test, and deploy your application based on a predefined Continious Integration and Delivery configuration.
= link_to 'Learn more about Auto DevOps', help_page_path('topics/autodevops/index.md')
= f.fields_for :auto_devops_attributes, @auto_devops do |form|
.radio
= form.label :enabled_true do
= form.radio_button :enabled, 'true'
%strong Enable Auto DevOps
%br
%span.descr
The Auto DevOps pipeline configuration will be used when there is no .gitlab-ci.yml
in the project.
.radio
= form.label :enabled_false do
= form.radio_button :enabled, 'false'
%strong Disable Auto DevOps
%br
%span.descr
A specific .gitlab-ci.yml file needs to be specified before you can begin using Continious Integration and Delivery.
.radio
= form.label :enabled do
= form.radio_button :enabled, nil
%strong
Instance default (status: #{current_application_settings.auto_devops_enabled?})
%br
%span.descr
Follow the instance default to either have Auto DevOps enabled or disabled when there is no project specific .gitlab-ci.yml file specified.
%br
%p
Define a domain used by Auto DevOps to deploy towards, this is required for deploys to succeed.
= form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
%hr %hr
.form-group.append-bottom-default .form-group.append-bottom-default
= f.label :runners_token, "Runner token", class: 'label-light' = f.label :runners_token, "Runner token", class: 'label-light'
......
---
title: Allow users and administrator to configure Auto-DevOps
merge_request: 13923
author:
type: added
...@@ -10,5 +10,10 @@ ...@@ -10,5 +10,10 @@
# end # end
# #
ActiveSupport::Inflector.inflections do |inflect| ActiveSupport::Inflector.inflections do |inflect|
inflect.uncountable %w(award_emoji project_statistics system_note_metadata) inflect.uncountable %w(
award_emoji
project_statistics
system_note_metadata
project_auto_devops
)
end end
class AddAutoDevopsEnabledToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:application_settings, :auto_devops_enabled, :boolean, default: false)
end
def down
remove_column(:application_settings, :auto_devops_enabled, :boolean)
end
end
class CreateProjectAutoDevOps < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
create_table :project_auto_devops do |t|
t.belongs_to :project, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
t.datetime_with_timezone :created_at, null: false
t.datetime_with_timezone :updated_at, null: false
t.boolean :enabled, default: nil, null: true
t.string :domain
end
end
def down
drop_table(:project_auto_devops)
end
end
class AddConfigSourceToPipelines < ActiveRecord::Migration
DOWNTIME = false
def change
add_column(:ci_pipelines, :config_source, :integer, allow_null: true)
end
end
...@@ -133,6 +133,7 @@ ActiveRecord::Schema.define(version: 20170905112933) do ...@@ -133,6 +133,7 @@ ActiveRecord::Schema.define(version: 20170905112933) do
t.integer "performance_bar_allowed_group_id" t.integer "performance_bar_allowed_group_id"
t.boolean "hashed_storage_enabled", default: false, null: false t.boolean "hashed_storage_enabled", default: false, null: false
t.boolean "project_export_enabled", default: true, null: false t.boolean "project_export_enabled", default: true, null: false
t.boolean "auto_devops_enabled", default: false, null: false
end end
create_table "audit_events", force: :cascade do |t| create_table "audit_events", force: :cascade do |t|
...@@ -340,6 +341,7 @@ ActiveRecord::Schema.define(version: 20170905112933) do ...@@ -340,6 +341,7 @@ ActiveRecord::Schema.define(version: 20170905112933) do
t.integer "auto_canceled_by_id" t.integer "auto_canceled_by_id"
t.integer "pipeline_schedule_id" t.integer "pipeline_schedule_id"
t.integer "source" t.integer "source"
t.integer "config_source"
t.boolean "protected" t.boolean "protected"
end end
...@@ -1108,6 +1110,16 @@ ActiveRecord::Schema.define(version: 20170905112933) do ...@@ -1108,6 +1110,16 @@ ActiveRecord::Schema.define(version: 20170905112933) do
add_index "project_authorizations", ["project_id"], name: "index_project_authorizations_on_project_id", using: :btree add_index "project_authorizations", ["project_id"], name: "index_project_authorizations_on_project_id", using: :btree
add_index "project_authorizations", ["user_id", "project_id", "access_level"], name: "index_project_authorizations_on_user_id_project_id_access_level", unique: true, using: :btree add_index "project_authorizations", ["user_id", "project_id", "access_level"], name: "index_project_authorizations_on_user_id_project_id_access_level", unique: true, using: :btree
create_table "project_auto_devops", force: :cascade do |t|
t.integer "project_id", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.boolean "enabled"
t.string "domain"
end
add_index "project_auto_devops", ["project_id"], name: "index_project_auto_devops_on_project_id", unique: true, using: :btree
create_table "project_features", force: :cascade do |t| create_table "project_features", force: :cascade do |t|
t.integer "project_id" t.integer "project_id"
t.integer "merge_requests_access_level" t.integer "merge_requests_access_level"
...@@ -1724,6 +1736,7 @@ ActiveRecord::Schema.define(version: 20170905112933) do ...@@ -1724,6 +1736,7 @@ ActiveRecord::Schema.define(version: 20170905112933) do
add_foreign_key "personal_access_tokens", "users" add_foreign_key "personal_access_tokens", "users"
add_foreign_key "project_authorizations", "projects", on_delete: :cascade add_foreign_key "project_authorizations", "projects", on_delete: :cascade
add_foreign_key "project_authorizations", "users", on_delete: :cascade add_foreign_key "project_authorizations", "users", on_delete: :cascade
add_foreign_key "project_auto_devops", "projects", on_delete: :cascade
add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade
add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade
add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade
......
...@@ -50,6 +50,7 @@ project_tree: ...@@ -50,6 +50,7 @@ project_tree:
- :push_event_payload - :push_event_payload
- :stages - :stages
- :statuses - :statuses
- :auto_devops
- :triggers - :triggers
- :pipeline_schedules - :pipeline_schedules
- :services - :services
......
...@@ -14,6 +14,7 @@ module Gitlab ...@@ -14,6 +14,7 @@ module Gitlab
create_access_levels: 'ProtectedTag::CreateAccessLevel', create_access_levels: 'ProtectedTag::CreateAccessLevel',
labels: :project_labels, labels: :project_labels,
priorities: :label_priorities, priorities: :label_priorities,
auto_devops: :project_auto_devops,
label: :project_label }.freeze label: :project_label }.freeze
USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id].freeze USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id].freeze
......
FactoryGirl.define do
factory :project_auto_devops do
project
enabled true
domain "example.com"
end
end
...@@ -41,5 +41,15 @@ feature "Pipelines settings" do ...@@ -41,5 +41,15 @@ feature "Pipelines settings" do
checkbox = find_field('project_auto_cancel_pending_pipelines') checkbox = find_field('project_auto_cancel_pending_pipelines')
expect(checkbox).to be_checked expect(checkbox).to be_checked
end end
scenario 'update auto devops settings' do
fill_in('project_auto_devops_attributes_domain', with: 'test.com')
page.choose('project_auto_devops_attributes_enabled_false')
click_on 'Save changes'
expect(page.status_code).to eq(200)
expect(project.auto_devops).to be_present
expect(project.auto_devops).not_to be_enabled
end
end end
end end
...@@ -29,6 +29,7 @@ describe('Pipelines table in Commits and Merge requests', () => { ...@@ -29,6 +29,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
propsData: { propsData: {
endpoint: 'endpoint', endpoint: 'endpoint',
helpPagePath: 'foo', helpPagePath: 'foo',
autoDevopsHelpPath: 'foo',
}, },
}).$mount(); }).$mount();
}); });
...@@ -64,6 +65,7 @@ describe('Pipelines table in Commits and Merge requests', () => { ...@@ -64,6 +65,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
propsData: { propsData: {
endpoint: 'endpoint', endpoint: 'endpoint',
helpPagePath: 'foo', helpPagePath: 'foo',
autoDevopsHelpPath: 'foo',
}, },
}).$mount(); }).$mount();
}); });
...@@ -115,6 +117,7 @@ describe('Pipelines table in Commits and Merge requests', () => { ...@@ -115,6 +117,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
propsData: { propsData: {
endpoint: 'endpoint', endpoint: 'endpoint',
helpPagePath: 'foo', helpPagePath: 'foo',
autoDevopsHelpPath: 'foo',
}, },
}).$mount(); }).$mount();
element.appendChild(this.component.$el); element.appendChild(this.component.$el);
...@@ -136,6 +139,7 @@ describe('Pipelines table in Commits and Merge requests', () => { ...@@ -136,6 +139,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
propsData: { propsData: {
endpoint: 'endpoint', endpoint: 'endpoint',
helpPagePath: 'foo', helpPagePath: 'foo',
autoDevopsHelpPath: 'foo',
}, },
}).$mount(); }).$mount();
}); });
......
...@@ -16,6 +16,7 @@ describe('Pipeline Url Component', () => { ...@@ -16,6 +16,7 @@ describe('Pipeline Url Component', () => {
path: 'foo', path: 'foo',
flags: {}, flags: {},
}, },
autoDevopsHelpPath: 'foo',
}, },
}).$mount(); }).$mount();
...@@ -30,6 +31,7 @@ describe('Pipeline Url Component', () => { ...@@ -30,6 +31,7 @@ describe('Pipeline Url Component', () => {
path: 'foo', path: 'foo',
flags: {}, flags: {},
}, },
autoDevopsHelpPath: 'foo',
}, },
}).$mount(); }).$mount();
...@@ -50,6 +52,7 @@ describe('Pipeline Url Component', () => { ...@@ -50,6 +52,7 @@ describe('Pipeline Url Component', () => {
path: '/', path: '/',
}, },
}, },
autoDevopsHelpPath: 'foo',
}; };
const component = new PipelineUrlComponent({ const component = new PipelineUrlComponent({
...@@ -73,6 +76,7 @@ describe('Pipeline Url Component', () => { ...@@ -73,6 +76,7 @@ describe('Pipeline Url Component', () => {
path: 'foo', path: 'foo',
flags: {}, flags: {},
}, },
autoDevopsHelpPath: 'foo',
}, },
}).$mount(); }).$mount();
...@@ -91,6 +95,7 @@ describe('Pipeline Url Component', () => { ...@@ -91,6 +95,7 @@ describe('Pipeline Url Component', () => {
stuck: true, stuck: true,
}, },
}, },
autoDevopsHelpPath: 'foo',
}, },
}).$mount(); }).$mount();
...@@ -98,4 +103,26 @@ describe('Pipeline Url Component', () => { ...@@ -98,4 +103,26 @@ describe('Pipeline Url Component', () => {
expect(component.$el.querySelector('.js-pipeline-url-yaml').textContent).toContain('yaml invalid'); expect(component.$el.querySelector('.js-pipeline-url-yaml').textContent).toContain('yaml invalid');
expect(component.$el.querySelector('.js-pipeline-url-stuck').textContent).toContain('stuck'); expect(component.$el.querySelector('.js-pipeline-url-stuck').textContent).toContain('stuck');
}); });
it('should render a badge for autodevops', () => {
const component = new PipelineUrlComponent({
propsData: {
pipeline: {
id: 1,
path: 'foo',
flags: {
latest: true,
yaml_errors: true,
stuck: true,
auto_devops: true,
},
},
autoDevopsHelpPath: 'foo',
},
}).$mount();
expect(
component.$el.querySelector('.js-pipeline-url-autodevops').textContent.trim(),
).toEqual('Auto DevOps');
});
}); });
...@@ -9,7 +9,7 @@ describe('Pipelines Table Row', () => { ...@@ -9,7 +9,7 @@ describe('Pipelines Table Row', () => {
el: document.querySelector('.test-dom-element'), el: document.querySelector('.test-dom-element'),
propsData: { propsData: {
pipeline, pipeline,
service: {}, autoDevopsHelpPath: 'foo',
}, },
}).$mount(); }).$mount();
}; };
......
...@@ -22,6 +22,7 @@ describe('Pipelines Table', () => { ...@@ -22,6 +22,7 @@ describe('Pipelines Table', () => {
component = new PipelinesTableComponent({ component = new PipelinesTableComponent({
propsData: { propsData: {
pipelines: [], pipelines: [],
autoDevopsHelpPath: 'foo',
}, },
}).$mount(); }).$mount();
}); });
...@@ -47,6 +48,7 @@ describe('Pipelines Table', () => { ...@@ -47,6 +48,7 @@ describe('Pipelines Table', () => {
const component = new PipelinesTableComponent({ const component = new PipelinesTableComponent({
propsData: { propsData: {
pipelines: [], pipelines: [],
autoDevopsHelpPath: 'foo',
}, },
}).$mount(); }).$mount();
expect(component.$el.querySelectorAll('.commit.gl-responsive-table-row').length).toEqual(0); expect(component.$el.querySelectorAll('.commit.gl-responsive-table-row').length).toEqual(0);
...@@ -58,6 +60,7 @@ describe('Pipelines Table', () => { ...@@ -58,6 +60,7 @@ describe('Pipelines Table', () => {
const component = new PipelinesTableComponent({ const component = new PipelinesTableComponent({
propsData: { propsData: {
pipelines: [pipeline], pipelines: [pipeline],
autoDevopsHelpPath: 'foo',
}, },
}).$mount(); }).$mount();
......
...@@ -256,6 +256,7 @@ project: ...@@ -256,6 +256,7 @@ project:
- environments - environments
- deployments - deployments
- project_feature - project_feature
- auto_devops
- pages_domains - pages_domains
- authorized_users - authorized_users
- project_authorizations - project_authorizations
......
...@@ -223,6 +223,7 @@ Ci::Pipeline: ...@@ -223,6 +223,7 @@ Ci::Pipeline:
- lock_version - lock_version
- auto_canceled_by_id - auto_canceled_by_id
- pipeline_schedule_id - pipeline_schedule_id
- config_source
- protected - protected
Ci::Stage: Ci::Stage:
- id - id
...@@ -466,3 +467,10 @@ Timelog: ...@@ -466,3 +467,10 @@ Timelog:
- user_id - user_id
- created_at - created_at
- updated_at - updated_at
ProjectAutoDevops:
- id
- enabled
- domain
- project_id
- created_at
- updated_at
...@@ -5,6 +5,7 @@ describe ApplicationSetting do ...@@ -5,6 +5,7 @@ describe ApplicationSetting do
it { expect(setting).to be_valid } it { expect(setting).to be_valid }
it { expect(setting.uuid).to be_present } it { expect(setting.uuid).to be_present }
it { expect(setting).to have_db_column(:auto_devops_enabled) }
describe 'validations' do describe 'validations' do
let(:http) { 'http://example.com' } let(:http) { 'http://example.com' }
......
...@@ -1688,6 +1688,30 @@ describe Ci::Build do ...@@ -1688,6 +1688,30 @@ describe Ci::Build do
{ key: 'secret', value: 'value', public: false }]) { key: 'secret', value: 'value', public: false }])
end end
end end
context 'when using auto devops' do
context 'and is enabled' do
before do
project.create_auto_devops!(enabled: true, domain: 'example.com')
end
it "includes AUTO_DEVOPS_DOMAIN" do
is_expected.to include(
{ key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true })
end
end
context 'and is disabled' do
before do
project.create_auto_devops!(enabled: false, domain: 'example.com')
end
it "includes AUTO_DEVOPS_DOMAIN" do
is_expected.not_to include(
{ key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true })
end
end
end
end end
describe 'state transition: any => [:pending]' do describe 'state transition: any => [:pending]' do
......
...@@ -799,14 +799,118 @@ describe Ci::Pipeline, :mailer do ...@@ -799,14 +799,118 @@ describe Ci::Pipeline, :mailer do
end end
end end
describe '#set_config_source' do
context 'on object initialisation' do
context 'when pipelines does not contain needed data' do
let(:pipeline) do
Ci::Pipeline.new
end
it 'defines source to be unknown' do
expect(pipeline).to be_unknown_source
end
end
context 'when pipeline contains all needed data' do
let(:pipeline) do
Ci::Pipeline.new(
project: project,
sha: '1234',
ref: 'master',
source: :push)
end
context 'when the repository has a config file' do
before do
allow(project.repository).to receive(:gitlab_ci_yml_for)
.and_return('config')
end
it 'defines source to be from repository' do
expect(pipeline).to be_repository_source
end
context 'when loading an object' do
let(:new_pipeline) { Ci::Pipeline.find(pipeline.id) }
it 'does not redefine the source' do
# force to overwrite the source
pipeline.unknown_source!
expect(new_pipeline).to be_unknown_source
end
end
end
context 'when the repository does not have a config file' do
let(:implied_yml) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content }
context 'auto devops enabled' do
before do
stub_application_setting(auto_devops_enabled: true)
allow(project).to receive(:ci_config_path) { 'custom' }
end
it 'defines source to be auto devops' do
subject
expect(pipeline).to be_auto_devops_source
end
end
end
end
end
end
describe '#ci_yaml_file' do describe '#ci_yaml_file' do
it 'reports error if the file is not found' do let(:implied_yml) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content }
allow(pipeline.project).to receive(:ci_config_path) { 'custom' }
pipeline.ci_yaml_file context 'the source is unknown' do
before do
pipeline.unknown_source!
end
it 'returns the configuration if found' do
allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
.and_return('config')
expect(pipeline.ci_yaml_file).to be_a(String)
expect(pipeline.ci_yaml_file).not_to eq(implied_yml)
expect(pipeline.yaml_errors).to be_nil
end
it 'sets yaml errors if not found' do
expect(pipeline.ci_yaml_file).to be_nil
expect(pipeline.yaml_errors) expect(pipeline.yaml_errors)
.to eq('Failed to load CI/CD config file at custom') .to start_with('Failed to load CI/CD config file')
end
end
context 'the source is the repository' do
before do
pipeline.repository_source!
end
it 'returns the configuration if found' do
allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
.and_return('config')
expect(pipeline.ci_yaml_file).to be_a(String)
expect(pipeline.ci_yaml_file).not_to eq(implied_yml)
expect(pipeline.yaml_errors).to be_nil
end
end
context 'when the source is auto_devops_source' do
before do
stub_application_setting(auto_devops_enabled: true)
pipeline.auto_devops_source!
end
it 'finds the implied config' do
expect(pipeline.ci_yaml_file).to eq(implied_yml)
expect(pipeline.yaml_errors).to be_nil
end
end end
end end
......
require 'spec_helper'
describe ProjectAutoDevops do
set(:project) { build(:project) }
it { is_expected.to belong_to(:project) }
it { is_expected.to respond_to(:created_at) }
it { is_expected.to respond_to(:updated_at) }
describe 'variables' do
let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: domain) }
context 'when domain is defined' do
let(:domain) { 'example.com' }
it 'returns AUTO_DEVOPS_DOMAIN' do
expect(auto_devops.variables).to include(
{ key: 'AUTO_DEVOPS_DOMAIN', value: 'example.com', public: true })
end
end
end
end
...@@ -53,6 +53,7 @@ describe Project do ...@@ -53,6 +53,7 @@ describe Project do
it { is_expected.to have_one(:import_data).class_name('ProjectImportData') } it { is_expected.to have_one(:import_data).class_name('ProjectImportData') }
it { is_expected.to have_one(:last_event).class_name('Event') } it { is_expected.to have_one(:last_event).class_name('Event') }
it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) } it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) }
it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') }
it { is_expected.to have_many(:commit_statuses) } it { is_expected.to have_many(:commit_statuses) }
it { is_expected.to have_many(:pipelines) } it { is_expected.to have_many(:pipelines) }
it { is_expected.to have_many(:builds) } it { is_expected.to have_many(:builds) }
...@@ -2508,4 +2509,133 @@ describe Project do ...@@ -2508,4 +2509,133 @@ describe Project do
end end
end end
end end
describe '#has_ci?' do
set(:project) { create(:project) }
let(:repository) { double }
before do
expect(project).to receive(:repository) { repository }
end
context 'when has .gitlab-ci.yml' do
before do
expect(repository).to receive(:gitlab_ci_yml) { 'content' }
end
it "CI is available" do
expect(project).to have_ci
end
end
context 'when there is no .gitlab-ci.yml' do
before do
expect(repository).to receive(:gitlab_ci_yml) { nil }
end
it "CI is not available" do
expect(project).not_to have_ci
end
context 'when auto devops is enabled' do
before do
stub_application_setting(auto_devops_enabled: true)
end
it "CI is available" do
expect(project).to have_ci
end
end
end
end
describe '#auto_devops_enabled?' do
set(:project) { create(:project) }
subject { project.auto_devops_enabled? }
context 'when enabled in settings' do
before do
stub_application_setting(auto_devops_enabled: true)
end
it 'auto devops is implicitly enabled' do
expect(project.auto_devops).to be_nil
expect(project).to be_auto_devops_enabled
end
context 'when explicitly enabled' do
before do
create(:project_auto_devops, project: project)
end
it "auto devops is enabled" do
expect(project).to be_auto_devops_enabled
end
end
context 'when explicitly disabled' do
before do
create(:project_auto_devops, project: project, enabled: false)
end
it "auto devops is disabled" do
expect(project).not_to be_auto_devops_enabled
end
end
end
context 'when disabled in settings' do
before do
stub_application_setting(auto_devops_enabled: false)
end
it 'auto devops is implicitly disabled' do
expect(project.auto_devops).to be_nil
expect(project).not_to be_auto_devops_enabled
end
context 'when explicitly enabled' do
before do
create(:project_auto_devops, project: project)
end
it "auto devops is enabled" do
expect(project).to be_auto_devops_enabled
end
end
end
end
context '#auto_devops_variables' do
set(:project) { create(:project) }
subject { project.auto_devops_variables }
context 'when enabled in settings' do
before do
stub_application_setting(auto_devops_enabled: true)
end
context 'when domain is empty' do
before do
create(:project_auto_devops, project: project, domain: nil)
end
it 'variables are empty' do
is_expected.to be_empty
end
end
context 'when domain is configured' do
before do
create(:project_auto_devops, project: project, domain: 'example.com')
end
it "variables are not empty" do
is_expected.not_to be_empty
end
end
end
end
end end
...@@ -36,7 +36,7 @@ describe PipelineEntity do ...@@ -36,7 +36,7 @@ describe PipelineEntity do
it 'contains flags' do it 'contains flags' do
expect(subject).to include :flags expect(subject).to include :flags
expect(subject[:flags]) expect(subject[:flags])
.to include :latest, :stuck, .to include :latest, :stuck, :auto_devops,
:yaml_errors, :retryable, :cancelable :yaml_errors, :retryable, :cancelable
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe Ci::CreatePipelineService do describe Ci::CreatePipelineService do
let(:project) { create(:project, :repository) } set(:project) { create(:project, :repository) }
let(:user) { create(:admin) } let(:user) { create(:admin) }
let(:ref_name) { 'refs/heads/master' } let(:ref_name) { 'refs/heads/master' }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment