Esempio n. 1
0
async def create_borrowed_items(asset_id: str, tree_id: str,
                                assets_gtw_client: AssetsGatewayClient,
                                context: Context):

    env = await context.get('env', YouwolEnvironment)
    async with context.start(action="create_borrowed_items",
                             with_attributes={
                                 'assetId': asset_id,
                                 'treeId': tree_id
                             }) as ctx:

        items_treedb = parse_json(env.pathsBook.local_treedb_docdb)
        tree_items = [
            item for item in items_treedb['documents']
            if item['related_id'] == asset_id
        ]
        borrowed_items = [
            item for item in tree_items
            if json.loads(item['metadata'])['borrowed']
        ]

        await asyncio.gather(*[
            create_borrowed_item(item=item,
                                 borrowed_tree_id=tree_id,
                                 assets_gtw_client=assets_gtw_client,
                                 context=ctx) for item in borrowed_items
        ])
Esempio n. 2
0
async def get_status(
        project: Project,
        flow_id: str,
        step: PipelineStep,
        context: Context
        ) -> PipelineStepStatusResponse:

    env = await context.get('env', YouwolEnvironment)
    paths: PathsBook = env.pathsBook
    async with context.start(
            action="get status",
            with_attributes={'projectId': project.id, 'flowId': flow_id, 'stepId': step.id}
            ) as _ctx:
        path = paths.artifacts_step(project_name=project.name, flow_id=flow_id, step_id=step.id)
        manifest = Manifest(**parse_json(path / 'manifest.json')) if (path / 'manifest.json').exists() else None

        status = await step.get_status(project=project, flow_id=flow_id, last_manifest=manifest, context=context)

        artifacts = [format_artifact_response(project=project, flow_id=flow_id, step=step, artifact=artifact,
                                              env=env)
                     for artifact in step.artifacts]

        return PipelineStepStatusResponse(
            projectId=project.id,
            flowId=flow_id,
            stepId=step.id,
            manifest=manifest,
            artifactFolder=path,
            artifacts=artifacts,
            status=status
            )
Esempio n. 3
0
async def create_borrowed_item(borrowed_tree_id: str, item: Mapping[str, any],
                               assets_gtw_client: AssetsGatewayClient,
                               context: Context):
    async with context.start(action="create_borrowed_items",
                             with_attributes={
                                 'borrowed_tree_id': borrowed_tree_id,
                                 'tree_id': item["item_id"]
                             }) as ctx:

        tree_id = item["item_id"]
        try:
            await assets_gtw_client.get_tree_item(item_id=tree_id)
            return
        except HTTPException as e:
            if e.status_code != 404:
                raise e

            path_item = await local_path({"treeId": tree_id}, context=ctx)
            await ctx.info(labels=[Label.RUNNING],
                           text="Borrowed tree item not found, start creation",
                           data={"treeItemPath": to_json(path_item)})
            await ensure_path(path_item, assets_gtw_client)
            parent_id = path_item.drive.driveId
            if len(path_item.folders) > 0:
                parent_id = path_item.folders[0].folderId

        await assets_gtw_client.borrow_tree_item(tree_id=borrowed_tree_id,
                                                 body={
                                                     "itemId":
                                                     tree_id,
                                                     "destinationFolderId":
                                                     parent_id
                                                 })
        await ctx.info(text="Borrowed item created")
Esempio n. 4
0
async def create_artifact(
        project: Project,
        flow_id: str,
        step: PipelineStep,
        artifact: Artifact,
        fingerprint: str,
        context: Context
        ):

    env = await context.get('env', YouwolEnvironment)
    async with context.start(
            action=f"create artifact '{artifact.id}'",
            with_attributes={'projectId': project.id, 'flowId': flow_id, 'stepId': step.id, 'artifactId': artifact.id,
                             'src fingerprint': fingerprint}
            ) as ctx:

        files = matching_files(
            folder=project.path,
            patterns=artifact.files
            )

        paths: PathsBook = env.pathsBook
        await ctx.info(text='got files listing', data=[str(f) for f in files])
        destination_folder = paths.artifact(project_name=project.name, flow_id=flow_id, step_id=step.id,
                                            artifact_id=artifact.id)

        for f in files:
            destination_path = destination_folder / f.relative_to(project.path)
            os.makedirs(os.path.dirname(destination_path), exist_ok=True)
            shutil.copy(src=f, dst=destination_path)

        await context.info(text="Zip file created", data={'path': str(destination_path)})
