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

Merge branch 'feature/intermediate/12729-group-secret-variables' into 'master'

Add Group secret variables

Closes #12729

See merge request !12582
parents 5c0d8d59 3b2f0928
...@@ -396,6 +396,7 @@ import PerformanceBar from './performance_bar'; ...@@ -396,6 +396,7 @@ import PerformanceBar from './performance_bar';
initSettingsPanels(); initSettingsPanels();
break; break;
case 'projects:settings:ci_cd:show': case 'projects:settings:ci_cd:show':
case 'groups:settings:ci_cd:show':
new gl.ProjectVariables(); new gl.ProjectVariables();
break; break;
case 'ci:lints:create': case 'ci:lints:create':
......
module Groups
module Settings
class CiCdController < Groups::ApplicationController
before_action :authorize_admin_pipeline!
def show
define_secret_variables
end
private
def define_secret_variables
@variable = Ci::GroupVariable.new(group: group)
.present(current_user: current_user)
@variables = group.variables.order_key_asc
.map { |variable| variable.present(current_user: current_user) }
end
def authorize_admin_pipeline!
return render_404 unless can?(current_user, :admin_pipeline, group)
end
end
end
end
module Groups
class VariablesController < Groups::ApplicationController
before_action :variable, only: [:show, :update, :destroy]
before_action :authorize_admin_build!
def index
redirect_to group_settings_ci_cd_path(group)
end
def show
end
def update
if variable.update(variable_params)
redirect_to group_variables_path(group),
notice: 'Variable was successfully updated.'
else
render "show"
end
end
def create
@variable = group.variables.create(variable_params)
.present(current_user: current_user)
if @variable.persisted?
redirect_to group_settings_ci_cd_path(group),
notice: 'Variable was successfully created.'
else
render "show"
end
end
def destroy
if variable.destroy
redirect_to group_settings_ci_cd_path(group),
status: 302,
notice: 'Variable was successfully removed.'
else
redirect_to group_settings_ci_cd_path(group),
status: 302,
notice: 'Failed to remove the variable.'
end
end
private
def variable_params
params.require(:variable).permit(*variable_params_attributes)
end
def variable_params_attributes
%i[key value protected]
end
def variable
@variable ||= group.variables.find(params[:id]).present(current_user: current_user)
end
def authorize_admin_build!
return render_404 unless can?(current_user, :admin_build, group)
end
end
end
...@@ -21,7 +21,10 @@ module Projects ...@@ -21,7 +21,10 @@ module Projects
end end
def define_secret_variables def define_secret_variables
@variable = Ci::Variable.new @variable = Ci::Variable.new(project: project)
.present(current_user: current_user)
@variables = project.variables.order_key_asc
.map { |variable| variable.present(current_user: current_user) }
end end
def define_triggers_variables def define_triggers_variables
......
class Projects::VariablesController < Projects::ApplicationController class Projects::VariablesController < Projects::ApplicationController
before_action :variable, only: [:show, :update, :destroy]
before_action :authorize_admin_build! before_action :authorize_admin_build!
layout 'project_settings' layout 'project_settings'
...@@ -8,37 +9,39 @@ class Projects::VariablesController < Projects::ApplicationController ...@@ -8,37 +9,39 @@ class Projects::VariablesController < Projects::ApplicationController
end end
def show def show
@variable = @project.variables.find(params[:id])
end end
def update def update
@variable = @project.variables.find(params[:id]) if variable.update(variable_params)
redirect_to project_variables_path(project),
if @variable.update_attributes(variable_params) notice: 'Variable was successfully updated.'
redirect_to project_variables_path(project), notice: 'Variable was successfully updated.'
else else
render action: "show" render "show"
end end
end end
def create def create
@variable = @project.variables.new(variable_params) @variable = project.variables.create(variable_params)
.present(current_user: current_user)
if @variable.save if @variable.persisted?
flash[:notice] = 'Variables were successfully updated.' redirect_to project_settings_ci_cd_path(project),
redirect_to project_settings_ci_cd_path(project) notice: 'Variable was successfully created.'
else else
render "show" render "show"
end end
end end
def destroy def destroy
@key = @project.variables.find(params[:id]) if variable.destroy
@key.destroy
redirect_to project_settings_ci_cd_path(project), redirect_to project_settings_ci_cd_path(project),
status: 302, status: 302,
notice: 'Variable was successfully removed.' notice: 'Variable was successfully removed.'
else
redirect_to project_settings_ci_cd_path(project),
status: 302,
notice: 'Failed to remove the variable.'
end
end end
private private
...@@ -50,4 +53,8 @@ class Projects::VariablesController < Projects::ApplicationController ...@@ -50,4 +53,8 @@ class Projects::VariablesController < Projects::ApplicationController
def variable_params_attributes def variable_params_attributes
%i[id key value protected _destroy] %i[id key value protected _destroy]
end end
def variable
@variable ||= project.variables.find(params[:id]).present(current_user: current_user)
end
end end
...@@ -200,6 +200,7 @@ module Ci ...@@ -200,6 +200,7 @@ module Ci
variables += project.deployment_variables if has_environment? variables += project.deployment_variables if has_environment?
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 += secret_variables(environment: environment) variables += secret_variables(environment: environment)
variables += trigger_request.user_variables if trigger_request variables += trigger_request.user_variables if trigger_request
variables += persisted_environment_variables if environment variables += persisted_environment_variables if environment
......
module Ci
class GroupVariable < ActiveRecord::Base
extend Ci::Model
include HasVariable
include Presentable
belongs_to :group
validates :key, uniqueness: { scope: :group_id }
scope :unprotected, -> { where(protected: false) }
end
end
...@@ -2,6 +2,7 @@ module Ci ...@@ -2,6 +2,7 @@ module Ci
class Variable < ActiveRecord::Base class Variable < ActiveRecord::Base
extend Ci::Model extend Ci::Model
include HasVariable include HasVariable
include Presentable
belongs_to :project belongs_to :project
......
...@@ -22,6 +22,7 @@ class Group < Namespace ...@@ -22,6 +22,7 @@ class Group < Namespace
has_many :shared_projects, through: :project_group_links, source: :project has_many :shared_projects, through: :project_group_links, source: :project
has_many :notification_settings, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent has_many :notification_settings, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
has_many :labels, class_name: 'GroupLabel' has_many :labels, class_name: 'GroupLabel'
has_many :variables, class_name: 'Ci::GroupVariable'
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :visibility_level_allowed_by_projects validate :visibility_level_allowed_by_projects
...@@ -248,6 +249,14 @@ class Group < Namespace ...@@ -248,6 +249,14 @@ class Group < Namespace
} }
end end
def secret_variables_for(ref, project)
list_of_ids = [self] + ancestors
variables = Ci::GroupVariable.where(group: list_of_ids)
variables = variables.unprotected unless project.protected_for?(ref)
variables = variables.group_by(&:group_id)
list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten
end
protected protected
def update_two_factor_requirement def update_two_factor_requirement
......
...@@ -31,6 +31,8 @@ class GroupPolicy < BasePolicy ...@@ -31,6 +31,8 @@ class GroupPolicy < BasePolicy
rule { master }.policy do rule { master }.policy do
enable :create_projects enable :create_projects
enable :admin_milestones enable :admin_milestones
enable :admin_pipeline
enable :admin_build
end end
rule { owner }.policy do rule { owner }.policy do
......
module Ci
class GroupVariablePresenter < Gitlab::View::Presenter::Delegated
presents :variable
def placeholder
'GROUP_VARIABLE'
end
def form_path
if variable.persisted?
group_variable_path(group, variable)
else
group_variables_path(group)
end
end
def edit_path
group_variable_path(group, variable)
end
def delete_path
group_variable_path(group, variable)
end
end
end
module Ci
class VariablePresenter < Gitlab::View::Presenter::Delegated
presents :variable
def placeholder
'PROJECT_VARIABLE'
end
def form_path
if variable.persisted?
project_variable_path(project, variable)
else
project_variables_path(project)
end
end
def edit_path
project_variable_path(project, variable)
end
def delete_path
project_variable_path(project, variable)
end
end
end
= form_for [@project.namespace.becomes(Namespace), @project, @variable] do |f| = form_for @variable, as: :variable, url: @variable.form_path do |f|
= form_errors(@variable) = form_errors(@variable)
.form-group .form-group
= f.label :key, "Key", class: "label-light" = f.label :key, "Key", class: "label-light"
= f.text_field :key, class: "form-control", placeholder: "PROJECT_VARIABLE", required: true = f.text_field :key, class: "form-control", placeholder: @variable.placeholder, required: true
.form-group .form-group
= f.label :value, "Value", class: "label-light" = f.label :value, "Value", class: "label-light"
= f.text_area :value, class: "form-control", placeholder: "PROJECT_VARIABLE" = f.text_area :value, class: "form-control", placeholder: @variable.placeholder
.form-group .form-group
.checkbox .checkbox
= f.label :protected do = f.label :protected do
......
.row.prepend-top-default.append-bottom-default .row.prepend-top-default.append-bottom-default
.col-lg-4 .col-lg-4
= render "projects/variables/content" = render "ci/variables/content"
.col-lg-8 .col-lg-8
%h5.prepend-top-0 %h5.prepend-top-0
Add a variable Add a variable
= render "projects/variables/form", btn_text: "Add new variable" = render "ci/variables/form", btn_text: "Add new variable"
%hr %hr
%h5.prepend-top-0 %h5.prepend-top-0
Your variables (#{@project.variables.size}) Your variables (#{@variables.size})
- if @project.variables.empty? - if @variables.empty?
%p.settings-message.text-center.append-bottom-0 %p.settings-message.text-center.append-bottom-0
No variables found, add one with the form above. No variables found, add one with the form above.
- else - else
= render "projects/variables/table" = render "ci/variables/table"
%button.btn.btn-info.js-btn-toggle-reveal-values{ "data-status" => 'hidden' } Reveal Values %button.btn.btn-info.js-btn-toggle-reveal-values{ "data-status" => 'hidden' } Reveal Values
- page_title "Variables"
.row.prepend-top-default.append-bottom-default
.col-lg-3
= render "ci/variables/content"
.col-lg-9
%h5.prepend-top-0
Update variable
= render "ci/variables/form", btn_text: "Save variable"
...@@ -11,18 +11,18 @@ ...@@ -11,18 +11,18 @@
%th Protected %th Protected
%th %th
%tbody %tbody
- @project.variables.order_key_asc.each do |variable| - @variables.each do |variable|
- if variable.id? - if variable.id?
%tr %tr
%td.variable-key= variable.key %td.variable-key= variable.key
%td.variable-value{ "data-value" => variable.value }****** %td.variable-value{ "data-value" => variable.value }******
%td.variable-protected= Gitlab::Utils.boolean_to_yes_no(variable.protected) %td.variable-protected= Gitlab::Utils.boolean_to_yes_no(variable.protected)
%td.variable-menu %td.variable-menu
= link_to project_variable_path(@project, variable), class: "btn btn-transparent btn-variable-edit" do = link_to variable.edit_path, class: "btn btn-transparent btn-variable-edit" do
%span.sr-only %span.sr-only
Update Update
= icon("pencil") = icon("pencil")
= link_to project_variable_path(@project, variable), class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do = link_to variable.delete_path, class: "btn btn-transparent btn-variable-delete", method: :delete, data: { confirm: "Are you sure?" } do
%span.sr-only %span.sr-only
Remove Remove
= icon("trash") = icon("trash")
...@@ -12,3 +12,8 @@ ...@@ -12,3 +12,8 @@
= link_to projects_group_path(@group), title: 'Projects' do = link_to projects_group_path(@group), title: 'Projects' do
%span %span
Projects Projects
= nav_link(controller: :ci_cd) do
= link_to group_settings_ci_cd_path(@group), title: 'Pipelines' do
%span
Pipelines
- page_title "Pipelines"
= render "groups/settings_head"
= render 'ci/variables/index'
...@@ -3,6 +3,6 @@ ...@@ -3,6 +3,6 @@
= render "projects/settings/head" = render "projects/settings/head"
= render 'projects/runners/index' = render 'projects/runners/index'
= render 'projects/variables/index' = render 'ci/variables/index'
= render 'projects/triggers/index' = render 'projects/triggers/index'
= render 'projects/pipelines_settings/show' = render 'projects/pipelines_settings/show'
- page_title "Variables" = render 'ci/variables/show'
.row.prepend-top-default.append-bottom-default
.col-lg-3
= render "content"
.col-lg-9
%h5.prepend-top-0
Update variable
= render "form", btn_text: "Save variable"
---
title: Add Group secret variables
merge_request: 12582
author:
...@@ -23,6 +23,14 @@ scope(path: 'groups/*group_id', ...@@ -23,6 +23,14 @@ scope(path: 'groups/*group_id',
resources :labels, except: [:show] do resources :labels, except: [:show] do
post :toggle_subscription, on: :member post :toggle_subscription, on: :member
end end
scope path: '-' do
namespace :settings do
resource :ci_cd, only: [:show], controller: 'ci_cd'
end
resources :variables, only: [:index, :show, :update, :create, :destroy]
end
end end
scope(path: 'groups/*id', scope(path: 'groups/*id',
......
class CreateGroupVariablesTable < ActiveRecord::Migration
DOWNTIME = false
def up
create_table :ci_group_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 :group_id, null: false
t.boolean :protected, default: false, null: false
t.timestamps_with_timezone null: false
end
add_index :ci_group_variables, [:group_id, :key], unique: true
end
def down
drop_table :ci_group_variables
end
end
class AddForeignKeyToGroupVariables < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :ci_group_variables, :namespaces, column: :group_id
end
def down
remove_foreign_key :ci_group_variables, column: :group_id
end
end
...@@ -253,6 +253,20 @@ ActiveRecord::Schema.define(version: 20170703102400) do ...@@ -253,6 +253,20 @@ ActiveRecord::Schema.define(version: 20170703102400) do
add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree 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 add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree
create_table "ci_group_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 "group_id", null: false
t.boolean "protected", default: false, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "ci_group_variables", ["group_id", "key"], name: "index_ci_group_variables_on_group_id_and_key", unique: true, using: :btree
create_table "ci_pipeline_schedules", force: :cascade do |t| create_table "ci_pipeline_schedules", force: :cascade do |t|
t.string "description" t.string "description"
t.string "ref" t.string "ref"
...@@ -1550,6 +1564,7 @@ ActiveRecord::Schema.define(version: 20170703102400) do ...@@ -1550,6 +1564,7 @@ ActiveRecord::Schema.define(version: 20170703102400) 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_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", "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_builds", "projects", name: "fk_befce0568a", 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", "projects", name: "fk_8ead60fcc4", on_delete: :cascade
add_foreign_key "ci_pipeline_schedules", "users", column: "owner_id", name: "fk_9ea99f58d2", on_delete: :nullify add_foreign_key "ci_pipeline_schedules", "users", column: "owner_id", name: "fk_9ea99f58d2", on_delete: :nullify
add_foreign_key "ci_pipelines", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_3d34ab2e06", on_delete: :nullify add_foreign_key "ci_pipelines", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_3d34ab2e06", on_delete: :nullify
......
...@@ -10,7 +10,8 @@ The variables can be overwritten and they take precedence over each other in ...@@ -10,7 +10,8 @@ The variables can be overwritten and they take precedence over each other in
this order: this order:
1. [Trigger variables][triggers] (take precedence over all) 1. [Trigger variables][triggers] (take precedence over all)
1. [Secret variables](#secret-variables) or [protected secret variables](#protected-secret-variables) 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) 1. YAML-defined [job-level variables](../yaml/README.md#job-variables)
1. YAML-defined [global variables](../yaml/README.md#variables) 1. YAML-defined [global variables](../yaml/README.md#variables)
1. [Deployment variables](#deployment-variables) 1. [Deployment variables](#deployment-variables)
...@@ -142,23 +143,28 @@ script: ...@@ -142,23 +143,28 @@ script:
>**Notes:** >**Notes:**
- This feature requires GitLab Runner 0.4.0 or higher. - This feature requires GitLab Runner 0.4.0 or higher.
- Group-level secret variables added in GitLab 9.4.
- Be aware that secret variables are not masked, and their values can be shown - Be aware that secret variables are not masked, and their values can be shown
in the job logs if explicitly asked to do so. If your project is public or in the job logs if explicitly asked to do so. If your project is public or
internal, you can set the pipelines private from your project's Pipelines internal, you can set the pipelines private from your project's Pipelines
settings. Follow the discussion in issue [#13784][ce-13784] for masking the settings. Follow the discussion in issue [#13784][ce-13784] for masking the
secret variables. secret variables.
GitLab CI allows you to define per-project **secret variables** that are set in GitLab CI allows you to define per-project or per-group **secret variables**
the build environment. The secret variables are stored out of the repository that are set in the build environment. The secret variables are stored out of
(`.gitlab-ci.yml`) and are securely passed to GitLab Runner making them the repository (`.gitlab-ci.yml`) and are securely passed to GitLab Runner
available in the build environment. It's the recommended method to use for making them available in the build environment. It's the recommended method to
storing things like passwords, secret keys and credentials. use for storing things like passwords, secret keys and credentials.
Secret variables can be added by going to your project's Project-level secret variables can be added by going to your project's
**Settings ➔ Pipelines**, then finding the section called **Settings ➔ Pipelines**, then finding the section called **Secret variables**.
**Secret variables**.
Once you set them, they will be available for all subsequent pipelines. Likewise, group-level secret variables can be added by going to your group's
**Settings ➔ Pipelines**, then finding the section called **Secret variables**.
Any variables of [subgroups] will be inherited recursively.
Once you set them, they will be available for all subsequent pipelines. You can also
[protect your variables](#protected-secret-variables).
### Protected secret variables ### Protected secret variables
...@@ -434,3 +440,4 @@ export CI_REGISTRY_PASSWORD="longalfanumstring" ...@@ -434,3 +440,4 @@ export CI_REGISTRY_PASSWORD="longalfanumstring"
[shellexecutors]: https://docs.gitlab.com/runner/executors/ [shellexecutors]: https://docs.gitlab.com/runner/executors/
[triggered]: ../triggers/README.md [triggered]: ../triggers/README.md
[triggers]: ../triggers/README.md#pass-job-variables-to-a-trigger [triggers]: ../triggers/README.md#pass-job-variables-to-a-trigger
[subgroups]: ../../user/group/subgroups/index.md
...@@ -112,6 +112,7 @@ module Gitlab ...@@ -112,6 +112,7 @@ module Gitlab
# this group would not be accessible through `/groups/parent/activity` since # this group would not be accessible through `/groups/parent/activity` since
# this would map to the activity-page of its parent. # this would map to the activity-page of its parent.
GROUP_ROUTES = %w[ GROUP_ROUTES = %w[
-
activity activity
analytics analytics
audit_events audit_events
......
require 'spec_helper'
describe Groups::Settings::CiCdController do
let(:group) { create(:group) }
let(:user) { create(:user) }
before do
group.add_master(user)
sign_in(user)
end
describe 'GET #show' do
it 'renders show with 200 status code' do
get :show, group_id: group
expect(response).to have_http_status(200)
expect(response).to render_template(:show)
end
end
end
require 'spec_helper'
describe Groups::VariablesController do
let(:group) { create(:group) }
let(:user) { create(:user) }
before do
sign_in(user)
group.add_master(user)
end
describe 'POST #create' do
context 'variable is valid' do
it 'shows a success flash message' do
post :create, group_id: group, variable: { key: "one", value: "two" }
expect(flash[:notice]).to include 'Variable was successfully created.'
expect(response).to redirect_to(group_settings_ci_cd_path(group))
end
end
context 'variable is invalid' do
it 'renders show' do
post :create, group_id: group, variable: { key: "..one", value: "two" }
expect(response).to render_template("groups/variables/show")
end
end
end
describe 'POST #update' do
let(:variable) { create(:ci_group_variable) }
context 'updating a variable with valid characters' do
before do
group.variables << variable
end
it 'shows a success flash message' do
post :update, group_id: group,
id: variable.id, variable: { key: variable.key, value: 'two' }
expect(flash[:notice]).to include 'Variable was successfully updated.'
expect(response).to redirect_to(group_variables_path(group))
end
it 'renders the action #show if the variable key is invalid' do
post :update, group_id: group,
id: variable.id, variable: { key: '?', value: variable.value }
expect(response).to have_http_status(200)
expect(response).to render_template :show
end
end
end
end
...@@ -15,13 +15,13 @@ describe Projects::VariablesController do ...@@ -15,13 +15,13 @@ describe Projects::VariablesController do
post :create, namespace_id: project.namespace.to_param, project_id: project, post :create, namespace_id: project.namespace.to_param, project_id: project,
variable: { key: "one", value: "two" } variable: { key: "one", value: "two" }
expect(flash[:notice]).to include 'Variables were successfully updated.' expect(flash[:notice]).to include 'Variable was successfully created.'
expect(response).to redirect_to(project_settings_ci_cd_path(project)) expect(response).to redirect_to(project_settings_ci_cd_path(project))
end end
end end
context 'variable is invalid' do context 'variable is invalid' do
it 'shows an alert flash message' do it 'renders show' do
post :create, namespace_id: project.namespace.to_param, project_id: project, post :create, namespace_id: project.namespace.to_param, project_id: project,
variable: { key: "..one", value: "two" } variable: { key: "..one", value: "two" }
...@@ -35,7 +35,6 @@ describe Projects::VariablesController do ...@@ -35,7 +35,6 @@ describe Projects::VariablesController do
context 'updating a variable with valid characters' do context 'updating a variable with valid characters' do
before do before do
variable.project_id = project.id
project.variables << variable project.variables << variable
end end
......
FactoryGirl.define do
factory :ci_group_variable, class: Ci::GroupVariable do
sequence(:key) { |n| "VARIABLE_#{n}" }
value 'VARIABLE_VALUE'
trait(:protected) do
protected true
end
group factory: :group
end
end
require 'spec_helper'
feature 'Group variables', js: true do
let(:user) { create(:user) }
let(:group) { create(:group) }
background do
group.add_master(user)
gitlab_sign_in(user)
end
context 'when user creates a new variable' do
background do
visit group_settings_ci_cd_path(group)
fill_in 'variable_key', with: 'AAA'
fill_in 'variable_value', with: 'AAA123'
find(:css, "#variable_protected").set(true)
click_on 'Add new variable'
end
scenario 'user sees the created variable' do
page.within('.variables-table') do
expect(find(".variable-key")).to have_content('AAA')
expect(find(".variable-value")).to have_content('******')
expect(find(".variable-protected")).to have_content('Yes')
end
click_on 'Reveal Values'
page.within('.variables-table') do
expect(find(".variable-value")).to have_content('AAA123')
end
end
end
context 'when user edits a variable' do
background do
create(:ci_group_variable, key: 'AAA', value: 'AAA123', protected: true,
group: group)
visit group_settings_ci_cd_path(group)
page.within('.variable-menu') do
click_on 'Update'
end
fill_in 'variable_key', with: 'BBB'
fill_in 'variable_value', with: 'BBB123'
find(:css, "#variable_protected").set(false)
click_on 'Save variable'
end
scenario 'user sees the updated variable' do
page.within('.variables-table') do
expect(find(".variable-key")).to have_content('BBB')
expect(find(".variable-value")).to have_content('******')
expect(find(".variable-protected")).to have_content('No')
end
end
end
context 'when user deletes a variable' do
background do
create(:ci_group_variable, key: 'BBB', value: 'BBB123', protected: false,
group: group)
visit group_settings_ci_cd_path(group)
page.within('.variable-menu') do
page.accept_alert 'Are you sure?' do
click_on 'Remove'
end
end
end
scenario 'user does not see the deleted variable' do
expect(page).to have_no_css('.variables-table')
end
end
end
...@@ -24,7 +24,7 @@ describe 'Project variables', js: true do ...@@ -24,7 +24,7 @@ describe 'Project variables', js: true do
fill_in('variable_value', with: 'key value') fill_in('variable_value', with: 'key value')
click_button('Add new variable') click_button('Add new variable')
expect(page).to have_content('Variables were successfully updated.') expect(page).to have_content('Variable was successfully created.')
page.within('.variables-table') do page.within('.variables-table') do
expect(page).to have_content('key') expect(page).to have_content('key')
expect(page).to have_content('No') expect(page).to have_content('No')
...@@ -36,7 +36,7 @@ describe 'Project variables', js: true do ...@@ -36,7 +36,7 @@ describe 'Project variables', js: true do
fill_in('variable_value', with: '') fill_in('variable_value', with: '')
click_button('Add new variable') click_button('Add new variable')
expect(page).to have_content('Variables were successfully updated.') expect(page).to have_content('Variable was successfully created.')
page.within('.variables-table') do page.within('.variables-table') do
expect(page).to have_content('new_key') expect(page).to have_content('new_key')
end end
...@@ -48,7 +48,7 @@ describe 'Project variables', js: true do ...@@ -48,7 +48,7 @@ describe 'Project variables', js: true do
check('Protected') check('Protected')
click_button('Add new variable') click_button('Add new variable')
expect(page).to have_content('Variables were successfully updated.') expect(page).to have_content('Variable was successfully created.')
page.within('.variables-table') do page.within('.variables-table') do
expect(page).to have_content('key') expect(page).to have_content('key')
expect(page).to have_content('Yes') expect(page).to have_content('Yes')
...@@ -82,7 +82,7 @@ describe 'Project variables', js: true do ...@@ -82,7 +82,7 @@ describe 'Project variables', js: true do
it 'deletes variable' do it 'deletes variable' do
page.within('.variables-table') do page.within('.variables-table') do
find('.btn-variable-delete').click click_on 'Remove'
end end
expect(page).not_to have_selector('variables-table') expect(page).not_to have_selector('variables-table')
...@@ -90,7 +90,7 @@ describe 'Project variables', js: true do ...@@ -90,7 +90,7 @@ describe 'Project variables', js: true do
it 'edits variable' do it 'edits variable' do
page.within('.variables-table') do page.within('.variables-table') do
find('.btn-variable-edit').click click_on 'Update'
end end
expect(page).to have_content('Update variable') expect(page).to have_content('Update variable')
...@@ -104,7 +104,7 @@ describe 'Project variables', js: true do ...@@ -104,7 +104,7 @@ describe 'Project variables', js: true do
it 'edits variable with empty value' do it 'edits variable with empty value' do
page.within('.variables-table') do page.within('.variables-table') do
find('.btn-variable-edit').click click_on 'Update'
end end
expect(page).to have_content('Update variable') expect(page).to have_content('Update variable')
...@@ -117,7 +117,7 @@ describe 'Project variables', js: true do ...@@ -117,7 +117,7 @@ describe 'Project variables', js: true do
it 'edits variable to be protected' do it 'edits variable to be protected' do
page.within('.variables-table') do page.within('.variables-table') do
find('.btn-variable-edit').click click_on 'Update'
end end
expect(page).to have_content('Update variable') expect(page).to have_content('Update variable')
...@@ -132,7 +132,7 @@ describe 'Project variables', js: true do ...@@ -132,7 +132,7 @@ describe 'Project variables', js: true do
project.variables.first.update(protected: true) project.variables.first.update(protected: true)
page.within('.variables-table') do page.within('.variables-table') do
find('.btn-variable-edit').click click_on 'Update'
end end
expect(page).to have_content('Update variable') expect(page).to have_content('Update variable')
......
...@@ -1356,6 +1356,59 @@ describe Ci::Build, :models do ...@@ -1356,6 +1356,59 @@ describe Ci::Build, :models do
end end
end end
context 'when group secret variable is defined' do
let(:secret_variable) do
{ key: 'SECRET_KEY', value: 'secret_value', public: false }
end
let(:group) { create(:group, :access_requestable) }
before do
build.project.update(group: group)
create(:ci_group_variable,
secret_variable.slice(:key, :value).merge(group: group))
end
it { is_expected.to include(secret_variable) }
end
context 'when group protected variable is defined' do
let(:protected_variable) do
{ key: 'PROTECTED_KEY', value: 'protected_value', public: false }
end
let(:group) { create(:group, :access_requestable) }
before do
build.project.update(group: group)
create(:ci_group_variable,
:protected,
protected_variable.slice(:key, :value).merge(group: group))
end
context 'when the branch is protected' do
before do
create(:protected_branch, project: build.project, name: build.ref)
end
it { is_expected.to include(protected_variable) }
end
context 'when the tag is protected' do
before do
create(:protected_tag, project: build.project, name: build.ref)
end
it { is_expected.to include(protected_variable) }
end
context 'when the ref is not protected' do
it { is_expected.not_to include(protected_variable) }
end
end
context 'when build is for triggers' do context 'when build is for triggers' do
let(:trigger) { create(:ci_trigger, project: project) } let(:trigger) { create(:ci_trigger, project: project) }
let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) } let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
......
require 'spec_helper'
describe Ci::GroupVariable, models: true do
subject { build(:ci_group_variable) }
it { is_expected.to include_module(HasVariable) }
it { is_expected.to include_module(Presentable) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:group_id) }
describe '.unprotected' do
subject { described_class.unprotected }
context 'when variable is protected' do
before do
create(:ci_group_variable, :protected)
end
it 'returns nothing' do
is_expected.to be_empty
end
end
context 'when variable is not protected' do
let(:variable) { create(:ci_group_variable, protected: false) }
it 'returns the variable' do
is_expected.to contain_exactly(variable)
end
end
end
end
...@@ -3,10 +3,9 @@ require 'spec_helper' ...@@ -3,10 +3,9 @@ require 'spec_helper'
describe Ci::Variable, models: true do describe Ci::Variable, models: true do
subject { build(:ci_variable) } subject { build(:ci_variable) }
let(:secret_value) { 'secret' }
describe 'validations' do describe 'validations' do
it { is_expected.to include_module(HasVariable) } it { is_expected.to include_module(HasVariable) }
it { is_expected.to include_module(Presentable) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope) } it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope) }
end end
......
...@@ -13,6 +13,7 @@ describe Group, models: true do ...@@ -13,6 +13,7 @@ describe Group, models: true do
it { is_expected.to have_many(:shared_projects).through(:project_group_links) } it { is_expected.to have_many(:shared_projects).through(:project_group_links) }
it { is_expected.to have_many(:notification_settings).dependent(:destroy) } it { is_expected.to have_many(:notification_settings).dependent(:destroy) }
it { is_expected.to have_many(:labels).class_name('GroupLabel') } it { is_expected.to have_many(:labels).class_name('GroupLabel') }
it { is_expected.to have_many(:variables).class_name('Ci::GroupVariable') }
it { is_expected.to have_many(:uploads).dependent(:destroy) } it { is_expected.to have_many(:uploads).dependent(:destroy) }
it { is_expected.to have_one(:chat_team) } it { is_expected.to have_one(:chat_team) }
...@@ -418,4 +419,69 @@ describe Group, models: true do ...@@ -418,4 +419,69 @@ describe Group, models: true do
expect(calls).to eq 2 expect(calls).to eq 2
end end
end end
describe '#secret_variables_for' do
let(:project) { create(:empty_project, group: group) }
let!(:secret_variable) do
create(:ci_group_variable, value: 'secret', group: group)
end
let!(:protected_variable) do
create(:ci_group_variable, :protected, value: 'protected', group: group)
end
subject { group.secret_variables_for('ref', project) }
shared_examples 'ref is protected' do
it 'contains all the variables' do
is_expected.to contain_exactly(secret_variable, protected_variable)
end
end
context 'when the ref is not protected' do
before do
stub_application_setting(
default_branch_protection: Gitlab::Access::PROTECTION_NONE)
end
it 'contains only the secret variables' do
is_expected.to contain_exactly(secret_variable)
end
end
context 'when the ref is a protected branch' do
before do
create(:protected_branch, name: 'ref', project: project)
end
it_behaves_like 'ref is protected'
end
context 'when the ref is a protected tag' do
before do
create(:protected_tag, name: 'ref', project: project)
end
it_behaves_like 'ref is protected'
end
context 'when group has children' do
let!(:group_child) { create(:group, parent: group) }
let!(:variable_child) { create(:ci_group_variable, group: group_child) }
let!(:group_child_3) { create(:group, parent: group_child_2) }
let!(:variable_child_3) { create(:ci_group_variable, group: group_child_3) }
let!(:group_child_2) { create(:group, parent: group_child) }
let!(:variable_child_2) { create(:ci_group_variable, group: group_child_2) }
it 'returns all variables belong to the group and parent groups' do
expected_array1 = [protected_variable, secret_variable]
expected_array2 = [variable_child, variable_child_2, variable_child_3]
got_array = group_child_3.secret_variables_for('ref', project).to_a
expect(got_array.shift(2)).to contain_exactly(*expected_array1)
expect(got_array).to eq(expected_array2)
end
end
end
end end
require 'spec_helper'
describe Ci::GroupVariablePresenter do
include Gitlab::Routing.url_helpers
let(:group) { create(:group) }
let(:variable) { create(:ci_group_variable, group: group) }
subject(:presenter) do
described_class.new(variable)
end
it 'inherits from Gitlab::View::Presenter::Delegated' do
expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated)
end
describe '#initialize' do
it 'takes a variable and optional params' do
expect { presenter }.not_to raise_error
end
it 'exposes variable' do
expect(presenter.variable).to eq(variable)
end
it 'forwards missing methods to variable' do
expect(presenter.key).to eq(variable.key)
end
end
describe '#placeholder' do
subject { described_class.new(variable).placeholder }
it { is_expected.to eq('GROUP_VARIABLE') }
end
describe '#form_path' do
context 'when variable is persisted' do
subject { described_class.new(variable).form_path }
it { is_expected.to eq(group_variable_path(group, variable)) }
end
context 'when variable is not persisted' do
let(:variable) { build(:ci_group_variable, group: group) }
subject { described_class.new(variable).form_path }
it { is_expected.to eq(group_variables_path(group)) }
end
end
describe '#edit_path' do
subject { described_class.new(variable).edit_path }
it { is_expected.to eq(group_variable_path(group, variable)) }
end
describe '#delete_path' do
subject { described_class.new(variable).delete_path }
it { is_expected.to eq(group_variable_path(group, variable)) }
end
end
require 'spec_helper'
describe Ci::VariablePresenter do
include Gitlab::Routing.url_helpers
let(:project) { create(:empty_project) }
let(:variable) { create(:ci_variable, project: project) }
subject(:presenter) do
described_class.new(variable)
end
it 'inherits from Gitlab::View::Presenter::Delegated' do
expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated)
end
describe '#initialize' do
it 'takes a variable and optional params' do
expect { presenter }.not_to raise_error
end
it 'exposes variable' do
expect(presenter.variable).to eq(variable)
end
it 'forwards missing methods to variable' do
expect(presenter.key).to eq(variable.key)
end
end
describe '#placeholder' do
subject { described_class.new(variable).placeholder }
it { is_expected.to eq('PROJECT_VARIABLE') }
end
describe '#form_path' do
context 'when variable is persisted' do
subject { described_class.new(variable).form_path }
it { is_expected.to eq(project_variable_path(project, variable)) }
end
context 'when variable is not persisted' do
let(:variable) { build(:ci_variable, project: project) }
subject { described_class.new(variable).form_path }
it { is_expected.to eq(project_variables_path(project)) }
end
end
describe '#edit_path' do
subject { described_class.new(variable).edit_path }
it { is_expected.to eq(project_variable_path(project, variable)) }
end
describe '#delete_path' do
subject { described_class.new(variable).delete_path }
it { is_expected.to eq(project_variable_path(project, variable)) }
end
end
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