Example #1
0
 def test_check_return(self, Command):
     Command.return_value.returncode = 1
     Command.return_value.stderr = io.BytesIO(b"Egads!")
     with pytest.raises(Exception) as exc_info:
         sfdx("cmd", check_return=True)
     assert str(
         exc_info.value) == "Command exited with return code 1:\nEgads!"
 def unset_default_org(self):
     """ unset the default orgs for tasks """
     for org in self.list_orgs():
         org_config = self.get_org(org)
         if org_config.default:
             del org_config.config["default"]
             self.set_org(org_config)
     sfdx("force:config:set defaultusername=")
Example #3
0
 def unset_default_org(self):
     """ unset the default orgs for tasks """
     for org in self.list_orgs():
         org_config = self.get_org(org)
         if org_config.default:
             del org_config.config["default"]
             org_config.save()
     sfdx("force:config:set defaultusername=")
Example #4
0
 def set_default_org(self, name):
     """ set the default org for tasks and flows by name """
     org = self.get_org(name)
     self.unset_default_org()
     org.config["default"] = True
     org.save()
     if org.created:
         sfdx(
             sarge.shell_format("force:config:set defaultusername={}",
                                org.sfdx_alias))
Example #5
0
 def _reset_sfdx_snapshot(self):
     # If org is from sfdx, reset sfdx source tracking
     if self.project_config.project__source_format == "sfdx" and isinstance(
             self.org_config, ScratchOrgConfig):
         sfdx(
             "force:source:tracking:reset",
             args=["-p"],
             username=self.org_config.username,
             capture_output=True,
             check_return=True,
         )
 def set_default_org(self, name):
     """ set the default org for tasks by name key """
     org = self.get_org(name)
     self.unset_default_org()
     org.config["default"] = True
     self.set_org(org)
     if org.created:
         sfdx(
             sarge.shell_format(
                 "force:config:set defaultusername={}", org.sfdx_alias
             )
         )
Example #7
0
    def delete_org(self):
        """ Uses sfdx force:org:delete to delete the org """
        if not self.created:
            self.logger.info(
                "Skipping org deletion: the scratch org has not been created")
            return

        p = sfdx("force:org:delete -p", self.username, "Deleting scratch org")

        stdout = []
        for line in p.stdout_text:
            stdout.append(line)
            if line.startswith("An error occurred deleting this org"):
                self.logger.error(line)
            else:
                self.logger.info(line)

        if p.returncode:
            message = "Failed to delete scratch org: \n{}".format(
                "".join(stdout))
            raise ScratchOrgException(message)

        # Flag that this org has been deleted
        self.config["created"] = False
        self.config["username"] = None
        self.config["date_created"] = None
Example #8
0
    def delete_org(self):
        """ Uses sfdx force:org:delete to delete the org """
        if not self.created:
            self.logger.info(
                "Skipping org deletion: the scratch org has not been created")
            return

        p = sfdx("force:org:delete -p", self.username, "Deleting scratch org")

        output = []
        for line in list(p.stdout_text) + list(p.stderr_text):
            output.append(line)
            if "error" in line.lower():
                self.logger.error(line)
            else:
                self.logger.info(line)

        if p.returncode:
            message = "Failed to delete scratch org"
            raise ScratchOrgException(message)

        # Flag that this org has been deleted
        self.config["created"] = False
        self.config["username"] = None
        self.config["date_created"] = None
        self.config["instance_url"] = None
Example #9
0
    def generate_password(self):
        """Generates an org password with the sfdx utility. """

        if self.password_failed:
            self.logger.warning(
                "Skipping resetting password since last attempt failed")
            return

        # Set a random password so it's available via cci org info

        p = sfdx(
            "force:user:password:generate",
            self.username,
            log_note="Generating scratch org user password",
        )

        stderr = p.stderr_text.readlines()
        stdout = p.stdout_text.readlines()

        if p.returncode:
            self.config["password_failed"] = True
            # Don't throw an exception because of failure creating the
            # password, just notify in a log message
            self.logger.warning("Failed to set password: \n{}\n{}".format(
                "\n".join(stdout), "\n".join(stderr)))
