diff --git a/actions/shared/collect_diag_info/README.md b/actions/shared/collect_diag_info/README.md new file mode 100644 index 00000000..f8e6b958 --- /dev/null +++ b/actions/shared/collect_diag_info/README.md @@ -0,0 +1,167 @@ +# 🚀 Collect Diagnostics Action + +GitHub Action to collect logs, resource manifests, and artifacts from a Kubernetes namespace +and upload them as a workflow artifact for post-deploy or post-failure investigation. + +--- + +## Features + +- Creates an `artifacts/` working directory for all collected files +- Collects pod list, per-pod YAML manifests, namespace events, PVC and PV information +- Captures container logs for all containers in all pods, with a pending-retry loop + (up to 300 seconds) for containers that are not yet running or terminated +- Captures previous-run logs for containers that have restarted at least once +- Lists all resources (`kubectl get all`) and secret names in the namespace +- Optionally collects VictoriaMetrics Custom Resource YAML files for monitoring pipelines +- Generates a timestamped artifact name automatically, or uses a caller-supplied name +- Uploads all collected files as a single GitHub Actions artifact + +--- + +## 📌 Inputs + +| Name | Description | Required | Default | +| --- | --- | --- | --- | +| `namespace` | Kubernetes namespace to collect diagnostics from. | Yes | - | +| `service_branch` | Branch or tag name of the service being tested. Used in the auto-generated artifact name — slashes are replaced with underscores, whitespace is stripped. Ignored when `artifact_name` is provided. | No | - | +| `artifact_name` | Custom base name for the uploaded artifact. When provided, the final name is `_`. When empty, the name is generated as `____artifacts_`. | No | `""` | +| `version` | Matrix dimension value (e.g. `${{ matrix.service_image }}`) appended as a suffix in the auto-generated artifact name. Has no effect when `artifact_name` is provided. | No | `""` | +| `monitoring_pipeline` | When `true`, dumps VictoriaMetrics CRs (`VMAlertManager`, `VMAlert`, `VMAgent`, `VMSingle`, `VMAuth`, `PlatformMonitoring`) as YAML files into `artifacts/`. | No | `false` | + +--- + +## 📌 Outputs + +This action produces no step outputs. All collected data is uploaded as a workflow artifact +whose name is derived from the inputs (see Additional Information). + +--- + +## How it works + +The action gathers diagnostics in the following order, with most steps running under +`if: always()` so they execute even when previous steps failed: + +1. **Monitoring resources** (only when `monitoring_pipeline: true`, runs under `if: always()`): exports six + VictoriaMetrics and `PlatformMonitoring` CRs as YAML files into `artifacts/`. +2. **Pod list**: runs `kubectl get pods` and saves output to + `artifacts/_get_pods.txt`. +3. **Pod YAML manifests**: saves each pod's full YAML to + `artifacts/pod_yamls/.yaml`. Failures write a `_FAILED.txt` marker instead. +4. **Namespace events**: saves `kubectl events` output to + `artifacts/_get_events.txt`. +5. **PVC YAML**: saves all PersistentVolumeClaim manifests to + `artifacts/_get_pvc_yaml.yaml`. +6. **PV list**: filters cluster-wide PVs for those bound to the namespace and saves to + `artifacts/_get_pv.txt`. +7. **Container logs**: for every container in every pod, collects current logs to + `artifacts/logs/__.log`. Containers not yet in `running` or `terminated` + state are queued and retried every 5 seconds until ready or the 300-second hard timeout + is reached. Containers with `restartCount > 0` also get their previous logs saved to + `artifacts/logs/____previous.log`. +8. **All resources**: prints `kubectl get all` to the workflow log (not saved to a file). +9. **Secrets list**: prints `kubectl get secrets` to the log (names only, not values). +10. **Artifact upload**: generates a timestamped artifact name and uploads the entire + `artifacts/` folder via `actions/upload-artifact@v4`. + +--- + +## Additional Information + +### Artifact naming + +When `artifact_name` is empty (the default), the artifact name is built as: + +```text +____artifacts_ +``` + +- `service_branch` has whitespace stripped and `/` replaced with `_`. +- `_` is included only when the `version` input is non-empty. +- The timestamp uses UTC with millisecond precision. + +When `artifact_name` is provided, the name is: + +```text +_ +``` + +Slashes in `artifact_name` are replaced with `_`. + +### Container log collection + +The log-collection step uses a two-pass approach: + +- **First pass**: iterates all pods and containers. Running or terminated containers have + logs collected immediately. All others are added to a pending list. +- **Retry loop**: pending containers are re-checked every 5 seconds. The loop exits when all + pending containers have been processed or the 300-second timeout is reached; timed-out + containers are skipped with a log message. + +Log files for the first pass use a double-underscore separator (`pod__container.log`). +Log files collected in the retry loop use a single-underscore separator (`pod_container.log`). + +### Monitoring YAML files + +When `monitoring_pipeline: true`, the following files are written to `artifacts/`: + +| File | Resource | +| --- | --- | +| `vmalertmanagers.yaml` | `VMAlertManager` | +| `vmalerts.yaml` | `VMAlert` | +| `vmagent.yaml` | `VMAgent` | +| `vmsingles.yaml` | `VMSingle` | +| `vmauths.yaml` | `VMAuth` | +| `PlatformMonitoring.yaml` | `PlatformMonitoring platformmonitoring` | + +All commands use `|| true` — missing CRDs do not fail the step. + +--- + +## Usage + +```yaml +name: Collect Diagnostics + +on: + workflow_dispatch: + +jobs: + diagnostics: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout pipeline repo + uses: actions/checkout@v4 + with: + path: qubership-test-pipelines + + - name: Collect diagnostics + if: always() + uses: Netcracker/qubership-test-pipelines/actions/shared/collect_diag_info@905b88900dc8c14291eaeff4eddcf4d4f734aee1 # v1.9.0 + with: + namespace: consul + service_branch: ${{ github.ref_name }} +``` + +--- + +## Notes + +- All diagnostic steps run with `if: always()` — they collect data even when earlier steps + in the caller's job have failed. Call this action with `if: always()` as well to ensure + it runs after a failed Helm deploy or verification step. +- The `artifacts/` directory is created but **not** cleared by this action. If a prior step + in the same job already populated `artifacts/`, those files will be included in the upload. +- The `Get Secrets` step logs secret names to the workflow run log (not values). Anyone with + read access to the workflow run can see the secret names in the namespace. +- The `version` input is used as a suffix in the auto-generated artifact name. Pass the + relevant matrix dimension (e.g. `${{ matrix.service_image }}`) explicitly — omit the input + to leave the suffix empty. +- Pin to a full 40-character commit SHA with the release tag as a trailing comment, e.g. + `@905b88900dc8c14291eaeff4eddcf4d4f734aee1 # v1.9.0`. The SHA is the immutable pin; the + comment shows which release it points to. Tags alone are mutable and can be moved — + acceptable only when callers explicitly want auto-updates within a minor version. Never + use `@main` or short SHAs. diff --git a/actions/shared/collect_diag_info/action.yml b/actions/shared/collect_diag_info/action.yml index a3ef96e7..86efa389 100644 --- a/actions/shared/collect_diag_info/action.yml +++ b/actions/shared/collect_diag_info/action.yml @@ -17,6 +17,13 @@ inputs: required: false default: "" + version: + description: | + Matrix version value used as a suffix in the auto-generated artifact name. + Pass any matrix dimension that identifies this run (e.g. matrix.version or matrix.service_image). + required: false + default: "" + monitoring_pipeline: description: | Only for Monitoring. Enable monitoring CRs checks (true) or disable them (false) @@ -33,7 +40,7 @@ runs: run: mkdir -p artifacts - name: Get monitoring specific resources - if: ${{ inputs.monitoring_pipeline == 'true' }} + if: always() && inputs.monitoring_pipeline == 'true' shell: bash env: NAMESPACE: ${{ inputs.namespace }} @@ -236,7 +243,7 @@ runs: env: SERVICE_BRANCH: ${{ inputs.service_branch }} INPUT_ARTIFACT_NAME: ${{ inputs.artifact_name }} - MATRIX_VERSION: ${{ matrix.version }} + MATRIX_VERSION: ${{ inputs.version }} GITHUB_JOB: ${{ github.job }} NAMESPACE: ${{ inputs.namespace }} run: | diff --git a/actions/shared/create_ingress/README.md b/actions/shared/create_ingress/README.md new file mode 100644 index 00000000..70d842b0 --- /dev/null +++ b/actions/shared/create_ingress/README.md @@ -0,0 +1,134 @@ +# 🚀 Create Ingress-Controller + +GitHub Action that installs ingress-nginx into a `kind` cluster and configures CoreDNS +with a wildcard `*.testdomain.local` DNS zone pointing to the ingress controller's ClusterIP. + +--- + +## Features + +- Deploys the official `ingress-nginx` controller for `kind` clusters +- Patches the `nginx` IngressClass to be the cluster-wide default +- Configures CoreDNS to resolve all `*.testdomain.local` hostnames to the nginx ClusterIP, + enabling in-cluster DNS-based routing without external DNS +- Restarts CoreDNS pods and waits up to 180 seconds for them to become ready +- Prints pod and event status for the `ingress-nginx` namespace after setup + +--- + +## 📌 Inputs + +This action has no inputs. + +--- + +## 📌 Outputs + +This action produces no outputs. + +--- + +## How it works + +The action performs four cluster-level changes, all of which are permanent for the lifetime +of the `kind` cluster: + +1. **Install ingress-nginx**: applies the official `kind` ingress manifest from + `https://kind.sigs.k8s.io/examples/ingress/deploy-ingress-nginx.yaml` and patches the + `nginx` IngressClass to be the default class. Any Ingress resource without an explicit + `ingressClassName` will be handled by this controller. +2. **Configure wildcard DNS**: reads the ClusterIP of the `ingress-nginx-controller` service + in the `ingress-nginx` namespace, then replaces the `kube-system/coredns` ConfigMap with + an updated Corefile. The new Corefile adds a `testdomain.local:53` zone that answers any + query matching `*.testdomain.local` with the nginx ClusterIP: + + ```text + testdomain.local:53 { + template ANY ANY { + match .*\.testdomain\.local\.$ + answer "{{ .Name }} 60 IN A " + fallthrough + } + } + ``` + +3. **Restart CoreDNS**: deletes all pods with label `k8s-app=kube-dns` in `kube-system` to + force a reload of the new ConfigMap. Kubernetes recreates them automatically. +4. **Wait for readiness**: runs `kubectl wait --for=condition=Ready` on the new CoreDNS pods + with a 180-second timeout. The action fails if pods do not become ready in time. + +After setup, any workload in the cluster can reach the ingress controller by pointing an +Ingress resource to a `*.testdomain.local` hostname — no external DNS configuration is needed. + +--- + +## Additional Information + +### testdomain.local DNS zone + +The wildcard zone is cluster-internal only. `*.testdomain.local` hostnames resolve to the +nginx ClusterIP, which is not routable outside the cluster. This is designed for in-cluster +test scenarios where services communicate via Ingress resources using fixed hostnames. + +The CoreDNS ConfigMap is replaced (not patched) using `kubectl replace`. The full Corefile, +including the original `cluster.local` zone configuration, is embedded in the action and +printed to the workflow log during the replace step. + +### kind cluster requirement + +The ingress manifest is sourced directly from `https://kind.sigs.k8s.io/examples/ingress/deploy-ingress-nginx.yaml`. +This action is designed exclusively for `kind` clusters and will not work correctly on other +cluster types without modification. + +### No waiting for ingress-nginx readiness + +The action does not wait for the `ingress-nginx-controller` deployment to become ready before +reading its ClusterIP. The ClusterIP is assigned at Service creation time and is available +immediately after `kubectl apply`, but the controller pods themselves may still be starting. +If subsequent steps depend on the controller being fully functional, add a `kubectl wait` +step after this action. + +--- + +## Usage + +```yaml +name: Setup Kind Ingress + +on: + workflow_dispatch: + +jobs: + setup: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout pipeline repo + uses: actions/checkout@v4 + with: + path: qubership-test-pipelines + + - name: Create ingress controller + uses: Netcracker/qubership-test-pipelines/actions/shared/create_ingress@905b88900dc8c14291eaeff4eddcf4d4f734aee1 # v1.9.0 +``` + +--- + +## Notes + +- This action modifies the `kube-system/coredns` ConfigMap using `kubectl replace` — it + overwrites any previous custom CoreDNS configuration. Run it before any step that adds + additional CoreDNS rules. +- The full Corefile content (including the nginx ClusterIP) is printed to the workflow log + during the CoreDNS patch step. +- Designed for `kind` clusters only. The ingress manifest URL is pinned to the official + `kind.sigs.k8s.io` example and may not suit production or other cluster types. +- If CoreDNS pods do not reach `Ready` within 180 seconds, the action fails and subsequent + steps will not run. Check `kubectl get pods -n kube-system` and `kubectl describe` output + in the workflow log for the root cause. +- Pin to a full 40-character commit SHA with the release tag as a trailing comment, e.g. + `@905b88900dc8c14291eaeff4eddcf4d4f734aee1 # v1.9.0`. The SHA is the immutable pin; the + comment shows which release it points to. Tags alone are mutable and can be moved — + acceptable only when callers explicitly want auto-updates within a minor version. Never + use `@main` or short SHAs. diff --git a/actions/shared/create_restricted_resources/README.md b/actions/shared/create_restricted_resources/README.md index 9e48e00e..5f6b827a 100644 --- a/actions/shared/create_restricted_resources/README.md +++ b/actions/shared/create_restricted_resources/README.md @@ -1,22 +1,127 @@ # 🚀 Create Restricted Resources Action -This Action creates resources before restricted installation + +GitHub Action to create RBAC resources and a client certificate for a restricted Helm +installation. Sets up a `restricted` user in kubeconfig and switches to a `restricted-context` +so that subsequent Helm commands run under that user's permissions. + +--- ## Features -- Creates ClusterRoles, ClusterRoleBindings and CRDs -- Generates client certificates for restricted access -- Creates restricted Role and RoleBinding -- Creates Kubernetes context for restricted user + +- Optionally creates service-specific ClusterRoles and ClusterRoleBindings from files in + `qubership-test-pipelines/restricted//` (skips with a warning if absent) +- Generates a 2048-bit RSA client key and certificate via the Kubernetes CSR API +- Creates a namespace-scoped `Role` and `RoleBinding` granting the `restricted` user full + access within the target namespace +- Registers the `restricted` user credentials in the current kubeconfig +- Creates and activates a `restricted-context` pointing to `kind-kind` and the target namespace + +--- ## 📌 Inputs -| Name | Required | Description | -|-------------------|----------|-------------------------------------------------------| -| `service_name` | Yes | Helm release name | -| `repository_name` | Yes | Service repository name (without organization prefix) | -| `path_to_chart` | Yes | Path to Helm chart in service repository | -| `namespace` | Yes | Target Kubernetes namespace | +| Name | Description | Required | Default | +| --- | --- | --- | --- | +| `service_name` | Helm release name. Also used as the subdirectory name under `qubership-test-pipelines/restricted/` when looking for service-specific ClusterRoles and ClusterRoleBindings. | Yes | - | +| `repository_name` | Service repository name. Accepted for call-site compatibility with `helm_deploy` but not used by any step in this action. | No | - | +| `path_to_chart` | Path to the Helm chart within the service repository. Accepted for call-site compatibility with `helm_deploy` but not used by any step in this action. | No | - | +| `namespace` | Kubernetes namespace for the installation. Used as the `metadata.namespace` in the `Role` and `RoleBinding` and as the default namespace in the kubeconfig context. | Yes | - | + +--- + +## 📌 Outputs + +This action produces no step outputs. Its side-effects are the RBAC objects created in the +cluster and the kubeconfig context switch to `restricted-context`. + +--- + +## How it works + +The action sets up a restricted user identity for a Helm install in four phases: + +**Phase 1 — Service-specific cluster RBAC (optional):** +Looks for `qubership-test-pipelines/restricted//clusterRoles/` and +`clusterRoleBindings/` directories. If either is present, every file in it is applied with +`kubectl create`. If either directory is missing, a warning annotation is emitted and the +phase is skipped — the action does not fail. + +**Phase 2 — Client certificate:** +Generates a 2048-bit RSA private key (`restricted.key`) and a CSR with subject +`CN=restricted/O=dev-team`. Any pre-existing `restricted-csr` CertificateSigningRequest +object is deleted first. A new CSR object (`kubernetes.io/kube-apiserver-client` signer, +`client auth` usage) is submitted, approved, and the signed certificate is retrieved as +`restricted.crt`. + +**Phase 3 — Namespace Role and RoleBinding:** +Patches `qubership-test-pipelines/restricted/restricted-role.yml` and +`qubership-test-pipelines/restricted/restricted-rb.yml` in-place, setting +`metadata.namespace` to `inputs.namespace` using `scripts/update_yaml.py`. Then applies both +files. The Role grants `["*"]` verbs on `["*"]` resources within the namespace; the +RoleBinding binds the `restricted` User to that Role. + +**Phase 4 — kubeconfig setup:** +Registers the `restricted` credentials (certificate + key) in kubeconfig, creates +`restricted-context` targeting the `kind-kind` cluster and the target namespace, and switches +the active context to `restricted-context`. All subsequent `kubectl` and `helm` calls in the +job run as the `restricted` user until the context is changed again. + +--- + +## Additional Information + +### Service-specific ClusterRole directory layout + +Place service-specific cluster-scoped resources under: + +```text +qubership-test-pipelines/ + restricted/ + / + clusterRoles/ + my-clusterrole.yaml + clusterRoleBindings/ + my-clusterrolebinding.yaml +``` + +Both subdirectories are optional. If neither exists (no entry for the service at all), +both phases emit a warning and continue. Existing services with files: + +- `consul` — 2 ClusterRoleBindings, 2 ClusterRoles +- `opensearch` — 2 ClusterRoleBindings, 3 ClusterRoles +- `rabbitmq` — 1 ClusterRoleBinding -## Usage Example +### Role permissions + +The `restricted-role.yml` template grants the following within the target namespace: + +```yaml +rules: +- apiGroups: ["*"] + resources: ["*"] + verbs: ["*"] +``` + +This is full namespace admin access, not a narrowly scoped restricted role. The name +`restricted` refers to the authentication identity (certificate-based user), not to +limited permissions. + +### update_yaml.py + +`scripts/update_yaml.py` modifies YAML files on disk using a slash-delimited path +(`metadata/namespace`). It overwrites the file in-place and re-serialises it with PyYAML, +which may alter key ordering and comment stripping. The `restricted-role.yml` and +`restricted-rb.yml` files are modified every time this action runs. + +### Context switch + +After this action completes, the active kubeconfig context is `restricted-context`. The +caller (`helm_deploy`) must switch back to `kind-kind` after the Helm call. The `helm_deploy` +action does this with `kubectl config use-context kind-kind` when `restricted: true`. + +--- + +## Usage ```yaml name: Create Restricted Resources @@ -26,13 +131,37 @@ on: jobs: restricted-install: - runs-on: ${{inputs.runner_type}} + runs-on: ubuntu-latest + permissions: + contents: read steps: + - name: Checkout pipeline repo + uses: actions/checkout@v4 + with: + path: qubership-test-pipelines + - name: Create restricted resources - uses: Netcracker/qubership-test-pipelines/actions/shared/create_restricted_resources@main + uses: Netcracker/qubership-test-pipelines/actions/shared/create_restricted_resources@905b88900dc8c14291eaeff4eddcf4d4f734aee1 # v1.9.0 with: - service_name: 'consul' - repository_name: 'qubership-consul' - path_to_chart: 'charts/helm/consul-service' - namespace: 'consul' + service_name: consul + repository_name: Netcracker/qubership-consul + path_to_chart: charts/helm/consul-service + namespace: consul ``` + +--- + +## Notes + +- The CSR YAML submitted to Kubernetes contains the base64-encoded CSR (derived from + `restricted.key`) and is printed to the log as part of `kubectl apply` output. +- The `restricted-role.yml` and `restricted-rb.yml` files are modified on disk. If the same + runner reuses the workspace across jobs without a clean checkout, the namespace value from + a previous run may persist. +- This action is designed for `kind` clusters. The kubeconfig context is hardcoded to + `kind-kind` as the cluster name. +- Pin to a full 40-character commit SHA with the release tag as a trailing comment, e.g. + `@905b88900dc8c14291eaeff4eddcf4d4f734aee1 # v1.9.0`. The SHA is the immutable pin; the + comment shows which release it points to. Tags alone are mutable and can be moved — + acceptable only when callers explicitly want auto-updates within a minor version. Never + use `@main` or short SHAs. diff --git a/actions/shared/create_restricted_resources/action.yml b/actions/shared/create_restricted_resources/action.yml index b575c427..3266e448 100644 --- a/actions/shared/create_restricted_resources/action.yml +++ b/actions/shared/create_restricted_resources/action.yml @@ -10,13 +10,13 @@ inputs: description: | Service repository name (without organization prefix) Example: 'qubership-consul' for https://github.com/Netcracker/qubership-consul - required: true + required: false path_to_chart: description: | Path to helm chart within service repository Example: 'charts/helm/consul-service' - required: true + required: false namespace: description: | @@ -29,28 +29,32 @@ runs: # Create CRD - name: Create ClusterRoles shell: bash + env: + SERVICE_NAME: ${{ inputs.service_name }} # language=bash run: | # ▶️ Create ClusterRoles - restricted_folder="qubership-test-pipelines/restricted/${{inputs.service_name}}" + restricted_folder="qubership-test-pipelines/restricted/${SERVICE_NAME}" if [ ! -d "$restricted_folder" ] || [ ! -d "$restricted_folder/clusterRoles" ]; then echo "::warning::Files with clusterRoles not found. Resources not created." else echo "Applying ClusterRoles..." - kubectl create -f $restricted_folder/clusterRoles + kubectl create -f "$restricted_folder/clusterRoles" fi - name: Create ClusterRoleBindings shell: bash + env: + SERVICE_NAME: ${{ inputs.service_name }} # language=bash run: | # ▶️ Create ClusterRoleBindings - restricted_folder="qubership-test-pipelines/restricted/${{inputs.service_name}}" + restricted_folder="qubership-test-pipelines/restricted/${SERVICE_NAME}" if [ ! -d "$restricted_folder" ] || [ ! -d "$restricted_folder/clusterRoleBindings" ]; then echo "::warning::Files with clusterRoleBindings not found. Resources not created." else echo "Applying clusterRoleBindings..." - kubectl create -f $restricted_folder/clusterRoleBindings + kubectl create -f "$restricted_folder/clusterRoleBindings" fi # Create Certificate @@ -103,23 +107,27 @@ runs: # Create Role and RB - name: Update yaml with Role shell: bash + env: + NAMESPACE: ${{ inputs.namespace }} # language=bash run: | # ▶️ Update yaml with Role python qubership-test-pipelines/scripts/update_yaml.py \ --file='qubership-test-pipelines/restricted/restricted-role.yml' \ --path='metadata/namespace' \ - --value='${{inputs.namespace}}' + --value="${NAMESPACE}" - name: Update yaml with RoleBinding shell: bash + env: + NAMESPACE: ${{ inputs.namespace }} # language=bash run: | # ▶️ Update yaml with RoleBinding python qubership-test-pipelines/scripts/update_yaml.py \ --file='qubership-test-pipelines/restricted/restricted-rb.yml' \ --path='metadata/namespace' \ - --value='${{inputs.namespace}}' + --value="${NAMESPACE}" - name: Create Role shell: bash @@ -139,8 +147,10 @@ runs: - name: Create context for user shell: bash + env: + NAMESPACE: ${{ inputs.namespace }} # language=bash - run: kubectl config set-context restricted-context --cluster=kind-kind --namespace=${{inputs.namespace}} --user=restricted + run: kubectl config set-context restricted-context --cluster=kind-kind --namespace="${NAMESPACE}" --user=restricted - name: Use context shell: bash @@ -150,4 +160,4 @@ runs: - name: Get config shell: bash # language=bash - run: kubectl config view --raw + run: kubectl config view diff --git a/actions/shared/get_certs/README.md b/actions/shared/get_certs/README.md new file mode 100644 index 00000000..164391cf --- /dev/null +++ b/actions/shared/get_certs/README.md @@ -0,0 +1,151 @@ +# 🚀 Get Certs Action + +GitHub Action to extract TLS certificate data from a Kubernetes secret and write +the base64-encoded values to files in the runner's temporary directory. + +--- + +## Features + +- Reads a named Kubernetes TLS secret from the specified namespace +- Extracts `ca.crt`, `tls.crt`, and `tls.key` fields from the secret's `data` block +- Fails immediately if any of the three fields is absent or empty +- Writes each base64-encoded value to a separate file in `${{ runner.temp }}` + for consumption by subsequent steps + +--- + +## 📌 Inputs + +| Name | Description | Required | Default | +| --- | --- | --- | --- | +| `service_name` | Helm release name. Accepted for call-site compatibility but not used by any step in this action. | No | - | +| `secret_name` | Name of the Kubernetes secret containing the TLS certificates. Also used as the prefix for the output file names. | Yes | - | +| `namespace` | Kubernetes namespace where the secret is located. | Yes | - | + +--- + +## 📌 Outputs + +This action produces no step outputs. Certificate data is written to files in +`${{ runner.temp }}` (see Additional Information for file paths and format). + +--- + +## How it works + +The action installs `yq` via `snap`, then reads the named Kubernetes secret from the cluster +and extracts three certificate fields. If any field is missing or empty the action exits with +an error. On success, the three base64-encoded values are written to files under +`${{ runner.temp }}`: + +| File | Content | +| --- | --- | +| `${{ runner.temp }}/_ca_crt.txt` | `data["ca.crt"]` — base64-encoded CA certificate | +| `${{ runner.temp }}/_tls_crt.txt` | `data["tls.crt"]` — base64-encoded server certificate | +| `${{ runner.temp }}/_tls_key.txt` | `data["tls.key"]` — base64-encoded private key | + +Subsequent steps in the same job can read these files directly. The `runner.temp` directory +is not uploaded as a workflow artifact and is cleaned up at the end of the job. + +--- + +## Additional Information + +### Output file format + +The values written to disk are the **base64-encoded** strings exactly as they appear in the +Kubernetes secret's `data` block — not the raw PEM content. Callers that need the decoded +PEM must base64-decode the file content: + +```bash +base64 --decode < "${{ runner.temp }}/_ca_crt.txt" > ca.crt +base64 --decode < "${{ runner.temp }}/_tls_crt.txt" > tls.crt +base64 --decode < "${{ runner.temp }}/_tls_key.txt" > tls.key +``` + +### Expected secret structure + +The action expects a standard Kubernetes TLS secret with the following `data` keys: + +```yaml +apiVersion: v1 +kind: Secret +type: kubernetes.io/tls +metadata: + name: + namespace: +data: + ca.crt: + tls.crt: + tls.key: +``` + +If any of `ca.crt`, `tls.crt`, or `tls.key` is absent or empty, the action exits with +`exit 1` and a `ERROR: Failed to extract certificates!` message. + +### yq installation + +The action installs `yq` via `sudo snap install yq` on every run. This requires: + +- The runner to support `snap` (standard on Ubuntu GitHub-hosted runners) +- Network access to the snap store +- Approximately 10–30 seconds of additional setup time per run + +There is no version pin on the `snap install` command — the latest available `yq` snap is +always installed. + +--- + +## Usage + +```yaml +name: Get TLS Certificates + +on: + workflow_dispatch: + +jobs: + get-certs: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout pipeline repo + uses: actions/checkout@v4 + with: + path: qubership-test-pipelines + + - name: Get TLS certificates from secret + uses: Netcracker/qubership-test-pipelines/actions/shared/get_certs@905b88900dc8c14291eaeff4eddcf4d4f734aee1 # v1.9.0 + with: + service_name: consul + secret_name: consul-tls + namespace: consul + + - name: Use certificate + shell: bash + run: | + base64 --decode < "${{ runner.temp }}/consul-tls_ca_crt.txt" > ca.crt + echo "CA certificate extracted." +``` + +--- + +## Notes + +- The full Kubernetes secret YAML (all fields, all data keys) is fetched into a shell + variable during extraction. The raw secret content passes through the step's shell + environment but is not echoed to the log by this action. +- Output files contain base64-encoded data, not decoded PEM. Decode with + `base64 --decode` before passing to tools that expect PEM format. +- Files in `${{ runner.temp }}` persist for the lifetime of the job but are not automatically + uploaded as workflow artifacts. If certificate data is needed across jobs, upload it + explicitly (with care given the sensitivity of private key material). +- The `tls.key` file contains the base64-encoded private key. Treat the runner's temp + directory as sensitive storage and avoid logging its contents. +- Pin to a full 40-character commit SHA with the release tag as a trailing comment, e.g. + `@905b88900dc8c14291eaeff4eddcf4d4f734aee1 # v1.9.0`. The SHA is the immutable pin; the + comment shows which release it points to. Tags alone are mutable and can be moved — + acceptable only when callers explicitly want auto-updates within a minor version. Never + use `@main` or short SHAs. diff --git a/actions/shared/get_certs/action.yml b/actions/shared/get_certs/action.yml index f1a68df9..16f9f623 100644 --- a/actions/shared/get_certs/action.yml +++ b/actions/shared/get_certs/action.yml @@ -4,7 +4,7 @@ inputs: service_name: description: | Helm release name - required: true + required: false secret_name: description: | Name of secret with certs @@ -19,11 +19,15 @@ runs: steps: - name: Get secrets shell: bash + env: + SECRET_NAME: ${{ inputs.secret_name }} + NAMESPACE: ${{ inputs.namespace }} + RUNNER_TEMP: ${{ runner.temp }} # language=bash run: | # ▶️ Get secrets sudo snap install yq - secret=$(kubectl get secret ${{inputs.secret_name}} -oyaml -n ${{inputs.namespace}}) + secret=$(kubectl get secret "${SECRET_NAME}" -oyaml -n "${NAMESPACE}") ca_crt=$(echo "$secret" | yq eval '.data["ca.crt"]' -) tls_crt=$(echo "$secret" | yq eval '.data["tls.crt"]' -) tls_key=$(echo "$secret" | yq eval '.data["tls.key"]' -) @@ -33,6 +37,6 @@ runs: exit 1 fi - echo "$ca_crt" > ${{runner.temp}}/${{inputs.secret_name}}_ca_crt.txt - echo "$tls_crt" > ${{runner.temp}}/${{inputs.secret_name}}_tls_crt.txt - echo "$tls_key" > ${{runner.temp}}/${{inputs.secret_name}}_tls_key.txt + echo "$ca_crt" > "${RUNNER_TEMP}/${SECRET_NAME}_ca_crt.txt" + echo "$tls_crt" > "${RUNNER_TEMP}/${SECRET_NAME}_tls_crt.txt" + echo "$tls_key" > "${RUNNER_TEMP}/${SECRET_NAME}_tls_key.txt" diff --git a/actions/shared/get_crds/action.yaml b/actions/shared/get_crds/action.yaml index 475c836f..a973ee0c 100644 --- a/actions/shared/get_crds/action.yaml +++ b/actions/shared/get_crds/action.yaml @@ -1,4 +1,5 @@ name: Get CRD names +description: GitHub Action to get CRD names from yaml files inputs: crd_location: description: | @@ -8,6 +9,7 @@ inputs: # if checked out to qubership-kafka directory, then # 'qubership-kafka/operator/charts/helm/kafka-service/crds' required: true + type: string outputs: crd_names: value: ${{ steps.get_crds.outputs.crd_names }} @@ -17,9 +19,11 @@ runs: - name: Get CRDs id: get_crds shell: bash + env: + CRD_LOCATION: ${{ inputs.crd_location }} run: | # ▶️ Get CRDs - yamls=$(find ${{inputs.crd_location}} -type f -name "*.yaml" -or -name "*.yml") + yamls=$(find "${CRD_LOCATION}" -type f -name "*.yaml" -or -name "*.yml") CRDS_ARRAY=() for yaml in ${yamls}; do yml_kind=$(yq e '.kind' $yaml) diff --git a/actions/shared/helm_deploy/README.md b/actions/shared/helm_deploy/README.md index 7c0fe673..4a63cf96 100644 --- a/actions/shared/helm_deploy/README.md +++ b/actions/shared/helm_deploy/README.md @@ -1,50 +1,163 @@ -# 🚀 Helm Deploy GitHub Action -This Action automates Helm install/upgrade services using Helm +# 🚀 Helm Deploy Action + +GitHub Action to install or upgrade Kubernetes services using Helm. + +--- + ## Features -- Checkout service and pipeline repositories -- Namespace creation for new installations -- Creation necessary resources before restricted installation -- Replace Docker image versions in Helm charts for specified components -- Deploy service with specified templates -- Deploy (install/upgrade) service using Helm -- Deploy with restricted permissions or cluster-admin rights + +- Checks out the target service repository at the specified branch or commit +- Creates the target namespace when deploying in `install` mode +- Applies pre-installation Kubernetes resources from a configurable folder +- Creates or replaces CRDs found in the chart's `crds/` directory +- Sets up restricted RBAC resources (ServiceAccount, ClusterRoleBinding) when running in restricted mode +- Patches `values.yaml` to override a specific Docker image tag (`image_replacement`) +- Updates the `tags` field in the values template for test filtering (`test_tags`) +- Runs `charts-values-update-action` to propagate the service branch or commit tag into chart values + (skipped when `service_branch` is a release version) +- Installs or upgrades the Helm release with a configurable values template +- Logs all running pod images after deploy +- Restores the default kubeconfig context after restricted-mode deploys + +--- ## 📌 Inputs -| Name | Required | Default | Type | Description | -|--------------------|----------|-----------|---------|----------------------------------------------------------------------------| -| `deploy_mode` | Yes | `install` | string | Deployment mode: 'install'/'update' | -| `restricted` | Yes | `false` | boolean | Use restricted mode or not (installation by user with restricted rights) | -| `path_to_template` | Yes | - | string | Path to template file in qubership-test-pipelines repository | -| `service_branch` | Yes | - | string | Branch in service repository | -| `service_name` | Yes | - | string | Helm release name | -| `repository_name` | Yes | - | string | Service repository name (without organization prefix) | -| `path_to_chart` | Yes | - | string | Path to Helm chart in service repository | -| `components` | Yes | - | string | List of components for image updates | -| `namespace` | Yes | - | string | Kubernetes target namespace | +| Name | Description | Required | Default | +| --- | --- | --- | --- | +| `deploy_mode` | Deployment mode: `install` for a clean installation, `upgrade` to upgrade an existing release. | Yes | `install` | +| `restricted` | When `true`, installs using a user with restricted rights and passes `--skip-crds` to Helm. When `false`, installs as cluster admin. | Yes | `false` | +| `path_to_template` | Path to the values template file inside the `qubership-test-pipelines` repository. Example: `templates/consul-service/consul_clean_infra_passport.yml` | Yes | - | +| `service_branch` | Branch, tag, or commit SHA in the service repository to check out. A value matching `vX.Y.Z` is treated as a release and skips chart-values update. | Yes | - | +| `service_name` | Helm release name used in `helm install`/`upgrade`. | Yes | - | +| `repository_name` | Full `org/repo` name of the service repository. Example: `Netcracker/qubership-consul` | Yes | - | +| `path_to_chart` | Path to the Helm chart directory within the service repository. Example: `charts/helm/consul-service` | Yes | - | +| `namespace` | Kubernetes namespace for the installation. Created automatically in `install` mode. | Yes | - | +| `resource_folder` | Path (relative to repo root) to a folder of Kubernetes manifests applied with `kubectl create` before installation. Pass an empty string to skip. | Yes | - | +| `image_replacement` | Full image name including tag to pin in `values.yaml`, e.g. `pgskipper-docker-patroni-18`. The base name (everything before the last `-`) is used to locate the existing value. Skipped when empty. | No | `""` | +| `test_tags` | Replacement value for the `tags:` field in the values template. Skipped when empty. | Yes | `""` | + +--- + +## 📌 Outputs + +This action produces no outputs. + +--- + +## How it works + +1. The service repository is checked out into a subdirectory named after `repository_name`. +2. In `install` mode the target namespace is created if it does not already exist. +3. If `resource_folder` is non-empty, every file in that folder is applied with `kubectl create`. +4. The chart's `crds/` directory is walked and each CRD is created or replaced via `kubectl`. +5. When `restricted: true` and `deploy_mode: install`, the + `actions/shared/create_restricted_resources` local action runs to set up RBAC objects. +6. The values template is copied from `qubership-test-pipelines` into the chart directory. +7. `service_branch` is classified as a release (`vX.Y.Z`) or non-release. For non-release branches + and commits, `charts-values-update-action` rewrites image tags in chart values to match the + branch name or a normalised commit hash (`git-<7chars>`). For release branches this step is + skipped entirely. +8. If `image_replacement` is set, the matching image tag in `values.yaml` is replaced with the + provided value using `sed`. +9. If `test_tags` is set, the `tags:` line in the copied template file is replaced. +10. `helm install` or `helm upgrade` runs against the chart directory, using the copied template as + `-f