antelope-cli 1.2.5 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  # Pinned published antelope-cli version that provides the `onboarding-ca` command.
3
- antelope_cli_version: "1.2.4"
3
+ antelope_cli_version: "1.2.5"
4
4
 
5
5
  # Where web-client-area is cloned on the control node.
6
6
  work_dir: "/tmp/onboard-ca/web-client-area"
@@ -45,7 +45,7 @@ dest_branches: ""
45
45
  # Require a signed-in `op` CLI on the control node (op signin, or OP_SERVICE_ACCOUNT_TOKEN).
46
46
  # Service-account auth requires an explicit vault for `op item get` by name (op errors
47
47
  # with "a vault query must be provided" otherwise) — the items live in the DevOps vault.
48
- onepassword_vault: "DevOps"
48
+ onepassword_vault: "jenkins"
49
49
  onepassword_bitbucket_item: "Bitbucket API - jenkins@xsites.co.il"
50
50
  onepassword_bitbucket_field: "credential"
51
51
  onepassword_consul_item: "New_Consul_Token"
@@ -0,0 +1,211 @@
1
+ // Self-service CRM onboarding — jenkins.xsites.xyz/job/self-service/job/onboarding/onboarding-crm
2
+ //
3
+ // Pipeline-as-Code (this file is the source of truth; the Jenkins job is just a
4
+ // "Pipeline script from SCM" pointer at antelope-cli@master, path ansible/onboarding-crm/Jenkinsfile).
5
+ //
6
+ // Design (best practice, replacing the legacy freestyle+vault-pem+canRoam pattern):
7
+ // - ephemeral containerized agent via `agent { dockerfile }`: the pinned runner image
8
+ // (ansible, node/npx, op, git) is BUILT on the spot fleet from runner.Dockerfile — no
9
+ // registry/ECR needed. Runs on any agent (the whole fleet is docker-capable).
10
+ // - ONE secret: a 1Password service-account token (onepassword-service-account-token) —
11
+ // op fetches the bitbucket/consul/jira tokens from the `jenkins` vault (matches the role)
12
+ // - a Bitbucket SSH key (bitbucket-jenkins-ssh) for the git push to web-crm
13
+ // - parameters named exactly as the configs.ts fields, validated up front
14
+ // - DRY_RUN preview (generate + show configs.ts, no writes) + human approval before any push
15
+ //
16
+ // Provisioning prereqs:
17
+ // - fleet agents on the docker-enabled AMI (the whole fleet carries docker)
18
+ // - credential 'onepassword-service-account-token' : Secret text = OP_SERVICE_ACCOUNT_TOKEN
19
+ // (1Password service account, read-only on the `jenkins` vault)
20
+ // - credential 'bitbucket-jenkins-ssh' : SSH key = jenkins Bitbucket push key
21
+ // (both folder-scoped to self-service/onboarding)
22
+
23
+ pipeline {
24
+ agent {
25
+ dockerfile {
26
+ dir 'ansible/onboarding-crm'
27
+ filename 'runner.Dockerfile'
28
+ additionalBuildArgs '--pull'
29
+ }
30
+ }
31
+
32
+ options {
33
+ timestamps()
34
+ ansiColor('xterm')
35
+ disableConcurrentBuilds()
36
+ buildDiscarder(logRotator(numToKeepStr: '30'))
37
+ }
38
+
39
+ parameters {
40
+ // --- named exactly as configs.ts fields ---
41
+ string(name: 'brandAssetsFolder', defaultValue: '', trim: true,
42
+ description: 'Antelope ID (lowercase alphanumeric). configs.ts brandAssetsFolder / devBrandName / colorTheme / templateTheme / favicon all derive from this. e.g. uzumy')
43
+ string(name: 'title', defaultValue: '', trim: true,
44
+ description: 'Brand name. configs.ts title becomes "<title> CRM". e.g. Excelttra')
45
+ choice(name: 'type', choices: ['CORP_FL', 'IB_FL', 'CORP_AFF_FL'],
46
+ description: 'configs.ts type')
47
+ string(name: 'baseUrl', defaultValue: '', trim: true,
48
+ description: 'configs.ts baseUrl. e.g. https://apicrm.mteeks.com/SignalsCRM/')
49
+ string(name: 'affiliatesApiUrl', defaultValue: '', trim: true,
50
+ description: 'configs.ts affiliatesApiUrl (FE host). e.g. apife.excelttra.com')
51
+ booleanParam(name: 'isArcticBrand', defaultValue: false,
52
+ description: 'configs.ts isArcticBrand')
53
+ // --- onboarding inputs (not configs.ts fields) ---
54
+ string(name: 'languages', defaultValue: 'en', trim: true,
55
+ description: 'Comma-separated 2-letter language codes. e.g. en,ar')
56
+ string(name: 'logo_url', defaultValue: '', trim: true,
57
+ description: 'Logo URL. Must be https://logos-<brandAssetsFolder>.s3.eu-west-1.amazonaws.com/logo-<title>.<ext>. Blank => derived.')
58
+ string(name: 'favicon', defaultValue: '', trim: true,
59
+ description: 'Favicon URL (configs.ts favicon). Must be https://logos-<brandAssetsFolder>.s3.eu-west-1.amazonaws.com/favicon-<title>.<ext>. Blank => derived.')
60
+ string(name: 'jira_key_crm_branding', defaultValue: '', trim: true,
61
+ description: 'Jira key for the branding PRs. e.g. ATLP-47345')
62
+ // --- safety ---
63
+ booleanParam(name: 'DRY_RUN', defaultValue: true,
64
+ description: 'Preview only: generate + show configs.ts, NO branch push / PR / Consul / Jira write')
65
+ }
66
+
67
+ environment {
68
+ ANSIBLE_HOST_KEY_CHECKING = 'False'
69
+ ANSIBLE_FORCE_COLOR = 'true'
70
+ WORK_DIR = '/tmp/onboard-crm/web-crm'
71
+ PLAYBOOK_DIR = 'ansible/onboarding-crm'
72
+ // The dockerfile agent runs the container as the Jenkins UID, which has no home dir
73
+ // (HOME=/), so ansible (~/.ansible/tmp), npm (~/.npm) and git (~/.gitconfig) can't write
74
+ // their dotdirs. Point HOME at the writable, correctly-owned workspace.
75
+ HOME = "${env.WORKSPACE}"
76
+ }
77
+
78
+ stages {
79
+ stage('Validate parameters') {
80
+ steps {
81
+ script {
82
+ def CRM_TYPES = ['CORP_FL', 'IB_FL', 'CORP_AFF_FL']
83
+ def errs = []
84
+ def req = { v, n -> if (!v?.trim()) errs << "${n} is required" }
85
+
86
+ req(params.brandAssetsFolder, 'brandAssetsFolder')
87
+ if (params.brandAssetsFolder && !(params.brandAssetsFolder ==~ /^[a-z0-9]+$/))
88
+ errs << "brandAssetsFolder must be lowercase alphanumeric (the Antelope ID); got '${params.brandAssetsFolder}'"
89
+
90
+ req(params.title, 'title')
91
+
92
+ if (!CRM_TYPES.contains(params.type))
93
+ errs << "type must be one of ${CRM_TYPES}; got '${params.type}'"
94
+
95
+ req(params.baseUrl, 'baseUrl')
96
+ if (params.baseUrl && !(params.baseUrl ==~ /^https:\/\/apicrm\.[^\/]+\/SignalsCRM\/$/))
97
+ errs << "baseUrl must look like https://apicrm.<domain>/SignalsCRM/ ; got '${params.baseUrl}'"
98
+
99
+ req(params.affiliatesApiUrl, 'affiliatesApiUrl')
100
+ if (params.affiliatesApiUrl && !(params.affiliatesApiUrl ==~ /^apife\.[a-z0-9.\-]+$/))
101
+ errs << "affiliatesApiUrl must look like apife.<ca-domain> (no scheme); got '${params.affiliatesApiUrl}'"
102
+
103
+ if (!(params.languages ==~ /^[a-z]{2}(,[a-z]{2})*$/))
104
+ errs << "languages must be comma-separated 2-letter codes; got '${params.languages}'"
105
+
106
+ // logo/favicon must follow logos-<antelope_id>/<kind>-<brand_name>.<ext> (when set; blank => derived)
107
+ def IMG_EXT = ['png', 'jpg', 'jpeg', 'svg', 'webp', 'ico', 'gif']
108
+ def checkImg = { url, kind ->
109
+ if (!url?.trim()) return null
110
+ def m = (url =~ /^https:\/\/logos-([a-z0-9]+)\.s3\.eu-west-1\.amazonaws\.com\/(logo|favicon)-(.+)\.([A-Za-z0-9]+)$/)
111
+ if (!m) return "${kind} must look like https://logos-<id>.s3.eu-west-1.amazonaws.com/${kind}-<brand>.<ext>; got '${url}'"
112
+ def (whole, bucketId, prefix, brand, ext) = m[0]
113
+ if (prefix != kind) return "${kind} filename must start with '${kind}-' (got '${prefix}-')"
114
+ if (bucketId != params.brandAssetsFolder) return "${kind} bucket must be logos-${params.brandAssetsFolder} (got logos-${bucketId})"
115
+ if (brand != params.title) return "${kind} filename brand must be '${params.title}' (got '${brand}')"
116
+ if (!IMG_EXT.contains(ext.toLowerCase())) return "${kind} extension '${ext}' not allowed (${IMG_EXT.join('/')})"
117
+ return null
118
+ }
119
+ [checkImg(params.logo_url, 'logo'), checkImg(params.favicon, 'favicon')].each { if (it) errs << it }
120
+
121
+ if (!(params.jira_key_crm_branding ==~ /^[A-Z][A-Z0-9]+-\d+$/))
122
+ errs << "jira_key_crm_branding must look like ATLP-12345; got '${params.jira_key_crm_branding}'"
123
+
124
+ if (errs) {
125
+ error("Parameter validation failed:\n - " + errs.join("\n - "))
126
+ }
127
+ echo "✅ Parameters valid — antelope_id=${params.brandAssetsFolder}, type=${params.type}, dry_run=${params.DRY_RUN}"
128
+ currentBuild.displayName = "#${env.BUILD_NUMBER} ${params.title}${params.DRY_RUN ? ' (dry-run)' : ''}"
129
+ }
130
+ }
131
+ }
132
+
133
+ stage('Onboard (dry-run preview)') {
134
+ when { expression { params.DRY_RUN } }
135
+ steps {
136
+ withCredentials([string(credentialsId: 'onepassword-service-account-token', variable: 'OP_SERVICE_ACCOUNT_TOKEN')]) {
137
+ sshagent(['bitbucket-jenkins-ssh']) {
138
+ sh label: 'ansible dry-run (no writes)', script: '''
139
+ cd "$PLAYBOOK_DIR"
140
+ ansible-playbook onboard-crm.yml \
141
+ -e antelope_id="$brandAssetsFolder" \
142
+ -e brand_name="$title" \
143
+ -e crm_type="$type" \
144
+ -e base_url="$baseUrl" \
145
+ -e affiliates_api_url="$affiliatesApiUrl" \
146
+ -e arctic_brand="$isArcticBrand" \
147
+ -e languages="$languages" \
148
+ ${logo_url:+-e logo_url=$logo_url} \
149
+ ${favicon:+-e favicon=$favicon} \
150
+ -e jira_key_crm_branding="$jira_key_crm_branding" \
151
+ -e dry_run=true \
152
+ < /dev/null
153
+ '''
154
+ }
155
+ }
156
+ script { archiveGeneratedConfigs() }
157
+ }
158
+ }
159
+
160
+ stage('Approve') {
161
+ when { expression { !params.DRY_RUN } }
162
+ steps {
163
+ script {
164
+ input message: "Open web-crm PR(s) + write Consul crm_env + stamp Jira for '${params.brandAssetsFolder}'?",
165
+ ok: 'Onboard'
166
+ }
167
+ }
168
+ }
169
+
170
+ stage('Onboard') {
171
+ when { expression { !params.DRY_RUN } }
172
+ steps {
173
+ withCredentials([string(credentialsId: 'onepassword-service-account-token', variable: 'OP_SERVICE_ACCOUNT_TOKEN')]) {
174
+ sshagent(['bitbucket-jenkins-ssh']) {
175
+ sh label: 'ansible onboard-crm', script: '''
176
+ cd "$PLAYBOOK_DIR"
177
+ ansible-playbook onboard-crm.yml \
178
+ -e antelope_id="$brandAssetsFolder" \
179
+ -e brand_name="$title" \
180
+ -e crm_type="$type" \
181
+ -e base_url="$baseUrl" \
182
+ -e affiliates_api_url="$affiliatesApiUrl" \
183
+ -e arctic_brand="$isArcticBrand" \
184
+ -e languages="$languages" \
185
+ ${logo_url:+-e logo_url=$logo_url} \
186
+ ${favicon:+-e favicon=$favicon} \
187
+ -e jira_key_crm_branding="$jira_key_crm_branding" \
188
+ < /dev/null
189
+ '''
190
+ }
191
+ }
192
+ script { archiveGeneratedConfigs() }
193
+ }
194
+ }
195
+ }
196
+
197
+ post {
198
+ always { cleanWs() }
199
+ failure { echo 'Onboarding failed — see the validation/ansible output above.' }
200
+ }
201
+ }
202
+
203
+ // Archive the generated (gitignored) configs.ts so the operator can copy it into Consul.
204
+ void archiveGeneratedConfigs() {
205
+ def cfg = "${env.WORK_DIR}/src/configs/configs.ts"
206
+ if (fileExists(cfg)) {
207
+ sh "mkdir -p out && cp '${cfg}' out/configs.ts"
208
+ archiveArtifacts artifacts: 'out/configs.ts', allowEmptyArchive: true
209
+ currentBuild.description = "configs.ts generated for ${params.brandAssetsFolder}"
210
+ }
211
+ }
@@ -67,7 +67,7 @@ token with `$` — the shell would try to expand it and send an empty token.
67
67
  |-----|----------|---------|-------|