Esempio n. 5
0
async def download_package(package_name: str, version: str, context: Context):

    async with context.start(
            action=f"download package {package_name}#{version}",
            with_labels=[str(Label.PACKAGE_DOWNLOADING)],
            with_attributes={
                'packageName': package_name,
                'packageVersion': version,
            }) as ctx:
        env = await context.get('env', YouwolEnvironment)
        remote_gtw = await RemoteClients.get_assets_gateway_client(context=ctx)
        default_drive = await env.get_default_drive(context=ctx)
        asset_id = encode_id(encode_id(package_name))

        await ctx.info(text=f"asset_id: {asset_id} queued for download")

        await create_asset_local(
            asset_id=asset_id,
            kind='package',
            default_owning_folder_id=default_drive.systemPackagesFolderId,
            get_raw_data=lambda: remote_gtw.cdn_get_package(
                library_name=package_name, version=version),
            to_post_raw_data=lambda pack: {'file': pack},
            context=ctx)
        db = parse_json(env.pathsBook.local_cdn_docdb)
        record = [
            d for d in db['documents']
            if d['library_id'] == f"{package_name}#{version}"
        ]
        response = DownloadedPackageResponse(
            packageName=package_name,
            version=version,
            fingerprint=record[0]['fingerprint'])
        await ctx.send(response)
Esempio n. 6
0
async def synchronize_metadata(asset_id: str,
                               assets_gtw_client: AssetsGatewayClient,
                               context: Context):
    env = await context.get('env', YouwolEnvironment)
    async with context.start(action="synchronize_metadata",
                             with_attributes={'asset_id': asset_id}) as ctx:

        local_assets_gtw: AssetsGatewayClient = LocalClients.get_assets_gateway_client(
            env=env)

        local_metadata, remote_metadata = await asyncio.gather(
            local_assets_gtw.get_asset_metadata(asset_id=asset_id),
            assets_gtw_client.get_asset_metadata(asset_id=asset_id))
        missing_images_urls = [
            p for p in local_metadata['images']
            if p not in remote_metadata['images']
        ]
        full_urls = [
            f"http://localhost:{env.http_port}{url}"
            for url in missing_images_urls
        ]
        filenames = [url.split('/')[-1] for url in full_urls]

        await ctx.info(labels=[str(Label.RUNNING)],
                       text="Synchronise metadata",
                       data={
                           'local_metadata': local_metadata,
                           'remote_metadata': remote_metadata,
                           'missing images': full_urls
                       })

        async def download_img(session: ClientSession, url: str):
            async with await session.get(url=url) as resp:
                if resp.status == 200:
                    return await resp.read()

        async with ClientSession() as http_session:
            images_data = await asyncio.gather(
                *[download_img(http_session, url) for url in full_urls])

        forms = []
        for filename, value in zip(filenames, images_data):
            form_data = FormData()
            form_data.add_field(name='file', value=value, filename=filename)
            forms.append(form_data)

        await asyncio.gather(
            assets_gtw_client.update_asset(asset_id=asset_id,
                                           body=local_metadata),
            *[
                assets_gtw_client.post_asset_image(asset_id=asset_id,
                                                   filename=name,
                                                   data=form)
                for name, form in zip(filenames, forms)
            ])
    async def execute_run(self, project: Project, flow_id: FlowId, context: Context):

        outputs = []
        await context.info(text="")
        async with context.start(
                action="deploy helm chart",
                with_attributes={
                    "namespace": self.namespace
                }) as ctx:

            k8s_info = await get_cluster_info()
            if not k8s_info:
                outputs.append("Can not connect to k8s proxy")
                raise CommandException(command="deploy helm chart", outputs=outputs)

            with_values = self.overridingHelmValues(project, context)
            chart_path = project.path / "chart"
            helm_package = HelmPackage(
                name=project.name,
                namespace=self.namespace,
                chart_folder=chart_path,
                with_values=with_values,
                values_filename='values.yaml',
                secrets=self.secrets,
                chart_explorer=get_chart_explorer(chart_path)
            )

            await ctx.send(data=helm_package)
            installed = await helm_package.is_installed(context=ctx)

            if installed and '-next' in project.version:
                outputs.append("Mutable version used, proceed to chart uninstall")
                await helm_package.uninstall(context=ctx)
                installed = False

            if installed:
                outputs.append(f"Helm chart already installed, proceed to chart upgrade")
                await ctx.info(text=f"Start helm chart install")
                return_code, cmd, outputs_bash = await helm_package.upgrade(context=ctx)
                outputs = outputs + [cmd] + outputs_bash
                if return_code > 0:
                    raise CommandException(command="deploy helm chart", outputs=outputs)

            if not installed:
                outputs.append(f"Helm chart not already installed, start helm chart install")
                await ctx.info(text=f"Start helm chart install")
                return_code, cmd, outputs_bash = await helm_package.install(context=ctx)
                outputs = outputs + [cmd] + outputs_bash
                if return_code > 0:
                    raise CommandException(command="deploy helm chart", outputs=outputs)

        return outputs
