Beispiel #1
0
Datei: vc.py Projekt: kaday/rose
 def validate_info_config(cls, info_config):
     """Validate contents in suite info file."""
     reports = DefaultValidators().validate(
         info_config,
         load_meta_config(info_config, config_type=rose.INFO_CONFIG_NAME))
     if reports:
         raise SuiteInfoError(reports)
Beispiel #2
0
 def validate_info_config(cls, info_config):
     """Validate contents in suite info file."""
     reports = DefaultValidators().validate(
         info_config,
         load_meta_config(info_config, config_type=rose.INFO_CONFIG_NAME))
     if reports:
         raise SuiteInfoError(reports)
Beispiel #3
0
 def check_fields(self, info_config, interactive_mode, from_id=None,
                  optional_file=None, prefix=None, error_reported=False):
     """Check the fields in the info config"""
     for key in ["owner", "project", "title"]:
         if not info_config.get([key], no_ignore=True):
             if optional_file is not None:
                 raise SuiteInfoFieldError(key)
             info_config_new = info_config
             error_reported = True
             if interactive_mode:
                 question = ("rose-suite.info: \n compulsory field \"%s\"" +
                             " not defined,\n try again?") % key
                 try:
                     response = raw_input(question + " y/n (default n) ")
                 except EOFError:
                     sys.exit(1)
                 if response != 'y':
                     sys.exit(1)
                 return info_config_new, error_reported
             else:
                 raise SuiteInfoFieldError(key)
     meta_config = load_meta_config(info_config, directory=None,
                                    config_type=rose.INFO_CONFIG_NAME,
                                    error_handler=None,
                                    ignore_meta_error=False)
     reports = DefaultValidators().validate(info_config, meta_config)
     if reports != []:
         reports_map = {None: reports}
         text = (rose.macro.get_reports_as_text
                 (reports_map, "rose.macro.DefaultValidators"))
         if interactive_mode:
             reporter = rose.reporter.Reporter()
             reporter(text, kind=reporter.KIND_ERR,
                      level=reporter.FAIL, prefix="")
             info_config_new = info_config
             error_reported = True
             question = "Metadata issue, do you want to try again?"
             try:
                 response = raw_input(question + " y/n (default n) ")
             except EOFError:
                 sys.exit(1)
             if response != 'y':
                 sys.exit(1)
             return info_config_new, error_reported
         else:
             raise SuiteInfoError(text)
     elif error_reported:
         return None, error_reported
     elif error_reported is False:
         return info_config, error_reported
     if from_id is not None:
         return self._copy(info_config, from_id)
     return info_config, error_reported
Beispiel #4
0
def create(argv):
    """CLI function: create and copy."""
    opt_parser = RoseOptionParser()
    opt_parser.add_my_options(
        "checkout_mode", "info_file", "meta_suite_mode", "non_interactive",
        "prefix", "project")
    opts, args = opt_parser.parse_args(argv)
    verbosity = opts.verbosity - opts.quietness
    client = RosieVCClient(event_handler=Reporter(verbosity))
    SuiteId.svn.event_handler = client.event_handler
    from_id = None
    if args:
        from_id = SuiteId(id_text=args[0])
        if from_id.branch is None:
            from_id.branch = from_id.BRANCH_TRUNK
        if from_id.revision is None:
            from_id.revision = from_id.REV_HEAD
            from_id = SuiteId(id_text=from_id.to_string_with_version())
    interactive_mode = not opts.non_interactive
    if opts.info_file is None:
        info_config = client.generate_info_config(
            from_id, opts.prefix, opts.project)
        if from_id is not None:
            meta_config = load_meta_config(
                info_config,
                directory=None,
                config_type=rose.INFO_CONFIG_NAME,
                error_handler=None,
                ignore_meta_error=False)
            for node_keys, node in meta_config.walk(no_ignore=True):
                if isinstance(node.value, dict):
                    continue
                sect, key = node_keys
                value = node.value
                sect = sect.replace("=", "")
                if key == "copy-mode" and value == "clear":
                    info_config.set([sect], "")
                if key == "copy-mode" and value == "never":
                    info_config.unset([sect])

        info_config = _edit_info_config(opts, client, info_config)
    else:
        file_ = opts.info_file
        if opts.info_file == "-":
            file_ = sys.stdin
        info_config = rose.config.load(file_)
    info_config = _validate_info_config(opts, client, info_config)
    if interactive_mode:
        prefix = opts.prefix
        if from_id:
            if not prefix:
                prefix = from_id.prefix
            question = PROMPT_COPY % (from_id.to_string_with_version(), prefix)
        else:
            if not prefix:
                prefix = SuiteId.get_prefix_default()
            question = PROMPT_CREATE % prefix
        try:
            response = input(question)
        except EOFError:
            sys.exit(1)
        if response != YES:
            sys.exit(1)
    try:
        id_ = client.create(
            info_config, from_id, opts.prefix, opts.meta_suite_mode)
    except (RosePopenError, SuiteIdOverflowError) as exc:
        client.event_handler(exc)
        sys.exit(1)
    if opts.checkout_mode:
        try:
            client.checkout(id_)
        except (FileExistError, RosePopenError) as exc:
            client.event_handler(exc)
            sys.exit(1)
