mirror of https://github.com/actions/cache.git
Merge pull request #1 from Cula-Technologies/gcs-cache-support
Add GCS cache backend with GitHub fallbackpull/1786/head
commit
7c01473a9b
@ -1 +0,0 @@
|
|||||||
* @actions/actions-cache
|
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
name: Auto-tag main
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tag:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Force-update v1 tag
|
||||||
|
run: |
|
||||||
|
git tag -f v1
|
||||||
|
git push -f origin v1
|
||||||
@ -1,23 +0,0 @@
|
|||||||
name: Close inactive issues
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "30 8 * * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
close-issues:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/stale@v9
|
|
||||||
with:
|
|
||||||
days-before-issue-stale: 200
|
|
||||||
days-before-issue-close: 5
|
|
||||||
stale-issue-label: "stale"
|
|
||||||
stale-issue-message: "This issue is stale because it has been open for 200 days with no activity. Leave a comment to avoid closing this issue in 5 days."
|
|
||||||
close-issue-message: "This issue was closed because it has been inactive for 5 days since being marked as stale."
|
|
||||||
days-before-pr-stale: -1
|
|
||||||
days-before-pr-close: -1
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
name: Assign issue
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
run-action:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Get current oncall
|
|
||||||
id: oncall
|
|
||||||
run: |
|
|
||||||
echo "CURRENT=$(curl --request GET 'https://api.pagerduty.com/oncalls?include[]=users&schedule_ids[]=P5VG2BX&earliest=true' --header 'Authorization: Token token=${{ secrets.PAGERDUTY_TOKEN }}' --header 'Accept: application/vnd.pagerduty+json;version=2' --header 'Content-Type: application/json' | jq -r '.oncalls[].user.name')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: add_assignees
|
|
||||||
run: |
|
|
||||||
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN}}" https://api.github.com/repos/${{github.repository}}/issues/${{ github.event.issue.number}}/assignees -d '{"assignees":["${{steps.oncall.outputs.CURRENT}}"]}'
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
name: License check
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
validate-cached-dependency-records:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Check licenses
|
|
||||||
steps:
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm ci --ignore-scripts
|
|
||||||
|
|
||||||
- name: Set up Ruby
|
|
||||||
uses: ruby/setup-ruby@v1
|
|
||||||
with:
|
|
||||||
ruby-version: '3.1.7'
|
|
||||||
|
|
||||||
- name: Install licensed tool
|
|
||||||
run: |
|
|
||||||
cd "$RUNNER_TEMP"
|
|
||||||
curl -Lfs -o licensed.tar.gz https://github.com/licensee/licensed/archive/refs/tags/v5.0.4.tar.gz
|
|
||||||
tar -xzf licensed.tar.gz
|
|
||||||
cd licensed-5.0.4
|
|
||||||
bundle install
|
|
||||||
|
|
||||||
- name: Check cached dependency records
|
|
||||||
run: |
|
|
||||||
cd ${{ github.workspace }}
|
|
||||||
BUNDLE_GEMFILE=$RUNNER_TEMP/licensed-5.0.4/Gemfile bundle exec $RUNNER_TEMP/licensed-5.0.4/exe/licensed status
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
name: Assign pull request reviewer
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
run-action:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Get current oncall
|
|
||||||
id: oncall
|
|
||||||
run: |
|
|
||||||
echo "CURRENT=$(curl --request GET 'https://api.pagerduty.com/oncalls?include[]=users&schedule_ids[]=P5VG2BX&earliest=true' --header 'Authorization: Token token=${{ secrets.PAGERDUTY_TOKEN }}' --header 'Accept: application/vnd.pagerduty+json;version=2' --header 'Content-Type: application/json' | jq -r '.oncalls[].user.name')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Request Review
|
|
||||||
run: |
|
|
||||||
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN}}" https://api.github.com/repos/${{github.repository}}/pulls/${{ github.event.pull_request.number}}/requested_reviewers -d '{"reviewers":["${{steps.oncall.outputs.CURRENT}}"]}'
|
|
||||||
|
|
||||||
- name: Add Assignee
|
|
||||||
run: |
|
|
||||||
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN}}" https://api.github.com/repos/${{github.repository}}/issues/${{ github.event.pull_request.number}}/assignees -d '{"assignees":["${{steps.oncall.outputs.CURRENT}}"]}'
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
name: Publish immutable action
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [released]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
id-token: write
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checking out
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
- name: Publish
|
|
||||||
id: publish
|
|
||||||
uses: actions/publish-immutable-action@0.0.3
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
name: Release new action version
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [released]
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
TAG_NAME:
|
|
||||||
description: 'Tag name that the major tag will point to'
|
|
||||||
required: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
TAG_NAME: ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }}
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update_tag:
|
|
||||||
name: Update the major tag to include the ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }} changes
|
|
||||||
environment:
|
|
||||||
name: releaseNewActionVersion
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Update the ${{ env.TAG_NAME }} tag
|
|
||||||
id: update-major-tag
|
|
||||||
uses: actions/publish-action@v0.3.0
|
|
||||||
with:
|
|
||||||
source-tag: ${{ env.TAG_NAME }}
|
|
||||||
slack-webhook: ${{ secrets.SLACK_WEBHOOK }}
|
|
||||||
@ -1,276 +0,0 @@
|
|||||||
name: Tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- releases/**
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- releases/**
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
# Build and unit test
|
|
||||||
build:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
|
||||||
fail-fast: false
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
- name: Setup Node.js 24.x
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 24.x
|
|
||||||
cache: npm
|
|
||||||
- run: npm ci
|
|
||||||
- name: Prettier Format Check
|
|
||||||
run: npm run format-check
|
|
||||||
- name: ESLint Check
|
|
||||||
run: npm run lint
|
|
||||||
- name: Build & Test
|
|
||||||
run: npm run test
|
|
||||||
|
|
||||||
# End to end save and restore
|
|
||||||
test-save:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
|
||||||
fail-fast: false
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
- name: Generate files in working directory
|
|
||||||
shell: bash
|
|
||||||
run: __tests__/create-cache-files.sh ${{ runner.os }} test-cache
|
|
||||||
- name: Generate files outside working directory
|
|
||||||
shell: bash
|
|
||||||
run: __tests__/create-cache-files.sh ${{ runner.os }} ~/test-cache
|
|
||||||
- name: Save cache
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
key: test-${{ runner.os }}-${{ github.run_id }}
|
|
||||||
path: |
|
|
||||||
test-cache
|
|
||||||
~/test-cache
|
|
||||||
|
|
||||||
test-restore:
|
|
||||||
needs: test-save
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
|
||||||
fail-fast: false
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
- name: Restore cache
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
key: test-${{ runner.os }}-${{ github.run_id }}
|
|
||||||
path: |
|
|
||||||
test-cache
|
|
||||||
~/test-cache
|
|
||||||
- name: Verify cache files in working directory
|
|
||||||
shell: bash
|
|
||||||
run: __tests__/verify-cache-files.sh ${{ runner.os }} test-cache
|
|
||||||
- name: Verify cache files outside working directory
|
|
||||||
shell: bash
|
|
||||||
run: __tests__/verify-cache-files.sh ${{ runner.os }} ~/test-cache
|
|
||||||
|
|
||||||
# End to end with proxy
|
|
||||||
test-proxy-save:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: ubuntu:latest
|
|
||||||
options: --cap-add=NET_ADMIN
|
|
||||||
services:
|
|
||||||
squid-proxy:
|
|
||||||
image: ubuntu/squid:latest
|
|
||||||
ports:
|
|
||||||
- 3128:3128
|
|
||||||
env:
|
|
||||||
http_proxy: http://squid-proxy:3128
|
|
||||||
https_proxy: http://squid-proxy:3128
|
|
||||||
steps:
|
|
||||||
- name: Wait for proxy to be ready
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "Waiting for squid proxy to be ready..."
|
|
||||||
echo "Resolving squid-proxy hostname:"
|
|
||||||
getent hosts squid-proxy || echo "DNS resolution failed"
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
if (echo > /dev/tcp/squid-proxy/3128) 2>/dev/null; then
|
|
||||||
echo "Proxy is ready!"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "Attempt $i: Proxy not ready, waiting..."
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
echo "Proxy failed to become ready"
|
|
||||||
exit 1
|
|
||||||
env:
|
|
||||||
http_proxy: ""
|
|
||||||
https_proxy: ""
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y iptables curl
|
|
||||||
- name: Verify proxy is working
|
|
||||||
run: |
|
|
||||||
echo "Testing proxy connectivity..."
|
|
||||||
curl -s -o /dev/null -w "%{http_code}" --proxy http://squid-proxy:3128 http://github.com || true
|
|
||||||
echo "Proxy verification complete"
|
|
||||||
- name: Block direct traffic (enforce proxy usage)
|
|
||||||
run: |
|
|
||||||
# Get the squid-proxy container IP
|
|
||||||
PROXY_IP=$(getent hosts squid-proxy | awk '{ print $1 }')
|
|
||||||
echo "Proxy IP: $PROXY_IP"
|
|
||||||
|
|
||||||
# Allow loopback traffic
|
|
||||||
iptables -A OUTPUT -o lo -j ACCEPT
|
|
||||||
|
|
||||||
# Allow traffic to the proxy container
|
|
||||||
iptables -A OUTPUT -d $PROXY_IP -j ACCEPT
|
|
||||||
|
|
||||||
# Allow established connections
|
|
||||||
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
||||||
|
|
||||||
# Allow DNS (needed for initial resolution)
|
|
||||||
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
|
||||||
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
|
|
||||||
|
|
||||||
# Block all other outbound traffic (HTTP/HTTPS)
|
|
||||||
iptables -A OUTPUT -p tcp --dport 80 -j REJECT
|
|
||||||
iptables -A OUTPUT -p tcp --dport 443 -j REJECT
|
|
||||||
|
|
||||||
# Log the iptables rules for debugging
|
|
||||||
iptables -L -v -n
|
|
||||||
- name: Verify direct HTTPS is blocked
|
|
||||||
run: |
|
|
||||||
echo "Testing that direct HTTPS requests fail..."
|
|
||||||
if curl --noproxy '*' -s --connect-timeout 5 https://github.com > /dev/null 2>&1; then
|
|
||||||
echo "ERROR: Direct HTTPS request succeeded - blocking is not working!"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "SUCCESS: Direct HTTPS request was blocked as expected"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Testing that HTTPS through proxy succeeds..."
|
|
||||||
if curl --proxy http://squid-proxy:3128 -s --connect-timeout 10 https://github.com > /dev/null 2>&1; then
|
|
||||||
echo "SUCCESS: HTTPS request through proxy succeeded"
|
|
||||||
else
|
|
||||||
echo "ERROR: HTTPS request through proxy failed!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
- name: Generate files
|
|
||||||
run: __tests__/create-cache-files.sh proxy test-cache
|
|
||||||
- name: Save cache
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
key: test-proxy-${{ github.run_id }}
|
|
||||||
path: test-cache
|
|
||||||
|
|
||||||
test-proxy-restore:
|
|
||||||
needs: test-proxy-save
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: ubuntu:latest
|
|
||||||
options: --cap-add=NET_ADMIN
|
|
||||||
services:
|
|
||||||
squid-proxy:
|
|
||||||
image: ubuntu/squid:latest
|
|
||||||
ports:
|
|
||||||
- 3128:3128
|
|
||||||
env:
|
|
||||||
http_proxy: http://squid-proxy:3128
|
|
||||||
https_proxy: http://squid-proxy:3128
|
|
||||||
steps:
|
|
||||||
- name: Wait for proxy to be ready
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "Waiting for squid proxy to be ready..."
|
|
||||||
echo "Resolving squid-proxy hostname:"
|
|
||||||
getent hosts squid-proxy || echo "DNS resolution failed"
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
if (echo > /dev/tcp/squid-proxy/3128) 2>/dev/null; then
|
|
||||||
echo "Proxy is ready!"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "Attempt $i: Proxy not ready, waiting..."
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
echo "Proxy failed to become ready"
|
|
||||||
exit 1
|
|
||||||
env:
|
|
||||||
http_proxy: ""
|
|
||||||
https_proxy: ""
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y iptables curl
|
|
||||||
- name: Verify proxy is working
|
|
||||||
run: |
|
|
||||||
echo "Testing proxy connectivity..."
|
|
||||||
curl -s -o /dev/null -w "%{http_code}" --proxy http://squid-proxy:3128 http://github.com || true
|
|
||||||
echo "Proxy verification complete"
|
|
||||||
- name: Block direct traffic (enforce proxy usage)
|
|
||||||
run: |
|
|
||||||
# Get the squid-proxy container IP
|
|
||||||
PROXY_IP=$(getent hosts squid-proxy | awk '{ print $1 }')
|
|
||||||
echo "Proxy IP: $PROXY_IP"
|
|
||||||
|
|
||||||
# Allow loopback traffic
|
|
||||||
iptables -A OUTPUT -o lo -j ACCEPT
|
|
||||||
|
|
||||||
# Allow traffic to the proxy container
|
|
||||||
iptables -A OUTPUT -d $PROXY_IP -j ACCEPT
|
|
||||||
|
|
||||||
# Allow established connections
|
|
||||||
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
||||||
|
|
||||||
# Allow DNS (needed for initial resolution)
|
|
||||||
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
|
||||||
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
|
|
||||||
|
|
||||||
# Block all other outbound traffic (HTTP/HTTPS)
|
|
||||||
iptables -A OUTPUT -p tcp --dport 80 -j REJECT
|
|
||||||
iptables -A OUTPUT -p tcp --dport 443 -j REJECT
|
|
||||||
|
|
||||||
# Log the iptables rules for debugging
|
|
||||||
iptables -L -v -n
|
|
||||||
- name: Verify direct HTTPS is blocked
|
|
||||||
run: |
|
|
||||||
echo "Testing that direct HTTPS requests fail..."
|
|
||||||
if curl --noproxy '*' -s --connect-timeout 5 https://github.com > /dev/null 2>&1; then
|
|
||||||
echo "ERROR: Direct HTTPS request succeeded - blocking is not working!"
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "SUCCESS: Direct HTTPS request was blocked as expected"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Testing that HTTPS through proxy succeeds..."
|
|
||||||
if curl --proxy http://squid-proxy:3128 -s --connect-timeout 10 https://github.com > /dev/null 2>&1; then
|
|
||||||
echo "SUCCESS: HTTPS request through proxy succeeded"
|
|
||||||
else
|
|
||||||
echo "ERROR: HTTPS request through proxy failed!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
- name: Restore cache
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
key: test-proxy-${{ github.run_id }}
|
|
||||||
path: test-cache
|
|
||||||
- name: Verify cache
|
|
||||||
run: __tests__/verify-cache-files.sh proxy test-cache
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
sources:
|
|
||||||
npm: true
|
|
||||||
|
|
||||||
# Force UTF-8 encoding
|
|
||||||
encoding: 'utf-8'
|
|
||||||
|
|
||||||
# Ignore problematic packages with encoding issues
|
|
||||||
ignored:
|
|
||||||
npm:
|
|
||||||
- form-data
|
|
||||||
|
|
||||||
allowed:
|
|
||||||
- apache-2.0
|
|
||||||
- bsd-2-clause
|
|
||||||
- bsd-3-clause
|
|
||||||
- isc
|
|
||||||
- mit
|
|
||||||
- cc0-1.0
|
|
||||||
- unlicense
|
|
||||||
- 0bsd
|
|
||||||
|
|
||||||
reviewed:
|
|
||||||
npm:
|
|
||||||
- sax
|
|
||||||
- "@protobuf-ts/plugin-framework" # Apache-2.0
|
|
||||||
- "@protobuf-ts/runtime" # Apache-2.0
|
|
||||||
- fs.realpath # ISC
|
|
||||||
- glob # ISC
|
|
||||||
- prettier # MIT
|
|
||||||
- lodash # MIT
|
|
||||||
- "@actions/http-client" # MIT
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,275 @@
|
|||||||
|
import * as cache from "@actions/cache";
|
||||||
|
import * as utils from "@actions/cache/lib/internal/cacheUtils";
|
||||||
|
import { CompressionMethod } from "@actions/cache/lib/internal/constants";
|
||||||
|
import {
|
||||||
|
createTar,
|
||||||
|
extractTar,
|
||||||
|
listTar
|
||||||
|
} from "@actions/cache/lib/internal/tar";
|
||||||
|
import { DownloadOptions, UploadOptions } from "@actions/cache/lib/options";
|
||||||
|
import * as core from "@actions/core";
|
||||||
|
import { Storage } from "@google-cloud/storage";
|
||||||
|
import * as path from "path";
|
||||||
|
|
||||||
|
import { Inputs } from "../constants";
|
||||||
|
import { isGCSAvailable } from "./actionUtils";
|
||||||
|
|
||||||
|
const DEFAULT_PATH_PREFIX = "github-cache";
|
||||||
|
|
||||||
|
// Function to initialize GCS client using Application Default Credentials
|
||||||
|
function getGCSClient(): Storage | null {
|
||||||
|
try {
|
||||||
|
core.info("Initializing GCS client");
|
||||||
|
return new Storage();
|
||||||
|
} catch (error) {
|
||||||
|
core.warning(
|
||||||
|
`Failed to initialize GCS client: ${(error as Error).message}`
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function restoreCache(
|
||||||
|
paths: string[],
|
||||||
|
primaryKey: string,
|
||||||
|
restoreKeys?: string[],
|
||||||
|
options?: DownloadOptions,
|
||||||
|
enableCrossOsArchive?: boolean
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
// Check if GCS is available
|
||||||
|
if (isGCSAvailable()) {
|
||||||
|
try {
|
||||||
|
const result = await restoreFromGCS(
|
||||||
|
paths,
|
||||||
|
primaryKey,
|
||||||
|
restoreKeys,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
core.info(`Cache restored from GCS with key: ${result}`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info("Cache not found in GCS, falling back to GitHub cache");
|
||||||
|
} catch (error) {
|
||||||
|
core.warning(
|
||||||
|
`Failed to restore from GCS: ${(error as Error).message}`
|
||||||
|
);
|
||||||
|
core.info("Falling back to GitHub cache");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
core.info("GCS not configured, using GitHub cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to GitHub cache
|
||||||
|
return await cache.restoreCache(
|
||||||
|
paths,
|
||||||
|
primaryKey,
|
||||||
|
restoreKeys,
|
||||||
|
options,
|
||||||
|
enableCrossOsArchive
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveCache(
|
||||||
|
paths: string[],
|
||||||
|
key: string,
|
||||||
|
options?: UploadOptions,
|
||||||
|
enableCrossOsArchive?: boolean
|
||||||
|
): Promise<number> {
|
||||||
|
if (isGCSAvailable()) {
|
||||||
|
try {
|
||||||
|
const result = await saveToGCS(paths, key);
|
||||||
|
if (result) {
|
||||||
|
core.info(`Cache saved to GCS with key: [${key} | ${result}]`);
|
||||||
|
return 1; // Success ID
|
||||||
|
}
|
||||||
|
|
||||||
|
core.warning("Failed to save to GCS, falling back to GitHub cache");
|
||||||
|
return -1;
|
||||||
|
} catch (error) {
|
||||||
|
core.warning(`Failed to save to GCS: ${(error as Error).message}`);
|
||||||
|
core.info("Falling back to GitHub cache");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
core.info("GCS not configured, using GitHub cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to GitHub cache
|
||||||
|
return await cache.saveCache(paths, key, options, enableCrossOsArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function that checks if the cache feature is available (either GCS or GitHub cache)
|
||||||
|
export function isFeatureAvailable(): boolean {
|
||||||
|
return isGCSAvailable() || cache.isFeatureAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restoreFromGCS(
|
||||||
|
_paths: string[], // validate paths?
|
||||||
|
primaryKey: string,
|
||||||
|
restoreKeys: string[] = [],
|
||||||
|
options?: DownloadOptions
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
const storage = getGCSClient();
|
||||||
|
if (!storage) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bucket = core.getInput(Inputs.GCSBucket);
|
||||||
|
const pathPrefix =
|
||||||
|
core.getInput(Inputs.GCSPathPrefix) || DEFAULT_PATH_PREFIX;
|
||||||
|
const compressionMethod = await utils.getCompressionMethod();
|
||||||
|
|
||||||
|
const archiveFolder = await utils.createTempDirectory();
|
||||||
|
const archivePath = path.join(
|
||||||
|
archiveFolder,
|
||||||
|
utils.getCacheFileName(compressionMethod)
|
||||||
|
);
|
||||||
|
|
||||||
|
const keys = [primaryKey, ...restoreKeys];
|
||||||
|
const gcsPath = await findFileOnGCS(
|
||||||
|
storage,
|
||||||
|
bucket,
|
||||||
|
pathPrefix,
|
||||||
|
keys,
|
||||||
|
compressionMethod
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!gcsPath) {
|
||||||
|
core.info(`No matching cache found`);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If lookup only, just return the key
|
||||||
|
if (options?.lookupOnly) {
|
||||||
|
core.info(`Cache found in GCS with key: ${gcsPath}`);
|
||||||
|
return gcsPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
core.info(`Downloading from GCS: ${bucket}/${gcsPath}`);
|
||||||
|
const file = storage.bucket(bucket).file(gcsPath);
|
||||||
|
await file.download({ destination: archivePath });
|
||||||
|
|
||||||
|
if (core.isDebug()) {
|
||||||
|
await listTar(archivePath, compressionMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
const archiveFileSize = utils.getArchiveFileSizeInBytes(archivePath);
|
||||||
|
core.info(
|
||||||
|
`Cache Size: ~${Math.round(
|
||||||
|
archiveFileSize / (1024 * 1024)
|
||||||
|
)} MB (${archiveFileSize} B)`
|
||||||
|
);
|
||||||
|
|
||||||
|
await extractTar(archivePath, compressionMethod);
|
||||||
|
core.info("Cache restored successfully");
|
||||||
|
|
||||||
|
return gcsPath;
|
||||||
|
} catch (error) {
|
||||||
|
core.warning(`Failed to restore: ${(error as Error).message}`);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
await utils.unlinkFile(archivePath);
|
||||||
|
} catch (error) {
|
||||||
|
core.debug(`Failed to delete archive: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGCSPath(
|
||||||
|
pathPrefix: string,
|
||||||
|
key: string,
|
||||||
|
compressionMethod: CompressionMethod
|
||||||
|
): string {
|
||||||
|
return `${pathPrefix}/${key}.${utils.getCacheFileName(compressionMethod)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveToGCS(
|
||||||
|
paths: string[],
|
||||||
|
key: string
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
const storage = getGCSClient();
|
||||||
|
if (!storage) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bucket = core.getInput(Inputs.GCSBucket);
|
||||||
|
const pathPrefix =
|
||||||
|
core.getInput(Inputs.GCSPathPrefix) || DEFAULT_PATH_PREFIX;
|
||||||
|
const compressionMethod = await utils.getCompressionMethod();
|
||||||
|
|
||||||
|
const cachePaths = await utils.resolvePaths(paths);
|
||||||
|
core.debug("Cache Paths:");
|
||||||
|
core.debug(`${JSON.stringify(cachePaths)}`);
|
||||||
|
|
||||||
|
if (cachePaths.length === 0) {
|
||||||
|
throw new Error(
|
||||||
|
`Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const archiveFolder = await utils.createTempDirectory();
|
||||||
|
const archivePath = path.join(
|
||||||
|
archiveFolder,
|
||||||
|
utils.getCacheFileName(compressionMethod)
|
||||||
|
);
|
||||||
|
|
||||||
|
core.debug(`Archive Path: ${archivePath}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createTar(archiveFolder, cachePaths, compressionMethod);
|
||||||
|
if (core.isDebug()) {
|
||||||
|
await listTar(archivePath, compressionMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gcsPath = getGCSPath(pathPrefix, key, compressionMethod);
|
||||||
|
core.info(`Uploading to GCS: ${bucket}/${gcsPath}`);
|
||||||
|
const [file] = await storage.bucket(bucket).upload(archivePath, {
|
||||||
|
destination: gcsPath,
|
||||||
|
resumable: false
|
||||||
|
});
|
||||||
|
|
||||||
|
return file.metadata.id;
|
||||||
|
} catch (error) {
|
||||||
|
core.warning(
|
||||||
|
`Error creating or uploading cache: ${(error as Error).message}`
|
||||||
|
);
|
||||||
|
throw new Error(
|
||||||
|
`Error creating or uploading cache: ${(error as Error).message}`
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
await utils.unlinkFile(archivePath);
|
||||||
|
} catch (error) {
|
||||||
|
core.debug(`Failed to delete archive: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findFileOnGCS(
|
||||||
|
storage: Storage,
|
||||||
|
bucket: string,
|
||||||
|
pathPrefix: string,
|
||||||
|
keys: string[],
|
||||||
|
compressionMethod: CompressionMethod
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
for (const key of keys) {
|
||||||
|
const gcsPath = getGCSPath(pathPrefix, key, compressionMethod);
|
||||||
|
if (await checkFileExists(storage, bucket, gcsPath)) {
|
||||||
|
core.info(`Found file on bucket: ${bucket} with key: ${gcsPath}`);
|
||||||
|
return gcsPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkFileExists(
|
||||||
|
storage: Storage,
|
||||||
|
bucket: string,
|
||||||
|
path: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
const [exists] = await storage.bucket(bucket).file(path).exists();
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue