Skip to content

Extend action with supporting relative path for portainer be #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 44 additions & 43 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,49 @@ on:
branches: [master]

jobs:
test:
name: "Test"
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: "Checkout"
uses: actions/checkout@v4

- name: "Write YAML"
id: yaml-action
uses: teunmooij/yaml@v1
with:
data: '{"version":"3.8","services":{"alpine":{"image":"alpine","command":"tail -f /dev/null"}}}'
to-file: "docker-compose.yaml"

- name: "Test Local Action"
id: test
uses: ./
with:
token: ${{ secrets.PORTAINER_TOKEN }}
url: ${{ secrets.PORTAINER_URL }}
name: alpine-test
file: docker-compose.yaml
type: file

- name: "Echo Output"
run: |
echo 'stackID: ${{ steps.test.outputs.stackID }}'
echo 'swarmID: ${{ steps.test.outputs.swarmID }}'
echo 'endpointID: ${{ steps.test.outputs.endpointID }}'

#- name: "Call API"
# uses: indiesdev/curl@v1.1
# with:
# url: "${{ secrets.PORTAINER_URL }}/api/stacks/${{ steps.test.outputs.stackID }}"
# method: "DELETE"
# accept: 204
# headers: '{ "X-API-Key": "${{ secrets.PORTAINER_TOKEN }}" }'
# params: '{ "endpointId": "${{ steps.test.outputs.endpointID }}" }'
# timeout: 5000
# log-response: true
# retries: 1
# test:
# name: "Test"
# runs-on: ubuntu-latest
# timeout-minutes: 5

# steps:
# - name: "Checkout"
# uses: actions/checkout@v4

# - name: "Write YAML"
# id: yaml-action
# uses: teunmooij/yaml@v1
# with:
# data: '{"version":"3.8","services":{"alpine":{"image":"alpine","command":"tail -f /dev/null"}}}'
# to-file: "docker-compose.yaml"

# - name: "Test Local Action"
# id: test
# uses: ./
# with:
# token: ${{ secrets.PORTAINER_TOKEN }}
# url: ${{ secrets.PORTAINER_URL }}
# name: alpine-test
# file: docker-compose.yaml
# type: file

# - name: "Echo Output"
# run: |
# echo 'stackID: ${{ steps.test.outputs.stackID }}'
# echo 'swarmID: ${{ steps.test.outputs.swarmID }}'
# echo 'endpointID: ${{ steps.test.outputs.endpointID }}'

# - name: "Clean up"
# uses: indiesdev/curl@v1.1
# with:
# url: "${{ secrets.PORTAINER_URL }}/api/stacks/${{ steps.test.outputs.stackID }}"
# method: "DELETE"
# accept: 204
# headers: '{ "X-API-Key": "${{ secrets.PORTAINER_TOKEN }}" }'
# params: '{ "endpointId": "${{ steps.test.outputs.endpointID }}" }'
# timeout: 5000
# log-response: true
# retries: 1

lint:
name: "Lint"
Expand Down Expand Up @@ -89,6 +89,7 @@ jobs:
npm install
npm run build

# Remove this
- name: "Verify"
run: |
git status --porcelain dist/
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ _No Portainer?_ You can deploy directly to a docker over ssh with: [cssnr/stack-
| env_file | No | - | Dotenv File Path \* |
| username | No | - | Repository Username \* |
| password | No | - | Repository Password \* |
| fs_path | No | - | Relative Path (BE) \* |

**token** - To create a Portainer API token see: https://docs.portainer.io/api/access

Expand All @@ -70,6 +71,9 @@ JSON should be an object. Example: `{"KEY": "Value"}`
**username/password** - Only set these if the `repo` is private and requires authentication.
This is NOT the Portainer username/password, see `token` for Portainer authentication.

**file_system_path** - Only available in Portainer Business Edition. When specified, enables relative path volumes support
and uses the provided path as the base directory for relative volume mappings in your compose file.

```yaml
- name: 'Portainer Deploy'
uses: cssnr/portainer-stack-deploy-action@v1
Expand Down Expand Up @@ -151,6 +155,19 @@ Specify environment variables, may use json, or file, or a combination of both:
env_file: .env
```

Deploy with relative path volumes (BE only):

```yaml
- name: 'Portainer Deploy'
uses: cssnr/portainer-stack-deploy-action@v1
with:
token: ${{ secrets.PORTAINER_TOKEN }}
url: https://portainer.example.com:9443
name: stack-name
file: docker-compose.yaml
fs_path: /mnt
```

To include this in a general workflow but only run on release events use an if:

- `if: ${{ github.event_name == 'release' }}`
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ inputs:
description: "Repository Password"
required: false
default: ""
fs_path:
description: "Relative path volume in host (only available in Portainer BE)"
required: false

outputs:
stackID:
Expand Down
23 changes: 23 additions & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33839,6 +33839,11 @@ class Portainer {
})
}

async getVersion() {
const response = await this.client.get('/status/version')
return response.data
}

async getEndpoints() {
const response = await this.client.get('/endpoints')
return response.data
Expand Down Expand Up @@ -40672,9 +40677,22 @@ const Portainer = __nccwpck_require__(1055)
repositoryUsername || repositoryPassword
)
console.log('repositoryAuthentication:', repositoryAuthentication)
let fs_path = core.getInput('fs_path')
console.log('fs_path:', fs_path)

const portainer = new Portainer(url, token)

if (fs_path) {
// get system info and check if portainer is BE edition
const version = await portainer.getVersion()
const is_portainer_be = version.ServerEdition === 'EE'
if (!is_portainer_be) {
return core.setFailed(
'Relative path is only supported on Portainer Business Edition'
)
}
}

if (!endpointID) {
const endpoints = await portainer.getEndpoints()
// console.log('endpoints:', endpoints)
Expand Down Expand Up @@ -40734,6 +40752,11 @@ const Portainer = __nccwpck_require__(1055)
repositoryAuthentication,
repositoryPassword,
repositoryUsername,
// If fs_path is set, add it to the body
...(fs_path && {
supportRelativePath: true,
fileSystemPath: fs_path
}),
}
// console.log('body:', body)
stack = await portainer.createStackRepo(endpointID, body)
Expand Down
18 changes: 18 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,22 @@ const Portainer = require('./portainer')
repositoryUsername || repositoryPassword
)
console.log('repositoryAuthentication:', repositoryAuthentication)
let fs_path = core.getInput('fs_path')
console.log('fs_path:', fs_path)

const portainer = new Portainer(url, token)

if (fs_path) {
// get system info and check if portainer is BE edition
const version = await portainer.getVersion()
const is_portainer_be = version.ServerEdition === 'EE'
if (!is_portainer_be) {
return core.setFailed(
'Relative path is only supported on Portainer Business Edition'
)
}
}

if (!endpointID) {
const endpoints = await portainer.getEndpoints()
// console.log('endpoints:', endpoints)
Expand Down Expand Up @@ -115,6 +128,11 @@ const Portainer = require('./portainer')
repositoryAuthentication,
repositoryPassword,
repositoryUsername,
// If fs_path is set, add it to the body
...(fs_path && {
supportRelativePath: true,
fileSystemPath: fs_path
}),
}
// console.log('body:', body)
stack = await portainer.createStackRepo(endpointID, body)
Expand Down
5 changes: 5 additions & 0 deletions src/portainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ class Portainer {
})
}

async getVersion() {
const response = await this.client.get('/status/version')
return response.data
}

async getEndpoints() {
const response = await this.client.get('/endpoints')
return response.data
Expand Down