Beispiel #5
0
    def generate_info_config(self, from_id=None, prefix=None, project=None):
        """Generate a rose.config.ConfigNode for a rose-suite.info.

        This is suitable for passing into the create method of this
        class.
        If from_id is defined, copy items from it.
        Return the rose.config.ConfigNode instance.

        """
        from_project = None
        from_title = None
        if from_id is not None:
            from_info_url = "%s/%s/rose-suite.info@%s" % (from_id.to_origin(),
                                                          from_id.branch,
                                                          from_id.revision)
            out_data = self.popen("svn", "cat", from_info_url)[0]
            from_config = rose.config.load(StringIO(out_data.decode()))

        res_loc = ResourceLocator.default()
        older_config = None
        info_config = rose.config.ConfigNode()

        # Determine project if given as a command-line option on create
        if from_id is None and project is not None:
            info_config.set(["project"], project)

        # Set the compulsory fields and use the project and metadata if
        #  available.
        meta_config = load_meta_config(
            info_config, config_type=rose.INFO_CONFIG_NAME)
        if from_id is None and project is not None:
            for node_keys, node in meta_config.walk(no_ignore=True):
                if isinstance(node.value, dict):
                    continue
                sect, key = node_keys
                value = node.value
                sect = sect.translate(None, "=")
                if key == "compulsory" and value == "true":
                    info_config.set([sect], "")
            info_config.set(["project"], project)
        else:
            if from_project is None:
                info_config.set(["project"], "")
            if from_title is None:
                info_config.set(["title"], "")

        # Determine prefix
        if prefix is None:
            if from_id is None:
                prefix = SuiteId.get_prefix_default()
            else:
                prefix = from_id.prefix

        # Determine owner:
        # 1. From user configuration [rosie-id]prefix-username
        # 2. From username of a matching group in [groups] in
        #    ~/.subversion/servers
        # 3. Current user ID
        owner = res_loc.get_conf().get_value(
            ["rosie-id", "prefix-username." + prefix])
        if not owner and self.subversion_servers_conf:
            servers_conf = rose.config.load(self.subversion_servers_conf)
            groups_node = servers_conf.get(["groups"])
            if groups_node is not None:
                prefix_loc = SuiteId.get_prefix_location(prefix)
                prefix_host = urlparse(prefix_loc).hostname
                for key, node in groups_node.value.items():
                    if fnmatch(prefix_host, node.value):
                        owner = servers_conf.get_value([key, "username"])
                        break
        if not owner:
            owner = pwd.getpwuid(os.getuid())[0]
        info_config.set(["owner"], owner)

        # Copy description
        try:
            from_id.to_string_with_version()
            info_config.set(
                ["description"],
                "Copy of %s" % (from_id.to_string_with_version()))
        except AttributeError:
            pass

        # Copy fields provided by the user
        try:
            from_config.walk(no_ignore=False)
            for node_keys, node in from_config.walk(no_ignore=False):
                if isinstance(node.value, dict):
                    continue
                sect, key = node_keys
                value = node.value
                if (key in ["description", "owner", "access-list"] or
                        (key == "project" and from_project is not None)):
                    pass
                else:
                    info_config.set([key], value)
        except UnboundLocalError:
            pass

        # Determine access list
        access_list_str = res_loc.get_conf().get_value(
            ["rosie-vc", "access-list-default"])
        if access_list_str:
            info_config.set(["access-list"], access_list_str)
        if from_id is None and project is not None:
            for node_keys, node in meta_config.walk(no_ignore=True):
                if isinstance(node.value, dict):
                    continue
                sect, key = node_keys
                value = node.value
                sect = sect.translate(None, "=")
                if key == "value-hints" or key == "values":
                    reminder = ("please remove all commented hints/lines " +
                                "in the main/top section before saving.")
                    info_config.set([sect],
                                    rose.variable.array_split(value)[0],
                                    comments=[value, reminder])
        if older_config is not None:
            for node_keys, node in older_config.walk(no_ignore=True):
                if isinstance(node.value, dict):
                    continue
                sect, key = node_keys
                value = node.value
                info_config.set([key], value)

        return info_config
