Commit df301891 authored by Christian Ceelen's avatar Christian Ceelen Committed by Pierre Smeyers
Browse files

feat(release): add publish support

parent ad096f32
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
@@ -284,6 +284,41 @@ The following SBOM reports are generated:
| -------------- | ---------------------------------------------------------------------------- | ----------------- |
| `reports/dotnet-sbom-*.cyclonedx.json` | [CycloneDX Json](https://cyclonedx.org/docs/1.7/json/) test report(s) | [GitLab integration](https://docs.gitlab.com/ci/yaml/artifacts_reports/#artifactsreportscyclonedx) |

### `dotnet-publish` job (Upload Artifacts)

This job takes the artifacts build in the `dotnet-package` step and publishes them depending upon the output type configured in the project files:
- Application binaries are zipped and always pushed to the generic GitLab Package Registry. 
- Nuget packages get pushed to the configured NuGet target repo using [`dotnet nuget push`](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-nuget-push). Default repo is the Gitlab Nuget Repo of the project.

See also [.NET Build Configuration Best Practices](#net-build-configuration-best-practices) to control the per project behavior of the pipeline template.

**Note on Authentication:** The job is designed to work seamlessly with the integrated GitLab Package Registry. By default, `DOTNET_NUGET_API_KEY` uses the `CI_JOB_TOKEN`, so you do not need to configure any secret variables. You only need to create and set `DOTNET_NUGET_API_KEY` as a project CI/CD variable if you override `nuget-repo` to publish to an external repository (like nuget.org or another private registry) that requires a specific API key.

**Execution rules:**
- Runs automatically on **tagged release pipelines** with non-prerelease versions.
- Runs manually on any other pipeline instance.
- Automatically configured for GitLab Package Registry using `CI_JOB_TOKEN`
- Publishes both `.nupkg` and `.snupkg` (symbol) packages if symbol package publication is activated.
- Publishes executable binaries for configured _frameworks_ and the runner _runtime_.

**Important notes:**
- Automatically configures GitLab Package Registry as the NuGet target feed.
- Uses `CI_JOB_TOKEN` for authentication (no manual configuration needed to use with the GitLab package repository)
- See [Project / Solution Versioning](#solution--project-versioning) for the build version selection.

**Example artifacts published:**

- **Libraries**: `MyLibrary.1.0.0.nupkg`, `MyLibrary.1.0.0.snupkg` 
- **Applications**: `MyApp-1.0.0.zip` containing application binaries

| Input / Variable      | Description                              | Default value     |
| --------------------- | ---------------------------------------- | ----------------- |
| `publish-enabled` / `DOTNET_PUBLISH_ENABLED` | Set to true to enable publishing of artifact to a [NuGet feed](https://www.nuget.org/). | `false` |
| `nuget-repo` / `DOTNET_NUGET_REPO` | Target NuGet package repository url to publish the packages to. (when overriding this, please set `DOTNET_NUGET_API_KEY` at project CI variable to set the [nuget api-key used by dotnet nuget push](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-nuget-push)) _defaults to [GitLab project's packages repository](https://docs.gitlab.com/user/packages/nuget_repository/)_ | `${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/packages/nuget/index.json` |
| :lock: `DOTNET_NUGET_API_KEY` | NuGet Repo API token used for authentication. | ${CI_JOB_TOKEN} |
| `nuget-symbol-repo` / `DOTNET_NUGET_SYMBOL_REPO` | Target NuGet package symbol repository url to publish the symbol packages to. (when overriding this, please set `DOTNET_NUGET_SYMBOL_API_KEY` at project CI variable to set the [nuget symbol api-key used by dotnet nuget push](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-nuget-push)) _defaults to [GitLab project's packages repository](https://docs.gitlab.com/user/packages/nuget_repository/)_ | `${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/packages/nuget/index.json` |
| :lock: `DOTNET_NUGET_SYMBOL_API_KEY` | NuGet Symbol Repo API token used for authentication. | ${CI_JOB_TOKEN} |

### Secrets management

Here are some advices about your **secrets** (variables marked with a :lock:):
+41 −0
Original line number Diff line number Diff line
@@ -142,6 +142,47 @@
          "advanced": true
        }
      ]
    },
    {
      "id": "publish",
      "name": "dotnet publish",
      "description": "Publishes built packages (with [dotnet publish](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-publish)) to a [NuGet feed](https://www.nuget.org/).",
      "enable_with": "DOTNET_PUBLISH_ENABLED",
      "variables": [
        {
          "name": "DOTNET_NUGET_REPO",
          "description": "Target NuGet package repository url to publish the packages to. (when overriding this, please set `DOTNET_NUGET_API_KEY` at project CI variable to set the [nuget api-key used by dotnet nuget push](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-nuget-push)) _defaults to [GitLab project's packages repository](https://docs.gitlab.com/user/packages/nuget_repository/)_",
          "default": "${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/packages/nuget/index.json",
          "advanced": true
        },
        {
          "name": "DOTNET_NUGET_API_KEY",
          "description": "NuGet Repo API token used for authentication.",
          "secret": true
        },
        {
          "name": "DOTNET_PACKAGE_CONFIGURATION",
          "description": "The build configuration to use for packaging (Debug or Release) a [library](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-pack) or [executable](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-publish)",
          "default": "Release",
          "advanced": true
        },
        {
          "name": "DOTNET_PACKAGE_SYMBOLS_DISABLED",
          "description": "Disable creation of symbol packages (snupkg) for debugging",
          "default": "false",
          "advanced": true
        },
        {
          "name": "DOTNET_NUGET_SYMBOL_REPO",
          "description": "Target NuGet package symbol repository url to publish the symbol packages to. (when overriding this, please set `DOTNET_NUGET_SYMBOL_API_KEY` at project CI variable to set the [nuget symbol api-key used by dotnet nuget push](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-nuget-push)) _defaults to [GitLab project's packages repository](https://docs.gitlab.com/user/packages/nuget_repository/)_",
          "default": "${CI_SERVER_URL}/api/v4/projects/${CI_PROJECT_ID}/packages/nuget/index.json"
        },
        {
          "name": "DOTNET_NUGET_SYMBOL_API_KEY",
          "description": "NuGet Symbol Repo API token used for authentication.",
          "secret": true
        }
      ]
    }
  ]
}
 No newline at end of file
+104 −0
Original line number Diff line number Diff line
@@ -1466,6 +1466,89 @@ stages:
  }


  # Publish functions
  function _dotnet_publish_library_packages() {
    local artifact_dir
    artifact_dir="${DOTNET_PROJECT_DIR}/${DOTNET_ARTIFACT_DIR}/packages/${DOTNET_PROJECT_NAME}"
    local packages
    packages=$(find "${artifact_dir}" -name "*.nupkg" -exec echo \"\{\}\" \;)
    if [[ ! -d "${artifact_dir}" ]] || [[ -z "${packages}" ]]; then
      log_info "No .nupkg package(s) found in ${artifact_dir}."
      return
    fi

    local push_opts="--source $DOTNET_NUGET_REPO --api-key $DOTNET_NUGET_API_KEY"
    
    if [[ "${DOTNET_PACKAGE_SYMBOLS_DISABLED}" == "true" ]]; then
      push_opts="$push_opts --no-symbols"
    elif [[ "${DOTNET_NUGET_SYMBOL_REPO}" != "${DOTNET_NUGET_REPO}" ]]; then
      push_opts="$push_opts --symbol-source $DOTNET_NUGET_SYMBOL_REPO --symbol-api-key $DOTNET_NUGET_SYMBOL_API_KEY"
    fi

    log_info "Pushing packages to repository: $DOTNET_NUGET_REPO"
    echo "${packages}" | xargs -n1  | {
      local package_count
      package_count=0
      while read -r package; do
        if [[ -n "$package" && -f "$package" ]]; then
          log_info "Pushing package file: $package"
          log_debug "Command: dotnet nuget push \"$package\" $push_opts"
          # shellcheck disable=SC2086
          dotnet nuget push "$package" ${push_opts}
          package_count=$((package_count + 1))
        else
          log_warn "Skipping invalid package: ${package}"
        fi
      done
      if [[ $package_count -eq 0 ]]; then
        log_info "No .nupkg package found in ${artifact_dir}."
        return
      fi
      log_info "Released ${package_count} NuGet package(s)."      
    }
  }
  
  function _dotnet_publish_application_binaries() {
    log_info "Preparing application binaries for release to GitLab Generic Package Registry (version: ${DOTNET_PROJECT_VERSION_FULL})"
    local artifact_dir
    artifact_dir="${DOTNET_PROJECT_DIR}/${DOTNET_ARTIFACT_DIR}/${DOTNET_PROJECT_NAME}/${DOTNET_PROJECT_VERSION_FULL}"

    if [[ ! -d "$artifact_dir" ]]; then
      log_info "No application binaries found in ${artifact_dir}."
      return
    fi

    local target_path
    target_path="${CI_PROJECT_DIR}/${DOTNET_PROJECT_DIR}/${DOTNET_ARTIFACT_DIR}/zip/${DOTNET_PROJECT_NAME}/${DOTNET_PROJECT_VERSION_FULL}"
    mkdir -p "${target_path}"
    local binary_count
    binary_count=0
    # shellcheck disable=SC2231
    for bin_artifact in ${artifact_dir}/*/; do
      log_info "Archiving and publishing binary: ${bin_artifact}"
      local bin_name
      bin_name=$(basename "${bin_artifact}" | sed 's/ /_/g')
      local archive_name
      archive_name="${bin_name}-${DOTNET_PROJECT_VERSION_FULL}.zip"
      pushd "${artifact_dir}" 
      zip -r -9 "${target_path}/${archive_name}" "${bin_name}"
      popd
      curl --location --header "JOB-TOKEN: ${CI_JOB_TOKEN}" \
           --upload-file "${target_path}/${archive_name}" \
           "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${bin_name}/${DOTNET_PROJECT_VERSION_FULL}/${archive_name}"
      binary_count=$((binary_count + 1))
    done
    log_info "Published ${binary_count} binary artifact(s)."
  }

  function dotnet_run_publish() {
    log_info "Publishing packaged artifacts"
    
    _dotnet_publish_library_packages
    _dotnet_publish_application_binaries
    log_info "All artifacts published successfully"
  }

  unscope_variables
  eval_all_secrets

@@ -1588,3 +1671,24 @@ dotnet-sbom:
    - if: '$TBC_SBOM_MODE == "always"'
    - if: '$TBC_SBOM_MODE == "onrelease" && ($CI_COMMIT_REF_NAME =~ $RELEASE_REF || $CI_COMMIT_REF_NAME =~ $INTEG_REF || $CI_COMMIT_REF_NAME =~ $PROD_REF)'
    - when: never

dotnet-publish:
  extends: .dotnet-env-base
  stage: publish
  variables:
    DOTNET_REQUIRES_SDK_INSTALL: "false"
  before_script:
    - !reference [.dotnet-scripts]
    - install_ca_certs "${CUSTOM_CA_CERTS:-$DEFAULT_CA_CERTS}"
    - dotnet_configure_environment
    - dotnet_configure_project
    - maybe_install_packages curl git zip 
  script:
    - dotnet_run_publish
  rules:
    - if: '$DOTNET_PUBLISH_ENABLED != "true"'
      when: never
    - if: '$CI_COMMIT_TAG =~ $RELEASE_REF'
      when: on_success
    - when: manual
      allow_failure: true