68
68
  | `antelope_id` | yes | `xyzxx` | Antelope ID → CLI `--brand_id`; keys every generated file + the branch name (normalized lowercase/no-spaces) |
69
69
  | `brand_name` | yes | `Zenith Horizon Group` | → CLI `--brand_name`, PR title, configs title |
70
- | `crm_type` | yes | `CORP_FL` | → CLI `--type`; one of `CORP_FL`, `IB_FL`, `CORP_AFF_FL`, `CORP_AFF_IB_PORTAL`, `CORP_AFF_MSQ` |
70
+ | `crm_type` | yes | `CORP_FL` | → CLI `--type`; one of `CORP_FL`, `IB_FL`, `CORP_AFF_FL` |
71
71
  | `logo_url` | yes | *(url)* | → CLI `--logo_url` (black-background logo) |
72
72
  | `favicon_url` | yes | *(url)* | → CLI `--favicon_url` |
73
73
  | `languages` | yes | `en,ar` | → CLI `--languages`; CSV string **or** a YAML list (`[en, ar]`) |
@@ -44,7 +44,7 @@
44
44
  prompt: "Brand display name — recorded in BRAND_LOCATIONS.md / configs title (e.g. Zenith Horizon Group)"
45
45
  private: false
46
46
  - name: crm_type