Example #10
0
    def sfdx_info(self):
        if hasattr(self, "_sfdx_info"):
            return self._sfdx_info

        # On-demand creation of scratch orgs
        if self.create_org is not None and not self.created:
            self.create_org()

        username = self.config.get("username")
        assert username is not None, "SfdxOrgConfig must have a username"

        self.logger.info(f"Getting org info from Salesforce CLI for {username}")

        # Call force:org:display and parse output to get instance_url and
        # access_token
        p = sfdx("force:org:display --json", self.username)

        org_info = None
        stderr_list = [line.strip() for line in p.stderr_text]
        stdout_list = [line.strip() for line in p.stdout_text]

        if p.returncode:
            self.logger.error(f"Return code: {p.returncode}")
            for line in stderr_list:
                self.logger.error(line)
            for line in stdout_list:
                self.logger.error(line)
            message = f"\nstderr:\n{nl.join(stderr_list)}"
            message += f"\nstdout:\n{nl.join(stdout_list)}"
            raise SfdxOrgException(message)

        else:
            try:
                org_info = json.loads("".join(stdout_list))
            except Exception as e:
                raise SfdxOrgException(
                    "Failed to parse json from output.\n  "
                    f"Exception: {e.__class__.__name__}\n  Output: {''.join(stdout_list)}"
                )
            org_id = org_info["result"]["accessToken"].split("!")[0]

        sfdx_info = {
            "instance_url": org_info["result"]["instanceUrl"],
            "access_token": org_info["result"]["accessToken"],
            "org_id": org_id,
            "username": org_info["result"]["username"],
        }
        if org_info["result"].get("password"):
            sfdx_info["password"] = org_info["result"]["password"]
        self._sfdx_info = sfdx_info
        self._sfdx_info_date = datetime.datetime.utcnow()
        self.config.update(sfdx_info)

        sfdx_info.update(
            {
                "created_date": org_info["result"].get("createdDate"),
                "expiration_date": org_info["result"].get("expirationDate"),
            }
        )
        return sfdx_info
Example #11
0
    def _convert_sfdx_format(self, path, name):
        orig_path = path
        with contextlib.ExitStack() as stack:
            if not pathlib.Path(path, "package.xml").exists():
                self.logger.info("Converting from sfdx to mdapi format")
                path = stack.enter_context(temporary_dir(chdir=False))
                args = ["-r", str(orig_path), "-d", path]
                if name:
                    args += ["-n", name]
                sfdx(
                    "force:source:convert",
                    args=args,
                    capture_output=False,
                    check_return=True,
                )

            yield path
Example #12
0
    def get_access_token(self, **userfields):
        """Get the access token for a specific user

        If no keyword arguments are passed in, this will return the
        access token for the default user. If userfields has the key
        "username", the access token for that user will be returned.
        Otherwise, a SOQL query will be made based off of the
        passed-in fields to find the username, and the token for that
        username will be returned.

        Examples:

        | # default user access token:
        | token = org.get_access_token()

        | # access token for '*****@*****.**'
        | token = org.get_access_token(username='******')

        | # access token for user based on lookup fields
        | token = org.get_access_token(alias='dadvisor')

        """
        if not userfields:
            # No lookup fields specified? Return the token for the default user
            return self.access_token

        # if we have a username, use it. Otherwise we need to do a
        # lookup using the passed-in fields.
        username = userfields.get("username", None)
        if username is None:
            where = [f"{key} = '{value}'" for key, value in userfields.items()]
            query = f"SELECT Username FROM User WHERE {' AND '.join(where)}"
            result = self.salesforce_client.query_all(query).get("records", [])
            if len(result) == 0:
                raise SfdxOrgException(
                    "Couldn't find a username for the specified user.")
            elif len(result) > 1:
                raise SfdxOrgException(
                    "More than one user matched the search critiera.")
            else:
                username = result[0]["Username"]

        p = sfdx(f"force:org:display --targetusername={username} --json")
        if p.returncode:
            output = p.stdout_text.read()
            try:
                info = json.loads(output)
                explanation = info["message"]
            except (JSONDecodeError, KeyError):
                explanation = output

            raise SfdxOrgException(
                f"Unable to find access token for {username}\n{explanation}")
        else:
            info = json.loads(p.stdout_text.read())
            return info["result"]["accessToken"]
    def force_refresh_oauth_token(self):
        # Call force:org:display and parse output to get instance_url and
        # access_token
        p = sfdx("force:org:open -r", self.username, log_note="Refreshing OAuth token")

        stdout_list = [line.strip() for line in p.stdout_text]

        if p.returncode:
            self.logger.error(f"Return code: {p.returncode}")
            for line in stdout_list:
                self.logger.error(line)
            message = f"Message: {nl.join(stdout_list)}"
            raise ScratchOrgException(message)
