Skip to content

Commit

Permalink
Merge pull request #228 from replicatedhq/instruqt-feature-tmux-template
Browse files Browse the repository at this point in the history
Provides a template for preserving the shell across challenges
  • Loading branch information
good-better-beck authored Jul 31, 2023
2 parents cd31987 + 580df13 commit 882ee37
Show file tree
Hide file tree
Showing 14 changed files with 466 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
slug: getting-to-know-the-template
id: ofusufdxjhva
type: challenge
title: Getting to Know the Template
teaser: Some tips and tricks for using this template
notes:
- type: text
contents: Let's learn about this template
tabs:
- title: Shell
type: terminal
hostname: shell
difficulty: basic
timelimit: 300
---

👋 Introduction
===============

This template is a baseline for labs that need to persist their shell
environment across challenges. This may be because you as the learner
to set some environment variables, or because they've started a long
running process, or just to make it feel more like the real world
where they're doing everything in the same shell session.

As a cool side-effect, you can also use this template if you want
to interact with the contents of the learner's shell session. The
track uses `tmux` to persist the shell, and with that comes the
opportunity to read the content of the entire `tmux` pane. That
content includes the commands the learner types and the output of
those commands. This can come in very handy in lifecycle scripts, as
can `tmux`'s ability to send keystrokes into the session.


🔤 Basics
=========

You don't really have to do anything special to use this template.
It's configured to start a shell container and a single node Kubernetes
cluster. The shell uses our Instruqt shell image, and runs a `tmux`
session named `shell`. In that sesion it starts a login shell as the
user `replicant` using `su`.

The first challenge will create the session, and additional challenges
will connect to the existing session. This is enabled by following
command which is the `shell` specified in `config.yml` for the `Shell`
sandbox.

```yaml
- name: shell
image: gcr.io/kots-field-labs/shell:instruqt-feature-tmux-template
shell: tmux new-session -A -s shell su - replicant
```
This one command will either create a new session named `shell` running
`su - replicant`, or join an existing session named `shell`. The existing
session will continue with whatever command it was running in the prior
challenge which may just be `replicant`'s shell.

🧪 Try It
=========

Let's set an environment variable in this challenge so we can take
advantage of it in the next one.

