Skip to main content

Building Sessions

When building sessions, you can follow the same methods used in Building Workflows & Apps.

However, there is one important note: when creating a session, the top-level definition sessions is required. Similar to the top-level definition jobs, sessions can be used to define multiple sessions with unique names. You can then use the session context to call a session by name inside steps.

In the example below, there is one session defined in sessions, named expose. That session is then referenced inside steps inside jobs with the context ${{ sessions.expose }}. The expose property redirect designates whether the user will be redirected to a specific session, especially in the event that you define multiple sessions. The property prompt-for-name determines whether a user will be prompted to name their session. If prompt-for-name is excluded, a name will automatically be generated based on the run numbers.

sessions:
expose:
redirect: true
prompt-for-name:
default: 'mysession'
jobs:
main:
steps:
- name: Expose port
uses: parallelworks/update-session
with:
remotePort: '3001'
name: ${{ sessions.expose }}
status: running
target: ${{ inputs.target.id }}
- name: Run forever
run: ssh ${{ inputs.target.ip }} tail -f /dev/null
'on':
execute:
inputs:
target:
type: compute-clusters
default: ${{ app.target }}

To target your user workspace, you can set the target input to user-workspace. This following example will start a small http server on a specified port in your user workspace, then expose that port as a session.

sessions:
expose:
redirect: true
jobs:
expose:
steps:
- name: Wait for server
run: |
while ! nc -z localhost ${{ inputs.port }}; do
sleep 1
done
- name: Expose port
uses: parallelworks/update-session
with:
remotePort: ${{ inputs.port }}
name: ${{ sessions.expose }}
status: 'running'
target: 'user-workspace'
- name: Keep running
run: sleep 500
main:
steps:
- name: Create simple http server
run: |
cat <<EOF > myServer.go
package main

import (
"fmt"
"net/http"
)
func hello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Hello from GO server!\n")
}

func main() {
http.HandleFunc("/", hello)
http.ListenAndServe(":${{ inputs.port }}", nil)
}
EOF
- name: Run Server
run: go run myServer.go
'on':
execute:
inputs:
port:
type: number
min: 4000
max: 65000
label: Port to run server on

You can also use the slurm-agent action to provision a node and start an ssh server so that you can run commands on it through ssh. Note that you must use a cluster with a partition for this to properly work.

permissions:
- '*'
sessions:
session:
jobs:
main:
ssh:
remoteHost: ${{ inputs.resource.ip }}
steps:
- uses: parallelworks/slurm-agent
id: slurmstep
- name: Get open port
ssh:
jumpNodeHost: ${{ inputs.resource.ip }}
remoteHost: ${{ needs.main.steps.slurmstep.outputs.remoteHost }}:${{ needs.main.steps.slurmstep.outputs.sshPort }}
run: |
echo sessionPort="$(pw agent open-port)" >> $OUTPUTS
cat $OUTPUTS
- uses: parallelworks/update-session
with:
status: running
name: ${{ sessions.session }}
target: ${{ inputs.resource.id }}
remoteHost: ${{ needs.main.steps.slurmstep.outputs.remoteHost }}
remotePort: ${{ needs.main.outputs.sessionPort }}
- name: Serve port
run: |
cat << EOF > myServer.go
package main

import (
"fmt"
"net/http"
)
func hello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Hello from slurm agent server!\n")
}

func main() {
http.HandleFunc("/", hello)
http.ListenAndServe(":${{ needs.main.outputs.sessionPort }}", nil)
}
EOF
go run myServer.go
ssh:
jumpNodeHost: ${{ inputs.resource.ip }}
remoteHost: ${{ needs.main.steps.slurmstep.outputs.remoteHost }}:${{ needs.main.steps.slurmstep.outputs.sshPort }}
'on':
execute:
inputs:
resource:
label: Resource Target
type: compute-clusters
autoselect: true
optional: false