Commit 86a53b7e authored by Sri's avatar Sri

Backend for Service Account Creation flow

- `BaseController` for all `google_cloud` requests
    - `GoogleCloudController` extends `BaseController`

- `ServiceAccountsController`
    - Perform Google OAuth2 on access
    - Flash alert on decline

- `ServiceAccounts # index` renders ID placeholder to show form
    - User's GCP projects are fetched
    - If `cloudresourcemanager` API is enabled, else alert user
        - Alert if no GCP projects found for user
    - Render `gcp_projects` and project `environments` for vue component

- `ServiceAccounts # create` creates service accounts
    - If `iam` API is enabled, else alert user
    - Receives selected GCP project and Environment from form submission
    - Creates service account and service account key
    - Stores GCP project id, service account and key as project CI vars
    - Redirect to `project/google_cloud` main page
parent 823805b5
...@@ -280,7 +280,10 @@ class Clusters::ClustersController < Clusters::BaseController ...@@ -280,7 +280,10 @@ class Clusters::ClustersController < Clusters::BaseController
end end
def generate_gcp_authorize_url def generate_gcp_authorize_url
state = generate_session_key_redirect(clusterable.new_path(provider: :gcp).to_s) new_path = clusterable.new_path(provider: :gcp).to_s
error_path = @project ? project_clusters_path(@project) : new_path
state = generate_session_key_redirect(new_path, error_path)
@authorize_url = GoogleApi::CloudPlatform::Client.new( @authorize_url = GoogleApi::CloudPlatform::Client.new(
nil, callback_google_api_auth_url, nil, callback_google_api_auth_url,
...@@ -339,9 +342,10 @@ class Clusters::ClustersController < Clusters::BaseController ...@@ -339,9 +342,10 @@ class Clusters::ClustersController < Clusters::BaseController
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]
end end
def generate_session_key_redirect(uri) def generate_session_key_redirect(uri, error_uri)
GoogleApi::CloudPlatform::Client.new_session_key_for_redirect_uri do |key| GoogleApi::CloudPlatform::Client.new_session_key_for_redirect_uri do |key|
session[key] = uri session[key] = uri
session[:error_uri] = error_uri
end end
end end
......
...@@ -8,19 +8,36 @@ module GoogleApi ...@@ -8,19 +8,36 @@ module GoogleApi
feature_category :kubernetes_management feature_category :kubernetes_management
##
# handle the response from google after the user
# goes through authentication and authorization process
def callback def callback
token, expires_at = GoogleApi::CloudPlatform::Client redirect_uri = redirect_uri_from_session
.new(nil, callback_google_api_auth_url) ##
.get_token(params[:code]) # when the user declines authorizations
# `error` param is returned
session[GoogleApi::CloudPlatform::Client.session_key_for_token] = token if params[:error]
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = flash[:alert] = _('Google Cloud authorizations required')
expires_at.to_s redirect_uri = session[:error_uri]
##
# on success, the `code` param is returned
elsif params[:code]
token, expires_at = GoogleApi::CloudPlatform::Client
.new(nil, callback_google_api_auth_url)
.get_token(params[:code])
session[GoogleApi::CloudPlatform::Client.session_key_for_token] = token
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = expires_at.to_s
redirect_uri = redirect_uri_from_session
end
##
# or google may just timeout
rescue ::Faraday::TimeoutError, ::Faraday::ConnectionFailed rescue ::Faraday::TimeoutError, ::Faraday::ConnectionFailed
flash[:alert] = _('Timeout connecting to the Google API. Please try again.') flash[:alert] = _('Timeout connecting to the Google API. Please try again.')
##
# regardless, we redirect the user appropriately
ensure ensure
redirect_to redirect_uri_from_session redirect_to redirect_uri
end end
private private
......
# frozen_string_literal: true
class Projects::GoogleCloud::BaseController < Projects::ApplicationController
feature_category :google_cloud
before_action :admin_project_google_cloud!
before_action :google_oauth2_enabled!
before_action :feature_flag_enabled!
private
def admin_project_google_cloud!
access_denied! unless can?(current_user, :admin_project_google_cloud, project)
end
def google_oauth2_enabled!
config = Gitlab::Auth::OAuth::Provider.config_for('google_oauth2')
if config.app_id.blank? || config.app_secret.blank?
access_denied! 'This GitLab instance not configured for Google Oauth2.'
end
end
def feature_flag_enabled!
access_denied! unless Feature.enabled?(:incubation_5mp_google_cloud)
end
end
# frozen_string_literal: true
class Projects::GoogleCloud::ServiceAccountsController < Projects::GoogleCloud::BaseController
before_action :validate_gcp_token!
def index
@google_cloud_path = project_google_cloud_index_path(project)
google_api_client = GoogleApi::CloudPlatform::Client.new(token_in_session, nil)
gcp_projects = google_api_client.list_projects
if gcp_projects.empty?
@js_data = {}.to_json
render status: :unauthorized, template: 'projects/google_cloud/errors/no_gcp_projects'
else
@js_data = {
gcpProjects: gcp_projects,
environments: project.environments,
cancelPath: project_google_cloud_index_path(project)
}.to_json
end
rescue Google::Apis::ClientError => error
handle_gcp_error(error, project)
end
def create
google_api_client = GoogleApi::CloudPlatform::Client.new(token_in_session, nil)
service_accounts_service = GoogleCloud::ServiceAccountsService.new(project)
gcp_project = params[:gcp_project]
environment = params[:environment]
generated_name = "GitLab :: #{@project.name} :: #{environment}"
generated_desc = "GitLab generated service account for project '#{@project.name}' and environment '#{environment}'"
service_account = google_api_client.create_service_account(gcp_project, generated_name, generated_desc)
service_account_key = google_api_client.create_service_account_key(gcp_project, service_account.unique_id)
service_accounts_service.add_for_project(
environment,
service_account.project_id,
service_account.to_json,
service_account_key.to_json
)
redirect_to project_google_cloud_index_path(project), notice: _('Service account generated successfully')
rescue Google::Apis::ClientError, Google::Apis::ServerError, Google::Apis::AuthorizationError => error
handle_gcp_error(error, project)
end
private
def validate_gcp_token!
is_token_valid = GoogleApi::CloudPlatform::Client.new(token_in_session, nil)
.validate_token(expires_at_in_session)
return if is_token_valid
return_url = project_google_cloud_service_accounts_path(project)
state = generate_session_key_redirect(request.url, return_url)
@authorize_url = GoogleApi::CloudPlatform::Client.new(nil,
callback_google_api_auth_url,
state: state).authorize_url
redirect_to @authorize_url
end
def generate_session_key_redirect(uri, error_uri)
GoogleApi::CloudPlatform::Client.new_session_key_for_redirect_uri do |key|
session[key] = uri
session[:error_uri] = error_uri
end
end
def token_in_session
session[GoogleApi::CloudPlatform::Client.session_key_for_token]
end
def expires_at_in_session
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]
end
def handle_gcp_error(error, project)
Gitlab::ErrorTracking.track_exception(error, project_id: project.id)
@js_data = { error: error.to_s }.to_json
render status: :unauthorized, template: 'projects/google_cloud/errors/gcp_error'
end
end
# frozen_string_literal: true # frozen_string_literal: true
class Projects::GoogleCloudController < Projects::ApplicationController class Projects::GoogleCloudController < Projects::GoogleCloud::BaseController
feature_category :google_cloud
before_action :admin_project_google_cloud?
before_action :google_oauth2_enabled?
before_action :feature_flag_enabled?
def index def index
@js_data = { @js_data = {
serviceAccounts: GoogleCloud::ServiceAccountsService.new(project).find_for_project, serviceAccounts: GoogleCloud::ServiceAccountsService.new(project).find_for_project,
createServiceAccountUrl: '#mocked-url-create-service', createServiceAccountUrl: project_google_cloud_service_accounts_path(project),
emptyIllustrationUrl: ActionController::Base.helpers.image_path('illustrations/pipelines_empty.svg') emptyIllustrationUrl: ActionController::Base.helpers.image_path('illustrations/pipelines_empty.svg')
}.to_json }.to_json
end end
private
def admin_project_google_cloud?
access_denied! unless can?(current_user, :admin_project_google_cloud, project)
end
def google_oauth2_enabled?
config = Gitlab::Auth::OAuth::Provider.config_for('google_oauth2')
if config.app_id.blank? || config.app_secret.blank?
access_denied! 'This GitLab instance not configured for Google Oauth2.'
end
end
def feature_flag_enabled?
access_denied! unless Feature.enabled?(:incubation_5mp_google_cloud)
end
end end
...@@ -27,6 +27,24 @@ module GoogleCloud ...@@ -27,6 +27,24 @@ module GoogleCloud
end end
end end
def add_for_project(environment, gcp_project_id, service_account, service_account_key)
project_var_create_or_replace(
environment,
'GCP_PROJECT_ID',
gcp_project_id
)
project_var_create_or_replace(
environment,
'GCP_SERVICE_ACCOUNT',
service_account
)
project_var_create_or_replace(
environment,
'GCP_SERVICE_ACCOUNT_KEY',
service_account_key
)
end
private private
def group_vars_by_environment def group_vars_by_environment
...@@ -36,5 +54,12 @@ module GoogleCloud ...@@ -36,5 +54,12 @@ module GoogleCloud
grouped[variable.environment_scope][variable.key] = variable.value grouped[variable.environment_scope][variable.key] = variable.value
end end
end end
def project_var_create_or_replace(environment_scope, key, value)
params = { key: key, filter: { environment_scope: environment_scope } }
existing_variable = ::Ci::VariablesFinder.new(@project, params).execute.first
existing_variable.destroy if existing_variable
@project.variables.create!(key: key, value: value, environment_scope: environment_scope, protected: true)
end
end end
end end
- breadcrumb_title _('Google Cloud')
- page_title _('Google Cloud')
- @content_class = "limit-container-width" unless fluid_layout
#js-google-cloud-error-gcp-error{ data: @js_data }
- breadcrumb_title _('Google Cloud')
- page_title _('Google Cloud')
- @content_class = "limit-container-width" unless fluid_layout
#js-google-cloud-error-no-gcp-projects{ data: @js_data }
- add_to_breadcrumbs _('Google Cloud'), @google_cloud_path
- breadcrumb_title _('Service Account')
- page_title _('Service Account')
- @content_class = "limit-container-width" unless fluid_layout
= form_tag project_google_cloud_service_accounts_path(@project), method: 'post' do
#js-google-cloud-service-accounts{ data: @js_data }
...@@ -317,6 +317,10 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -317,6 +317,10 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :google_cloud, only: [:index] resources :google_cloud, only: [:index]
namespace :google_cloud do
resources :service_accounts, only: [:index, :create]
end
resources :environments, except: [:destroy] do resources :environments, except: [:destroy] do
member do member do
post :stop post :stop
......
# frozen_string_literal: true # frozen_string_literal: true
require 'securerandom'
require 'google/apis/compute_v1' require 'google/apis/compute_v1'
require 'google/apis/container_v1' require 'google/apis/container_v1'
require 'google/apis/container_v1beta1' require 'google/apis/container_v1beta1'
require 'google/apis/cloudbilling_v1' require 'google/apis/cloudbilling_v1'
require 'google/apis/cloudresourcemanager_v1' require 'google/apis/cloudresourcemanager_v1'
require 'google/apis/iam_v1'
module GoogleApi module GoogleApi
module CloudPlatform module CloudPlatform
...@@ -83,6 +85,51 @@ module GoogleApi ...@@ -83,6 +85,51 @@ module GoogleApi
m[1] if m m[1] if m
end end
def list_projects
result = []
service = Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService.new
service.authorization = access_token
response = service.fetch_all(items: :projects) do |token|
service.list_projects
end
# Google API results are paged by default, so we need to iterate through
response.each do |project|
result.append(project)
end
result
end
def create_service_account(gcp_project_id, display_name, description)
name = "projects/#{gcp_project_id}"
# initialize google iam service
service = Google::Apis::IamV1::IamService.new
service.authorization = access_token
# generate account id
random_account_id = "gitlab-" + SecureRandom.hex(11)
body_params = { account_id: random_account_id,
service_account: { display_name: display_name,
description: description } }
request_body = Google::Apis::IamV1::CreateServiceAccountRequest.new(**body_params)
service.create_service_account(name, request_body)
end
def create_service_account_key(gcp_project_id, service_account_id)
service = Google::Apis::IamV1::IamService.new
service.authorization = access_token
name = "projects/#{gcp_project_id}/serviceAccounts/#{service_account_id}"
request_body = Google::Apis::IamV1::CreateServiceAccountKeyRequest.new
service.create_service_account_key(name, request_body)
end
private private
def make_cluster_options(cluster_name, cluster_size, machine_type, legacy_abac, enable_addons) def make_cluster_options(cluster_name, cluster_size, machine_type, legacy_abac, enable_addons)
......
...@@ -16183,6 +16183,9 @@ msgstr "" ...@@ -16183,6 +16183,9 @@ msgstr ""
msgid "Google Cloud Project" msgid "Google Cloud Project"
msgstr "" msgstr ""
msgid "Google Cloud authorizations required"
msgstr ""
msgid "Google authentication is not %{link_start}properly configured%{link_end}. Ask your GitLab administrator if you want to use this service." msgid "Google authentication is not %{link_start}properly configured%{link_end}. Ask your GitLab administrator if you want to use this service."
msgstr "" msgstr ""
...@@ -31560,6 +31563,9 @@ msgstr "" ...@@ -31560,6 +31563,9 @@ msgstr ""
msgid "Service URL" msgid "Service URL"
msgstr "" msgstr ""
msgid "Service account generated successfully"
msgstr ""
msgid "Service ping is disabled in your configuration file, and cannot be enabled through this form." msgid "Service ping is disabled in your configuration file, and cannot be enabled through this form."
msgstr "" msgstr ""
......
...@@ -88,5 +88,26 @@ RSpec.describe GoogleApi::AuthorizationsController do ...@@ -88,5 +88,26 @@ RSpec.describe GoogleApi::AuthorizationsController do
it_behaves_like 'access denied' it_behaves_like 'access denied'
end end
context 'user logs in but declines authorizations' do
subject { get :callback, params: { error: 'xxx', state: state } }
let(:session_key) { 'session-key' }
let(:redirect_uri) { 'example.com' }
let(:error_uri) { 'error.com' }
let(:state) { session_key }
before do
session[GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(session_key)] = redirect_uri
session[:error_uri] = error_uri
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |instance|
allow(instance).to receive(:get_token).and_return([token, expires_at])
end
end
it 'redirects to error uri' do
expect(subject).to redirect_to(error_uri)
end
end
end end
end end
...@@ -209,4 +209,47 @@ RSpec.describe GoogleApi::CloudPlatform::Client do ...@@ -209,4 +209,47 @@ RSpec.describe GoogleApi::CloudPlatform::Client do
expect(subject.header).to eq({ 'User-Agent': 'GitLab/10.3 (GPN:GitLab;)' }) expect(subject.header).to eq({ 'User-Agent': 'GitLab/10.3 (GPN:GitLab;)' })
end end
end end
describe '#list_projects' do
subject { client.list_projects }
let(:list_of_projects) { [{}, {}, {}] }
let(:next_page_token) { nil }
let(:operation) { double('projects': list_of_projects, 'next_page_token': next_page_token) }
it 'calls Google Api CloudResourceManagerService#list_projects' do
expect_any_instance_of(Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService)
.to receive(:list_projects)
.and_return(operation)
is_expected.to eq(list_of_projects)
end
end
describe '#create_service_account' do
subject { client.create_service_account(spy, spy, spy) }
let(:operation) { double('Service Account') }
it 'calls Google Api IamService#create_service_account' do
expect_any_instance_of(Google::Apis::IamV1::IamService)
.to receive(:create_service_account)
.with(any_args)
.and_return(operation)
is_expected.to eq(operation)
end
end
describe '#create_service_account_key' do
subject { client.create_service_account_key(spy, spy) }
let(:operation) { double('Service Account Key') }
it 'class Google Api IamService#create_service_account_key' do
expect_any_instance_of(Google::Apis::IamV1::IamService)
.to receive(:create_service_account_key)
.with(any_args)
.and_return(operation)
is_expected.to eq(operation)
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
# Mock Types
MockGoogleOAuth2Credentials = Struct.new(:app_id, :app_secret)
MockServiceAccount = Struct.new(:project_id, :unique_id)
RSpec.describe Projects::GoogleCloud::ServiceAccountsController do
let_it_be(:project) { create(:project, :public) }
describe 'GET index' do
let_it_be(:url) { "#{project_google_cloud_service_accounts_path(project)}" }
let(:user_guest) { create(:user) }
let(:user_developer) { create(:user) }
let(:user_maintainer) { create(:user) }
let(:user_creator) { project.creator }
let(:unauthorized_members) { [user_guest, user_developer] }
let(:authorized_members) { [user_maintainer, user_creator] }
before do
project.add_guest(user_guest)
project.add_developer(user_developer)
project.add_maintainer(user_maintainer)
end
context 'when a public request is made' do
it 'returns not found on GET request' do
get url
expect(response).to have_gitlab_http_status(:not_found)
end
it 'returns not found on POST request' do
post url
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when unauthorized members make requests' do
it 'returns not found on GET request' do
unauthorized_members.each do |unauthorized_member|
sign_in(unauthorized_member)
get url
expect(response).to have_gitlab_http_status(:not_found)
end
end
it 'returns not found on POST request' do
unauthorized_members.each do |unauthorized_member|
sign_in(unauthorized_member)
post url
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'when authorized members make requests' do
it 'redirects on GET request' do
authorized_members.each do |authorized_member|
sign_in(authorized_member)
get url
expect(response).to redirect_to(assigns(:authorize_url))
end
end
it 'redirects on POST request' do
authorized_members.each do |authorized_member|
sign_in(authorized_member)
post url
expect(response).to redirect_to(assigns(:authorize_url))
end
end
context 'and user has successfully completed the google oauth2 flow' do
before do
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
allow(client).to receive(:validate_token).and_return(true)
allow(client).to receive(:list_projects).and_return([{}, {}, {}])
allow(client).to receive(:create_service_account).and_return(MockServiceAccount.new(123, 456))
allow(client).to receive(:create_service_account_key).and_return({})
end
end
it 'returns success on GET' do
authorized_members.each do |authorized_member|
sign_in(authorized_member)
get url
expect(response).to have_gitlab_http_status(:ok)
end
end
it 'returns success on POST' do
authorized_members.each do |authorized_member|
sign_in(authorized_member)
post url, params: { gcp_project: 'prj1', environment: 'env1' }
expect(response).to redirect_to(project_google_cloud_index_path(project))
end
end
end
context 'but google returns client error' do
before do
allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client|
allow(client).to receive(:validate_token).and_return(true)
allow(client).to receive(:list_projects).and_raise(Google::Apis::ClientError.new(''))
allow(client).to receive(:create_service_account).and_raise(Google::Apis::ClientError.new(''))
allow(client).to receive(:create_service_account_key).and_raise(Google::Apis::ClientError.new(''))
end
end
it 'renders gcp_error template on GET' do
authorized_members.each do |authorized_member|
sign_in(authorized_member)
get url
expect(response).to render_template(:gcp_error)
end
end
it 'renders gcp_error template on POST' do
authorized_members.each do |authorized_member|
sign_in(authorized_member)
post url, params: { gcp_project: 'prj1', environment: 'env1' }
expect(response).to render_template(:gcp_error)
end
end
end
context 'but gitlab instance is not configured for google oauth2' do
before do
unconfigured_google_oauth2 = MockGoogleOAuth2Credentials.new('', '')
allow(Gitlab::Auth::OAuth::Provider).to receive(:config_for)
.with('google_oauth2')
.and_return(unconfigured_google_oauth2)
end
it 'returns forbidden' do
authorized_members.each do |authorized_member|
sign_in(authorized_member)
get url
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
context 'but feature flag is disabled' do
before do
stub_feature_flags(incubation_5mp_google_cloud: false)
end
it 'returns not found' do
authorized_members.each do |authorized_member|
sign_in(authorized_member)
get url
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
end
end
...@@ -3,11 +3,11 @@ ...@@ -3,11 +3,11 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe GoogleCloud::ServiceAccountsService do RSpec.describe GoogleCloud::ServiceAccountsService do
let_it_be(:project) { create(:project) }
let(:service) { described_class.new(project) } let(:service) { described_class.new(project) }
describe 'find_for_project' do describe 'find_for_project' do
let_it_be(:project) { create(:project) }
context 'when a project does not have GCP service account vars' do context 'when a project does not have GCP service account vars' do
before do before do
project.variables.build(key: 'blah', value: 'foo', environment_scope: 'world') project.variables.build(key: 'blah', value: 'foo', environment_scope: 'world')
...@@ -21,13 +21,13 @@ RSpec.describe GoogleCloud::ServiceAccountsService do ...@@ -21,13 +21,13 @@ RSpec.describe GoogleCloud::ServiceAccountsService do
context 'when a project has GCP service account ci vars' do context 'when a project has GCP service account ci vars' do
before do before do
project.variables.build(environment_scope: '*', key: 'GCP_PROJECT_ID', value: 'prj1') project.variables.build(protected: true, environment_scope: '*', key: 'GCP_PROJECT_ID', value: 'prj1')
project.variables.build(environment_scope: '*', key: 'GCP_SERVICE_ACCOUNT_KEY', value: 'mock') project.variables.build(protected: true, environment_scope: '*', key: 'GCP_SERVICE_ACCOUNT_KEY', value: 'mock')
project.variables.build(environment_scope: 'staging', key: 'GCP_PROJECT_ID', value: 'prj2') project.variables.build(protected: true, environment_scope: 'staging', key: 'GCP_PROJECT_ID', value: 'prj2')
project.variables.build(environment_scope: 'staging', key: 'GCP_SERVICE_ACCOUNT', value: 'mock') project.variables.build(protected: true, environment_scope: 'staging', key: 'GCP_SERVICE_ACCOUNT', value: 'mock')
project.variables.build(environment_scope: 'production', key: 'GCP_PROJECT_ID', value: 'prj3') project.variables.build(protected: true, environment_scope: 'production', key: 'GCP_PROJECT_ID', value: 'prj3')
project.variables.build(environment_scope: 'production', key: 'GCP_SERVICE_ACCOUNT', value: 'mock') project.variables.build(protected: true, environment_scope: 'production', key: 'GCP_SERVICE_ACCOUNT', value: 'mock')
project.variables.build(environment_scope: 'production', key: 'GCP_SERVICE_ACCOUNT_KEY', value: 'mock') project.variables.build(protected: true, environment_scope: 'production', key: 'GCP_SERVICE_ACCOUNT_KEY', value: 'mock')
project.save! project.save!
end end
...@@ -55,4 +55,55 @@ RSpec.describe GoogleCloud::ServiceAccountsService do ...@@ -55,4 +55,55 @@ RSpec.describe GoogleCloud::ServiceAccountsService do
end end
end end
end end
describe 'add_for_project' do
let_it_be(:project) { create(:project) }
it 'saves GCP creds as project CI vars' do
service.add_for_project('env_1', 'gcp_prj_id_1', 'srv_acc_1', 'srv_acc_key_1')
service.add_for_project('env_2', 'gcp_prj_id_2', 'srv_acc_2', 'srv_acc_key_2')
list = service.find_for_project
aggregate_failures 'testing list of service accounts' do
expect(list.length).to eq(2)
expect(list.first[:environment]).to eq('env_1')
expect(list.first[:gcp_project]).to eq('gcp_prj_id_1')
expect(list.first[:service_account_exists]).to eq(true)
expect(list.first[:service_account_key_exists]).to eq(true)
expect(list.second[:environment]).to eq('env_2')
expect(list.second[:gcp_project]).to eq('gcp_prj_id_2')
expect(list.second[:service_account_exists]).to eq(true)
expect(list.second[:service_account_key_exists]).to eq(true)
end
end
it 'replaces previously stored CI vars with new CI vars' do
service.add_for_project('env_1', 'new_project', 'srv_acc_1', 'srv_acc_key_1')
list = service.find_for_project
aggregate_failures 'testing list of service accounts' do
expect(list.length).to eq(2)
# asserting that the first service account is replaced
expect(list.first[:environment]).to eq('env_1')
expect(list.first[:gcp_project]).to eq('new_project')
expect(list.first[:service_account_exists]).to eq(true)
expect(list.first[:service_account_key_exists]).to eq(true)
expect(list.second[:environment]).to eq('env_2')
expect(list.second[:gcp_project]).to eq('gcp_prj_id_2')
expect(list.second[:service_account_exists]).to eq(true)
expect(list.second[:service_account_key_exists]).to eq(true)
end
end
it 'underlying project CI vars must be protected' do
expect(project.variables.first.protected).to eq(true)
expect(project.variables.second.protected).to eq(true)
end
end
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