Skip to content
Snippets Groups Projects
Commit ae549219 authored by baldwinlouie's avatar baldwinlouie Committed by Yas Naoi
Browse files

Issue #3402993 by baldwinlouie, yas: Update tenant creation CFn in OpenStackProvider module

parent a5c0e3eb
Branches
Tags
1 merge request!36Added support for distributed Cloud Orchestrator installation CFn.
Pipeline #56992 passed with warnings
Showing
with 1056 additions and 148 deletions
......@@ -6,7 +6,6 @@ dependencies:
- field.field.tenant.openstack.field_aws_cloud_stack
- field.field.tenant.openstack.field_aws_region
- field.field.tenant.openstack.field_bearer_token
- field.field.tenant.openstack.field_billing_information
- field.field.tenant.openstack.field_city
- field.field.tenant.openstack.field_cloud_orchestrator_url
- field.field.tenant.openstack.field_country
......@@ -14,10 +13,9 @@ dependencies:
- field.field.tenant.openstack.field_openstack_region
- field.field.tenant.openstack.field_stack_id
- field.field.tenant.openstack.field_status
- field.field.tenant.openstack.field_subdomain
- field.field.tenant.openstack.field_username
- field.field.tenant.openstack.field_zoom_level
module:
- text
enforced:
module:
- openstack_provider
......@@ -26,17 +24,9 @@ targetEntityType: tenant
bundle: openstack
mode: default
content:
field_billing_information:
type: text_textarea
weight: 7
region: content
settings:
rows: 5
placeholder: ''
third_party_settings: { }
field_city:
type: string_textfield
weight: 5
weight: 6
region: content
settings:
size: 60
......@@ -44,7 +34,7 @@ content:
third_party_settings: { }
field_country:
type: options_select
weight: 4
weight: 5
region: content
settings: { }
third_party_settings: { }
......@@ -62,6 +52,14 @@ content:
region: content
settings: { }
third_party_settings: { }
field_subdomain:
type: string_textfield
weight: 4
region: content
settings:
size: 60
placeholder: ''
third_party_settings: { }
field_username:
type: string_textfield
weight: 1
......@@ -72,7 +70,7 @@ content:
third_party_settings: { }
field_zoom_level:
type: options_select
weight: 6
weight: 7
region: content
settings: { }
third_party_settings: { }
......
......@@ -6,7 +6,6 @@ dependencies:
- field.field.tenant.openstack.field_aws_cloud_stack
- field.field.tenant.openstack.field_aws_region
- field.field.tenant.openstack.field_bearer_token
- field.field.tenant.openstack.field_billing_information
- field.field.tenant.openstack.field_city
- field.field.tenant.openstack.field_cloud_orchestrator_url
- field.field.tenant.openstack.field_country
......@@ -14,6 +13,7 @@ dependencies:
- field.field.tenant.openstack.field_openstack_region
- field.field.tenant.openstack.field_stack_id
- field.field.tenant.openstack.field_status
- field.field.tenant.openstack.field_subdomain
- field.field.tenant.openstack.field_username
- field.field.tenant.openstack.field_zoom_level
module:
......@@ -43,13 +43,6 @@ content:
third_party_settings: { }
weight: 6
region: content
field_billing_information:
type: text_default
label: inline
settings: { }
third_party_settings: { }
weight: 1
region: content
field_cloud_orchestrator_url:
type: string
label: inline
......@@ -95,5 +88,6 @@ hidden:
field_openstack_region: true
field_stack_id: true
field_status: true
field_subdomain: true
field_zoom_level: true
uid: true
......@@ -3,19 +3,18 @@ status: true
dependencies:
config:
- facade.tenant_type.openstack
- field.storage.tenant.field_billing_information
- field.storage.tenant.field_subdomain
module:
- text
- openstack_provider
id: tenant.openstack.field_billing_information
field_name: field_billing_information
id: tenant.openstack.field_subdomain
field_name: field_subdomain
entity_type: tenant
bundle: openstack
label: 'Billing Information'
description: ''
required: false
label: Subdomain
description: 'The subdomain to use with this tenant. If the subdomain is domain1.example.com, only enter domain1'
required: true
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: text_long
field_type: string
......@@ -2,17 +2,20 @@ langcode: en
status: true
dependencies:
module:
- cloud
- openstack_provider
- text
enforced:
module:
- openstack_provider
id: tenant.field_billing_information
field_name: field_billing_information
id: tenant.field_subdomain
field_name: field_subdomain
entity_type: tenant
type: text_long
settings: { }
module: text
type: string
settings:
max_length: 255
case_sensitive: false
is_ascii: false
module: core
locked: false
cardinality: 1
translatable: true
......
target_provider: ''
public_subnet_1: ''
private_subnet_1: ''
private_subnet_2: ''
instance_type: 't3.medium'
mysql_username: 'administrator'
database_name: 'cloud_orchestrator'
rds_instance_type: 'db.t2.micro'
db_allocated_storage: '24'
drupal_timezone: 'America/Los_Angeles'
drupal_site_email: 'noreply@airgap.host'
drupal_site_name: 'Cloud Orchestrator'
cloud_orchestrator_version: '6.x-dev'
creation_timeout: 'PT30M'
docker_file_url: 'https://git.drupalcode.org/project/facade/-/raw/1.0.x/modules/tenants/openstack_provider/deployments/docker/Dockerfile'
rds_version: '10.5.19'
autocreate_projects: false
additional_roles: ''
rds_instance_secret_name: ''
ecs_cluster_arn: ''
image_uri: ''
installation_lambda_arn: ''
ecs_task_definition_arn: ''
ecs_security_group: ''
facade_task_definition_container_name: 'facade'
efs_id: ''
efs_access_point_id: ''
load_balancer_dns_name: ''
github_token: ''
drupal_enduser_username: 'cloud_end_user_admin'
drupal_enduser_email: ''
use_spa_ui: false
country: ''
city: ''
welcome_subject: 'Cloud Orchestrator is ready for use'
welcome_message: 'Congratulations! Cloud Orchestrator installed successfully. It is ready to use.\r\n\r\nPlease use this link to login and set your password.\r\n${LOGIN_LINK}\r\n\r\nYour username is: ${CO_ADMIN_ID}. You can access Cloud Orchestrator at: https://${DNS_NAME}\r\n\r\nCloud Orchestrator team.'
enable_smtp: true
smtp_secret_name: ''
memcache_address: ''
memcache_port: ''
hosted_zone_name: ''
lambda_timeout: '600'
disable_ssl_verify: false
openstack_provider.settings:
type: config_object
mapping:
target_provider:
type: string
vpc:
type: string
public_subnet_1:
type: string
private_subnet_1:
drupal_timezone:
type: string
private_subnet_2:
drupal_site_email:
type: string
instance_type:
drupal_site_name:
type: string
mysql_username:
cloud_orchestrator_version:
type: string
database_name:
selectable_regions:
type: sequence
sequence:
- type: string
zoom_level:
type: string
rds_instance_type:
autocreate_projects:
type: boolean
additional_roles:
type: string
rds_version:
rds_instance_secret_name:
type: string
db_allocated_storage:
ecs_cluster_arn:
type: string
drupal_timezone:
image_uri:
type: string
drupal_site_email:
installation_lambda_arn:
type: string
drupal_site_name:
ecs_task_definition_arn:
type: string
cloud_orchestrator_version:
ecs_security_group:
type: string
creation_timeout:
facade_task_definition_container_name:
type: string
docker_file_url:
efs_id:
type: string
efs_access_point_id:
type: string
load_balancer_dns_name:
type: string
github_token:
type: string
drupal_enduser_username:
type: string
drupal_enduser_email:
type: string
use_spa_ui:
type: string
selectable_regions:
type: sequence
sequence:
- type: string
country:
type: string
city:
type: string
zoom_level:
welcome_subject:
type: string
autocreate_projects:
type: boolean
additional_roles:
welcome_message:
type: string
enable_smtp:
type: string
smtp_secret_name:
type: string
memcache_address:
type: string
memcache_port:
type: string
hosted_zone_name:
type: string
lambda_timeout:
type: string
disable_ssl_verify:
type: boolean
AWSTemplateFormatVersion: 2010-09-09
Description: Cloud Orchestrator Installer - Shared ECS, RDS, ElastiCache
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
-
Label:
default: Stack Prefix
Parameters:
- StackPrefix
-
Label:
default: RDS
Parameters:
- RdsInstanceSecretName
-
Label:
default: ECS Service
Parameters:
- EcsClusterArn
- ImageUri
- InstallationLambdaArn
- EcsTaskDefinitionArn
- EcsSecurityGroup
- FacadeTaskDefinitionContainerName
-
Label:
default: EFS
Parameters:
- EfsId
- EfsAccessPointId
-
Label:
default: Network
Parameters:
- PublicSubnetId
-
Label:
default: Load Balancer
Parameters:
- DNSName
-
Label:
default: Drupal
Parameters:
- GitHubToken
- DrupalSiteName
- DrupalTimezone
- DrupalUserName
- DrupalEmail
- DrupalEndUserUserName
- DrupalEndUserEmail
- UseSpaUI
- JSONData1
- JSONData2
- JSONData3
- Country
- City
- BearerToken
- CloudOrchestratorVersion
- WelcomeSubject
- WelcomeMessage
- EnableSmtp
- SmtpSecretName
-
Label:
default: ElastiCache
Parameters:
- MemcacheAddress
- MemcachePort
-
Label:
default: Route53
Parameters:
- Subdomain
- HostedZoneName
-
Label:
default: Lambda
Parameters:
- LambdaTimeout
Parameters:
StackPrefix:
Type: String
Description: A prefix to append to resource names/IDs. Must be between 1 and 20 characters
and only contain alphanumeric characters and hyphens.
MinLength: '1'
MaxLength: '20'
AllowedPattern: '[a-zA-Z0-9\\-]*'
# RDS Parameters
RdsInstanceSecretName:
Type: String
Description: The secret name containing RDS configuration information.
DatabaseName:
Description: The name of the database. Must be between 4 and 32 characters and
only contain alphanumeric characters and underscores.
Type: String
AllowedPattern: '[a-zA-Z0-9_]*'
MinLength: '4'
MaxLength: '64'
ConstraintDescription: Your database name must be between 4 and 32 characters
and contain alphanumeric characters and underscores.
# Drupal Parameters
DrupalUserName:
Default: cloud_admin
Description: The Drupal admin account username. Must be between 5 and 16 characters
and only contain alphanumeric characters and underscores.
Type: String
MinLength: '5'
MaxLength: '60'
AllowedPattern: '[a-zA-Z][a-zA-Z0-9_]*'
NoEcho: 'false'
ConstraintDescription: Must only contain alphanumeric characters, underscores
and must be between 5 and 16 characters long.
DrupalEmail:
Description: Drupal site administrator email.
Type: String
AllowedPattern: ^[\w_.+-]+@[\w-]+\.[-.\w]+$
ConstraintDescription: Must be a valid email address.
DrupalEndUserUserName:
Default: cloud_end_user_admin
Description: The Drupal end user admin account username. Must be between 5 and 16 characters
and only contain alphanumeric characters and underscores.
Type: String
MinLength: '5'
MaxLength: '60'
AllowedPattern: '[a-zA-Z][a-zA-Z0-9_]*'
NoEcho: 'false'
ConstraintDescription: Must only contain alphanumeric characters, underscores
and must be between 5 and 16 characters long.
DrupalEndUserEmail:
Description: Drupal site end user administrator email.
Type: String
AllowedPattern: ^[\w_.+-]+@[\w-]+\.[-.\w]+$
ConstraintDescription: Must be a valid email address.
DrupalSiteName:
Description: Site name
Type: String
Default: Cloud Orchestrator
GitHubToken:
Description: GitHub token for using the API.
Type: String
DrupalTimezone:
Default: Asia/Tokyo
Description: Default time zone
Type: String
UseSpaUI:
Description: Use React SPA UI or default Drupal UI
Type: String
Default: true
AllowedValues:
- true
- false
JSONData1:
NoEcho: true
Type: String
Default: ''
Description: Chunked JSON data for creating Cloud Service Providers. Data
is chunked because each parameter can only accommodate 4096 bytes.
JSONData2:
NoEcho: true
Type: String
Default: ''
Description: Chunked JSON data for creating Cloud Service Providers. Data
is chunked because each parameter can only accommodate 4096 bytes.
JSONData3:
NoEcho: true
Type: String
Default: ''
Description: Chunked JSON data for creating Cloud Service Providers. Data
is chunked because each parameter can only accommodate 4096 bytes.
Country:
Type: String
Default: ''
Description: Country code for the location map
City:
Type: String
Default: ''
Description: City name for the location map
BearerToken:
NoEcho: true
Description: Bearer Token for API calls
Type: String
CloudOrchestratorVersion:
Type: String
Description: Cloud Orchestrator version
Default: 6.x-dev
WelcomeSubject:
Type: String
Description: Welcome email subject
Default: 'Cloud Orchestrator is ready for use'
WelcomeMessage:
Type: String
Description: Welcome email body. The variables ${LOGIN_LINK}, ${DNS_NAME}, ${CO_ADMIN_ID} are available. \r\n are needed for newline characters.
Default: >
Congratulations! Cloud Orchestrator installed successfully. It is ready to use.
\r\n
\r\nPlease use this link to login and set your password.
${LOGIN_LINK}
\r\n
\r\nYour username is: ${CO_ADMIN_ID}. You can access Cloud Orchestrator at: https://${DNS_NAME}
\r\n
\r\nCloud Orchestrator team.
EnableSmtp:
Type: String
Description: Turn on AWS SES for sending emails
Default: true
AllowedValues:
- true
- false
SmtpSecretName:
Type: String
Description: The secret name containing SMTP configuration information.
Default: ''
ZoomLevel:
Type: String
Default: ''
Description: Zoom level for the location map
# ElastiCache parameters
MemcacheAddress:
Description: Memcache address
Type: String
MemcachePort:
Description: Memcache port
Type: String
Default: 11211
# Route 53 parameters
HostedZoneName:
Description: The hosted zone name
Type: String
Subdomain:
Description: The prefered subdomain
Type: String
# ECS parameters
EcsSecurityGroup:
Description: ECS security group ID
Type: String
EcsClusterArn:
Description: ECS Cluster ARN
Type: String
InstallationLambdaArn:
Description: Installation Lambda ARN
Type: String
EcsTaskDefinitionArn:
Description: ECS installation task definition
Type: String
ImageUri:
Description: Please enter the URL of Cloud Orchestrator builder image
Type: String
FacadeTaskDefinitionContainerName:
Type: String
Description: The container name of the Facade tenant creation definition.
Default: facade
# Network parameters
PublicSubnetId:
Type: AWS::EC2::Subnet::Id
Description: Select a public subnet ID to use with the ECS task.
# EFS parameters
EfsId:
Type: String
Description: EFS ID
EfsAccessPointId:
Type: String
Description: EFS access point id.
# DNS Parameters
DNSName:
Type: String
Description: Load balancer DNS
# Lambda Parameters
LambdaTimeout:
Type: String
Description: Lambda timeout
Default: 600
Resources:
EcsInstallTask:
Type: Custom::EcsInstallTask
Properties:
FacadeTaskDefinitionContainerName: !Ref FacadeTaskDefinitionContainerName
RDSInstanceSecretName: !Ref RdsInstanceSecretName
SmtpSecretName: !Ref SmtpSecretName
EnableSmtp: !Ref EnableSmtp
ServiceToken: !Ref InstallationLambdaArn
EcsClusterArn: !Ref EcsClusterArn
TaskDefinitionArn: !Ref EcsTaskDefinitionArn
PublicSubnetId: !Ref PublicSubnetId
AssignPublicIp: 'ENABLED'
LaunchType: 'FARGATE'
EcsSecurityGroup: !Ref EcsSecurityGroup
GITHUB_TOKEN: !Ref GitHubToken
CO_VERSION: !Ref CloudOrchestratorVersion
MYSQL_DATABASE_NAME: !Ref DatabaseName
MEMCACHE_ADDRESS: !Ref MemcacheAddress
MEMCACHE_PORT: !Ref MemcachePort
CO_ADMIN_ID: !Ref DrupalUserName
CO_ADMIN_EMAIL: !Ref DrupalEmail
CO_END_USER_ADMIN_ID: !Ref DrupalEndUserUserName
CO_END_USER_ADMIN_EMAIL: !Ref DrupalEndUserEmail
CO_SITE_NAME: !Ref DrupalSiteName
CO_TIMEZONE: !Ref DrupalTimezone
DNS_NAME: !Ref Dns
BEARER_TOKEN: !Ref BearerToken
USE_SPA_UI: !Ref UseSpaUI
JSON_DATA: !Join [ '', [ !Ref JSONData1, !Ref JSONData2, !Ref JSONData3 ] ]
COUNTRY: !Ref Country
CITY: !Ref City
WELCOME_SUBJECT: !Ref WelcomeSubject
WELCOME_MESSAGE: !Ref WelcomeMessage
Dns:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneName: !Sub '${HostedZoneName}.'
Comment: !Sub DNS for ${Subdomain}.${HostedZoneName}.
Name:
!Sub
- '${Subdomain}.${HostedZoneName}.'
- Subdomain: !Ref Subdomain
HostedZoneName: !Ref HostedZoneName
Type: CNAME
TTL: 600
ResourceRecords:
- !Ref DNSName
FirelensCloudWatchLogs:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub ${StackPrefix}-co-firelens
RetentionInDays: 1
Outputs:
DrupalUrl:
Value: !Sub https://${Dns}
DNS:
Value: !Ref Dns
deployName: CFn based Cloud Orchestrator deployment
description: Deploys Cloud Orchestrator using manually selected VPC and RDS.
cloud_formation_template: deployments/cfn/cloud_orchestrator_single_rds_manual_vpc.yaml
deployName: CFn based Cloud Orchestrator installer
description: Installs Cloud Orchestrator into a distribution multisite production environment.
cloud_formation_template: deployments/cfn/cloud_orchestrator_ecs_elasticache_rds_prod.yml
parameters:
StackName:
type: auto
......@@ -8,49 +8,62 @@ parameters:
StackPrefix:
type: auto
method: generateStackPrefix
BearerToken:
type: auto
method: generateBearerToken
VPC:
RdsInstanceSecretName:
type: config
config_name: openstack_provider.settings
config_key: vpc
PublicSubnet1:
config_key: rds_instance_secret_name
EcsClusterArn:
type: config
config_name: openstack_provider.settings
config_key: public_subnet_1
PrivateSubnet1:
config_key: ecs_cluster_arn
ImageUri:
type: config
config_name: openstack_provider.settings
config_key: private_subnet_1
PrivateSubnet2:
config_key: image_uri
InstallationLambdaArn:
type: config
config_name: openstack_provider.settings
config_key: private_subnet_2
InstanceType:
config_key: installation_lambda_arn
EcsTaskDefinitionArn:
type: config
config_name: openstack_provider.settings
config_key: instance_type
MySQLUserName:
config_key: ecs_task_definition_arn
EcsSecurityGroup:
type: config
config_name: openstack_provider.settings
config_key: mysql_username
DatabaseName:
config_key: ecs_security_group
FacadeTaskDefinitionContainerName:
type: config
config_name: openstack_provider.settings
config_key: facade_task_definition_container_name
EfsId:
type: config
config_name: openstack_provider.settings
config_key: database_name
RDSInstanceType:
config_key: efs_id
EfsAccessPointId:
type: config
config_name: openstack_provider.settings
config_key: rds_instance_type
RDSVersion:
config_key: efs_access_point_id
PublicSubnetId:
type: config
config_name: openstack_provider.settings
config_key: rds_version
DBAllocatedStorage:
config_key: public_subnet_1
DNSName:
type: config
config_name: openstack_provider.settings
config_key: db_allocated_storage
config_key: load_balancer_dns_name
GitHubToken:
type: config
config_name: openstack_provider.settings
config_key: github_token
DrupalSiteName:
type: config
config_name: openstack_provider.settings
config_key: drupal_site_name
DrupalTimezone:
type: config
config_name: openstack_provider.settings
config_key: drupal_timezone
DrupalUserName:
type: entity
entity_type: tenant
......@@ -61,30 +74,18 @@ parameters:
entity_type: tenant
entity_bundle: openstack
field: field_email
DrupalTimezone:
type: config
config_name: openstack_provider.settings
config_key: drupal_timezone
DrupalSiteEmail:
type: config
config_name: openstack_provider.settings
config_key: drupal_site_email
DrupalSiteName:
type: config
config_name: openstack_provider.settings
config_key: drupal_site_name
CloudOrchestratorVersion:
DrupalEndUserUserName:
type: config
config_name: openstack_provider.settings
config_key: cloud_orchestrator_version
CreationTimeout:
config_key: drupal_enduser_username
DrupalEndUserEmail:
type: config
config_name: openstack_provider.settings
config_key: creation_timeout
DockerFileUrl:
config_key: drupal_enduser_email
UseSpaUI:
type: config
config_name: openstack_provider.settings
config_key: docker_file_url
config_key: use_spa_ui
Country:
type: entity
entity_type: tenant
......@@ -95,8 +96,57 @@ parameters:
entity_type: tenant
entity_bundle: openstack
field: field_city
BearerToken:
type: auto
method: generateBearerToken
ZoomLevel:
type: entity
entity_type: tenant
entity_bundle: openstack
field: field_zoom_level
CloudOrchestratorVersion:
type: config
config_name: openstack_provider.settings
config_key: cloud_orchestrator_version
WelcomeSubject:
type: config
config_name: openstack_provider.settings
config_key: welcome_subject
WelcomeMessage:
type: config
config_name: openstack_provider.settings
config_key: welcome_message
EnableSmtp:
type: config
config_name: openstack_provider.settings
config_key: enable_smtp
SmtpSecretName:
type: config
config_name: openstack_provider.settings
config_key: smtp_secret_name
MemcacheAddress:
type: config
config_name: openstack_provider.settings
config_key: memcache_address
MemcachePort:
type: config
config_name: openstack_provider.settings
config_key: memcache_port
Subdomain:
type: entity
entity_type: tenant
entity_bundle: openstack
field: field_subdomain
HostedZoneName:
type: config
config_name: openstack_provider.settings
config_key: hosted_zone_name
LambdaTimeout:
type: config
config_name: openstack_provider.settings
config_key: lambda_timeout
DatabaseName:
type: auto
method: generateDatabaseName
# Docs
This folder contains documentation, archived CloudFormation templates and Dockerfiles.
......@@ -2,5 +2,5 @@ openstack_provider.facade_launch_template:
id: openstack_provider
entity_type: tenant
entity_bundle: openstack
parameter_yaml: deployments/parameters/single_rds_manual_vpc.yml
parameter_yaml: deployments/parameters/ecs_elasticache_rds_prod.yml
class: 'Drupal\openstack_provider\Plugin\facade\launch_tenant\OpenStackProviderLaunchTenantPlugin'
......@@ -92,3 +92,70 @@ function openstack_provider_update_9105(): void {
$config->set('autocreate_projects', FALSE);
$config->save();
}
/**
* Refactor configuration parameters for openstack_provider.settings.
*
* Delete billing field and add subdomain field.
*/
function openstack_provider_update_9106(): void {
$config_factory = \Drupal::configFactory();
$config = $config_factory->getEditable('openstack_provider.settings');
$config->set('rds_instance_secret_name', '');
$config->set('ecs_cluster_arn', '');
$config->set('image_uri', '');
$config->set('installation_lambda_arn', '');
$config->set('ecs_task_definition_arn', '');
$config->set('ecs_security_group', 'facade');
$config->set('facade_task_definition_container_name', 'facade');
$config->set('efs_id', '');
$config->set('efs_access_point_id', '');
$config->set('load_balancer_dns_name', '');
$config->set('github_token', '');
$config->set('drupal_enduser_username', 'cloud_end_user_admin');
$config->set('drupal_enduser_email', '');
$config->set('use_spa_ui', 'true');
$config->set('country', '');
$config->set('city', '');
$config->set('welcome_subject', 'Cloud Orchestrator is ready for use');
$config->set('welcome_message', 'Congratulations! Cloud Orchestrator installed successfully. It is ready to use.\r\n\r\nPlease use this link to login and set your password.\r\n${LOGIN_LINK}\r\n\r\nYour username is: ${CO_ADMIN_ID}. You can access Cloud Orchestrator at: https://${DNS_NAME}\r\n\r\nCloud Orchestrator team.');
$config->set('enable_smtp', 'true');
$config->set('smtp_secret_name', '');
$config->set('memcache_address', '');
$config->set('memcache_port', '');
$config->set('hosted_zone_name', '');
$config->set('lambda_timeout', '600');
$config->clear('database_name');
$config->clear('private_subnet_1');
$config->clear('private_subnet_2');
$config->clear('instance_type');
$config->clear('db_allocated_storage');
$config->clear('rds_instance_type');
$config->clear('mysql_username');
$config->clear('creation_timeout');
$config->clear('docker_file_url');
$config->clear('rds_version');
$config->clear('cloud_orchestrator_version');
$config->save();
// Delete billing field since it is not needed.
\Drupal::service('cloud')->deleteFields(
'tenant',
'openstack',
[
'field_billing_information',
]
);
\Drupal::service('cloud')->addFields(
'tenant',
'openstack',
[
'field_subdomain',
],
'openstack_provider'
);
}
......@@ -19,6 +19,8 @@ use Drupal\Core\Locale\CountryManager;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\aws_cloud\Service\Route53\Route53ServiceException;
use Drupal\aws_cloud\Service\Route53\Route53Service;
/**
* Implements hook_query_TAG_alter().
......@@ -62,6 +64,123 @@ function openstack_provider_form_tenant_openstack_add_form_alter(array &$form, F
if ($autocreate_projects === FALSE) {
$form['field_openstack_region']['#access'] = FALSE;
}
$form['#validate'][] = 'openstack_provider_validate_subdomain_function';
}
/**
* Validate if subdomain exists.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
function openstack_provider_validate_subdomain_function(array &$form, FormStateInterface $form_state): void {
/** @var \Drupal\aws_cloud\Service\Route53\Route53Service $route53_service */
$route53_service = Drupal::service('aws_cloud.route53');
$settings = \Drupal::configFactory()
->get('openstack_provider.settings');
$target_provider = $settings->get('target_provider');
$dns_name = $settings->get('hosted_zone_name');
if (empty($target_provider) || empty($dns_name)) {
$form_state->setErrorByName('field_subdomain', t('Unable to verify subdomain. Target provider or hosted_zone_name are undefined.'));
return;
}
$subdomain = $form_state->getValue('field_subdomain')[0]['value'] ?? '';
if (empty($subdomain)) {
$form_state->setErrorByName('field_subdomain', t('Subdomain field is empty.'));
return;
}
$route53_service->setCloudContext($target_provider);
try {
$zone_id = openstack_provider_get_zone_id($route53_service, $dns_name);
if (empty($zone_id)) {
$form_state->setErrorByName('field_subdomain', t('Unable to find hosted zone.'));
return;
}
// Records contain a trailing ".".
$record = "${subdomain}.${dns_name}.";
if (openstack_provider_resource_record_exists($route53_service, $zone_id, $record) === TRUE) {
$form_state->setErrorByName('field_subdomain', t('Subdomain already exists. Please choose another subdomain.'));
return;
}
}
catch (\Exception $e) {
\Drupal::service('cloud')->handleException($e);
$form_state->setErrorByName('field_subdomain', t('Unable to verify subdomain.'));
}
}
/**
* Helper function to check if a DNS record exists.
*
* @param Route53Service $route53_service
* Route53 service.
* @param string $zone_id
* Zone id.
* @param string $record_name
* Record name to check.
*
* @return bool
* TRUE if it exists, FALSE otherwise.
*
* @throws \Exception
* Throw an exception if there is an error during the API call.
*/
function openstack_provider_resource_record_exists(Route53Service $route53_service, string $zone_id, string $record_name): bool {
$results = $route53_service->listResourceRecordSets([
'HostedZoneId' => $zone_id,
'StartRecordName' => $record_name,
'MaxItems' => 1,
]);
if (empty($results)) {
throw new Exception('Unable to list resource records.');
}
// Check if the record exists.
foreach ($results['ResourceRecordSets'] ?:[] as $result) {
if ($result['Name'] === $record_name) {
return TRUE;
}
}
return FALSE;
}
/**
* Helper function to get the zone_id.
*
* @param Route53Service $route53_service
* Route53 service.
* @param string $dns_name
* The DNS name to search for.
*
* @return string|null
*
* @throws \Exception
* Throw an exception if there is an error during the API call.
*/
function openstack_provider_get_zone_id(Route53Service $route53_service, string $dns_name): ?string {
try {
$zones = $route53_service->listHostedZonesByName([
'DNSName' => "${dns_name}.",
]);
if (empty($zones)) {
throw new Exception('Unable to list hosted zones.');
}
foreach ($zones['HostedZones'] ?: [] as $zone) {
if ($zone['Name'] === "${dns_name}.") {
return $zone['Id'];
}
}
}
catch (Route53ServiceException $e) {
\Drupal::service('cloud')->handleException($e);
}
return NULL;
}
/**
......
......@@ -179,8 +179,6 @@ class OpenStackProviderAdminSettingsForm extends ConfigFormBase {
$vpc_id = $config->get('vpc') ?? '';
$target_provider = $form_state->getValue('target_provider') ?? $config->get('target_provider') ?? '';
$public_subnet_1 = $form_state->getValue('public_subnet_1') ?? $config->get('public_subnet_1') ?? '';
$private_subnet_1 = $form_state->getValue('private_subnet_1') ?? $config->get('private_subnet_1') ?? '';
$private_subnet_2 = $form_state->getValue('private_subnet_1') ?? $config->get('private_subnet_2') ?? '';
// Clear any selected values during an ajax callback.
if (!empty($triggering_element)) {
......@@ -189,15 +187,11 @@ class OpenStackProviderAdminSettingsForm extends ConfigFormBase {
$target_provider = $triggering_element['#value'];
$vpc_id = '';
$public_subnet_1 = '';
$private_subnet_1 = '';
$private_subnet_2 = '';
break;
case 'vpc':
$vpc_id = $triggering_element['#value'];
$public_subnet_1 = '';
$private_subnet_1 = '';
$private_subnet_2 = '';
break;
}
}
......@@ -295,105 +289,161 @@ class OpenStackProviderAdminSettingsForm extends ConfigFormBase {
'#default_value' => $public_subnet_1,
];
$form['aws']['network']['subnets']['private_subnet_1'] = [
'#type' => 'select',
'#title' => $this->t('Private subnet 1'),
'#description' => $this->t('Select a private subnet for RDS, security groups and cache
resources. Private Subnet 1 and Private Subnet 2 must be in two different
Availability Zones in the same region.'),
'#options' => $this->getSubnets($target_provider, $vpc_id),
$form['aws']['rds'] = [
'#type' => 'details',
'#title' => $this->t('RDS'),
'#open' => TRUE,
];
$form['aws']['rds']['rds_instance_secret_name'] = [
'#type' => 'textfield',
'#title' => $this->t('RDS secret name'),
'#description' => $this->t('The AWS Secret name containing RDS configuration information.'),
'#default_value' => $config->get('rds_instance_secret_name'),
'#required' => TRUE,
'#default_value' => $private_subnet_1,
];
$form['aws']['network']['subnets']['private_subnet_2'] = [
'#type' => 'select',
'#title' => $this->t('Private subnet 2'),
'#description' => $this->t('Select a private subnet for RDS, security groups and cache
resources. Private Subnet 1 and Private Subnet 2 must be in two different
Availability Zones in the same region.'),
'#options' => $this->getSubnets($target_provider, $vpc_id),
$form['aws']['memcache'] = [
'#type' => 'details',
'#title' => $this->t('Memcache'),
'#open' => TRUE,
];
$form['aws']['memcache']['memcache_address'] = [
'#type' => 'textfield',
'#title' => $this->t('Memcache address'),
'#description' => $this->t('Memcache endpoint'),
'#default_value' => $config->get('memcache_address'),
'#required' => TRUE,
'#default_value' => $private_subnet_2,
];
$form['cfn'] = [
$form['aws']['memcache']['memcache_port'] = [
'#type' => 'textfield',
'#title' => $this->t('Memcache port'),
'#description' => $this->t('Memcache port'),
'#default_value' => $config->get('memcache_port'),
'#required' => TRUE,
];
$form['aws']['dns'] = [
'#type' => 'details',
'#title' => $this->t('CloudFormation'),
'#title' => $this->t('Route 53'),
'#open' => TRUE,
];
$form['cfn']['instance_type'] = [
$form['aws']['dns']['hosted_zone_name'] = [
'#type' => 'textfield',
'#title' => $this->t('Instance type'),
'#description' => $this->t('The EC2 instance type'),
'#default_value' => $config->get('instance_type'),
'#title' => $this->t('Hosted zone name'),
'#description' => $this->t('The hosted zone name (ex: example.com)'),
'#default_value' => $config->get('hosted_zone_name'),
'#required' => TRUE,
];
$form['cfn']['cloud_orchestrator_version'] = [
$form['aws']['ecs'] = [
'#type' => 'details',
'#title' => $this->t('ECS'),
'#open' => TRUE,
];
$form['aws']['ecs']['ecs_cluster_arn'] = [
'#type' => 'textfield',
'#title' => $this->t('Cloud Orchestrator version'),
'#description' => $this->t('Version of the Cloud Orchestrator to install.'),
'#default_value' => $config->get('cloud_orchestrator_version'),
'#title' => $this->t('ECS cluster ARN'),
'#description' => $this->t('ARN of the ECS cluster ARN that the tenant is installed into.'),
'#default_value' => $config->get('ecs_cluster_arn'),
'#required' => TRUE,
];
$form['cfn']['creation_timeout'] = [
$form['aws']['ecs']['image_uri'] = [
'#type' => 'textfield',
'#title' => $this->t('Creation timeout'),
'#description' => $this->t('The length of time that CloudFormation waits until template
creation is considered unsuccessful and is deleted.'),
'#default_value' => $config->get('creation_timeout'),
'#title' => $this->t('Docker URL'),
'#description' => $this->t('Docker URL of Cloud Orchestrator installation image'),
'#default_value' => $config->get('image_uri'),
'#required' => TRUE,
];
$form['cfn']['docker_file_url'] = [
$form['aws']['ecs']['installation_lambda_arn'] = [
'#type' => 'textfield',
'#title' => $this->t('Docker file url'),
'#description' => $this->t('The Dockerfile used to build Cloud Orchestrator container'),
'#default_value' => $config->get('docker_file_url'),
'#title' => $this->t('Installation lambda ARN'),
'#description' => $this->t('Installation lambda ARN'),
'#default_value' => $config->get('installation_lambda_arn'),
'#required' => TRUE,
];
$form['rds'] = [
'#type' => 'details',
'#title' => $this->t('RDS'),
'#open' => TRUE,
$form['aws']['ecs']['ecs_task_definition_arn'] = [
'#type' => 'textfield',
'#title' => $this->t('ECS task definition ARN'),
'#description' => $this->t('ECS task definition ARN'),
'#default_value' => $config->get('ecs_task_definition_arn'),
'#required' => TRUE,
];
$form['aws']['ecs']['ecs_security_group'] = [
'#type' => 'textfield',
'#title' => $this->t('ECS security group ID'),
'#description' => $this->t('ECS security group ID'),
'#default_value' => $config->get('ecs_security_group'),
'#required' => TRUE,
];
$form['aws']['ecs']['facade_task_definition_container_name'] = [
'#type' => 'textfield',
'#title' => $this->t('Facade task definition container name'),
'#description' => $this->t('The container name of the Facade tenant creation definition. The default "facade" should not need to be changed.'),
'#default_value' => $config->get('facade_task_definition_container_name'),
'#required' => TRUE,
];
$form['aws']['ecs']['efs_id'] = [
'#type' => 'textfield',
'#title' => $this->t('EFS ID'),
'#description' => $this->t('EFS ID'),
'#default_value' => $config->get('efs_id'),
'#required' => TRUE,
];
$form['rds']['rds_instance_type'] = [
$form['aws']['ecs']['efs_access_point_id'] = [
'#type' => 'textfield',
'#title' => $this->t('RDS instance type'),
'#default_value' => $config->get('rds_instance_type'),
'#title' => $this->t('EFS access point ID'),
'#description' => $this->t('EFS access point ID'),
'#default_value' => $config->get('efs_access_point_id'),
'#required' => TRUE,
];
$form['rds']['rds_version'] = [
$form['aws']['ecs']['load_balancer_dns_name'] = [
'#type' => 'textfield',
'#title' => $this->t('RDS MariaDB version'),
'#default_value' => $config->get('rds_version'),
'#title' => $this->t('Load balancer DNS name'),
'#description' => $this->t('The FQDN for the load balancer.'),
'#default_value' => $config->get('load_balancer_dns_name'),
'#required' => TRUE,
];
$form['rds']['mysql_username'] = [
$form['aws']['ecs']['lambda_timeout'] = [
'#type' => 'textfield',
'#title' => $this->t('MySQL username'),
'#default_value' => $config->get('mysql_username'),
'#title' => $this->t('Lambda timeout'),
'#description' => $this->t('Lambda timeout before the stack errors out.'),
'#default_value' => $config->get('lambda_timeout'),
'#required' => TRUE,
];
$form['rds']['database_name'] = [
$form['cfn'] = [
'#type' => 'details',
'#title' => $this->t('CloudFormation'),
'#open' => TRUE,
];
$form['cfn']['cloud_orchestrator_version'] = [
'#type' => 'textfield',
'#title' => $this->t('Database name'),
'#default_value' => $config->get('database_name'),
'#title' => $this->t('Cloud Orchestrator version'),
'#description' => $this->t('Version of the Cloud Orchestrator to install.'),
'#default_value' => $config->get('cloud_orchestrator_version'),
'#required' => TRUE,
];
$form['rds']['db_allocated_storage'] = [
$form['cfn']['github_token'] = [
'#type' => 'textfield',
'#title' => $this->t('Database size'),
'#description' => $this->t('The size of the database (Gb). Must be between 5 and 16384.'),
'#default_value' => $config->get('db_allocated_storage'),
'#title' => $this->t('Github token'),
'#description' => $this->t('Github token to avoid API rate limits.'),
'#default_value' => $config->get('github_token'),
'#required' => TRUE,
];
......@@ -419,6 +469,50 @@ class OpenStackProviderAdminSettingsForm extends ConfigFormBase {
'#required' => TRUE,
];
$form['drupal']['drupal_enduser_username'] = [
'#type' => 'textfield',
'#title' => $this->t('End user username'),
'#default_value' => $config->get('drupal_enduser_username'),
'#description' => $this->t('A second administrative user used for Cloud Orchestrator management and troubleshooting.'),
'#required' => TRUE,
];
$form['drupal']['drupal_enduser_email'] = [
'#type' => 'textfield',
'#title' => $this->t('End user email'),
'#default_value' => $config->get('drupal_enduser_email'),
'#description' => $this->t("A second administrative user's email address."),
'#required' => TRUE,
];
$form['drupal']['welcome_subject'] = [
'#type' => 'textfield',
'#title' => $this->t('Welcome email subject'),
'#default_value' => $config->get('welcome_subject'),
'#description' => $this->t('Welcome email subject used for sending welcome email.'),
'#required' => TRUE,
];
$form['drupal']['welcome_message'] = [
'#type' => 'textarea',
'#title' => $this->t('Welcome email message'),
'#default_value' => $config->get('welcome_message'),
'#description' => $this->t('Welcome email body used for sending welcome email. The variables ${LOGIN_LINK}, ${DNS_NAME}, ${CO_ADMIN_ID} are available for substitutions. "\r\n" are needed for newline characters.'),
'#required' => TRUE,
];
$form['drupal']['use_spa_ui'] = [
'#type' => 'select',
'#options' => [
'true' => $this->t('Use React SPA UI'),
'false' => $this->t('Use Drupal UI'),
],
'#title' => $this->t('Use SPA UI'),
'#default_value' => $config->get('use_spa_ui'),
'#description' => $this->t('Use React SPA UI or keep traditional Drupal UI.'),
'#required' => TRUE,
];
$zones = system_time_zones(NULL, TRUE);
$form['drupal']['drupal_timezone'] = [
......@@ -456,9 +550,52 @@ class OpenStackProviderAdminSettingsForm extends ConfigFormBase {
'#options' => $this->openStackProviderService->getLocationMapZoomLevels(),
];
$form['drupal']['enable_smtp'] = [
'#type' => 'select',
'#options' => [
'true' => $this->t('Use Amazon SES'),
'false' => $this->t('Use local email agent'),
],
'#title' => $this->t('Enable SMTP'),
'#default_value' => $config->get('enable_smtp'),
'#description' => $this->t('Select which email agent to send system emails.'),
'#required' => TRUE,
];
$form['drupal']['smtp_secret_name'] = [
'#type' => 'textfield',
'#title' => $this->t('SMTP Secret'),
'#description' => $this->t('AWS Secret containing SMTP configuration information.'),
'#default_value' => $config->get('smtp_secret_name'),
];
$form['debug'] = [
'#type' => 'details',
'#title' => $this->t('Debug'),
'#open' => TRUE,
];
$form['debug']['disable_ssl_verify'] = [
'#type' => 'checkbox',
'#title' => $this->t('Disable SSL verification in Guzzle client during Facade to Cloud Orchestrator REST API calls'),
'#description' => $this->t('IMPORTANT: Do no enable in production environment. This should only be used in development or testing where Cloud Orchestrator is behind a self-signed certificate.'),
'#default_value' => $config->get('disable_ssl_verify'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$enable_smtp = (bool) $form_state->getValue('enable_smtp');
$smtp_secret_name = $form_state->getValue('smtp_secret_name');
if ($enable_smtp === TRUE && empty($smtp_secret_name)) {
$form_state->setErrorByName('smtp_secret_name', $this->t('SMTP Secret needs to be set if using Amazon SES.'));
}
}
/**
* {@inheritdoc}
*/
......
......@@ -277,12 +277,17 @@ class OpenStackProviderService implements OpenStackProviderServiceInterface, Con
throw new \Exception('Cannot add project. No endpoint found.');
}
$response = $this->httpClient->post(
$endpoint . OpenStackProviderServiceInterface::CLOUD_ORCHESTRATOR_ADD_PROVIDER_ENDPOINT,
[
$options = [
'body' => json_encode($params, JSON_THROW_ON_ERROR),
'headers' => $headers,
]
];
// Only set verify=FALSE if the the disable_ssl_verify is set to true.
if ((bool) $this->getConfigurationValue('openstack_provider.settings', 'disable_ssl_verify') === TRUE) {
$options['verify'] = FALSE;
}
$response = $this->httpClient->post(
$endpoint . OpenStackProviderServiceInterface::CLOUD_ORCHESTRATOR_ADD_PROVIDER_ENDPOINT,
$options
);
if ($response->getStatusCode() !== 201) {
throw new \Exception("Error adding project to $endpoint");
......@@ -411,14 +416,19 @@ class OpenStackProviderService implements OpenStackProviderServiceInterface, Con
}
$data = [];
try {
$response = $this->httpClient->get(
$endpoint . OpenStackProviderServiceInterface::CLOUD_ORCHESTRATOR_GET_PROVIDER_ENDPOINT,
[
$options = [
'headers' => $headers,
'query' => [
'name' => $name,
],
]
];
// Only set verify=FALSE if the the disable_ssl_verify is set to true.
if ((bool) $this->getConfigurationValue('openstack_provider.settings', 'disable_ssl_verify') === TRUE) {
$options['verify'] = FALSE;
}
$response = $this->httpClient->get(
$endpoint . OpenStackProviderServiceInterface::CLOUD_ORCHESTRATOR_GET_PROVIDER_ENDPOINT,
$options
);
if ($response->getStatusCode() !== 200) {
throw new \Exception('Error accessing cloud orchestrator.');
......@@ -684,13 +694,7 @@ class OpenStackProviderService implements OpenStackProviderServiceInterface, Con
}
/**
* Generate the CFn stack name.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* Tenant entity.
*
* @return string
* The generated stack name.
* {@inheritdoc}
*/
public function generateStackName(EntityInterface $entity): string {
return $this->t(':name-:timestamp', [
......@@ -700,13 +704,7 @@ class OpenStackProviderService implements OpenStackProviderServiceInterface, Con
}
/**
* Generate the CFn stack prefix.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* Tenant entity.
*
* @return string
* The generated stack name.
* {@inheritdoc}
*/
public function generateStackPrefix(EntityInterface $entity): string {
// Stack prefix can only be 20 characters in length.
......@@ -716,10 +714,21 @@ class OpenStackProviderService implements OpenStackProviderServiceInterface, Con
])->render();
}
/**
* {@inheritdoc}
*/
public function generateDatabaseName(EntityInterface $entity): string {
$subdomain = $entity->get('field_subdomain')->value;
return $this->t(':subdomain_:timestamp_co', [
':subdomain' => substr($this->formatName($subdomain), 0, 15),
':timestamp' => time(),
])->render();
}
/**
* Helper function to strip tenant entity name.
*
* The formatted tenant name is used for CloudFormnation stack name and
* The formatted tenant name is used for CloudFormation stack name and
* can only contain alphanumeric characters and hyphens.
*
* @param string $name
......
......@@ -118,4 +118,37 @@ interface OpenStackProviderServiceInterface {
*/
public function getLocationMapZoomLevels(): array;
/**
* Generate the CFn stack name.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* Tenant entity.
*
* @return string
* The generated stack name.
*/
public function generateStackName(EntityInterface $entity): string;
/**
* Generate the CFn stack prefix.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* Tenant entity.
*
* @return string
* The generated stack name.
*/
public function generateStackPrefix(EntityInterface $entity): string;
/**
* Generate the database name.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* Tenant entity.
*
* @return string
* The generated database name.
*/
public function generateDatabaseName(EntityInterface $entity): string;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment