This is an example three challenge Instruqt track, designed to show off the basics of how a track works.
You must have a Team Secret called
IGGYS_SSH_PRIVATE_KEY_BASE64 which was created from the output of running
TMP=$(mktemp -d); ssh-keygen -t ed25519 -f "${TMP}/id_ed25519" -N "" >/dev/null; base64 -b 0 < "${TMP}/id_ed25519"; rm -rf "${TMP}"
NOTE: Your variant of base64 or equivalent may have different command-line flags,
the output must have no line breaks.
The basic unit of content on Instruqt is a track. This is what a learner interacts with. Every time a learner starts a track, they get a unique, isolated instance of that track. Track instances are ephemeral: once the track is finished, or a timeout reached, the track instance is deprovisioned and the resources within it go away.
An Instruqt track has three primary components:
- A sandbox which contains the resources this track needs, like virtual machines, containers, virtual web browsers, and cloud accounts
- Challenges which are the individual steps a learner progresses through to complete a track, which can also reference assets like images or videos.
- Lifecycle scripts which are scripts that run at well-defined points within the lifecycle of a track, and are responsible for setting and cleaning up tracks, and setting up, cleaning up, solving, and checking challenges.
Rounding this out are two metadata files,
config.yml
and track.yml.
We'll cover each of these in turn.
The track.yml file contains various track metadata, such as its title and description,
a "slug" (the URL component which identifies this particular track), and configuration
for the owner organization, the developers
for this track, and configuration of timeouts and how challenges are displayed. You
can also set track tags, a label attached
to a lab which can be used to group similar content together and filter on the team
content page.
Rounding out this file are some autogenerated fields, id and checksum, which
the Instruqt platform will set for you.
The config.yml file defines the sandbox resources required for this track. In our
track here, we have three types of resources.
The virtualmachines key defines any number of virtual machines
which will be provisioned in each instance of a track when it is launched. In our
case, we have two, a VM named workstation and a VM named fileserver. These names
are used to reference VMs within challenge assignments (for example, you can have a
terminal window in a challenge assignment, and you specify which VM the terminal
runs on). Additionally, within a sandbox you can use the VM names as
dns names.
While we do not use them in this track, you can also use containers
Instruqt leverages the Google Cloud Platform, and both VMs and containers can reference public images, or you can create custom VM or container. Instruqt supports both Linux and Windows VMs, including support for Remote Desktop within a challenge.
This track uses two custom VM images. If you are curious, you can check out the Packer directory, which contains the definitions of the custom VM images used in this track. Packer is not an Instruqt product, and is outside the scope of this annotation. You can build custom VM images with any tool which allows you to produce GCP images, or you can use the built-in Instruqt Host Images tool.
If you install a bunch of software or have a lot of setup tasks which are the same for every instance of a track you are using the VM image in, it makes sense to build a custom image, as you will do that work once, and a track instance will be able to start immediately with everything installed, etc. But, if you only have a small amount of things to customize, you can easily do it in a track startup script (discussed later).
A virtual web browser
allows you to display a website in a tab within a track challenge. Instruqt
also supports websites
directly; but if a given website will not run within an iframe you must
use a virtual web browser.
Here we configure our virtual web browser to show the rsync documentation website.
A secret is a way to store and use sensitive values in a lifecycle script (discussed later). You might store credentials or API tokens in a secret. Secrets are defined at a the team level by team owners, and can be used by multiple tracks.
Secrets show up as environment variables in lifecycle scripts, and only lifecycle scripts. They are not visible to the end learner unless you copy them somewhere accessible by the learner.
In our track, we create a user iggy on both the fileserver and
workstation, and want that user to be able to ssh between both hosts,
so we store an ssh private key as a secret and use it during track
setup scripts to install it in the proper place.
While not used in this track, you can also include any number of cloud accounts in a track.
Within a given track are one or more challenges.
A challenge is an individual step the learner must complete along the
way to finish the track. In our example track, we have three challenges,
an introduction to rsync, a demonstration of incremental updates, and
selective transfers. We will talk about challenge lifecycle scripts later
on, for now, the key component in each challenge is the assignment.md file.
We will walk through this file for Challenge 02 - Incremental Update.
At the top of the assignment.md file is a section of yaml which
contains the challenge metadata, and defines which tabs are exposed
in this challenge:
- The
titleis the displayed title of this challenge - The
slugis the URL fragment which references this particular challenge. - The
tabssection defines the tabs which are visible in this challenge. A tab can point at a website, a service running on a VM or container within this track instance sandbox, a simple code editor, a terminal, or a virtual browser. In this particular challenge, we have one tab to a terminal on theworkstation host, and another to a virtual browser to display the rsync documentation webpage.
The remainder of the assignment.md file, after the second ---, is the assignment,
the instructions presented to the learner as they go through this challenge. This is
in Markdown, which you can edit by hand or use the built-in Markdown editor.
Note that the first item in the challenge 02 assignment text is
. This is a normal Markdown
reference to show an image or video, which leverages the Instruqt
platform support for assets.
Any image or video placed in the assets/ directory in your track
and referenced like this example will be automatically uploaded and
rendered in your track.
Note also that rather than hardcoding the directory paths the learner is supposed to reference, you see the following:
[[ Instruqt-Var key="WORKSTATION_DST_DIR" hostname="workstation" ]]
These are runtime variables,
which allow you to set variables in your sandbox hosts, and leverage them
in your assignment text. As you'll see later when we cover lifecycle scripts,
we set the runtime variables WORKSTATION_DST_DIR and FILESERVER_SRC_DIR.
Here we do it for ease of updating the track if we decide to change the
source and destination directories, but you can use these for any
dynamic content you may have in your track. For example, you may provision
temporary credentials in a lifecycle script, if you set them as a runtime
variable you can surface those to your learner in a challenge assignment.
The third component of an Instruqt track are lifecycle scripts. These are scripts which run at certain well defined points of a track's lifecycle, and are divided into two categories: track-level scripts and challenge-level scripts.
These scripts run behind the scenes, invisible to your learner, and can be leveraged by you to do things which have to happen per instance of a running track. These tend to be shell scripts, although on Linux sandbox hosts and containers they can be scripting language which is installed in the sandbox (for example, Python or Ruby), on Windows VM PowerShell is supported.
Track-level scripts are placed in the track_scripts/ directory
at the top level of your track. They can run at track setup, when
a sandbox is created, and at track cleanup, when the sandbox is
deprovisioned. Each sandbox host can have it's own script, setup scripts
are named setup-<host name>, and cleanup scripts are named
cleanup-<host name>. In our track, we do not have any track cleanup
scripts, only setup scripts, but both of our sandbox hosts have
setup scripts.
Since our hosts are called fileserver and workstation, our
setup scripts are setup-fileserver and setup-workstation, respectively.
In our example track here, we mostly use the track setup scripts
to install an SSH key for our test user iggy, but you could use
these scripts to do any setup or cleanup which has to be done when
the track sandbox is running. For tracks which use cloud accounts,
this tends to be where resources are provisioned within those
accounts; since each track gets its own ephemeral cloud account,
that setup cannot happen until the sandbox is created.
While we do not have any cleanup scripts here, these tend to be
used for cleanup of any external ephemeral resources. For example,
if you create a temporary account on a SaaS platform for use in
a track, you can clean it up and keep things tidy in a track cleanup
script.
Our setup-workstation script also leverages two Instruqt features,
secrets, and runtime variables.
We covered how you add secrets
to a track above when we looked at the track config.yml. The
setup-workstation script shows how you use them within a lifecycle
script: they show up as environment variables. So, when we added this
section to our config.yml:
secrets:
- name: IGGYS_SSH_PRIVATE_KEY_BASE64
we make available an environment variable which we use in the
setup-workstation script on this line:
echo "${IGGYS_SSH_PRIVATE_KEY_BASE64}" | base64 -d > id_ed25519
Note that only secrets configured in your config.yml are available
to your track, you may have several secrets configured in your Instruqt
team but only the ones you ask for will be exposed to your lifecycle
scripts.
Note also that these values are available only in the lifecycle scripts. They are not available to the learner, even if there have a terminal on a given sandbox host.
We previously talked about using Runtime variables in the Challenge Assignment section, but did not cover how to set them.
agent variable set FILESERVER_SRC_DIR "${FILESERVER_SRC_DIR}"
agent variable set WORKSTATION_DST_DIR "${WORKSTATION_DST_DIR}"
This section of the setup-workstation track lifecycle script
is how we set those runtime variables. As mentioned above, we
can use runtime variables in challenge assignments to have dynamic
assignment content. It is also possible to use runtime variables
within other lifecycle scripts after they are set, as long as
you are on the same sandbox host. For example, in a later
lifecycle script which runs on the workstation host you could run
the command agent variable get FILESERVER_SRC_DIR to retrieve
the value set above.
In addition to track lifecycle scripts, each challenge has its own lifecycle scripts, which again can run on any (or multiple) sandbox hosts.
A challenge setup script runs when the learner starts a challenge,
and they are called setup-<host name>. Looking again at this track's
second challenge, the setup-workstation script runs on the workstation
host, and does setup specific to this challenge. In our case, we want
to set up a scenario where certain files are removed and we want to
restore them, our setup script copies all of the files and then deletes
the ones we want, to set up the challenge for the user.
A challenge check script runs when the learner clicks on the "Check"
button in an assignment, and they are called check-<host name>. A
check script allows you to give your learner feedback by allowing you to
verify that they completed the steps necessary to successfully complete
this challenge. If the return code of the script is 0, the check successfully
completed and the learner can move on, any other return code indicates
an error. You can use the fail-message
helper script to return feedback to the user.
A challenge solve script has two uses. First, you can enable skipping within challenges, which can be useful if a learner is stuck on a particular challenge, or if they return to a previously partially completed track and want to return to where they left off.
Second, they can be used for track testing,
by allowing you to simulate a learner's actions when using the instruqt track test
command.
These scripts are called solve-<host name>; in this particular challenge
we have a solve-workstation script. The challenge 03 solve script
is a particularly good example:
# We run *every* command we tell iggy to run
sudo -u iggy rsync -av --stats --include '*.lbl' "fileserver:${FILESERVER_SRC_DIR}" "${WORKSTATION_DST_DIR}"
sudo -u iggy rm -rf "${WORKSTATION_DST_DIR}"
sudo -u iggy rsync -av --stats --exclude '*' "fileserver:${FILESERVER_SRC_DIR}" "${WORKSTATION_DST_DIR}"
sudo -u iggy rsync -av --stats --exclude '*' --include '*.lbl' "fileserver:${FILESERVER_SRC_DIR}" "${WORKSTATION_DST_DIR}"
sudo -u iggy rsync -av --stats --include '*.lbl' --exclude '*' "fileserver:${FILESERVER_SRC_DIR}" "${WORKSTATION_DST_DIR}"
sudo -u iggy rsync -av --stats --include '*.lbl' --include '*/' --exclude '*' "fileserver:${FILESERVER_SRC_DIR}" "${WORKSTATION_DST_DIR}"
A challenge cleanup script runs at the end of each challenge,
and can be used to cleanup any actions performed during that challenge
if necessary before moving to the next challenge. These scripts are
called cleanup-<host name>.