antelope-cli 1.2.4 → 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.
- package/ansible/onboarding-ca/roles/onboard_ca/defaults/main.yml +2 -2
- package/ansible/onboarding-crm/Jenkinsfile +211 -0
- package/ansible/onboarding-crm/README.md +1 -1
- package/ansible/onboarding-crm/onboard-crm.yml +1 -4
- package/ansible/onboarding-crm/roles/onboard_crm/defaults/main.yml +19 -19
- package/ansible/onboarding-crm/roles/onboard_crm/tasks/main.yml +39 -5
- package/ansible/onboarding-crm/roles/onboard_crm/tasks/per_branch.yml +31 -7
- package/ansible/onboarding-crm/runner.Dockerfile +47 -0
- package/ansible/onboarding-logos/Jenkinsfile +105 -0
- package/onboarding-crm/brand.js +4 -3
- package/onboarding-crm/createConfig.js +20 -13
- package/onboarding-crm/index.js +24 -13
- package/package.json +1 -1
|
@@ -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.
|
|
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: "
|
|
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
|
|
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,14 +44,11 @@
|
|
|
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
|
|
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)"
|
|
51
51
|
private: false
|
|
52
|
-
- name: fcm_sender_id
|
|
53
|
-
prompt: "Firebase FCMSenderId"
|
|
54
|
-
private: false
|
|
55
52
|
- name: jira_key_crm_branding
|
|
56
53
|
prompt: "Jira key (e.g. ATLP-47067)"
|
|
57
54
|
private: false
|
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
---
|
|
2
2
|
# Pinned published antelope-cli version that provides the `onboarding-crm` command.
|
|
3
|
-
antelope_cli_version: "1.2.
|
|
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"
|
|
7
7
|
|
|
8
8
|
# --- CRM brand inputs (map to the `onboarding-crm` CLI flags) ------------------------
|
|
9
|
-
# antelope_id
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# favicon_url and base_url default to the SAME derivations as the deployment config
|
|
14
|
-
# (xsites-devops ansible-deployment-new/roles/consul/templates/crm.json.j2), so the values
|
|
15
|
-
# match what gets seeded into Consul. They resolve from the brand vars (antelope_id,
|
|
16
|
-
# brand_name, crm_server_url) in the create_brand context; the standalone playbook's prompts
|
|
17
|
-
# override them. crm_type is a per-brand value (not defaulted anywhere — set it in brand_vars
|
|
18
|
-
# or -e). logo_url has no derivation in that template — must be provided explicitly.
|
|
9
|
+
# antelope_id => --brand_id (connectedBrandName; keys theme/folder/devBrandName/favicon in configs.ts).
|
|
10
|
+
# REQUIRED: antelope_id, brand_name, crm_type, logo_url, languages. base_url + affiliates_api_url
|
|
11
|
+
# derive from the brand vars in create_brand; pass -e for a standalone run. configs.ts favicon is
|
|
12
|
+
# DERIVED by the CLI (logos-<id>/favicon-<name>.png) and FCMSenderId is always '' (both placeholders).
|
|
19
13
|
antelope_id: ""
|
|
20
|
-
color: "" # --color
|
|
21
|
-
logo_url: "https://logos-{{ antelope_id }}.s3.eu-west-1.amazonaws.com/logo-{{ brand_name }}.png" #
|
|
14
|
+
color: "" # --color hex; blank => default palette
|
|
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
|
|
22
17
|
languages: "" # --languages: CSV "en,ar" or a YAML list [en, ar]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
favicon_url: "https://logos-{{ antelope_id }}.s3.eu-west-1.amazonaws.com/favicon-{{ brand_name }}.png"
|
|
18
|
+
crm_type: "" # --type: one of allowed_crm_types (per-brand; from group_vars derivation)
|
|
19
|
+
# baseUrl uses the BE/CRM domain; affiliatesApiUrl uses the FE/CA domain (fe_server_url_list.0).
|
|
26
20
|
base_url: "{{ ('https://' ~ crm_server_url ~ '/SignalsCRM/') if (crm_server_url | default('') | length > 0) else '' }}"
|
|
21
|
+
affiliates_api_url: "{{ fe_server_url_list.0 | default('') }}"
|
|
22
|
+
arctic_brand: false # --arctic_brand => configs.ts isArcticBrand
|
|
23
|
+
# Consul KV key the generated configs.ts is PUT to (the env CRM config). Needs a consul_token
|
|
24
|
+
# with write on this path. brand_name = the Consul namespace segment (e.g. Antelope/excelttra/crm_env).
|
|
25
|
+
crm_env_consul_key: "Antelope/{{ brand_name }}/crm_env"
|
|
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
|
|
27
29
|
# Kept in sync with ALLOWED_TYPES in antelope-cli/onboarding-crm/index.js.
|
|
28
30
|
allowed_crm_types:
|
|
29
31
|
- CORP_FL
|
|
30
32
|
- IB_FL
|
|
31
33
|
- CORP_AFF_FL
|
|
32
|
-
- CORP_AFF_IB_PORTAL
|
|
33
|
-
- CORP_AFF_MSQ
|
|
34
34
|
|
|
35
35
|
# Reviewers for the PR. Empty => auto-fetched from the repo's Bitbucket "Default reviewers"
|
|
36
36
|
# config (the authenticating account is excluded automatically). Override with an explicit
|
|
@@ -67,7 +67,7 @@ dest_branches: ""
|
|
|
67
67
|
# Require a signed-in `op` CLI on the control node (op signin, or OP_SERVICE_ACCOUNT_TOKEN).
|
|
68
68
|
# Service-account auth requires an explicit vault for `op item get` by name (op errors
|
|
69
69
|
# with "a vault query must be provided" otherwise) — the items live in the DevOps vault.
|
|
70
|
-
onepassword_vault: "
|
|
70
|
+
onepassword_vault: "jenkins"
|
|
71
71
|
onepassword_bitbucket_item: "Bitbucket API - jenkins@xsites.co.il"
|
|
72
72
|
onepassword_bitbucket_field: "credential"
|
|
73
73
|
onepassword_consul_item: "New_Consul_Token"
|
|
@@ -75,16 +75,13 @@
|
|
|
75
75
|
- brand_name is defined and (brand_name | length) > 0
|
|
76
76
|
- jira_key_crm_branding is defined and (jira_key_crm_branding | length) > 0
|
|
77
77
|
- logo_url is defined and (logo_url | length) > 0
|
|
78
|
-
- favicon_url is defined and (favicon_url | length) > 0
|
|
79
78
|
- languages is defined and (languages | length) > 0
|
|
80
|
-
- fcm_sender_id is defined and (fcm_sender_id | length) > 0
|
|
81
79
|
- crm_type is defined and (crm_type | length) > 0
|
|
82
80
|
- crm_type in allowed_crm_types
|
|
83
81
|
- bitbucket_token is defined and (bitbucket_token | length) > 0
|
|
84
82
|
- bitbucket_email is defined and (bitbucket_email | length) > 0
|
|
85
83
|
fail_msg: >-
|
|
86
|
-
Required: antelope_id, brand_name, jira_key_crm_branding, logo_url,
|
|
87
|
-
fcm_sender_id, crm_type (one of {{ allowed_crm_types | join(', ') }}), bitbucket_email.
|
|
84
|
+
Required: antelope_id, brand_name, jira_key_crm_branding, logo_url, languages, crm_type (one of {{ allowed_crm_types | join(', ') }}), bitbucket_email.
|
|
88
85
|
bitbucket_token must be provided (via --extra-vars / prompt) or resolvable from 1Password
|
|
89
86
|
item '{{ onepassword_bitbucket_item }}' field '{{ onepassword_bitbucket_field }}' (op signin).
|
|
90
87
|
|
|
@@ -153,7 +150,7 @@
|
|
|
153
150
|
# Fix Version, so stamp the release version (V<version/prod>) on the ticket BEFORE the
|
|
154
151
|
# branches are pushed. Non-fatal — a missing token / unmatched version warns and skips.
|
|
155
152
|
- name: Set the Jira Fix Version on the branding ticket
|
|
156
|
-
when: manage_jira_fix_version | bool
|
|
153
|
+
when: manage_jira_fix_version | bool and not (dry_run | default(false) | bool)
|
|
157
154
|
block:
|
|
158
155
|
- name: Fetch the Jira token from 1Password
|
|
159
156
|
when: (jira_token | default('') | length) == 0
|
|
@@ -322,3 +319,40 @@
|
|
|
322
319
|
- name: Summary of pull requests
|
|
323
320
|
ansible.builtin.debug:
|
|
324
321
|
msg: "{{ pr_results | default([]) }}"
|
|
322
|
+
|
|
323
|
+
# --- Consul: publish the generated configs.ts to the brand's crm_env KV --------------
|
|
324
|
+
# configs.ts is gitignored (not committed) — it is the env's CRM config, so push it to Consul
|
|
325
|
+
# (Antelope/<brand>/crm_env) in addition to the PR body. Non-fatal: warns if the token lacks write.
|
|
326
|
+
- name: Read generated configs.ts for Consul crm_env
|
|
327
|
+
when: manage_consul_crm_env | default(true) | bool
|
|
328
|
+
ansible.builtin.slurp:
|
|
329
|
+
src: "{{ work_dir }}/src/configs/configs.ts"
|
|
330
|
+
register: crm_configs_ts_final
|
|
331
|
+
failed_when: false
|
|
332
|
+
|
|
333
|
+
- name: Update Consul crm_env with the generated configs.ts
|
|
334
|
+
when:
|
|
335
|
+
- manage_consul_crm_env | default(true) | bool
|
|
336
|
+
- crm_configs_ts_final is not skipped
|
|
337
|
+
- (crm_configs_ts_final.content | default('') | length) > 0
|
|
338
|
+
- (consul_token | default('') | length) > 0
|
|
339
|
+
- not (dry_run | default(false) | bool)
|
|
340
|
+
ansible.builtin.uri:
|
|
341
|
+
url: "{{ consul_url }}/v1/kv/{{ crm_env_consul_key }}"
|
|
342
|
+
method: PUT
|
|
343
|
+
headers:
|
|
344
|
+
X-Consul-Token: "{{ consul_token }}"
|
|
345
|
+
body: "{{ crm_configs_ts_final.content | b64decode }}"
|
|
346
|
+
status_code: [200]
|
|
347
|
+
register: consul_crm_env_put
|
|
348
|
+
failed_when: false
|
|
349
|
+
no_log: false
|
|
350
|
+
|
|
351
|
+
- name: Confirm the Consul crm_env update
|
|
352
|
+
when: manage_consul_crm_env | default(true) | bool and (consul_crm_env_put is defined)
|
|
353
|
+
ansible.builtin.debug:
|
|
354
|
+
msg: >-
|
|
355
|
+
{{ 'Updated Consul ' ~ crm_env_consul_key
|
|
356
|
+
if (consul_crm_env_put.status | default(0)) == 200
|
|
357
|
+
else 'Consul crm_env NOT updated (HTTP ' ~ (consul_crm_env_put.status | default('n/a'))
|
|
358
|
+
~ ') - token may lack write on ' ~ crm_env_consul_key }}
|
|
@@ -48,17 +48,35 @@
|
|
|
48
48
|
'--brand_id', antelope_id_norm,
|
|
49
49
|
'--brand_name', brand_name,
|
|
50
50
|
'--logo_url', logo_url,
|
|
51
|
-
'--favicon_url', favicon_url,
|
|
52
51
|
'--languages', languages_csv,
|
|
53
|
-
'--
|
|
54
|
-
'--
|
|
52
|
+
'--type', crm_type,
|
|
53
|
+
'--affiliates_api_url', affiliates_api_url,
|
|
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"
|
|
60
61
|
changed_when: true
|
|
61
62
|
|
|
63
|
+
- name: "[{{ dest_branch }}] Read generated configs.ts (gitignored) for the PR body / Consul"
|
|
64
|
+
ansible.builtin.slurp:
|
|
65
|
+
src: "{{ work_dir }}/src/configs/configs.ts"
|
|
66
|
+
register: crm_configs_ts
|
|
67
|
+
failed_when: false
|
|
68
|
+
|
|
69
|
+
- name: "[{{ dest_branch }}] Compose PR description (ticket + configs.ts for Consul copy-paste)"
|
|
70
|
+
ansible.builtin.set_fact:
|
|
71
|
+
pr_description: |-
|
|
72
|
+
{{ jira_key_crm_branding }}: On-Boarding {{ brand_name }} CRM Branding
|
|
73
|
+
|
|
74
|
+
configs.ts — copy into Consul `{{ crm_env_consul_key }}`:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
{{ crm_configs_ts.content | default('') | b64decode }}
|
|
78
|
+
```
|
|
79
|
+
|
|
62
80
|
- name: "[{{ dest_branch }}] Stage changes"
|
|
63
81
|
ansible.builtin.command:
|
|
64
82
|
cmd: git add -A
|
|
@@ -79,15 +97,20 @@
|
|
|
79
97
|
git -c user.name="{{ git_user_name }}" -c user.email="{{ git_user_email }}"
|
|
80
98
|
commit -m "{{ jira_key_crm_branding }}: On-Boarding {{ brand_name }} CRM Branding"
|
|
81
99
|
chdir: "{{ work_dir }}"
|
|
82
|
-
when:
|
|
100
|
+
when:
|
|
101
|
+
- staged.rc == 1
|
|
102
|
+
- not (dry_run | default(false) | bool)
|
|
83
103
|
|
|
84
104
|
- name: "[{{ dest_branch }}] Push source branch"
|
|
85
105
|
ansible.builtin.command:
|
|
86
106
|
cmd: "git push -u origin {{ source_branch }}"
|
|
87
107
|
chdir: "{{ work_dir }}"
|
|
88
|
-
when:
|
|
108
|
+
when:
|
|
109
|
+
- staged.rc == 1
|
|
110
|
+
- not (dry_run | default(false) | bool)
|
|
89
111
|
|
|
90
112
|
- name: "[{{ dest_branch }}] Open pull request"
|
|
113
|
+
when: not (dry_run | default(false) | bool)
|
|
91
114
|
ansible.builtin.uri:
|
|
92
115
|
url: "{{ api_base }}/repositories/{{ workspace }}/{{ repo_slug }}/pullrequests"
|
|
93
116
|
method: POST
|
|
@@ -97,7 +120,7 @@
|
|
|
97
120
|
body_format: json
|
|
98
121
|
body:
|
|
99
122
|
title: "{{ jira_key_crm_branding }}: On-Boarding {{ brand_name }} CRM Branding"
|
|
100
|
-
description: "{{
|
|
123
|
+
description: "{{ pr_description }}"
|
|
101
124
|
source:
|
|
102
125
|
branch:
|
|
103
126
|
name: "{{ source_branch }}"
|
|
@@ -111,6 +134,7 @@
|
|
|
111
134
|
register: pr_response
|
|
112
135
|
|
|
113
136
|
- name: "[{{ dest_branch }}] Record PR result"
|
|
137
|
+
when: not (dry_run | default(false) | bool)
|
|
114
138
|
ansible.builtin.set_fact:
|
|
115
139
|
pr_results: >-
|
|
116
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
|
+
}
|
package/onboarding-crm/brand.js
CHANGED
|
@@ -21,20 +21,27 @@ function start() {
|
|
|
21
21
|
|
|
22
22
|
const updateConfigFile = () => {
|
|
23
23
|
const brand = brandModule.getBrand();
|
|
24
|
+
const id = brand.connectedBrandName;
|
|
24
25
|
return `export const configs: WebCrmConfig = {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
baseUrl: '${brand.baseUrl}',
|
|
27
|
+
affiliatesApiUrl: '${brand.affiliatesApiUrl}',
|
|
28
|
+
appId: 0,
|
|
29
|
+
type: '${brand.type}',
|
|
30
|
+
title: '${brand.brandName} CRM',
|
|
31
|
+
colorTheme: 'theme-${id}',
|
|
32
|
+
templateTheme: 'theme-template-${id}',
|
|
33
|
+
brandAssetsFolder: '${id}',
|
|
34
|
+
devBrandName: '${id}',
|
|
35
|
+
favicon: '${brand.favicon || `https://logos-${id}.s3.eu-west-1.amazonaws.com/favicon-${brand.brandName}.png`}',
|
|
36
|
+
gaTrackingID: 'UA-70456753-2',
|
|
37
|
+
FCMSenderId: '',
|
|
38
|
+
isFnsStatusActive: true,
|
|
39
|
+
showTradingInfo: true,
|
|
40
|
+
CrmAvailableCurrencies: ['USD'],
|
|
41
|
+
showNewClientCard: true,
|
|
42
|
+
newAppSubdomain: 'ant-crm',
|
|
43
|
+
isArcticBrand: ${brand.isArcticBrand ? 'true' : 'false'}
|
|
44
|
+
};`;
|
|
38
45
|
};
|
|
39
46
|
|
|
40
47
|
module.exports = { start };
|
package/onboarding-crm/index.js
CHANGED
|
@@ -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'
|
|
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 = `
|
|
@@ -29,10 +29,15 @@ Options:
|
|
|
29
29
|
--color <hex> Brand hex color, e.g. "#1a2b3c". Empty => default palette ($corp)
|
|
30
30
|
--logo_url <url> Logo image URL (black background) (required)
|
|
31
31
|
--languages <csv> 2-letter language codes, comma-separated, e.g. "en,ar" (required)
|
|
32
|
-
--
|
|
33
|
-
|
|
32
|
+
--affiliates_api_url configs.ts affiliatesApiUrl — the FE host, e.g. "apife.<ca-domain>"
|
|
33
|
+
(apife.excelttra.com). Not derivable from base_url (that's the BE domain).
|
|
34
|
+
--arctic_brand <t|f> configs.ts isArcticBrand. Default: false
|
|
34
35
|
--type <type> One of: ${ALLOWED_TYPES.join(', ')} → configs.ts (required)
|
|
35
36
|
--base_url <url> configs.ts baseUrl. Default: ${DEFAULT_BASE_URL}
|
|
37
|
+
--favicon <url> configs.ts favicon. Empty => derived (logos-<id>/favicon-<name>.png)
|
|
38
|
+
|
|
39
|
+
Note: --favicon defaults to the derived logos-<id>/favicon-<name>.png; FCMSenderId is always '';
|
|
40
|
+
both are placeholders that get hand-edited per brand. configs.ts is gitignored (Consul copy-paste).
|
|
36
41
|
-h, --help Show this help
|
|
37
42
|
|
|
38
43
|
Run from the root of a web-crm checkout — files are written relative to the current directory.
|
|
@@ -44,10 +49,11 @@ const FLAGS = {
|
|
|
44
49
|
'--color': 'color',
|
|
45
50
|
'--logo_url': 'logo',
|
|
46
51
|
'--languages': 'languages',
|
|
47
|
-
'--
|
|
48
|
-
'--
|
|
52
|
+
'--affiliates_api_url': 'affiliatesApiUrl',
|
|
53
|
+
'--arctic_brand': 'arcticBrand',
|
|
49
54
|
'--type': 'type',
|
|
50
|
-
'--base_url': 'baseUrl'
|
|
55
|
+
'--base_url': 'baseUrl',
|
|
56
|
+
'--favicon': 'favicon'
|
|
51
57
|
};
|
|
52
58
|
|
|
53
59
|
function parseArgs(argv) {
|
|
@@ -97,9 +103,7 @@ async function run() {
|
|
|
97
103
|
: (interactive ? await ask(rl, `Please set a color for "${connectedBrandName}", use hex value, leave empty for default.\n`) : '');
|
|
98
104
|
|
|
99
105
|
const logo = await resolve(opts, 'logo', 'Please enter the URL to the logo image suitable for a black background:\n', interactive, rl);
|
|
100
|
-
const favicon = await resolve(opts, 'favicon', 'Please provide the url to your favicon image:\n', interactive, rl);
|
|
101
106
|
const languagesInput = await resolve(opts, 'languages', 'Enter the languages (2-letter codes) separated by commas:\n', interactive, rl);
|
|
102
|
-
const fcmSenderId = await resolve(opts, 'fcmSenderId', 'Enter the FCMSenderId value (see https://xsites.atlassian.net/wiki/spaces/IKB/pages/777650347/Onboarding+Branding+Manual+for+devs#Firebase): ', interactive, rl);
|
|
103
107
|
const typeInput = await resolve(opts, 'type', `Enter the type value (allowed values: ${ALLOWED_TYPES.join(', ')}): `, interactive, rl);
|
|
104
108
|
|
|
105
109
|
// baseUrl is optional (defaults to the dev URL).
|
|
@@ -107,14 +111,20 @@ async function run() {
|
|
|
107
111
|
? opts.baseUrl
|
|
108
112
|
: (interactive ? (await ask(rl, `Enter the configs.ts baseUrl (leave empty for ${DEFAULT_BASE_URL}):\n`)) || DEFAULT_BASE_URL : DEFAULT_BASE_URL);
|
|
109
113
|
|
|
114
|
+
// affiliatesApiUrl (FE host, e.g. apife.<ca-domain>) → configs.ts. Explicit input; NOT
|
|
115
|
+
// derivable from baseUrl (baseUrl is the BE/CRM domain). configs.ts favicon is derived
|
|
116
|
+
// from the id + brand_name; FCMSenderId is always ''. isArcticBrand defaults false.
|
|
117
|
+
const affiliatesApiUrl = opts.affiliatesApiUrl || '';
|
|
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 || '';
|
|
121
|
+
|
|
110
122
|
// Validate required fields.
|
|
111
123
|
const missing = [];
|
|
112
124
|
if (!connectedBrandName) missing.push('--brand_id');
|
|
113
125
|
if (!brandName) missing.push('--brand_name');
|
|
114
126
|
if (!logo) missing.push('--logo_url');
|
|
115
|
-
if (!favicon) missing.push('--favicon_url');
|
|
116
127
|
if (!languagesInput) missing.push('--languages');
|
|
117
|
-
if (!fcmSenderId) missing.push('--fcm_sender_id');
|
|
118
128
|
if (!typeInput) missing.push('--type');
|
|
119
129
|
if (missing.length) {
|
|
120
130
|
console.error(chalk.red(`Error: missing required value(s): ${missing.join(', ')}`));
|
|
@@ -144,10 +154,11 @@ async function run() {
|
|
|
144
154
|
color: color || '',
|
|
145
155
|
logo,
|
|
146
156
|
languages,
|
|
147
|
-
|
|
148
|
-
|
|
157
|
+
affiliatesApiUrl,
|
|
158
|
+
isArcticBrand,
|
|
149
159
|
type,
|
|
150
|
-
baseUrl
|
|
160
|
+
baseUrl,
|
|
161
|
+
favicon
|
|
151
162
|
});
|
|
152
163
|
|
|
153
164
|
console.log(chalk.bold(chalk.yellow(`\nOnboarding "${brandName}" (id: ${connectedBrandName})`)));
|