Beispiel #6
0
    def run(self, repos, txn):
        """Apply the rule engine on transaction "txn" to repository "repos"."""

        changes = set()  # set([(status, path), ...])
        for line in self._svnlook("changed", "-t", txn, repos).splitlines():
            status, path = line.split(None, 1)
            changes.add((status, path))
        bad_changes = []
        author = None
        super_users = None
        rev_info_map = {}  # {path-id: (owner, access-list), ...}
        txn_info_map = {}

        conf = ResourceLocator.default().get_conf()
        ignores_str = conf.get_value(["rosa-svn", "ignores"], self.IGNORES)
        ignores = shlex.split(ignores_str)

        for status, path in sorted(changes):
            if any([fnmatch(path, ignore) for ignore in ignores]):
                continue

            names = path.split("/", self.LEN_ID + 1)
            if not names[-1]:
                names.pop()

            # Directories above the suites must match the ID patterns
            is_bad = False
            for name, pattern in zip(names, self.RE_ID_NAMES):
                if not re.compile(r"\A" + pattern + r"\Z").match(name):
                    is_bad = True
                    break
            if is_bad:
                bad_changes.append(BadChange(status, path))
                continue

            # Can only add directories at levels above the suites
            if len(names) < self.LEN_ID:
                if status[0] != self.ST_ADD:
                    bad_changes.append(BadChange(status, path))
                continue
            else:
                is_meta_suite = "".join(names[0:self.LEN_ID]) == "ROSIE"

            # No need to check non-trunk changes
            if len(names) > self.LEN_ID and names[self.LEN_ID] != "trunk":
                continue

            # New suite should have an info file
            if status[0] == self.ST_ADD and len(names) == self.LEN_ID:
                if (self.ST_ADD, path + "trunk/") not in changes:
                    bad_changes.append(
                        BadChange(status, path, BadChange.NO_TRUNK))
                    continue
                path_trunk_info_file = path + self.TRUNK_INFO_FILE
                if ((self.ST_ADD, path_trunk_info_file) not in changes and
                        (self.ST_UPDATE, path_trunk_info_file) not in changes):
                    bad_changes.append(
                        BadChange(status, path, BadChange.NO_INFO))
                continue

            # The rest are trunk changes in a suite
            path_head = "/".join(names[0:self.LEN_ID]) + "/"
            path_tail = path[len(path_head):]

            # For meta suite, make sure keys in keys file can be parsed
            if is_meta_suite and path_tail == self.TRUNK_KNOWN_KEYS_FILE:
                out = self._svnlook("cat", "-t", txn, repos, path)
                try:
                    shlex.split(out)
                except ValueError:
                    bad_changes.append(
                        BadChange(status, path, BadChange.VALUE))
                    continue

            # Suite trunk information file must have an owner
            # User IDs of owner and access list must be real
            if (status not in self.ST_DELETE and
                    path_tail == self.TRUNK_INFO_FILE):
                if path_head not in txn_info_map:
                    txn_info_map[path_head] = self._get_info(
                        repos, path_head, txn)
                txn_owner, txn_access_list = self._get_access_info(
                    txn_info_map[path_head])
                if not txn_owner:
                    bad_changes.append(
                        BadChange(status, path, BadChange.NO_OWNER))
                    continue
                if self._verify_users(
                        status, path, txn_owner, txn_access_list, bad_changes):
                    continue
                reports = DefaultValidators().validate(
                    txn_info_map[path_head],
                    load_meta_config(
                        txn_info_map[path_head],
                        config_type=rose.INFO_CONFIG_NAME))
                if reports:
                    reports_str = get_reports_as_text({None: reports}, path)
                    bad_changes.append(
                        BadChange(status, path, BadChange.VALUE, reports_str))
                    continue

            # Can only remove trunk information file with suite
            if status == self.ST_DELETE and path_tail == self.TRUNK_INFO_FILE:
                if (self.ST_DELETE, path_head) not in changes:
                    bad_changes.append(
                        BadChange(status, path, BadChange.NO_INFO))
                continue

            # Can only remove trunk with suite
            if status == self.ST_DELETE and path_tail == "trunk/":
                if (self.ST_DELETE, path_head) not in changes:
                    bad_changes.append(
                        BadChange(status, path, BadChange.NO_TRUNK))
                continue

            # New suite trunk: ignore the rest
            if (self.ST_ADD, path_head + "trunk/") in changes:
                continue

            # See whether author has permission to make changes
            if author is None:
                author = self._svnlook("author", "-t", txn, repos).strip()
            if super_users is None:
                super_users = []
                for s_key in ["rosa-svn", "rosa-svn-pre-commit"]:
                    value = conf.get_value([s_key, "super-users"])
                    if value is not None:
                        super_users = shlex.split(value)
                        break
            if path_head not in rev_info_map:
                rev_info_map[path_head] = self._get_info(
                    repos, path_head)
            owner, access_list = self._get_access_info(rev_info_map[path_head])
            admin_users = super_users + [owner]

            # Only admin users can remove the suite
            if author not in admin_users and not path_tail:
                bad_changes.append(BadChange(status, path))
                continue

            # Admin users and those in access list can modify everything in
            # trunk apart from specific entries in the trunk info file
            if "*" in access_list or author in admin_users + access_list:
                if path_tail != self.TRUNK_INFO_FILE:
                    continue
            else:
                bad_changes.append(BadChange(status, path))
                continue

            # The owner must not be deleted
            if path_head not in txn_info_map:
                txn_info_map[path_head] = self._get_info(
                    repos, path_head, txn)
            txn_owner, txn_access_list = self._get_access_info(
                txn_info_map[path_head])
            if not txn_owner:
                bad_changes.append(BadChange(status, path, BadChange.NO_OWNER))
                continue

            # Only the admin users can change owner and access list
            if owner == txn_owner and access_list == txn_access_list:
                continue
            if author not in admin_users:
                if owner != txn_owner:
                    bad_changes.append(BadChange(status, path, BadChange.PERM,
                                                 "owner=" + txn_owner))
                else:  # access list
                    bad_change = BadChange(
                        status, path, BadChange.PERM,
                        "access-list=" + " ".join(txn_access_list))
                    bad_changes.append(bad_change)
                continue

        if bad_changes:
            raise BadChanges(bad_changes)
