# Workflow Sessions

> Source: https://parallelworks.com/docs/run/workflows/building-workflows/sessions

# Workflow Sessions

A workflow can expose an application as a [session](/docs/run/sessions) by including a top-level `sessions` definition in its YAML. The session is created when the workflow runs and removed when it stops.

Similar to [the top-level definition `jobs`](/docs/run/workflows/building-workflows/yaml-fields#jobs), the `sessions` definition can describe 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.

```yaml
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
```

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.

```yaml
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

            "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 `scheduler-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.

```yaml
permissions:
  - '*'
sessions:
  session: 
jobs:
  main:
    ssh:
      remoteHost: ${{ inputs.resource.ip }}
    steps:
      - uses: parallelworks/scheduler-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

              "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
```
