Exemple #1
0
    async def build_image(app_name, version):
        tag_name = f"{static.APP_PREFIX}_{app_name}"
        repo = f"{config.DOCKER_REGISTRY}/{tag_name}:{version}"
        try:
            pathlib.Path(f"./temp_apps/{app_name}/{version}/src").mkdir(parents=True, exist_ok=True)
        except Exception as e:
            print(e)

        minio_client = Minio(config.MINIO, access_key=config.get_from_file(config.MINIO_ACCESS_KEY_PATH),
                             secret_key=config.get_from_file(config.MINIO_SECRET_KEY_PATH), secure=False)
        objects = minio_client.list_objects("apps-bucket", recursive=True)
        for obj in objects:
            size = obj.size
            p_src = Path(obj.object_name)
            if p_src.parts[1] == app_name:
                hold = str(p_src)
                p_dst = hold[hold.find(app_name):]
                p_dst = Path("temp_apps") / p_dst
                os.makedirs(p_dst.parent, exist_ok=True)

                data = minio_client.get_object('apps-bucket', hold)
                with open(p_dst, 'wb+') as file_data:
                    for d in data.stream(size):
                        file_data.write(d)

        logger.setLevel("DEBUG")
        docker_logger.setLevel("DEBUG")
        async with connect_to_aiodocker() as docker_client:
            context_dir = f"./temp_apps/{app_name}/{version}/"
            with docker_context(Path(context_dir)) as context:
                logger.info("Sending image to be built")
                dockerfile = "./Dockerfile"
                try:
                    log_stream = await docker_client.images.build(fileobj=context, tag=repo, rm=True,
                                                                  forcerm=True, pull=True, stream=True,
                                                                  path_dockerfile=dockerfile,
                                                                  encoding="application/x-tar")
                    logger.info("Docker image building")
                    await stream_docker_log(log_stream)
                    logger.info("Docker image Built")
                    # if await push_image(docker_client, repo):
                    #     return "Docker image built and pushed successfully."
                    success = await push_image(docker_client, repo)
                    if success:
                        return True, "Successfully built and pushed image"
                    else:
                        return False, "Failed to push image"
                except Exception as e:
                    return False, str(e)
Exemple #2
0
    async def down(self):

        # Set up a subcommand parser
        parser = argparse.ArgumentParser(
            description=
            "Remove the WALKOFF stack and optionally related artifacts.")
        parser.add_argument("-c",
                            "--clean",
                            action="store_true",
                            help="Removes all encryption keys, volumes, data.")
        parser.add_argument("-d",
                            "--debug",
                            action="store_true",
                            help="Set log level to debug.")
        parser.add_argument("-y",
                            "--yes",
                            action="store_true",
                            help="Skips all verification questions.")

        # Parse out the command
        args = parser.parse_args(sys.argv[2:])

        if args.debug:
            logger.setLevel("DEBUG")
            docker_logger.setLevel("DEBUG")

        proc = await rm_stack("walkoff")

        # if not args.skipnetwork:
        #     logger.info("Waiting for containers to exit and network to be removed...")
        #     await exponential_wait(check_for_network, [self.docker_client], "Network walkoff_default still exists")

        if args.clean:
            if args.yes or await are_you_sure(
                    "Are you sure you want to remove all WALKOFF data? This will remove "
                    "all of WALKOFF's docker secrets, docker volumes, and data for "
                    "walkoff_resource services,"):

                for key in key_names:
                    await delete_encryption_key(self.docker_client, key)

                for volume in volume_names:
                    await delete_volume(self.docker_client, volume)

        logger.info(
            "Walkoff stack removed, it may take a few seconds to stop all containers."
        )

        return proc.returncode
