Skip to content
Snippets Groups Projects
Commit a77158ff authored by Kamil Trzcińśki's avatar Kamil Trzcińśki
Browse files

Merge branch 'feature/intermediate/32568-adding-variables-to-pipelines-schedules' into 'master'

Add variables to pipelines schedules

Closes #32568

See merge request !12372
parents 89724028 e0c150da
No related branches found
No related tags found
2 merge requests!14773Maxraab master patch 51809,!12372Add variables to pipelines schedules
Pipeline #
Showing
with 302 additions and 9 deletions
Loading
Loading
@@ -3,6 +3,7 @@ import Translate from '../vue_shared/translate';
import intervalPatternInput from './components/interval_pattern_input.vue';
import TimezoneDropdown from './components/timezone_dropdown';
import TargetBranchDropdown from './components/target_branch_dropdown';
import { setupPipelineVariableList } from './setup_pipeline_variable_list';
 
Vue.use(Translate);
 
Loading
Loading
@@ -39,4 +40,6 @@ document.addEventListener('DOMContentLoaded', () => {
gl.timezoneDropdown = new TimezoneDropdown();
gl.targetBranchDropdown = new TargetBranchDropdown();
gl.pipelineScheduleFieldErrors = new gl.GlFieldErrors(formElement);
setupPipelineVariableList($('.js-pipeline-variable-list'));
});
function insertRow($row) {
const $rowClone = $row.clone();
$rowClone.removeAttr('data-is-persisted');
$rowClone.find('input, textarea').val('');
$row.after($rowClone);
}
function removeRow($row) {
const isPersisted = gl.utils.convertPermissionToBoolean($row.attr('data-is-persisted'));
if (isPersisted) {
$row.hide();
$row
.find('.js-destroy-input')
.val(1);
} else {
$row.remove();
}
}
function checkIfRowTouched($row) {
return $row.find('.js-user-input').toArray().some(el => $(el).val().length > 0);
}
function setupPipelineVariableList(parent = document) {
const $parent = $(parent);
$parent.on('click', '.js-row-remove-button', (e) => {
const $row = $(e.currentTarget).closest('.js-row');
removeRow($row);
e.preventDefault();
});
// Remove any empty rows except the last r
$parent.on('blur', '.js-user-input', (e) => {
const $row = $(e.currentTarget).closest('.js-row');
const isTouched = checkIfRowTouched($row);
if ($row.is(':not(:last-child)') && !isTouched) {
removeRow($row);
}
});
// Always make sure there is an empty last row
$parent.on('input', '.js-user-input', () => {
const $lastRow = $parent.find('.js-row').last();
const isTouched = checkIfRowTouched($lastRow);
if (isTouched) {
insertRow($lastRow);
}
});
// Clear out the empty last row so it
// doesn't get submitted and throw validation errors
$parent.closest('form').on('submit', () => {
const $lastRow = $parent.find('.js-row').last();
const isTouched = checkIfRowTouched($lastRow);
if (!isTouched) {
$lastRow.find('input, textarea').attr('name', '');
}
});
}
export {
setupPipelineVariableList,
insertRow,
removeRow,
};
Loading
Loading
@@ -153,6 +153,7 @@ $code_line_height: 1.6;
* Padding
*/
$gl-padding: 16px;
$gl-col-padding: 15px;
$gl-btn-padding: 10px;
$gl-input-padding: 10px;
$gl-vert-padding: 6px;
Loading
Loading
@@ -443,6 +444,7 @@ $logs-p-color: #333;
/*
* Forms
*/
$input-height: 34px;
$input-danger-bg: #f2dede;
$input-danger-border: $red-400;
$input-group-addon-bg: #f7f8fa;
Loading
Loading
@@ -574,6 +576,12 @@ $stage-hover-bg: #eaf3fc;
$stage-hover-border: #d1e7fc;
$action-icon-color: #d6d6d6;
 
