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

Add latest changes from gitlab-org/gitlab@master

parent d41c040f
No related branches found
No related tags found
No related merge requests found
Showing
with 552 additions and 25 deletions
Loading
Loading
@@ -3,12 +3,12 @@
class Clusters::ClustersController < Clusters::BaseController
include RoutableActions
 
before_action :cluster, except: [:index, :new, :create_gcp, :create_user, :authorize_aws_role]
before_action :cluster, only: [:cluster_status, :show, :update, :destroy]
before_action :generate_gcp_authorize_url, only: [:new]
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]
before_action :authorize_create_cluster!, only: [:new, :authorize_aws_role, :revoke_aws_role, :aws_proxy]
before_action :authorize_update_cluster!, only: [:update]
before_action :authorize_admin_cluster!, only: [:destroy]
before_action :update_applications_status, only: [:cluster_status]
Loading
Loading
@@ -117,6 +117,19 @@ class Clusters::ClustersController < Clusters::BaseController
end
end
 
def create_aws
@aws_cluster = ::Clusters::CreateService
.new(current_user, create_aws_cluster_params)
.execute
.present(current_user: current_user)
if @aws_cluster.persisted?
head :created, location: @aws_cluster.show_path
else
render status: :unprocessable_entity, json: @aws_cluster.errors
end
end
def create_user
@user_cluster = ::Clusters::CreateService
.new(current_user, create_user_cluster_params)
Loading
Loading
@@ -140,6 +153,21 @@ class Clusters::ClustersController < Clusters::BaseController
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
).execute
render json: response.body, status: response.status
end
private
 
def destroy_params
Loading
Loading
@@ -200,6 +228,28 @@ class Clusters::ClustersController < Clusters::BaseController
)
end
 