Exemple #3
0
    async def down(self):

        # Set up a subcommand parser
        parser = argparse.ArgumentParser(description="Remove the WALKOFF stack and optionally related artifacts.")
        parser.add_argument("-k", "--key", action="store_true",
                            help="Removes the walkoff_encryption_key secret.")
        parser.add_argument("-r", "--registry", action="store_true",
                            help="Clears the registry bind mount directory.")
        parser.add_argument("-v", "--volume", action="store_true", help="Clears the postgresql volume")
        parser.add_argument("-d", "--debug", action="store_true",
                            help="Set log level to debug.")

        # Parse out the command
        args = parser.parse_args(sys.argv[2:])

        if args.debug:
            logger.setLevel("DEBUG")
            docker_logger.setLevel("DEBUG")

        proc = await rm_stack("walkoff")

        # if not args.skipnetwork:
        #     logger.info("Waiting for containers to exit and network to be removed...")
        #     await exponential_wait(check_for_network, [self.docker_client], "Network walkoff_default still exists")

        if args.key:
            if await are_you_sure("Deleting encryption key will render database unreadable, so it will be cleared. "
                                  "This will delete all workflows, execution results, globals, users, roles, etc. "):
                await delete_encryption_key(self.docker_client, "walkoff_encryption_key")
                await delete_encryption_key(self.docker_client, "walkoff_internal_key")
                await delete_encryption_key(self.docker_client, "walkoff_postgres_key")
                await delete_dir_contents(static.POSTGRES_DATA_PATH)

        if args.registry:
            await delete_dir_contents(static.REGISTRY_DATA_PATH)
            await delete_dir_contents(static.MINIO_DATA_PATH)
            await delete_encryption_key(self.docker_client, "walkoff_minio_access_key")
            await delete_encryption_key(self.docker_client, "walkoff_minio_secret_key")

        if args.volume:
            await remove_volume("walkoff_postgres-data", wait=True)


        logger.info("Walkoff stack removed, it may take a little time to stop all services. "
                    "It is OK if the walkoff_default network is not fully removed.")

        return proc.returncode
Exemple #4
0
    async def build_image(app_name, version):
        tag_name = f"{static.APP_PREFIX}_{app_name}"
        repo = f"{config.DOCKER_REGISTRY}/{tag_name}:{version}"
        try:
            pathlib.Path(f"./temp_apps/{app_name}/{version}/src").mkdir(
                parents=True, exist_ok=True)
        except Exception as e:
            print(e)

        minio_client = Minio(config.MINIO,
                             access_key='walkoff',
                             secret_key='walkoff123',
                             secure=False)
        objects = minio_client.list_objects("apps-bucket", recursive=True)
        for obj in objects:
            size = obj.size
            p_src = Path(obj.object_name)
            if p_src.parts[1] == app_name:
                hold = str(p_src)
                p_dst = hold[hold.find(app_name):]
                p_dst = f"./temp_apps/{p_dst}"

                data = minio_client.get_object('apps-bucket', hold)
                with open(str(p_dst), 'wb') as file_data:
                    for d in data.stream(size):
                        file_data.write(d)

        logger.setLevel("DEBUG")
        docker_logger.setLevel("DEBUG")
        async with connect_to_aiodocker() as docker_client:
            context_dir = f"./temp_apps/{app_name}/{version}/"
            with docker_context(Path(context_dir)) as context:
                logger.info("Sending image to be built")
                dockerfile = "./Dockerfile"
                log_stream = await docker_client.images.build(
                    fileobj=context,
                    tag=repo,
                    rm=True,
                    forcerm=True,
                    pull=True,
                    stream=True,
                    path_dockerfile=dockerfile,
                    encoding="application/x-tar")
                logger.info("Docker image building")
                await stream_docker_log(log_stream)
                logger.info("Docker image Built")
                await push_image(docker_client, repo)
Exemple #5
0
    async def run():
        """ Landing pad to launch primary command and do whatever async init the bootloader needs. """
        # TODO: fill in the helps, and further develop cli with the end user in mind
        commands = {"up", "down", "refresh"}
        parser = argparse.ArgumentParser()
        parser.add_argument("command", choices=commands)
        parser.add_argument("args", nargs=argparse.REMAINDER)

        logger.setLevel("DEBUG")
        docker_logger.setLevel("DEBUG")

        # Parse out the command
        args = parser.parse_args(sys.argv[1:2])

        async with aiohttp.ClientSession() as session, connect_to_aiodocker() as docker_client:
            bootloader = Bootloader(session, docker_client)

            if hasattr(bootloader, args.command):
                await getattr(bootloader, args.command)()
            else:
                logger.error("Invalid command.")
                # TODO: Pipe this through the logger. print_help() accepts a file kwarg that we can use to do this
                parser.print_help()
