Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .config/cspell-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ tostring
quayregistry
quayadmin
esac
eicadmin
Burstable
sslmode
cabundles
selectattr
equalto
Expand Down
7 changes: 4 additions & 3 deletions REUSE.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2024 SAP edge team
# SPDX-FileCopyrightText: 2025 SAP edge team
# SPDX-FileContributor: Kirill Satarin (@kksat)
# SPDX-FileContributor: Manjun Jiao (@mjiao)
#
Expand All @@ -7,6 +7,7 @@
version = 1

[[annotations]]
path = "./*"
SPDX-FileCopyrightText = "2024 SAP edge team"
path = "**/*"
SPDX-FileCopyrightText = "2025 SAP edge team"
SPDX-License-Identifier = "Apache-2.0"

140 changes: 134 additions & 6 deletions bicep/aro.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,92 @@
// SPDX-License-Identifier: Apache-2.0

param clusterName string
param domain string
@description('The pull secret for the ARO cluster.')
@secure()
param pullSecret string = ''
param pullSecret string
@description('The domain for the ARO cluster.')
param domain string
@description('OpenShift version for the ARO cluster')
@allowed([
'4.16.39'
'4.15.35'
'4.14.38'
'4.15.35'
'4.16.39'
'4.17.27'
'4.18.9'
])
param version string

param servicePrincipalClientId string

@secure()
param servicePrincipalClientSecret string

param aroResourceGroup string = '${resourceGroup().name}-resources'

param vnetName string = '${resourceGroup().name}-vnet'
param masterSubnetName string = 'master'
param workerSubnetName string = 'worker'

@description('VM size for master nodes')
@allowed(['Standard_D8s_v3', 'Standard_D16s_v3'])
param masterVmSize string = 'Standard_D8s_v3'

@description('VM size for worker nodes (optimized for testing)')
@allowed(['Standard_D4s_v3', 'Standard_D8s_v3'])
param workerVmSize string = 'Standard_D4s_v3'

@description('Disk size for worker nodes in GB')
@minValue(128)
@maxValue(1024)
param workerDiskSizeGB int = 128

@description('Number of worker nodes (limited for testing)')
@minValue(3)
@maxValue(10)
param workerCount int = 3

param location string = resourceGroup().location

@description('Whether to deploy Azure Database for PostgreSQL')
param deployPostgres bool = true

@description('Whether to deploy Azure Cache for Redis')
param deployRedis bool = true

@description('PostgreSQL admin password')
@secure()
param postgresAdminPassword string = ''

// Cost optimization parameters for testing
@description('Enable auto-shutdown for cost savings (testing only)')
param enableAutoShutdown bool = true

@description('Auto-shutdown time in 24-hour format (testing only)')
param shutdownTime string = '19:00'

@description('Testing-specific tags for resource management')
param testingTags object = {
purpose: 'testing'
team: 'sap-edge'
autoCleanup: 'enabled'
maxLifetime: '7days'
costOptimized: 'true'
}

