Skip to content
Snippets Groups Projects
Commit 2e3cbf7d authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent e44bb865
No related branches found
No related tags found
No related merge requests found
Showing
with 206 additions and 177 deletions
Loading
Loading
@@ -22,10 +22,7 @@ const {
mapState: mapSecurityGroupsState,
mapActions: mapSecurityGroupsActions,
} = createNamespacedHelpers('securityGroups');
const {
mapState: mapInstanceTypesState,
mapActions: mapInstanceTypesActions,
} = createNamespacedHelpers('instanceTypes');
const { mapState: mapInstanceTypesState } = createNamespacedHelpers('instanceTypes');
 
export default {
components: {
Loading
Loading
@@ -265,12 +262,10 @@ export default {
mounted() {
this.fetchRegions();
this.fetchRoles();
this.fetchInstanceTypes();
},
methods: {
...mapActions([
'createCluster',
'signOut',
'setClusterName',
'setEnvironmentScope',
'setKubernetesVersion',
Loading
Loading
@@ -290,7 +285,6 @@ export default {
...mapRolesActions({ fetchRoles: 'fetchItems' }),
...mapKeyPairsActions({ fetchKeyPairs: 'fetchItems' }),
...mapSecurityGroupsActions({ fetchSecurityGroups: 'fetchItems' }),
...mapInstanceTypesActions({ fetchInstanceTypes: 'fetchItems' }),
setRegionAndFetchVpcsAndKeyPairs(region) {
this.setRegion({ region });
this.setVpc({ vpc: null });
Loading
Loading
@@ -316,11 +310,6 @@ export default {
{{ s__('ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster') }}
</h2>
<div class="mb-3" v-html="kubernetesIntegrationHelpText"></div>
<div class="mb-3">
<button class="btn btn-link js-sign-out" @click.prevent="signOut()">
{{ s__('ClusterIntegration|Select a different AWS role') }}
</button>
</div>
<div class="form-group">
<label class="label-bold" for="eks-cluster-name">{{
s__('ClusterIntegration|Kubernetes cluster name')
Loading
Loading
Loading
Loading
@@ -28,7 +28,7 @@ export default {
},
data() {
return {
roleArn: '',
roleArn: this.$store.state.roleArn,
};
},
computed: {
Loading
Loading
Loading
Loading
@@ -12,20 +12,14 @@ export default el => {
kubernetesIntegrationHelpPath,
accountAndExternalIdsHelpPath,
createRoleArnHelpPath,
getRolesPath,
getRegionsPath,
getKeyPairsPath,
getVpcsPath,
getSubnetsPath,
getSecurityGroupsPath,
getInstanceTypesPath,
externalId,
accountId,
instanceTypes,
hasCredentials,
createRolePath,
createClusterPath,
signOutPath,
externalLinkIcon,
roleArn,
} = el.dataset;
 
return new Vue({
Loading
Loading
@@ -35,18 +29,10 @@ export default el => {
hasCredentials: parseBoolean(hasCredentials),
externalId,
accountId,
instanceTypes: JSON.parse(instanceTypes),
createRolePath,
createClusterPath,
signOutPath,
},
apiPaths: {
getRolesPath,
getRegionsPath,
getKeyPairsPath,
getVpcsPath,
getSubnetsPath,
getSecurityGroupsPath,
getInstanceTypesPath,
roleArn,
},
}),
components: {
Loading
Loading
import axios from '~/lib/utils/axios_utils';
export default apiPaths => ({
fetchRoles() {
return axios
.get(apiPaths.getRolesPath)
.then(({ data: { roles } }) =>
roles.map(({ role_name: name, arn: value }) => ({ name, value })),
);
},
fetchKeyPairs({ region }) {
return axios
.get(apiPaths.getKeyPairsPath, { params: { region } })
.then(({ data: { key_pairs: keyPairs } }) =>
keyPairs.map(({ key_name }) => ({ name: key_name, value: key_name })),
);
},
fetchRegions() {
return axios.get(apiPaths.getRegionsPath).then(({ data: { regions } }) =>
regions.map(({ region_name }) => ({
name: region_name,
value: region_name,
import AWS from 'aws-sdk/global';
import EC2 from 'aws-sdk/clients/ec2';
import IAM from 'aws-sdk/clients/iam';
const lookupVpcName = ({ Tags: tags, VpcId: id }) => {
const nameTag = tags.find(({ Key: key }) => key === 'Name');
return nameTag ? nameTag.Value : id;
};
export const DEFAULT_REGION = 'us-east-2';
export const setAWSConfig = ({ awsCredentials }) => {
AWS.config = {
...awsCredentials,
region: DEFAULT_REGION,
};
};
export const fetchRoles = () => {
const iam = new IAM();
return iam
.listRoles()
.promise()
.then(({ Roles: roles }) => roles.map(({ RoleName: name, Arn: value }) => ({ name, value })));
};
export const fetchRegions = () => {
const ec2 = new EC2();
return ec2
.describeRegions()
.promise()
.then(({ Regions: regions }) =>
regions.map(({ RegionName: name }) => ({
name,
value: name,
})),
);
},
fetchVpcs({ region }) {
return axios.get(apiPaths.getVpcsPath, { params: { region } }).then(({ data: { vpcs } }) =>
vpcs.map(({ vpc_id }) => ({
value: vpc_id,
name: vpc_id,
};
export const fetchKeyPairs = ({ region }) => {
const ec2 = new EC2({ region });
return ec2
.describeKeyPairs()
.promise()
.then(({ KeyPairs: keyPairs }) => keyPairs.map(({ KeyName: name }) => ({ name, value: name })));
};
export const fetchVpcs = ({ region }) => {
const ec2 = new EC2({ region });
return ec2
.describeVpcs()
.promise()
.then(({ Vpcs: vpcs }) =>
vpcs.map(vpc => ({
value: vpc.VpcId,
name: lookupVpcName(vpc),
})),
);
},
fetchSubnets({ vpc, region }) {
return axios
.get(apiPaths.getSubnetsPath, { params: { vpc_id: vpc, region } })
.then(({ data: { subnets } }) =>
subnets.map(({ subnet_id }) => ({ name: subnet_id, value: subnet_id })),
);
},
fetchSecurityGroups({ vpc, region }) {
return axios
.get(apiPaths.getSecurityGroupsPath, { params: { vpc_id: vpc, region } })
.then(({ data: { security_groups: securityGroups } }) =>
securityGroups.map(({ group_name: name, group_id: value }) => ({ name, value })),
);
},
fetchInstanceTypes() {
return axios
.get(apiPaths.getInstanceTypesPath)
.then(({ data: { instance_types: instanceTypes } }) =>
instanceTypes.map(({ instance_type_name }) => ({
name: instance_type_name,
value: instance_type_name,
})),
);
},
});
};
export const fetchSubnets = ({ vpc, region }) => {
const ec2 = new EC2({ region });
return ec2
.describeSubnets({
Filters: [
{
Name: 'vpc-id',
Values: [vpc],
},
],
})
.promise()
.then(({ Subnets: subnets }) => subnets.map(({ SubnetId: id }) => ({ value: id, name: id })));
};
export const fetchSecurityGroups = ({ region, vpc }) => {
const ec2 = new EC2({ region });
return ec2
.describeSecurityGroups({
Filters: [
{
Name: 'vpc-id',
Values: [vpc],
},
],
})
.promise()
.then(({ SecurityGroups: securityGroups }) =>
securityGroups.map(({ GroupName: name, GroupId: value }) => ({ name, value })),
);
};
import * as types from './mutation_types';
import { setAWSConfig } from '../services/aws_services_facade';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
 
const getErrorMessage = data => {
const errorKey = Object.keys(data)[0];
Loading
Loading
@@ -28,7 +30,7 @@ export const createRole = ({ dispatch, state: { createRolePath } }, payload) =>
role_arn: payload.roleArn,
role_external_id: payload.externalId,
})
.then(() => dispatch('createRoleSuccess'))
.then(({ data }) => dispatch('createRoleSuccess', convertObjectPropsToCamelCase(data)))
.catch(error => dispatch('createRoleError', { error }));
};
 
Loading
Loading
@@ -36,7 +38,8 @@ export const requestCreateRole = ({ commit }) => {
commit(types.REQUEST_CREATE_ROLE);
};
 
export const createRoleSuccess = ({ commit }) => {
export const createRoleSuccess = ({ commit }, awsCredentials) => {
setAWSConfig({ awsCredentials });
commit(types.CREATE_ROLE_SUCCESS);
};
 
Loading
Loading
@@ -117,9 +120,3 @@ export const setInstanceType = ({ commit }, payload) => {
export const setNodeCount = ({ commit }, payload) => {
commit(types.SET_NODE_COUNT, payload);
};
export const signOut = ({ commit, state: { signOutPath } }) =>
axios
.delete(signOutPath)
.then(() => commit(types.SIGN_OUT))
.catch(({ response: { data } }) => createFlash(getErrorMessage(data)));
Loading
Loading
@@ -3,11 +3,11 @@ import actions from './actions';
import mutations from './mutations';
import state from './state';
 
const createStore = fetchFn => ({
const createStore = ({ fetchFn, initialState }) => ({
actions: actions(fetchFn),
getters,
mutations,
state: state(),
state: Object.assign(state(), initialState || {}),
});
 
export default createStore;
Loading
Loading
@@ -6,12 +6,17 @@ import state from './state';
 
import clusterDropdownStore from './cluster_dropdown';
 
import awsServicesFactory from '../services/aws_services_facade';
import {
fetchRoles,
fetchRegions,
fetchKeyPairs,
fetchVpcs,
fetchSubnets,
fetchSecurityGroups,
} from '../services/aws_services_facade';
 
const createStore = ({ initialState, apiPaths }) => {
const awsServices = awsServicesFactory(apiPaths);
return new Vuex.Store({
const createStore = ({ initialState }) =>
new Vuex.Store({
actions,
getters,
mutations,
Loading
Loading
@@ -19,34 +24,33 @@ const createStore = ({ initialState, apiPaths }) => {
modules: {
roles: {
namespaced: true,
...clusterDropdownStore(awsServices.fetchRoles),
...clusterDropdownStore({ fetchFn: fetchRoles }),
},
regions: {
namespaced: true,
...clusterDropdownStore(awsServices.fetchRegions),
...clusterDropdownStore({ fetchFn: fetchRegions }),
},
keyPairs: {
namespaced: true,
...clusterDropdownStore(awsServices.fetchKeyPairs),
...clusterDropdownStore({ fetchFn: fetchKeyPairs }),
},
vpcs: {
namespaced: true,
...clusterDropdownStore(awsServices.fetchVpcs),
...clusterDropdownStore({ fetchFn: fetchVpcs }),
},
subnets: {
namespaced: true,
...clusterDropdownStore(awsServices.fetchSubnets),
...clusterDropdownStore({ fetchFn: fetchSubnets }),
},
securityGroups: {
namespaced: true,
...clusterDropdownStore(awsServices.fetchSecurityGroups),
...clusterDropdownStore({ fetchFn: fetchSecurityGroups }),
},
instanceTypes: {
namespaced: true,
...clusterDropdownStore(awsServices.fetchInstanceTypes),
...clusterDropdownStore({ initialState: { items: initialState.instanceTypes } }),
},
},
});
};
 
export default createStore;
Loading
Loading
@@ -13,7 +13,6 @@ export const SET_GITLAB_MANAGED_CLUSTER = 'SET_GITLAB_MANAGED_CLUSTER';
export const REQUEST_CREATE_ROLE = 'REQUEST_CREATE_ROLE';
export const CREATE_ROLE_SUCCESS = 'CREATE_ROLE_SUCCESS';
export const CREATE_ROLE_ERROR = 'CREATE_ROLE_ERROR';
export const SIGN_OUT = 'SIGN_OUT';
export const REQUEST_CREATE_CLUSTER = 'REQUEST_CREATE_CLUSTER';
export const CREATE_CLUSTER_SUCCESS = 'CREATE_CLUSTER_SUCCESS';
export const CREATE_CLUSTER_ERROR = 'CREATE_CLUSTER_ERROR';
Loading
Loading
@@ -60,7 +60,4 @@ export default {
state.isCreatingCluster = false;
state.createClusterError = error;
},
[types.SIGN_OUT](state) {
state.hasCredentials = false;
},
};
Loading
Loading
@@ -12,6 +12,8 @@ export default () => ({
accountId: '',
externalId: '',
 
roleArn: '',
clusterName: '',
environmentScope: '*',
kubernetesVersion,
Loading
Loading
Loading
Loading
@@ -228,10 +228,6 @@ class ApplicationController < ActionController::Base
end
end
 
def respond_201
head :created
end
def respond_422
head :unprocessable_entity
end
Loading
Loading
Loading
Loading
@@ -8,7 +8,7 @@ class Clusters::ClustersController < Clusters::BaseController
before_action :validate_gcp_token, only: [:new]
before_action :gcp_cluster, only: [:new]
before_action :user_cluster, only: [:new]
before_action :authorize_create_cluster!, only: [:new, :authorize_aws_role, :revoke_aws_role, :aws_proxy]
before_action :authorize_create_cluster!, only: [:new, :authorize_aws_role]
before_action :authorize_update_cluster!, only: [:update]
before_action :authorize_admin_cluster!, only: [:destroy, :clear_cache]
before_action :update_applications_status, only: [:cluster_status]
Loading
Loading
@@ -42,6 +42,7 @@ class Clusters::ClustersController < Clusters::BaseController
if params[:provider] == 'aws'
@aws_role = current_user.aws_role || Aws::Role.new
@aws_role.ensure_role_external_id!
@instance_types = load_instance_types.to_json
 
elsif params[:provider] == 'gcp'
redirect_to @authorize_url if @authorize_url && !@valid_gcp_token
Loading
Loading
@@ -145,21 +146,9 @@ class Clusters::ClustersController < Clusters::BaseController
end
 
def authorize_aws_role
role = current_user.build_aws_role(create_role_params)
role.save ? respond_201 : respond_422
end
def revoke_aws_role
current_user.aws_role&.destroy
head :no_content
end
def aws_proxy
response = Clusters::Aws::ProxyService.new(
current_user.aws_role,
params: params
response = Clusters::Aws::AuthorizeRoleService.new(
current_user,
params: aws_role_params
).execute
 
render json: response.body, status: response.status
Loading
Loading
@@ -268,7 +257,7 @@ class Clusters::ClustersController < Clusters::BaseController
)
end
 
def create_role_params
def aws_role_params
params.require(:cluster).permit(:role_arn, :role_external_id)
end
 
Loading
Loading
@@ -314,6 +303,19 @@ class Clusters::ClustersController < Clusters::BaseController
end
end
 
##
# Unfortunately the EC2 API doesn't provide a list of
# possible instance types. There is a workaround, using
# the Pricing API, but instead of requiring the
# user to grant extra permissions for this we use the
# values that validate the CloudFormation template.
def load_instance_types
stack_template = File.read(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml'))
instance_types = YAML.safe_load(stack_template).dig('Parameters', 'NodeInstanceType', 'AllowedValues')
instance_types.map { |type| Hash(name: type, value: type) }
end
def update_applications_status
@cluster.applications.each(&:schedule_status_update)
end
Loading
Loading
Loading
Loading
@@ -77,8 +77,10 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
 
return if handle_errors(result)
 
result_with_syntax_highlight = Gitlab::ErrorTracking::StackTraceHighlightDecorator.decorate(result[:latest_event])
render json: {
error: serialize_error_event(result[:latest_event])
error: serialize_error_event(result_with_syntax_highlight)
}
end
 
Loading
Loading
Loading
Loading
@@ -8,9 +8,11 @@ module Clusters
 
self.table_name = 'cluster_providers_aws'
 
DEFAULT_REGION = 'us-east-1'
belongs_to :cluster, inverse_of: :provider_aws, class_name: 'Clusters::Cluster'
 
default_value_for :region, 'us-east-1'
default_value_for :region, DEFAULT_REGION
default_value_for :num_nodes, 3
default_value_for :instance_type, 'm5.large'
 
Loading
Loading
Loading
Loading
@@ -9,7 +9,7 @@ class DeployKey < Key
 
scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) }
scope :are_public, -> { where(public: true) }
scope :with_projects, -> { includes(deploy_keys_projects: { project: [:route, :namespace] }) }
scope :with_projects, -> { includes(deploy_keys_projects: { project: [:route, namespace: :route] }) }
 
ignore_column :can_push, remove_after: '2019-12-15', remove_with: '12.6'
 
Loading
Loading
@@ -24,7 +24,7 @@ class DeployKey < Key
end
 
def almost_orphaned?
self.deploy_keys_projects.count == 1
self.deploy_keys_projects.size == 1
end
 
def destroyed_when_orphaned?
Loading
Loading
@@ -44,7 +44,11 @@ class DeployKey < Key
end
 
def deploy_keys_project_for(project)
deploy_keys_projects.find_by(project: project)
if association(:deploy_keys_projects).loaded?
deploy_keys_projects.find { |dkp| dkp.project_id.eql?(project&.id) }
else
deploy_keys_projects.find_by(project: project)
end
end
 
def projects_with_write_access
Loading
Loading
Loading
Loading
@@ -1001,7 +1001,7 @@ class User < ApplicationRecord
end
 
def project_deploy_keys
DeployKey.in_projects(authorized_projects.select(:id)).distinct(:id)
@project_deploy_keys ||= DeployKey.in_projects(authorized_projects.select(:id)).distinct(:id)
end
 
def highest_role
Loading
Loading
Loading
Loading
@@ -3,10 +3,7 @@
class DeployKeyPolicy < BasePolicy
with_options scope: :subject, score: 0
condition(:private_deploy_key) { @subject.private? }
# rubocop: disable CodeReuse/ActiveRecord
condition(:has_deploy_key) { @user.project_deploy_keys.exists?(id: @subject.id) }
# rubocop: enable CodeReuse/ActiveRecord
condition(:has_deploy_key) { @user.project_deploy_keys.any? { |pdk| pdk.id.eql?(@subject.id) } }
 
rule { anonymous }.prevent_all
 
Loading
Loading
Loading
Loading
@@ -29,18 +29,10 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
new_polymorphic_path([clusterable, :cluster], options)
end
 
def aws_api_proxy_path(resource)
polymorphic_path([clusterable, :clusters], action: :aws_proxy, resource: resource)
end
def authorize_aws_role_path
polymorphic_path([clusterable, :clusters], action: :authorize_aws_role)
end
 
def revoke_aws_role_path
polymorphic_path([clusterable, :clusters], action: :revoke_aws_role)
end
def create_user_clusters_path
polymorphic_path([clusterable, :clusters], action: :create_user)
end
Loading
Loading
Loading
Loading
@@ -67,16 +67,6 @@ class InstanceClusterablePresenter < ClusterablePresenter
authorize_aws_role_admin_clusters_path
end
 
override :revoke_aws_role_path
def revoke_aws_role_path
revoke_aws_role_admin_clusters_path
end
override :aws_api_proxy_path
def aws_api_proxy_path(resource)
aws_proxy_admin_clusters_path(resource: resource)
end
override :empty_state_help_text
def empty_state_help_text
s_('ClusterIntegration|Adding an integration will share the cluster across all projects.')
Loading
Loading
Loading
Loading
@@ -3,6 +3,8 @@
module Projects
module Settings
class DeployKeysPresenter < Gitlab::View::Presenter::Simple
include Gitlab::Utils::StrongMemoize
presents :project
delegate :size, to: :enabled_keys, prefix: true
delegate :size, to: :available_project_keys, prefix: true
Loading
Loading
@@ -13,37 +15,45 @@ module Projects
end
 
def enabled_keys
project.deploy_keys
strong_memoize(:enabled_keys) do
project.deploy_keys.with_projects
end
end
 
def available_keys
current_user
.accessible_deploy_keys
.id_not_in(enabled_keys.select(:id))
.with_projects
strong_memoize(:available_keys) do
current_user
.accessible_deploy_keys
.id_not_in(enabled_keys.select(:id))
.with_projects
end
end
 
def available_project_keys
current_user
.project_deploy_keys
.id_not_in(enabled_keys.select(:id))
.with_projects
strong_memoize(:available_project_keys) do
current_user
.project_deploy_keys
.id_not_in(enabled_keys.select(:id))
.with_projects
end
end
 
def available_public_keys
DeployKey
.are_public
.id_not_in(enabled_keys.select(:id))
.id_not_in(available_project_keys.select(:id))
.with_projects
strong_memoize(:available_public_keys) do
DeployKey
.are_public
.id_not_in(enabled_keys.select(:id))
.id_not_in(available_project_keys.select(:id))
.with_projects
end
end
 
def as_json
serializer = DeployKeySerializer.new # rubocop: disable CodeReuse/Serializer
opts = { user: current_user, project: project }
opts = { user: current_user, project: project, readable_project_ids: readable_project_ids }
 
{
enabled_keys: serializer.represent(enabled_keys.with_projects, opts),
enabled_keys: serializer.represent(enabled_keys, opts),
available_project_keys: serializer.represent(available_project_keys, opts),
public_keys: serializer.represent(available_public_keys, opts)
}
Loading
Loading
@@ -56,6 +66,26 @@ module Projects
def form_partial_path
'projects/deploy_keys/form'
end
private
# Caching all readable project ids for the user that are associated with the queried deploy keys
def readable_project_ids
strong_memoize(:readable_projects_by_id) do
Set.new(user_readable_project_ids)
end
end
# rubocop: disable CodeReuse/ActiveRecord
def user_readable_project_ids
project_ids = (available_keys + available_project_keys + available_public_keys)
.flat_map { |deploy_key| deploy_key.deploy_keys_projects.map(&:project_id) }
.compact
.uniq
current_user.authorized_projects(Gitlab::Access::GUEST).id_in(project_ids).pluck(:id)
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
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