diff --git a/.github/TEMPLATES/secret-mapping-opencrvs-deps.yml b/.github/TEMPLATES/secret-mapping-opencrvs-deps.yml index 16abd61c..ae4343cb 100644 --- a/.github/TEMPLATES/secret-mapping-opencrvs-deps.yml +++ b/.github/TEMPLATES/secret-mapping-opencrvs-deps.yml @@ -33,12 +33,15 @@ kibana-users-secret: - KIBANA_USERNAME - KIBANA_PASSWORD +# Traefik static SSL certificate +# backward compatible with existing implementation, +# See: https://documentation.opencrvs.org/v1.8/setup/3.-installation/3.3-set-up-a-server-hosted-environment/3.3.5-setup-dns-a-records/4.3.2.3-static-tls-certificates traefik-cert: type: tls namespace: traefik data: - - TRAEFIK_CERT: cert - - TRAEFIK_KEY: key + - SSL_CRT: cert + - SSL_KEY: key # If backup is configured then workflow will use GitHub secrets for current environment # If restore is configured then workflow will fetch secrets from source environment (usually production) diff --git a/.github/workflows/deploy-dependencies.yml b/.github/workflows/deploy-dependencies.yml index af6725a9..eb2f15a9 100644 --- a/.github/workflows/deploy-dependencies.yml +++ b/.github/workflows/deploy-dependencies.yml @@ -9,7 +9,7 @@ on: default: "dev" type: choice options: - - "" + - swarm-to-k8s jobs: approve: environment: ${{ inputs.environment }} diff --git a/.github/workflows/deploy-opencrvs.yml b/.github/workflows/deploy-opencrvs.yml index 4fc73a0b..3adf9eeb 100644 --- a/.github/workflows/deploy-opencrvs.yml +++ b/.github/workflows/deploy-opencrvs.yml @@ -19,13 +19,18 @@ on: description: "Tag of the countryconfig image" required: true default: "v1.9.1" + data-seed-enabled: + description: "Enable data seeding during deployment" + required: false + default: "true" + type: boolean environment: description: "Target environment" required: true default: "dev" type: choice options: - - "" + - swarm-to-k8s jobs: approve: @@ -140,6 +145,7 @@ jobs: --set countryconfig.image.tag="$COUNTRYCONFIG_IMAGE_TAG" \ --set countryconfig.image.name="$COUNTRYCONFIG_IMAGE_NAME" \ --set data_seed.env.ACTIVATE_USERS="${{ vars.ACTIVATE_USERS || 'false' }}" \ + --set data_seed.enabled="${{ inputs.data-seed-enabled }}" \ --set hostname=${{ vars.DOMAIN }} 2>&1 ; STATUS=$?; kill $STERN_PID 2>/dev/null || true exit $STATUS diff --git a/.github/workflows/github-to-k8s-sync-env.yml b/.github/workflows/github-to-k8s-sync-env.yml index e288aced..52ca36d3 100644 --- a/.github/workflows/github-to-k8s-sync-env.yml +++ b/.github/workflows/github-to-k8s-sync-env.yml @@ -10,7 +10,7 @@ on: default: "development" type: choice options: - - development + - swarm-to-k8s namespace_template: description: "Secrets mapping template" default: "opencrvs" diff --git a/.github/workflows/k8s-reindex.yml b/.github/workflows/k8s-reindex.yml index cf4dfb9e..9c69b3a0 100644 --- a/.github/workflows/k8s-reindex.yml +++ b/.github/workflows/k8s-reindex.yml @@ -9,7 +9,7 @@ on: default: "dev" type: choice options: - - "" + - swarm-to-k8s workflow_call: inputs: environment: diff --git a/.github/workflows/k8s-reset-data.yml b/.github/workflows/k8s-reset-data.yml index bcb1bdd6..0d8a8d7d 100644 --- a/.github/workflows/k8s-reset-data.yml +++ b/.github/workflows/k8s-reset-data.yml @@ -9,7 +9,7 @@ on: default: "dev" type: choice options: - - "" + - swarm-to-k8s workflow_call: inputs: environment: diff --git a/.github/workflows/k8s-seed-data.yml b/.github/workflows/k8s-seed-data.yml index 7e8fe4d7..aba135a4 100644 --- a/.github/workflows/k8s-seed-data.yml +++ b/.github/workflows/k8s-seed-data.yml @@ -9,7 +9,7 @@ on: default: "dev" type: choice options: - - "" + - swarm-to-k8s workflow_call: inputs: environment: diff --git a/.github/workflows/provision.yml b/.github/workflows/provision.yml index c4b872d5..67c915c3 100644 --- a/.github/workflows/provision.yml +++ b/.github/workflows/provision.yml @@ -9,7 +9,7 @@ on: default: 'dev' type: choice options: - - "" + - swarm-to-k8s tags: description: 'Tags to apply to the provisioned resources' required: true @@ -93,5 +93,6 @@ jobs: # Add --verbose to get more output options: |- --inventory inventory/${{ inputs.environment }}.yml + --verbose ${{ inputs.tags != 'all' && format('--tags={0}', inputs.tags) || '' }} --extra-vars ""${{ steps.ansible-variables.outputs.EXTRA_VARS }}"" diff --git a/.github/workflows/reset-2fa.yml b/.github/workflows/reset-2fa.yml index a67f0737..09bb9a79 100644 --- a/.github/workflows/reset-2fa.yml +++ b/.github/workflows/reset-2fa.yml @@ -13,7 +13,7 @@ on: default: required: true options: - - "" + - swarm-to-k8s jobs: approve: diff --git a/environments/swarm-to-k8s/dependencies/values.yaml b/environments/swarm-to-k8s/dependencies/values.yaml new file mode 100644 index 00000000..4e534b3f --- /dev/null +++ b/environments/swarm-to-k8s/dependencies/values.yaml @@ -0,0 +1,41 @@ +storage_type: host_path + +environment_type: production + +minio: + use_default_credentials: false + +elasticsearch: + use_default_credentials: false + +mongodb: + use_default_credentials: false + +postgres: + use_default_credentials: false + +redis: + auth_mode: acl + +monitoring: + enabled: true + +elastalert: + env: + HTTP_POST2_ALERT_URL: http://countryconfig.opencrvs-swarm-to-k8s.svc.cluster.local:3040/email + +# Backup configuration +backup: + enabled: false + schedule: "0 1 * * *" + backup_server_secret: backup-server-ssh-credentials + backup_server_dir: /home/backup/swarm-to-k8s + + +# Restore configuration +restore: + enabled: false + schedule: "0 0 * * *" + backup_server_secret: backup-server-ssh-credentials + backup_server_dir: /home/backup/ + backup_encryption_secret: restore-encryption-secret \ No newline at end of file diff --git a/environments/swarm-to-k8s/mosip-api/values.yaml b/environments/swarm-to-k8s/mosip-api/values.yaml new file mode 100644 index 00000000..442be8ee --- /dev/null +++ b/environments/swarm-to-k8s/mosip-api/values.yaml @@ -0,0 +1,2 @@ +ingress: + ssl_enabled: true \ No newline at end of file diff --git a/environments/swarm-to-k8s/opencrvs-services/values.yaml b/environments/swarm-to-k8s/opencrvs-services/values.yaml new file mode 100644 index 00000000..49e4221d --- /dev/null +++ b/environments/swarm-to-k8s/opencrvs-services/values.yaml @@ -0,0 +1,58 @@ +######################################################################################## +# Initial configuration file for OpenCRVS installation +######################################################################################## +# Some properties are not defined in this file and should be provided as key/value at +# installation time: +# - hostname: valid DNS name for opencrvs +# - countryconfig.image.name: Countryconfig image repository +# - countryconfig.image.tag: Countryconfig image tag +environment_type: production + +hpa: + enabled: false + +env: + APN_SERVICE_URL: "http://apm-server.opencrvs-deps-swarm-to-k8s.svc.cluster.local:8200" + QA_ENV: true +influxdb: + host: influxdb-0.influxdb.opencrvs-deps-swarm-to-k8s.svc.cluster.local +elasticsearch: + auth_mode: auto + host: elasticsearch.opencrvs-deps-swarm-to-k8s.svc.cluster.local + + +minio: + auth_mode: use_secret + host: minio-0.minio.opencrvs-deps-swarm-to-k8s.svc.cluster.local + +mongodb: + auth_mode: auto + host: mongodb-0.mongodb.opencrvs-deps-swarm-to-k8s.svc.cluster.local + +redis: + auth_mode: use_secret + host: redis-0.redis.opencrvs-deps-swarm-to-k8s.svc.cluster.local + +postgres: + auth_mode: auto + host: postgres-0.postgres.opencrvs-deps-swarm-to-k8s.svc.cluster.local + +imagePullSecrets: + # Default value for credentials created while yarn environment:init + - name: dockerhub-credentials + +countryconfig: + env: + OPENID_PROVIDER_CLAIMS: name,family_name,given_name,middle_name,birthdate,address + OPENID_PROVIDER_CLIENT_ID: mock-client_id + ESIGNET_REDIRECT_URL: https://esignet-mock.swarm-to-k8s.opencrvs.dev/authorize + MOSIP_API_USERINFO_URL: https://mosip-api.swarm-to-k8s.opencrvs.dev/esignet/get-oidp-user-info + secrets: + smtp-config: + - ALERT_EMAIL + - SENDER_EMAIL_ADDRESS + - SMTP_HOST + - SMTP_PASSWORD + - SMTP_PORT + - SMTP_SECURE + - SMTP_USERNAME diff --git a/environments/swarm-to-k8s/traefik/values.yaml b/environments/swarm-to-k8s/traefik/values.yaml new file mode 100644 index 00000000..51a3ba38 --- /dev/null +++ b/environments/swarm-to-k8s/traefik/values.yaml @@ -0,0 +1,86 @@ +# Overwriting https://github.com/traefik/traefik-helm-chart/blob/master/traefik/values.yaml +namespaceOverride: "traefik" +logs: + general: + # "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "PANIC" + level: "INFO" + # format: "common" # For local environment + format: "json" # For server environment + access: + # -- To enable access logs + enabled: true + format: "json" + +ingressRoute: + dashboard: + enabled: false + +# Be explicit that we only use CRDs, not ingress/gw support +providers: + kubernetesCRD: + enabled: true + kubernetesIngress: + enabled: false + kubernetesGateway: + enabled: false + +service: + enabled: true + single: false + type: NodePort + +ports: + web: + port: 8000 + hostPort: 80 + protocol: TCP + nodePort: 30080 + http: + redirections: + entryPoint: + to: websecure + scheme: https + permanent: true + + websecure: + port: 8443 + hostPort: 443 + protocol: TCP + nodePort: 30443 + http: + tls: + enabled: true + certResolver: letsencrypt + + +# šŸ‘‡ Adjust this section if needed +certificatesResolvers: + letsencrypt: + acme: + tlsChallenge: false + httpChallenge: + entryPoint: web + # šŸ‘‡ Provide admin email address + email: admin@opencrvs.org + # Storage for certificates: + storage: /certificates/acme.json + # NOTE: Sometimes Let's Encrypt hit production SSL certificate issuing limits + # If you are having issues, switch to staging + # Staging server + # caServer: https://acme-staging-v02.api.letsencrypt.org/directory + # Production server + caServer: https://acme-v02.api.letsencrypt.org/directory + +deployment: + hostNetwork: true + additionalVolumes: + - name: acme + hostPath: + path: /data/traefik + +additionalVolumeMounts: + - name: acme + mountPath: /certificates + +nodeSelector: + traefik-role: ingress diff --git a/infrastructure/environments/swarm-to-k8s.ts b/infrastructure/environments/swarm-to-k8s.ts new file mode 100644 index 00000000..cfcbaf8e --- /dev/null +++ b/infrastructure/environments/swarm-to-k8s.ts @@ -0,0 +1,64 @@ +import * as path from 'path'; +import kleur from 'kleur' +import { error, info, log, success, warn } from './logger' +import { updateWorkflowEnvironments } from './update-workflows'; +import { generateInventory, copyChartsValues, extractAndModifyUsers, extractWorkerNodes, extractBackupNode, dockerManagerFirst, readYamlFile } from './templates' + + + +(async () => { + const environment_type = process.env.ENVIRONMENT_TYPE || 'production'; + const environment = process.env.ENVIRONMENT || ''; + if (!environment) { + error('\n', 'Environment variable ENVIRONMENT is not set. Exiting.'); + process.exit(1); + } + log('\n'); + log(kleur.bold().underline(`Environment: ${environment} Migrating Swarm configurations to Kubernetes`)) + + const old_inventory_path = process.env.OLD_INVENTORY_PATH || ''; + if (!old_inventory_path) { + error('\n', 'Environment variable OLD_INVENTORY_PATH is not set. Exiting.'); + log('\n', 'Old inventory path is required to read existing Swarm configurations.'); + process.exit(1); + } + const ansible_inventory = path.join(old_inventory_path, environment + '.yml'); + const data = readYamlFile(ansible_inventory) as any; + log(` āœ“ Loaded old inventory file: ${ansible_inventory}`); + const master = dockerManagerFirst(data) || '' + log(` āœ“ Kubernetes API Host (Docker Manager): ${master}`); + const users = extractAndModifyUsers(data); + // console.log(users.forEach((u: any) => console.log(u.name))); + let worker_nodes: string[] = []; + worker_nodes = extractWorkerNodes(data); + log(` āœ“ Worker nodes: ${worker_nodes.join(', ')}`); + let backup_host = ''; + backup_host = extractBackupNode(data); + log(` āœ“ Backup host: ${backup_host}`); + + generateInventory( + environment, + { + worker_nodes: worker_nodes, + users: users, + backup_host: backup_host, + kube_api_host: master + } + ) + + copyChartsValues( + environment, + { + env: environment, + environment_type: environment_type, + // FIXME: In general that should be environment_type, + // Hardcode like this blocks us from being generic: + // https://github.com/opencrvs/opencrvs-core/issues/11171 + is_qa_env: environment !== 'production' ? "true" : "false", + backup_enabled: environment === 'production' ? "true" : "false", + restore_enabled: environment === 'staging' ? "true" : "false", + restore_environment_name: environment === 'staging' ? "production" : "" + } + ) + await updateWorkflowEnvironments(); +})(); \ No newline at end of file diff --git a/infrastructure/environments/templates.ts b/infrastructure/environments/templates.ts index 9d6fbab1..4fad2d4c 100644 --- a/infrastructure/environments/templates.ts +++ b/infrastructure/environments/templates.ts @@ -1,18 +1,60 @@ import fs from "fs"; import path from "path"; -import { log } from './logger' -/** - * Replace placeholders in file content. - * Customize the replacements map to your needs. - */ -function replacePlaceholders(content: string, replacements: Record): string { - let updated = content; - for (const [key, value] of Object.entries(replacements)) { - const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g"); // matches ${KEY} - let clear_value = String(value).replace(/[\x00-\x1F\x7F]/g, ""); // remove control characters - updated = updated.replace(regex, clear_value); +import { log, success, warn } from './logger' +import * as yaml from 'js-yaml'; +import Handlebars from 'handlebars'; + +// Register a helper to increment numbers +Handlebars.registerHelper('data_label_idx', function(value) { + return parseInt(value) + 2; +}); + +export function readYamlFile(filePath: any): any { + const fileContent = fs.readFileSync(filePath, "utf8"); + return yaml.load(fileContent); +} + + +// Extract users from the old inventory +export function extractAndModifyUsers(data: any): any { + if (!data?.all?.vars?.users) { + return { users: [] }; + } + return data.all.vars.users; +} + +export function dockerManagerFirst(data: any): string { + if (!data?.['docker-manager-first']?.hosts) { + console.log(data); + throw new Error('Invalid YAML structure: missing docker-manager-first.hosts'); + } + const hosts = data['docker-manager-first'].hosts; + const dockerManagerFirst = Object.values(hosts) + .filter((host: any) => host.ansible_host) + .map((host: any) => host.ansible_host); + return dockerManagerFirst.length === 1 ? dockerManagerFirst[0] : ''; +} + +export function extractBackupNode(data: any): string { + if (!data?.['backups']?.hosts) { + return ''; + } + const hosts = data['backups'].hosts; + const backupHostEntry = Object.values(hosts) + .filter((host: any) => host.ansible_host) + .map((host: any) => host.ansible_host); + return backupHostEntry.length === 1 ? backupHostEntry[0] : ''; +} + +export function extractWorkerNodes(data: any): string[] { + if (!data?.['docker-workers']?.hosts) { + return []; } - return updated; + const hosts = data['docker-workers'].hosts; + const worker_hosts = Object.values(hosts) + .filter((host: any) => host.ansible_host) + .map((host: any) => host.ansible_host); + return worker_hosts; } /** @@ -38,22 +80,23 @@ export function copyChartsValues(env: string, replacements: Record){ // Check if output file already exists if (fs.existsSync(outputPath)) { - log(`āš ļø Skipping ${templatePath}, file already exists at ${outputPath}`); + warn(` āš ļø Skipping ${templatePath}, file already exists at ${outputPath}`); return; } let template = fs.readFileSync(templatePath, "utf-8"); - // Extract worker nodes and backup host from values - let worker_nodes = values['worker_nodes'].map((e: string) => String(e) - .replace(/[\x00-\x1F\x7F]/g, "")) - .filter((e: string) => e.length > 0); - - // Generate workers block - if (worker_nodes && worker_nodes.length > 0) { - let workersBlock = ` - # Workers section is optional, for single node cluster feel free to remove this section - # section can be added later - # more workers can be added later as well - workers: - hosts:`; - - worker_nodes.forEach((host: string, index: number) => { - const isFirstWorker = index === 0; - workersBlock += ` - worker${index}: - ansible_host: ${host}${isFirstWorker ? ` - labels: - # By default all datastores are deployed to worker node with role data1 - role: data1` : ''} -`; - }); - - template = template.replace('{{WORKERS_BLOCK}}', workersBlock); - } else { - // No worker nodes, remove the placeholder - template = template.replace('{{WORKERS_BLOCK}}', ''); - } - - - // Generate backup block if backup_host is provided - const backupHost = String(values['backup_host']).replace(/[\x00-\x1F\x7F]/g, ""); - let backupBlock = ''; - if (backupHost.length > 0) { - backupBlock = ` - # backup section is optional, feel free to remove if backups are not enabled - # section can be added later - backup: - hosts: - backup1: - ansible_host: ${backupHost} -`; - } - template = template.replace('{{BACKUP_BLOCK}}', backupBlock); + const tpl = Handlebars.compile(template); + values['single_node'] = (values['worker_nodes'].length > 0 || values['backup_host']) ? "false" : "true"; + console.log(values); + const updated = tpl(values); - // Determine if single-node or multi-node - values['single_node'] = (worker_nodes.length > 0 || backupHost) ? "false" : "true"; - const updated = replacePlaceholders(template, values); - values fs.mkdirSync(path.dirname(outputPath), { recursive: true }); fs.writeFileSync(outputPath, updated); - log(`āœ… Generated inventory file at ${outputPath}`); + log(`\nāœ… Generated inventory file at ${outputPath}\n`); } diff --git a/infrastructure/environments/templates/inventory/inventory.template.yml b/infrastructure/environments/templates/inventory/inventory.template.yml index a8242973..fa06b05c 100644 --- a/infrastructure/environments/templates/inventory/inventory.template.yml +++ b/infrastructure/environments/templates/inventory/inventory.template.yml @@ -36,7 +36,25 @@ all: # Allowed states: # - present: user is allowed to login # - absent: user account is disabled - users: [] + # users: [] + {{#if users}} + users: + {{#each users as |user|}} + - name: {{user.name}} + ssh_keys: + {{#each user.ssh_keys as |key| }} + - {{key}} + {{/each}} + state: {{user.state}} + {{#if user.sudoer}} + role: admin + {{else}} + role: operator + {{/if}} + {{/each}} + {{else}} + users: [] + {{/if}} children: master: @@ -50,8 +68,27 @@ all: labels: # traefik-role label is used to identify where to deploy traefik traefik-role: ingress - -{{WORKERS_BLOCK}} - - -{{BACKUP_BLOCK}} \ No newline at end of file + # By default all datastores are deployed to node with role data1 + role: data1 + {{#if worker_nodes}} + # Workers section is optional, for single node cluster feel free to remove this section + # section can be added later + # more workers can be added later as well + workers: + hosts: + {{#each worker_nodes as |host idx|}} + worker{{idx}}: + ansible_host: {{host}} + labels: + # Labels have index + 2 for backward compatibility with existing swarm setup + role: data{{data_label_idx idx}} + {{/each}} + {{/if}} + {{#if backup_host}} + # backup section is optional, feel free to remove if backups are not enabled + # section can be added later + backup: + hosts: + backup0: + ansible_host: {{backup_host}} + {{/if}} diff --git a/infrastructure/environments/update-workflows.ts b/infrastructure/environments/update-workflows.ts index 1c997255..b9942bc1 100644 --- a/infrastructure/environments/update-workflows.ts +++ b/infrastructure/environments/update-workflows.ts @@ -3,7 +3,7 @@ import { readFileSync, writeFileSync, statSync, existsSync } from 'fs'; import { basename, join } from 'path'; import * as glob from 'glob'; import * as yaml from 'js-yaml'; - +import { error, info, log, success, warn } from './logger' interface WorkflowConfig { workflows: string[]; path: string; @@ -18,9 +18,7 @@ async function extractInfrastructureNames(): Promise { console.log('āš ļø Warning: No environment directories found in infrastructure/server-setup/inventory/'); return []; } - console.log('List of existing infrastructure configurations:'); - console.log(infraEnvironments.join(', ')); - + log('šŸ” Found infrastructure configurations:', infraEnvironments.join(', ')); return infraEnvironments; } @@ -37,9 +35,7 @@ async function extractEnvironmentNames(): Promise { return []; } - console.log('\nList of existing environment configurations:'); - console.log(environments.join(', ')); - + log('šŸ” Found OpenCRVS configurations:', environments.join(', ')); return environments; } @@ -72,8 +68,6 @@ async function updateWorkflows( const { workflows } = config; for (const workflowPath of workflows) { - console.log(`\nUpdating ${workflowPath} with: [${envList.join(', ')}]`); - try { const fileContents = readFileSync(workflowPath, 'utf8'); @@ -87,8 +81,9 @@ async function updateWorkflows( const updatedContent = updateOptionsInYaml(fileContents, envList); writeFileSync(workflowPath, updatedContent, 'utf8'); - console.log(`āœ“ Successfully updated ${workflowPath}`); + log(` āœ“ Successfully updated ${workflowPath}`); } catch (error) { + console.error(`\nāš ļø Error updating ${workflowPath} with environments: [${envList.join(', ')}]`); console.error(`āœ— Failed to update ${workflowPath}:`, error); throw error; } @@ -96,16 +91,12 @@ async function updateWorkflows( } export async function updateWorkflowEnvironments(): Promise { - try { - console.log('šŸ”„ Updating workflow environments...\n'); - + try { // Extract infrastructure names const infraEnvironments = await extractInfrastructureNames(); - // Extract environment names (only directories) - const environments = await extractEnvironmentNames(); - // Update workflows with infrastructure configurations + console.log('šŸ”„ Updating infrastructure workflows:'); await updateWorkflows(infraEnvironments, { workflows: [ '.github/workflows/provision.yml', @@ -114,7 +105,9 @@ export async function updateWorkflowEnvironments(): Promise { path: 'on.workflow_dispatch.inputs.environment.options' }); - console.log(`\nšŸ“‹ Updating workflows...`); + // Extract environment names (only directories) + const environments = await extractEnvironmentNames(); + const workflows = [ '.github/workflows/deploy-dependencies.yml', '.github/workflows/deploy-opencrvs.yml', @@ -123,16 +116,14 @@ export async function updateWorkflowEnvironments(): Promise { '.github/workflows/k8s-reindex.yml', '.github/workflows/github-to-k8s-sync-env.yml' ]; - + log("šŸ“‹ Updating OpenCRVS application workflows:"); await updateWorkflows(environments, { workflows, path: 'on.workflow_dispatch.inputs.environment.options' }); - console.log('\nāœ… All workflows updated successfully!'); - console.log('\nšŸ’” Review the changes and commit them when ready.'); - + success('āœ… All workflows updated successfully!'); } catch (error) { console.error('\nāŒ Error updating workflows:', error); process.exit(1); diff --git a/infrastructure/server-setup/inventory/swarm-to-k8s.yml b/infrastructure/server-setup/inventory/swarm-to-k8s.yml new file mode 100644 index 00000000..e219ef5b --- /dev/null +++ b/infrastructure/server-setup/inventory/swarm-to-k8s.yml @@ -0,0 +1,115 @@ +all: + vars: + + # Domain/IP address for remote access to your cluster API + # Domain/IP address will be added as main endpoint to your ~/.kube/config + # - If you are behind VPN, use private IP address + # - If your server is exposed (not recommeded), use public IP address + # - If you would like to run kubectl commands from the remote server, leave this field empty + # kube_api_endpoint: '' + + # IMPORTANT: If master VM has multiple ethernet interfaces, put private IP address at kube_api_address + # kube_api_host: 10.10.10.10 + kube_api_host: 46.224.251.95 + + # Default ansible provision user, keep as is + ansible_user: provision + # single_node: + # For development/qa/testing/staging keep true + # For production keep false + # Defaults production configuration: + # - master node + # - 2 worker nodes + single_node: true + + # users: Add as many users as you wish + # Configuration example + # - name: + # ssh_keys: + # - + # - + # state: present + # role: admin + # Allowed roles: + # - operator: grant read only access to OS and full access to kubernetes cluster + # - admin: grant full access to OS and kubernetes cluster + # Allowed states: + # - present: user is allowed to login + # - absent: user account is disabled + # users: [] + users: + - name: pyry + ssh_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJBcrSLLdrkLrhqNQi7Uo/ZIWXb1y4kc0vGb16e2s0Jq pyry@opencrvs.org + state: present + role: admin + - name: tameem + ssh_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGUprcQyUFYwRto0aRpgriR95C1pgNxrQ0lEWEe1D8he haidertameem@gmail.com + state: present + role: admin + - name: riku + ssh_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDWIF63S4f3z9wQMvWibmvl7MPuJ6EVrkP0HuvgNhcs/4DZYMcR/GRBvV4ldOSYMlBevIXycgGzNDxKJgENUuwIWanjBu7uVAHyD6+cIRD1h63qq7Cjv/2HYTfBDKOrKzPOhA6zWvKO0ZGWsjRXk5LWMCbKOkvKJCxOpj/NVBxeE4FTK5YADYPV3OSsmBtqTHrVLm2sMmShU/2hMYYswWkobidjX65+nK/X+3C+yJbHwiydVvn+QCrFlFfCLPWKe8rUpOxyxofPqWVQh6CHhHfT8okaOc9sOE8Qeip9ljo84DftJh3Xm3ynOdWK1hH2BvRvxNadWqcE1qECbkg4tx2x riku.rouvila@gmail.com + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDGfWxxQHJv6Md/vBVoDH2UNm/uYgIBlFpP1mfh2Yj6jRNiQ/TQrfwpTawq0Sg+UW4LfYk5yxttsZ0h6L/v6PLiawgbMtf2ZqSviRTYSZTSihkK2zLmeJA2ByBCh57w4tR6IGqJK4w0kjYQSaaU6V5skQ4u+gnLQoKtkVQ4K34EFXAiIur96tLwjwDd/xCm+9T91+cAxGLv8Pe0PjirjwnvktUtzpgOhedkYK7KX0l8SKxQXUK6Ul2/QbpGO3rmguzEdtrl3Dw1TAEfu2njXbNGVQ+JWV9htH+ymsMIGoeumJRaaAZ4AXLlQPBCxTXcdQDuAjfFDPuppms/h7qB1S4Aioz7zqyd7pL7Z6Z8mJBZZlP3PsfGvADM2CdShpbL4HAa+n9miNNSYcJ7cHvC/zCitNjfaEYLVYkB5G+ggeK8Ss/MDcnsh3YFB8WnT582zt/TTJda5n+5Q7tquc1m+61t2gEKKTfBoDft9UYW2/4ViHj3ROL2Oyj7udrh/oAqV8M= riku@MBP16inch2231 + state: present + role: admin + - name: euan + ssh_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDECqHO65UpyrrO8uueD06RxGaVVq22f152Rf8qVQQAAIGAMu6gCs7ztlZ8a3yQgSEIjM/Jl1/RqIVs6CziTEef74nLFTZ5Ufz3CLRVgdebBeSBEmhTfTUV0HLkSyNzwKFpuzJxucGd72ulPvEp6eHvyJAPJz37YcU8cjaL1v05T6s2ee99li35GlDDtCzfjVV4ZPAg5JdfWuTj41RAVC0LQhk2/NB4qEu37UxGGjhRFSjBEsS5LxI9QfvgrsHpl/VOn+soH7ZkK7kS6qRgNP/uYsXRWXhHaamcl5OX68gJWTbrW6c7PCqlbCWGnsHJswCmqPIthwXXMfC7ULDNLSKG6mslAt5Dyc8/MCr3vTW7pDyr2d0FvvY86SMQUggxv3qF7TZewqfX1bhK0fMLarIxVMQ1RFo//wN9QGA+2we8rxd2Y1Kr1DBuJyuwXPfv+Exo8yNYQ+x/AYH5k6UVcSYuaB8eYmplG2KQCxt8RBFtoChrwOKNRWLqXdKyfpdp5XmnnWxPvR95gf3h3yLocVYkF0i0uvKKJ0vt8J0Ezfkdfow0B1kUg5bPXKJROX7PwbaCPdYcxyDaO6wwOigRnSmoFvkH1pLb4j1RQAXcX531CHgfN6Izi/h0mpMS4bnyIUcv2GQr+h4z4TxcCtj7qpH2y6yw7XG12jVh7TfeesXG2Q== euanmillar77@gmail.com + state: present + role: admin + - name: tahmid + ssh_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINUml9O5ySwPtEMD1yGEYHlf9Z3jro97NWAnM9+ew9gn tahmidrahman.dsi@gmail.com + state: present + role: admin + - name: tareq + ssh_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCWQihdKkwxTItN+rwYAX1vBg+8sv59sFsjYoVaO2mzS01rARfh+M+UVqpEv3zFT/3v6Dr5Z5VhzYvvbH8akiGQxURqie9quEi1iBCqcq+LApkMZxNm7yyvexlFsbkKMHsSZyVCzjE2Wt+6fwR1NqkMQgJjZS+b4CB+CUTNP2i6ytUTmck9K5iAOp1Gpm+Xgyvz6ZEJPkAJ16gV7gzNJUt/DSCkCyV8G2BqYLWeR2QxAbKyuf3LzO5i4XZdiZi9o60QAt3A6KGGLazd0UuYdehQDqVwXzwimLeeuZbaPNmwoAy7DeatOdurrWbnL7ytaiPvAbwai6Grt3PhhM41qO+uojnqTdnFdSOEPVIYMR7+mYu9tuwHZcMJIbbvMPD6EvKumD5Ndn5OxiLY/zQF5PuG89pBdTkTzzREvbV1Dkh2hwAIvgavlZl3P64On+4+FAgjrAx5U55khoRAe2FbEvB+EUGwro0bRffiM2NmxkUBraEuT2Xt5K01ZoBU6F4feO0= tareq.aziz@dsinnovators.com + state: present + role: admin + - name: jamil + ssh_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMSNTIIsM0C3uJg3V/Fqh2gi4lvl2y6nenrb2Ft1JlX jamil31415926@gmail.com + state: present + role: admin + - name: ashikul + ssh_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDFr/v3hUGEbc2wQsDLCmqLrwiz964yVrnLZ6kafemjmX8aRGLp1CNFvrZ674SLnXidZGMkx9d5xVvv8IdFR3R50MqSqfolF43MV34/JVHjQHh9Vk4MJT/3GIaeNmr2GQ/38qAmt2BQn1ecnb7FjNO2bFvHokLhm2wCXt+A4avuTgJe0p4e6uu01IHeIzDb5sPzZ3ID0h6jJnjEDcET+Lf5NGpCjn7YKhLhBWSSl9cXQdOGLzNzg3aBk32kgJ1beP1funSeVd0jniJPZeZRC1G/kRdqBUOHKiENtwgquzZxXzdHkZV9+4mF7YGlx6LpQdNuDpW7JADtYNldtdbexdyfrgNoRzKwyMmaKNDbeHd1FsIHSDJmGm9hCoLTM2dEtsGzgghfe0tat8sOWmsj5v2en0V8rKV+w8OQEmHtaQkgMjqmZaAnd8uWiB2xIbrUuax5Pq8zkj37xnfbRxUPOEkMlOUbhh1wzGbqeUEB7nbv/vXZxwC0b7ryMk5egBP+0ZRONsdib9RkSTr3B9uSb7iTOQftdhy+CTqqOq+6s+TyC2qnu12B1WZb9sx9jQl0mBHd9gx/FgYDs8jfIr2vF4jRkejW/moaVqvCd/FLyS91eCMXQjIXdGKWKPUUL7GEBqdZRLnYSJOqgPp9sk1+NEvMabTXlWmoUjaShq8z+o7JsQ== nileeeem36@gmail.com + state: present + role: admin + - name: markus + ssh_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDGvvjFxXqcdKn9kk8VHzm38R3nLWvvwP8W0e3uXxOgby/7LJZx2bosXCZ28FyPTYwVRezHE9lguKiaCo2kxqzNwwx64MzUFRH60sE5cYeH1IqjCBTY3Ht8hkZlYaVoRmsHiqiqogW+bJPo8PBO+ydCh53KUdJFEOXAYvKZ/RfDsWh7/SjeQrQzpRFNeb9keefX+uNNBbKRm9/AEWIHFCGJpDvpJcz3i8hKbRPtXi5OTcEx1Kr4iOMikGXvGzsC1u84qgiy5moeBzpWeROwyJOHRLqPqQ/IHvUkE4F1BXen02G69nHpFdmjTOcjBbT1RzGTeWZs+ehc/kJaS3dUMHd5rSPsimjiCKZ5+wCAyxc5gJlQof71IpHVN4ZDoetH4Lo2bnLdA1YX6DaVU1Fd/6rPWw02DA1OEIhrjJ3Gak87/HUYGNhpZVyIxyNYGXBMPkmHCHCjzjN7sPdMRvkbl5tahD2PoS4172tsO7YYMfAZ/UYYZw745CDxQYIjjfrFRn8= markuslaurila@MacBook-Pro.local + state: present + role: admin + - name: vmudryi + ssh_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINgMcsSBwTE0EbMDRSF1T4vJDcN/5HAjKGbi2DqV7g/Q vmudryi@opencrvs.org + state: present + role: admin + - name: cihan + ssh_keys: + - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEEtz5M5hYKcUehDiCm84BplV+3t1ex8DPjIsMtQEWGv cihan.m.bebek@gmail.com + state: present + role: admin + + children: + master: + hosts: + # Replace master with value returned by command: hostname + master: + # Keep values (ansible_host, ansible_connection) as is + # Ansible is executed on master node + ansible_host: localhost + ansible_connection: local + labels: + # traefik-role label is used to identify where to deploy traefik + traefik-role: ingress + # By default all datastores are deployed to node with role data1 + role: data1 + + + + diff --git a/infrastructure/server-setup/tasks/k8s/install-containerd.yml b/infrastructure/server-setup/tasks/k8s/install-containerd.yml index 6f911c11..e1ffaa73 100644 --- a/infrastructure/server-setup/tasks/k8s/install-containerd.yml +++ b/infrastructure/server-setup/tasks/k8s/install-containerd.yml @@ -5,7 +5,13 @@ purge: true loop: - docker + - docker-ce + - docker-ce-cli + - docker-buildx-plugin + - docker-ce-rootless-extras + - docker-compose-plugin - docker-engine + - python3-docker - docker.io - containerd - runc diff --git a/package.json b/package.json index e33c87d3..22353718 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "husky": {}, "scripts": { "environment:init": "ts-node infrastructure/environments/setup-environment.ts", + "environment:swarm-to-k8s": "ts-node infrastructure/environments/swarm-to-k8s.ts", "environment:upgrade": "yarn environment:init", "prepare": "husky" }, @@ -28,7 +29,8 @@ "ts-node": "^10.9.1", "typescript": "^5.1.6", "js-yaml": "4.1.0", - "glob": "11.0.3" + "glob": "11.0.3", + "handlebars": "^4.7.8" }, "dependencies": { "@types/node": "^24.0.0", diff --git a/yarn.lock b/yarn.lock index 34f2ff28..d6af4c7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -338,6 +338,18 @@ glob@11.0.3: package-json-from-dist "^1.0.0" path-scurry "^2.0.0" +handlebars@^4.7.8: + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + husky@9.1.7: version "9.1.7" resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.7.tgz#d46a38035d101b46a70456a850ff4201344c0b2d" @@ -411,6 +423,11 @@ minimatch@^10.0.3: dependencies: "@isaacs/brace-expansion" "^5.0.0" +minimist@^1.2.5: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + minipass@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" @@ -421,6 +438,11 @@ nan@^2.19.0, nan@^2.23.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.24.0.tgz#a8919b36e692aa5b260831910e4f81419fc0a283" integrity sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg== +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" @@ -488,6 +510,11 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + ssh2@^1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.17.0.tgz#dc686e8e3abdbd4ad95d46fa139615903c12258c" @@ -581,6 +608,11 @@ typescript@^5.1.6: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== +uglify-js@^3.1.4: + version "3.19.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" @@ -626,6 +658,11 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"