Esempio n. 8
0
    async def install(self, context: Context):

        async with context.start(action='install helm package') as ctx:
            await self.before_cmd(context=ctx)
            keys = HelmPackage.flatten_schema_values(self.with_values)
            args = functools.reduce(lambda acc, e: acc + f"--set {e[1:]} ", keys, "")
            return await helm_install(
                release_name=self.name,
                namespace=self.namespace,
                values_file=self.chart_folder / self.values_filename,
                chart_folder=Path(self.chart_folder),
                timeout=240,
                args=args,
                context=ctx)
Esempio n. 9
0
async def check_update(local_package: TargetPackage, context: Context):

    remote_gtw_client = await RemoteClients.get_assets_gateway_client(
        context=context)
    headers = {"authorization": context.request.headers.get("authorization")}
    async with context.start(
            action=f"Check update for {local_package.library_name}",
            with_attributes={
                'event': 'check_update_pending',
                'packageName': local_package.library_name,
                'packageVersion': local_package.version,
            }) as ctx:
        package_id = encode_id(local_package.library_name)

        try:
            remote_package = await remote_gtw_client.cdn_get_versions(
                package_id=package_id, headers=headers)
        except HTTPException as e:
            if e.status_code == 404:
                await ctx.info(
                    text=f"{local_package.library_name} does not exist in remote"
                )
            raise e
        await ctx.info(text=f"Retrieved remote info",
                       data={'remote_package': remote_package})

        latest = remote_package['releases'][0]
        status = UpdateStatus.mismatch
        if latest['fingerprint'] == local_package.fingerprint:
            status = UpdateStatus.upToDate
        elif latest['version_number'] > local_package.version_number:
            status = UpdateStatus.remoteAhead
        elif latest['version_number'] < local_package.version_number:
            status = UpdateStatus.localAhead

        await ctx.info(text=f"Status: {str(status)}")
        response = CheckUpdateResponse(
            status=status,
            packageName=local_package.library_name,
            localVersionInfo=PackageVersionInfo(
                version=local_package.version,
                fingerprint=local_package.fingerprint),
            remoteVersionInfo=PackageVersionInfo(
                version=latest['version'], fingerprint=latest['fingerprint']))
        await ctx.send(response)
        return response
Esempio n. 10
0
async def execute_shell_cmd(cmd: str, context: Context, log_outputs=True):

    async with context.start(action="execute shell command",
                             with_labels=["BASH"]) as ctx:
        await ctx.info(text=cmd)
        p = await asyncio.create_subprocess_shell(
            cmd=cmd,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
            shell=True)
        outputs = []
        async with stream.merge(p.stdout,
                                p.stderr).stream() as messages_stream:
            async for message in messages_stream:
                outputs.append(message.decode('utf-8'))
                log_outputs and await context.info(text=outputs[-1])
        await p.communicate()
        return p.returncode, outputs
