mirror of
https://github.com/Azure/k8s-deploy.git
synced 2026-08-06 20:09:29 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8cfec839d |
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 #v7.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 #v3.29.5
|
||||
uses: github/codeql-action/init@c793b717bc78562f491db7b0e93a3a178b099162 #v3.29.5
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
# with:
|
||||
# languages: go, javascript, csharp, python, cpp, java
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@f205ea1c3313d32999d8d6a48b4f6530d4437b38 #v3.29.5
|
||||
uses: github/codeql-action/autobuild@c793b717bc78562f491db7b0e93a3a178b099162 #v3.29.5
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -46,4 +46,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 #v3.29.5
|
||||
uses: github/codeql-action/analyze@c793b717bc78562f491db7b0e93a3a178b099162 #v3.29.5
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
|
||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||
name: Setting issue as idle
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
operations-per-run: 100
|
||||
exempt-issue-labels: 'backlog'
|
||||
|
||||
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
|
||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||
name: Setting PR as idle
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: install deps
|
||||
run: npm install
|
||||
|
||||
|
||||
@@ -13,6 +13,6 @@ jobs:
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
uses: Azure/action-release-workflows/.github/workflows/release_js_project.yaml@2ff084415c5af591fd13d95ddbfc4cdb5ed2012e # v1.1.0
|
||||
uses: Azure/action-release-workflows/.github/workflows/release_js_project.yaml@v1
|
||||
with:
|
||||
changelogPath: ./CHANGELOG.md
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
KUBECONFIG: /home/runner/.kube/config
|
||||
NAMESPACE: test-${{ github.run_id }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: ./.github/actions/minikube-setup
|
||||
name: Setup Minikube Environment
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
KUBECONFIG: /home/runner/.kube/config
|
||||
NAMESPACE: test-${{ github.run_id }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: ./.github/actions/minikube-setup
|
||||
name: Setup Minikube Environment
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
KUBECONFIG: /home/runner/.kube/config
|
||||
NAMESPACE: test-${{ github.run_id }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: ./.github/actions/minikube-setup
|
||||
name: Setup Minikube Environment
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
KUBECONFIG: /home/runner/.kube/config
|
||||
NAMESPACE: test-${{ github.run_id }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: ./.github/actions/minikube-setup
|
||||
name: Setup Minikube Environment
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
KUBECONFIG: /home/runner/.kube/config
|
||||
NAMESPACE: test-${{ github.run_id }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: ./.github/actions/minikube-setup
|
||||
name: Setup Minikube Environment
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
KUBECONFIG: /home/runner/.kube/config
|
||||
NAMESPACE: test-${{ github.run_id }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: ./.github/actions/minikube-setup
|
||||
name: Setup Minikube Environment
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
NAMESPACE1: integration-test-namespace1-${{ github.run_id }}
|
||||
NAMESPACE2: integration-test-namespace2-${{ github.run_id }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: ./.github/actions/minikube-setup
|
||||
name: Setup Minikube Environment
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -30,13 +30,13 @@ jobs:
|
||||
run: npm run build
|
||||
|
||||
- name: Azure login
|
||||
uses: azure/login@v3.0.0
|
||||
uses: azure/login@v2.3.0
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
|
||||
- uses: Azure/setup-kubectl@829323503d1be3d00ca8346e5391ca0b07a9ab0d # v5.1.0
|
||||
- uses: Azure/setup-kubectl@776406bce94f63e41d621b960d78ee25c8b76ede # v4.0.1
|
||||
name: Install Kubectl
|
||||
|
||||
- name: Create private AKS cluster and set context
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
run: |
|
||||
az aks command invoke --resource-group ${{ env.NAMESPACE }} --name ${{ env.NAMESPACE }} --command "kubectl create ns ${{ env.NAMESPACE }}"
|
||||
|
||||
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # 7.0.0
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # 6.2.0
|
||||
name: Install Python
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
KUBECONFIG: /home/runner/.kube/config
|
||||
NAMESPACE: test-${{ github.run_id }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: ./.github/actions/minikube-setup
|
||||
name: Setup Minikube Environment
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
name: Run Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- run: |
|
||||
npm install
|
||||
npm test
|
||||
|
||||
@@ -2,6 +2,5 @@ node_modules
|
||||
|
||||
.DS_Store
|
||||
.idea
|
||||
lib/
|
||||
|
||||
coverage/
|
||||
@@ -1,45 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [7.0.0] - 2026-06-23
|
||||
|
||||
### Changed
|
||||
|
||||
- #528 [Confine manifest paths to GITHUB_WORKSPACE (**breaking**: workflows passing `manifests:` paths outside the workspace now fail with a clear error)](https://github.com/Azure/k8s-deploy/pull/528)
|
||||
- #527 [Pin release workflow to commit SHA for supply chain safety](https://github.com/Azure/k8s-deploy/pull/527)
|
||||
|
||||
### Security
|
||||
|
||||
- #528 [Harden URL fetcher error handling in writeYamlFromURLToFile](https://github.com/Azure/k8s-deploy/pull/528)
|
||||
- #537 [Bump undici from 6.25.0 to 6.27.0](https://github.com/Azure/k8s-deploy/pull/537)
|
||||
- #536 [Bump actions/checkout in /.github/workflows in the actions group](https://github.com/Azure/k8s-deploy/pull/536)
|
||||
- #534 [Bump the actions group with 3 updates](https://github.com/Azure/k8s-deploy/pull/534)
|
||||
- #533 [Bump esbuild from 0.28.0 to 0.28.1](https://github.com/Azure/k8s-deploy/pull/533)
|
||||
- #532 [Bump github/codeql-action in /.github/workflows in the actions group](https://github.com/Azure/k8s-deploy/pull/532)
|
||||
- #531 [Bump @types/node from 25.9.1 to 25.9.2 in the actions group](https://github.com/Azure/k8s-deploy/pull/531)
|
||||
- #530 [Bump the actions group in /.github/workflows with 2 updates](https://github.com/Azure/k8s-deploy/pull/530)
|
||||
- #529 [Bump the actions group with 2 updates](https://github.com/Azure/k8s-deploy/pull/529)
|
||||
- #526 [Bump the actions group in /.github/workflows with 2 updates](https://github.com/Azure/k8s-deploy/pull/526)
|
||||
- #525 [Bump the actions group with 2 updates](https://github.com/Azure/k8s-deploy/pull/525)
|
||||
- #524 [Bump @types/node from 25.7.0 to 25.9.0 in the actions group](https://github.com/Azure/k8s-deploy/pull/524)
|
||||
- #523 [Bump github/codeql-action in /.github/workflows in the actions group](https://github.com/Azure/k8s-deploy/pull/523)
|
||||
|
||||
## [6.0.0] - 2026-04-17
|
||||
|
||||
### Changed
|
||||
|
||||
- #504 [Update Node.js runtime from node20 to node24](https://github.com/Azure/k8s-deploy/pull/504)
|
||||
- #500 [Update action version references in README to latest majors](https://github.com/Azure/k8s-deploy/pull/500)
|
||||
|
||||
### Security
|
||||
|
||||
- #506 [Bump undici from 6.23.0 to 6.24.1](https://github.com/Azure/k8s-deploy/pull/506)
|
||||
- #513 [Bump vite from 8.0.3 to 8.0.5](https://github.com/Azure/k8s-deploy/pull/513)
|
||||
- #509 [Bump the actions group across 1 directory with 4 updates](https://github.com/Azure/k8s-deploy/pull/509)
|
||||
- #510 [Bump the actions group across 1 directory with 2 updates](https://github.com/Azure/k8s-deploy/pull/510)
|
||||
- #511 [Bump vitest from 4.1.1 to 4.1.2](https://github.com/Azure/k8s-deploy/pull/511)
|
||||
- #514 [Bump the actions group with 2 updates](https://github.com/Azure/k8s-deploy/pull/514)
|
||||
- #501 [Bump @types/node from 25.3.3 to 25.4.0](https://github.com/Azure/k8s-deploy/pull/501)
|
||||
|
||||
## [5.1.0] - 2026-03-03
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Deploy manifests action for Kubernetes
|
||||
|
||||
This action is used to deploy manifests to Kubernetes clusters. It requires that the cluster context be set earlier in the workflow by using either the [Azure/aks-set-context](https://github.com/Azure/aks-set-context/tree/releases/v4) action or the [Azure/k8s-set-context](https://github.com/Azure/k8s-set-context/tree/releases/v4) action. It also requires Kubectl to be installed (you can use the [Azure/setup-kubectl](https://github.com/Azure/setup-kubectl) action).
|
||||
This action is used to deploy manifests to Kubernetes clusters. It requires that the cluster context be set earlier in the workflow by using either the [Azure/aks-set-context](https://github.com/Azure/aks-set-context/tree/releases/v1) action or the [Azure/k8s-set-context](https://github.com/Azure/k8s-set-context/tree/releases/v1) action. It also requires Kubectl to be installed (you can use the [Azure/setup-kubectl](https://github.com/Azure/setup-kubectl) action).
|
||||
|
||||
If you are looking to automate your workflows to deploy to [Azure Web Apps](https://azure.microsoft.com/en-us/services/app-service/web/) and [Azure Web App for Containers](https://azure.microsoft.com/en-us/services/app-service/containers/), consider using [`Azure/webapps-deploy`](https://github.com/Azure/webapps-deploy) action.
|
||||
|
||||
@@ -147,25 +147,10 @@ Following are the key capabilities of this action:
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Pinning the action version
|
||||
|
||||
For supply-chain security, GitHub [recommends](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions) pinning third-party actions to a full-length commit SHA rather than a mutable tag. The examples below use `Azure/k8s-deploy@v7` for readability, but the pinned form is preferred in production workflows:
|
||||
|
||||
```yaml
|
||||
# Pinned to a specific commit SHA (recommended)
|
||||
- uses: Azure/k8s-deploy@<full-commit-sha> # v7.0.0
|
||||
with:
|
||||
manifests: |
|
||||
deployment.yaml
|
||||
service.yaml
|
||||
```
|
||||
|
||||
Replace `<full-commit-sha>` with the 40-character commit SHA of the [release](https://github.com/Azure/k8s-deploy/releases) you want to pin to. Dependabot can keep pinned SHAs up to date automatically — see [Keeping your actions up to date with Dependabot](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot).
|
||||
|
||||
### Basic deployment (without any deployment strategy)
|
||||
|
||||
```yaml
|
||||
- uses: Azure/k8s-deploy@v7
|
||||
- uses: Azure/k8s-deploy@v5
|
||||
with:
|
||||
namespace: 'myapp'
|
||||
manifests: |
|
||||
@@ -179,7 +164,7 @@ Replace `<full-commit-sha>` with the 40-character commit SHA of the [release](ht
|
||||
### Private cluster deployment
|
||||
|
||||
```yaml
|
||||
- uses: Azure/k8s-deploy@v7
|
||||
- uses: Azure/k8s-deploy@v5
|
||||
with:
|
||||
resource-group: yourResourceGroup
|
||||
name: yourClusterName
|
||||
@@ -199,7 +184,7 @@ Replace `<full-commit-sha>` with the 40-character commit SHA of the [release](ht
|
||||
### Canary deployment without service mesh
|
||||
|
||||
```yaml
|
||||
- uses: Azure/k8s-deploy@v7
|
||||
- uses: Azure/k8s-deploy@v5
|
||||
with:
|
||||
namespace: 'myapp'
|
||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||
@@ -218,7 +203,7 @@ Replace `<full-commit-sha>` with the 40-character commit SHA of the [release](ht
|
||||
To promote/reject the canary created by the above snippet, the following YAML snippet could be used:
|
||||
|
||||
```yaml
|
||||
- uses: Azure/k8s-deploy@v7
|
||||
- uses: Azure/k8s-deploy@v5
|
||||
with:
|
||||
namespace: 'myapp'
|
||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||
@@ -236,7 +221,7 @@ To promote/reject the canary created by the above snippet, the following YAML sn
|
||||
### Canary deployment based on Service Mesh Interface
|
||||
|
||||
```yaml
|
||||
- uses: Azure/k8s-deploy@v7
|
||||
- uses: Azure/k8s-deploy@v5
|
||||
with:
|
||||
namespace: 'myapp'
|
||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||
@@ -257,7 +242,7 @@ To promote/reject the canary created by the above snippet, the following YAML sn
|
||||
To promote/reject the canary created by the above snippet, the following YAML snippet could be used:
|
||||
|
||||
```yaml
|
||||
- uses: Azure/k8s-deploy@v7
|
||||
- uses: Azure/k8s-deploy@v5
|
||||
with:
|
||||
namespace: 'myapp'
|
||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }} '
|
||||
@@ -276,7 +261,7 @@ To promote/reject the canary created by the above snippet, the following YAML sn
|
||||
### Blue-Green deployment with different route methods
|
||||
|
||||
```yaml
|
||||
- uses: Azure/k8s-deploy@v7
|
||||
- uses: Azure/k8s-deploy@v5
|
||||
with:
|
||||
namespace: 'myapp'
|
||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||
@@ -296,7 +281,7 @@ To promote/reject the canary created by the above snippet, the following YAML sn
|
||||
To promote/reject the green workload created by the above snippet, the following YAML snippet could be used:
|
||||
|
||||
```yaml
|
||||
- uses: Azure/k8s-deploy@v7
|
||||
- uses: Azure/k8s-deploy@v5
|
||||
with:
|
||||
namespace: 'myapp'
|
||||
images: 'contoso.azurecr.io/myapp:${{ event.run_id }}'
|
||||
@@ -325,9 +310,9 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: Azure/docker-login@v2
|
||||
- uses: Azure/docker-login@v1
|
||||
with:
|
||||
login-server: contoso.azurecr.io
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
@@ -346,14 +331,14 @@ jobs:
|
||||
cluster-name: contoso
|
||||
resource-group: contoso-rg
|
||||
|
||||
- uses: Azure/k8s-create-secret@v5
|
||||
- uses: Azure/k8s-create-secret@v4
|
||||
with:
|
||||
container-registry-url: contoso.azurecr.io
|
||||
container-registry-username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
container-registry-password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
secret-name: demo-k8s-secret
|
||||
|
||||
- uses: Azure/k8s-deploy@v7
|
||||
- uses: Azure/k8s-deploy@v5
|
||||
with:
|
||||
action: deploy
|
||||
manifests: |
|
||||
@@ -374,9 +359,9 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: Azure/docker-login@v2
|
||||
- uses: Azure/docker-login@v1
|
||||
with:
|
||||
login-server: contoso.azurecr.io
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
@@ -392,14 +377,14 @@ jobs:
|
||||
with:
|
||||
kubeconfig: ${{ secrets.KUBE_CONFIG }}
|
||||
|
||||
- uses: Azure/k8s-create-secret@v5
|
||||
- uses: Azure/k8s-create-secret@v4
|
||||
with:
|
||||
container-registry-url: contoso.azurecr.io
|
||||
container-registry-username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
container-registry-password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
secret-name: demo-k8s-secret
|
||||
|
||||
- uses: Azure/k8s-deploy@v7
|
||||
- uses: Azure/k8s-deploy@v4
|
||||
with:
|
||||
action: deploy
|
||||
manifests: |
|
||||
@@ -424,9 +409,9 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: Azure/docker-login@v2
|
||||
- uses: Azure/docker-login@v1
|
||||
with:
|
||||
login-server: contoso.azurecr.io
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
@@ -448,9 +433,9 @@ jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- uses: Azure/docker-login@v2
|
||||
- uses: Azure/docker-login@v1
|
||||
with:
|
||||
login-server: contoso.azurecr.io
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
@@ -465,7 +450,7 @@ jobs:
|
||||
cluster-name: contoso
|
||||
resource-group: contoso-rg
|
||||
|
||||
- uses: Azure/k8s-create-secret@v5
|
||||
- uses: Azure/k8s-create-secret@v4
|
||||
with:
|
||||
namespace: ${{ env.NAMESPACE }}
|
||||
container-registry-url: contoso.azurecr.io
|
||||
@@ -483,7 +468,7 @@ jobs:
|
||||
helm-version: 'latest'
|
||||
id: bake
|
||||
|
||||
- uses: Azure/k8s-deploy@v7
|
||||
- uses: Azure/k8s-deploy@v5
|
||||
with:
|
||||
action: deploy
|
||||
manifests: ${{ steps.bake.outputs.manifestsBundle }}
|
||||
|
||||
+1
-1
@@ -96,5 +96,5 @@ inputs:
|
||||
branding:
|
||||
color: 'green'
|
||||
runs:
|
||||
using: 'node24'
|
||||
using: 'node20'
|
||||
main: 'lib/index.js'
|
||||
|
||||
+30274
File diff suppressed because one or more lines are too long
Generated
+527
-1215
File diff suppressed because it is too large
Load Diff
+7
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "k8s-deploy-action",
|
||||
"version": "7.0.0",
|
||||
"version": "5.1.0",
|
||||
"author": "Deepak Sattiraju",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
@@ -14,23 +14,23 @@
|
||||
"prepare": "husky"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^3.0.1",
|
||||
"@actions/core": "^3.0.0",
|
||||
"@actions/exec": "^3.0.0",
|
||||
"@actions/io": "^3.0.2",
|
||||
"@actions/tool-cache": "4.0.0",
|
||||
"@octokit/core": "^7.0.6",
|
||||
"@octokit/plugin-retry": "^8.1.0",
|
||||
"js-yaml": "5.2.2",
|
||||
"js-yaml": "4.1.1",
|
||||
"minimist": "^1.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/node": "^26.1.1",
|
||||
"esbuild": "^0.28",
|
||||
"@types/node": "^25.3.3",
|
||||
"esbuild": "^0.27",
|
||||
"husky": "^9.1.7",
|
||||
"prettier": "^3.9.5",
|
||||
"typescript": "7.0.2",
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "5.9.3",
|
||||
"vitest": "^4"
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -31,4 +31,5 @@ export function parseResourceTypeInput(rawInput: string): ClusterType {
|
||||
)
|
||||
}
|
||||
export type ClusterType =
|
||||
typeof ResourceTypeManagedCluster | typeof ResourceTypeFleet
|
||||
| typeof ResourceTypeManagedCluster
|
||||
| typeof ResourceTypeFleet
|
||||
|
||||
@@ -635,10 +635,11 @@ describe('Kubectl class', () => {
|
||||
it('gets new replica sets', async () => {
|
||||
const kubectl = new Kubectl(kubectlPath, testNamespace)
|
||||
|
||||
const name = 'my-app-6d4cf56db6'
|
||||
const newReplicaSetName = 'newreplicaset'
|
||||
const name = 'name'
|
||||
const describeReturn = {
|
||||
exitCode: 0,
|
||||
stdout: `NewReplicaSet: ${name} (1/1 replicas created)`,
|
||||
stdout: newReplicaSetName + name + ' ' + 'extra',
|
||||
stderr: ''
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ export class Kubectl {
|
||||
const stdout = result.stdout.split('\n')
|
||||
core.debug('stdout from getNewReplicaSet is ' + JSON.stringify(stdout))
|
||||
stdout.forEach((line: string) => {
|
||||
const newreplicaset = 'newreplicaset:'
|
||||
const newreplicaset = 'newreplicaset'
|
||||
if (line && line.toLowerCase().indexOf(newreplicaset) > -1) {
|
||||
core.debug(
|
||||
`found string of interest for replicaset, line is ${line}`
|
||||
|
||||
+11
-361
@@ -3,16 +3,12 @@ import * as fileUtils from './fileUtils.js'
|
||||
|
||||
import * as yaml from 'js-yaml'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import * as path from 'path'
|
||||
import {K8sObject} from '../types/k8sObject.js'
|
||||
|
||||
const sampleYamlUrl =
|
||||
'https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/controllers/nginx-deployment.yaml'
|
||||
describe('File utils', () => {
|
||||
beforeAll(() => {
|
||||
process.env.GITHUB_WORKSPACE ??= process.cwd()
|
||||
})
|
||||
test('correctly parses a yaml file from a URL', async () => {
|
||||
const tempFile = await fileUtils.writeYamlFromURLToFile(sampleYamlUrl, 0)
|
||||
const fileContents = fs.readFileSync(tempFile).toString()
|
||||
@@ -57,7 +53,7 @@ describe('File utils', () => {
|
||||
expect(testSearch).toHaveLength(10)
|
||||
expectedManifests.forEach((fileName) => {
|
||||
if (fileName.startsWith('test/unit')) {
|
||||
expect(testSearch).toContain(path.resolve(fileName))
|
||||
expect(testSearch).toContain(fileName)
|
||||
} else {
|
||||
expect(fileName.includes(fileUtils.urlFileKind)).toBe(true)
|
||||
expect(fileName.startsWith(fileUtils.getTempDirectory()))
|
||||
@@ -109,366 +105,20 @@ describe('File utils', () => {
|
||||
fileUtils.writeYamlFromURLToFile(badUrl, 0)
|
||||
).rejects.toBeTruthy()
|
||||
})
|
||||
|
||||
it('rejects manifest inputs that resolve outside the workspace', async () => {
|
||||
const originalWs = process.env.GITHUB_WORKSPACE
|
||||
const ws = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-'))
|
||||
const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'outside-'))
|
||||
fs.writeFileSync(path.join(outside, 'secrets.yaml'), 'api_key: x')
|
||||
process.env.GITHUB_WORKSPACE = ws
|
||||
try {
|
||||
await expect(
|
||||
fileUtils.getFilesFromDirectoriesAndURLs([outside])
|
||||
).rejects.toThrow(/outside the workspace/)
|
||||
await expect(
|
||||
fileUtils.getFilesFromDirectoriesAndURLs([
|
||||
path.join(outside, 'secrets.yaml')
|
||||
])
|
||||
).rejects.toThrow(/outside the workspace/)
|
||||
} finally {
|
||||
if (originalWs === undefined) delete process.env.GITHUB_WORKSPACE
|
||||
else process.env.GITHUB_WORKSPACE = originalWs
|
||||
fs.rmSync(ws, {recursive: true, force: true})
|
||||
fs.rmSync(outside, {recursive: true, force: true})
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects symlinks inside a directory that escape the workspace', async () => {
|
||||
const originalWs = process.env.GITHUB_WORKSPACE
|
||||
const ws = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-'))
|
||||
const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'outside-'))
|
||||
const escapeTarget = path.join(outside, 'passwd.yaml')
|
||||
fs.writeFileSync(escapeTarget, 'root:x:0:0')
|
||||
const dir = path.join(ws, 'manifests')
|
||||
fs.mkdirSync(dir)
|
||||
fs.symlinkSync(escapeTarget, path.join(dir, 'escape.yaml'))
|
||||
process.env.GITHUB_WORKSPACE = ws
|
||||
try {
|
||||
await expect(
|
||||
fileUtils.getFilesFromDirectoriesAndURLs([dir])
|
||||
).rejects.toThrow(/outside the workspace/)
|
||||
} finally {
|
||||
if (originalWs === undefined) delete process.env.GITHUB_WORKSPACE
|
||||
else process.env.GITHUB_WORKSPACE = originalWs
|
||||
fs.rmSync(ws, {recursive: true, force: true})
|
||||
fs.rmSync(outside, {recursive: true, force: true})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('moveFileToTmpDir', () => {
|
||||
let workspace: string
|
||||
let originalWorkspace: string | undefined
|
||||
let originalTemp: string | undefined
|
||||
let originalCwd: string
|
||||
let tmpDir: string
|
||||
describe('moving files to temp', () => {
|
||||
it('correctly moves the contents of a file to the temporary directory', () => {
|
||||
vi.spyOn(fs, 'writeFileSync').mockImplementation(() => {})
|
||||
vi.spyOn(fs, 'readFileSync').mockImplementation((filename) => {
|
||||
return 'test contents'
|
||||
})
|
||||
const originalFilePath = path.join('path', 'in', 'repo')
|
||||
|
||||
beforeEach(() => {
|
||||
originalWorkspace = process.env.GITHUB_WORKSPACE
|
||||
originalTemp = process.env.RUNNER_TEMP
|
||||
originalCwd = process.cwd()
|
||||
workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-'))
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rt-'))
|
||||
process.env.GITHUB_WORKSPACE = workspace
|
||||
process.env.RUNNER_TEMP = tmpDir
|
||||
})
|
||||
const output = fileUtils.moveFileToTmpDir(originalFilePath)
|
||||
|
||||
afterEach(() => {
|
||||
process.chdir(originalCwd)
|
||||
if (originalWorkspace === undefined) delete process.env.GITHUB_WORKSPACE
|
||||
else process.env.GITHUB_WORKSPACE = originalWorkspace
|
||||
if (originalTemp === undefined) delete process.env.RUNNER_TEMP
|
||||
else process.env.RUNNER_TEMP = originalTemp
|
||||
fs.rmSync(workspace, {recursive: true, force: true})
|
||||
fs.rmSync(tmpDir, {recursive: true, force: true})
|
||||
})
|
||||
|
||||
it('copies a workspace file to RUNNER_TEMP using a basename-only destination', () => {
|
||||
const src = path.join(workspace, 'svc.yaml')
|
||||
fs.writeFileSync(src, 'kind: Service')
|
||||
|
||||
const out = fileUtils.moveFileToTmpDir(src)
|
||||
|
||||
expect(fs.realpathSync(path.dirname(out))).toBe(fs.realpathSync(tmpDir))
|
||||
expect(path.basename(out)).toMatch(/^svc_\d+_\d+\.yaml$/)
|
||||
expect(fs.readFileSync(out).toString()).toBe('kind: Service')
|
||||
})
|
||||
|
||||
it('rejects relative traversal that escapes the workspace', () => {
|
||||
const outside = fs.mkdtempSync(path.join(os.tmpdir(), 'outside-'))
|
||||
fs.writeFileSync(path.join(outside, 'secrets.yaml'), 'api_key: x')
|
||||
process.chdir(workspace)
|
||||
const rel = path.relative(workspace, path.join(outside, 'secrets.yaml'))
|
||||
expect(() => fileUtils.moveFileToTmpDir(rel)).toThrow(
|
||||
/outside the workspace/
|
||||
expect(output).toEqual(
|
||||
path.join(fileUtils.getTempDirectory(), '/path/in/repo')
|
||||
)
|
||||
fs.rmSync(outside, {recursive: true, force: true})
|
||||
})
|
||||
|
||||
it('does not collide when two inputs share a basename', () => {
|
||||
const a = path.join(workspace, 'a')
|
||||
const b = path.join(workspace, 'b')
|
||||
fs.mkdirSync(a)
|
||||
fs.mkdirSync(b)
|
||||
fs.writeFileSync(path.join(a, 'svc.yaml'), 'A')
|
||||
fs.writeFileSync(path.join(b, 'svc.yaml'), 'B')
|
||||
|
||||
const outA = fileUtils.moveFileToTmpDir(path.join(a, 'svc.yaml'))
|
||||
const outB = fileUtils.moveFileToTmpDir(path.join(b, 'svc.yaml'))
|
||||
|
||||
expect(outA).not.toBe(outB)
|
||||
expect(fs.readFileSync(outA).toString()).toBe('A')
|
||||
expect(fs.readFileSync(outB).toString()).toBe('B')
|
||||
})
|
||||
})
|
||||
|
||||
describe('assertPathWithinWorkspace', () => {
|
||||
let workspace: string
|
||||
let outside: string
|
||||
let originalWorkspace: string | undefined
|
||||
let originalCwd: string
|
||||
|
||||
beforeEach(() => {
|
||||
originalWorkspace = process.env.GITHUB_WORKSPACE
|
||||
originalCwd = process.cwd()
|
||||
workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-'))
|
||||
outside = fs.mkdtempSync(path.join(os.tmpdir(), 'outside-'))
|
||||
process.env.GITHUB_WORKSPACE = workspace
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
process.chdir(originalCwd)
|
||||
if (originalWorkspace === undefined) {
|
||||
delete process.env.GITHUB_WORKSPACE
|
||||
} else {
|
||||
process.env.GITHUB_WORKSPACE = originalWorkspace
|
||||
}
|
||||
fs.rmSync(workspace, {recursive: true, force: true})
|
||||
fs.rmSync(outside, {recursive: true, force: true})
|
||||
})
|
||||
|
||||
it('returns the resolved path for files inside the workspace', () => {
|
||||
const inside = path.join(workspace, 'a.yaml')
|
||||
fs.writeFileSync(inside, 'kind: X')
|
||||
const result = fileUtils.assertPathWithinWorkspace(inside)
|
||||
expect(result).toBe(fs.realpathSync(inside))
|
||||
})
|
||||
|
||||
it('accepts workspace files whose basename starts with ..', () => {
|
||||
const inside = path.join(workspace, '..bar.yaml')
|
||||
fs.writeFileSync(inside, 'kind: X')
|
||||
expect(fileUtils.assertPathWithinWorkspace(inside)).toBe(
|
||||
fs.realpathSync(inside)
|
||||
)
|
||||
})
|
||||
|
||||
it('throws for relative traversal paths that escape the workspace', () => {
|
||||
const target = path.join(outside, 'secrets.yaml')
|
||||
fs.writeFileSync(target, 'api_key: secret')
|
||||
const rel = path.relative(workspace, target)
|
||||
process.chdir(workspace)
|
||||
expect(() => fileUtils.assertPathWithinWorkspace(rel)).toThrow(
|
||||
/outside the workspace/
|
||||
)
|
||||
})
|
||||
|
||||
it('resolves relative paths against GITHUB_WORKSPACE even when CWD differs', () => {
|
||||
const inside = path.join(workspace, 'manifest.yaml')
|
||||
fs.writeFileSync(inside, 'kind: X')
|
||||
// Deliberately chdir somewhere unrelated so a process.cwd()-based
|
||||
// resolver would either reject or resolve to the wrong place.
|
||||
process.chdir(os.tmpdir())
|
||||
const result = fileUtils.assertPathWithinWorkspace('manifest.yaml')
|
||||
expect(result).toBe(fs.realpathSync(inside))
|
||||
})
|
||||
|
||||
it('throws for absolute paths outside the workspace', () => {
|
||||
const target = path.join(outside, 'secrets.yaml')
|
||||
fs.writeFileSync(target, 'api_key: secret')
|
||||
expect(() => fileUtils.assertPathWithinWorkspace(target)).toThrow(
|
||||
/outside the workspace/
|
||||
)
|
||||
})
|
||||
|
||||
it('throws when a symlink inside the workspace points outside', () => {
|
||||
const target = path.join(outside, 'secrets.yaml')
|
||||
fs.writeFileSync(target, 'api_key: secret')
|
||||
const link = path.join(workspace, 'evil.yaml')
|
||||
fs.symlinkSync(target, link)
|
||||
expect(() => fileUtils.assertPathWithinWorkspace(link)).toThrow(
|
||||
/outside the workspace/
|
||||
)
|
||||
})
|
||||
|
||||
it('throws a clear error for missing files', () => {
|
||||
const missing = path.join(workspace, 'nope.yaml')
|
||||
expect(() => fileUtils.assertPathWithinWorkspace(missing)).toThrow(
|
||||
/does not exist or is not readable/
|
||||
)
|
||||
})
|
||||
|
||||
it('skips containment when GITHUB_WORKSPACE is unset', () => {
|
||||
delete process.env.GITHUB_WORKSPACE
|
||||
const target = path.join(outside, 'whatever.yaml')
|
||||
fs.writeFileSync(target, 'kind: X')
|
||||
expect(fileUtils.assertPathWithinWorkspace(target)).toBe(target)
|
||||
})
|
||||
})
|
||||
|
||||
import {EventEmitter} from 'node:events'
|
||||
import {PassThrough} from 'node:stream'
|
||||
import * as https from 'node:https'
|
||||
|
||||
const httpsState = vi.hoisted(() => ({impl: null as any}))
|
||||
|
||||
vi.mock('https', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('https')>()
|
||||
const get = (...args: any[]) =>
|
||||
httpsState.impl ? httpsState.impl(...args) : (actual.get as any)(...args)
|
||||
return {
|
||||
...actual,
|
||||
default: {...actual, get},
|
||||
get
|
||||
}
|
||||
})
|
||||
|
||||
describe('writeYamlFromURLToFile error handling', () => {
|
||||
let tempDir: string
|
||||
let originalRunnerTemp: string | undefined
|
||||
|
||||
beforeEach(() => {
|
||||
originalRunnerTemp = process.env.RUNNER_TEMP
|
||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'url-fetch-'))
|
||||
process.env.RUNNER_TEMP = tempDir
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
httpsState.impl = null
|
||||
vi.restoreAllMocks()
|
||||
if (originalRunnerTemp === undefined) {
|
||||
delete process.env.RUNNER_TEMP
|
||||
} else {
|
||||
process.env.RUNNER_TEMP = originalRunnerTemp
|
||||
}
|
||||
fs.rmSync(tempDir, {recursive: true, force: true})
|
||||
})
|
||||
|
||||
// Wait one tick so cleanupAndReject's async fs.rm callback can fire
|
||||
// before the test inspects the temp directory.
|
||||
const waitForCleanup = () =>
|
||||
new Promise<void>((r) => setImmediate(() => setImmediate(r)))
|
||||
|
||||
function mockHttpsGet(
|
||||
makeResponse: () => {
|
||||
response: EventEmitter & {
|
||||
statusCode?: number
|
||||
statusMessage?: string
|
||||
pipe: PassThrough['pipe']
|
||||
resume: () => void
|
||||
}
|
||||
requestEmitter: EventEmitter
|
||||
}
|
||||
) {
|
||||
httpsState.impl = ((url: string, cb?: any) => {
|
||||
const {response, requestEmitter} = makeResponse()
|
||||
if (cb) setImmediate(() => cb(response))
|
||||
return requestEmitter as any
|
||||
}) as any
|
||||
}
|
||||
|
||||
it('rejects on HTTP 500 without writing a file', async () => {
|
||||
const requestEmitter = new EventEmitter()
|
||||
const response = Object.assign(new PassThrough(), {
|
||||
statusCode: 500,
|
||||
statusMessage: 'Server Error',
|
||||
resume() {
|
||||
/* drain */
|
||||
}
|
||||
})
|
||||
mockHttpsGet(() => ({response: response as any, requestEmitter}))
|
||||
|
||||
await expect(
|
||||
fileUtils.writeYamlFromURLToFile('https://example.com/x.yaml', 99)
|
||||
).rejects.toThrow(/Server Error/)
|
||||
})
|
||||
|
||||
it('rejects when the response stream errors mid-download', async () => {
|
||||
const requestEmitter = new EventEmitter()
|
||||
const response = Object.assign(new PassThrough(), {
|
||||
statusCode: 200,
|
||||
statusMessage: 'OK',
|
||||
resume() {}
|
||||
})
|
||||
mockHttpsGet(() => ({response: response as any, requestEmitter}))
|
||||
|
||||
const p = fileUtils.writeYamlFromURLToFile(
|
||||
'https://example.com/y.yaml',
|
||||
100
|
||||
)
|
||||
setImmediate(() => response.emit('error', new Error('socket reset')))
|
||||
await expect(p).rejects.toThrow(/socket reset/)
|
||||
})
|
||||
|
||||
it('rejects on request-level errors', async () => {
|
||||
const requestEmitter = new EventEmitter()
|
||||
const response = Object.assign(new PassThrough(), {
|
||||
statusCode: 200,
|
||||
resume() {}
|
||||
})
|
||||
mockHttpsGet(() => ({response: response as any, requestEmitter}))
|
||||
|
||||
const p = fileUtils.writeYamlFromURLToFile(
|
||||
'https://example.com/z.yaml',
|
||||
101
|
||||
)
|
||||
setImmediate(() => requestEmitter.emit('error', new Error('DNS failure')))
|
||||
await expect(p).rejects.toThrow(/DNS failure/)
|
||||
})
|
||||
|
||||
it('removes temp file when verification fails', async () => {
|
||||
const requestEmitter = new EventEmitter()
|
||||
const response = Object.assign(new PassThrough(), {
|
||||
statusCode: 200,
|
||||
statusMessage: 'OK'
|
||||
})
|
||||
mockHttpsGet(() => ({response: response as any, requestEmitter}))
|
||||
|
||||
const before = new Set(fs.readdirSync(tempDir))
|
||||
const p = fileUtils.writeYamlFromURLToFile(
|
||||
'https://example.com/bad.yaml',
|
||||
200
|
||||
)
|
||||
// Stream a YAML document missing required k8s fields so verifyYaml fails.
|
||||
setImmediate(() => {
|
||||
response.end('not: a-real-manifest\n')
|
||||
})
|
||||
await expect(p).rejects.toThrow(/missing fields|failed to parse/)
|
||||
await waitForCleanup()
|
||||
const after = fs.readdirSync(tempDir).filter((f) => !before.has(f))
|
||||
expect(after).toEqual([])
|
||||
})
|
||||
|
||||
it('removes temp file on mid-stream response error', async () => {
|
||||
const requestEmitter = new EventEmitter()
|
||||
const response = Object.assign(new PassThrough(), {
|
||||
statusCode: 200,
|
||||
statusMessage: 'OK',
|
||||
resume() {}
|
||||
})
|
||||
mockHttpsGet(() => ({response: response as any, requestEmitter}))
|
||||
|
||||
const before = new Set(fs.readdirSync(tempDir))
|
||||
const p = fileUtils.writeYamlFromURLToFile(
|
||||
'https://example.com/midstream.yaml',
|
||||
201
|
||||
)
|
||||
setImmediate(() => {
|
||||
response.write('kind: Foo\n')
|
||||
response.emit('error', new Error('socket reset'))
|
||||
})
|
||||
await expect(p).rejects.toThrow(/socket reset/)
|
||||
await waitForCleanup()
|
||||
const after = fs.readdirSync(tempDir).filter((f) => !before.has(f))
|
||||
expect(after).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
+28
-87
@@ -11,56 +11,10 @@ import {K8sObject} from '../types/k8sObject.js'
|
||||
|
||||
export const urlFileKind = 'urlfile'
|
||||
|
||||
let moveCounter = 0
|
||||
|
||||
export function getTempDirectory(): string {
|
||||
return process.env['RUNNER_TEMP'] || os.tmpdir()
|
||||
}
|
||||
|
||||
// Exported for tests. Validates that `inputPath` resolves (after symlink
|
||||
// resolution) to a location inside GITHUB_WORKSPACE. When GITHUB_WORKSPACE
|
||||
// is not set (e.g. local dev / unit tests), the check is skipped — callers
|
||||
// that write to RUNNER_TEMP still get protection from basename-only
|
||||
// destinations.
|
||||
export function assertPathWithinWorkspace(inputPath: string): string {
|
||||
const workspace = process.env.GITHUB_WORKSPACE
|
||||
if (!workspace) {
|
||||
core.warning(
|
||||
'GITHUB_WORKSPACE is not set; skipping manifest path containment check'
|
||||
)
|
||||
return inputPath
|
||||
}
|
||||
const resolvedWorkspace = fs.realpathSync(path.resolve(workspace))
|
||||
// Resolve relative inputs against the workspace (not process.cwd()), so
|
||||
// a relative `manifests:` input is interpreted consistently regardless of
|
||||
// whether a prior step changed the working directory. Absolute paths are
|
||||
// passed through unchanged and still validated below.
|
||||
const absoluteInput = path.isAbsolute(inputPath)
|
||||
? inputPath
|
||||
: path.resolve(resolvedWorkspace, inputPath)
|
||||
let resolvedInput: string
|
||||
try {
|
||||
resolvedInput = fs.realpathSync(absoluteInput)
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`manifest path ${inputPath} does not exist or is not readable: ${e}`
|
||||
)
|
||||
}
|
||||
const rel = path.relative(resolvedWorkspace, resolvedInput)
|
||||
if (
|
||||
rel === '' ||
|
||||
(rel !== '..' &&
|
||||
!rel.startsWith('..' + path.sep) &&
|
||||
!path.isAbsolute(rel))
|
||||
) {
|
||||
return resolvedInput
|
||||
}
|
||||
throw new Error(
|
||||
`manifest path ${inputPath} resolves to ${resolvedInput}, ` +
|
||||
`which is outside the workspace ${resolvedWorkspace}`
|
||||
)
|
||||
}
|
||||
|
||||
export function writeObjectsToFile(inputObjects: any[]): string[] {
|
||||
const newFilePaths = []
|
||||
|
||||
@@ -110,20 +64,22 @@ export function writeManifestToFile(
|
||||
}
|
||||
|
||||
export function moveFileToTmpDir(originalFilepath: string) {
|
||||
const safeSource = assertPathWithinWorkspace(originalFilepath)
|
||||
const tempDirectory = getTempDirectory()
|
||||
const ext = path.extname(safeSource)
|
||||
const base = path.basename(safeSource, ext)
|
||||
const uniqueName = `${base}_${getCurrentTime()}_${moveCounter++}${ext}`
|
||||
const newPath = path.join(tempDirectory, uniqueName)
|
||||
const newPath = path.join(tempDirectory, originalFilepath)
|
||||
|
||||
core.debug(`reading original contents from path: ${originalFilepath}`)
|
||||
const contents = fs.readFileSync(safeSource)
|
||||
const contents = fs.readFileSync(originalFilepath).toString()
|
||||
|
||||
const dirName = path.dirname(newPath)
|
||||
if (!fs.existsSync(dirName)) {
|
||||
core.debug(`path ${dirName} doesn't exist yet, making new dir...`)
|
||||
fs.mkdirSync(dirName, {recursive: true})
|
||||
}
|
||||
core.debug(`writing contents to new path ${newPath}`)
|
||||
fs.writeFileSync(newPath, contents)
|
||||
fs.writeFileSync(path.join(newPath), contents)
|
||||
|
||||
core.debug(`moved contents from ${originalFilepath} to ${newPath}`)
|
||||
|
||||
return newPath
|
||||
}
|
||||
|
||||
@@ -153,20 +109,15 @@ export async function getFilesFromDirectoriesAndURLs(
|
||||
`encountered error trying to pull YAML from URL ${fileName}: ${e}`
|
||||
)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
const safePath = assertPathWithinWorkspace(fileName)
|
||||
|
||||
if (fs.lstatSync(safePath).isDirectory()) {
|
||||
recurisveManifestGetter(safePath).forEach((file) => {
|
||||
} else if (fs.lstatSync(fileName).isDirectory()) {
|
||||
recurisveManifestGetter(fileName).forEach((file) => {
|
||||
fullPathSet.add(file)
|
||||
})
|
||||
} else if (
|
||||
getFileExtension(safePath) === 'yml' ||
|
||||
getFileExtension(safePath) === 'yaml'
|
||||
getFileExtension(fileName) === 'yml' ||
|
||||
getFileExtension(fileName) === 'yaml'
|
||||
) {
|
||||
fullPathSet.add(safePath)
|
||||
fullPathSet.add(fileName)
|
||||
} else {
|
||||
core.debug(
|
||||
`Detected non-manifest file, ${fileName}, continuing... `
|
||||
@@ -189,33 +140,24 @@ export async function writeYamlFromURLToFile(
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
https
|
||||
.get(url, (response) => {
|
||||
.get(url, async (response) => {
|
||||
const code = response.statusCode ?? 0
|
||||
if (code >= 400) {
|
||||
response.resume()
|
||||
reject(
|
||||
new Error(
|
||||
Error(
|
||||
`received response status ${response.statusMessage} from url ${url}`
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const targetPath = getNewTempManifestFileName(
|
||||
urlFileKind,
|
||||
fileNumber.toString()
|
||||
)
|
||||
// Once the write stream is created the file exists on disk;
|
||||
// route all post-stream rejections through this helper so we
|
||||
// don't leave truncated YAML in RUNNER_TEMP for later tooling
|
||||
// to pick up. Do NOT unlink on the success path.
|
||||
const cleanupAndReject = (err: unknown) => {
|
||||
fs.rm(targetPath, {force: true}, () => reject(err))
|
||||
}
|
||||
const fileWriter = fs.createWriteStream(targetPath)
|
||||
fileWriter.on('error', cleanupAndReject)
|
||||
fileWriter.on('finish', () => {
|
||||
try {
|
||||
// save the file to disk
|
||||
const fileWriter = fs
|
||||
.createWriteStream(targetPath)
|
||||
.on('finish', () => {
|
||||
const verification = verifyYaml(targetPath, url)
|
||||
if (succeeded(verification)) {
|
||||
core.debug(
|
||||
@@ -225,16 +167,15 @@ export async function writeYamlFromURLToFile(
|
||||
)
|
||||
resolve(targetPath)
|
||||
} else {
|
||||
cleanupAndReject(new Error(verification.error))
|
||||
reject(verification.error)
|
||||
}
|
||||
} catch (e) {
|
||||
cleanupAndReject(e)
|
||||
}
|
||||
})
|
||||
response.on('error', cleanupAndReject)
|
||||
})
|
||||
|
||||
response.pipe(fileWriter)
|
||||
})
|
||||
.on('error', reject)
|
||||
.on('error', (error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -258,7 +199,7 @@ function verifyYaml(filepath: string, url: string): Errorable<K8sObject[]> {
|
||||
}
|
||||
|
||||
for (const obj of inputObjects) {
|
||||
if (obj == null || !obj.kind || !obj.apiVersion || !obj.metadata) {
|
||||
if (!obj.kind || !obj.apiVersion || !obj.metadata) {
|
||||
return {
|
||||
succeeded: false,
|
||||
error: `failed to parse manifest from ${url}: missing fields`
|
||||
@@ -280,7 +221,7 @@ function recurisveManifestGetter(dirName: string): string[] {
|
||||
getFileExtension(fileName) === 'yml' ||
|
||||
getFileExtension(fileName) === 'yaml'
|
||||
) {
|
||||
toRet.push(assertPathWithinWorkspace(fnwd))
|
||||
toRet.push(path.join(dirName, fileName))
|
||||
} else {
|
||||
core.debug(`Detected non-manifest file, ${fileName}, continuing... `)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user