47
- prompt: "CRM type (one of: CORP_FL, IB_FL, CORP_AFF_FL, CORP_AFF_IB_PORTAL, CORP_AFF_MSQ)"
47
+ prompt: "CRM type (one of: CORP_FL, IB_FL, CORP_AFF_FL)"
48
48
  private: false
49
49
  - name: languages
50
50
  prompt: "Languages — 2-letter codes, comma-separated (e.g. en,ar)"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  # Pinned published antelope-cli version that provides the `onboarding-crm` command.
3
- antelope_cli_version: "1.2.4"
3
+ antelope_cli_version: "1.2.5"
4
4
 
5
5
  # Where web-crm is cloned on the control node.
6
6
  work_dir: "/tmp/onboard-crm/web-crm"
@@ -13,6 +13,7 @@ work_dir: "/tmp/onboard-crm/web-crm"
13
13
  antelope_id: ""
14
14
  color: "" # --color hex; blank => default palette
15
15
  logo_url: "https://logos-{{ antelope_id }}.s3.eu-west-1.amazonaws.com/logo-{{ brand_name }}.png" # brand-logos.json entry; override with -e
16
+ favicon: "https://logos-{{ antelope_id }}.s3.eu-west-1.amazonaws.com/favicon-{{ brand_name }}.png" # --favicon => configs.ts favicon; blank => CLI derives the same
16
17
  languages: "" # --languages: CSV "en,ar" or a YAML list [en, ar]