Exemple #6
0
    async def up(self):
        data = {"name": "postgres-data"}
        # Create Postgres Volume
        await self.docker_client.volumes.create(data)

        # Create Walkoff encryption key
        wek = await create_encryption_key(self.docker_client, "walkoff_encryption_key")

        # Create internal user key
        wik = await create_encryption_key(self.docker_client, "walkoff_internal_key")

        # Create Postgres user password
        wpk = await create_encryption_key(self.docker_client, "walkoff_postgres_key")

        # Create Minio secret key
        wmak = await create_encryption_key(self.docker_client, "walkoff_minio_access_key", b"walkoff")
        wmsk = await create_encryption_key(self.docker_client, "walkoff_minio_secret_key")

        # Set up a subcommand parser
        parser = argparse.ArgumentParser(description="Bring the WALKOFF stack up and initialize it")
        parser.add_argument("-b", "--build", action="store_true",
                            help="Builds and pushes all WALKOFF components to local registry.")
        parser.add_argument("-d", "--debug", action="store_true",
                            help="Set log level to debug.")
        parser.add_argument("-k", "--keys", action="store_true",
                            help="Prints all keys to STDOUT (dangerous).")
        # Parse out the command
        args = parser.parse_args(sys.argv[2:])

        if args.debug:
            logger.setLevel("DEBUG")
            docker_logger.setLevel("DEBUG")

        logger.info("Creating persistent directories for registry, postgres, portainer...")
        os.makedirs(static.REGISTRY_DATA_PATH, exist_ok=True)
        os.makedirs(static.POSTGRES_DATA_PATH, exist_ok=True)
        os.makedirs(static.PORTAINER_DATA_PATH, exist_ok=True)
        os.makedirs(static.MINIO_DATA_PATH, exist_ok=True)

        # Bring up the base compose with the registry
        logger.info("Deploying base services (registry, postgres, portainer, redis)...")
        base_compose = parse_yaml(config.BASE_COMPOSE)

        await deploy_compose(base_compose)

        await self.wait_for_registry()

        # Merge the base, walkoff, and app composes
        app_composes = generate_app_composes()
        walkoff_compose = parse_yaml(config.WALKOFF_COMPOSE)
        merged_compose = merge_composes(walkoff_compose, app_composes)

        dump_yaml(config.TMP_COMPOSE, merged_compose)

        if args.build:
            walkoff_app_sdk = walkoff_compose["services"]["app_sdk"]
            await build_image(self.docker_client, walkoff_app_sdk["image"],
                              walkoff_app_sdk["build"]["dockerfile"],
                              walkoff_app_sdk["build"]["context"],
                              self.dockerignore)
            await push_image(self.docker_client, walkoff_app_sdk["image"])

            builders = []
            pushers = []

            for service_name, service in walkoff_compose["services"].items():
                if "build" in service:
                    build_func = build_image(self.docker_client, service["image"],
                                             service["build"]["dockerfile"],
                                             service["build"]["context"],
                                             self.dockerignore)
                    push_func = push_image(self.docker_client, service["image"])
                    if args.debug:
                        await build_func
                        await push_func
                    else:
                        builders.append(build_func)
                        pushers.append(push_func)

            if not args.debug:
                logger.info("Building Docker images asynchronously, this could take some time...")
                await asyncio.gather(*builders)
                logger.info("Build process complete.")
                logger.info("Pushing Docker images asynchronously, this could take some time...")
                await asyncio.gather(*pushers)
                logger.info("Push process complete.")

        await self.wait_for_minio()
        # await self.push_to_minio()

        logger.info("Deploying Walkoff stack...")

        return_code = await deploy_compose(merged_compose)

        if args.keys:
            if await are_you_sure("You specified -k/--keys, which will print all newly created keys to stdout."):
                print(f"walkoff_encryption_key:\t\t{wek.decode()}")
                print(f"walkoff_internal_key:\t\t{wik.decode()}")
                print(f"walkoff_postgres_key:\t\t{wpk.decode()}")
                print(f"walkoff_minio_access_key:\t{wmak.decode()}")
                print(f"walkoff_minio_secret_key:\t{wmsk.decode()}\n\n")

        logger.info("Walkoff stack deployed, it may take a little time to converge. \n"
                    "Use 'docker stack services walkoff' to check on Walkoff services. \n"
                    "Web interface should be available at 'https://127.0.0.1:8080' once walkoff_resource_nginx is up.")

        return return_code