Example #14
0
    def force_refresh_oauth_token(self):
        # Call force:org:display and parse output to get instance_url and
        # access_token
        p = sfdx("force:org:open -r",
                 self.username,
                 log_note="Refreshing OAuth token")

        stdout_list = [line.strip() for line in io.TextIOWrapper(p.stdout)]

        if p.returncode:
            self.logger.error("Return code: {}".format(p.returncode))
            for line in stdout_list:
                self.logger.error(line)
            message = "Message: {}".format("\n".join(stdout_list))
            raise ScratchOrgException(message)
Example #15
0
    def scratch_info(self):
        if hasattr(self, "_scratch_info"):
            return self._scratch_info

        # Create the org if it hasn't already been created
        if not self.created:
            self.create_org()

        self.logger.info("Getting scratch org info from Salesforce DX")

        username = self.config.get("username")
        if not username:
            raise ScratchOrgException(
                "SFDX claimed to be successful but there was no username "
                "in the output...maybe there was a gack?")

        # Call force:org:display and parse output to get instance_url and
        # access_token
        p = sfdx("force:org:display --json", self.username)

        org_info = None
        stderr_list = [line.strip() for line in p.stderr_text]
        stdout_list = [line.strip() for line in p.stdout_text]

        if p.returncode:
            self.logger.error("Return code: {}".format(p.returncode))
            for line in stderr_list:
                self.logger.error(line)
            for line in stdout_list:
                self.logger.error(line)
            message = "\nstderr:\n{}".format("\n".join(stderr_list))
            message += "\nstdout:\n{}".format("\n".join(stdout_list))
            raise ScratchOrgException(message)

        else:
            try:
                org_info = json.loads("".join(stdout_list))
            except Exception as e:
                raise ScratchOrgException(
                    "Failed to parse json from output. This can happen if "
                    "your scratch org gets deleted.\n  "
                    "Exception: {}\n  Output: {}".format(
                        e.__class__.__name__, "".join(stdout_list)))
            org_id = org_info["result"]["accessToken"].split("!")[0]

        if "password" in org_info["result"] and org_info["result"]["password"]:
            password = org_info["result"]["password"]
        else:
            password = self.config.get("password")

        self._scratch_info = {
            "instance_url": org_info["result"]["instanceUrl"],
            "access_token": org_info["result"]["accessToken"],
            "org_id": org_id,
            "username": org_info["result"]["username"],
            "password": password,
        }

        self.config.update(self._scratch_info)

        self._scratch_info_date = datetime.datetime.utcnow()

        return self._scratch_info