17
18
  crm_type: "" # --type: one of allowed_crm_types (per-brand; from group_vars derivation)
18
19
  # baseUrl uses the BE/CRM domain; affiliatesApiUrl uses the FE/CA domain (fe_server_url_list.0).
@@ -23,13 +24,13 @@ arctic_brand: false # --arctic_brand => configs.ts isAr
23
24
  # with write on this path. brand_name = the Consul namespace segment (e.g. Antelope/excelttra/crm_env).
24
25
  crm_env_consul_key: "Antelope/{{ brand_name }}/crm_env"
25
26
  manage_consul_crm_env: true
27
+ # Preview mode: clone + generate configs.ts/brand-logos but SKIP commit/push/PR/Jira/Consul writes.
28
+ dry_run: false
26
29
  # Kept in sync with ALLOWED_TYPES in antelope-cli/onboarding-crm/index.js.
27
30
  allowed_crm_types:
28
31
  - CORP_FL
29
32
  - IB_FL
30
33
  - CORP_AFF_FL
31
- - CORP_AFF_IB_PORTAL
32
- - CORP_AFF_MSQ
33
34
 
34
35
  # Reviewers for the PR. Empty => auto-fetched from the repo's Bitbucket "Default reviewers"
35
36
  # config (the authenticating account is excluded automatically). Override with an explicit
@@ -66,7 +67,7 @@ dest_branches: ""
66
67
  # Require a signed-in `op` CLI on the control node (op signin, or OP_SERVICE_ACCOUNT_TOKEN).
67
68
  # Service-account auth requires an explicit vault for `op item get` by name (op errors
68
69
  # with "a vault query must be provided" otherwise) — the items live in the DevOps vault.
