Skip to content
Open
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
90 changes: 60 additions & 30 deletions bin/cap-op-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,18 @@ const yaml = require('@sap/cds-foss').yaml
const Mustache = require('mustache')
const { spawn } = require('child_process')

const { ask, mergeObj, isCAPOperatorChart, isConfigurableTemplateChart, transformValuesAndFillCapOpCroYaml, isServiceOnlyChart } = require('../lib/util')
const {
ask,
mergeObj,
isCAPOperatorChart,
isConfigurableTemplateChart,
transformValuesAndFillCapOpCroYaml,
isServiceOnlyChart,
getServiceInstanceKeyName,
getConfigurableCapOpCroYaml,
getDomainCroYaml,
getHelperTpl
} = require('../lib/util')

const SUPPORTED = { 'generate-runtime-values': ['--with-input-yaml'], 'convert-to-configurable-template-chart': ['--with-runtime-yaml'] }

Expand All @@ -25,7 +36,7 @@ async function capOperatorPlugin(cmd, option, yamlPath) {
if (option === '--with-input-yaml' && !yamlPath)
return _usage(`Input yaml path is missing.`)

if (option === '--with-input-yaml' && !yamlPath && cds.utils.exists(cds.utils.path.join(cds.root,yamlPath)))
if (option === '--with-input-yaml' && !yamlPath && cds.utils.exists(cds.utils.path.join(cds.root, yamlPath)))
return _usage(`Input yaml path ${yamlPath} does not exist.`)

await generateRuntimeValues(option, yamlPath)
Expand All @@ -35,7 +46,7 @@ async function capOperatorPlugin(cmd, option, yamlPath) {
if (option === '--with-runtime-yaml' && !yamlPath)
return _usage(`Input runtime yaml path is missing.`)

if (option === '--with-runtime-yaml' && !yamlPath && cds.utils.exists(cds.utils.path.join(cds.root,yamlPath)))
if (option === '--with-runtime-yaml' && !yamlPath && cds.utils.exists(cds.utils.path.join(cds.root, yamlPath)))
return _usage(`Input runtime yaml path ${yamlPath} does not exist.`)

await convertToconfigurableTemplateChart(option, yamlPath)
Expand Down Expand Up @@ -81,13 +92,13 @@ EXAMPLES
}

async function transformRuntimeValues(runtimeYamlPath) {
console.log('Transforming runtime values file '+ cds.utils.path.join(cds.root,runtimeYamlPath) + ' to the configurable template chart format.')
console.log('Transforming runtime values file ' + cds.utils.path.join(cds.root, runtimeYamlPath) + ' to the configurable template chart format.')
let runtimeYaml = yaml.parse(await cds.utils.read(cds.utils.path.join(cds.root, runtimeYamlPath)))
if (runtimeYaml?.workloads?.server?.deploymentDefinition?.env) {
const index = runtimeYaml.workloads.server.deploymentDefinition.env.findIndex(e => e.name === 'CDS_CONFIG')
if (index > -1) {
const cdsConfigValueJson = JSON.parse(runtimeYaml.workloads.server.deploymentDefinition.env[index].value)
if (cdsConfigValueJson?.requires?.['cds.xt.DeploymentService']?.hdi?.create?.database_id){
if (cdsConfigValueJson?.requires?.['cds.xt.DeploymentService']?.hdi?.create?.database_id) {
runtimeYaml['hanaInstanceId'] = cdsConfigValueJson.requires['cds.xt.DeploymentService'].hdi.create.database_id
delete runtimeYaml['workloads']
await cds.utils.write(yaml.stringify(runtimeYaml)).to(cds.utils.path.join(cds.root, runtimeYamlPath))
Expand All @@ -102,36 +113,55 @@ async function isRuntimeValueAlreadyTransformed(runtimeYamlPath) {
}

async function convertToconfigurableTemplateChart(option, runtimeYamlPath) {
if (!((cds.utils.exists('chart') && isCAPOperatorChart(cds.utils.path.join(cds.root,'chart')))))
if (!((cds.utils.exists('chart') && isCAPOperatorChart(cds.utils.path.join(cds.root, 'chart')))))
throw new Error("No CAP Operator chart found in the project. Please run 'cds add cap-operator --force' to add the CAP Operator chart folder.")

if (isConfigurableTemplateChart(cds.utils.path.join(cds.root,'chart'))){
if (isConfigurableTemplateChart(cds.utils.path.join(cds.root, 'chart'))) {
console.log("Exisiting chart is already a configurable template chart. No need for conversion.")
if (option === '--with-runtime-yaml' && runtimeYamlPath && !(await isRuntimeValueAlreadyTransformed(runtimeYamlPath)))
await transformRuntimeValues(runtimeYamlPath)
else
console.log('Runtime values file '+ cds.utils.path.join(cds.root,runtimeYamlPath) + ' already in the configurable template chart format.')
console.log('Runtime values file ' + cds.utils.path.join(cds.root, runtimeYamlPath) + ' already in the configurable template chart format.')
return
}

console.log('Converting chart '+cds.utils.path.join(cds.root,'chart')+' to configurable template chart.')
console.log('Converting chart ' + cds.utils.path.join(cds.root, 'chart') + ' to configurable template chart.')

// Copy templates
await cds.utils.copy(cds.utils.path.join(__dirname, '../files/configurableTemplatesChart/templates/_helpers.tpl')).to(cds.utils.path.join(cds.root,'chart/templates/_helpers.tpl'))
await cds.utils.copy(cds.utils.path.join(__dirname, '../files/commonTemplates/')).to(cds.utils.path.join(cds.root,'chart/templates/'))
await cds.utils.copy(cds.utils.path.join(__dirname, '../files/commonTemplates/')).to(cds.utils.path.join(cds.root, 'chart/templates/'))

isServiceOnlyChart(cds.utils.path.join(cds.root,'chart')) ? await cds.utils.copy(cds.utils.path.join(__dirname, '../files/configurableTemplatesChart/templates/cap-operator-cros-svc.yaml')).to(cds.utils.path.join(cds.root,'chart/templates/cap-operator-cros.yaml')) :
await cds.utils.copy(cds.utils.path.join(__dirname, '../files/configurableTemplatesChart/templates/cap-operator-cros.yaml')).to(cds.utils.path.join(cds.root,'chart/templates/cap-operator-cros.yaml'))
const valuesYaml = yaml.parse(await cds.utils.read(cds.utils.path.join(cds.root, 'chart/values.yaml')))
const hasIas = getServiceInstanceKeyName(valuesYaml['serviceInstances'], 'identity') != null
const hasXsuaa = getServiceInstanceKeyName(valuesYaml['serviceInstances'], 'xsuaa') != null

// Create _helpers.tpl
await cds.utils.write(getHelperTpl({
hasXsuaa: hasXsuaa
}, false)).to(cds.utils.path.join(cds.root, 'chart/templates/_helpers.tpl'))

// Create domain.yaml
await cds.utils.write(getDomainCroYaml({
hasIas: hasIas
})).to(cds.utils.path.join(cds.root, 'chart/templates/domain.yaml'))

// Create cap-operator-cros.yaml
// Only filling those fields in the project input struct that are required to create CAPApplication CR
// Workloads will be filled during transformValuesAndFillCapOpCroYaml function call
await cds.utils.write(getConfigurableCapOpCroYaml({
hasXsuaa: hasXsuaa,
hasIas: hasIas,
isService: isServiceOnlyChart(cds.utils.path.join(cds.root, 'chart'))
})).to(cds.utils.path.join(cds.root, 'chart/templates/cap-operator-cros.yaml'))

// Copy values.schema.json
await cds.utils.copy(cds.utils.path.join(__dirname, '../files/configurableTemplatesChart/values.schema.json')).to(cds.utils.path.join(cds.root,'chart', 'values.schema.json'))
await cds.utils.copy(cds.utils.path.join(__dirname, '../files/configurableTemplatesChart/values.schema.json')).to(cds.utils.path.join(cds.root, 'chart', 'values.schema.json'))

// Add annotation to chart.yaml
const chartYaml = yaml.parse(await cds.utils.read(cds.utils.path.join(cds.root, 'chart/Chart.yaml')))
chartYaml['annotations']['app.kubernetes.io/part-of'] = 'cap-operator-configurable-templates'
await cds.utils.write(yaml.stringify(chartYaml)).to(cds.utils.path.join(cds.root, 'chart/Chart.yaml'))

// Transform
// Transform CAPApplicationVersion CR from values.yaml
await transformValuesAndFillCapOpCroYaml()

if (option === '--with-runtime-yaml' && runtimeYamlPath) {
Expand All @@ -140,14 +170,14 @@ async function convertToconfigurableTemplateChart(option, runtimeYamlPath) {
}

async function generateRuntimeValues(option, inputYamlPath) {
if (!((cds.utils.exists('chart') && isCAPOperatorChart(cds.utils.path.join(cds.root,'chart'))))) {
if (!((cds.utils.exists('chart') && isCAPOperatorChart(cds.utils.path.join(cds.root, 'chart'))))) {
throw new Error("No CAP Operator chart found in the project. Please run 'cds add cap-operator --force' to add the CAP Operator chart folder.")
}

let answerStruct = {}
const { appName, appDescription } = getAppDetails()
const isConfigurableTempChart = isConfigurableTemplateChart(cds.utils.path.join(cds.root,'chart'))
const isServiceOnly = isServiceOnlyChart(cds.utils.path.join(cds.root,'chart'))
const isConfigurableTempChart = isConfigurableTemplateChart(cds.utils.path.join(cds.root, 'chart'))
const isServiceOnly = isServiceOnlyChart(cds.utils.path.join(cds.root, 'chart'))

if (option === '--with-input-yaml' && inputYamlPath) {

Expand Down Expand Up @@ -190,8 +220,16 @@ async function generateRuntimeValues(option, inputYamlPath) {
const valuesYaml = yaml.parse(await cds.utils.read(cds.utils.path.join(cds.root, 'chart/values.yaml')))

//get saas-registry and xsuaa service keys
answerStruct['saasRegistryKeyName'] = getServiceInstanceKeyName(valuesYaml['serviceInstances'], 'saas-registry') || 'saas-registry'
answerStruct['xsuaaKeyName'] = getServiceInstanceKeyName(valuesYaml['serviceInstances'], 'xsuaa') || 'xsuaa'
const xsuaaServiceInstanceKey = getServiceInstanceKeyName(valuesYaml['serviceInstances'], 'xsuaa')
if (xsuaaServiceInstanceKey == null) {
answerStruct['hasXsuaa'] = false
answerStruct['subscriptionManagerKeyName'] = getServiceInstanceKeyName(valuesYaml['serviceInstances'], 'subscription-manager') || 'subscription-manager'
answerStruct['identityKeyName'] = getServiceInstanceKeyName(valuesYaml['serviceInstances'], 'identity') || 'identity'
} else {
answerStruct['hasXsuaa'] = true
answerStruct['saasRegistryKeyName'] = getServiceInstanceKeyName(valuesYaml['serviceInstances'], 'saas-registry') || 'saas-registry'
answerStruct['xsuaaKeyName'] = getServiceInstanceKeyName(valuesYaml['serviceInstances'], 'xsuaa') || 'xsuaa'
}

answerStruct['isApp'] = !isServiceOnly
answerStruct['isService'] = isServiceOnly
Expand Down Expand Up @@ -220,7 +258,7 @@ function updateWorkloadEnv(runtimeValuesYaml, valuesYaml, answerStruct) {

const cdsConfigHana = Mustache.render('{"requires":{"cds.xt.DeploymentService":{"hdi":{"create":{"database_id":"{{hanaInstanceId}}"}}}}}', answerStruct)

if ((workloadDetails?.deploymentDefinition?.type === 'CAP' || workloadDetails?.deploymentDefinition?.type === 'service') && answerStruct['hanaInstanceId']) {
if ((workloadDetails?.deploymentDefinition?.type === 'CAP' || workloadDetails?.deploymentDefinition?.type === 'Service') && answerStruct['hanaInstanceId']) {
updateCdsConfigEnv(runtimeValuesYaml, workloadKey, 'deploymentDefinition', cdsConfigHana)
}

Expand All @@ -242,14 +280,6 @@ function updateWorkloadEnv(runtimeValuesYaml, valuesYaml, answerStruct) {
}
}

function getServiceInstanceKeyName(serviceInstances, offeringName) {
for (const key in serviceInstances) {
if (serviceInstances[key].serviceOfferingName === offeringName)
return key
}
return null
}

function updateCdsConfigEnv(runtimeValuesYaml, workloadKey, workloadDefintion, cdsConfigHana) {
const index = runtimeValuesYaml['workloads'][workloadKey][workloadDefintion]['env'].findIndex(e => e.name === 'CDS_CONFIG')
if (index > -1) {
Expand Down Expand Up @@ -285,7 +315,7 @@ async function getShootDomain() {

kubectl.on('close', () => { resolve() })
})
} catch (error) {}
} catch (error) { }

return domain
}
Expand Down
12 changes: 9 additions & 3 deletions files/approuter.yaml.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,22 @@ workloads:
consumedBTPServices:
{{#hasXsuaa}}
- {{appName}}-uaa-bind
{{#isApp}}
- {{appName}}-saas-registry-bind
{{/isApp}}
{{/hasXsuaa}}
{{#hasIas}}
- {{appName}}-identity-bind
{{#isApp}}
- {{appName}}-subscription-manager-bind
{{/isApp}}
{{/hasIas}}
{{#hasDestination}}
- {{appName}}-destination-bind
{{/hasDestination}}
{{#hasHTML5Repo}}
- {{appName}}-html5-repo-runtime-bind
{{/hasHTML5Repo}}
{{#hasMultitenancy}}
- {{appName}}-saas-registry-bind
{{/hasMultitenancy}}
deploymentDefinition:
type: Router
image:
Expand Down
18 changes: 13 additions & 5 deletions files/commonTemplates/service-instance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,20 @@ spec:

{{- if or $serviceSpec.parameters $serviceSpec.jsonParameters }}
{{- $parameters := $serviceSpec.parameters | default (dict) }}
{{- if and (eq $serviceSpec.serviceOfferingName "xsuaa") (or (not (get (get $parameters "oauth2-configuration" | default (dict)) "redirect-uris")) (eq (len (get (get $parameters "oauth2-configuration" | default (dict)) "redirect-uris")) 0)) }}
{{- $redirectUris := include "redirectUris" $ | fromJson }}
{{- $oauth2Cfg := merge ($redirectUris) (get $parameters "oauth2-configuration" | default (dict)) }}
{{- $_ := set $parameters "oauth2-configuration" $oauth2Cfg }}
{{- end }}
{{- $jsonParameters := $serviceSpec.jsonParameters | default "{}" }}

{{- if or (eq $serviceSpec.serviceOfferingName "xsuaa") (eq $serviceSpec.serviceOfferingName "identity") }}
{{- $missingRedirects := or
(not (get (get $parameters "oauth2-configuration" | default (dict)) "redirect-uris"))
(eq (len (get (get $parameters "oauth2-configuration" | default (dict)) "redirect-uris")) 0)
-}}

{{- if $missingRedirects }}
{{- $redirectUris := include "redirectUris" (dict "serviceOfferingName" $serviceSpec.serviceOfferingName "context" $) | fromJson }}
{{- $oauth2Cfg := merge $redirectUris (get $parameters "oauth2-configuration" | default (dict)) }}
{{- $_ := set $parameters "oauth2-configuration" $oauth2Cfg }}
{{- end }}
{{- end }}
parameters: {{- $jsonParameters | fromJson | merge $parameters | toYaml | nindent 4 }}
{{- end }}

Expand Down
4 changes: 4 additions & 0 deletions files/configurableTemplatesChart/values.yaml.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ workloads:
{{/isApp}}
contentDeploy:
image:
{{#hasAms}}
amsDeployer:
image:
{{/hasAms}}
27 changes: 27 additions & 0 deletions files/ias.yaml.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
serviceInstances:
identity:
name: {{appName}}-identity
serviceOfferingName: identity
servicePlanName: application
parameters:
display-name: {{appName}}
multi-tenant: true
xsuaa-cross-consumption: true
{{#hasAms}}
authorization:
enabled: true
{{/hasAms}}
oauth2-configuration:
redirect-uris: []
post-logout-redirect-uris: []
home-url: ""

serviceBindings:
identity:
name: {{appName}}-identity-bind
serviceInstanceName: {{appName}}-identity
secretName: {{appName}}-identity-bind-secret
secretKey: credentials
parameters:
credential-type: X509_GENERATED
app-identifier: {{appName}}
31 changes: 30 additions & 1 deletion files/runtime-values.yaml.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
serviceInstances:
{{#hasXsuaa}}
{{#isApp}}
{{saasRegistryKeyName}}:
parameters:
xsappname: {{appName}}
Expand All @@ -13,10 +15,37 @@ serviceInstances:
getDependencies: "https://{{appName}}.{{clusterDomain}}/callback/v1.0/dependencies"
{{/isService}}
onSubscription: "https://{{capOperatorSubdomain}}.{{clusterDomain}}/provision/tenants/{tenantId}"
{{/isApp}}
{{xsuaaKeyName}}:
parameters:
xsappname: {{appName}}

{{/hasXsuaa}}
{{^hasXsuaa}}
{{#isApp}}
{{subscriptionManagerKeyName}}:
parameters:
appName: {{appName}}
commercialAppName: {{appName}}
displayName: ({{appName}})
description: ({{appDescription}})
category: CAP
appCallbacks:
dependenciesCallbacks:
url: "https://{{providerSubdomain}}.{{appName}}.{{clusterDomain}}/v1.0/callback/tenants/{app_tid}/dependencies"
subscriptionCallbacks:
url: "https://{{capOperatorSubdomain}}.{{clusterDomain}}/sms/provision/tenants/{app_tid}"
{{/isApp}}
{{identityKeyName}}:
parameters:
display-name: {{appName}}
home-url: "https://$BTP_SUBDOMAIN.{{appName}}.{{clusterDomain}}"
{{/hasXsuaa}}
{{^hasXsuaa}}
serviceBindings:
{{identityKeyName}}:
parameters:
app-identifier: {{appName}}
{{/hasXsuaa}}
app:
domains:
primary: {{appName}}.{{clusterDomain}}
Expand Down
4 changes: 3 additions & 1 deletion files/saas-registry.yaml.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ serviceInstances:
displayName: ({{appName}})
description: ({{appDescription}})
appUrls:
getDependencies: ""
onSubscription: ""
callbackTimeoutMillis: 300000
onSubscriptionAsync: true
onUnSubscriptionAsync: true
category: "CAP"
category: CAP

serviceBindings:
saasRegistry:
Expand Down
30 changes: 30 additions & 0 deletions files/subscription-manager.yaml.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
serviceInstances:
subscriptionManager:
name: {{appName}}-subscription-manager
serviceOfferingName: subscription-manager
servicePlanName: provider
parameters:
iasServiceInstanceName: {{appName}}-identity
applicationType: application
appName: {{appName}}
commercialAppName: {{appName}}
displayName: ({{appName}})
description: ({{appDescription}})
category: CAP
appCallbacks:
dependenciesCallbacks:
url: ""
subscriptionCallbacks:
url: ""
async:
subscribeEnable: true
unSubscribeEnable: true
timeoutInMillis: 300000

serviceBindings:
subscriptionManager:
name: {{appName}}-subscription-manager-bind
serviceInstanceName: {{appName}}-subscription-manager
secretName: {{appName}}-subscription-manager-bind-secret
secretKey: credentials
parameters: {}
Loading
Loading