Esempio n. 11
0
async def create_artifacts(
        project: Project,
        flow_id: str,
        step: PipelineStep,
        fingerprint: str,
        context: Context
        ):

    async with context.start(
            action="create artifacts",
            with_attributes={'projectId': project.id, 'flowId': flow_id, 'stepId': step.id,
                             'src fingerprint': fingerprint}
            ) as ctx:

        for artifact in step.artifacts:
            await create_artifact(
                project=project,
                flow_id=flow_id,
                step=step,
                artifact=artifact,
                fingerprint=fingerprint,
                context=ctx)
    async def get_status(self, project: Project, flow_id: str,
                         last_manifest: Optional[Manifest],
                         context: Context) -> PipelineStepStatus:

        async with context.start(
                action="get status of project's dependencies") as ctx:
            if last_manifest is None:
                return PipelineStepStatus.none
            if not last_manifest.succeeded:
                return PipelineStepStatus.KO

            await ctx.info(text='previous manifest',
                           data=to_json(last_manifest))
            data = await SyncFromDownstreamStep.get_input_data(project=project,
                                                               flow_id=flow_id,
                                                               context=context)
            prev_checksums = last_manifest.cmdOutputs['checksums']
            ok = len(data.keys()) == len(prev_checksums.keys())\
                and all(k in prev_checksums and prev_checksums[k] == v.checksum for k, v in data.items())

            if not ok:
                return PipelineStepStatus.outdated

            # Any of the inner dependencies code in node_modules should be checked to make sure
            # no 'external' tool (e.g doing 'yarn') changed the node_module files
            prev_node_module_checksums = last_manifest.cmdOutputs.get(
                'nodeModuleChecksums', {})
            node_module_checksums: Mapping[str, str] = {
                name:
                SyncFromDownstreamStep.node_module_checksum(project=project,
                                                            name=name)
                for name in data.keys()
            }
            if not all(node_module_checksums[name] ==
                       prev_node_module_checksums.get(name, "")
                       for name in data.keys()):
                return PipelineStepStatus.outdated

            return PipelineStepStatus.OK
    async def execute_run(self, project: Project, flow_id: FlowId,
                          context: Context):

        async with context.start(
                action="run synchronization of workspace dependencies") as ctx:

            data = await SyncFromDownstreamStep.get_input_data(project=project,
                                                               flow_id=flow_id,
                                                               context=ctx)

            destination_folders: Mapping[str, Path] = {
                name: project.path / 'node_modules' / name
                for name in data.keys()
            }
            for name, p in data.items():
                if destination_folders[name].exists():
                    shutil.rmtree(destination_folders[name])
                copy_tree(p.dist_folder, destination_folders[name])
                for file in p.src_files:
                    destination = destination_folders[
                        name] / 'src' / file.relative_to(p.src_folder)
                    copy_file(source=file,
                              destination=destination,
                              create_folders=True)

            all_files = functools.reduce(
                lambda acc, e: acc + e.src_files + e.dist_files, data.values(),
                [])

            return {
                'fingerprint': files_check_sum(all_files),
                'checksums': {name: d.checksum
                              for name, d in data.items()},
                'nodeModuleChecksums': {
                    name: SyncFromDownstreamStep.node_module_checksum(
                        project=project, name=name)
                    for name in data.keys()
                }
            }
Esempio n. 14
0
async def synchronize_permissions(assets_gtw_client: AssetsGatewayClient,
                                  asset_id: str, context: Context):

    async with context.start(action="synchronize_permissions",
                             with_attributes={'assetId': asset_id}) as ctx:
        env = await context.get('env', YouwolEnvironment)
        local_assets_gtw = LocalClients.get_assets_gateway_client(env=env)
        access_info = await local_assets_gtw.get_asset_access(asset_id=asset_id
                                                              )
        await ctx.info(labels=[str(Label.RUNNING)],
                       text="Permissions retrieved",
                       data={"access_info": access_info})
        default_permission = access_info["ownerInfo"]["defaultAccess"]
        groups = access_info["ownerInfo"]["exposingGroups"]
        await asyncio.gather(
            assets_gtw_client.put_asset_access(asset_id=asset_id,
                                               group_id='*',
                                               body=default_permission),
            *[
                assets_gtw_client.put_asset_access(asset_id=asset_id,
                                                   group_id=g['groupId'],
                                                   body=g['access'])
                for g in groups
            ])
Esempio n. 15
0
async def create_asset_local(asset_id: str, kind: str,
                             default_owning_folder_id,
                             get_raw_data: Callable[[], Awaitable[T]],
                             to_post_raw_data: Callable[[T], any],
                             context: Context):
    env = await context.get("env", YouwolEnvironment)
    async with context.start(
            action=f"Fetch asset {asset_id} of kind {kind}", ) as ctx:
        local_treedb: TreeDbClient = LocalClients.get_treedb_client(env)
        local_gtw: AssetsGatewayClient = LocalClients.get_assets_gateway_client(
            env)
        remote_gtw = await RemoteClients.get_assets_gateway_client(context)
        remote_treedb = await RemoteClients.get_treedb_client(context)

        raw_data, metadata, tree_items = await asyncio.gather(
            get_raw_data(),
            remote_gtw.get_asset_metadata(asset_id=asset_id),
            remote_gtw.get_tree_items_by_related_id(related_id=asset_id),
            return_exceptions=True)

        if isinstance(raw_data, Exception):
            await ctx.error(f"Can not fetch raw part of the asset")
            raise raw_data

        if isinstance(metadata, Exception):
            await ctx.error(f"Can not fetch asset's metadata")
            raise metadata

        if isinstance(tree_items, Exception):
            await ctx.error(f"Can not fetch tree-db items")
            raise tree_items

        raw_data = cast(Dict, raw_data)
        metadata = cast(Dict, metadata)
        tree_items = cast(Dict, tree_items)

        await ctx.info(text="Raw & meta data retrieved",
                       data={
                           "metadata": metadata,
                           "tree_items": tree_items,
                       })
        owning_location, borrowed_locations = await get_remote_paths(
            remote_treedb=remote_treedb,
            tree_items=ItemsResponse(**tree_items))
        await ctx.info(text="Explorer paths retrieved",
                       data={
                           "owning_location":
                           owning_location.dict() if owning_location else
                           "No owning location in available groups",
                           "borrowed_locations":
                           [p.dict() for p in borrowed_locations]
                       })

        owning_folder_id = await get_local_owning_folder_id(
            owning_location=owning_location,
            local_treedb=local_treedb,
            default_folder_id=default_owning_folder_id)
        await ctx.info(text="Owning folder retrieved",
                       data={"owning_folder_id": owning_folder_id})

        await local_gtw.put_asset_with_raw(kind=kind,
                                           folder_id=owning_folder_id,
                                           data=to_post_raw_data(raw_data))
        await ctx.info(text="Asset raw's data downloaded successfully")

        await sync_borrowed_items(asset_id=asset_id,
                                  borrowed_locations=borrowed_locations,
                                  local_treedb=local_treedb,
                                  local_gtw=local_gtw)
        await ctx.info(text="Borrowed items created successfully")
        # the next line is not fetching images
        await local_gtw.update_asset(asset_id=asset_id, body=metadata)
        await ctx.info(text="Asset metadata uploaded successfully")