/*
Pipeline Schedules
*/
$pipeline-variable-remove-button-width: calc(1em + #{2 * $gl-padding});
/*
Filtered Search
*/
Loading
Loading
Loading
Loading
@@ -74,3 +74,84 @@
margin-right: 3px;
}
}
.pipeline-variable-list {
margin-left: 0;
margin-bottom: 0;
padding-left: 0;
list-style: none;
clear: both;
}
.pipeline-variable-row {
display: flex;
align-items: flex-end;
&:not(:last-child) {
margin-bottom: $gl-btn-padding;
}
@media (max-width: $screen-sm-max) {
padding-right: $gl-col-padding;
}
&:last-child {
& .pipeline-variable-row-remove-button {
display: none;
}
@media (max-width: $screen-sm-max) {
& .pipeline-variable-value-input {
margin-right: $pipeline-variable-remove-button-width;
}
}
@media (max-width: $screen-xs-max) {
.pipeline-variable-row-body {
margin-right: $pipeline-variable-remove-button-width;
}
}
}
}
.pipeline-variable-row-body {
display: flex;
width: calc(75% - #{$gl-col-padding});
padding-left: $gl-col-padding;
@media (max-width: $screen-sm-max) {
width: 100%;
}
@media (max-width: $screen-xs-max) {
display: block;
}
}
.pipeline-variable-key-input {
margin-right: $gl-btn-padding;
@media (max-width: $screen-xs-max) {
margin-bottom: $gl-btn-padding;
}
}
.pipeline-variable-row-remove-button {
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
width: $pipeline-variable-remove-button-width;
height: $input-height;
padding: 0;
background: transparent;
border: 0;
color: $gl-text-color-secondary;
@include transition(color);
&:hover,
&:focus {
outline: none;
color: $gl-text-color;
}
}
class Projects::PipelineSchedulesController < Projects::ApplicationController
before_action :schedule, except: [:index, :new, :create]
before_action :authorize_read_pipeline_schedule!
before_action :authorize_create_pipeline_schedule!, only: [:new, :create]
before_action :authorize_update_pipeline_schedule!, only: [:edit, :take_ownership, :update]
before_action :authorize_update_pipeline_schedule!, except: [:index, :new, :create]
before_action :authorize_admin_pipeline_schedule!, only: [:destroy]
 
before_action :schedule, only: [:edit, :update, :destroy, :take_ownership]
def index
@scope = params[:scope]
@all_schedules = PipelineSchedulesFinder.new(@project).execute
Loading
Loading
@@ -53,7 +53,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
redirect_to pipeline_schedules_path(@project), status: 302
else
redirect_to pipeline_schedules_path(@project),
status: 302,
status: :forbidden,
alert: _("Failed to remove the pipeline schedule")
end
end
Loading
Loading
@@ -66,6 +66,15 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
 
def schedule_params
params.require(:schedule)
.permit(:description, :cron, :cron_timezone, :ref, :active)
.permit(:description, :cron, :cron_timezone, :ref, :active,
variables_attributes: [:id, :key, :value, :_destroy] )
end
def authorize_update_pipeline_schedule!
return access_denied! unless can?(current_user, :update_pipeline_schedule, schedule)
end
def authorize_admin_pipeline_schedule!
return access_denied! unless can?(current_user, :admin_pipeline_schedule, schedule)
end
end
Loading
Loading
@@ -203,6 +203,7 @@ module Ci
variables += project.group.secret_variables_for(ref, project).map(&:to_runner_variable) if project.group
variables += secret_variables(environment: environment)
variables += trigger_request.user_variables if trigger_request
variables += pipeline.pipeline_schedule.job_variables if pipeline.pipeline_schedule
variables += persisted_environment_variables if environment
 
variables
Loading
Loading
Loading
Loading
@@ -9,17 +9,21 @@ module Ci
belongs_to :owner, class_name: 'User'
has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline'
has_many :pipelines
has_many :variables, class_name: 'Ci::PipelineScheduleVariable'
 
validates :cron, unless: :importing?, cron: true, presence: { unless: :importing? }
validates :cron_timezone, cron_timezone: true, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? }
validates :description, presence: true
validates :variables, variable_duplicates: true
 