Beispiel #7
0
Datei: vc.py Projekt: kaday/rose
def create(argv):
    """CLI function: create and copy."""
    opt_parser = RoseOptionParser()
    opt_parser.add_my_options("checkout_mode", "info_file", "meta_suite_mode",
                              "non_interactive", "prefix", "project")
    opts, args = opt_parser.parse_args(argv)
    verbosity = opts.verbosity - opts.quietness
    client = RosieVCClient(event_handler=Reporter(verbosity))
    SuiteId.svn.event_handler = client.event_handler
    from_id = None
    if args:
        from_id = SuiteId(id_text=args[0])
        if from_id.branch is None:
            from_id.branch = from_id.BRANCH_TRUNK
        if from_id.revision is None:
            from_id.revision = from_id.REV_HEAD
            from_id = SuiteId(id_text=from_id.to_string_with_version())
    interactive_mode = not opts.non_interactive
    if opts.info_file is None:
        info_config = client.generate_info_config(from_id, opts.prefix,
                                                  opts.project)
        if from_id is not None:
            meta_config = load_meta_config(info_config,
                                           directory=None,
                                           config_type=rose.INFO_CONFIG_NAME,
                                           error_handler=None,
                                           ignore_meta_error=False)
            for node_keys, node in meta_config.walk(no_ignore=True):
                if isinstance(node.value, dict):
                    continue
                sect, key = node_keys
                value = node.value
                sect = sect.translate(None, "=")
                if key == "copy-mode" and value == "clear":
                    info_config.set([sect], "")
                if key == "copy-mode" and value == "never":
                    info_config.unset([sect])
        info_config = _edit_info_config(opts, client, info_config)
    else:
        file_ = opts.info_file
        if opts.info_file == "-":
            file_ = sys.stdin
        info_config = rose.config.load(file_)
    info_config = _validate_info_config(opts, client, info_config)
    if interactive_mode:
        prefix = opts.prefix
        if from_id:
            if not prefix:
                prefix = from_id.prefix
            question = PROMPT_COPY % (from_id.to_string_with_version(), prefix)
        else:
            if not prefix:
                prefix = SuiteId.get_prefix_default()
            question = PROMPT_CREATE % prefix
        try:
            response = raw_input(question)
        except EOFError:
            sys.exit(1)
        if response != YES:
            sys.exit(1)
    try:
        id_ = client.create(info_config, from_id, opts.prefix,
                            opts.meta_suite_mode)
    except (RosePopenError, SuiteIdOverflowError) as exc:
        client.event_handler(exc)
        sys.exit(1)
    if opts.checkout_mode:
        try:
            client.checkout(id_)
        except (FileExistError, RosePopenError) as exc:
            client.event_handler(exc)
            sys.exit(1)