Esempio n. 16
0
async def upload_asset(body: JSON, context: Context):
    upload_factories: Dict[str, any] = {
        "data": UploadDataTask,
        "flux-project": UploadFluxProjectTask,
        "story": UploadStoryTask,
        "package": UploadPackageTask
    }

    asset_id = body['assetId']

    async with context.start(action="upload_asset",
                             with_attributes={'asset_id': asset_id}) as ctx:

        env = await context.get('env', YouwolEnvironment)
        local_treedb: TreeDbClient = LocalClients.get_treedb_client(env=env)
        local_assets: AssetsClient = LocalClients.get_assets_client(env=env)
        raw_id = decode_id(asset_id)
        asset, tree_item = await asyncio.gather(
            local_assets.get(asset_id=asset_id),
            local_treedb.get_item(item_id=asset_id),
            return_exceptions=True)
        if isinstance(asset, HTTPException) and asset.status_code == 404:
            await ctx.error(
                text="Can not find the asset in the local assets store")
            raise RuntimeError(
                "Can not find the asset in the local assets store")
        if isinstance(tree_item,
                      HTTPException) and tree_item.status_code == 404:
            await ctx.error(
                text="Can not find the tree item in the local treedb store")
            raise RuntimeError(
                "Can not find the tree item in the local treedb store")
        if isinstance(asset, Exception) or isinstance(tree_item, Exception):
            raise RuntimeError(
                "A problem occurred while fetching the local asset/tree items")
        asset = cast(Dict, asset)
        tree_item = cast(Dict, tree_item)

        factory: UploadTask = upload_factories[asset['kind']](
            raw_id=raw_id, asset_id=asset_id, context=ctx)

        local_data = await factory.get_raw()
        try:
            path_item = await local_treedb.get_path(item_id=tree_item['itemId']
                                                    )
        except HTTPException as e:
            if e.status_code == 404:
                await ctx.error(
                    text=
                    f"Can not get path of item with id '{tree_item['itemId']}'",
                    data={
                        "tree_item": tree_item,
                        "error_detail": e.detail
                    })
            raise e

        await ctx.info(text="Data retrieved",
                       data={
                           "path_item": path_item,
                           "raw data": local_data
                       })

        assets_gtw_client = await RemoteClients.get_assets_gateway_client(
            context=ctx)

        await ensure_path(path_item=PathResponse(**path_item),
                          assets_gateway_client=assets_gtw_client)
        try:
            await assets_gtw_client.get_asset_metadata(asset_id=asset_id)
            await ctx.info(text="Asset already found in deployed environment")
            await factory.update_raw(data=local_data,
                                     folder_id=tree_item['folderId'])
        except HTTPException as e:
            if e.status_code != 404:
                raise e
            await ctx.info(labels=[Label.RUNNING],
                           text="Project not already found => start creation")
            await factory.create_raw(data=local_data,
                                     folder_id=tree_item['folderId'])

        await synchronize_permissions_metadata_symlinks(
            asset_id=asset_id,
            tree_id=tree_item['itemId'],
            assets_gtw_client=assets_gtw_client,
            context=ctx)

    return {}
Esempio n. 17
0
    async def uninstall(self, context: Context):

        async with context.start(action='uninstall helm package') as ctx:
            await self.before_cmd(context=ctx)
            return await helm_uninstall(release_name=self.name, namespace=self.namespace, context=ctx)