def create_aws_cluster_params
params.require(:cluster).permit(
:enabled,
:name,
:environment_scope,
:managed,
provider_aws_attributes: [
:key_name,
:role_arn,
:region,
:vpc_id,
:instance_type,
:num_nodes,
:security_group_id,
subnet_ids: []
]).merge(
provider_type: :aws,
platform_type: :kubernetes,
clusterable: clusterable.subject
)
end
def create_user_cluster_params
params.require(:cluster).permit(
:enabled,
Loading
Loading
Loading
Loading
@@ -40,7 +40,7 @@ module ClustersHelper
def has_rbac_enabled?(cluster)
return cluster.platform_kubernetes_rbac? if cluster.platform_kubernetes
 
!cluster.provider.legacy_abac?
cluster.provider.has_rbac_enabled?
end
end
 
Loading
Loading
Loading
Loading
@@ -56,6 +56,7 @@ module Clusters
has_many :kubernetes_namespaces
 
accepts_nested_attributes_for :provider_gcp, update_only: true
accepts_nested_attributes_for :provider_aws, update_only: true
accepts_nested_attributes_for :platform_kubernetes, update_only: true
 
validates :name, cluster_name: true
Loading
Loading
@@ -73,6 +74,7 @@ module Clusters
delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true
delegate :knative_pre_installed?, to: :provider, allow_nil: true
 
delegate :active?, to: :platform_kubernetes, prefix: true, allow_nil: true
delegate :rbac?, to: :platform_kubernetes, prefix: true, allow_nil: true
Loading
Loading
@@ -258,10 +260,6 @@ module Clusters
end
end
 
def knative_pre_installed?
provider&.knative_pre_installed?
end
private
 
def unique_management_project_environment_scope
Loading
Loading
Loading
Loading
@@ -9,7 +9,6 @@ module Clusters
self.table_name = 'cluster_providers_aws'
 
belongs_to :cluster, inverse_of: :provider_aws, class_name: 'Clusters::Cluster'
belongs_to :created_by_user, class_name: 'User'
 
default_value_for :region, 'us-east-1'
default_value_for :num_nodes, 3
Loading
Loading
@@ -55,6 +54,18 @@ module Clusters
::Aws::Credentials.new(access_key_id, secret_access_key, session_token)
end
end
def has_rbac_enabled?
true
end
def knative_pre_installed?
false
end
def created_by_user
cluster.user
end
end
end
end
Loading
Loading
@@ -54,6 +54,10 @@ module Clusters
assign_attributes(operation_id: operation_id)
end
 
def has_rbac_enabled?
!legacy_abac
end
def knative_pre_installed?
cloud_run?
end
Loading
Loading
Loading
Loading
@@ -50,7 +50,7 @@ module DataFields
end
 
def data_fields_present?
data_fields.persisted?
data_fields.present?
rescue NotImplementedError
false
end
Loading
Loading
Loading
Loading
@@ -29,10 +29,18 @@ 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
@@ -41,6 +49,10 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
polymorphic_path([clusterable, :clusters], action: :create_gcp)
end
 
def create_aws_clusters_path
polymorphic_path([clusterable, :clusters], action: :create_aws)
end
def cluster_status_cluster_path(cluster, params = {})
raise NotImplementedError
end
Loading
Loading
Loading
Loading
@@ -52,11 +52,26 @@ class InstanceClusterablePresenter < ClusterablePresenter
create_gcp_admin_clusters_path
end
 
override :create_aws_clusters_path
def create_aws_clusters_path
create_aws_admin_clusters_path
end
override :authorize_aws_role_path
def authorize_aws_role_path
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,11 +3,13 @@
module Clusters
module Aws
class FetchCredentialsService
attr_reader :provider
attr_reader :provision_role
 
MissingRoleError = Class.new(StandardError)
 
def initialize(provider)
def initialize(provision_role, region:, provider: nil)
@provision_role = provision_role
@region = region
@provider = provider
end
 
Loading
Loading
@@ -24,12 +26,10 @@ module Clusters
 
private
 
def provision_role
provider.created_by_user.aws_role
end
attr_reader :provider, :region
 
def client
::Aws::STS::Client.new(credentials: gitlab_credentials, region: provider.region)
::Aws::STS::Client.new(credentials: gitlab_credentials, region: region)
end
 
def gitlab_credentials
Loading
Loading
@@ -45,7 +45,11 @@ module Clusters
end
 
def session_name
"gitlab-eks-cluster-#{provider.cluster_id}-user-#{provider.created_by_user_id}"
if provider.present?
"gitlab-eks-cluster-#{provider.cluster_id}-user-#{provision_role.user_id}"
else
"gitlab-eks-autofill-user-#{provision_role.user_id}"
end
end
end
end
Loading
Loading
Loading
Loading
@@ -21,7 +21,7 @@ module Clusters
end
rescue Clusters::Aws::FetchCredentialsService::MissingRoleError
provider.make_errored!('Amazon role is not configured')
rescue ::Aws::Errors::MissingCredentialsError, Settingslogic::MissingSetting
rescue ::Aws::Errors::MissingCredentialsError
provider.make_errored!('Amazon credentials are not configured')
rescue ::Aws::STS::Errors::ServiceError => e
provider.make_errored!("Amazon authentication failed; #{e.message}")
Loading
Loading
@@ -31,8 +31,16 @@ module Clusters
 
private
 
def provision_role
provider.created_by_user&.aws_role
end
def credentials
@credentials ||= Clusters::Aws::FetchCredentialsService.new(provider).execute
@credentials ||= Clusters::Aws::FetchCredentialsService.new(
provision_role,
provider: provider,
region: provider.region
).execute
end
 
def configure_provider_credentials
Loading
Loading
# frozen_string_literal: true
module Clusters
module Aws
class ProxyService
DEFAULT_REGION = 'us-east-1'
BadRequest = Class.new(StandardError)
Response = Struct.new(:status, :body)
def initialize(role, params:)
@role = role
@params = params
end
def execute
api_response = request_from_api!
Response.new(:ok, api_response.to_hash)
rescue *service_errors
Response.new(:bad_request, {})
end
private
attr_reader :role, :params
def request_from_api!
case requested_resource
when 'key_pairs'
ec2_client.describe_key_pairs
when 'instance_types'
instance_types
when 'roles'
iam_client.list_roles
when 'regions'
ec2_client.describe_regions
when 'security_groups'
raise BadRequest unless vpc_id.present?
ec2_client.describe_security_groups(vpc_filter)
when 'subnets'
raise BadRequest unless vpc_id.present?
ec2_client.describe_subnets(vpc_filter)
when 'vpcs'
ec2_client.describe_vpcs
else
raise BadRequest
end
end
def requested_resource
params[:resource]
end
def vpc_id
params[:vpc_id]
end
def region
params[:region] || DEFAULT_REGION
end
def vpc_filter
{
filters: [{
name: "vpc-id",
values: [vpc_id]
}]
}
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 instance_types
{
instance_types: cluster_stack_instance_types.map { |type| Hash(instance_type_name: type) }
}
end
def cluster_stack_instance_types
YAML.safe_load(stack_template).dig('Parameters', 'NodeInstanceType', 'AllowedValues')
end
def stack_template
File.read(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml'))
end
def ec2_client
::Aws::EC2::Client.new(client_options)
end
def iam_client
::Aws::IAM::Client.new(client_options)
end
def credentials
Clusters::Aws::FetchCredentialsService.new(role, region: region).execute
end
def client_options
{
credentials: credentials,
region: region,
http_open_timeout: 5,
http_read_timeout: 10
}
end
def service_errors
[
BadRequest,
Clusters::Aws::FetchCredentialsService::MissingRoleError,
::Aws::Errors::MissingCredentialsError,
::Aws::EC2::Errors::ServiceError,
::Aws::IAM::Errors::ServiceError,
::Aws::STS::Errors::ServiceError
]
end
end
end
end
.hidden.js-cluster-error.bs-callout.bs-callout-danger{ role: 'alert' }
= s_('ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine')
= s_('ClusterIntegration|Something went wrong while creating your Kubernetes cluster')
%p.js-error-reason
 
.hidden.js-cluster-creating.bs-callout.bs-callout-info{ role: 'alert' }
%span.spinner.spinner-dark.spinner-sm{ 'aria-label': 'Loading' }
%span.prepend-left-4= s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...')
%span.prepend-left-4= s_('ClusterIntegration|Kubernetes cluster is being created...')
 
.hidden.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' }
.col-11
Loading
Loading
@@ -19,4 +19,4 @@
%button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×"
 
.hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' }
= s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine.")
= s_("ClusterIntegration|Kubernetes cluster was successfully created.")
Loading
Loading
@@ -4,6 +4,15 @@
- else
.js-create-eks-cluster-form-container{ data: { 'gitlab-managed-cluster-help-path' => help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'),
'create-role-path' => clusterable.authorize_aws_role_path,
'sign-out-path' => clusterable.revoke_aws_role_path,
'create-cluster-path' => clusterable.create_aws_clusters_path,
'get-roles-path' => clusterable.aws_api_proxy_path('roles'),
'get-regions-path' => clusterable.aws_api_proxy_path('regions'),
'get-key-pairs-path' => clusterable.aws_api_proxy_path('key_pairs'),
'get-vpcs-path' => clusterable.aws_api_proxy_path('vpcs'),
'get-subnets-path' => clusterable.aws_api_proxy_path('subnets'),
'get-security-groups-path' => clusterable.aws_api_proxy_path('security_groups'),
'get-instance-types-path' => clusterable.aws_api_proxy_path('instance_types'),
'account-id' => Gitlab::CurrentSettings.eks_account_id,
'external-id' => @aws_role.role_external_id,
'kubernetes-integration-help-path' => help_page_path('user/project/clusters/index'),
Loading
Loading
---
title: Fix project service API 500 error
merge_request: 19367
author:
type: fixed
Loading
Loading
@@ -142,7 +142,13 @@ Rails.application.routes.draw do
collection do
post :create_user
post :create_gcp
post :create_aws
post :authorize_aws_role
delete :revoke_aws_role
scope :aws do
get 'api/:resource', to: 'clusters#aws_proxy', as: :aws_proxy
end
end
 
member do
Loading
Loading
Loading
Loading
@@ -3790,13 +3790,13 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr ""
 
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgid "ClusterIntegration|Kubernetes cluster is being created..."
msgstr ""
 
msgid "ClusterIntegration|Kubernetes cluster name"
msgstr ""
 
msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine."
msgid "ClusterIntegration|Kubernetes cluster was successfully created."
msgstr ""
 
msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way."
Loading
Loading
@@ -4039,7 +4039,7 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
 
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster"
msgstr ""
 
msgid "ClusterIntegration|Something went wrong while installing %{title}"
Loading
Loading
Loading
Loading
@@ -248,6 +248,69 @@ describe Admin::ClustersController do
end
end
 
describe 'POST #create_aws' do
let(:params) do
{
cluster: {
name: 'new-cluster',
provider_aws_attributes: {
key_name: 'key',
role_arn: 'arn:role',
region: 'region',
vpc_id: 'vpc',
instance_type: 'instance type',
num_nodes: 3,
security_group_id: 'security group',
subnet_ids: %w(subnet1 subnet2)
}
}
}
end
def post_create_aws
post :create_aws, params: params
end
it 'creates a new cluster' do
expect(ClusterProvisionWorker).to receive(:perform_async)
expect { post_create_aws }.to change { Clusters::Cluster.count }
.and change { Clusters::Providers::Aws.count }
cluster = Clusters::Cluster.instance_type.first
expect(response.status).to eq(201)
expect(response.location).to eq(admin_cluster_path(cluster))
expect(cluster).to be_aws
expect(cluster).to be_kubernetes
end
context 'params are invalid' do
let(:params) do
{
cluster: { name: '' }
}
end
it 'does not create a cluster' do
expect { post_create_aws }.not_to change { Clusters::Cluster.count }
expect(response.status).to eq(422)
expect(response.content_type).to eq('application/json')
expect(response.body).to include('is invalid')
end
end
describe 'security' do
before do
allow(WaitForClusterCreationWorker).to receive(:perform_in)
end
it { expect { post_create_aws }.to be_allowed_for(:admin) }
it { expect { post_create_aws }.to be_denied_for(:user) }
it { expect { post_create_aws }.to be_denied_for(:external) }
end
end
describe 'POST #create_user' do
let(:params) do
{
Loading
Loading
@@ -363,6 +426,27 @@ describe Admin::ClustersController do
end
end
 
describe 'DELETE revoke AWS role for EKS cluster' do
let!(:role) { create(:aws_role, user: admin) }
def go
delete :revoke_aws_role
end
it 'deletes the Aws::Role record' do
expect { go }.to change { Aws::Role.count }
expect(response.status).to eq 204
expect(admin.reload_aws_role).to be_nil
end
describe 'security' do
it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
end
describe 'GET #cluster_status' do
let(:cluster) { create(:cluster, :providing_by_gcp, :instance) }
 
Loading
Loading
Loading
Loading
@@ -372,6 +372,74 @@ describe Groups::ClustersController do
end
end
 
describe 'POST #create_aws' do
let(:params) do
{
cluster: {
name: 'new-cluster',
provider_aws_attributes: {
key_name: 'key',
role_arn: 'arn:role',
region: 'region',
vpc_id: 'vpc',
instance_type: 'instance type',
num_nodes: 3,
security_group_id: 'security group',
subnet_ids: %w(subnet1 subnet2)
}
}
}
end
def post_create_aws
post :create_aws, params: params.merge(group_id: group)
end
it 'creates a new cluster' do
expect(ClusterProvisionWorker).to receive(:perform_async)
expect { post_create_aws }.to change { Clusters::Cluster.count }
.and change { Clusters::Providers::Aws.count }
cluster = group.clusters.first
expect(response.status).to eq(201)
expect(response.location).to eq(group_cluster_path(group, cluster))
expect(cluster).to be_aws
expect(cluster).to be_kubernetes
end
context 'params are invalid' do
let(:params) do
{
cluster: { name: '' }
}
end
it 'does not create a cluster' do
expect { post_create_aws }.not_to change { Clusters::Cluster.count }
expect(response.status).to eq(422)
expect(response.content_type).to eq('application/json')
expect(response.body).to include('is invalid')
end
end
describe 'security' do
before do
allow(WaitForClusterCreationWorker).to receive(:perform_in)
end
it { expect { post_create_aws }.to be_allowed_for(:admin) }
it { expect { post_create_aws }.to be_allowed_for(:owner).of(group) }
it { expect { post_create_aws }.to be_allowed_for(:maintainer).of(group) }
it { expect { post_create_aws }.to be_denied_for(:developer).of(group) }
it { expect { post_create_aws }.to be_denied_for(:reporter).of(group) }
it { expect { post_create_aws }.to be_denied_for(:guest).of(group) }
it { expect { post_create_aws }.to be_denied_for(:user) }
it { expect { post_create_aws }.to be_denied_for(:external) }
end
end
describe 'POST authorize AWS role for EKS cluster' do
let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' }
let(:role_external_id) { '12345' }
Loading
Loading
@@ -422,6 +490,32 @@ describe Groups::ClustersController do
end
end
 
describe 'DELETE revoke AWS role for EKS cluster' do
let!(:role) { create(:aws_role, user: user) }
def go
delete :revoke_aws_role, params: { group_id: group }
end
it 'deletes the Aws::Role record' do
expect { go }.to change { Aws::Role.count }
expect(response.status).to eq 204
expect(user.reload_aws_role).to be_nil
end
describe 'security' do
it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(group) }
it { expect { go }.to be_allowed_for(:maintainer).of(group) }
it { expect { go }.to be_denied_for(:developer).of(group) }
it { expect { go }.to be_denied_for(:reporter).of(group) }
it { expect { go }.to be_denied_for(:guest).of(group) }
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
end
describe 'GET cluster_status' do
let(:cluster) { create(:cluster, :providing_by_gcp, cluster_type: :group_type, groups: [group]) }
 
Loading
Loading
Loading
Loading
@@ -373,6 +373,74 @@ describe Projects::ClustersController do
end
end
 
describe 'POST #create_aws' do
let(:params) do
{
cluster: {
name: 'new-cluster',
provider_aws_attributes: {
key_name: 'key',
role_arn: 'arn:role',
region: 'region',
vpc_id: 'vpc',
instance_type: 'instance type',
num_nodes: 3,
security_group_id: 'security group',
subnet_ids: %w(subnet1 subnet2)
}
}
}
end
def post_create_aws
post :create_aws, params: params.merge(namespace_id: project.namespace, project_id: project)
end
it 'creates a new cluster' do
expect(ClusterProvisionWorker).to receive(:perform_async)
expect { post_create_aws }.to change { Clusters::Cluster.count }
.and change { Clusters::Providers::Aws.count }
cluster = project.clusters.first
expect(response.status).to eq(201)
expect(response.location).to eq(project_cluster_path(project, cluster))
expect(cluster).to be_aws
expect(cluster).to be_kubernetes
end
context 'params are invalid' do
let(:params) do
{
cluster: { name: '' }
}
end
it 'does not create a cluster' do
expect { post_create_aws }.not_to change { Clusters::Cluster.count }
expect(response.status).to eq(422)
expect(response.content_type).to eq('application/json')
expect(response.body).to include('is invalid')
end
end
describe 'security' do
before do
allow(WaitForClusterCreationWorker).to receive(:perform_in)
end
it { expect { post_create_aws }.to be_allowed_for(:admin) }
it { expect { post_create_aws }.to be_allowed_for(:owner).of(project) }
it { expect { post_create_aws }.to be_allowed_for(:maintainer).of(project) }
it { expect { post_create_aws }.to be_denied_for(:developer).of(project) }
it { expect { post_create_aws }.to be_denied_for(:reporter).of(project) }
it { expect { post_create_aws }.to be_denied_for(:guest).of(project) }
it { expect { post_create_aws }.to be_denied_for(:user) }
it { expect { post_create_aws }.to be_denied_for(:external) }
end
end
describe 'POST authorize AWS role for EKS cluster' do
let(:role_arn) { 'arn:aws:iam::123456789012:role/role-name' }
let(:role_external_id) { '12345' }
Loading
Loading
@@ -423,6 +491,32 @@ describe Projects::ClustersController do
end
end
 
describe 'DELETE revoke AWS role for EKS cluster' do
let!(:role) { create(:aws_role, user: user) }
def go
delete :revoke_aws_role, params: { namespace_id: project.namespace, project_id: project }
end
it 'deletes the Aws::Role record' do
expect { go }.to change { Aws::Role.count }
expect(response.status).to eq 204
expect(user.reload_aws_role).to be_nil
end
describe 'security' do
it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(project) }
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
it { expect { go }.to be_denied_for(:developer).of(project) }
it { expect { go }.to be_denied_for(:reporter).of(project) }
it { expect { go }.to be_denied_for(:guest).of(project) }
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
end
describe 'GET cluster_status' do
let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
 
Loading
Loading
Loading
Loading
@@ -3,7 +3,6 @@
FactoryBot.define do
factory :cluster_provider_aws, class: Clusters::Providers::Aws do
association :cluster, platform_type: :kubernetes, provider_type: :aws
created_by_user factory: :user
 
role_arn { 'arn:aws:iam::123456789012:role/role-name' }
vpc_id { 'vpc-00000000000000000' }
Loading
Loading
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