Beispiel #8
0
Datei: vc.py Projekt: kaday/rose
    def generate_info_config(self, from_id=None, prefix=None, project=None):
        """Generate a rose.config.ConfigNode for a rose-suite.info.

        This is suitable for passing into the create method of this
        class.
        If from_id is defined, copy items from it.
        Return the rose.config.ConfigNode instance.

        """
        from_project = None
        from_title = None
        if from_id is not None:
            from_info_url = "%s/%s/rose-suite.info@%s" % (
                from_id.to_origin(), from_id.branch, from_id.revision)
            out_data = self.popen("svn", "cat", from_info_url)[0]
            from_config = rose.config.load(StringIO(out_data))

        res_loc = ResourceLocator.default()
        older_config = None
        info_config = rose.config.ConfigNode()

        # Determine project if given as a command-line option on create
        if from_id is None and project is not None:
            info_config.set(["project"], project)

        # Set the compulsory fields and use the project and metadata if
        #  available.
        meta_config = load_meta_config(info_config,
                                       config_type=rose.INFO_CONFIG_NAME)
        if from_id is None and project is not None:
            for node_keys, node in meta_config.walk(no_ignore=True):
                if isinstance(node.value, dict):
                    continue
                sect, key = node_keys
                value = node.value
                sect = sect.translate(None, "=")
                if key == "compulsory" and value == "true":
                    info_config.set([sect], "")
            info_config.set(["project"], project)
        else:
            if from_project is None:
                info_config.set(["project"], "")
            if from_title is None:
                info_config.set(["title"], "")

        # Determine prefix
        if prefix is None:
            if from_id is None:
                prefix = SuiteId.get_prefix_default()
            else:
                prefix = from_id.prefix

        # Determine owner:
        # 1. From user configuration [rosie-id]prefix-username
        # 2. From username of a matching group in [groups] in
        #    ~/.subversion/servers
        # 3. Current user ID
        owner = res_loc.get_conf().get_value(
            ["rosie-id", "prefix-username." + prefix])
        if not owner and self.subversion_servers_conf:
            servers_conf = rose.config.load(self.subversion_servers_conf)
            groups_node = servers_conf.get(["groups"])
            if groups_node is not None:
                prefix_loc = SuiteId.get_prefix_location(prefix)
                prefix_host = urlparse(prefix_loc).hostname
                for key, node in groups_node.value.items():
                    if fnmatch(prefix_host, node.value):
                        owner = servers_conf.get_value([key, "username"])
                        break
        if not owner:
            owner = pwd.getpwuid(os.getuid())[0]
        info_config.set(["owner"], owner)

        # Copy description
        try:
            from_id.to_string_with_version()
            info_config.set(["description"],
                            "Copy of %s" % (from_id.to_string_with_version()))
        except AttributeError:
            pass

        # Copy fields provided by the user
        try:
            from_config.walk(no_ignore=False)
            for node_keys, node in from_config.walk(no_ignore=False):
                if isinstance(node.value, dict):
                    continue
                sect, key = node_keys
                value = node.value
                if (key in ["description", "owner", "access-list"]
                        or (key == "project" and from_project is not None)):
                    pass
                else:
                    info_config.set([key], value)
        except UnboundLocalError:
            pass

        # Determine access list
        access_list_str = res_loc.get_conf().get_value(
            ["rosie-vc", "access-list-default"])
        if access_list_str:
            info_config.set(["access-list"], access_list_str)
        if from_id is None and project is not None:
            for node_keys, node in meta_config.walk(no_ignore=True):
                if isinstance(node.value, dict):
                    continue
                sect, key = node_keys
                value = node.value
                sect = sect.translate(None, "=")
                if key == "value-hints" or key == "values":
                    reminder = ("please remove all commented hints/lines " +
                                "in the main/top section before saving.")
                    info_config.set([sect],
                                    rose.variable.array_split(value)[0],
                                    comments=[value, reminder])
        if older_config is not None:
            for node_keys, node in older_config.walk(no_ignore=True):
                if isinstance(node.value, dict):
                    continue
                sect, key = node_keys
                value = node.value
                info_config.set([key], value)

        return info_config
