Skip to content

Commit

Permalink
Merge pull request #274 from vshn/cloud-billing-component
Browse files Browse the repository at this point in the history
Add cloud billing component
  • Loading branch information
TheBigLee authored Dec 20, 2023
2 parents 3c77b2e + f353d13 commit 1d2da10
Show file tree
Hide file tree
Showing 157 changed files with 3,647 additions and 8 deletions.
4 changes: 2 additions & 2 deletions component/.cruft.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"template": "https://github.com/projectsyn/commodore-component-template.git",
"commit": "f3e9e403454f4f46a18608d0e4d2c2c96b593d3b",
"commit": "8b2395f87fd723628a4318f2dfba36519777ec72",
"checkout": "main",
"context": {
"cookiecutter": {
"name": "AppCat",
"slug": "appcat",
"parameter_key": "appcat",
"test_cases": "defaults exoscale cloudscale openshift vshn apiserver controllers minio",
"test_cases": "defaults exoscale cloudscale openshift vshn apiserver controllers minio cloudscale-metrics-collector-cloud cloudscale-metrics-collector-managed exoscale-metrics-collector-cloud exoscale-metrics-collector-managed",
"add_lib": "n",
"add_pp": "n",
"add_golden": "y",
Expand Down
8 changes: 8 additions & 0 deletions component/.github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ jobs:
- apiserver
- controllers
- minio
- cloudscale-metrics-collector-cloud
- cloudscale-metrics-collector-managed
- exoscale-metrics-collector-cloud
- exoscale-metrics-collector-managed
defaults:
run:
working-directory: ${{ env.COMPONENT_NAME }}
Expand All @@ -62,6 +66,10 @@ jobs:
- apiserver
- controllers
- minio
- cloudscale-metrics-collector-cloud
- cloudscale-metrics-collector-managed
- exoscale-metrics-collector-cloud
- exoscale-metrics-collector-managed
defaults:
run:
working-directory: ${{ env.COMPONENT_NAME }}
Expand Down
2 changes: 1 addition & 1 deletion component/Makefile.vars.mk
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ KUBENT_IMAGE ?= ghcr.io/doitintl/kube-no-trouble:latest
KUBENT_DOCKER ?= $(DOCKER_CMD) $(DOCKER_ARGS) $(root_volume) --entrypoint=/app/kubent $(KUBENT_IMAGE)

instance ?= defaults
test_instances = tests/defaults.yml tests/exoscale.yml tests/cloudscale.yml tests/openshift.yml tests/vshn.yml tests/apiserver.yml tests/controllers.yml tests/minio.yml
test_instances = tests/defaults.yml tests/exoscale.yml tests/cloudscale.yml tests/openshift.yml tests/vshn.yml tests/apiserver.yml tests/controllers.yml tests/minio.yml tests/cloudscale-metrics-collector-cloud.yml cloudscale-metrics-collector-managed.yml tests/exoscale-metrics-collector-cloud.yml tests/component/exoscale-metrics-collector-managed.yml
4 changes: 4 additions & 0 deletions component/class/appcat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ parameters:
- ${_base_directory}/component/billing.jsonnet
input_type: jsonnet
output_path: appcat/
- input_paths:
- ${_base_directory}/component/cloud_billing.jsonnet
input_type: jsonnet
output_path: appcat/cloud_billing

- input_paths:
- ${_base_directory}/component/appcat_sli_exporter.jsonnet
Expand Down
57 changes: 57 additions & 0 deletions component/class/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ parameters:
registry: ghcr.io
repository: appuio/appuio-reporting
tag: v0.1.1
collector:
registry: ghcr.io
repository: vshn/billing-collector-cloudservices
tag: v3.0.0

=_crd_version: ${appcat:images:appcat:tag}

Expand All @@ -74,6 +78,7 @@ parameters:
billing:
namespace: ${appcat:namespace}
tenantID: ${cluster:tenant}
clusterID: ${cluster:name}
instanceUOM: uom_uom_45_1e112771
enableMockOrgInfo: false
salesOrder: "" # TODO: Update once the fact is available in Leutenant
Expand All @@ -83,6 +88,55 @@ parameters:
schedule: 30 * * * *
# Deploy metering prometheus rules
meteringRules: false
cloud:
secrets:
exoscale:
credentials:
stringData:
EXOSCALE_API_KEY: "?{vaultkv:${cluster:tenant}/${cluster:name}/billing-collector-cloudservices/exoscale-key}"
EXOSCALE_API_SECRET: "?{vaultkv:${cluster:tenant}/${cluster:name}/billing-collector-cloudservices/exoscale-secret}"
KUBERNETES_SERVER_URL: "?{vaultkv:${cluster:tenant}/${cluster:name}/billing-collector-cloudservices/cluster-server}"
KUBERNETES_SERVER_TOKEN: "?{vaultkv:${cluster:tenant}/${cluster:name}/billing-collector-cloudservices/cluster-token}"
cloudscale:
credentials:
stringData:
CLOUDSCALE_API_TOKEN: "?{vaultkv:${cluster:tenant}/${cluster:name}/billing-collector-cloudservices/cloudscale-token}"
KUBERNETES_SERVER_URL: "?{vaultkv:${cluster:tenant}/${cluster:name}/billing-collector-cloudservices/cluster-server}"
KUBERNETES_SERVER_TOKEN: "?{vaultkv:${cluster:tenant}/${cluster:name}/billing-collector-cloudservices/cluster-token}"
# Unit of measure map "cloud service value": "odoo16 value"
uom:
GB: uom_uom_71_6f28fc21
GBDay: uom_uom_78_b847edc1
KReq: uom_uom_60_a83156c7
InstanceHour: uom_uom_45_1e112771
exoscale:
enabled: false
dbaas:
enabled: false
# in minutes
collectIntervalMinutes: 15
# Exoscale metrics are generally fast
# and we are less likely to miss and dbaas instances
# in hours
objectStorage:
enabled: false
collectIntervalHours: 23
# Run the tool only after 6 in the morning.+
# in hours
billingHour: 6

cloudscale:
enabled: false
# cloudscale.ch queries are rather slow.
# Also the metrics are fetched for the previous day, won't change often.
# in hours
collectIntervalHours: 23
# Run the tool only after 6 in the morning.+
# in hours
billingHour: 6
# How many days ago to get the metrics
# The result is always 1 day of metrics
days: 1
monitoring:
enabled: true
alerts:
Expand Down Expand Up @@ -111,6 +165,9 @@ parameters:
url: "https://test.central.vshn.ch/api/v2/authentication/oauth2/token"
clientID: "?{vaultkv:${cluster:tenant}/${cluster:name}/billing-collector-cloudservices/odoo-oauth-client-id}"
clientSecret: "?{vaultkv:${cluster:tenant}/${cluster:name}/billing-collector-cloudservices/odoo-oauth-client-secret}"
controlAPI:
url: "?{vaultkv:${cluster:tenant}/${cluster:name}/billing-collector-cloudservices/cloud-api-url}"
token: "?{vaultkv:${cluster:tenant}/${cluster:name}/billing-collector-cloudservices/cloud-api-token}"

defaultRestoreRoleRules:
- apiGroups:
Expand Down
245 changes: 245 additions & 0 deletions component/component/cloud_billing.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
local kap = import 'lib/kapitan.libjsonnet';
local inv = kap.inventory();
local params = inv.parameters.appcat.billing;
local paramsCloud = inv.parameters.appcat.billing.cloud;
local kube = import 'lib/kube.libjsonnet';
local com = import 'lib/commodore.libjsonnet';
local collectorImage = '%(registry)s/%(repository)s:%(tag)s' % inv.parameters.appcat.images.collector;
local component_name = 'billing-collector-cloudservices';
local appuioManaged = if params.salesOrder == '' then false else true;

local labels = {
'app.kubernetes.io/name': component_name,
'app.kubernetes.io/managed-by': 'commodore',
'app.kubernetes.io/component': component_name,
};

local secret(key, suf) = [
if paramsCloud.secrets[key][s] != null then
kube.Secret(s + '-' + key + if suf != '' then '-' + suf else '') {
metadata+: {
namespace: params.namespace,
},
stringData+: {
ODOO_OAUTH_CLIENT_ID: params.odoo.oauth.clientID,
ODOO_OAUTH_CLIENT_SECRET: params.odoo.oauth.clientSecret,
CONTROL_API_URL: params.controlAPI.url,
CONTROL_API_TOKEN: params.controlAPI.token,
},
} + com.makeMergeable(paramsCloud.secrets[key][s])
for s in std.objectFields(paramsCloud.secrets[key])
];

local exoDbaasClusterRole = kube.ClusterRole('appcat:cloudcollector:exoscale:dbaas') + {
rules: [
{
apiGroups: [ '*' ],
resources: [ 'namespaces' ],
verbs: [ 'get', 'list' ],
},
{
apiGroups: [ 'exoscale.crossplane.io' ],
resources: [
'postgresqls',
'mysqls',
'redis',
'opensearches',
'kafkas',
],
verbs: [
'get',
'list',
'watch',
],
},
],
};

local exoObjectStorageClusterRole = kube.ClusterRole('appcat:cloudcollector:exoscale:objectstorage') + {
rules: [
{
apiGroups: [ '*' ],
resources: [ 'namespaces' ],
verbs: [ 'get', 'list' ],
},
{
apiGroups: [ 'exoscale.crossplane.io' ],
resources: [
'buckets',
],
verbs: [
'get',
'list',
'watch',
],
},
],
};

local cloudscaleClusterRole = kube.ClusterRole('appcat:cloudcollector:cloudscale') + {
rules: [
{
apiGroups: [ '' ],
resources: [ 'namespaces' ],
verbs: [ 'get', 'list' ],
},
{
apiGroups: [ 'cloudscale.crossplane.io' ],
resources: [
'buckets',
],
verbs: [
'get',
'list',
'watch',
],
},
],
};

local serviceAccount(name, clusterRole) = {
local sa = kube.ServiceAccount(name) + {
metadata+: {
namespace: params.namespace,
},
},
local rb = kube.ClusterRoleBinding(name) {
roleRef_: clusterRole,
subjects_: [ sa ],
},
sa: sa,
rb: rb,
};

local deployment(name, args, config) =
kube.Deployment(name) {
metadata+: {
labels+: labels,
namespace: params.namespace,
},
spec+: {
template+: {
spec+: {
serviceAccount: name,
containers_:: {
exporter: kube.Container('exporter') {
imagePullPolicy: 'IfNotPresent',
image: collectorImage,
args: args,
envFrom: [
{
configMapRef: {
name: config,
},
},
{
secretRef: {
name: 'credentials-' + name,
},
},
],
},
},
},
},
},
};

local config(name, extraConfig) = kube.ConfigMap(name) {
metadata: {
name: name,
namespace: params.namespace,
},
data: {
ODOO_URL: std.toString(params.odoo.url),
ODOO_OAUTH_TOKEN_URL: std.toString(params.odoo.oauth.url),
CLUSTER_ID: std.toString(params.clusterID),
APPUIO_MANAGED_SALES_ORDER: if appuioManaged then std.toString(params.salesOrder) else '',
UOM: std.toString(paramsCloud.uom),
},
} + extraConfig;

({
local odoo = params.odoo,
assert odoo.oauth != null : 'odoo.oauth must be set.',
assert odoo.oauth.clientID != null : 'odoo.oauth.clientID must be set.',
assert odoo.oauth.clientSecret != null : 'odoo.oauth.clientSecret must be set.',
})
+
(if paramsCloud.exoscale.enabled && paramsCloud.exoscale.dbaas.enabled then {
local name = 'exoscale-dbaas',
local secrets = paramsCloud.secrets.exoscale,
local sa = serviceAccount(name, exoDbaasClusterRole),
local extraConfig = {
data+: {
COLLECT_INTERVAL: std.toString(paramsCloud.exoscale.dbaas.collectIntervalMinutes),
},
},
local cm = config(name + '-env', extraConfig),

assert secrets != null : 'secrets must be set.',
assert secrets.credentials != null : 'secrets.credentials must be set.',
assert secrets.credentials.stringData != null : 'secrets.credentials.stringData must be set.',
assert secrets.credentials.stringData.EXOSCALE_API_KEY != null : 'secrets.credentials.stringData.EXOSCALE_API_KEY must be set.',
assert secrets.credentials.stringData.EXOSCALE_API_SECRET != null : 'secrets.credentials.stringData.EXOSCALE_API_SECRET must be set.',

'10_exoscale_dbaas_secret': std.filter(function(it) it != null, secret('exoscale', 'dbaas')),
'10_exoscale_dbaas_cluster_role': exoDbaasClusterRole,
'10_exoscale_dbaas_service_account': sa.sa,
'10_exoscale_dbaas_role_binding': sa.rb,
'10_exoscale_dbaas_configmap': cm,
'10_exoscale_dbaas_exporter': deployment(name, [ 'exoscale', 'dbaas' ], name + '-env'),
} else {})
+
(if paramsCloud.exoscale.enabled && paramsCloud.exoscale.objectStorage.enabled then {
local name = 'exoscale-objectstorage',
local secrets = paramsCloud.secrets.exoscale,
local sa = serviceAccount(name, exoObjectStorageClusterRole),
local extraConfig = {
data+: {
COLLECT_INTERVAL: std.toString(paramsCloud.exoscale.objectStorage.collectIntervalHours),
BILLING_HOUR: std.toString(paramsCloud.exoscale.objectStorage.billingHour),
},
},
local cm = config(name + '-env', extraConfig),

assert secrets != null : 'secrets must be set.',
assert secrets.credentials != null : 'secrets.credentials must be set.',
assert secrets.credentials.stringData != null : 'secrets.credentials.stringData must be set.',
assert secrets.credentials.stringData.EXOSCALE_API_KEY != null : 'secrets.credentials.stringData.EXOSCALE_API_KEY must be set.',
assert secrets.credentials.stringData.EXOSCALE_API_SECRET != null : 'secrets.credentials.stringData.EXOSCALE_API_SECRET must be set.',

'10_exoscale_object_storage_secret': std.filter(function(it) it != null, secret('exoscale', 'objectstorage')),
'10_exoscale_object_storage_cluster_role': exoObjectStorageClusterRole,
'10_exoscale_object_storage_service_account': sa.sa,
'10_exoscale_object_storage_rolebinding': sa.rb,
'10_exoscale_object_storage_configmap': cm,
'20_exoscale_object_storage_exporter': deployment(name, [ 'exoscale', 'objectstorage' ], name + '-env'),

} else {})
+
(if paramsCloud.cloudscale.enabled then {
local name = 'cloudscale',
local secrets = paramsCloud.secrets.cloudscale,
local sa = serviceAccount(name, cloudscaleClusterRole),
local extraConfig = {
data+: {
COLLECT_INTERVAL: std.toString(paramsCloud.cloudscale.collectIntervalHours),
BILLING_HOUR: std.toString(paramsCloud.cloudscale.billingHour),
DAYS: std.toString(paramsCloud.cloudscale.days),
},
},
local cm = config(name + '-env', extraConfig),

assert secrets != null : 'secrets must be set.',
assert secrets.credentials != null : 'secrets.credentials must be set.',
assert secrets.credentials.stringData != null : 'secrets.credentials.stringData must be set.',
assert secrets.credentials.stringData.CLOUDSCALE_API_TOKEN != null : 'secrets.credentials.stringData.CLOUDSCALE_API_TOKEN must be set.',

'10_cloudscale_secrets': std.filter(function(it) it != null, secret(name, '')),
'10_cloudscale_cluster_role': cloudscaleClusterRole,
'10_cloudscale_service_account': sa.sa,
'10_cloudscale_rolebinding': sa.rb,
'10_cloudscale_configmap': cm,
'20_cloudscale_exporter': deployment(name, [ 'cloudscale', 'objectstorage' ], name + '-env'),
} else {})
1 change: 1 addition & 0 deletions component/component/exoscale_kafka.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ local composition =
comp.FromCompositeFieldPath('spec.parameters.maintenance.timeOfDay', 'spec.forProvider.maintenance.timeOfDay'),
comp.FromCompositeFieldPath('spec.parameters.service.version', 'spec.forProvider.version'),
comp.ToCompositeFieldPath('status.atProvider.version', 'status.version'),
comp.FromCompositeFieldPath('spec.parameters.service.zone', 'metadata.annotations[appcat.vshn.io/cloudzone]'),
],
},
],
Expand Down
Loading

0 comments on commit 1d2da10

Please sign in to comment.