Ejemplo n.º 1
0
 def test_get_default_org(self):
     keychain = self.keychain_class(self.project_config, self.key)
     org_config = self.org_config.config.copy()
     org_config = OrgConfig(org_config, "test", keychain=keychain)
     org_config.save()
     keychain.set_default_org("test")
     org_config.config["default"] = True
     self.assertEqual(keychain.get_default_org()[1].config, org_config.config)
Ejemplo n.º 2
0
 def test_get_default_org__file_missing_fallback(self):
     keychain = self.keychain_class(self.project_config, self.key)
     org_config = OrgConfig(self.org_config.config.copy(),
                            "test",
                            keychain=keychain)
     org_config.config["default"] = True
     org_config.save()
     self.assertEqual(keychain.get_default_org()[1].config,
                      org_config.config)
Ejemplo n.º 3
0
 def test_get_default_org__with_files(self):
     keychain = self.keychain_class(self.project_config, self.key)
     org_config = OrgConfig(self.org_config.config.copy(), "test", keychain=keychain)
     org_config.save()
     with open(self._default_org_path(), "w") as f:
         f.write("test")
     try:
         self.assertEqual(keychain.get_default_org()[1].config, org_config.config)
     finally:
         self._default_org_path().unlink()
Ejemplo n.º 4
0
def run_flows(
    *,
    plan: Plan,
    skip_steps: list[str],
    result_class: Union[type[Job], type[PreflightResult]],
    result_id: int,
):
    """
    This operates with side effects; it changes things in a Salesforce
    org, and then records the results of those operations on to a
    `result`.

    Args:
        plan (Plan): The Plan instance for the flow you're running.
        skip_steps (List[str]): The strings in the list should be valid
            step_num values for steps in this flow.
        result_class (Union[Type[Job], Type[PreflightResult]]): The type
            of the instance onto which to record the results of running
            steps in the flow. Either a PreflightResult or a Job, as
            appropriate.
        result_id (int): the PK of the result instance to get.
    """
    result = result_class.objects.get(pk=result_id)
    scratch_org = None
    if not result.user:
        # This means we're in a ScratchOrg.
        scratch_org = ScratchOrg.objects.get(org_id=result.org_id)

    repo_url = plan.version.product.repo_url
    commit_ish = plan.commit_ish or plan.version.commit_ish

    with contextlib.ExitStack() as stack:
        stack.enter_context(finalize_result(result))
        if result.user:
            stack.enter_context(report_errors_to(result.user))
        if scratch_org:
            stack.enter_context(delete_org_on_error(scratch_org))

        # Let's clone the repo locally:
        repo_user, repo_name = extract_user_and_repo(repo_url)
        repo_root = stack.enter_context(
            local_github_checkout(repo_user, repo_name, commit_ish)
        )

        # Get cwd into Python path, so that the tasks below can import
        # from the checked-out repo:
        stack.enter_context(prepend_python_path(os.path.abspath(repo_root)))

        # There's a lot of setup to make configs and keychains, link
        # them properly, and then eventually pass them into a flow,
        # which we then run:
        ctx = MetaDeployCCI(repo_root=repo_root, plan=plan)

        current_org = "current_org"
        if settings.METADEPLOY_FAST_FORWARD:  # pragma: no cover
            org_config = OrgConfig({}, name=current_org, keychain=ctx.keychain)
        elif scratch_org:
            org_config = scratch_org.get_refreshed_org_config(
                org_name=current_org, keychain=ctx.keychain
            )
        else:
            token, token_secret = result.user.token
            org_config = OrgConfig(
                {
                    "access_token": token,
                    "instance_url": result.user.instance_url,
                    "refresh_token": token_secret,
                    "username": result.user.sf_username,
                    # 'id' is used by CumulusCI to pick the right 'aud' for JWT auth
                    "id": result.user.oauth_id,
                },
                current_org,
                keychain=ctx.keychain,
            )
        org_config.save()

        # Set up the connected_app:
        connected_app = ServiceConfig(
            {
                "client_secret": settings.SFDX_CLIENT_SECRET,
                "callback_url": settings.SFDX_CLIENT_CALLBACK_URL,
                "client_id": settings.SFDX_CLIENT_ID,
                # Note: login_url is not used when refreshing the existing token,
                # so it doesn't matter whether it's login vs test
                "login_url": "https://login.salesforce.com",
            }
        )
        ctx.keychain.set_service("connected_app", "metadeploy", connected_app)
        ctx.keychain._default_services["connected_app"] = "metadeploy"

        steps = [
            step.to_spec(
                project_config=ctx.project_config, skip=step.step_num in skip_steps
            )
            for step in plan.steps.all()
        ]
        org = ctx.keychain.get_org(current_org)
        if not settings.METADEPLOY_FAST_FORWARD:
            result.run(ctx, plan, steps, org)
