<a id="exp-workshop-concepts"></a>

# Workshop concepts

<!-- @artefact project -->
<!-- @artefact workshop (container) -->
<!-- @artefact workshop definition -->

A *workshop*
(lowercase; not to be confused with **Workshop** itself)
is a container that enables consistent environment builds.
A workshop is defined by a single YAML file
that acts as the blueprint for **Workshop** to implement at launch time.
It describes how individual components fit together
to create a cohesive development environment.
A *project* is the working directory where workshop definitions are placed.
When you start a workshop, the project directory is mounted inside it,
so storing repositories, code, or data such as models in the project directory
enables you to use them inside the workshop.

Currently, these containers are hosted by [LXD](https://documentation.ubuntu.com/lxd/latest/),
but it’s not recommended to rely on this implementation detail.

<a id="exp-workshop-status"></a>

## Workshop status

<!-- @artefact workshop status -->

A workshop’s lifecycle can see it switch between several statuses:

| State     | Description                                                                                                                                                                                                                                                                         |
|-----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| *Off*     | Just defined, not operational;<br/>the workshop container does not exist yet.                                                                                                                                                                                                       |
| *Ready*   | Operational;<br/>the workshop container is running and ready for use.                                                                                                                                                                                                               |
| *Stopped* | Operational;<br/>the workshop container is stopped and can be restarted.                                                                                                                                                                                                            |
| *Pending* | Not operational;<br/>the workshop container is running<br/>but is being updated and is not ready for use.                                                                                                                                                                           |
| *Waiting* | Operational;<br/>the workshop container is running and available for command execution,<br/>typically for debugging a launch or refresh error;<br/>the current [change](https://ubuntu.com/workshop/docs//explanation/workshops/changes-tasks.md#exp-changes-tasks) is in progress. |
| *Error*   | Not operational;<br/>the workshop is in a nonfunctional state due to an error.                                                                                                                                                                                                      |

Status diagrams in the [See also]() section below
provide more details of valid transitions.

<a id="exp-workshop-lifecycle"></a>

## Launch, refresh, and restore

<!-- @artefact workshop launch -->
<!-- @artefact workshop refresh -->
<!-- @artefact workshop restore -->

Three commands move a workshop between the statuses listed above:

- **workshop launch** builds a workshop for the first time
- **workshop refresh** updates an existing workshop
  to match its current definition
- **workshop restore** rolls a workshop back
  to the state it had right after its last successful launch or refresh.

The table below summarizes when to use each command
and what it does to the workshop and to its interface connections:

| Command              | Use case                                                                                   | Workshop effect                                                                                        | Connections effect                                                                                                                       |
|----------------------|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|
| **workshop launch**  | The workshop has never been built<br/>and is in the *Off* state.                           | Builds the workshop from scratch,<br/>installing the base image and each SDK in order.                 | All auto-connect candidates are evaluated<br/>and connected for the first time.                                                          |
| **workshop refresh** | The definition has changed<br/>and you want those changes applied to the running workshop. | Reuses snapshots for SDKs whose configuration is unchanged;<br/>reinstalls the rest from scratch.      | Re-evaluates auto-connect against the new definition;<br/>**any connections established manually after launch are dropped**.             |
| **workshop restore** | You want to discard runtime drift in the workshop<br/>and return it to a known-good state. | Rolls the workshop filesystem back<br/>to the snapshot taken at the last successful launch or refresh. | Re-evaluates auto-connect against the unchanged definition;<br/>**any connections established manually since the snapshot are dropped**. |

<a id="exp-workshop-launch"></a>

### Launch

First, **workshop launch** is the one-time builder.
It applies the workshop definition layer by layer,
taking a ZFS snapshot after each SDK
so that later operations can reuse the work;
see [SDKs](#exp-workshop-definition-sdks) for the layering details.

Once a workshop has been launched,
running **workshop launch** against it again fails with no effect.
To apply changes from the definition to a launched workshop,
use **workshop refresh**.

<a id="exp-workshop-refresh"></a>

### Refresh

Next, **workshop refresh** updates an existing workshop
to match its current definition file.
The workshop must be in the *Ready* state.

Refresh is incremental:
SDKs whose configuration is unchanged
are kept as-is and restored from their snapshots
without re-running their `setup-base` hook,
while SDKs that have been **added, removed, or changed in the definition**
go through a fresh installation.

The `save-state` hook of each surviving SDK
runs before the rebuild,
and the matching `restore-state` hook runs after it,
so SDK state kept by these hooks carries across the refresh.

<a id="exp-workshop-restore"></a>

### Restore

Finally, **workshop restore** runs the same machinery as refresh,
but uses the workshop’s state from the last successful launch or refresh
as both the source and the target,
ignoring any edits made to the workshop definition since then.
The workshop must be in the *Ready* state.
No SDK changes are applied;
the container filesystem is rolled back
to the snapshot it had right after the last successful launch or refresh,
discarding any changes made inside the workshop since then.

Restore is the right tool
when the workshop has inadvertently drifted at runtime
(for example, packages installed ad-hoc inside the container)
and you want a clean slate without rebuilding from scratch.

<a id="exp-workshop-definition"></a>

## Workshop definition

<!-- @artefact workshop base image -->
<!-- @artefact workshop definition -->

The workshop definition is a YAML file
that lists the base image of the workshop
and the specific components installed on top of it.
It acts as a single source of truth about the workshop.
It usually takes a few tries to produce a definition that works for your project,
so you can edit and update the file iteratively.

A simple workshop definition might look like this:

```yaml
name: dev
base: ubuntu@22.04
sdks:
  - name: go
    channel: "1.26"
```

<!-- @artefact SDK -->
<!-- @artefact interface -->

It specifies a *base* and an *SDK*.
A more complete definition would usually list several SDKs
that use different [interfaces](https://ubuntu.com/workshop/docs//explanation/interfaces/concepts.md#exp-interface-concepts),
software packages, and [hooks](https://ubuntu.com/workshop/docs//explanation/sdks/concepts.md#exp-sdk-hooks).

<a id="exp-base"></a>

## Base image

The base specifies the underlying operating system image,
such as a particular Ubuntu LTS release.
This is the first layer of the workshop,
upon which all other components are applied.

For details on how the images are handled behind the scenes,
see [Images](https://ubuntu.com/workshop/docs//explanation/architecture/components.md#exp-arch-images).

<a id="exp-workshop-definition-sdks"></a>

## SDKs

The `sdks` section brings in the features and tools,
layering them on top of the base image.
Each SDK listed here is a bundle of code, data, and configurations,
prepackaged with **SDKcraft** to be used with **Workshop**;
see [SDK concepts](https://ubuntu.com/workshop/docs//explanation/sdks/concepts.md#exp-sdk-concepts) for details.

This layering is not just conceptual;
at launch time,
**Workshop** uses ZFS snapshots to separate the SDKs:

1. The `base` OS is installed.
2. The `system` SDK is installed,
   and its `setup-base` hook is run.
3. A ZFS snapshot is taken,
   and cloned to create a new ZFS file system.
4. For each subsequent SDK
   in the order of their appearance on the `sdks` list,
   its `setup-base` hook is run
   and another snapshot is taken and cloned.

This will create a chain of snapshots,
where each one represents a cumulative layer of the workshop.
Snapshots makes operations like refreshing or reverting a workshop very fast,
as **Workshop** can simply restore a previous snapshot
instead of rebuilding the environment from scratch.
No snapshots are created for other hook types,
such as `setup-project` or `save-state`.

In order to restore an old snapshot,
newer snapshots must be destroyed first.
If refreshing fails,
the workshop reverts to its previous state.
The cloned file systems are used to restore the deleted snapshots.

For details on how **Workshop** leverages ZFS,
see [Storage backends](https://ubuntu.com/workshop/docs//explanation/architecture/components.md#exp-arch-zfs-storage).

<a id="exp-workshop-definition-connections"></a>

## Plugs, slots, connections

<!-- @artefact interface plug -->
<!-- @artefact interface slot -->
<!-- @artefact interface connection -->

Once all the SDKs are installed,
they often need to communicate with each other or with the host system.
This is handled by establishing interface connections
between plugs (service consumers) and slots (service providers);
see [Interface concepts](https://ubuntu.com/workshop/docs//explanation/interfaces/concepts.md#exp-interface-concepts) for details.

These plugs and slots can be defined in two ways:

- By the SDK itself:
  An SDK can define its own plugs and slots in its `sdk.yaml` file.
  These are the standard capabilities the SDK offers.
  For Store SDKs,
  the `sdk.yaml` file is generated by **SDKcraft**;
  plugs and slots are copied as-is from `sdkcraft.yaml`.
- Grafted by the workshop:
  A workshop definition can add plugs or slots to an SDK it references.
  This is done within an SDK’s entry in the `workshop.yaml` file.
  Grafting extends an SDK’s capabilities locally,
  possibly without the SDK publisher’s involvement or expectation;
  the user can add interface elements that the publisher didn’t anticipate,
  reducing the need for manual post-launch configuration.

The `connections` section of the definition can explicitly link
any plugs and slots available within the workshop,
on top of what the [auto-connection mechanism](https://ubuntu.com/workshop/docs//explanation/interfaces/concepts.md#exp-interface-connections)
in **Workshop** provides:
eventually, all interface connections are
[resolved, validated, and established](https://ubuntu.com/workshop/docs//explanation/interfaces/concepts.md#exp-interfaces-validation)
in a single task *after* all the SDK layers have been created,
because all components must be in place before the wiring can be done.

This example adds a slot, a plug, and a connection to its SDKs:

```yaml
base: ubuntu@22.04
name: dev
sdks:
  - name: tensorflow
    plugs:
      cuda:
        interface: mount
        workshop-target: /usr/local/cuda/lib64
  - name: imagenet
    slots:
      images:
        interface: mount
        workshop-source: $SDK/images
  - name: cuda
connections:
  - plug: tensorflow:cuda
    slot: cuda:libs
```

This extends the `tensorflow` SDK
with a standard path for CUDA runtime libraries.
In `connections`,
we explicitly connect the `cuda` plug,
newly defined under the `tensorflow` SDK,
to the `libs` slot from the `cuda` SDK.
Thus, upon workshop creation,
the plug will be connected
not to a default system SDK location on the host
(for example, `.../<ID>/<WORKSHOP>/...`),
but to a library path *inside* the workshop,
which is set by `workshop-target`.

Mind that the connection established in this way
is no different from those created via the command line.

<a id="exp-workshop-connection-lifecycle"></a>

### Connections across refresh and restore

Interface connections fall into three observable categories,
each treated differently when a workshop is refreshed or restored:

- *Auto-connections* are established at launch
  from the SDK’s auto-connect rules
  and from the `connections` section of the workshop definition.
- *Manual runtime connections* are added with **workshop connect**
  after the workshop has been launched
  and are not present in the workshop definition.
- *Manual disconnects of an auto-connection* are made
  with **workshop disconnect**
  against a connection that the workshop established by itself.

The table below summarizes how each category is treated
by **workshop refresh** and **workshop restore**:

| Connection type and state                                           | After **workshop refresh**   | After **workshop restore**                |
|---------------------------------------------------------------------|------------------------------|-------------------------------------------|
| Auto-connection still valid in the new definition                   | Re-established               | Re-established                            |
| Auto-connection whose plug or slot is removed in the new definition | Dropped                      | Not applicable; definition doesn’t change |
| Manual runtime connection added with **workshop connect**           | Dropped                      | Dropped                                   |
| Manually disconnected auto-connection                               | Stays disconnected           | Stays disconnected                        |

The practical consequence is that
runtime use of **workshop connect**
should be reserved for short-lived experimentation:
to make a connection that survives a refresh,
add it to the `connections` section of the workshop definition.
Conversely, a deliberate **workshop disconnect**
is preserved across refreshes and restores,
so once a default auto-connection has been turned off,
it stays off until explicitly reconnected.

<a id="exp-workshop-definition-actions"></a>

## Actions

<!-- @artefact workshop actions -->

Another optional part of a workshop definition is the `actions` section;
it contains named shell scripts to be copied and executed inside the workshop.
This section provides a degree of convenience,
allowing the users to define simple aliases
for longer or more complex shell commands
that they expect to run frequently inside the workshop,
right in the definition file.

Because action bodies are **bash** scripts,
they receive the trailing arguments of **workshop run**
as standard positional parameters.
Use `"$@"` to forward every argument
and `"$1"`, `"$2"`, and so on to pick individual ones.

Actions are not part of the layered snapshot system at all.
They stay in the definition,
and are parsed by the [daemon](https://ubuntu.com/workshop/docs//explanation/architecture/components.md#exp-arch-daemon)
every time the **workshop run** command is executed.
This means the users can add or modify actions and use them immediately,
without needing to refresh or restart the workshop.

The following example adds four actions,
`lint`, `shellcheck`, `unit`, and `cover`,
intended as utility helpers for a development environment:

```yaml
name: dev
base: ubuntu@24.04
sdks:
  - name: go
    channel: 1.26
actions:
  lint: |
    golangci-lint run  --out-format=colored-line-number -c .golangci.yaml
  shellcheck: |
    git ls-files | file --mime-type -Nnf- | grep shellscript | cut -f1 -d: | xargs shellcheck --check-sourced --external-sources
  unit: |
    go test "$@" ./...
  cover: |
    go test ./... -coverprofile=coverage.out
    go tool cover -html=coverage.out
```

To run these actions, you use the **workshop run** command:

```console
$ workshop run dev -- lint
```

When you thus invoke an action, it’s injected into the workshop
and executed there in a fashion similar to **workshop exec**.
Even if you update the `actions` section in the definition,
there’s no need to refresh the workshop to use the updated action;
it’s available immediately.

For a quick reference of the actions in your workshop,
run **workshop actions**:

```console
$ workshop actions dev
```

This mechanism avoids the need to maintain helper scripts manually,
ensuring instead that they are stored with the rest of the workshop’s metadata.

## Origins and locations

<!-- @artefact system SDK -->
<!-- @artefact sketch SDK -->
<!-- @artefact in-project SDK -->

Workshop components, including the many SDK types,
originate from different sources
and end up in multiple locations.
The workshop definition file acts as a blueprint
that brings these distributed components together:

| Component                                                                                            | Origin                                                           | Storage location                                                                      | Description                                                                                                                                                                                 |
|------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|---------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Workshop definition](#exp-workshop-definition)                                                      | Created manually in YAML by **Workshop** users                   | Project directory on the host                                                         | Defines the workshop environment<br/>and how it should be built and run.                                                                                                                    |
| [System SDK](https://ubuntu.com/workshop/docs//explanation/sdks/concepts.md#exp-system-sdk)          | Built into **Workshop**                                          | Automatically exposed in the workshop at launch                                       | Provides host system integration capabilities<br/>(mounts, camera, GPU, networking, and so on).                                                                                             |
| [Regular SDKs](https://ubuntu.com/workshop/docs//explanation/sdks/concepts.md#exp-sdk-concepts)      | Distributed via the SDK Store<br/>with `channel` versioning      | Downloaded, cached on the host,<br/>and installed in the workshop at launch           | These SDKs are the most common variation,<br/>providing tools and libraries from external publishers.                                                                                       |
| [In-project SDKs](https://ubuntu.com/workshop/docs//explanation/sdks/concepts.md#exp-in-project-sdk) | Created manually or ejected with **workshop sketch-sdk --eject** | Defined in the project directory on the host;<br/>installed in the workshop at launch | Custom SDKs, specific to the workshop;<br/>these are defined within the project directory<br/>and can be identified by the `project-` prefix in their names<br/>in the workshop definition. |
| [Sketch SDK](https://ubuntu.com/workshop/docs//explanation/sdks/concepts.md#exp-sketch-sdk)          | Generated with **workshop sketch-sdk**                           | Defined under `$XDG_DATA_HOME/workshop/`;<br/>installed in the workshop at refresh    | Encapsulates local, transient logic<br/>in an SDK that can be quickly iterated upon<br/>and later ejected as an in-project SDK.                                                             |
| [Actions](#exp-workshop-definition-actions)                                                          | Defined by **Workshop** users                                    | Listed directly in the workshop definition                                            | Utility scripts, specific to the workshop;<br/>these are injected into the workshop at run time.                                                                                            |

## See also

Explanation:

- [Interfaces](https://ubuntu.com/workshop/docs//explanation/index.md#exp-interfaces)
- [Projects](https://ubuntu.com/workshop/docs//explanation/workshops/projects.md#exp-projects)
- [SDKs](https://ubuntu.com/workshop/docs//explanation/index.md#exp-sdks)

How-to guides:

- [How to add actions to your workshop](https://ubuntu.com/workshop/docs//how-to/customize-workshops/add-actions.md#how-add-actions)
- [Customize workshops](https://ubuntu.com/workshop/docs//how-to/index.md#how-use-workshops)

Reference:

- [workshop (CLI)](https://ubuntu.com/workshop/docs//reference/cli/workshop.md#ref-workshop-cli)
- [workshop actions](https://ubuntu.com/workshop/docs//reference/cli/workshop.md#ref-workshop-actions)
- [workshop connections](https://ubuntu.com/workshop/docs//reference/cli/workshop.md#ref-workshop-connections)
- [Workshop definition](https://ubuntu.com/workshop/docs//reference/definition-files/workshop-definition.md#ref-workshop-definition)
- [workshop launch](https://ubuntu.com/workshop/docs//reference/cli/workshop.md#ref-workshop-launch)
- [workshop refresh](https://ubuntu.com/workshop/docs//reference/cli/workshop.md#ref-workshop-refresh)
- [workshop restore](https://ubuntu.com/workshop/docs//reference/cli/workshop.md#ref-workshop-restore)
- [workshop run](https://ubuntu.com/workshop/docs//reference/cli/workshop.md#ref-workshop-run)
- [Workshop status diagrams](https://ubuntu.com/workshop/docs//reference/workshop-status.md#ref-workshop-status)