Beispiel #9
0
    def run(self, repos, txn):
        """Apply the rule engine on transaction "txn" to repository "repos"."""

        changes = set()  # set([(status, path), ...])
        for line in self._svnlook("changed", "-t", txn, repos).splitlines():
            status, path = line.split(None, 1)
            changes.add((status, path))
        bad_changes = []
        author = None
        super_users = None
        rev_info_map = {}  # {path-id: (owner, access-list), ...}
        txn_info_map = {}

        conf = ResourceLocator.default().get_conf()
        ignores_str = conf.get_value(["rosa-svn", "ignores"], self.IGNORES)
        ignores = shlex.split(ignores_str)

        for status, path in sorted(changes):
            if any(fnmatch(path, ignore) for ignore in ignores):
                continue

            names = path.split("/", self.LEN_ID + 1)
            tail = None
            if not names[-1]:
                tail = names.pop()

            # Directories above the suites must match the ID patterns
            is_bad = False
            for name, pattern in zip(names, self.RE_ID_NAMES):
                if not re.compile(r"\A" + pattern + r"\Z").match(name):
                    is_bad = True
                    break
            if is_bad:
                bad_changes.append(BadChange(status, path))
                continue

            # Can only add directories at levels above the suites
            if len(names) < self.LEN_ID:
                if status[0] != self.ST_ADD:
                    bad_changes.append(BadChange(status, path))
                continue
            else:
                is_meta_suite = "".join(names[0:self.LEN_ID]) == "ROSIE"

            # A file at the branch level
            if len(names) == self.LEN_ID + 1 and tail is None:
                bad_changes.append(BadChange(status, path))
                continue

            # No need to check non-trunk changes
            if len(names) > self.LEN_ID and names[self.LEN_ID] != "trunk":
                continue

            # New suite should have an info file
            if status[0] == self.ST_ADD and len(names) == self.LEN_ID:
                if (self.ST_ADD, path + "trunk/") not in changes:
                    bad_changes.append(
                        BadChange(status, path, BadChange.NO_TRUNK))
                    continue
                path_trunk_info_file = path + self.TRUNK_INFO_FILE
                if ((self.ST_ADD, path_trunk_info_file) not in changes and
                    (self.ST_UPDATE, path_trunk_info_file) not in changes):
                    bad_changes.append(
                        BadChange(status, path, BadChange.NO_INFO))
                continue

            # The rest are trunk changes in a suite
            path_head = "/".join(names[0:self.LEN_ID]) + "/"
            path_tail = path[len(path_head):]

            # For meta suite, make sure keys in keys file can be parsed
            if is_meta_suite and path_tail == self.TRUNK_KNOWN_KEYS_FILE:
                out = self._svnlook("cat", "-t", txn, repos, path)
                try:
                    shlex.split(out)
                except ValueError:
                    bad_changes.append(BadChange(status, path,
                                                 BadChange.VALUE))
                    continue

            # Suite trunk information file must have an owner
            # User IDs of owner and access list must be real
            if (status not in self.ST_DELETE
                    and path_tail == self.TRUNK_INFO_FILE):
                if path_head not in txn_info_map:
                    txn_info_map[path_head] = self._get_info(
                        repos, path_head, txn)
                txn_owner, txn_access_list = self._get_access_info(
                    txn_info_map[path_head])
                if not txn_owner:
                    bad_changes.append(
                        BadChange(status, path, BadChange.NO_OWNER))
                    continue
                if self._verify_users(status, path, txn_owner, txn_access_list,
                                      bad_changes):
                    continue
                reports = DefaultValidators().validate(
                    txn_info_map[path_head],
                    load_meta_config(txn_info_map[path_head],
                                     config_type=rose.INFO_CONFIG_NAME))
                if reports:
                    reports_str = get_reports_as_text({None: reports}, path)
                    bad_changes.append(
                        BadChange(status, path, BadChange.VALUE, reports_str))
                    continue

            # Can only remove trunk information file with suite
            if status == self.ST_DELETE and path_tail == self.TRUNK_INFO_FILE:
                if (self.ST_DELETE, path_head) not in changes:
                    bad_changes.append(
                        BadChange(status, path, BadChange.NO_INFO))
                continue

            # Can only remove trunk with suite
            if status == self.ST_DELETE and path_tail == "trunk/":
                if (self.ST_DELETE, path_head) not in changes:
                    bad_changes.append(
                        BadChange(status, path, BadChange.NO_TRUNK))
                continue

            # New suite trunk: ignore the rest
            if (self.ST_ADD, path_head + "trunk/") in changes:
                continue

            # See whether author has permission to make changes
            if author is None:
                author = self._svnlook("author", "-t", txn, repos).strip()
            if super_users is None:
                super_users = []
                for s_key in ["rosa-svn", "rosa-svn-pre-commit"]:
                    value = conf.get_value([s_key, "super-users"])
                    if value is not None:
                        super_users = shlex.split(value)
                        break
            if path_head not in rev_info_map:
                rev_info_map[path_head] = self._get_info(repos, path_head)
            owner, access_list = self._get_access_info(rev_info_map[path_head])
            admin_users = super_users + [owner]

            # Only admin users can remove the suite
            if author not in admin_users and not path_tail:
                bad_changes.append(BadChange(status, path))
                continue

            # Admin users and those in access list can modify everything in
            # trunk apart from specific entries in the trunk info file
            if "*" in access_list or author in admin_users + access_list:
                if path_tail != self.TRUNK_INFO_FILE:
                    continue
            else:
                bad_changes.append(BadChange(status, path))
                continue

            # The owner must not be deleted
            if path_head not in txn_info_map:
                txn_info_map[path_head] = self._get_info(repos, path_head, txn)
            txn_owner, txn_access_list = self._get_access_info(
                txn_info_map[path_head])
            if not txn_owner:
                bad_changes.append(BadChange(status, path, BadChange.NO_OWNER))
                continue

            # Only the admin users can change owner and access list
            if owner == txn_owner and access_list == txn_access_list:
                continue
            if author not in admin_users:
                if owner != txn_owner:
                    bad_changes.append(
                        BadChange(status, path, BadChange.PERM,
                                  "owner=" + txn_owner))
                else:  # access list
                    bad_change = BadChange(
                        status, path, BadChange.PERM,
                        "access-list=" + " ".join(txn_access_list))
                    bad_changes.append(bad_change)
                continue

        if bad_changes:
            raise BadChanges(bad_changes)