```shell
export THIS="the way"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash

# This set line ensures that all failures will cause the script to error and exit
set -euxo pipefail

# save the entire session to check user inputs and outputs
tmux capture-pane -t shell -S -
SESSION=$(tmux save-buffer -)

# check for disconnection
if ! grep -qE 'THIS=[A-Za-z "]+' <(echo ${SESSION}) ; then
fail-message 'Please try it out by setting the environment variable `$THIS`'
exit 1
fi

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash

# This set line ensures that all failures will cause the script to error and exit
set -euxo pipefail

# clear the tmux pane and scrollback to look like a fresh shell
tmux clear-history -t shell
tmux send-keys -t shell clear ENTER

exit 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash

# This set line ensures that all failures will cause the script to error and exit
set -euxo pipefail

HOME_DIR=/home/replicant

# Wait for Instruqt bootstrap to be complete
while [ ! -f /opt/instruqt/bootstrap/host-bootstrap-completed ]
do
echo "Waiting for Instruqt to finish booting the VM"
sleep 1
done

# Display PARTICIPANT_ID info for Vendor Portal
cat<<SCRIPT >> ${HOME_DIR}/.bashrc
show_credentials () {
echo Credentials for https://vendor.replicated.com
echo Username: $INSTRUQT_PARTICIPANT_ID@replicated-labs.com
echo Password: $INSTRUQT_PARTICIPANT_ID
}
show_credentials
SCRIPT
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash

# This set line ensures that all failures will cause the script to error and exit
set -euxo pipefail

### Assure the tmux session exists
#
# In a test scenario Instuqt does not run the user shell for the
# challenge, which means the tmux session is never established. We
# need to session for the solve scripts for other challenges to
# succeed, so let's create it here.
#

if ! tmux has-session -t shell ; then
tmux new-session -d -s shell su - replicant
fi

tmux send-keys -t shell export SPACE 'THIS="the way"' ENTER
83 changes: 83 additions & 0 deletions instruqt/shared-env-template/02-using-the-template/assignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
slug: using-the-template
id: zkvoizkrqisu
type: challenge
title: Using the Template
teaser: Some guidance on using the template
notes:
- type: text
contents: How and Why to Use This Template
tabs:
- title: Shell
type: terminal
hostname: shell
difficulty: basic
timelimit: 300
---

✨ Uses
=======

## Environment Variables

Only one shell runs across all challenges. This means the values of
environment variables persist from challenge to challenge without the
user setting them into their `.bashrc`.

## Long Running Commands

If a challenge needs to end with long running command (for example
downloading an airgap bundle or starting a kURL install), tell the user
they can click **Check** and leave the comamnd running. When the next
challenge starts then their shell will look the same and the command
will still be running (unless it happened to finish during the Cleanup
and Check scripts).

🔄 Lifecycle Scripts
====================

Lifecycle scripts can take advantage of `tmux` to read and write from
the learner's session. This is useful in Check scripts, for example,
to read what the user has typed and what the output was from those
commands. It also means that Setup, Cleanup, and Solve scripts can
type into the users shell to run commands.

Here are a couple of `tmux` commands to be aware of to interact with
the session:

`tmux capture-pane`
: This command the history of what's been done in the learner's
shell so you can intertact with it, for example to test whether
they typed the commands you expected

`tmux save-buffer`
: After you've captured what the learner has done, you can use the
`save-buffer` command to access it. The combination of the two is
useful in Check scripts

`tmux send-keys`
: Allows you to send keystrokes to the learner's shell. You have to
be explicit about charaters like `SPACE` and `ENTER` so that they
are sent to. This can be great for Solve scripts.

`tmux clear-histry`
: Clears the scrollback history (not the shell history) to keep
what's captured by `capture-pane` nice and fresh.

🧪 Did It Work?
===============

Remember that variable we set in the last step? Let's make sure it
stuck around like we expected.

```
echo $THIS
```

It should have, so you should see

```text
replicant@shell:~$ echo $THIS
the way
replicant@shell:~$
```
15 changes: 15 additions & 0 deletions instruqt/shared-env-template/02-using-the-template/check-shell
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash

# This set line ensures that all failures will cause the script to error and exit
set -euxo pipefail

# save the entire session to check user inputs and outputs
tmux capture-pane -t shell -S -
SESSION=$(tmux save-buffer -)

# check for disconnection
if ! grep -qE 'echo' <(echo ${SESSION}) ; then
fail-message 'Please make sure you can still access `$THIS`'
exit 1
fi

11 changes: 11 additions & 0 deletions instruqt/shared-env-template/02-using-the-template/cleanup-shell
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh
#
# This script runs when the platform cleanup the challenge.
#
# The platform determines if the script was successful using the exit code of this
# script. If the exit code is not 0, the script fails.
#

echo "This is the cleanup script"

exit 0
18 changes: 18 additions & 0 deletions instruqt/shared-env-template/02-using-the-template/solve-shell
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash

# This set line ensures that all failures will cause the script to error and exit
set -euxo pipefail

### Assure the tmux session exists
#
# In a test scenario Instuqt does not run the user shell for the
# challenge, which means the tmux session is never established. We
# need to session for the solve scripts for other challenges to
# succeed, so let's create it here.
#

if ! tmux has-session -t shell ; then
tmux new-session -d -s shell su - replicant
fi

tmux send-keys -t shell echo SPACE '$THIS' ENTER
111 changes: 111 additions & 0 deletions instruqt/shared-env-template/03-tips-and-tricks/assignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
---
slug: tips-and-tricks
id: 8axg0ijvq0ru
type: challenge
title: Tips and Tricks
teaser: Some tips and recomended defaults
notes:
- type: text
contents: Making the most of this template
tabs:
- title: Shell
type: terminal
hostname: shell
difficulty: basic
timelimit: 300
---

💡 Tips and Tricks
==================

## Checking What Happened

As mentioned above, you can capture what went on during a challenge
using a pair of `tmux` commands. Here's an example of how you might
capture the entire history of a challenge:

```shell
# save the entire session to check user inputs and outputs
tmux capture-pane -t shell -S -
SESSION=$(tmux save-buffer -)
```

You may want to capture a subset of what was done. In this example,
we get the last ten lines.

```shell
# save the last _LINES_ lines to check inputs and outputs
LINES=10
HEIGHT=$(tmux list-panes -F "#{pane_height}")
SESSION=$(tmux capture-pane -t shell -S $(expr $HEIGHT - $LINES) -p)
```

In either case, you can use `grep` or either shell commands to examine
`SESSION` and see if they user followed you instructions.

## Entering Commands in Solve

Your solve command can run as usual and not interact with the learner's
shell. That's the recommended approach for most challenges. Some solve
scripts can be done that way, for example setting environment variables
you'll need in another challenge. In that case, you can use `send-keys`
in your solve script to change the environment.

```shell
tmux send-keys -t shell export SPACE REPLICATED_APP=wordpress-civet ENTER
```

Note that since the shell isn't restarted with each challenge, you'll
need to do this for any variable you want to persist in the environment
after the first challenge.

## Default Cleanup Script

The following is a good default cleanup script. Use this to make the
shell look clean and new just like it does in tracks that don't use
`tmux`.

```shell
#!/usr/bin/env bash

# This set line ensures that all failures will cause the script to error and exit
set -euxo pipefail

# clear the tmux pane and scrollback to look like a fresh shell
tmux clear-history -t shell
tmux send-keys -t shell clear ENTER
```

This is the cleanup script for this track, when you click **Check** to
move on to the next challenge you can see it's results.

## Testing

If you use any `tmux` commands in your lifecycle scripts, you will need
to make sure that the session is created if you want to run tests with
`instruqt track test`. This is necessary since the test lifecycle does not
run the shell commands in `config.yml`.

Put the following early in your `solve-shell` script for your first
challenge to make sure testing behaves.

```shell
### Assure the tmux session exists
#
# In a test scenario Instuqt does not run the user shell for the
# challenge, which means the tmux session is never established. We
# need to session for the solve scripts for other challenges to
# succeed, so let's create it here.
#

if ! tmux has-session -t shell ; then
tmux new-session -d -s shell su - replicant
fi
```

🏁 Finish
=========

You've now had a bit of a tour through this template. You're ready to
base a lab on it. Feel free to browse through the source code to see
examples of these tips in action.
10 changes: 10 additions & 0 deletions instruqt/shared-env-template/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: "3"
containers:
- name: shell
image: gcr.io/kots-field-labs/shell
shell: tmux new-session -A -s shell su - replicant
virtualmachines:
- name: cluster
image: instruqt/k3s-v1-25-0
shell: /bin/bash
machine_type: n1-standard-1
Loading

0 comments on commit 882ee37

Please sign in to comment.