diff --git a/build-pipeline.yml b/build-pipeline.yml index a3f62820792a5d3e97bde53a7fa6b7ae4049fcc4..38428aa07daf2fc892a18173ce8f53b413d6463a 100644 --- a/build-pipeline.yml +++ b/build-pipeline.yml @@ -19,9 +19,7 @@ workflow: .initDeployENV: &initDeployENV - | echo "Initiating deploy.env" - if [[ "$BITBAKE_TASK" == "populate_sdk" ]]; then echo "SDK_FILE_NAME=$sdk_filename" | tee -a deploy.env - else echo "DEPLOY_DATE=$DEPLOY_DATE" | tee -a deploy.env echo "BOARD=$BOARD" | tee -a deploy.env echo "IMAGE_NAME=$IMAGE_NAME" | tee -a deploy.env @@ -33,7 +31,6 @@ workflow: echo "BMAP_FILE_NAME=$bmap_name" | tee -a deploy.env echo "BUNDLE_FILE_NAME=$bundle_filename" | tee -a deploy.env echo "MD5_SUMS_FILE_NAME=$md5sums_filename" | tee -a deploy.env - fi # ----------------------------------- # Stage: build diff --git a/release-pipeline-clea.yml b/release-pipeline-clea.yml index 37d7120c7f806d6b79d168789a9f4d2c3e3a7176..34e5ab387d55b912aee9043678ee880bb63fd1d3 100644 --- a/release-pipeline-clea.yml +++ b/release-pipeline-clea.yml @@ -17,9 +17,13 @@ variables: RELEASE_TEMPLATES_DIR: release_templates RELEASE_GITLAB_TEMPLATE: release_clea.jinja2 RELEASE_PAGE_FILE_NAME: release-gitlab.md + RELEASE_CONFLUENCE_TECHSRC_TEMPLATE: confluence_secone_tech_page.jinja2 + RELEASE_CONFLUENCE_TECHSRC_FILE_NAME: release-confluence-secone-techresources.xml + RELEASE_CONFLUENCE_TECHSRC_LABELS: confluence_secone_tech_labels.jinja2 CHANGELOG_FILE: Changelog_${SECO_REMOTE}_${CI_COMMIT_TAG}.md DEPLOY_STAGE: deploy FILTER_LIST: "TOKEN KEY" + CONFLUENCE_URL: "https://secogroup.atlassian.net" stages: - collect @@ -104,7 +108,8 @@ changelog:deploy: timeout: 4h tags: - azure_deploy - needs: ["changelog:generate"] + needs: + - changelog:generate script: - *azure_storage_properties - echo "Deploying changelog.." @@ -120,12 +125,13 @@ generate:gitlab-release-page: extends: .infrastructure rules: - if: $CI_PIPELINE_SOURCE == "parent_pipeline" && $CI_COMMIT_TAG - needs: ["collect:release-information", "changelog:deploy"] + needs: + - collect:release-information + - changelog:deploy script: - *azure_storage_properties - export CHANGELOG_FILE_NAME="https://${AZURE_STORAGE_ACCOUNT}.blob.core.windows.net/${AZURE_CONTAINER_NAME}/${SECO_REMOTE}/reports/${CHANGELOG_FILE}" - set -o allexport && source ${RELEASE_VARIABLES_FILE_NAME} && set +o allexport - - *printenv - echo "Rendering release page.." - cd .gitlab-ci/${RELEASE_TEMPLATES_DIR} - ../scripts/render_jinja2_template.py @@ -135,10 +141,32 @@ generate:gitlab-release-page: paths: - ${RELEASE_PAGE_FILE_NAME} +generate:confluence-techresources-release-page: + stage: prepare + extends: .infrastructure + rules: + - if: $CI_PIPELINE_SOURCE == "parent_pipeline" && $CI_COMMIT_TAG + needs: + - collect:release-information + - generate:gitlab-release-page + script: + - export RELEASE_DATE=$(date -d "$CI_PIPELINE_CREATED_AT" +"%d %B %Y") + - export RELEASE_NAME=Release-${CI_COMMIT_TAG} + - export RELEASE_URL=${CI_PROJECT_PATH}/-/releases/${CI_COMMIT_TAG} + - echo "Rendering release page.." + - cd .gitlab-ci/${RELEASE_TEMPLATES_DIR} + - ../scripts/render_jinja2_template.py + --template ${RELEASE_CONFLUENCE_TECHSRC_TEMPLATE} + > ../../${RELEASE_CONFLUENCE_TECHSRC_FILE_NAME} + artifacts: + paths: + - ${RELEASE_CONFLUENCE_TECHSRC_FILE_NAME} + publish:gitlab-release-page: stage: publish extends: .infrastructure - needs: ["generate:gitlab-release-page"] + needs: + - generate:gitlab-release-page script: - .gitlab-ci/scripts/create_gitlab_release.py --gitlab-url ${CI_SERVER_URL} @@ -147,3 +175,36 @@ publish:gitlab-release-page: --name "Release-${CI_COMMIT_TAG}" --tag-name ${CI_COMMIT_TAG} --description-file=${RELEASE_PAGE_FILE_NAME} + +publish:confluence-techresources-release-page: + stage: publish + extends: .infrastructure + variables: + CONFLUENCE_SPACE: SECONorthTech + CONFLUENCE_PARENT_ID: 2351595548 + needs: + - collect:release-information + - generate:confluence-techresources-release-page + - publish:gitlab-release-page + script: + - eval "$(grep '^BOARD=' ${RELEASE_VARIABLES_FILE_NAME})" + - export BOARD + - cd .gitlab-ci/${RELEASE_TEMPLATES_DIR} + - ../scripts/render_jinja2_template.py + --template ${RELEASE_CONFLUENCE_TECHSRC_LABELS} + > ../../techsrc-confluence-labels.txt + - cd ../.. + - while IFS= read -r line; do + [ -n "$line" ] && LABEL_ARGS="${LABEL_ARGS} --label=$line"; + done < techsrc-confluence-labels.txt + - .gitlab-ci/scripts/confluence_create_or_update_page.py + --inherit-parent-restrictions + --confluence-url="${CONFLUENCE_URL}" + --username="${CONFLUENCE_USERNAME}" + --token="${CONFLUENCE_TOKEN}" + --space-id="${CONFLUENCE_TECHSRC_SPACE}" + --parent-id="${CONFLUENCE_TECHSRC_PARENT_ID}" + --page-name="Clea OS ${RELEASE_NAME}" + --content-file="${RELEASE_CONFLUENCE_TECHSRC_FILE_NAME}" + --label="os-release" + ${LABEL_ARGS} diff --git a/release_templates/confluence_secone_tech_labels.jinja2 b/release_templates/confluence_secone_tech_labels.jinja2 new file mode 100644 index 0000000000000000000000000000000000000000..31d7b454c865f6399b651e4917c0439725b150f5 --- /dev/null +++ b/release_templates/confluence_secone_tech_labels.jinja2 @@ -0,0 +1,21 @@ +{#- Board lables -#} +{%- if BOARD is defined -%} + +{%- set lbs = namespace(labels = []) -%} +{%- for board in BOARD.split(' ') | sort -%} + {%- set label = "" -%} + {%- if "e83-d18" in board -%} + {%- set label = "modular-vision" -%} + {%- elif "santino" in board -%} + {%- set label = "santino" -%} + {%- endif -%} + {%- if label != "" -%} + {%- set lbs.labels = lbs.labels + [label] -%} + {%- endif -%} +{%- endfor -%} + +{% for label in lbs.labels -%} +{{ label }} +{% endfor -%} + +{%- endif -%} diff --git a/release_templates/confluence_secone_tech_page.jinja2 b/release_templates/confluence_secone_tech_page.jinja2 new file mode 100644 index 0000000000000000000000000000000000000000..3e068636e7c823fae126ccb0016e98fb30056191 --- /dev/null +++ b/release_templates/confluence_secone_tech_page.jinja2 @@ -0,0 +1,47 @@ +{#- + See documentation for storage format: + https://confluence.atlassian.com/doc/confluence-storage-format-790796544.html +-#} +<ac:structured-macro ac:name="details" ac:schema-version="1" data-layout="default" ac:macro-id="4a0ac7ab071a2383a7d0b5134ec5b46b"> + <ac:rich-text-body> + <table data-layout="default"> + <tbody> + <tr> + {#- + Attention: no line breaks allowed within <th>..</th>, + otherwise the field is not correctly parsed by the Page + Properties Report macro. + #} + <th><b>Version</b></th> + <td>{{ RELEASE_NAME }}</td> + </tr> + <tr> + <th><b>Release Date</b></th> + <td><time datetime="{{ RELEASE_DATE }}"/></td> + </tr> + <tr> + <th><b>Architecture / Processor</b></th> + <td> + {%- if MACHINE is defined %} + {%- for machine in MACHINE.split(' ') | sort %} + <p>{{ machine }}</p> + {%- endfor %} + {%- endif %} + </td> + </tr> + {%- if FILES_documentation is defined %} + <tr> + <th><b>Documentation</b></th> + <td> + {%- for doc_file in FILES_documentation.split(' ') | sort %} + {%- if doc_file.endswith(".html") %} + <p><a href="{{ doc_file }}">{{ (doc_file | basename | splitext)[0] }}</a></p> + {%- endif %} + {%- endfor %} + </td> + </tr> + {%- endif %} + </tbody> + </table> + </ac:rich-text-body> +</ac:structured-macro> diff --git a/release_templates/release_clea.jinja2 b/release_templates/release_clea.jinja2 index 7e54c9775c26e63a46654384caa2c115c8d044c3..5b7b0ddeee258493399f1377cb8837bed6043fb9 100644 --- a/release_templates/release_clea.jinja2 +++ b/release_templates/release_clea.jinja2 @@ -43,12 +43,15 @@ {%- macro get_file_link(machine_distro, file_type) -%} {%- set return_link = namespace(rl = "") -%} + {%- set filename = "" -%} {%- set machine_underscore = machine_distro | replace("-", "_") -%} {#- see https://stackoverflow.com/questions/72654161/dynamically-referencing-a-variable-in-jinja2 -#} - {%- set filename = self._TemplateReference__context.resolve(filenames_mapping[file_type] ~ "_FILE_NAME_" ~ machine_underscore) -%} + {%- if self._TemplateReference__context.resolve(filenames_mapping[file_type] ~ "_FILE_NAME_" ~ machine_underscore) is defined -%} + {%- set filename = self._TemplateReference__context.resolve(filenames_mapping[file_type] ~ "_FILE_NAME_" ~ machine_underscore) -%} + {%- endif -%} {%- set link_var = self._TemplateReference__context.resolve("FILE_LINKS_" ~ machine_underscore) -%} {%- for full_link in self._TemplateReference__context.resolve("FILE_LINKS_" ~ machine_underscore).split(" ") -%} - {%- if filename in full_link -%} + {%- if filename != "" and filename in full_link -%} {%- set return_link.rl = full_link -%} {%- endif -%} {%- endfor -%} diff --git a/scripts/confluence_create_or_update_page.py b/scripts/confluence_create_or_update_page.py new file mode 100755 index 0000000000000000000000000000000000000000..abf73f3c28372cde720c9aa87596b6599a75dc30 --- /dev/null +++ b/scripts/confluence_create_or_update_page.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 + +import argparse +import json + +import requests +from atlassian import Confluence +from requests.auth import HTTPBasicAuth + + +def str_to_bool(argument): # Helper, converter for parsing boolean argument values + if isinstance(argument, bool): + return argument + if argument.lower() in {"true", "yes", "1"}: + return True + elif argument.lower() in {"false", "no", "0"}: + return False + else: + raise argparse.ArgumentTypeError("Boolean value expected.") + + +def main(): + # Create parser and add possible command line arguments + parser = argparse.ArgumentParser() + + parser.add_argument( + "--confluence-url", + help="""URL to the Confluence instance""", + dest="confluence_url", + ) + parser.add_argument( + "--username", + help="""Username for the Confluence API""", + dest="username", + required=True, + ) + + parser.add_argument( + "--token", + help="""Token for the Confluence API""", + dest="token", + required=True, + ) + + parser.add_argument( + "--space-id", + help="""Confluence space ID or name""", + dest="space_id", + required=True, + ) + + parser.add_argument( + "--parent-id", + help="""ID of the parent page""", + dest="parent_id", + required=True, + ) + + parser.add_argument( + "--page-name", + help="""Name for the new page""", + dest="page_name", + required=True, + ) + + parser.add_argument( + "--content-file", + help="""File to load the content for the new page from""", + dest="content_file", + required=True, + ) + + parser.add_argument( + "--label", + help="""Label to apply to the new page""", + dest="label", + action="append", + required=False, + ) + + parser.add_argument( + "--inherit-parent-restrictions", + help="Inherit restrictions from parent page (default: false)", + dest="inherit_parent_restrictions", + type=str_to_bool, + nargs="?", + const=True, + default=False, + ) + + # Parse command line arguments + args, _ = parser.parse_known_args() + + # Read content file + try: + with open(args.content_file, "r", encoding="utf8") as content_file: + content = content_file.read() + except FileNotFoundError: + exit(f"ERROR: '{args.content_file}' not found") + + # Connect to confluence + confluence = Confluence( + url=args.confluence_url, + username=args.username, + password=args.token, + ) + + try: + # Check if page already exists + page = confluence.get_page_by_title(args.space_id, args.page_name) + + if type(page) is dict: + # Update existing page + page = confluence.update_page( + page["id"], args.page_name, content, args.parent_id, full_width=False + ) + print("Updated existing Confluence page:") + + else: + # Create new page + page = confluence.create_page( + args.space_id, + args.page_name, + content, + args.parent_id, + representation="storage", + type="page", + editor="v2", + full_width=False, + ) + # Update it immediately with the same contents because sometimes the + # "full_width=False" argument does not work, see: + # https://community.developer.atlassian.com/t/how-to-set-fixed-width-when-creating-new-page-with-rest-call/53591 + page = confluence.update_page( + page["id"], + args.page_name, + content, + args.parent_id, + representation="storage", + type="page", + full_width=False, + minor_edit=True, + ) + print("Created new Confluence page:") + + if type(page) is dict: + # Print absolute page URL + print(page["_links"]["base"] + page["_links"]["webui"]) + + # Set labels for the page + for label in args.label: + confluence.set_page_label(page["id"], label) + + if args.inherit_parent_restrictions: + # Retrieve all restrictions applied to the parent page + parent_restrictions = confluence.get_all_restrictions_for_content( + args.parent_id + ) + # The retrieved data includes restrictions/permissions as well as references to the parent page. + # Before applying it to the new page, we need to remove these old references. + # For more details on the data structure, refer to: + # https://developer.atlassian.com/cloud/confluence/rest/v1/api-group-content-restrictions/#api-wiki-rest-api-content-id-restriction-get + + # Reset '_links' and '_expandable' fields from pointing to the parent page + restrictions_request = {"results": []} + + for key, value in parent_restrictions.items(): + if key == "_links" or key == "_expandable": + continue + if "_links" in value.keys(): + value["_links"] = {} + if "_expandable" in value.keys(): + value["_expandable"] = {} + + restrictions_request["results"].append(value) + + # Directly making this request, as the current Confluence module lacks methods for setting restrictions + + response = requests.request( + "PUT", + f'{args.confluence_url}/wiki/rest/api/content/{page["id"]}/restriction', + headers={ + "Accept": "application/json", + "Content-Type": "application/json", + }, + auth=HTTPBasicAuth(args.username, args.token), + data=json.dumps(restrictions_request), + ) + if response.ok: + print("Page restrictions have been updated.") + else: + print("Could not update page restrictions !") + exit(f"ERROR: {response.text}") + + except requests.exceptions.HTTPError as e: + exit(f"ERROR: {e}") + + +if __name__ == "__main__": + main()