Skip to content

Commit

Permalink
Add workarounds to support Vlocity local comp in CI (#3642)
Browse files Browse the repository at this point in the history
We discovered that using sf.accessToken for persistent orgs is not
compatible with the local compilation of omniscripts, which requires
this workaround. Moreover, vlocity_build does not support passing the
NPM auth token via CLI or environment variable, so we're adding support
for this to the vlocity_pack_deploy task.
  • Loading branch information
jstvz authored Aug 29, 2023
1 parent 9ccf3c9 commit 371a942
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 11 deletions.
44 changes: 39 additions & 5 deletions cumulusci/tasks/vlocity/tests/test_vlocity.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
BUILD_TOOL_MISSING_ERROR,
LWC_RSS_NAME,
OMNI_NAMESPACE,
SF_TOKEN_ENV,
VBT_SF_ALIAS,
VBT_TOKEN_ENV,
VF_LEGACY_RSS_NAME,
VF_RSS_NAME,
OmniStudioDeployRemoteSiteSettings,
Expand Down Expand Up @@ -67,7 +70,7 @@
persistent_org_config,
VlocityRetrieveTask,
None,
f"vlocity packExport -job vlocity.yaml -sf.accessToken '{access_token}' -sf.instanceUrl '{instance_url}'",
f"vlocity packExport -job vlocity.yaml -sfdx.username '{VBT_SF_ALIAS}'",
),
(
scratch_org_config,
Expand All @@ -79,13 +82,13 @@
persistent_org_config,
VlocityDeployTask,
None,
f"vlocity packDeploy -job vlocity.yaml -sf.accessToken '{access_token}' -sf.instanceUrl '{instance_url}'",
f"vlocity packDeploy -job vlocity.yaml -sfdx.username '{VBT_SF_ALIAS}'",
),
(
persistent_org_config,
VlocityDeployTask,
"foo=bar",
f"vlocity packDeploy -job vlocity.yaml -sf.accessToken '{access_token}' -sf.instanceUrl '{instance_url}' foo=bar",
f"vlocity packDeploy -job vlocity.yaml -sfdx.username '{VBT_SF_ALIAS}' foo=bar",
),
]

Expand All @@ -96,7 +99,6 @@
def test_vlocity_simple_job(
project_config, org_config, task_class, extra, expected_command
):

