def test_make_executable_read_bits(tmp_path): pth = tmp_path / "test" pth.touch(mode=0o640) # sanity check assert pth.stat().st_mode & 0o777 == 0o640 with pth.open() as fd: make_executable(fd) # only read bits got made executable assert pth.stat().st_mode & 0o777 == 0o750
def handle_dispatcher(self, linked_entrypoint): """Handle modern and classic dispatch mechanisms.""" # dispatch mechanism, create one if wasn't provided by the project dispatch_path = self.buildpath / DISPATCH_FILENAME if not dispatch_path.exists(): logger.debug("Creating the dispatch mechanism") dispatch_content = DISPATCH_CONTENT.format( entrypoint_relative_path=linked_entrypoint.relative_to( self.buildpath)) with dispatch_path.open("wt", encoding="utf8") as fh: fh.write(dispatch_content) make_executable(fh) # bunch of symlinks, to support old juju: verify that any of the already included hooks # in the directory is not linking directly to the entrypoint, and also check all the # mandatory ones are present dest_hookpath = self.buildpath / HOOKS_DIR if not dest_hookpath.exists(): dest_hookpath.mkdir() # get those built hooks that we need to replace because they are pointing to the # entrypoint directly and we need to fix the environment in the middle current_hooks_to_replace = [] for node in dest_hookpath.iterdir(): if node.resolve() == linked_entrypoint: current_hooks_to_replace.append(node) node.unlink() logger.debug( "Replacing existing hook %r as it's a symlink to the entrypoint", node.name, ) # include the mandatory ones and those we need to replace hooknames = MANDATORY_HOOK_NAMES | { x.name for x in current_hooks_to_replace } for hookname in hooknames: logger.debug("Creating the %r hook script pointing to dispatch", hookname) dest_hook = dest_hookpath / hookname if not dest_hook.exists(): relative_link = relativise(dest_hook, dispatch_path) dest_hook.symlink_to(relative_link)
def run(self, args): """Execute command's actual functionality.""" if any(self.config.project.dirpath.iterdir()) and not args.force: raise CommandError( "{} is not empty (consider using --force to work on nonempty directories)" .format(self.config.project.dirpath)) logger.debug("Using project directory '%s'", self.config.project.dirpath) if args.author is None: gecos = pwd.getpwuid(os.getuid()).pw_gecos.split(",", 1)[0] if not gecos: raise CommandError( "Author not given, and nothing in GECOS field") logger.debug("Setting author to %r from GECOS field", gecos) args.author = gecos if not args.name: args.name = self.config.project.dirpath.name logger.debug("Set project name to '%s'", args.name) if not re.match(r"[a-z][a-z0-9-]*[a-z0-9]$", args.name): raise CommandError("{} is not a valid charm name".format( args.name)) context = { "name": args.name, "author": args.author, "year": date.today().year, "class_name": "".join(re.split(r"\W+", args.name.title())) + "Charm", } env = get_templates_environment("init") _todo_rx = re.compile("TODO: (.*)") todos = [] executables = ["run_tests", "src/charm.py"] for template_name in env.list_templates(): if not template_name.endswith(".j2"): continue template = env.get_template(template_name) template_name = template_name[:-3] logger.debug("Rendering %s", template_name) path = self.config.project.dirpath / template_name if path.exists(): continue path.parent.mkdir(parents=True, exist_ok=True) with path.open("wt", encoding="utf8") as fh: out = template.render(context) fh.write(out) for todo in _todo_rx.findall(out): todos.append((template_name, todo)) if template_name in executables: make_executable(fh) logger.debug(" made executable") logger.info( "Charm operator package file and directory tree initialized.") if todos: logger.info("TODO:") logger.info("") w = max(len(i[0]) for i in todos) for fn, todo in todos: logger.info("%*s: %s", w + 2, fn, todo)
def run(self, args): """Execute command's actual functionality.""" init_dirpath = self.config.project.dirpath if not init_dirpath.exists(): init_dirpath.mkdir(parents=True) elif any(init_dirpath.iterdir()) and not args.force: tpl = "{!r} is not empty (consider using --force to work on nonempty directories)" raise CommandError(tpl.format(str(init_dirpath))) emit.trace(f"Using project directory {str(init_dirpath)!r}") if args.author is None and pwd is not None: args.author = _get_users_full_name_gecos() if not args.author: raise CommandError( "Unable to automatically determine author's name, specify it with --author" ) if not args.name: args.name = init_dirpath.name emit.trace(f"Set project name to '{args.name}'") if not re.match(r"[a-z][a-z0-9-]*[a-z0-9]$", args.name): raise CommandError("{} is not a valid charm name".format( args.name)) context = { "name": args.name, "author": args.author, "year": date.today().year, "class_name": "".join(re.split(r"\W+", args.name.title())) + "Charm", } env = get_templates_environment("init") _todo_rx = re.compile("TODO: (.*)") todos = [] executables = ["run_tests", "src/charm.py"] for template_name in env.list_templates(): if not template_name.endswith(".j2"): continue template = env.get_template(template_name) template_name = template_name[:-3] emit.trace(f"Rendering {template_name}") path = init_dirpath / template_name if path.exists(): continue path.parent.mkdir(parents=True, exist_ok=True) with path.open("wt", encoding="utf8") as fh: out = template.render(context) fh.write(out) for todo in _todo_rx.findall(out): todos.append((template_name, todo)) if template_name in executables and os.name == "posix": make_executable(fh) emit.trace(" made executable") emit.message( "Charm operator package file and directory tree initialized.") if todos: emit.message("TODO:") emit.message("") width = max(len(i[0]) for i in todos) + 2 for fn, todo in todos: emit.message(f"{fn:>{width}s}: {todo}")