Example #16
0
    def create_org(self):
        """ Uses sfdx force:org:create to create the org """
        if not self.config_file:
            # FIXME: raise exception
            return
        if not self.scratch_org_type:
            self.config["scratch_org_type"] = "workspace"

        # If the scratch org definition itself contains an `adminEmail` entry,
        # we don't want to override it from our own configuration, which may
        # simply come from the user's Git config.

        with open(self.config_file, "r") as org_def:
            org_def_data = json.load(org_def)
            org_def_has_email = "adminEmail" in org_def_data

        options = {
            "config_file":
            self.config_file,
            "devhub":
            " --targetdevhubusername {}".format(self.devhub)
            if self.devhub else "",
            "namespaced":
            " -n" if not self.namespaced else "",
            "days":
            " --durationdays {}".format(self.days) if self.days else "",
            "alias":
            sarge.shell_format(' -a "{0!s}"', self.sfdx_alias)
            if self.sfdx_alias else "",
            "email":
            sarge.shell_format('adminEmail="{0!s}"', self.email_address)
            if self.email_address and not org_def_has_email else "",
            "default":
            " -s" if self.default else "",
            "extraargs":
            os.environ.get("SFDX_ORG_CREATE_ARGS", ""),
        }

        # This feels a little dirty, but the use cases for extra args would mostly
        # work best with env vars
        command = "force:org:create -f {config_file}{devhub}{namespaced}{days}{alias}{default} {email} {extraargs}".format(
            **options)
        p = sfdx(command, username=None, log_note="Creating scratch org")

        stderr = [line.strip() for line in p.stderr_text]
        stdout = [line.strip() for line in p.stdout_text]

        if p.returncode:
            message = "{}: \n{}\n{}".format(FAILED_TO_CREATE_SCRATCH_ORG,
                                            "\n".join(stdout),
                                            "\n".join(stderr))
            raise ScratchOrgException(message)

        re_obj = re.compile(
            "Successfully created scratch org: (.+), username: (.+)")
        for line in stdout:
            match = re_obj.search(line)
            if match:
                self.config["org_id"] = match.group(1)
                self.config["username"] = match.group(2)
            self.logger.info(line)
        for line in stderr:
            self.logger.error(line)

        self.config["date_created"] = datetime.datetime.now()

        if self.config.get("set_password"):
            self.generate_password()

        # Flag that this org has been created
        self.config["created"] = True
Example #17
0
 def test_check_return(self, Command):
     Command.return_value.returncode = 1
     with pytest.raises(Exception) as exc_info:
         sfdx("cmd", check_return=True)
     assert str(exc_info.value) == "Command exited with return code 1"
Example #18
0
 def test_posix_quoting(self, Command):
     sfdx("cmd", args=["a'b"])
     cmd = Command.call_args[0][0]
     assert cmd == r"sfdx cmd 'a'\''b'"
Example #19
0
    def create_org(self):
        """ Uses sfdx force:org:create to create the org """
        if not self.config_file:
            raise ScratchOrgException(
                f"Scratch org config {self.name} is missing a config_file")
        if not self.scratch_org_type:
            self.config["scratch_org_type"] = "workspace"

        # If the scratch org definition itself contains an `adminEmail` entry,
        # we don't want to override it from our own configuration, which may
        # simply come from the user's Git config.

        with open(self.config_file, "r") as org_def:
            org_def_data = json.load(org_def)
            org_def_has_email = "adminEmail" in org_def_data

        devhub = self._choose_devhub()
        instance = self.instance or os.environ.get("SFDX_SIGNUP_INSTANCE")
        options = {
            "config_file":
            self.config_file,
            "devhub":
            f" --targetdevhubusername {devhub}" if devhub else "",
            "namespaced":
            " -n" if not self.namespaced else "",
            "days":
            f" --durationdays {self.days}" if self.days else "",
            "wait":
            " -w 120",
            "alias":
            sarge.shell_format(' -a "{0!s}"', self.sfdx_alias)
            if self.sfdx_alias else "",
            "email":
            sarge.shell_format(' adminEmail="{0!s}"', self.email_address)
            if self.email_address and not org_def_has_email else "",
            "default":
            " -s" if self.default else "",
            "instance":
            f" instance={instance}" if instance else "",
            "extraargs":
            os.environ.get("SFDX_ORG_CREATE_ARGS", ""),
        }

        # This feels a little dirty, but the use cases for extra args would mostly
        # work best with env vars
        command = "force:org:create -f {config_file}{devhub}{namespaced}{days}{alias}{default}{wait}{email}{instance} {extraargs}".format(
            **options)
        p = sfdx(command, username=None, log_note="Creating scratch org")

        stderr = [line.strip() for line in p.stderr_text]
        stdout = [line.strip() for line in p.stdout_text]

        if p.returncode:
            message = f"{FAILED_TO_CREATE_SCRATCH_ORG}: \n{nl.join(stdout)}\n{nl.join(stderr)}"
            raise ScratchOrgException(message)

        re_obj = re.compile(
            "Successfully created scratch org: (.+), username: (.+)")
        username = None
        for line in stdout:
            match = re_obj.search(line)
            if match:
                self.config["org_id"] = match.group(1)
                self.config["username"] = username = match.group(2)
            self.logger.info(line)
        for line in stderr:
            self.logger.error(line)

        if username is None:
            raise ScratchOrgException(
                "SFDX claimed to be successful but there was no username "
                "in the output...maybe there was a gack?")

        self.config["date_created"] = datetime.datetime.utcnow()

        if self.config.get("set_password"):
            self.generate_password()

        # Flag that this org has been created
        self.config["created"] = True
