Skip to content

Deploy Program

Deploy Program #37

name: Deploy Program
on:
workflow_dispatch:
inputs:
git_ref:
description: Release tag (release/[email protected]) or commit to deploy
required: true
type: string
default: release/[email protected]
program:
description: Program
required: true
default: core
type: choice
options:
- core
cluster:
description: Cluster environment
required: true
default: devnet
type: choice
options:
- devnet
- mainnet-beta
- sonic-devnet
- sonic-testnet
- eclipse-mainnet
- eclipse-devnet
dry_run:
description: Dry run
required: false
type: boolean
default: false
env:
CACHE: true
jobs:
check_tag:
name: 'Check tag'
runs-on: ubuntu-latest
outputs:
program: ${{ steps.set_program.outputs.program }}
type: ${{ steps.set_program.outputs.type }}
steps:
- name: Check tag
id: set_program
run: |
echo program="core" >> $GITHUB_OUTPUT
if [[ "${{ inputs.git_ref }}" =~ ^release/core@* ]]; then
echo type="release" >> $GITHUB_OUTPUT
else
echo type="ref" >> $GITHUB_OUTPUT
fi
build_programs:
name: Programs
uses: ./.github/workflows/build-programs.yml
secrets: inherit
needs: check_tag
if: needs.check_tag.outputs.type == 'ref'
with:
git_ref: ${{ inputs.git_ref }}
test_programs:
name: Test Programs
uses: ./.github/workflows/test-programs.yml
secrets: inherit
needs: [build_programs, check_tag]
if: needs.check_tag.outputs.type == 'ref'
with:
program_matrix: '["mpl-${{ inputs.program }}"]'
git_ref: ${{ inputs.git_ref }}
test_js:
name: JS client
needs: [build_programs, check_tag]
uses: ./.github/workflows/test-js-client.yml
secrets: inherit
if: needs.check_tag.outputs.type == 'ref'
with:
git_ref: ${{ inputs.git_ref }}
test_rust:
name: Rust client
needs: [build_programs, check_tag]
uses: ./.github/workflows/test-rust-client.yml
secrets: inherit
if: needs.check_tag.outputs.type == 'ref'
with:
git_ref: ${{ inputs.git_ref }}
deploy_program:
name: Program / Deploy
runs-on: ubuntu-latest-16-cores
needs: [check_tag, test_js, test_programs, test_rust]
if: |
always()
&& (needs.test_js.result == 'success' || needs.test_js.result == 'skipped')
&& (needs.test_programs.result == 'success' || needs.test_programs.result == 'skipped')
&& (needs.test_rust.result == 'success' || needs.test_rust.result == 'skipped')
permissions:
contents: write
steps:
- name: Git checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.SVC_TOKEN }}
ref: ${{ inputs.git_ref }}
- name: Load environment variables
run: cat .github/.env >> $GITHUB_ENV
- name: Install Rust
uses: metaplex-foundation/actions/install-rust@v1
with:
toolchain: ${{ env.RUST_VERSION }}
- name: Install Solana
uses: metaplex-foundation/actions/install-solana@v1
with:
version: ${{ env.DEPLOY_SOLANA_VERSION }}
cache: ${{ env.CACHE }}
- name: Install cargo-release
uses: metaplex-foundation/actions/install-cargo-release@v1
if: github.event.inputs.publish_crate == 'true'
with:
cache: ${{ env.CACHE }}
- name: Set RPC
run: |
# We do this if waterfall because github actions does not allow dynamic access to secrets
if [ "${{ inputs.cluster }}" == "devnet" ]; then
echo RPC=${{ secrets.DEVNET_RPC }} >> $GITHUB_ENV
elif [ "${{ inputs.cluster }}" == "mainnet-beta" ]; then
echo RPC=${{ secrets.MAINNET_RPC }} >> $GITHUB_ENV
elif [ "${{ inputs.cluster }}" == "sonic-devnet" ]; then
echo RPC=${{ secrets.SONIC_DEVNET_RPC }} >> $GITHUB_ENV
elif [ "${{ inputs.cluster }}" == "sonic-testnet" ]; then
echo RPC=${{ secrets.SONIC_TESTNET_RPC }} >> $GITHUB_ENV
elif [ "${{ inputs.cluster }}" == "eclipse-devnet" ]; then
echo RPC=${{ secrets.ECLIPSE_DEVNET_RPC }} >> $GITHUB_ENV
elif [ "${{ inputs.cluster }}" == "eclipse-testnet" ]; then
echo RPC=${{ secrets.ECLIPSE_TESTNET_RPC }} >> $GITHUB_ENV
elif [ "${{ inputs.cluster }}" == "eclipse-mainnet" ]; then
echo RPC=${{ secrets.ECLIPSE_MAINNET_RPC }} >> $GITHUB_ENV
fi
- name: Identify Program
run: |
echo PROGRAM_NAME="mpl_core" >> $GITHUB_ENV
echo ${{ secrets.CORE_ID }} > ./program-id.json
if [[ "${{ inputs.cluster }}" == "sonic"* ]]; then
echo ${{ secrets.CORE_SONIC_DEPLOY_KEY }} > ./deployer-key.json
echo DEPLOY_TYPE="direct" >> $GITHUB_ENV
elif [[ "${{ inputs.cluster }}" == "eclipse"* ]]; then
echo ${{ secrets.CORE_ECLIPSE_DEPLOY_KEY }} > ./deployer-key.json
echo DEPLOY_TYPE="direct" >> $GITHUB_ENV
elif [[ "${{ inputs.cluster }}" == "devnet" ]]; then
echo DEPLOY_TYPE="squads" >> $GITHUB_ENV
echo SQUADS_MULTISIG="Gs6jZWxXFvmZtBcyYr6fBXX5ikwRTemBDS4f6kFuB31U" >> $GITHUB_ENV
echo SQUADS_VAULT="Fsxr5WVKZZoeb7xgwTWRHymSRVGY9vk7m5B5GPu1KU59" >> $GITHUB_ENV
echo SQUADS_PROGRAM_INDEX="1" >> $GITHUB_ENV
elif [[ "${{ inputs.cluster }}" == "mainnet-beta" ]]; then
echo DEPLOY_TYPE="squads" >> $GITHUB_ENV
echo SQUADS_MULTISIG="Gs6jZWxXFvmZtBcyYr6fBXX5ikwRTemBDS4f6kFuB31U" >> $GITHUB_ENV
echo SQUADS_VAULT="Fsxr5WVKZZoeb7xgwTWRHymSRVGY9vk7m5B5GPu1KU59" >> $GITHUB_ENV
echo SQUADS_PROGRAM_INDEX="1" >> $GITHUB_ENV
else
echo "Invalid cluster: ${{ inputs.cluster }}"
exit 1
fi
- name: Download program builds
uses: actions/download-artifact@v4
if: needs.check_tag.outputs.type == 'ref'
with:
name: program-builds-${{ inputs.git_ref }}
- name: Download release asset
uses: actions/github-script@v5
id: get_release
if: needs.check_tag.outputs.type == 'release'
with:
script: |
const tag = "${{ inputs.git_ref }}";
const assetName = "${{ env.PROGRAM_NAME }}_program.so";
// Fetch the release associated with the tag
const release = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag: tag
});
if (release.status !== 200) {
throw new Error(`Failed to fetch release for tag ${tag}`);
}
const asset = release.data.assets.find(asset => asset.name === assetName);
if (!asset) {
throw new Error(`Asset ${assetName} not found in release tagged ${tag}`);
}
core.setOutput("url", asset.url);
- name: Download the Selected Asset
if: needs.check_tag.outputs.type == 'release'
run: |
mkdir -p ${{ github.workspace }}/programs/.bin
curl -L -o ${{ github.workspace }}/programs/.bin/${{ env.PROGRAM_NAME }}_program.so \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/octet-stream" \
"${{ steps.get_release.outputs.url }}"
- name: Deploy Program
if: github.event.inputs.dry_run == 'false' && env.DEPLOY_TYPE == 'direct'
run: |
echo "Deploying ${{ needs.check_tag.outputs.program }} to ${{ inputs.cluster }}"
solana -v program deploy ./programs/.bin/${{ env.PROGRAM_NAME }}_program.so \
-u ${{ env.RPC }} \
--program-id ./program-id.json \
-k ./deployer-key.json \
--max-sign-attempts 100 \
--use-rpc
rm ./deployer-key.json
rm ./program-id.json
- name: Deploy squads buffer
if: github.event.inputs.dry_run == 'false' && env.DEPLOY_TYPE == 'squads'
run: |
echo "Deploying buffer for ${{ inputs.program }} on ${{ inputs.cluster }}"
echo ${{ secrets.SQUADS_BOT_KEY }} > ./submitter-key.json
BUFFER=$(solana program write-buffer -u ${{ env.RPC }} -k ./submitter-key.json --max-sign-attempts 100 --use-rpc ./programs/.bin/${{ env.PROGRAM_NAME }}_program.so | awk '{print $2}')
echo "Buffer: $BUFFER"
solana program set-buffer-authority $BUFFER \
--new-buffer-authority ${{ env.SQUADS_VAULT }} \
-k ./submitter-key.json \
-u ${{ env.RPC }}
rm ./submitter-key.json
echo "BUFFER=$BUFFER" >> $GITHUB_ENV
- name: Create Squads proposal
if: github.event.inputs.dry_run == 'false' && env.DEPLOY_TYPE == 'squads'
uses: metaplex-foundation/squads-program-upgrade@main
with:
network-url: ${{ env.RPC }}
program-multisig: ${{ env.SQUADS_MULTISIG }}
program-id: 'CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d'
program-index: ${{ env.SQUADS_PROGRAM_INDEX }}
buffer: ${{ env.BUFFER }}
spill-address: 'botTxAkJhuCtNNn9xsH8fHJjzTkcN6XD4dR3R5hkzV2'
authority: ${{ env.SQUADS_VAULT }}
name: 'Deploy ${{ inputs.git_ref }}'
keypair: ${{ secrets.SQUADS_BOT_KEY }}
- name: Create env tag
uses: actions/github-script@v7
if: github.event.inputs.dry_run == 'false'
with:
github-token: ${{ secrets.GH_TAGGER_TOKEN }}
script: |
const refData = await github.rest.git.getRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'tags/${{ inputs.git_ref }}'
});
if (refData.status !== 200) {
throw new Error('Failed to fetch existing tag');
}
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/tags/${{ needs.check_tag.outputs.program }}-${{ inputs.cluster }}',
sha: refData.data.object.sha
}).catch(err => {
if (err.status !== 422) throw err;
github.rest.git.updateRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'tags/${{ needs.check_tag.outputs.program }}-${{ inputs.cluster }}',
sha: refData.data.object.sha
});
})