resource aroCluster 'Microsoft.RedHatOpenShift/openShiftClusters@2023-11-22' = {
name: clusterName
location: location
tags: union(testingTags, {
clusterName: clusterName
deployment: 'bicep'
})
properties: {
clusterProfile: {
pullSecret: !empty(pullSecret) ? pullSecret : null
domain: domain
pullSecret: base64ToString(pullSecret)
resourceGroupId: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${aroResourceGroup}'
version: version
fipsValidatedModules: 'Disabled'
resourceGroupId: '/subscriptions/${subscription().subscriptionId}/resourceGroups/${aroResourceGroup}'
}
networkProfile: {
podCidr: '10.128.0.0/14'
Expand Down Expand Up @@ -90,3 +140,81 @@ resource workerSubnet 'Microsoft.Network/virtualNetworks/subnets@2024-01-01' exi
name: workerSubnetName
parent: vnet
}

// Deploy Azure services using the azure-services.bicep module
module azureServices 'azure-services.bicep' = if (deployPostgres || deployRedis) {
name: 'azure-services-deployment'
params: {
clusterName: clusterName
location: location
deployPostgres: deployPostgres
deployRedis: deployRedis
postgresAdminPassword: postgresAdminPassword
}
}

// Outputs for Azure services
output postgresServerName string = (deployPostgres || deployRedis) && deployPostgres ? azureServices.outputs.postgresServerName : ''
output postgresServerFqdn string = (deployPostgres || deployRedis) && deployPostgres ? azureServices.outputs.postgresServerFqdn : ''
output postgresAdminUsername string = (deployPostgres || deployRedis) && deployPostgres ? azureServices.outputs.postgresAdminUsername : ''
output postgresDatabaseName string = (deployPostgres || deployRedis) && deployPostgres ? azureServices.outputs.postgresDatabaseName : ''

output redisCacheName string = (deployPostgres || deployRedis) && deployRedis ? azureServices.outputs.redisCacheName : ''
output redisHostName string = (deployPostgres || deployRedis) && deployRedis ? azureServices.outputs.redisHostName : ''
output redisPort int = (deployPostgres || deployRedis) && deployRedis ? azureServices.outputs.redisPort : 0
output redisSslPort int = (deployPostgres || deployRedis) && deployRedis ? azureServices.outputs.redisSslPort : 0

// Connection strings
output postgresConnectionString string = (deployPostgres || deployRedis) && deployPostgres ? azureServices.outputs.postgresConnectionString : ''
output redisConnectionString string = (deployPostgres || deployRedis) && deployRedis ? azureServices.outputs.redisConnectionString : ''

// Enhanced testing-focused outputs
@description('Quick connection info for testing and debugging')
output quickConnectionInfo object = {
cluster: {
name: clusterName
apiServerUrl: aroCluster.properties.apiserverProfile.url
consoleUrl: aroCluster.properties.consoleProfile.url
version: version
workerCount: workerCount
domain: domain
}
commands: {
getKubeconfig: 'az aro get-admin-kubeconfig --name ${clusterName} --resource-group ${resourceGroup().name} --file kubeconfig'
getCredentials: 'az aro list-credentials --name ${clusterName} --resource-group ${resourceGroup().name}'
ocLogin: 'oc login ${aroCluster.properties.apiserverProfile.url}'
}
services: {
postgres: (deployPostgres || deployRedis) && deployPostgres ? {
serverName: azureServices.outputs.postgresServerName
serverFqdn: azureServices.outputs.postgresServerFqdn
databaseName: azureServices.outputs.postgresDatabaseName
adminUsername: azureServices.outputs.postgresAdminUsername
connectCommand: 'az postgres flexible-server connect --name ${azureServices.outputs.postgresServerName} --admin-user ${azureServices.outputs.postgresAdminUsername} --database ${azureServices.outputs.postgresDatabaseName}'
} : null
redis: (deployPostgres || deployRedis) && deployRedis ? {
cacheName: azureServices.outputs.redisCacheName
hostName: azureServices.outputs.redisHostName
port: azureServices.outputs.redisPort
sslPort: azureServices.outputs.redisSslPort
getKeysCommand: 'az redis list-keys --name ${azureServices.outputs.redisCacheName} --resource-group ${resourceGroup().name}'
} : null
}
testing: {
tags: testingTags
autoShutdown: enableAutoShutdown
shutdownTime: shutdownTime
estimatedMonthlyCost: 'Check Azure Cost Management for current costs'
cleanupCommand: 'az group delete --name ${resourceGroup().name} --yes --no-wait'
}
}

@description('ARO cluster resource ID for additional operations')
output aroClusterId string = aroCluster.id

@description('ARO cluster API server URL')
output apiServerUrl string = aroCluster.properties.apiserverProfile.url

@description('ARO cluster console URL')
output consoleUrl string = aroCluster.properties.consoleProfile.url

150 changes: 150 additions & 0 deletions bicep/azure-services.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// SPDX-FileCopyrightText: 2025 SAP edge team
// SPDX-FileContributor: Manjun Jiao (@mjiao)
//
// SPDX-License-Identifier: Apache-2.0

@description('The name of the ARO cluster (used for naming the services)')
param clusterName string

@description('The location for the services')
param location string = resourceGroup().location

@description('Whether to deploy PostgreSQL Flexible Server')
param deployPostgres bool = true

@description('Whether to deploy Redis Cache')
param deployRedis bool = true

@description('PostgreSQL server name (auto-generated if not provided)')
param postgresServerName string = ''

@description('PostgreSQL admin username')
param postgresAdminUsername string = 'eicadmin'

@description('PostgreSQL admin password')
@secure()
param postgresAdminPassword string

@description('PostgreSQL SKU name (cost-optimized for testing)')
@allowed(['Standard_B1ms', 'Standard_B2s', 'Standard_D2s_v3'])
param postgresSkuName string = 'Standard_B1ms'

@description('PostgreSQL tier (cost-optimized for testing)')
@allowed(['Burstable', 'GeneralPurpose'])
param postgresTier string = 'Burstable'

@description('PostgreSQL storage size in GB (minimal for testing)')
@minValue(32)
@maxValue(128)
param postgresStorageSize int = 32

@description('PostgreSQL version')
@allowed(['13', '14', '15', '16'])
param postgresVersion string = '15'

@description('Redis cache name (auto-generated if not provided)')
param redisCacheName string = ''

@description('Redis SKU (cost-optimized for testing)')
@allowed(['Basic', 'Standard'])
param redisSku string = 'Basic'

@description('Redis size (minimal for testing)')
@allowed(['C0', 'C1', 'C2'])
param redisSize string = 'C0'

@description('Testing-specific tags for resource management')
param testingTags object = {
purpose: 'testing'
team: 'sap-edge'
autoCleanup: 'enabled'
maxLifetime: '7days'
costOptimized: 'true'
}

// Variables
var postgresServerNameFinal = empty(postgresServerName) ? 'postgres-${clusterName}' : postgresServerName
var redisCacheNameFinal = empty(redisCacheName) ? 'redis-${clusterName}' : redisCacheName

// PostgreSQL Flexible Server
resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2023-06-01-preview' = if (deployPostgres) {
name: postgresServerNameFinal
location: location
tags: union(testingTags, {
service: 'postgresql'
clusterName: clusterName
})
sku: {
name: postgresSkuName
tier: postgresTier
}
properties: {
administratorLogin: postgresAdminUsername
administratorLoginPassword: postgresAdminPassword
version: postgresVersion
storage: {
storageSizeGB: postgresStorageSize
}
network: {
delegatedSubnetResourceId: ''
privateDnsZoneArmResourceId: ''
}
backup: {
backupRetentionDays: 7
geoRedundantBackup: 'Disabled'
}
highAvailability: {
mode: 'Disabled'
}
maintenanceWindow: {
customWindow: 'Disabled'
dayOfWeek: 0
startHour: 2
startMinute: 0
}
}
}

// PostgreSQL Database
resource postgresDatabase 'Microsoft.DBforPostgreSQL/flexibleServers/databases@2023-06-01-preview' = if (deployPostgres) {
name: 'eic'
parent: postgresServer
properties: {
charset: 'utf8'
collation: 'en_US.utf8'
}
}

// Redis Cache
resource redisCache 'Microsoft.Cache/redis@2023-08-01' = if (deployRedis) {
name: redisCacheNameFinal
location: location
tags: union(testingTags, {
service: 'redis'
clusterName: clusterName
})
properties: {
sku: {
name: redisSku
family: 'C'
capacity: int(replace(redisSize, 'C', ''))
}
enableNonSslPort: true
minimumTlsVersion: '1.2'
}
}

// Outputs
output postgresServerName string = deployPostgres ? postgresServer.name : ''
output postgresServerFqdn string = deployPostgres ? postgresServer.properties.fullyQualifiedDomainName : ''
output postgresAdminUsername string = deployPostgres ? postgresAdminUsername : ''
output postgresDatabaseName string = deployPostgres ? 'eic' : ''

output redisCacheName string = deployRedis ? redisCache.name : ''
output redisHostName string = deployRedis ? redisCache.properties.hostName : ''
output redisPort int = deployRedis ? redisCache.properties.port : 0
output redisSslPort int = deployRedis ? redisCache.properties.sslPort : 0

// Connection strings (without passwords - get from Azure portal)
output postgresConnectionString string = deployPostgres ? 'postgresql://${postgresAdminUsername}:[PASSWORD]@${postgresServerNameFinal}.postgres.database.azure.com:5432/eic?sslmode=require' : ''
output redisConnectionString string = deployRedis ? 'redis://${redisCacheNameFinal}.redis.cache.windows.net:6379' : ''
Loading