Example #20
0
def retrieve_components(
    components,
    org_config,
    target: str,
    md_format: bool,
    extra_package_xml_opts: dict,
    namespace_tokenize: str,
    api_version: str,
):
    """Retrieve specified components from an org into a target folder.

    Retrieval is done using the sfdx force:source:retrieve command.

    Set `md_format` to True if retrieving into a folder with a package
    in metadata format. In this case the folder will be temporarily
    converted to dx format for the retrieval and then converted back.
    Retrievals to metadata format can also set `namespace_tokenize`
    to a namespace prefix to replace it with a `%%%NAMESPACE%%%` token.
    """

    with contextlib.ExitStack() as stack:
        if md_format:
            # Create target if it doesn't exist
            if not os.path.exists(target):
                os.mkdir(target)
                touch(os.path.join(target, "package.xml"))

            # Inject namespace
            if namespace_tokenize:
                process_text_in_directory(
                    target,
                    functools.partial(inject_namespace,
                                      namespace=namespace_tokenize,
                                      managed=True),
                )

            # Temporarily convert metadata format to DX format
            stack.enter_context(temporary_dir())
            os.mkdir("target")
            # We need to create sfdx-project.json
            # so that sfdx will recognize force-app as a package directory.
            with open("sfdx-project.json", "w") as f:
                json.dump(
                    {
                        "packageDirectories": [{
                            "path": "force-app",
                            "default": True
                        }]
                    }, f)
            sfdx(
                "force:mdapi:convert",
                log_note="Converting to DX format",
                args=["-r", target, "-d", "force-app"],
                check_return=True,
            )

        # Construct package.xml with components to retrieve, in its own tempdir
        package_xml_path = stack.enter_context(temporary_dir(chdir=False))
        _write_manifest(components, package_xml_path, api_version)

        # Retrieve specified components in DX format
        sfdx(
            "force:source:retrieve",
            access_token=org_config.access_token,
            log_note="Retrieving components",
            args=[
                "-a",
                str(api_version),
                "-x",
                os.path.join(package_xml_path, "package.xml"),
                "-w",
                "5",
            ],
            capture_output=False,
            check_return=True,
            env={"SFDX_INSTANCE_URL": org_config.instance_url},
        )

        if md_format:
            # Convert back to metadata format
            sfdx(
                "force:source:convert",
                log_note="Converting back to metadata format",
                args=["-r", "force-app", "-d", target],
                capture_output=False,
                check_return=True,
            )

            # Reinject namespace tokens
            if namespace_tokenize:
                process_text_in_directory(
                    target,
                    functools.partial(tokenize_namespace,
                                      namespace=namespace_tokenize),
                )

            # Regenerate package.xml,
            # to avoid reformatting or losing package name/scripts
            package_xml_opts = {
                "directory": target,
                "api_version": api_version,
                **extra_package_xml_opts,
            }
            package_xml = PackageXmlGenerator(**package_xml_opts)()
            with open(os.path.join(target, "package.xml"), "w") as f:
                f.write(package_xml)
Example #21
0
 def test_windows_quoting(self, Command):
     sfdx("cmd", args=['a"b'], access_token="token")
     cmd = Command.call_args[0][0]
     assert cmd == r'sfdx cmd "a\"b" -u token'