Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions .github/workflows/spread.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,23 @@ jobs:
env:
SUITE_PATHS: ${{ matrix.suite.paths }}
LXD_CHANNEL: ${{ inputs.lxd_channel || '6/stable' }}
ARTIFACTS: ${{ github.workspace }}.cover
run: |
mkdir ${{ github.workspace }}.cover
mkdir "$ARTIFACTS"
read -r -a suite_paths <<< "$SUITE_PATHS"
spread -artifacts=${{ github.workspace }}.cover "${suite_paths[@]}"
spread -artifacts="$ARTIFACTS" "${suite_paths[@]}"

# Workaround https://github.com/actions/upload-artifact/issues/546
cd "$ARTIFACTS/lxd:ubuntu-24.04:tests" || exit 0
tasks=(*/*/)
for task in "${tasks[@]}"; do
suite=${task%%/*}
task=${task#*/}
task=${task%/}
safe=${task//:/.}
[ "$task" = "$safe" ] || (cd "$suite" && mv "$task" "$safe")
done
shell: bash
- name: Upload Coverage
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
Expand Down
15 changes: 8 additions & 7 deletions client/workshop.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,14 @@ type Workshops struct {
}

type WorkshopInfo struct {
ProjectId string `json:"project-id"`
Name string `json:"name"`
Base string `json:"base"`
Status string `json:"status"`
Sdks []*Sdk `json:"sdks,omitempty"`
Hostname string `json:"hostname,omitempty"`
Notes []string `json:"notes,omitempty"`
ProjectId string `json:"project-id"`
Name string `json:"name"`
Base string `json:"base"`
Confinement string `json:"confinement"`
Status string `json:"status"`
Sdks []*Sdk `json:"sdks,omitempty"`
Hostname string `json:"hostname,omitempty"`
Notes []string `json:"notes,omitempty"`
}

type WorkshopFile struct {
Expand Down
1 change: 1 addition & 0 deletions cmd/workshop/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ func (c *CmdInfo) Run(cmd *cobra.Command, av []string) error {
fmt.Fprintf(w, "hostname:\t%s\n", workshop.Hostname)
}
fmt.Fprintf(w, "status:\t%s\n", strings.ToLower(workshop.Status))
fmt.Fprintf(w, "confinement:\t%s\n", workshop.Confinement)

// get the workshop notes
notes := workshop.Notes
Expand Down
50 changes: 29 additions & 21 deletions cmd/workshop/info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func (m *workshopInfo) SetUpTest(c *check.C) {
var mockWorkshopWithSdks = `{"type":"sync","status-code":200,"status":"OK","result":{
"name":"ws",
"base":"ubuntu@22.04",
"confinement":"container",
"project-id":"42424242",
"status":"Error",
"hostname":"ws.sdkcraft.wp",
Expand Down Expand Up @@ -94,12 +95,13 @@ func (m *workshopInfo) TestWorkshopInfo(c *check.C) {

err = cmd.Run(cmd.Command(), nil)
c.Assert(err, check.IsNil)
c.Assert(m.stdout.String(), check.Matches, fmt.Sprintf(`name: ws
base: ubuntu@22.04
project: %s
hostname: ws\.sdkcraft\.wp
status: error
notes: missing-project
c.Assert(m.stdout.String(), check.Matches, fmt.Sprintf(`name: ws
base: ubuntu@22.04
project: %s
hostname: ws\.sdkcraft\.wp
status: error
confinement: container
notes: missing-project
sdks:
go:
tracking: latest/edge
Expand All @@ -114,6 +116,7 @@ sdks:
var mockWorkshopWithHealth = `{"type":"sync","status-code":200,"status":"OK","result":{
"name":"ws",
"base":"ubuntu@22.04",
"confinement":"container",
"project-id":"42424242",
"status":"Pending",
"notes":["workshop-note"],
Expand Down Expand Up @@ -152,11 +155,12 @@ func (m *workshopInfo) TestWorkshopInfoWithSdkHealthReport(c *check.C) {

err := cmd.Run(cmd.Command(), []string{workshop})
c.Assert(err, check.IsNil)
c.Assert(m.stdout.String(), check.Matches, fmt.Sprintf(`name: ws
base: ubuntu@22.04
project: %s
status: pending
notes: workshop-note,try-later
c.Assert(m.stdout.String(), check.Matches, fmt.Sprintf(`name: ws
base: ubuntu@22.04
project: %s
status: pending
confinement: container
notes: workshop-note,try-later
sdks:
go:
tracking: latest/edge
Expand All @@ -169,6 +173,7 @@ sdks:
var mockWorkshopWithMounts = `{"type":"sync","status-code":200,"status":"OK","result":{
"name":"ws",
"base":"ubuntu@22.04",
"confinement":"container",
"project-id":"42424242",
"status":"Ready",
"sdks":[{
Expand Down Expand Up @@ -200,11 +205,12 @@ var mockWorkshopWithMounts = `{"type":"sync","status-code":200,"status":"OK","re
}]
}}`

var mockWorkshopWithMountsOutput = `name: ws
base: ubuntu@22.04
project: %s
status: ready
notes: %s
var mockWorkshopWithMountsOutput = `name: ws
base: ubuntu@22.04
project: %s
status: ready
confinement: container
notes: %s
sdks:
go:
tracking: latest/edge
Expand Down Expand Up @@ -307,6 +313,7 @@ var mockWorkshopWithTunnels = `{
"result": {
"name": "ws",
"base": "ubuntu@22.04",
"confinement": "container",
"project-id": "42424242",
"status": "Ready",
"sdks": [
Expand Down Expand Up @@ -392,11 +399,12 @@ func (m *workshopInfo) TestWorkshopInfoWithSdkTunnels(c *check.C) {

err = cmd.Run(cmd.Command(), []string{workshop})
c.Assert(err, check.IsNil)
c.Assert(m.stdout.String(), check.Matches, fmt.Sprintf(`name: ws
base: ubuntu@22.04
project: %s
status: ready
notes: --
c.Assert(m.stdout.String(), check.Matches, fmt.Sprintf(`name: ws
base: ubuntu@22.04
project: %s
status: ready
confinement: container
notes: --
sdks:
system:
installed: \(1\)
Expand Down
13 changes: 13 additions & 0 deletions cmd/workshop/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type CmdInit struct {
root *CmdRoot
sdks []string
base string
vm bool
}

func (c *CmdInit) Command() *cobra.Command {
Expand Down Expand Up @@ -53,6 +54,9 @@ $ workshop init dev --base ubuntu@22.04 --sdks go`,

cmd.Flags().StringSliceVar(&c.sdks, "sdks", nil, `Comma-separated list of SDKs (e.g., "go,uv/latest/stable").`)
cmd.Flags().StringVar(&c.base, "base", defaultBase, "Base image for the workshop.")
cmd.Flags().BoolVar(&c.vm, "vm", false, "Use a virtual machine instead of a container.")

cmd.MarkFlagsMutuallyExclusive("sdks", "vm")

return cmd
}
Expand All @@ -66,6 +70,11 @@ func (c *CmdInit) Run(cmd *cobra.Command, args []string) error {
return err
}

confinement := workshop.ConfinementContainer
if c.vm {
confinement = workshop.ConfinementVirtualMachine
}

wfile := &workshop.File{
Name: name,
Base: c.base,
Expand All @@ -76,6 +85,10 @@ func (c *CmdInit) Run(cmd *cobra.Command, args []string) error {
return err
}

// TODO: include confinement in the validation. We skip validation here
// because we might not have the experimental flag in the environment.
wfile.Confinement = confinement

if err := ensureCanCreate(projectDir, name); err != nil {
return err
}
Expand Down
15 changes: 15 additions & 0 deletions cmd/workshop/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@ base: ubuntu@24.04
`)
}

func (s *workshopInit) TestInitVM(c *check.C) {
projectDir := c.MkDir()
cmd := s.makeCmd(projectDir)
cmd.vm = true

err := s.run(cmd, "dev")
c.Assert(err, check.IsNil)

path := workshop.Filepath(projectDir, "dev")
c.Check(path, testutil.FileEquals, `name: dev
base: ubuntu@24.04
confinement: virtual-machine
`)
}

func (s *workshopInit) TestInitWithSdkChannel(c *check.C) {
projectDir := c.MkDir()
cmd := s.makeCmd(projectDir)
Expand Down
9 changes: 9 additions & 0 deletions cmd/workshopctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

"github.com/canonical/workshop/client"
"github.com/canonical/workshop/internal/dirs"
"github.com/canonical/workshop/internal/fsfreeze"
"github.com/canonical/workshop/internal/waitready"
)

Expand All @@ -45,6 +46,14 @@ func main() {
return
}

if fsfreeze.IsFsfreezeInvocation() {
if err := fsfreeze.FreezeLocalFilesystems(os.Stdin, os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "error: %s\n", err)
os.Exit(1)
}
return
}

// Set the user and group IDs to the workshop user
uid := uint32(1000) // Change this to the workshop UID

Expand Down
9 changes: 9 additions & 0 deletions docs/reference/definition-files/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@
],
"errorMessage": "The base must be one of the supported values: ubuntu@20.04, ubuntu@22.04, ubuntu@24.04, ubuntu@26.04."
},
"confinement": {
"type": "string",
"description": "Type of sandboxing to use to run the workshop.",
"enum": [
"container",
"virtual-machine"
],
"errorMessage": "The confinement must be one of the supported values: container, virtual-machine."
Comment thread
jonathan-conder marked this conversation as resolved.
},
"sdks": {
"type": "array",
"description": "Ordered list of SDKs to install on top of the base. Each entry references an existing SDK; names must be unique within the list. The system SDK is installed first implicitly and need not be listed.",
Expand Down
6 changes: 5 additions & 1 deletion internal/daemon/api_connections.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,11 @@ func v1GetConnections(c *Command, r *http.Request, _ *userState) Response {
onlyConnected := qselect == ""

if workshop != "" {
if err := checkWorkshopExists(r.Context(), c.d.overlord.WorkshopManager(), projectId, workshop); err != nil {
st := c.d.overlord.State()
st.Lock()
err := checkWorkshopExists(r.Context(), c.d.overlord.WorkshopManager(), projectId, workshop)
st.Unlock()
if err != nil {
return statusNotFound("cannot access %q workshop: %w", workshop, err)
}
}
Expand Down
4 changes: 2 additions & 2 deletions internal/daemon/api_connections_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func (s *apiSuite) workshopFile(ws string, sdks []*sdk.Info) *workshop.File {
func (s *apiSuite) mockInstalledSDK(c *check.C, yaml string, w string) *workshop.Workshop {
info := sdk.MockInfo(c, yaml, s.project.ProjectId, w)
wf := s.workshopFile(w, []*sdk.Info{info})
snapshot := workshop.BaseOnly(sdk.R(1), wf.Base, "fakeimage123")
snapshot := workshop.BaseOnly(sdk.R(1), wf.Base, workshop.ConfinementContainer, "fakeimage123")
c.Assert(s.b.LaunchOrRebuildWorkshop(s.ctx, wf, snapshot), check.IsNil)

wp, err := s.b.Workshop(s.ctx, w)
Expand Down Expand Up @@ -128,7 +128,7 @@ func (s *apiSuite) mockInstalledSDKBoundPlug(c *check.C, yaml string, w string,
Name: to}
c.Assert(s.d.overlord.InterfaceManager().Repository().AddSdk(info), check.IsNil)
wf := s.workshopFile(w, []*sdk.Info{info})
snapshot := workshop.BaseOnly(sdk.R(1), wf.Base, "fakeimage123")
snapshot := workshop.BaseOnly(sdk.R(1), wf.Base, workshop.ConfinementContainer, "fakeimage123")
c.Assert(s.b.LaunchOrRebuildWorkshop(s.ctx, wf, snapshot), check.IsNil)
wp, err := s.b.Workshop(s.ctx, w)
c.Check(err, check.IsNil)
Expand Down
2 changes: 1 addition & 1 deletion internal/daemon/api_exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (s *apiSuite) setupExec(c *check.C) *Command {
s.createWFile(c, "ws", wsYaml)

wf := &workshop.File{Name: "ws", Base: "ubuntu@20.04", Actions: map[string]workshop.Action{"lint": "\n\n\ngolangci-lint run\n"}}
snapshot := workshop.BaseOnly(sdk.R(1), wf.Base, "fakeimage123")
snapshot := workshop.BaseOnly(sdk.R(1), wf.Base, workshop.ConfinementContainer, "fakeimage123")

err := s.b.LaunchOrRebuildWorkshop(s.ctx, wf, snapshot)
c.Assert(err, check.IsNil)
Expand Down
8 changes: 4 additions & 4 deletions internal/daemon/api_sdks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,11 +439,11 @@ func (s *apiSuite) TestSdkInfoGetOk(c *check.C) {
s.createWFile(c, "lerobot", "name: lerobot\nbase: ubuntu@20.04\n")

wf := &workshop.File{Name: "nav2", Base: "ubuntu@20.04"}
snapshot := workshop.BaseOnly(sdk.R(1), wf.Base, "fakeimage123")
snapshot := workshop.BaseOnly(sdk.R(1), wf.Base, workshop.ConfinementContainer, "fakeimage123")
c.Assert(s.b.LaunchOrRebuildWorkshop(s.ctx, wf, snapshot), check.IsNil)

wf = &workshop.File{Name: "lerobot", Base: "ubuntu@20.04"}
snapshot = workshop.BaseOnly(sdk.R(1), wf.Base, "fakeimage123")
snapshot = workshop.BaseOnly(sdk.R(1), wf.Base, workshop.ConfinementContainer, "fakeimage123")
c.Assert(s.b.LaunchOrRebuildWorkshop(s.ctx, wf, snapshot), check.IsNil)

// Add SDK setups with channels so the endpoint can report channels.
Expand Down Expand Up @@ -667,7 +667,7 @@ func (s *apiSuite) TestSdkInfoLocalOnly(c *check.C) {

s.createWFile(c, "nav2", "name: nav2\nbase: ubuntu@20.04\n")
wf := &workshop.File{Name: "nav2", Base: "ubuntu@20.04"}
snapshot := workshop.BaseOnly(sdk.R(1), wf.Base, "fakeimage123")
snapshot := workshop.BaseOnly(sdk.R(1), wf.Base, workshop.ConfinementContainer, "fakeimage123")
c.Assert(s.b.LaunchOrRebuildWorkshop(s.ctx, wf, snapshot), check.IsNil)

// Add SDK setup with channels so the endpoint can report channels.
Expand Down Expand Up @@ -798,7 +798,7 @@ func (s *apiSuite) TestSdkInfoGetInvalidLocalMetadata(c *check.C) {

s.createWFile(c, "ws", "name: ws\nbase: ubuntu@20.04\n")
wf := &workshop.File{Name: "ws", Base: "ubuntu@20.04"}
snapshot := workshop.BaseOnly(sdk.R(1), wf.Base, "fakeimage123")
snapshot := workshop.BaseOnly(sdk.R(1), wf.Base, workshop.ConfinementContainer, "fakeimage123")
c.Assert(s.b.LaunchOrRebuildWorkshop(s.ctx, wf, snapshot), check.IsNil)

meta := sdk.Meta{
Expand Down
27 changes: 20 additions & 7 deletions internal/daemon/api_workshops.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,14 @@ type Workshops struct {
}

type WorkshopInfo struct {
ProjectId string `json:"project-id"`
Name string `json:"name"`
Base string `json:"base"`
Status string `json:"status"`
Sdks []*SdkInfo `json:"sdks,omitempty"`
Hostname string `json:"hostname,omitempty"`
Notes []string `json:"notes,omitempty"`
ProjectId string `json:"project-id"`
Name string `json:"name"`
Base string `json:"base"`
Confinement string `json:"confinement"`
Status string `json:"status"`
Sdks []*SdkInfo `json:"sdks,omitempty"`
Hostname string `json:"hostname,omitempty"`
Notes []string `json:"notes,omitempty"`
}

type WorkshopFileInfo struct {
Expand Down Expand Up @@ -202,6 +203,12 @@ func workshopToInfo(username string, w *workshop.Workshop, health healthstate.He
info.ProjectId = w.Project.ProjectId
info.Base = w.File.Base

confinement, err := w.File.Confinement.MarshalText()
if err != nil {
return nil, err
}
info.Confinement = string(confinement)

sdkSetups := w.SdksByInstallOrder()

usr, env, err := osutil.UserAndEnv(username)
Expand Down Expand Up @@ -255,6 +262,12 @@ func workshopToInfoFull(ctx context.Context, username string, w *workshop.Worksh
info.ProjectId = w.Project.ProjectId
info.Base = w.File.Base

confinement, err := w.File.Confinement.MarshalText()
if err != nil {
return nil, err
}
info.Confinement = string(confinement)

sdks, err := w.SdkInfosByInstallOrder(ctx)
if err != nil {
return nil, err
Expand Down
Loading
Loading