69
- onepassword_vault: "DevOps"
70
+ onepassword_vault: "jenkins"
70
71
  onepassword_bitbucket_item: "Bitbucket API - jenkins@xsites.co.il"
71
72
  onepassword_bitbucket_field: "credential"
72
73
  onepassword_consul_item: "New_Consul_Token"
@@ -150,7 +150,7 @@
150
150
  # Fix Version, so stamp the release version (V<version/prod>) on the ticket BEFORE the
151
151
  # branches are pushed. Non-fatal — a missing token / unmatched version warns and skips.
152
152
  - name: Set the Jira Fix Version on the branding ticket
153
- when: manage_jira_fix_version | bool
153
+ when: manage_jira_fix_version | bool and not (dry_run | default(false) | bool)
154
154
  block:
155
155
  - name: Fetch the Jira token from 1Password
156
156
  when: (jira_token | default('') | length) == 0
@@ -336,6 +336,7 @@
336
336
  - crm_configs_ts_final is not skipped
337
337
  - (crm_configs_ts_final.content | default('') | length) > 0
338
338
  - (consul_token | default('') | length) > 0
339
+ - not (dry_run | default(false) | bool)
339
340
  ansible.builtin.uri:
340
341
  url: "{{ consul_url }}/v1/kv/{{ crm_env_consul_key }}"
341
342
  method: PUT
@@ -53,7 +53,8 @@
53
53
  '--affiliates_api_url', affiliates_api_url,
54
54
  '--arctic_brand', (arctic_brand | default(false) | string | lower)]
55
55
  + (['--color', color] if (color | default('') | length) > 0 else [])
56
- + (['--base_url', base_url] if (base_url | default('') | length) > 0 else []) }}
56
+ + (['--base_url', base_url] if (base_url | default('') | length) > 0 else [])
57
+ + (['--favicon', favicon] if (favicon | default('') | length) > 0 else []) }}
57
58
  chdir: "{{ work_dir }}"
58
59
  environment:
59
60
  CI: "true"
@@ -96,15 +97,20 @@
96
97
  git -c user.name="{{ git_user_name }}" -c user.email="{{ git_user_email }}"
97
98
  commit -m "{{ jira_key_crm_branding }}: On-Boarding {{ brand_name }} CRM Branding"
98
99
  chdir: "{{ work_dir }}"
99
- when: staged.rc == 1
100
+ when:
101
+ - staged.rc == 1
102
+ - not (dry_run | default(false) | bool)
100
103
 
101
104
  - name: "[{{ dest_branch }}] Push source branch"
102
105
  ansible.builtin.command:
103
106
  cmd: "git push -u origin {{ source_branch }}"
104
107
  chdir: "{{ work_dir }}"
105
- when: staged.rc == 1
108
+ when:
109
+ - staged.rc == 1
110
+ - not (dry_run | default(false) | bool)
106
111
 
107
112
  - name: "[{{ dest_branch }}] Open pull request"
113
+ when: not (dry_run | default(false) | bool)
108
114
  ansible.builtin.uri:
109
115
  url: "{{ api_base }}/repositories/{{ workspace }}/{{ repo_slug }}/pullrequests"
110
116
  method: POST
@@ -128,6 +134,7 @@
128
134
  register: pr_response
129
135
 
130
136
  - name: "[{{ dest_branch }}] Record PR result"
137
+ when: not (dry_run | default(false) | bool)
131
138
  ansible.builtin.set_fact:
132
139
  pr_results: >-