Ejemplo n.º 5
0
class TestEncryptedFileProjectKeychain(ProjectKeychainTestMixin):
    keychain_class = EncryptedFileProjectKeychain

    def setUp(self):
        self.universal_config = UniversalConfig()
        self.project_config = BaseProjectConfig(
            self.universal_config, config={"noyaml": True}
        )
        self.project_config.config["services"] = {
            "connected_app": {"attributes": {"test": {"required": True}}},
            "github": {"attributes": {"git": {"required": True}, "password": {}}},
            "not_configured": {"attributes": {"foo": {"required": True}}},
        }
        self.project_config.project__name = "TestProject"
        self.project_name = "TestProject"
        self.org_config = OrgConfig({"foo": "bar"}, "test")
        self.scratch_org_config = ScratchOrgConfig(
            {"foo": "bar", "scratch": True}, "test_scratch"
        )
        self.services = {
            "connected_app": ServiceConfig({"test": "value"}),
            "github": ServiceConfig({"git": "hub"}),
        }
        self.key = "0123456789123456"

        self._mk_temp_home()
        self._home_patch = mock.patch(
            "pathlib.Path.home", return_value=Path(self.tempdir_home)
        )
        self._home_patch.__enter__()
        self._mk_temp_project()
        os.chdir(self.tempdir_project)

    def tearDown(self):
        self._home_patch.__exit__(None, None, None)

    def _mk_temp_home(self):
        self.tempdir_home = tempfile.mkdtemp()
        global_config_dir = os.path.join(self.tempdir_home, ".cumulusci")
        os.makedirs(global_config_dir)

    def _mk_temp_project(self):
        self.tempdir_project = tempfile.mkdtemp()
        git_dir = os.path.join(self.tempdir_project, ".git")
        os.makedirs(git_dir)
        self._create_git_config()

    def _create_git_config(self):
        filename = os.path.join(self.tempdir_project, ".git", "config")
        content = (
            '[remote "origin"]\n'
            + f"  url = [email protected]:TestOwner/{self.project_name}"
        )
        self._write_file(filename, content)

    def _write_file(self, filename, content):
        with open(filename, "w") as f:
            f.write(content)

    def test_set_service_github_project(self):
        self.test_set_service_github(True)

    def test_set_and_get_org_global(self):
        self.test_set_and_get_org(True)

    def test_set_and_get_org__universal_config(self):
        keychain = self.keychain_class(self.universal_config, self.key)
        keychain.set_org(self.org_config, False)
        self.assertEqual(list(keychain.orgs.keys()), [])

    def test_load_files__empty(self):
        dummy_keychain = BaseEncryptedProjectKeychain(self.project_config, self.key)
        os.makedirs(os.path.join(self.tempdir_home, ".cumulusci", self.project_name))
        self._write_file(
            os.path.join(self.tempdir_home, "test.org"),
            dummy_keychain._encrypt_config(BaseConfig({"foo": "bar"})).decode("utf-8"),
        )
        keychain = self.keychain_class(self.project_config, self.key)
        del keychain.config["orgs"]
        with mock.patch.object(
            self.keychain_class, "global_config_dir", Path(self.tempdir_home)
        ):
            keychain._load_orgs()
        self.assertIn("foo", keychain.get_org("test").config)
        self.assertEqual(keychain.get_org("test").keychain, keychain)

    def test_load_file(self):
        self._write_file(os.path.join(self.tempdir_home, "config"), "foo")
        keychain = self.keychain_class(self.project_config, self.key)
        keychain._load_file(self.tempdir_home, "config", "from_file")
        self.assertEqual("foo", keychain.config["from_file"])

    def test_load_file__universal_config(self):
        self._write_file(os.path.join(self.tempdir_home, "config"), "foo")
        keychain = self.keychain_class(self.project_config, self.key)
        keychain._load_file(self.tempdir_home, "config", "from_file")
        self.assertEqual("foo", keychain.config["from_file"])

    @mock.patch("cumulusci.core.utils.cleanup_org_cache_dirs")
    def test_remove_org(self, cleanup_org_cache_dirs):
        keychain = self.keychain_class(self.project_config, self.key)
        keychain.set_org(self.org_config)
        keychain.remove_org("test")
        self.assertNotIn("test", keychain.orgs)
        assert cleanup_org_cache_dirs.called_once_with(keychain, self.project_config)

    def test_remove_org__not_found(self):
        keychain = self.keychain_class(self.project_config, self.key)
        keychain.orgs["test"] = mock.Mock()
        with self.assertRaises(OrgNotFound):
            keychain.remove_org("test")

    def test_remove_org__global__not_found(self):
        keychain = self.keychain_class(self.project_config, self.key)
        keychain.orgs["test"] = mock.Mock()
        with self.assertRaises(OrgNotFound):
            keychain.remove_org("test", global_org=True)

    def test_set_and_get_org_local_should_not_shadow_global(self):
        keychain = self.keychain_class(self.project_config, self.key)
        self.org_config.global_org = True
        keychain.set_org(self.org_config, global_org=True)
        assert ["test"] == list(keychain.orgs.keys())
        assert isinstance(keychain.orgs["test"], GlobalOrg), keychain.orgs["test"]
        assert self.org_config.config == keychain.get_org("test").config
        assert Path(self.tempdir_home, ".cumulusci", "test.org").exists()

        # check that it saves to the right place
        with mock.patch(
            "cumulusci.core.keychain.encrypted_file_project_keychain.open"
        ) as o:
            self.org_config.save()
            opened_filename = o.mock_calls[0][1][0]
            assert ".cumulusci/test.org" in opened_filename.replace(
                os.sep, "/"
            ), opened_filename

        # check that it can be loaded in a fresh keychain
        new_keychain = self.keychain_class(self.project_config, self.key)
        org_config = new_keychain.get_org("test")
        assert org_config.global_org

    def test_cache_dir(self):
        keychain = self.keychain_class(self.project_config, self.key)
        assert keychain.cache_dir.name == ".cci"

    def test_get_default_org__with_files(self):
        keychain = self.keychain_class(self.project_config, self.key)
        org_config = OrgConfig(self.org_config.config.copy(), "test", keychain=keychain)
        org_config.save()
        with open(self._default_org_path(), "w") as f:
            f.write("test")
        try:
            self.assertEqual(keychain.get_default_org()[1].config, org_config.config)
        finally:
            self._default_org_path().unlink()

    def test_get_default_org__with_files__missing_org(self):
        keychain = self.keychain_class(self.project_config, self.key)
        with open(self._default_org_path(), "w") as f:
            f.write("should_not_exist")
        assert self._default_org_path().exists()
        assert keychain.get_default_org() == (None, None)
        assert not self._default_org_path().exists()

    @mock.patch("sarge.Command")
    def test_set_default_org__with_files(self, Command):
        keychain = self.keychain_class(self.project_config, self.key)
        org_config = OrgConfig(self.org_config.config.copy(), "test")
        keychain.set_org(org_config)
        keychain.set_default_org("test")
        with open(self._default_org_path()) as f:
            assert f.read() == "test"
        self._default_org_path().unlink()

    @mock.patch("sarge.Command")
    def test_unset_default_org__with_files(self, Command):
        keychain = self.keychain_class(self.project_config, self.key)
        org_config = self.org_config.config.copy()
        org_config = OrgConfig(org_config, "test")
        keychain.set_org(org_config)
        keychain.set_default_org("test")
        keychain.unset_default_org()
        self.assertEqual(keychain.get_default_org()[1], None)
        assert not self._default_org_path().exists()

    def _default_org_path(self):
        return Path(self.tempdir_home) / ".cumulusci/TestProject/DEFAULT_ORG.txt"

    # old way of finding defaults used contents of the files themselves
    # we should preserve backwards compatibiliity for a few months
    def test_get_default_org__file_missing_fallback(self):
        keychain = self.keychain_class(self.project_config, self.key)
        org_config = OrgConfig(self.org_config.config.copy(), "test", keychain=keychain)
        org_config.config["default"] = True
        org_config.save()
        self.assertEqual(keychain.get_default_org()[1].config, org_config.config)

    def test_get_default_org__outside_project(self):
        keychain = self.keychain_class(self.universal_config, self.key)
        assert keychain.get_default_org() == (None, None)