before_save :set_next_run_at
 
scope :active, -> { where(active: true) }
scope :inactive, -> { where(active: false) }
 
accepts_nested_attributes_for :variables, allow_destroy: true
def owned_by?(current_user)
owner == current_user
end
Loading
Loading
@@ -56,5 +60,9 @@ module Ci
Gitlab::Ci::CronParser.new(worker_cron, worker_time_zone)
.next_time_from(next_run_at)
end
def job_variables
variables&.map(&:to_runner_variable) || []
end
end
end
module Ci
class PipelineScheduleVariable < ActiveRecord::Base
extend Ci::Model
include HasVariable
belongs_to :pipeline_schedule
end
end
module Ci
class PipelineSchedulePolicy < PipelinePolicy
alias_method :pipeline_schedule, :subject
condition(:owner_of_schedule) do
can?(:developer_access) && pipeline_schedule.owned_by?(@user)
end
rule { can?(:master_access) | owner_of_schedule }.policy do
enable :update_pipeline_schedule
enable :admin_pipeline_schedule
end
end
end
Loading
Loading
@@ -162,7 +162,6 @@ class ProjectPolicy < BasePolicy
enable :create_pipeline
enable :update_pipeline
enable :create_pipeline_schedule
enable :update_pipeline_schedule
enable :create_merge_request
enable :create_wiki
enable :push_code
Loading
Loading
@@ -188,7 +187,6 @@ class ProjectPolicy < BasePolicy
enable :admin_build
enable :admin_container_image
enable :admin_pipeline
enable :admin_pipeline_schedule
enable :admin_environment
enable :admin_deployment
enable :admin_pages
Loading
Loading
# VariableDuplicatesValidator
#
# This validtor is designed for especially the following condition
# - Use `accepts_nested_attributes_for :xxx` in a parent model
# - Use `validates :xxx, uniqueness: { scope: :xxx_id }` in a child model
class VariableDuplicatesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
duplicates = value.reject(&:marked_for_destruction?).group_by(&:key).select { |_, v| v.many? }.map(&:first)
if duplicates.any?
record.errors.add(attribute, "Duplicate variables: #{duplicates.join(", ")}")
end
end
end
Loading
Loading
@@ -22,6 +22,14 @@
= f.label :ref, _('Target Branch'), class: 'label-light'
= dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
.form-group
.col-md-9
%label.label-light
#{ s_('PipelineSchedules|Variables') }
%ul.js-pipeline-variable-list.pipeline-variable-list
- @schedule.variables.each do |variable|
= render 'variable_row', id: variable.id, key: variable.key, value: variable.value
= render 'variable_row'
.form-group
.col-md-9
= f.label :active, s_('PipelineSchedules|Activated'), class: 'label-light'
Loading
Loading
Loading
Loading
@@ -26,7 +26,7 @@
= pipeline_schedule.owner&.name
%td
.pull-right.btn-group
- if can?(current_user, :update_pipeline_schedule, @project) && !pipeline_schedule.owned_by?(current_user)
- if can?(current_user, :update_pipeline_schedule, pipeline_schedule)
= link_to take_ownership_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('PipelineSchedules|Take ownership'), class: 'btn' do
= s_('PipelineSchedules|Take ownership')
- if can?(current_user, :update_pipeline_schedule, pipeline_schedule)
Loading
Loading
- id = local_assigns.fetch(:id, nil)
- key = local_assigns.fetch(:key, "")
- value = local_assigns.fetch(:value, "")
%li.js-row.pipeline-variable-row{ data: { is_persisted: "#{!id.nil?}" } }
.pipeline-variable-row-body
%input{ type: "hidden", name: "schedule[variables_attributes][][id]", value: id }
%input.js-destroy-input{ type: "hidden", name: "schedule[variables_attributes][][_destroy]" }
%input.js-user-input.pipeline-variable-key-input.form-control{ type: "text",
name: "schedule[variables_attributes][][key]",
value: key,
placeholder: s_('PipelineSchedules|Input variable key') }
%textarea.js-user-input.pipeline-variable-value-input.form-control{ rows: 1,
name: "schedule[variables_attributes][][value]",
placeholder: s_('PipelineSchedules|Input variable value') }
= value
%button.js-row-remove-button.pipeline-variable-row-remove-button{ 'aria-label': s_('PipelineSchedules|Remove variable row') }
%i.fa.fa-minus-circle{ 'aria-hidden': "true" }
---
title: Add variables to pipelines schedules
merge_request: 12372
author:
class CreateCiPipelineScheduleVariables < ActiveRecord::Migration
DOWNTIME = false
def up
create_table :ci_pipeline_schedule_variables do |t|
t.string :key, null: false
t.text :value
t.text :encrypted_value
t.string :encrypted_value_salt
t.string :encrypted_value_iv
t.integer :pipeline_schedule_id, null: false
t.timestamps_with_timezone null: true
end
add_index :ci_pipeline_schedule_variables,
[:pipeline_schedule_id, :key],
name: "index_ci_pipeline_schedule_variables_on_schedule_id_and_key",
unique: true
end
def down
drop_table :ci_pipeline_schedule_variables
end
end
class AddForeignKeyToCiPipelineScheduleVariables < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key(:ci_pipeline_schedule_variables, :ci_pipeline_schedules, column: :pipeline_schedule_id)
end
def down
remove_foreign_key(:ci_pipeline_schedule_variables, column: :pipeline_schedule_id)
end
end
Loading
Loading
@@ -253,6 +253,19 @@ ActiveRecord::Schema.define(version: 20170724184243) do
add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree
add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree
 