Exemple #7
0
    async def up(self):

        # Set up a subcommand parser
        parser = argparse.ArgumentParser(
            description="Bring the WALKOFF stack up and initialize it")
        parser.add_argument(
            "-b",
            "--build",
            action="store_true",
            help="Builds and pushes all WALKOFF components to local registry.")
        parser.add_argument("-d",
                            "--debug",
                            action="store_true",
                            help="Set log level to debug.")
        parser.add_argument("-y",
                            "--yes",
                            action="store_true",
                            help="Skips all verification questions.")
        parser.add_argument("-r",
                            "--resources",
                            action="store_true",
                            help="Only runs the resource services.")
        parser.add_argument("-o",
                            "--offline",
                            action="store_true",
                            help="Runs WALKOFF offline")

        # Parse out the command
        args = parser.parse_args(sys.argv[2:])

        debug_pw = None
        debug_pw_encryption = None
        if args.debug:
            logger.setLevel("DEBUG")
            docker_logger.setLevel("DEBUG")

            if args.yes or await are_you_sure(
                    "You specified -d/--debug, which will use 'walkoff123456' as the password for all "
                    "resources, This should be used for debug only. "
                    "(Choose 'no' to use randomly generated passwords.)"):
                debug_pw = b"walkoff123456"
                debug_pw_encryption = b"walkoff123456789987654321walkoff"

        # Create Walkoff encryption key
        await create_encryption_key(self.docker_client, static.ENCRYPTION_KEY,
                                    debug_pw_encryption)

        # Create internal user key
        await create_encryption_key(self.docker_client, static.INTERNAL_KEY,
                                    debug_pw)

        # Create Minio secret key
        await create_encryption_key(self.docker_client,
                                    static.MINIO_ACCESS_KEY, b"walkoff123456")
        await create_encryption_key(self.docker_client,
                                    static.MINIO_SECRET_KEY, debug_pw)

        # Create Mongo user password
        await create_encryption_key(self.docker_client, static.MONGO_KEY,
                                    debug_pw)

        # Create Redis key
        await create_encryption_key(self.docker_client, static.REDIS_KEY,
                                    debug_pw)

        # Create volumes
        logger.info(
            "Creating volumes for persisting (registry, minio, mongo, portainer)..."
        )
        for volume in volume_names:
            await self.docker_client.volumes.create({"name": volume})

        # Bring up the base compose with the registry
        base_compose = parse_yaml(config.BASE_COMPOSE)

        for service_name, service in base_compose["services"].items():
            if args.offline:
                if not await image_exists(self.docker_client,
                                          service["image"]):
                    if not await pull_image(self.docker_client,
                                            service["image"]):
                        logger.info(
                            "Failed to pull requisite images, aborting deployment. "
                            "Please use the 'down' command to clean up. ")
                        return
            else:
                if not await pull_image(self.docker_client, service["image"]):
                    logger.info(
                        "Failed to pull requisite images, aborting deployment. "
                        "Please use the 'down' command to clean up. ")
                    return

        logger.info(
            "Deploying base services (registry, minio, mongo, portainer, redis)..."
        )
        await deploy_compose(base_compose)

        if args.resources:
            return

        await self.wait_for_registry()

        # Merge the base, walkoff, and app composes
        app_composes = generate_app_composes()
        walkoff_compose = parse_yaml(config.WALKOFF_COMPOSE)
        merged_compose = merge_composes(walkoff_compose, app_composes)

        dump_yaml(config.TMP_COMPOSE, merged_compose)

        if args.build:
            walkoff_app_sdk = walkoff_compose["services"]["app_sdk"]
            await build_image(self.docker_client, walkoff_app_sdk["image"],
                              walkoff_app_sdk["build"]["dockerfile"],
                              walkoff_app_sdk["build"]["context"],
                              self.dockerignore)
            await push_image(self.docker_client, walkoff_app_sdk["image"])

            builders = []
            pushers = []

            for service_name, service in walkoff_compose["services"].items():
                if "build" in service:
                    build_func = build_image(self.docker_client,
                                             service["image"],
                                             service["build"]["dockerfile"],
                                             service["build"]["context"],
                                             self.dockerignore)
                    push_func = push_image(self.docker_client,
                                           service["image"])
                    if args.debug:
                        await build_func
                        await push_func
                    else:
                        builders.append(build_func)
                        pushers.append(push_func)

            if not args.debug:
                logger.info(
                    "Building Docker images asynchronously, this could take some time..."
                )
                await asyncio.gather(*builders)
                logger.info("Build process complete.")
                logger.info(
                    "Pushing Docker images asynchronously, this could take some time..."
                )
                await asyncio.gather(*pushers)
                logger.info("Push process complete.")

        await self.wait_for_minio()
        # await self.push_to_minio()

        logger.info("Deploying Walkoff stack...")

        return_code = await deploy_compose(merged_compose)

        logger.info(
            "Walkoff stack deployed, it may take a little time to converge. \n"
            "Use 'docker stack services walkoff' to check on Walkoff services. \n"
            "Web interface should be available at 'https://127.0.0.1:8080' once walkoff_resource_nginx is up."
        )

        return return_code