Beispiel #10
0
def create(argv):
    """CLI function: create and copy."""
    opt_parser = RoseOptionParser()
    opt_parser.add_my_options("checkout_mode", "info_file",
                              "meta_suite_mode", "non_interactive", "prefix",
                              "project")
    opts, args = opt_parser.parse_args(argv)
    verbosity = opts.verbosity - opts.quietness
    report = Reporter(verbosity)
    client = RosieVCClient(event_handler=report)
    SuiteId.svn.event_handler = client.event_handler # FIXME: ugly?
    from_id = None
    if args:
        from_id = SuiteId(id_text=args[0])
        if from_id.branch is None:
            from_id.branch = from_id.BRANCH_TRUNK
        if from_id.revision is None:
            from_id.revision = from_id.REV_HEAD
            from_id = SuiteId(id_text=from_id.to_string_with_version())
    info_config_new = None
    interactive_mode = not opts.non_interactive
    if opts.info_file is None:
        try:
            info_config = client.generate_info_config(from_id, opts.prefix,
                                                      opts.project)
        except (RosePopenError) as e:
            report(e)
            sys.exit(1)
        info_file = tempfile.NamedTemporaryFile()
        if args:
            meta_config = load_meta_config(info_config, directory=None,
                                           config_type=rose.INFO_CONFIG_NAME,
                                           error_handler=None,
                                           ignore_meta_error=False)
            for node_keys, node in meta_config.walk(no_ignore=True):
                if isinstance(node.value, dict):
                    continue
                sect, key = node_keys
                value = node.value
                sect = sect.translate(None, "=")
                if key == "copy-mode" and value == "clear":
                    info_config.set([sect], "")
                if key == "copy-mode" and value == "never":
                    info_config.unset([sect])
        rose.config.dump(info_config, info_file)
        info_file.write(CREATE_INFO_CONFIG_COMMENT)
        info_file.seek(0)
        command_list = client.popen.get_cmd("editor", info_file.name)
        client.popen(*command_list, stdout=sys.stdout)
        info_config = rose.config.load(info_file)
        try:
            info_config_new, error_reported = client.check_fields(info_config,
                                                              interactive_mode,
                                                              from_id,
                                                              opts.prefix)
        except (RosePopenError, SuiteInfoFieldError,
                SuiteIdOverflowError) as e:
            report(e)
            sys.exit(1)
        while error_reported is True:
            info_file = tempfile.NamedTemporaryFile()
            info_config = info_config_new
            if (info_config.get(["project"]).value is not None and
               opts.project is None):
                project = info_config.get(["project"]).value
                info_config = client.generate_info_config(from_id, opts.prefix,
                                                          project,
                                                          info_config)
            rose.config.dump(info_config, info_file)
            info_file.write(CREATE_INFO_CONFIG_COMMENT)
            info_file.seek(0)
            command_list = client.popen.get_cmd("editor", info_file.name)
            client.popen(*command_list, stdout=sys.stdout)
            info_config = rose.config.load(info_file)
            try:
                info_config_new, error_reported = client.check_fields(
                                                              info_config,
                                                              interactive_mode,
                                                              from_id,
                                                              opts.prefix)
            except (RosePopenError, SuiteInfoFieldError,
                    SuiteIdOverflowError) as e:
                report(e)
                sys.exit(1)
    elif opts.info_file == "-":
        info_config = rose.config.load(sys.stdin)
        try:
            info_config_new, error_reported = client.check_fields(info_config,
                                                          interactive_mode,
                                                          from_id,
                                                          opts.info_file,
                                                          opts.prefix)
        except (RosePopenError, SuiteInfoFieldError,
                SuiteIdOverflowError) as e:
            report(e)
            sys.exit(1)
    else:
        info_config = rose.config.load(opts.info_file)
        try:
            info_config_new, error_reported = client.check_fields(info_config,
                                                          interactive_mode,
                                                          from_id,
                                                          opts.info_file,
                                                          opts.prefix)
        except (RosePopenError, SuiteInfoFieldError,
                SuiteIdOverflowError) as e:
            report(e)
            sys.exit(1)
    if interactive_mode:
        if from_id:
            question = "Copy \"%s\"?" % from_id.to_string_with_version()
        else:
            prefix = opts.prefix
            if not prefix:
                prefix = SuiteId.get_prefix_default()
            question = "Create suite at \"%s\"?" % prefix
        try:
            response = raw_input(question + " y/n (default n) ")
        except EOFError:
            sys.exit(1)
        if response != 'y':
            sys.exit(1)
    try:
        id = client.create(info_config, from_id, opts.prefix,
                           opts.meta_suite_mode)
    except (RosePopenError, SuiteInfoFieldError, SuiteIdOverflowError) as e:
        report(e)
        sys.exit(1)
    if opts.checkout_mode:
        try:
            client.checkout(id)
        except (FileExistError, RosePopenError) as e:
            report(e)
            sys.exit(1)