Ejemplo n.º 6
0
def run_flows(*, user, plan, skip_steps, organization_url, result_class,
              result_id):
    """
    This operates with side effects; it changes things in a Salesforce
    org, and then records the results of those operations on to a
    `result`.

    Args:
        user (User): The User requesting this flow be run.
        plan (Plan): The Plan instance for the flow you're running.
        skip_steps (List[str]): The strings in the list should be valid
            step_num values for steps in this flow.
        organization_url (str): The URL of the organization, required by
            the OrgConfig.
        result_class (Union[Type[Job], Type[PreflightResult]]): The type
            of the instance onto which to record the results of running
            steps in the flow. Either a PreflightResult or a Job, as
            appropriate.
        result_id (int): the PK of the result instance to get.
    """
    result = result_class.objects.get(pk=result_id)
    token, token_secret = user.token
    repo_url = plan.version.product.repo_url
    commit_ish = plan.commit_ish or plan.version.commit_ish

    with contextlib.ExitStack() as stack:
        stack.enter_context(finalize_result(result))
        stack.enter_context(report_errors_to(user))
        tmpdirname = stack.enter_context(temporary_dir())

        # Get cwd into Python path, so that the tasks below can import
        # from the checked-out repo:
        stack.enter_context(prepend_python_path(os.path.abspath(tmpdirname)))

        # Let's clone the repo locally:
        user, repo_name = extract_user_and_repo(repo_url)
        gh = get_github_api_for_repo(None, user, repo_name)
        repo = gh.repository(user, repo_name)
        # Make sure we have the actual owner/repo name if we were redirected
        user = repo.owner.login
        repo_name = repo.name
        zip_file_name = "archive.zip"
        repo.archive("zipball", path=zip_file_name, ref=commit_ish)
        zip_file = zipfile.ZipFile(zip_file_name)
        if not zip_file_is_safe(zip_file):
            # This is very unlikely, as we get the zipfile from GitHub,
            # but must be considered:
            url = f"https://github.com/{user}/{repo_name}#{commit_ish}"
            logger.error(f"Malformed or malicious zip file from {url}.")
            return
        zip_file.extractall()
        # We know that the zipball contains a root directory named
        # something like this by GitHub's convention. If that ever
        # breaks, this will break:
        zipball_root = glob(f"{user}-{repo_name}-*")[0]
        # It's not unlikely that the zipball root contains a directory
        # with the same name, so we pre-emptively rename it to probably
        # avoid collisions:
        shutil.move(zipball_root, "zipball_root")
        for path in itertools.chain(glob("zipball_root/*"),
                                    glob("zipball_root/.*")):
            shutil.move(path, ".")
        shutil.rmtree("zipball_root")

        # There's a lot of setup to make configs and keychains, link
        # them properly, and then eventually pass them into a flow,
        # which we then run:
        ctx = MetaDeployCCI(repo_root=tmpdirname, plan=plan)

        current_org = "current_org"
        org_config = OrgConfig(
            {
                "access_token": token,
                "instance_url": organization_url,
                "refresh_token": token_secret,
            },
            current_org,
            keychain=ctx.keychain,
        )
        org_config.save()

        # Set up the connected_app:
        connected_app = ServiceConfig({
            "client_secret":
            settings.CONNECTED_APP_CLIENT_SECRET,
            "callback_url":
            settings.CONNECTED_APP_CALLBACK_URL,
            "client_id":
            settings.CONNECTED_APP_CLIENT_ID,
        })
        ctx.keychain.set_service("connected_app", connected_app, True)

        steps = [
            step.to_spec(project_config=ctx.project_config,
                         skip=step.step_num in skip_steps)
            for step in plan.steps.all()
        ]
        org = ctx.keychain.get_org(current_org)
        result.run(ctx, plan, steps, org)