diff --git a/cumulusci/cli/org.py b/cumulusci/cli/org.py index c8ee718e6f..301e423581 100644 --- a/cumulusci/cli/org.py +++ b/cumulusci/cli/org.py @@ -563,9 +563,20 @@ def org_remove(runtime, org_name, global_org): @click.option( "--no-password", is_flag=True, help="If set, don't set a password for the org" ) +@click.option( + "--release", + help="If provided, specify either previous or preview when creating a scratch org", +) @pass_runtime(require_keychain=True) -def org_scratch(runtime, config_name, org_name, default, devhub, days, no_password): +def org_scratch( + runtime, config_name, org_name, default, devhub, days, no_password, release +): runtime.check_org_overwrite(org_name) + release_options = ["previous", "preview"] + if release and release not in release_options: + raise click.UsageError( + "Release options value is not valid. Either specify preview or previous." + ) scratch_configs = runtime.project_config.lookup("orgs__scratch") if not scratch_configs: @@ -580,7 +591,7 @@ def org_scratch(runtime, config_name, org_name, default, devhub, days, no_passwo scratch_config["devhub"] = devhub runtime.keychain.create_scratch_org( - org_name, config_name, days, set_password=not (no_password) + org_name, config_name, days, set_password=not (no_password), release=release ) if default: diff --git a/cumulusci/cli/tests/test_org.py b/cumulusci/cli/tests/test_org.py index 007e82249d..c6ed45ed80 100644 --- a/cumulusci/cli/tests/test_org.py +++ b/cumulusci/cli/tests/test_org.py @@ -1249,20 +1249,40 @@ def test_org_scratch(self): devhub="hub", days=7, no_password=True, + release="previous", ) runtime.check_org_overwrite.assert_called_once() runtime.keychain.create_scratch_org.assert_called_with( - "test", "dev", 7, set_password=False + "test", "dev", 7, set_password=False, release="previous" ) runtime.keychain.set_default_org.assert_called_with("test") - def test_org_scratch__not_default(self): + def test_org_scratch_release_invalid(self): runtime = mock.Mock() + runtime.project_config.lookup = MockLookup( orgs__scratch={"dev": {"orgName": "Dev"}} ) + with pytest.raises(click.UsageError): + run_click_command( + org.org_scratch, + runtime=runtime, + config_name="dev", + org_name="test", + default=True, + devhub="hub", + days=7, + no_password=True, + release="next", + ) + runtime.check_org_overwrite.assert_called_once() + def test_org_scratch__not_default(self): + runtime = mock.Mock() + runtime.project_config.lookup = MockLookup( + orgs__scratch={"dev": {"orgName": "Dev"}} + ) run_click_command( org.org_scratch, runtime=runtime, @@ -1272,11 +1292,12 @@ def test_org_scratch__not_default(self): devhub="hub", days=7, no_password=True, + release=None, ) runtime.check_org_overwrite.assert_called_once() runtime.keychain.create_scratch_org.assert_called_with( - "test", "dev", 7, set_password=False + "test", "dev", 7, set_password=False, release=None ) def test_org_scratch_no_configs(self): @@ -1293,6 +1314,7 @@ def test_org_scratch_no_configs(self): devhub="hub", days=7, no_password=True, + release="previous", ) def test_org_scratch_config_not_found(self): @@ -1309,6 +1331,7 @@ def test_org_scratch_config_not_found(self): devhub="hub", days=7, no_password=True, + release="previous", ) def test_org_scratch_delete(self): diff --git a/cumulusci/core/config/scratch_org_config.py b/cumulusci/core/config/scratch_org_config.py index 06e81198d4..edd0c18807 100644 --- a/cumulusci/core/config/scratch_org_config.py +++ b/cumulusci/core/config/scratch_org_config.py @@ -23,6 +23,7 @@ class ScratchOrgConfig(SfdxOrgConfig): instance: str password_failed: bool devhub: str + release: str createable: bool = True @@ -145,6 +146,8 @@ def _build_org_create_args(self) -> List[str]: args += ["--noancestors"] if self.days: args += ["--durationdays", str(self.days)] + if self.release: + args += [f"release={self.release}"] if self.sfdx_alias: args += ["-a", self.sfdx_alias] with open(self.config_file, "r") as org_def: diff --git a/cumulusci/core/config/tests/test_config_expensive.py b/cumulusci/core/config/tests/test_config_expensive.py index 13485a7b77..7c3e879fca 100644 --- a/cumulusci/core/config/tests/test_config_expensive.py +++ b/cumulusci/core/config/tests/test_config_expensive.py @@ -793,6 +793,7 @@ def test_build_org_create_args(self, scratch_def_file): "sfdx_alias": "project__org", "default": True, "instance": "NA01", + "release": "previous", }, "test", mock_keychain, @@ -809,6 +810,7 @@ def test_build_org_create_args(self, scratch_def_file): "--noancestors", "--durationdays", "1", + "release=previous", "-a", "project__org", "adminEmail=test@example.com", diff --git a/cumulusci/core/keychain/base_project_keychain.py b/cumulusci/core/keychain/base_project_keychain.py index 78f40a9035..561caeaec7 100644 --- a/cumulusci/core/keychain/base_project_keychain.py +++ b/cumulusci/core/keychain/base_project_keychain.py @@ -54,7 +54,9 @@ def _validate_key(self): # Orgs # ####################################### - def create_scratch_org(self, org_name, config_name, days=None, set_password=True): + def create_scratch_org( + self, org_name, config_name, days=None, set_password=True, release=None + ): """Adds/Updates a scratch org config to the keychain from a named config""" scratch_config = self.project_config.lookup(f"orgs__scratch__{config_name}") if scratch_config is None: @@ -65,16 +67,20 @@ def create_scratch_org(self, org_name, config_name, days=None, set_password=True else: # Use scratch config days or default of 1 day scratch_config.setdefault("days", 1) + if release is not None: + scratch_config["release"] = release scratch_config["set_password"] = bool(set_password) scratch_config["scratch"] = True scratch_config.setdefault("namespaced", False) scratch_config["config_name"] = config_name + scratch_config[ "sfdx_alias" ] = f"{self.project_config.project__name}__{org_name}" org_config = ScratchOrgConfig( scratch_config, org_name, keychain=self, global_org=False ) + org_config.save() def set_org(self, org_config, global_org=False, save=True): diff --git a/cumulusci/core/keychain/tests/test_base_project_keychain.py b/cumulusci/core/keychain/tests/test_base_project_keychain.py index 06e9757fdc..89cc12318a 100644 --- a/cumulusci/core/keychain/tests/test_base_project_keychain.py +++ b/cumulusci/core/keychain/tests/test_base_project_keychain.py @@ -152,9 +152,10 @@ def test_create_scratch_org(self, key): ) keychain = BaseProjectKeychain(project_config, key) keychain.key = key - keychain.create_scratch_org("test", "dev", days=3) + keychain.create_scratch_org("test", "dev", days=3, release="previous") org_config = keychain.get_org("test").config assert org_config["days"] == 3 + assert org_config["release"] == "previous" def test_load_scratch_orgs(self, keychain): assert list(keychain.orgs) == [] diff --git a/cumulusci/schema/cumulusci.jsonschema.json b/cumulusci/schema/cumulusci.jsonschema.json index 7d53f2c257..255d2de4d4 100644 --- a/cumulusci/schema/cumulusci.jsonschema.json +++ b/cumulusci/schema/cumulusci.jsonschema.json @@ -430,6 +430,11 @@ "noancestors": { "title": "Noancestors", "type": "boolean" + }, + "release": { + "title": "Release", + "enum": ["preview", "previous"], + "type": "string" } }, "additionalProperties": false diff --git a/cumulusci/utils/yaml/cumulusci_yml.py b/cumulusci/utils/yaml/cumulusci_yml.py index a85cee77bd..f8498ed9ea 100644 --- a/cumulusci/utils/yaml/cumulusci_yml.py +++ b/cumulusci/utils/yaml/cumulusci_yml.py @@ -150,6 +150,7 @@ class ScratchOrg(CCIDictModel): namespaced: str = None setup_flow: str = None noancestors: bool = None + release: Literal["preview", "previous"] = None class Orgs(CCIDictModel):