133
140
  {{ (pr_results | default([])) + [{
@@ -0,0 +1,47 @@
1
+ # Onboarding runner image for the self-service/onboarding/onboarding-crm Jenkins pipeline.
2
+ # Built on the fly by the declarative `agent { dockerfile { … } }` on a docker-capable fleet
3
+ # agent (no registry/ECR). Bakes in the pinned toolchain the onboard_crm role needs:
4
+ # ansible + community.general, node/npx (for `npx antelope-cli`), git + ssh (push to web-crm),
5
+ # and the 1Password CLI (op, reads the bitbucket/consul/jira tokens from the `jenkins` vault
6
+ # via the OP_SERVICE_ACCOUNT_TOKEN Jenkins credential).
7
+ FROM python:3.12-slim
8
+
9
+ ARG ANSIBLE_VERSION=10.3.0
10
+ ARG OP_VERSION=2.31.1
11
+ ARG NODE_MAJOR=20
12
+
13
+ RUN apt-get update && apt-get install -y --no-install-recommends \
14
+ git openssh-client curl ca-certificates gnupg unzip jq \
15
+ && rm -rf /var/lib/apt/lists/*
16
+
17
+ # Node.js — provides npx for `npx antelope-cli@<ver> onboarding-crm`
18
+ RUN curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash - \
19
+ && apt-get update && apt-get install -y --no-install-recommends nodejs \
20
+ && rm -rf /var/lib/apt/lists/*
21
+
22
+ # Ansible + community.general (dict_kv filter used by the role)
23
+ RUN pip install --no-cache-dir "ansible==${ANSIBLE_VERSION}" \
24
+ && ansible-galaxy collection install community.general
25
+
26
+ # 1Password CLI (op)
27
+ RUN curl -fsSL "https://cache.agilebits.com/dist/1P/op2/pkg/v${OP_VERSION}/op_linux_amd64_v${OP_VERSION}.zip" -o /tmp/op.zip \
28
+ && unzip -o /tmp/op.zip op -d /usr/local/bin/ && rm -f /tmp/op.zip && chmod +x /usr/local/bin/op
29
+
30
+ # AWS CLI v2 — used by the onboarding-logos pipeline to upload logo/favicon to the brand's
31
+ # logos-<antelope_id> S3 bucket (creds fetched from the brand's Consul folder at runtime).
32
+ RUN curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip \
33
+ && unzip -q /tmp/awscliv2.zip -d /tmp && /tmp/aws/install && rm -rf /tmp/aws /tmp/awscliv2.zip
34
+
35
+ # Pre-trust Bitbucket's host key so `git push` over SSH is non-interactive
36
+ RUN mkdir -p /etc/ssh && ssh-keyscan bitbucket.org >> /etc/ssh/ssh_known_hosts 2>/dev/null || true
37
+
38
+ # Jenkins runs this container as `-u 1000:1000`. `op` (and other tools) resolve "the current
39
+ # user" via getpwuid(); with no /etc/passwd entry for uid 1000 it fails with
40
+ # 'couldn't start daemon … not owned by the current user' and never enters service-account
41
+ # mode. Bake a uid/gid 1000 user so getpwuid succeeds. Also make /etc/passwd group-writable
42
+ # (root group) so a differently-numbered agent uid can self-register if ever needed.
43
+ RUN groupadd -g 1000 jenkins 2>/dev/null || true \
44
+ && useradd -u 1000 -g 1000 -m -d /home/jenkins -s /bin/bash jenkins 2>/dev/null || true \
45
+ && chmod g+w /etc/passwd
46
+
47
+ WORKDIR /work
@@ -0,0 +1,105 @@
1
+ // Self-service logo/favicon upload — jenkins.xsites.xyz/job/self-service/job/onboarding/job/onboarding-logos
2
+ //
3
+ // Pipeline-as-Code (this file is the source of truth; the Jenkins job is a "Pipeline script from SCM"
4
+ // pointer at antelope-cli@master, path ansible/onboarding-logos/Jenkinsfile).
5
+ //
6
+ // Flow: operator uploads the logo + favicon image files -> the pipeline reads the brand's AWS creds
7
+ // from its Consul folder (Antelope/<brand_name>/AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|AWS_DEFAULT_REGION)
8
+ // -> uploads them to the brand's S3 bucket logos-<antelope_id> as logo-<brand_name>.<ext> /
9
+ // favicon-<brand_name>.<ext> (the exact objects configs.ts / brand-logos.json reference).
10
+ //
11
+ // Reuses the onboarding-crm runner image (op + curl + jq + aws + uid-1000 user). Requires the
12
+ // file-parameters plugin (base64File) and the onepassword-service-account-token credential
13
+ // (op reads the Consul token from the `jenkins` vault).
14
+ pipeline {
15
+ agent {
16
+ dockerfile {
17
+ dir 'ansible/onboarding-crm'
18
+ filename 'runner.Dockerfile'
19
+ additionalBuildArgs '--pull'
20
+ }
21
+ }
22
+
23
+ options {
24
+ timestamps()
25
+ ansiColor('xterm')
26
+ disableConcurrentBuilds()
27
+ buildDiscarder(logRotator(numToKeepStr: '30'))
28
+ }
29
+
30
+ parameters {
31
+ string(name: 'brandAssetsFolder', defaultValue: '', trim: true,
32
+ description: 'Antelope ID (lowercase alphanumeric). Target bucket = logos-<brandAssetsFolder>. e.g. uzumy')
33
+ string(name: 'brand_name', defaultValue: '', trim: true,
34
+ description: 'Brand name — the Consul folder Antelope/<brand_name>/ AND the filename token logo-<brand_name>/favicon-<brand_name>. e.g. excelttra')
35
+ base64File(name: 'logo_file', description: 'Logo image file (uploaded → logo-<brand_name>.<logo_type>)')
36
+ base64File(name: 'favicon_file', description: 'Favicon image file (uploaded → favicon-<brand_name>.<favicon_type>)')
37
+ choice(name: 'logo_type', choices: ['png', 'jpg', 'jpeg', 'svg', 'webp', 'gif'], description: 'Logo file extension')
38
+ choice(name: 'favicon_type', choices: ['png', 'ico', 'jpg', 'jpeg', 'svg', 'webp'], description: 'Favicon file extension')
39
+ booleanParam(name: 'DRY_RUN', defaultValue: true,
40
+ description: 'Preview only: resolve creds + validate + aws s3 cp --dryrun (no actual upload)')
41
+ }
42
+
43
+ environment {
44
+ HOME = "${env.WORKSPACE}"
45
+ CONSUL_URL = 'https://consul.xsites.xyz'
46
+ }
47
+
48
+ stages {
49
+ stage('Validate parameters') {
50
+ steps {
51
+ script {
52
+ def IMG_EXT = ['png', 'jpg', 'jpeg', 'svg', 'webp', 'ico', 'gif']
53
+ def errs = []
54
+ if (!(params.brandAssetsFolder ==~ /^[a-z0-9]+$/))
55
+ errs << "brandAssetsFolder must be lowercase alphanumeric (the Antelope ID); got '${params.brandAssetsFolder}'"
56
+ // brand_name must be filename-safe so logo-<brand_name>.<ext> / favicon-<brand_name>.<ext> are valid keys
57
+ if (!(params.brand_name ==~ /^[A-Za-z0-9._-]+$/))
58
+ errs << "brand_name must be filename-safe (letters/digits/._-, no spaces); got '${params.brand_name}'"
59
+ if (!IMG_EXT.contains(params.logo_type)) errs << "logo_type '${params.logo_type}' not allowed"
60
+ if (!IMG_EXT.contains(params.favicon_type)) errs << "favicon_type '${params.favicon_type}' not allowed"
61
+ if (errs) error("Parameter validation failed:\n - " + errs.join("\n - "))
62
+ env.LOGO_KEY = "logo-${params.brand_name}.${params.logo_type}"
63
+ env.FAVICON_KEY = "favicon-${params.brand_name}.${params.favicon_type}"
64
+ echo "✅ Valid — bucket=logos-${params.brandAssetsFolder}, ${env.LOGO_KEY}, ${env.FAVICON_KEY}, dry_run=${params.DRY_RUN}"
65
+ currentBuild.displayName = "#${env.BUILD_NUMBER} ${params.brand_name}${params.DRY_RUN ? ' (dry-run)' : ''}"
66
+ }
67
+ }
68
+ }
69
+
70
+ stage('Upload to S3') {
71
+ steps {
72
+ withCredentials([string(credentialsId: 'onepassword-service-account-token', variable: 'OP_SERVICE_ACCOUNT_TOKEN')]) {
73
+ withFileParameter('logo_file') {
74
+ withFileParameter('favicon_file') {
75
+ sh label: 'resolve Consul AWS creds + upload', script: '''
76
+ set -eu
77
+ CT=$(op read "op://jenkins/New_Consul_Token/password")
78
+ base="$CONSUL_URL/v1/kv/Antelope/$brand_name"
79
+ AWS_ACCESS_KEY_ID=$(curl -sf -H "X-Consul-Token: $CT" "$base/AWS_ACCESS_KEY_ID?raw")
80
+ AWS_SECRET_ACCESS_KEY=$(curl -sf -H "X-Consul-Token: $CT" "$base/AWS_SECRET_ACCESS_KEY?raw")
81
+ AWS_DEFAULT_REGION=$(curl -sf -H "X-Consul-Token: $CT" "$base/AWS_DEFAULT_REGION?raw" || echo eu-west-1)
82
+ export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_DEFAULT_REGION
83
+ if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
84
+ echo "ERROR: no AWS creds under Consul Antelope/$brand_name/ (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY)"; exit 1
85
+ fi
86
+ ct() { case "$1" in png) echo image/png;; jpg|jpeg) echo image/jpeg;; svg) echo image/svg+xml;; webp) echo image/webp;; gif) echo image/gif;; ico) echo image/x-icon;; *) echo application/octet-stream;; esac; }
87
+ DRY=""; [ "$DRY_RUN" = "true" ] && DRY="--dryrun"
88
+ echo "Uploading to s3://logos-$brandAssetsFolder/ (region $AWS_DEFAULT_REGION)${DRY:+ [DRY-RUN]}"
89
+ aws s3 cp "$logo_file" "s3://logos-$brandAssetsFolder/$LOGO_KEY" --content-type "$(ct $logo_type)" $DRY
90
+ aws s3 cp "$favicon_file" "s3://logos-$brandAssetsFolder/$FAVICON_KEY" --content-type "$(ct $favicon_type)" $DRY
91
+ echo "Done: $LOGO_KEY, $FAVICON_KEY"
92
+ '''
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ post {
101
+ always { cleanWs() }
102
+ success { echo "logos ${params.DRY_RUN ? 'previewed' : 'uploaded'} to logos-${params.brandAssetsFolder}: ${env.LOGO_KEY}, ${env.FAVICON_KEY}" }
103
+ failure { echo 'Logo upload failed — see the validation/aws output above.' }
104
+ }
105
+ }
@@ -8,7 +8,8 @@ let brand = {
8
8
  affiliatesApiUrl: '',
9
9
  isArcticBrand: false,
10
10
  type: '',
11
- baseUrl: ''
11
+ baseUrl: '',
12
+ favicon: ''
12
13
  };
13
14
 
14
15
  function updateBrand(updatedBrand) {
@@ -32,7 +32,7 @@ const updateConfigFile = () => {
32
32
  templateTheme: 'theme-template-${id}',
33
33
  brandAssetsFolder: '${id}',
34
34
  devBrandName: '${id}',
35
- favicon: 'https://logos-${id}.s3.eu-west-1.amazonaws.com/favicon-${brand.brandName}.png',
35
+ favicon: '${brand.favicon || `https://logos-${id}.s3.eu-west-1.amazonaws.com/favicon-${brand.brandName}.png`}',
36
36
  gaTrackingID: 'UA-70456753-2',
37
37
  FCMSenderId: '',
38
38
  isFnsStatusActive: true,
@@ -8,7 +8,7 @@ const createConfig = require('./createConfig.js');
8
8
  const brandLogos = require('./brandLogos.js');
9
9
  const brandLocations = require('./brandLocations.js');
10
10
 
11
- const ALLOWED_TYPES = ['CORP_FL', 'IB_FL', 'CORP_AFF_FL', 'CORP_AFF_IB_PORTAL', 'CORP_AFF_MSQ'];
11
+ const ALLOWED_TYPES = ['CORP_FL', 'IB_FL', 'CORP_AFF_FL'];
12
12
  const DEFAULT_BASE_URL = 'https://dev-apicrm.antelopesystem.com/SignalsCRM/';
13
13
 
14
14
  const HELP = `
@@ -34,8 +34,9 @@ Options:
34
34
  --arctic_brand <t|f> configs.ts isArcticBrand. Default: false
35
35
  --type <type> One of: ${ALLOWED_TYPES.join(', ')} → configs.ts (required)
36
36
  --base_url <url> configs.ts baseUrl. Default: ${DEFAULT_BASE_URL}
37
+ --favicon <url> configs.ts favicon. Empty => derived (logos-<id>/favicon-<name>.png)
37
38
 
38
- Note: configs.ts favicon is DERIVED (logos-<id>/favicon-<name>.png) and FCMSenderId is always '';
39
+ Note: --favicon defaults to the derived logos-<id>/favicon-<name>.png; FCMSenderId is always '';
39
40
  both are placeholders that get hand-edited per brand. configs.ts is gitignored (Consul copy-paste).
40
41
  -h, --help Show this help
41
42
 
@@ -51,7 +52,8 @@ const FLAGS = {
51
52
  '--affiliates_api_url': 'affiliatesApiUrl',
52
53
  '--arctic_brand': 'arcticBrand',
53
54
  '--type': 'type',
54
- '--base_url': 'baseUrl'
55
+ '--base_url': 'baseUrl',
56
+ '--favicon': 'favicon'
55
57
  };
56
58
 
57
59
  function parseArgs(argv) {
@@ -114,6 +116,8 @@ async function run() {
114
116
  // from the id + brand_name; FCMSenderId is always ''. isArcticBrand defaults false.
115
117
  const affiliatesApiUrl = opts.affiliatesApiUrl || '';
116
118
  const isArcticBrand = String(opts.arcticBrand).toLowerCase() === 'true';
119
+ // favicon: explicit override, else createConfig derives logos-<id>/favicon-<name>.png
120
+ const favicon = opts.favicon || '';
117
121
 
118
122
  // Validate required fields.
119
123
  const missing = [];
@@ -153,7 +157,8 @@ async function run() {
153
157
  affiliatesApiUrl,
154
158
  isArcticBrand,
155
159
  type,
156
- baseUrl
160
+ baseUrl,
161
+ favicon
157
162
  });
158
163
 
159
164
  console.log(chalk.bold(chalk.yellow(`\nOnboarding "${brandName}" (id: ${connectedBrandName})`)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antelope-cli",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "CLI-Tool for automating processes for Antelope-Systems ",
5
5
  "main": "index.js",
6
6
  "bin": {