task_config = TaskConfig(
config={
"options": {
Expand All @@ -108,7 +110,16 @@ def test_vlocity_simple_job(
)
task = task_class(project_config, task_config, org_config)

assert task._get_command() == expected_command
with mock.patch(
"cumulusci.tasks.vlocity.vlocity.sarge.Command",
) as Command:
assert task._get_command() == expected_command
if not isinstance(org_config, ScratchOrgConfig):
cmd_args, cmd_kwargs = Command.call_args
assert SF_TOKEN_ENV in cmd_kwargs["env"]
assert cmd_kwargs["env"][SF_TOKEN_ENV] == access_token
assert instance_url in cmd_args[0]
assert VBT_SF_ALIAS in cmd_args[0]


def test_vlocity_build_tool_missing(project_config):
Expand All @@ -125,6 +136,29 @@ def test_vlocity_build_tool_missing(project_config):
task._init_task()


def test_vlocity_npm_auth_env(project_config, tmp_path, monkeypatch):
job_file = tmp_path / "vlocity.yaml"
job_file.write_text("key: value")
task_config = TaskConfig(
config={"options": {"job_file": job_file, "org": org_name}}
)
task = VlocityDeployTask(project_config, task_config, scratch_org_config)
# No env, don't write
failure: bool = task.add_npm_auth_to_jobfile(str(job_file), VBT_TOKEN_ENV)
assert failure is False

monkeypatch.setenv(VBT_TOKEN_ENV, "token")
success: bool = task.add_npm_auth_to_jobfile(str(job_file), VBT_TOKEN_ENV)
job_file_txt = job_file.read_text()
assert success is True
assert "npmAuthKey: token" in job_file_txt

skipped: bool = task.add_npm_auth_to_jobfile(str(job_file), VBT_TOKEN_ENV)
job_file_skip = job_file.read_text()
assert skipped is False
assert job_file_skip == job_file_txt


namespace = "omnistudio"
test_cases = [
(TaskConfig(config={}), OMNI_NAMESPACE),
Expand Down
100 changes: 94 additions & 6 deletions cumulusci/tasks/vlocity/vlocity.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import os
import re
import sys
from abc import ABC
from pathlib import Path
from typing import Final

import sarge

from cumulusci.core.config.scratch_org_config import ScratchOrgConfig
from cumulusci.core.exceptions import SfdxOrgException
from cumulusci.core.tasks import BaseSalesforceTask
from cumulusci.tasks.command import Command
from cumulusci.tasks.metadata_etl.remote_site_settings import (
Expand All @@ -23,6 +26,9 @@
VF_LEGACY_RSS_NAME = "OmniStudioLegacyVisualforce"
LWC_RSS_NAME = "OmniStudioLightning"
OMNI_NAMESPACE = "omnistudio"
VBT_SF_ALIAS = "cci-vbt-target"
SF_TOKEN_ENV = "SFDX_ACCESS_TOKEN"
VBT_TOKEN_ENV = "OMNIOUT_TOKEN"


class VlocityBaseTask(Command, BaseSalesforceTask):
Expand Down Expand Up @@ -80,16 +86,41 @@ def _get_command(self) -> str:

command: str = f"{self.command_keyword} -job {job_file}"

if isinstance(self.org_config, ScratchOrgConfig):
command = f"{command} -sfdx.username '{username}'"
else:
access_token: str = f"-sf.accessToken '{self.org_config.access_token}'"
instance_url: str = f"-sf.instanceUrl '{self.org_config.instance_url}'"
command = f"{command} {access_token} {instance_url}"
if not isinstance(self.org_config, ScratchOrgConfig):
username = self._add_token_to_sfdx(
self.org_config.access_token, self.org_config.instance_url
)
command = f"{command} -sfdx.username '{username}'"

self.options["command"] = command
return super()._get_command()

def _add_token_to_sfdx(self, access_token: str, instance_url: str) -> str:
"""
HACK: VBT's documentation suggests that passing sf.accessToken/sf.instanceUrl
is compatible with local compilation, but our experience (as of VBT v1.17)
says otherwise. This function is our workaround: by adding the access token
and temporarily setting it as the default we allow VBT to deploy the
locally compiled components via SFDX or salesforce-alm.
"""
# TODO: Use the sf v2 form of this command instead (when we migrate)
token_store_cmd = [
"sfdx",
"force:auth:accesstoken:store",
"--no-prompt",
"--alias",
f"{VBT_SF_ALIAS}",
"--instance-url",
f"{instance_url}",
]
try:
p = sarge.Command(token_store_cmd, env={SF_TOKEN_ENV: access_token})
p.run(async_=True)
p.wait()
except Exception as exc:
raise SfdxOrgException("token store failed") from exc
return VBT_SF_ALIAS


class VlocityRetrieveTask(VlocitySimpleJobTask):
"""Runs a `vlocity packExport` command with a given user and job file"""
Expand All @@ -102,6 +133,63 @@ class VlocityDeployTask(VlocitySimpleJobTask):

command_keyword: Final[str] = "packDeploy"

task_options: dict = {
"job_file": {
"description": "Filepath to the jobfile",
"required": True,
},
"extra": {"description": "Any extra arguments to pass to the vlocity CLI"},
"npm_auth_key_env": {
"description": (
"Environment variable storing an auth token for the "
"Vlocity NPM Repository (npmAuthKey). If defined, appended "
"to the job file."
),
"default": VBT_TOKEN_ENV,
},
}

def _get_command(self) -> str:
npm_env_var: str = self.options.get("npm_auth_key_env", VBT_TOKEN_ENV)
job_file: str = self.options.get("job_file")

self.add_npm_auth_to_jobfile(job_file, npm_env_var)

return super()._get_command()

def add_npm_auth_to_jobfile(self, job_file: str, npm_env_var: str) -> bool:
"""
HACK: VBT local compilation requires use of an auth token for a private
NPM repository, defined as the npmAuthKey option. Unfortunately, this
option can't be defined in an environment variable, nor can it be passed
via the CLI. Instead, the option is _only_ read from the job file, so the
secret must be committed to source control for CI/CD. Our workaround is to
check for the presence of npm_env_var in the environment, and appending it
to the job file if it is.
Retuns:
- False: No environment variable found or conflict exists in job file
- True: Auth token written to job file
"""
found_msg = f"VBT NPM environment variable named '{npm_env_var}' found."
if npm_key := os.environ.get(npm_env_var):
self.logger.info(found_msg)
else:
self.logger.debug(f"No {found_msg}")
return False

job_file_path: Path = Path(job_file)
job_file_txt = job_file_path.read_text()

# Warn about duplicate keys to avoid showing the user a nasty JS traceback
if "npmAuthKey" in job_file_txt:
self.logger.warning("npmAuthKey present in job file, skipping...")
return False

self.logger.info(f"Appending to {job_file}")
job_file_path.write_text(f"{job_file_txt}\nnpmAuthKey: {npm_key}")
return True


class OmniStudioDeployRemoteSiteSettings(AddRemoteSiteSettings):
"""Deploys remote site settings needed for OmniStudio.
Expand Down

0 comments on commit 371a942

Please sign in to comment.