Exemple #8
0
    async def down(self):

        # Set up a subcommand parser
        parser = argparse.ArgumentParser(
            description=
            "Remove the WALKOFF stack and optionally related artifacts.")
        parser.add_argument("-k",
                            "--key",
                            action="store_true",
                            help="Removes the walkoff_encryption_key secret.")
        parser.add_argument("-r",
                            "--registry",
                            action="store_true",
                            help="Clears the registry bind mount directory.")
        parser.add_argument("-d",
                            "--debug",
                            action="store_true",
                            help="Set log level to debug.")

        # Parse out the command
        args = parser.parse_args(sys.argv[2:])

        if args.debug:
            logger.setLevel("DEBUG")
            docker_logger.setLevel("DEBUG")

        logger.info("Removing Walkoff stack and related artifacts...")

        proc = await asyncio.create_subprocess_exec(
            "docker",
            "stack",
            "rm",
            "walkoff",
            stderr=asyncio.subprocess.PIPE,
            stdout=asyncio.subprocess.PIPE)

        await log_proc_output(proc)

        # if not args.skipnetwork:
        #     logger.info("Waiting for containers to exit and network to be removed...")
        #     await exponential_wait(check_for_network, [self.docker_client], "Network walkoff_default still exists")

        if args.key:
            resp = input(
                "Deleting encryption key will render database unreadable, and therefore it will be cleared. "
                "This will delete all workflows, execution results, globals, users, roles, etc. "
                "Are you sure? (yes/no): ")
            while resp.lower() not in ("yes", "no"):
                resp = input("Please answer 'yes' or 'no': ")

            if resp.lower() == "yes":
                await delete_encryption_key(self.docker_client,
                                            "walkoff_encryption_key")
                await delete_encryption_key(self.docker_client,
                                            "walkoff_internal_key")
                await delete_dir_contents("data/postgres")

        if args.registry:
            await delete_dir_contents("data/registry")
            await delete_dir_contents("data/minio/min_data")

        return proc.returncode
Exemple #9
0
    async def up(self):

        # Create Walkoff encryption key
        return_code = await create_encryption_key(self.docker_client,
                                                  "walkoff_encryption_key")
        if return_code:
            logger.exception(
                "Could not create secret walkoff_encryption_key. Exiting.")
            os._exit(return_code)

        # Create internal user key
        return_code2 = await create_encryption_key(self.docker_client,
                                                   "walkoff_internal_key")
        if return_code2:
            logger.exception(
                "Could not create secret walkoff_internal_key. Exiting.")
            os._exit(return_code2)

        # Set up a subcommand parser
        parser = argparse.ArgumentParser(
            description="Bring the WALKOFF stack up and initialize it")
        parser.add_argument(
            "-b",
            "--build",
            action="store_true",
            help="Builds and pushes all WALKOFF components to local registry.")
        parser.add_argument("-d",
                            "--debug",
                            action="store_true",
                            help="Set log level to debug.")

        # Parse out the command
        args = parser.parse_args(sys.argv[2:])

        if args.debug:
            logger.setLevel("DEBUG")
            docker_logger.setLevel("DEBUG")

        logger.info(
            "Creating persistent directories for registry, postgres, portainer..."
        )
        os.makedirs(Path("data") / "registry" / "reg_data", exist_ok=True)
        os.makedirs(Path("data") / "postgres" / "pg_data", exist_ok=True)
        os.makedirs(Path("data") / "portainer" / "prt_data", exist_ok=True)
        os.makedirs(Path("data") / "minio" / "min_data", exist_ok=True)

        # Bring up the base compose with the registry
        logger.info(
            "Deploying base services (registry, postgres, portainer, redis)..."
        )
        base_compose = parse_yaml(config.BASE_COMPOSE)

        await deploy_compose(base_compose)

        await self.wait_for_registry()

        # Merge the base, walkoff, and app composes
        app_composes = generate_app_composes()
        walkoff_compose = parse_yaml(config.WALKOFF_COMPOSE)
        merged_compose = merge_composes(walkoff_compose, app_composes)

        dump_yaml(config.TMP_COMPOSE, merged_compose)

        if args.build:
            walkoff_app_sdk = walkoff_compose["services"]["app_sdk"]
            await build_image(self.docker_client, walkoff_app_sdk["image"],
                              walkoff_app_sdk["build"]["dockerfile"],
                              walkoff_app_sdk["build"]["context"],
                              self.dockerignore)
            await push_image(self.docker_client, walkoff_app_sdk["image"])

            for service_name, service in walkoff_compose["services"].items():
                if "build" in service:
                    await build_image(self.docker_client, service["image"],
                                      service["build"]["dockerfile"],
                                      service["build"]["context"],
                                      self.dockerignore)
                    await push_image(self.docker_client, service["image"])

        await self.wait_for_minio()
        await self.push_to_minio()

        logger.info("Deploying Walkoff stack...")

        return_code = await deploy_compose(merged_compose)

        return return_code