create_table "ci_pipeline_schedule_variables", force: :cascade do |t|
t.string "key", null: false
t.text "value"
t.text "encrypted_value"
t.string "encrypted_value_salt"
t.string "encrypted_value_iv"
t.integer "pipeline_schedule_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "ci_pipeline_schedule_variables", ["pipeline_schedule_id", "key"], name: "index_ci_pipeline_schedule_variables_on_schedule_id_and_key", unique: true, using: :btree
create_table "ci_group_variables", force: :cascade do |t|
t.string "key", null: false
t.text "value"
Loading
Loading
@@ -1566,6 +1579,7 @@ ActiveRecord::Schema.define(version: 20170724184243) do
add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify
add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade
add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade
add_foreign_key "ci_pipeline_schedule_variables", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_41c35fda51", on_delete: :cascade
add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade
add_foreign_key "ci_pipeline_schedules", "projects", name: "fk_8ead60fcc4", on_delete: :cascade
add_foreign_key "ci_pipeline_schedules", "users", column: "owner_id", name: "fk_9ea99f58d2", on_delete: :nullify
Loading
Loading
Loading
Loading
@@ -9,7 +9,7 @@ and a list of **user-defined variables**.
The variables can be overwritten and they take precedence over each other in
this order:
 
1. [Trigger variables][triggers] (take precedence over all)
1. [Trigger variables][triggers] or [scheduled pipeline variables](../../user/project/pipelines/schedules.md#making-use-of-scheduled-pipeline-variables) (take precedence over all)
1. Project-level [secret variables](#secret-variables) or [protected secret variables](#protected-secret-variables)
1. Group-level [secret variables](#secret-variables) or [protected secret variables](#protected-secret-variables)
1. YAML-defined [job-level variables](../yaml/README.md#job-variables)
Loading
Loading
doc/user/project/pipelines/img/pipeline_schedule_variables.png

13.2 KiB

0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment