# ========================================================================================= # Copyright (C) 2021 Orange & contributors # # This program is free software; you can redistribute it and/or modify it under the terms # of the GNU Lesser General Public License as published by the Free Software Foundation; # either version 3 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License along with this # program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth # Floor, Boston, MA 02110-1301, USA. # ========================================================================================= # default workflow rules workflow: rules: # exclude merge requests - if: $CI_MERGE_REQUEST_ID when: never - when: always variables: # variabilized tracking image TBC_TRACKING_IMAGE: "$CI_REGISTRY/to-be-continuous/tools/tracking:master" # Default ng workspace NG_WORKSPACE_DIR: . # Default Docker image for ANGULAR CLI (can be overridden) NG_CLI_IMAGE: trion/ng-cli-karma:latest # JUnit test report NG_JUNIT_TEST_REPORT_PATH: "reports/junit_test_report.xml" NG_E2E_REPORT_PATH: "reports/e2e" # Angular lint NG_LINT_ARGS: "lint" # Angular test NG_TEST_ARGS: >- test --code-coverage --reporters progress,junit NG_E2E_ARGS: >- e2e # Angular Build NG_BUILD_ARGS: "build --prod" # default production ref name (pattern) PROD_REF: '/^(master|main)$/' # default integration ref name (pattern) INTEG_REF: '/^develop$/' # ================================================== # Variables for publication # ================================================== # NG_PUBLISH_ENABLED # List of projects to publish, use space (" ") for separation # ex: NG_PUBLISH_PROJECTS: "Project1 Project2 myLib" # By default, NG_PUBLISH_PROJECTS is the value of angular.json, "defaultProject" property # Set some args of `npm publish` command # ex: NG_PUBLISH_ARGS: "--dry-run" NG_PUBLISH_ARGS: '--verbose' # ================================================== # Stages definition # ================================================== stages: - build - test - publish ############################################################################################### # Script definition # ############################################################################################### .ng-cli-scripts: &ng-cli-scripts | # BEGSCRIPT set -e function log_debug() { if [[ -n "$TRACE" ]] then echo -e "[\\e[1;36mTRACE\\e[0m] $*" fi } function log_info() { echo -e "[\\e[1;94mINFO\\e[0m] $*" } function log_warn() { echo -e "[\\e[1;93mWARN\\e[0m] $*" } function log_error() { echo -e "[\\e[1;91mERROR\\e[0m] $*" } function assert_defined() { if [[ -z "$1" ]] then log_error "$2" exit 1 fi } function sonar_lint_report() { if [[ -n "$SONAR_URL" ]] then mkdir -p reports # generate ts lint report in json for SONARqube # shellcheck disable=SC2086 ng $NG_LINT_ARGS --format=json --force > reports/tslint-report.json fi } function install_ca_certs() { certs=$1 if [[ -z "$certs" ]] then return fi # import in system if echo "$certs" >> /etc/ssl/certs/ca-certificates.crt then log_info "CA certificates imported in \\e[33;1m/etc/ssl/certs/ca-certificates.crt\\e[0m" fi if echo "$certs" >> /etc/ssl/cert.pem then log_info "CA certificates imported in \\e[33;1m/etc/ssl/cert.pem\\e[0m" fi # import in Java keystore (if keytool command found) if command -v keytool > /dev/null then # shellcheck disable=SC2046 javahome=${JAVA_HOME:-$(dirname $(readlink -f $(command -v java)))/..} # shellcheck disable=SC2086 keystore=${JAVA_KEYSTORE_PATH:-$(ls -1 $javahome/jre/lib/security/cacerts 2>/dev/null || ls -1 $javahome/lib/security/cacerts 2>/dev/null || echo "")} if [[ -f "$keystore" ]] then storepass=${JAVA_KEYSTORE_PASSWORD:-changeit} nb_certs=$(echo "$certs" | grep -c 'END CERTIFICATE') log_info "importing $nb_certs certificates in Java keystore \\e[33;1m$keystore\\e[0m..." for idx in $(seq 0 $((nb_certs - 1))) do # TODO: use keytool option -trustcacerts ? if echo "$certs" | awk "n==$idx { print }; /END CERTIFICATE/ { n++ }" | keytool -noprompt -import -alias "imported CA Cert $idx" -keystore "$keystore" -storepass "$storepass" then log_info "... CA certificate [$idx] successfully imported" else log_warn "... Failed importing CA certificate [$idx]: abort" return fi done else log_warn "Java keystore \\e[33;1m$keystore\\e[0m not found: could not import CA certificates" fi fi } function unscope_variables() { _scoped_vars=$(env | awk -F '=' "/^scoped__[a-zA-Z0-9_]+=/ {print \$1}" | sort) if [[ -z "$_scoped_vars" ]]; then return; fi log_info "Processing scoped variables..." for _scoped_var in $_scoped_vars do _fields=${_scoped_var//__/:} _condition=$(echo "$_fields" | cut -d: -f3) case "$_condition" in if) _not="";; ifnot) _not=1;; *) log_warn "... unrecognized condition \\e[1;91m$_condition\\e[0m in \\e[33;1m${_scoped_var}\\e[0m" continue ;; esac _target_var=$(echo "$_fields" | cut -d: -f2) _cond_var=$(echo "$_fields" | cut -d: -f4) _cond_val=$(eval echo "\$${_cond_var}") _test_op=$(echo "$_fields" | cut -d: -f5) case "$_test_op" in defined) if [[ -z "$_not" ]] && [[ -z "$_cond_val" ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" ]]; then continue; fi ;; equals|startswith|endswith|contains|in|equals_ic|startswith_ic|endswith_ic|contains_ic|in_ic) # comparison operator # sluggify actual value _cond_val=$(echo "$_cond_val" | tr '[:punct:]' '_') # retrieve comparison value _cmp_val_prefix="scoped__${_target_var}__${_condition}__${_cond_var}__${_test_op}__" _cmp_val=${_scoped_var#"$_cmp_val_prefix"} # manage 'ignore case' if [[ "$_test_op" == *_ic ]] then # lowercase everything _cond_val=$(echo "$_cond_val" | tr '[:upper:]' '[:lower:]') _cmp_val=$(echo "$_cmp_val" | tr '[:upper:]' '[:lower:]') fi case "$_test_op" in equals*) if [[ -z "$_not" ]] && [[ "$_cond_val" != "$_cmp_val" ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" == "$_cmp_val" ]]; then continue; fi ;; startswith*) if [[ -z "$_not" ]] && [[ "$_cond_val" != "$_cmp_val"* ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" == "$_cmp_val"* ]]; then continue; fi ;; endswith*) if [[ -z "$_not" ]] && [[ "$_cond_val" != *"$_cmp_val" ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" == *"$_cmp_val" ]]; then continue; fi ;; contains*) if [[ -z "$_not" ]] && [[ "$_cond_val" != *"$_cmp_val"* ]]; then continue; elif [[ "$_not" ]] && [[ "$_cond_val" == *"$_cmp_val"* ]]; then continue; fi ;; in*) if [[ -z "$_not" ]] && [[ "__${_cmp_val}__" != *"__${_cond_val}__"* ]]; then continue; elif [[ "$_not" ]] && [[ "__${_cmp_val}__" == *"__${_cond_val}__"* ]]; then continue; fi ;; esac ;; *) log_warn "... unrecognized test operator \\e[1;91m${_test_op}\\e[0m in \\e[33;1m${_scoped_var}\\e[0m" continue ;; esac # matches _val=$(eval echo "\$${_target_var}") log_info "... apply \\e[32m${_target_var}\\e[0m from \\e[32m\$${_scoped_var}\\e[0m${_val:+ (\\e[33;1moverwrite\\e[0m)}" _val=$(eval echo "\$${_scoped_var}") export "${_target_var}"="${_val}" done log_info "... done" } function read_default_project() { # current directory should be ${NG_WORKSPACE_DIR} angular_json=./angular.json default_project=$(node -pe "require('${angular_json}').defaultProject") default_project_type=$(node -pe "require('${angular_json}').projects['${default_project}'].projectType") } function run_ng_build() { read_default_project case ${default_project_type} in library) # shellcheck disable=SC2086 ng $NG_BUILD_ARGS ;; *) # shellcheck disable=SC2086 ng $NG_BUILD_ARGS --no-progress ;; esac } function run_ng_build_libs() { # current directory should be ${NG_WORKSPACE_DIR} angular_json=./angular.json libs=$(node -pe "Object.entries(require('${angular_json}').projects).filter(entry => entry[1].projectType === 'library').map(entry => entry[0]).join(' ')") log_debug "parsed libs: ${libs}" if [ -n "${libs}" ]; then log_info "libs detected: ${libs}" for lib in ${libs}; do log_debug "building lib ${lib}" ng build "${lib}" done fi } function configure_gitlab_instance_level_npm_registry_auth() { npm config set "//${CI_SERVER_HOST}/api/v4/packages/npm/:_authToken" "${CI_JOB_TOKEN}" } function compute_gitlab_registry_url() { gitlab_registry_url="https://${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm" } function configure_npm_publish_registry_auth() { publish_registry=${NPM_PUBLISH_REGISTRY} if [[ "${publish_registry}" ]]; then log_info "configured publish registry: ${publish_registry}" if [[ "$NPM_PUBLISH_TOKEN" ]]; then shopt -s extglob npm_publish_registry_host_and_path=${publish_registry/#http?(s):/} shopt -u extglob npm config set "${npm_publish_registry_host_and_path}:_authToken" "${NPM_PUBLISH_TOKEN}" fi else compute_gitlab_registry_url log_info "No defined publication registry, falling back to GitLab packages ${gitlab_registry_url}" publish_registry=${gitlab_registry_url} log_info "be sure to have your projects name starting with @${CI_PROJECT_ROOT_NAMESPACE}/" npm config set "@${CI_PROJECT_ROOT_NAMESPACE}:registry" "https://${CI_SERVER_HOST}/api/v4/packages/npm/" npm config set "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken" "${CI_JOB_TOKEN}" fi } function npm_publish() { projects_to_publish=${NG_PUBLISH_PROJECTS} if [ -z "${projects_to_publish}" ]; then read_default_project log_info "No projects specified, falling back to default project: \\e[33;1m${default_project}\\e[0m." projects_to_publish=${default_project} fi log_info "Publishing the following projects: ${projects_to_publish}..." # current directory should be ${NG_WORKSPACE_DIR} angular_json=./angular.json package_json=./package.json # first we check all specified projects exist for project in ${projects_to_publish}; do if ! projectRoot=$(node -pe "require('${angular_json}').projects['${project}'].root"); then log_error "unknown project: ${project}"; exit 1; fi done for project in ${projects_to_publish}; do projectTypeSelector="projects['${project}'].projectType" projectType=$(node -pe "require('${angular_json}').${projectTypeSelector}") case ${projectType} in library) # output dir for library is defined in ng-package.json projectRoot=$(node -pe "require('${angular_json}').projects['${project}'].root") projectRelativeOutputDir=$(node -pe "require('./${projectRoot}/ng-package.json').dest") projectOutputDir=${projectRoot}/${projectRelativeOutputDir} ;; *) # other output dir is defined in angular.json projectOutputDir=$(node -pe "require('${angular_json}').projects['${project}'].architect.build.options.outputPath") log_warn "It is not recommended to publish a project with '${projectType}' type as npm package." log_warn "Please consider using another packaging solution as this support might be limited." # WORKAROUND: # as application type projects don't generate a package.json which is required for publishing # we generate one with the global package.json version if [ ! -f "${projectOutputDir}/package.json" ]; then global_version=$(node -pe "require('${package_json}').version") cat > "${projectOutputDir}"/package.json <<- EOF { "name": "${project}", "version": "${global_version}" } EOF fi ;; esac projectOutputDir=$(realpath "${projectOutputDir}") if [ ! -d "${projectOutputDir}" ]; then log_error "'${project}' project output folder '${projectOutputDir}' not found" log_error "please check that module '${project}' has been built" exit 1 fi log_info "Publishing project '${project}' from ${projectOutputDir}" npm publish "${projectOutputDir}" "${NG_PUBLISH_ARGS}" done } unscope_variables # ENDSCRIPT ############################################################################################### # Generic job definition # ############################################################################################### .ng-cli-base: image: $NG_CLI_IMAGE services: - name: "$TBC_TRACKING_IMAGE" command: ["--service", "angular", "2.1.0"] # cache configuration cache: key: "$CI_COMMIT_REF_SLUG-angular" paths: - ${NG_WORKSPACE_DIR}/.npm/ before_script: - *ng-cli-scripts - cd ${NG_WORKSPACE_DIR} - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}" # NPM_CONFIG_REGISTRY is not supported by old npm versions: force with cli - if [[ "$NPM_CONFIG_REGISTRY" ]]; then npm config set registry $NPM_CONFIG_REGISTRY; fi - | if [[ "$NPM_CONFIG_SCOPED_REGISTRIES" ]]; then for scoped_registry in $NPM_CONFIG_SCOPED_REGISTRIES do npm config set "${scoped_registry%%:*}":registry "${scoped_registry#*:}"; done fi - configure_gitlab_instance_level_npm_registry_auth - npm ci --cache .npm --prefer-offline ############################################################################################### # check stage: # # - ng-lint # ############################################################################################### ng-lint: extends: .ng-cli-base stage: build script: # generate lint report for sonar - sonar_lint_report # display ng lint result for job console - ng $NG_LINT_ARGS artifacts: when: always # save artifact even if test failed name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG" paths: - $NG_WORKSPACE_DIR/reports expire_in: 1 day rules: # on production or integration branches: auto - if: '$CI_COMMIT_REF_NAME =~ $PROD_REF || $CI_COMMIT_REF_NAME =~ $INTEG_REF' # else (development branches): auto & non-blocking - allow_failure: true ############################################################################################### # build stage: # # - ng-build # ############################################################################################### ng-build: extends: .ng-cli-base stage: build script: # build libs so they are available for other projects tests - run_ng_build_libs # launch unit test and code coverage - ng $NG_TEST_ARGS --watch=false --no-progress # build production artifact - run_ng_build coverage: '/^Statements\s*:\s*([^%]+)/' artifacts: reports: junit: $NG_WORKSPACE_DIR/$NG_JUNIT_TEST_REPORT_PATH when: always # save artifact even if test failed name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG" paths: - $NG_WORKSPACE_DIR/coverage - $NG_WORKSPACE_DIR/dist - $NG_WORKSPACE_DIR/reports expire_in: 1 day ############################################################################################### # test stage: # # - ng-e2e # ############################################################################################### ng-e2e: extends: .ng-cli-base stage: test script: - ng $NG_E2E_ARGS artifacts: reports: junit: $NG_WORKSPACE_DIR/$NG_E2E_REPORT_PATH/junit*.xml when: always # save artifact even if test failed name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG" paths: - $NG_WORKSPACE_DIR/$NG_E2E_REPORT_PATH expire_in: 1 day rules: # only run if feature is enabled - if: '$NG_E2E_ENABLED == "true"' ############################################################################################### # publish stage: # # - npm-publish # ############################################################################################### npm-publish: extends: .ng-cli-base stage: publish before_script: - *ng-cli-scripts - cd ${NG_WORKSPACE_DIR} - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}" - configure_gitlab_instance_level_npm_registry_auth - configure_npm_publish_registry_auth - npm ci --cache .npm --prefer-offline script: - npm_publish rules: # on production branche: manual - if: '$NG_PUBLISH_ENABLED == "true" && $CI_COMMIT_REF_NAME =~ $PROD_REF' when: manual