From 5609f80827543a1ff6dde1d5ddcdf4889fca9f74 Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Wed, 8 Apr 2026 11:31:05 +1200 Subject: [PATCH 01/17] Show daemon debug logs in failed end-to-end tests --- .spread.yaml | 13 ++++++++++++- snap/local/commands/run_daemon | 3 +++ tests/lib/utils.sh | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.spread.yaml b/.spread.yaml index 30b1a0631..38c14d478 100644 --- a/.spread.yaml +++ b/.spread.yaml @@ -24,12 +24,15 @@ suites: . "$TESTSLIB"/utils.sh prepare_environment setup_workshop + prepare-each: | + date --rfc-3339=seconds > /tmp/spread-job-start restore: | . "$TESTSLIB"/utils.sh cleanup_workshop - debug: | + debug-each: | lxc list --project workshop.ubuntu sudo -u ubuntu 2>&1 -- workshop list --global + journalctl --since="$( /tmp/spread-job-start restore: | . "$TESTSLIB"/utils.sh cleanup_workshop true # used real store, no need to clean it + debug-each: | + journalctl --since="$( /tmp/spread-job-start restore: | . "$TESTSLIB"/utils.sh cleanup_workshop true # used real store, no need to clean it + debug-each: | + journalctl --since="$( Date: Thu, 2 Apr 2026 15:54:42 +1300 Subject: [PATCH 02/17] Workaround missing timezone error from SDK Store resolve endpoint This is intended as a temporary fix while the Store team adds the required timezones. If it becomes a longer-term solution, we should probably split it into another type to avoid being too lenient about the input we accept. --- internal/timeutil/utc.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/internal/timeutil/utc.go b/internal/timeutil/utc.go index eaefcdaae..e1777fe6e 100644 --- a/internal/timeutil/utc.go +++ b/internal/timeutil/utc.go @@ -20,6 +20,8 @@ package timeutil import ( + "errors" + "slices" "time" "gopkg.in/yaml.v3" @@ -35,7 +37,18 @@ func (t TimeUTC) MarshalText() ([]byte, error) { func (t *TimeUTC) UnmarshalText(data []byte) error { var temp time.Time if err := temp.UnmarshalText(data); err != nil { - return err + // If data has no timezone, the parser essentially complains that "" is + // not a valid timezone. Currently the SDK Store returns these + // timestamps, which are always UTC. Appending Z should fix the error, + // but we should remove this once the Store starts giving us timezones. + parseErr, ok := errors.AsType[*time.ParseError](err) + if !ok || parseErr.ValueElem != "" || parseErr.LayoutElem != "Z07:00" { + return err + } + data = append(slices.Clone(data), 'Z') + if err1 := temp.UnmarshalText(data); err1 != nil { + return err + } } *t = TimeUTC(temp.UTC()) return nil From 67311c009ca57072aa6e9333d95410139018cf9d Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Tue, 7 Apr 2026 15:09:24 +1200 Subject: [PATCH 03/17] Add channel parsing logic based on snapd --- internal/daemon/api_sdks_test.go | 4 +- internal/overlord/sdkstate/manager.go | 30 +++-- internal/sdk/channel.go | 131 +++++++++++++++++++++ internal/sdk/channel_test.go | 146 ++++++++++++++++++++++++ internal/workshop/workshop_file.go | 7 +- internal/workshop/workshop_file_test.go | 9 +- 6 files changed, 310 insertions(+), 17 deletions(-) create mode 100644 internal/sdk/channel.go create mode 100644 internal/sdk/channel_test.go diff --git a/internal/daemon/api_sdks_test.go b/internal/daemon/api_sdks_test.go index efc7e0446..1b3c86b31 100644 --- a/internal/daemon/api_sdks_test.go +++ b/internal/daemon/api_sdks_test.go @@ -746,7 +746,9 @@ func (s *apiSuite) TestSdkInfoInvalidStoreMetadata(c *check.C) { Name: "bad", ChannelMap: []transport.InfoChannelMap{{ Channel: transport.Channel{ - Name: "latest/stable", + Name: "latest/stable", + Track: "latest", + Risk: "stable", Platform: transport.Platform{ Name: "ubuntu", Channel: "20.04", diff --git a/internal/overlord/sdkstate/manager.go b/internal/overlord/sdkstate/manager.go index 56361feb7..bfeb6e23b 100644 --- a/internal/overlord/sdkstate/manager.go +++ b/internal/overlord/sdkstate/manager.go @@ -168,6 +168,13 @@ func (w *SdkManager) FindSdks(ctx context.Context, query string) ([]SdkSummary, base = "" } + channel := sdk.Channel{ + Name: entry.DefaultRelease.Channel.Name, + Track: entry.DefaultRelease.Channel.Track, + Risk: entry.DefaultRelease.Channel.Risk, + } + channel = channel.Full() + summary := SdkSummary{ Name: entry.Name, PackageID: entry.PackageID, @@ -175,9 +182,9 @@ func (w *SdkManager) FindSdks(ctx context.Context, query string) ([]SdkSummary, Description: entry.Metadata.Description, License: entry.Metadata.License, Publisher: publisher, - Channel: entry.DefaultRelease.Channel.Track + "/" + entry.DefaultRelease.Channel.Risk, - Track: entry.DefaultRelease.Channel.Track, - Risk: entry.DefaultRelease.Channel.Risk, + Channel: channel.Name, + Track: channel.Track, + Risk: channel.Risk, Revision: sdk.Revision{N: entry.DefaultRelease.Revision}.String(), ReleasedAt: (*time.Time)(entry.DefaultRelease.Channel.ReleasedAt), Version: entry.DefaultRelease.Version, @@ -241,10 +248,17 @@ func (w *SdkManager) fillChannels(ctx context.Context, name string, full *SdkFul base = "" } - channel := SdkRevision{ - Channel: entry.Channel.Track + "/" + entry.Channel.Risk, - Track: entry.Channel.Track, - Risk: entry.Channel.Risk, + channel := sdk.Channel{ + Name: entry.Channel.Name, + Track: entry.Channel.Track, + Risk: entry.Channel.Risk, + } + channel = channel.Full() + + revision := SdkRevision{ + Channel: channel.Name, + Track: channel.Track, + Risk: channel.Risk, Revision: sdk.Revision{N: entry.Revision.Revision}.String(), BuiltAt: (*time.Time)(sdkYaml.BuiltAt), UploadedAt: (*time.Time)(entry.Revision.CreatedAt), @@ -254,7 +268,7 @@ func (w *SdkManager) fillChannels(ctx context.Context, name string, full *SdkFul Arch: entry.Channel.Platform.Architecture, DownloadSize: entry.Revision.Download.Size, } - full.Channels = append(full.Channels, channel) + full.Channels = append(full.Channels, revision) } return nil } diff --git a/internal/sdk/channel.go b/internal/sdk/channel.go new file mode 100644 index 000000000..6f6deba8a --- /dev/null +++ b/internal/sdk/channel.go @@ -0,0 +1,131 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2018-2019 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package sdk + +import ( + "fmt" + "regexp" + "slices" + "strings" +) + +const ( + MAX_TRACK_LENGTH = 28 + MAX_BRANCH_LENGTH = 128 +) + +var ( + channelTrack = regexp.MustCompile(`^[a-zA-Z0-9](?:[_.-]?[a-zA-Z0-9])*$`) + channelRisks = []string{"stable", "candidate", "beta", "edge"} + channelBranch = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9]$`) +) + +// Channel identifies and describes completely a store channel. +type Channel struct { + Name string `json:"name"` + Track string `json:"track"` + Risk string `json:"risk"` + Branch string `json:"branch,omitempty"` +} + +func (c Channel) MarshalText() ([]byte, error) { + return []byte(c.String()), nil +} + +func (c *Channel) UnmarshalText(data []byte) error { + channel, err := ParseChannel(string(data)) + if err == nil { + *c = channel + } + return err +} + +func (c Channel) String() string { + return c.Name +} + +func ParseChannel(channel string) (Channel, error) { + parts := strings.Split(channel, "/") + var risk, track, branch *string + switch len(parts) { + case 1: + if slices.Contains(channelRisks, parts[0]) { + risk = &parts[0] + } else { + track = &parts[0] + } + case 2: + if slices.Contains(channelRisks, parts[0]) { + risk, branch = &parts[0], &parts[1] + } else { + track, risk = &parts[0], &parts[1] + } + case 3: + track, risk, branch = &parts[0], &parts[1], &parts[2] + default: + return Channel{}, fmt.Errorf("channel %q has too many components", channel) + } + + ch := Channel{Name: channel} + if track != nil { + if len(*track) > MAX_TRACK_LENGTH || !channelTrack.MatchString(*track) || slices.Contains(channelRisks, *track) { + return Channel{}, fmt.Errorf("invalid track %q in channel %q", *track, channel) + } + ch.Track = *track + } + if risk != nil { + if !slices.Contains(channelRisks, *risk) { + return Channel{}, fmt.Errorf("invalid risk %q in channel %q", *risk, channel) + } + ch.Risk = *risk + } + if branch != nil { + if len(*branch) > MAX_BRANCH_LENGTH || !channelBranch.MatchString(*branch) || slices.Contains(channelRisks, *branch) { + return Channel{}, fmt.Errorf("invalid branch %q in channel %q", *branch, channel) + } + ch.Branch = *branch + } + return ch, nil +} + +// Full replaces empty fields with their defaults. +func (c *Channel) Full() Channel { + track := c.Track + if track == "" { + track = "latest" + } + risk := c.Risk + if risk == "" { + risk = "stable" + } + + var name strings.Builder + fmt.Fprint(&name, track, "/", risk) + if c.Branch != "" { + fmt.Fprint(&name, "/", c.Branch) + } + + return Channel{ + Name: name.String(), + Track: track, + Risk: risk, + Branch: c.Branch, + } +} diff --git a/internal/sdk/channel_test.go b/internal/sdk/channel_test.go new file mode 100644 index 000000000..bd889eea1 --- /dev/null +++ b/internal/sdk/channel_test.go @@ -0,0 +1,146 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2018 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package sdk_test + +import ( + "strings" + + "gopkg.in/check.v1" + + "github.com/canonical/workshop/internal/sdk" +) + +type sdkChannel struct{} + +var _ = check.Suite(&sdkChannel{}) + +func (sdkChannel) TestParse(c *check.C) { + ch, err := sdk.ParseChannel("stable") + c.Assert(err, check.IsNil) + c.Check(ch, check.DeepEquals, sdk.Channel{ + Name: "stable", + Track: "", + Risk: "stable", + Branch: "", + }) + + ch, err = sdk.ParseChannel("latest/stable") + c.Assert(err, check.IsNil) + c.Check(ch, check.DeepEquals, sdk.Channel{ + Name: "latest/stable", + Track: "latest", + Risk: "stable", + Branch: "", + }) + + ch, err = sdk.ParseChannel("1.0/edge") + c.Assert(err, check.IsNil) + c.Check(ch, check.DeepEquals, sdk.Channel{ + Name: "1.0/edge", + Track: "1.0", + Risk: "edge", + Branch: "", + }) + + ch, err = sdk.ParseChannel("1.0") + c.Assert(err, check.IsNil) + c.Check(ch, check.DeepEquals, sdk.Channel{ + Name: "1.0", + Track: "1.0", + Risk: "", + Branch: "", + }) + + ch, err = sdk.ParseChannel("1.0/beta/foo") + c.Assert(err, check.IsNil) + c.Check(ch, check.DeepEquals, sdk.Channel{ + Name: "1.0/beta/foo", + Track: "1.0", + Risk: "beta", + Branch: "foo", + }) + + ch, err = sdk.ParseChannel("candidate/foo") + c.Assert(err, check.IsNil) + c.Check(ch, check.DeepEquals, sdk.Channel{ + Name: "candidate/foo", + Track: "", + Risk: "candidate", + Branch: "foo", + }) +} + +func (sdkChannel) TestParseErrors(c *check.C) { + for _, tc := range []struct { + channel string + err string + }{ + {"", `invalid track "" in channel ""`}, + {strings.Repeat("a", 100), `invalid track "a*" in channel "a*"`}, + {"!@#", `invalid track "!@#" in channel "!@#"`}, + {"/edge", `invalid track "" in channel "/edge"`}, + {strings.Repeat("a", 100) + "/beta", `invalid track "a*" in channel "a*/beta"`}, + {"!@#/stable", `invalid track "!@#" in channel "!@#/stable"`}, + {"1.0/cand", `invalid risk "cand" in channel "1.0/cand"`}, + {"beta/", `invalid branch "" in channel "beta/"`}, + {"edge/" + strings.Repeat("a", 200), `invalid branch "a*" in channel "edge/a*"`}, + {"stable/!@#", `invalid branch "!@#" in channel "stable/!@#"`}, + {"candidate/edge", `invalid branch "edge" in channel "candidate/edge"`}, + {"/stable/fix", `invalid track "" in channel "/stable/fix"`}, + {strings.Repeat("a", 100) + "/edge/branch", `invalid track "a*" in channel "a*/edge/branch"`}, + {"!@#/beta/temp", `invalid track "!@#" in channel "!@#/beta/temp"`}, + {"beta/edge/fix", `invalid track "beta" in channel "beta/edge/fix"`}, + {"1.0//branch", `invalid risk "" in channel "1.0//branch"`}, + {"track/edge/", `invalid branch "" in channel "track/edge/"`}, + {"2022/beta/" + strings.Repeat("a", 200), `invalid branch "a*" in channel "2022/beta/a*"`}, + {"latest/stable/!@#", `invalid branch "!@#" in channel "latest/stable/!@#"`}, + {"0.9/candidate/edge", `invalid branch "edge" in channel "0.9/candidate/edge"`}, + {"///", `channel "///" has too many components`}, + } { + _, err := sdk.ParseChannel(tc.channel) + c.Check(err, check.ErrorMatches, tc.err) + } +} + +func (sdkChannel) TestChannelFull(c *check.C) { + tests := []struct { + channel string + name string + track string + risk string + }{ + {"stable", "latest/stable", "latest", "stable"}, + {"latest/stable", "latest/stable", "latest", "stable"}, + {"1.0/edge", "1.0/edge", "1.0", "edge"}, + {"1.0/beta/foo", "1.0/beta/foo", "1.0", "beta"}, + {"1.0", "1.0/stable", "1.0", "stable"}, + {"candidate/foo", "latest/candidate/foo", "latest", "candidate"}, + } + + for _, t := range tests { + ch, err := sdk.ParseChannel(t.channel) + c.Assert(err, check.IsNil) + + full := ch.Full() + c.Check(full.Name, check.Equals, t.name) + c.Check(full.Track, check.Equals, t.track) + c.Check(full.Risk, check.Equals, t.risk) + } +} diff --git a/internal/workshop/workshop_file.go b/internal/workshop/workshop_file.go index 4fc08c06b..81e7791e8 100644 --- a/internal/workshop/workshop_file.go +++ b/internal/workshop/workshop_file.go @@ -22,7 +22,6 @@ var ( SupportedBases = []string{"ubuntu@20.04", "ubuntu@22.04", "ubuntu@24.04"} workshopName = regexp.MustCompile(`^[a-z](?:-?[a-z0-9])*$`) - channel = regexp.MustCompile(`^(?:[a-z0-9](?:[.-]?[a-z0-9])*/(?:stable|candidate|beta|edge)|)$`) actionName = workshopName Directory = ".workshop" @@ -295,8 +294,10 @@ func validateSdks(sdks []SdkRecord) error { } seen[s.Name] = true - if !channel.MatchString(s.Channel) { - return fmt.Errorf("unsupported channel %q for %q SDK", s.Channel, s.Name) + if s.Channel != "" { + if _, err := sdk.ParseChannel(s.Channel); err != nil { + return fmt.Errorf("%q SDK: %w", s.Name, err) + } } } return nil diff --git a/internal/workshop/workshop_file_test.go b/internal/workshop/workshop_file_test.go index 2ef058358..d20264b05 100644 --- a/internal/workshop/workshop_file_test.go +++ b/internal/workshop/workshop_file_test.go @@ -61,11 +61,10 @@ base: ubuntu@20.04 sdks: - name: system - name: huggingface - channel: latest/stable - name: cuda channel: latest/edge - name: zookeeper - channel: latest/candidate + channel: candidate - name: automotive channel: latest/beta - name: try-rocm @@ -83,9 +82,9 @@ actions: c.Assert(file.Name, check.Equals, "xbert-gpu") c.Assert(file.Base, check.Equals, "ubuntu@20.04") c.Assert(file.Sdks[0], check.DeepEquals, workshop.SdkRecord{Name: "system", Source: sdk.SystemSource}) - c.Assert(file.Sdks[1], check.DeepEquals, workshop.SdkRecord{Name: "huggingface", Channel: "latest/stable"}) + c.Assert(file.Sdks[1], check.DeepEquals, workshop.SdkRecord{Name: "huggingface"}) c.Assert(file.Sdks[2], check.DeepEquals, workshop.SdkRecord{Name: "cuda", Channel: "latest/edge"}) - c.Assert(file.Sdks[3], check.DeepEquals, workshop.SdkRecord{Name: "zookeeper", Channel: "latest/candidate"}) + c.Assert(file.Sdks[3], check.DeepEquals, workshop.SdkRecord{Name: "zookeeper", Channel: "candidate"}) c.Assert(file.Sdks[4], check.DeepEquals, workshop.SdkRecord{Name: "automotive", Channel: "latest/beta"}) c.Assert(file.Sdks[5], check.DeepEquals, workshop.SdkRecord{Name: "rocm", Source: sdk.TrySource}) c.Assert(file.Sdks[6], check.DeepEquals, workshop.SdkRecord{Name: "linter", Source: sdk.ProjectSource}) @@ -315,7 +314,7 @@ sdks: f.createWFile(c, "xbert-gpu", yaml) file, err := f.project.Workshop("xbert-gpu") c.Assert(file, check.IsNil) - c.Assert(err, check.ErrorMatches, `unsupported channel "latest/foo" for "cuda" SDK`) + c.Assert(err, check.ErrorMatches, `"cuda" SDK: invalid risk "foo" in channel "latest/foo"`) } func (f *workshopFile) TestShortcuts(c *check.C) { From 0f46f8520b33b0b819567983af4a013797fd9271 Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Wed, 25 Mar 2026 14:41:55 +1300 Subject: [PATCH 04/17] Add SDK Store resolve client I left out tests for now, since the Store API hasn't been finalised yet. When it is, we can follow the same pattern as the find and info tests. --- internal/sdkstore/client.go | 12 +- internal/sdkstore/find_integration_test.go | 1 - internal/sdkstore/info_integration_test.go | 1 - internal/sdkstore/resolve.go | 44 ++++++ internal/sdkstore/resolve_integration_test.go | 94 +++++++++++++ internal/sdkstore/resolve_test.go | 130 ++++++++++++++++++ internal/sdkstore/test-resolve-id.raw.json | 44 ++++++ internal/sdkstore/test-resolve-name.json | 44 ++++++ internal/sdkstore/test-resolve-name.raw.json | 44 ++++++ .../sdkstore/test-resolve-not-found.raw.json | 17 +++ internal/sdkstore/transport/error.go | 2 +- internal/sdkstore/transport/resolve.go | 129 +++++++++++++++++ 12 files changed, 557 insertions(+), 5 deletions(-) create mode 100644 internal/sdkstore/resolve.go create mode 100644 internal/sdkstore/resolve_integration_test.go create mode 100644 internal/sdkstore/resolve_test.go create mode 100644 internal/sdkstore/test-resolve-id.raw.json create mode 100644 internal/sdkstore/test-resolve-name.json create mode 100644 internal/sdkstore/test-resolve-name.raw.json create mode 100644 internal/sdkstore/test-resolve-not-found.raw.json create mode 100644 internal/sdkstore/transport/resolve.go diff --git a/internal/sdkstore/client.go b/internal/sdkstore/client.go index f5be24e1c..fa17f9343 100644 --- a/internal/sdkstore/client.go +++ b/internal/sdkstore/client.go @@ -57,10 +57,16 @@ func basePath(base *url.URL) path.Path { return path.MakePath(base).JoinPath(serverVersion, serverEntity) } +// resolvePath returns the configuration path for speaking to the resolve API. +func resolvePath(base *url.URL) path.Path { + return path.MakePath(base).JoinPath(serverVersion, "revisions", "resolve") +} + // Client represents the client side of an SDK store. type Client struct { *findClient *infoClient + *resolveClient } // NewClient creates a new SDK Store client from the supplied configuration. @@ -78,12 +84,14 @@ func NewClient(config Config) *Client { base := basePath(baseURL) findPath := base.JoinPath("find") infoPath := base.JoinPath("info") + resolvePath := resolvePath(baseURL) apiRequester := newAPIRequester(httpClient) restClient := newHTTPRESTClient(apiRequester) return &Client{ - findClient: newFindClient(findPath, restClient), - infoClient: newInfoClient(infoPath, restClient), + findClient: newFindClient(findPath, restClient), + infoClient: newInfoClient(infoPath, restClient), + resolveClient: newResolveClient(resolvePath, restClient), } } diff --git a/internal/sdkstore/find_integration_test.go b/internal/sdkstore/find_integration_test.go index 7b6607e0f..3f0d06523 100644 --- a/internal/sdkstore/find_integration_test.go +++ b/internal/sdkstore/find_integration_test.go @@ -4,7 +4,6 @@ package sdkstore import ( "context" - _ "embed" "encoding/json" "gopkg.in/check.v1" diff --git a/internal/sdkstore/info_integration_test.go b/internal/sdkstore/info_integration_test.go index d9c7d2a80..b1717150a 100644 --- a/internal/sdkstore/info_integration_test.go +++ b/internal/sdkstore/info_integration_test.go @@ -4,7 +4,6 @@ package sdkstore import ( "context" - _ "embed" "encoding/json" "gopkg.in/check.v1" diff --git a/internal/sdkstore/resolve.go b/internal/sdkstore/resolve.go new file mode 100644 index 000000000..38f2a14ff --- /dev/null +++ b/internal/sdkstore/resolve.go @@ -0,0 +1,44 @@ +// Copyright 2020 Canonical Ltd. +// Licensed under the AGPLv3, see LICENCE file for details. + +package sdkstore + +import ( + "context" + + "github.com/canonical/workshop/internal/sdkstore/path" + "github.com/canonical/workshop/internal/sdkstore/transport" +) + +type resolveClient struct { + path path.Path + client RESTClient +} + +func newResolveClient(path path.Path, client RESTClient) *resolveClient { + return &resolveClient{ + path: path, + client: client, + } +} + +// Resolve is used to find the latest revisions of a given set of SDKs. +func (c *resolveClient) Resolve(ctx context.Context, req transport.ResolveRequest) (transport.ResolveResponse, error) { + var resp struct { + transport.ResolveResponse + transport.ErrorResponse + } + if err := c.resolve(ctx, &resp, req); err != nil { + return resp.ResolveResponse, err + } + if err := handleBasicAPIErrors(resp.ErrorList); err != nil { + return resp.ResolveResponse, err + } + + return resp.ResolveResponse, nil +} + +func (c *resolveClient) resolve(ctx context.Context, resp any, req transport.ResolveRequest) error { + _, err := c.client.Post(ctx, c.path, nil, req, resp) + return err +} diff --git a/internal/sdkstore/resolve_integration_test.go b/internal/sdkstore/resolve_integration_test.go new file mode 100644 index 000000000..fecd3d525 --- /dev/null +++ b/internal/sdkstore/resolve_integration_test.go @@ -0,0 +1,94 @@ +//go:build integration + +package sdkstore + +import ( + "context" + "encoding/json" + + "gopkg.in/check.v1" + + "github.com/canonical/workshop/internal/sdkstore/transport" +) + +type resolveIntegration struct{} + +var _ = check.Suite(&resolveIntegration{}) + +func (f *resolveIntegration) TestResolveByName(c *check.C) { + req := transport.ResolveRequest{ + Packages: []transport.ResolvePackage{{ + InstanceKey: "random123", + Namespace: "sdk", + Name: "test-sdk-info-multi-base", + Channel: "latest/stable", + Platform: transport.Platform{ + Name: "ubuntu", + Channel: "22.04", + Architecture: "amd64", + }, + }}, + } + + client := NewClient(Config{}) + var response any + err := client.resolve(context.Background(), &response, req) + c.Assert(err, check.IsNil) + + var expected any + err = json.Unmarshal(testResolveNameRaw, &expected) + c.Assert(err, check.IsNil) + c.Check(response, check.DeepEquals, expected) +} + +func (f *resolveIntegration) TestResolveByID(c *check.C) { + req := transport.ResolveRequest{ + Packages: []transport.ResolvePackage{{ + InstanceKey: "random456", + Namespace: "sdk", + ID: "ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1", + Channel: "latest/edge", + Platform: transport.Platform{ + Name: "ubuntu", + Channel: "24.04", + Architecture: "riscv64", + }, + }}, + } + + client := NewClient(Config{}) + var response any + err := client.resolve(context.Background(), &response, req) + c.Assert(err, check.IsNil) + + var expected any + err = json.Unmarshal(testResolveIDRaw, &expected) + c.Assert(err, check.IsNil) + c.Check(response, check.DeepEquals, expected) +} + +func (f *resolveIntegration) TestResolveNotFound(c *check.C) { + req := transport.ResolveRequest{ + Packages: []transport.ResolvePackage{{ + InstanceKey: "random789", + Namespace: "sdk", + Name: "not-found", + Channel: "latest/stable", + Platform: transport.Platform{ + Name: "ubuntu", + Channel: "24.04", + Architecture: "s390x", + }, + }}, + } + + client := NewClient(Config{}) + var response any + err := client.resolve(context.Background(), &response, req) + c.Assert(err, check.IsNil) + + var expected any + err = json.Unmarshal(testResolveNotFoundRaw, &expected) + c.Assert(err, check.IsNil) + c.Check(response, check.DeepEquals, expected) +} diff --git a/internal/sdkstore/resolve_test.go b/internal/sdkstore/resolve_test.go new file mode 100644 index 000000000..e083c35fd --- /dev/null +++ b/internal/sdkstore/resolve_test.go @@ -0,0 +1,130 @@ +// Copyright 2020 Canonical Ltd. +// Licensed under the AGPLv3. + +package sdkstore + +import ( + "context" + _ "embed" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + + "go.uber.org/mock/gomock" + "gopkg.in/check.v1" + + "github.com/canonical/workshop/internal/sdkstore/path" + "github.com/canonical/workshop/internal/sdkstore/transport" + "github.com/canonical/workshop/internal/testutil" +) + +//go:embed test-resolve-name.raw.json +var testResolveNameRaw []byte + +//go:embed test-resolve-name.json +var testResolveNameResponse []byte + +//go:embed test-resolve-id.raw.json +var testResolveIDRaw []byte + +//go:embed test-resolve-not-found.raw.json +var testResolveNotFoundRaw []byte + +type ResolveSuite struct{} + +var _ = check.Suite(&ResolveSuite{}) + +func (s *ResolveSuite) TestResolve(c *check.C) { + ctrl := gomock.NewController(c) + defer ctrl.Finish() + + baseURL := MustParseURL(c, "http://api.foo.bar") + + path := path.MakePath(baseURL) + + restClient := NewMockRESTClient(ctrl) + s.expectPost(restClient, path) + + client := newResolveClient(path, restClient) + response, err := client.Resolve(context.Background(), transport.ResolveRequest{}) + c.Assert(err, check.IsNil) + c.Assert(response.PackageResults, check.HasLen, 1) + c.Check(response.PackageResults[0], check.DeepEquals, transport.ResolvePackageResponse{ID: "ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1"}) +} + +func (s *ResolveSuite) TestResolveFailure(c *check.C) { + ctrl := gomock.NewController(c) + defer ctrl.Finish() + + baseURL := MustParseURL(c, "http://api.foo.bar") + + path := path.MakePath(baseURL) + + restClient := NewMockRESTClient(ctrl) + s.expectPostFailure(restClient) + + client := newResolveClient(path, restClient) + _, err := client.Resolve(context.Background(), transport.ResolveRequest{}) + c.Assert(err, check.ErrorMatches, "boom") +} + +func (s *ResolveSuite) expectPost(client *MockRESTClient, p path.Path) { + client.EXPECT().Post(gomock.Any(), p, gomock.Any(), gomock.Any(), gomock.Any()).Do(func(_ context.Context, _ path.Path, _ http.Header, _, r any) (restResponse, error) { + resp := transport.ResolveResponse{ + PackageResults: []transport.ResolvePackageResponse{{ + ID: "ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1", + }}, + } + data, err := json.Marshal(resp) + if err != nil { + return restResponse{}, err + } + err = json.Unmarshal(data, r) + return restResponse{}, err + }) +} + +func (s *ResolveSuite) expectPostFailure(client *MockRESTClient) { + client.EXPECT().Post(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(restResponse{StatusCode: http.StatusInternalServerError}, errors.New("boom")) +} + +func (s *ResolveSuite) TestResolvePayload(c *check.C) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.URL.Path, check.Equals, "/v2/revisions/resolve") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err := w.Write(testResolveNameRaw) + c.Check(err, check.IsNil) + }) + + server := httptest.NewServer(handler) + defer server.Close() + + resolvePath := resolvePath(MustParseURL(c, server.URL)) + + apiRequester := newAPIRequester(DefaultHTTPClient()) + restClient := newHTTPRESTClient(apiRequester) + + client := newResolveClient(resolvePath, restClient) + req := transport.ResolveRequest{ + Packages: []transport.ResolvePackage{{ + InstanceKey: "random123", + Namespace: "sdk", + Name: "test-sdk-info-multi-base", + Channel: "latest/stable", + Platform: transport.Platform{ + Name: "ubuntu", + Channel: "22.04", + Architecture: "amd64", + }, + }}, + } + response, err := client.Resolve(context.Background(), req) + c.Assert(err, check.IsNil) + + var expected any + err = json.Unmarshal(testResolveNameResponse, &expected) + c.Assert(err, check.IsNil) + c.Check(response, testutil.JsonEquals, expected) +} diff --git a/internal/sdkstore/test-resolve-id.raw.json b/internal/sdkstore/test-resolve-id.raw.json new file mode 100644 index 000000000..11df6ac03 --- /dev/null +++ b/internal/sdkstore/test-resolve-id.raw.json @@ -0,0 +1,44 @@ +{ + "package-results": [ + { + "instance-key": "random456", + "status": "ok", + "namespace": "sdk", + "name": "test-sdk-info-multi-base", + "id": "ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1", + "result": { + "channel": { + "name": "edge", + "released-at": "2026-03-11T00:13:19.621866", + "track": null, + "risk": "edge", + "branch": null, + "effective-channel": "edge", + "platform": { + "name": "ubuntu", + "channel": "24.04", + "architecture": "riscv64" + } + }, + "revision": { + "created-at": "2026-03-11T00:13:15.679272", + "platforms": [ + { + "name": "ubuntu", + "channel": "24.04", + "architecture": "riscv64" + } + ], + "download": { + "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_13.sdk", + "size": 480, + "sha3-384": "6e4067003e140090d017872a8a730a1205679d388efde0e2eff6ac4e89e639593ae0f4665f361bb52d704e8ec670be06" + }, + "revision": 13, + "version": "0.3.1" + } + } + } + ], + "craft-results": [] +} diff --git a/internal/sdkstore/test-resolve-name.json b/internal/sdkstore/test-resolve-name.json new file mode 100644 index 000000000..bcf9ee7a7 --- /dev/null +++ b/internal/sdkstore/test-resolve-name.json @@ -0,0 +1,44 @@ +{ + "package-results": [ + { + "instance-key": "random123", + "status": "ok", + "namespace": "sdk", + "name": "test-sdk-info-multi-base", + "id": "ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1", + "result": { + "channel": { + "name": "stable", + "released-at": "2026-03-10T23:48:44.327082Z", + "track": "", + "risk": "stable", + "branch": "", + "effective-channel": "stable", + "platform": { + "name": "ubuntu", + "channel": "22.04", + "architecture": "amd64" + } + }, + "revision": { + "created-at": "2026-03-10T23:16:31.917435Z", + "platforms": [ + { + "name": "ubuntu", + "channel": "22.04", + "architecture": "amd64" + } + ], + "download": { + "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_2.sdk", + "size": 464, + "sha3-384": "59606df612d0290225f427935ac300437542a3c3024c52a0fb2bc5ae486ff801855f571ab8ecfe5f14f16b051d97673e" + }, + "revision": 2, + "version": "0.2" + } + } + } + ], + "craft-results": [] +} diff --git a/internal/sdkstore/test-resolve-name.raw.json b/internal/sdkstore/test-resolve-name.raw.json new file mode 100644 index 000000000..12ec458a7 --- /dev/null +++ b/internal/sdkstore/test-resolve-name.raw.json @@ -0,0 +1,44 @@ +{ + "package-results": [ + { + "instance-key": "random123", + "status": "ok", + "namespace": "sdk", + "name": "test-sdk-info-multi-base", + "id": "ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1", + "result": { + "channel": { + "name": "stable", + "released-at": "2026-03-10T23:48:44.327082", + "track": null, + "risk": "stable", + "branch": null, + "effective-channel": "stable", + "platform": { + "name": "ubuntu", + "channel": "22.04", + "architecture": "amd64" + } + }, + "revision": { + "created-at": "2026-03-10T23:16:31.917435", + "platforms": [ + { + "name": "ubuntu", + "channel": "22.04", + "architecture": "amd64" + } + ], + "download": { + "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_2.sdk", + "size": 464, + "sha3-384": "59606df612d0290225f427935ac300437542a3c3024c52a0fb2bc5ae486ff801855f571ab8ecfe5f14f16b051d97673e" + }, + "revision": 2, + "version": "0.2" + } + } + } + ], + "craft-results": [] +} diff --git a/internal/sdkstore/test-resolve-not-found.raw.json b/internal/sdkstore/test-resolve-not-found.raw.json new file mode 100644 index 000000000..2f6635350 --- /dev/null +++ b/internal/sdkstore/test-resolve-not-found.raw.json @@ -0,0 +1,17 @@ +{ + "package-results": [ + { + "instance-key": "random789", + "status": "error", + "namespace": "sdk", + "name": "not-found", + "id": null, + "error": { + "code": "package-not-found", + "message": "Package not found" + }, + "result": null + } + ], + "craft-results": [] +} diff --git a/internal/sdkstore/transport/error.go b/internal/sdkstore/transport/error.go index 8aab0ec59..e852d3fa5 100644 --- a/internal/sdkstore/transport/error.go +++ b/internal/sdkstore/transport/error.go @@ -15,7 +15,7 @@ type ErrorResponse struct { type APIError struct { Code APIErrorCode `json:"code"` Message string `json:"message"` - Extra APIErrorExtra `json:"extra"` + Extra APIErrorExtra `json:"extra,omitzero"` } func (a APIError) Error() string { diff --git a/internal/sdkstore/transport/resolve.go b/internal/sdkstore/transport/resolve.go new file mode 100644 index 000000000..c037acb33 --- /dev/null +++ b/internal/sdkstore/transport/resolve.go @@ -0,0 +1,129 @@ +// Copyright 2020 Canonical Ltd. +// Licensed under the AGPLv3. + +package transport + +import ( + "github.com/canonical/workshop/internal/timeutil" +) + +// ResolveRequest defines a typed request for resolving revisions. +type ResolveRequest struct { + Packages []ResolvePackage `json:"packages"` + Crafts []ResolveCraft `json:"crafts,omitempty"` +} + +// ResolvePackage defines a typed request for resolving SDK revisions. +type ResolvePackage struct { + // InstanceKey should be unique for every SDK, as results may not be + // ordered in the same way, so it is expected to use this to ensure + // completeness and ordering. + InstanceKey string `json:"instance-key"` + // Must be "sdk". + Namespace string `json:"namespace"` + // Either Name or ID must be supplied. + Name string `json:"name,omitempty"` + ID string `json:"id,omitempty"` + Channel string `json:"channel"` + Platform Platform `json:"platform"` +} + +// ResolveCraft defines a typed request for resolving *craft revisions. +type ResolveCraft struct { + InstanceKey string `json:"instance-key"` + Namespace string `json:"namespace"` + // Either Name or ID must be supplied. + Name string `json:"name,omitempty"` + ID string `json:"id,omitempty"` + Channel string `json:"channel"` +} + +// ResolveResponse holds a list of resolved SDKs and *craft recipes. +type ResolveResponse struct { + PackageResults []ResolvePackageResponse `json:"package-results"` + CraftResults []ResolveCraftResponse `json:"craft-results"` +} + +// ResolvePackageResponse describes the response for a single SDK. +type ResolvePackageResponse struct { + InstanceKey string `json:"instance-key"` + // Status can be "ok" or "error". + Status string `json:"status"` + Error *APIError `json:"error,omitempty"` + Namespace string `json:"namespace"` + Name string `json:"name"` + ID string `json:"id"` + Result ResolvePackageResult `json:"result"` +} + +// ResolvePackageResult describes an SDK channel and revision. +type ResolvePackageResult struct { + Channel ResolvePackageChannel `json:"channel,omitzero"` + Revision ResolveRevision `json:"revision"` +} + +// ResolvePackageChannel defines a unique permutation that corresponds to the +// track, risk, branch, and platform. There can be multiple channels of the +// same track and risk, but with different platforms. +type ResolvePackageChannel struct { + Name string `json:"name"` + ReleasedAt timeutil.TimeUTC `json:"released-at"` + Track string `json:"track"` + Risk string `json:"risk"` + Branch string `json:"branch"` + EffectiveChannel string `json:"effective-channel"` + Platform Platform `json:"platform"` +} + +// ResolveRevision contains SDK details that apply to a single revision. +type ResolveRevision struct { + CreatedAt timeutil.TimeUTC `json:"created-at"` + Platforms []Platform `json:"platforms"` + Download Download `json:"download"` + Revision int `json:"revision"` + Version string `json:"version"` +} + +// ResolveCraftResponse describes the response for a single *craft recipe. +type ResolveCraftResponse struct { + InstanceKey string `json:"instance-key"` + // Status can be "ok" or "error". + Status string `json:"status"` + Error *APIError `json:"error,omitempty"` + Namespace string `json:"namespace"` + Name string `json:"name"` + ID string `json:"id"` + Result ResolveCraftResult `json:"result"` +} + +// ResolveCraftResult describes a *craft recipe channel and commit. +type ResolveCraftResult struct { + Channel ResolveCraftChannel `json:"channel,omitzero"` + Commit ResolveCommit `json:"commit"` +} + +// ResolveCraftChannel defines a unique permutation that corresponds to the +// track, risk, branch, and platform. There can be multiple channels of the +// same track and risk, but with different platforms. +type ResolveCraftChannel struct { + Name string `json:"name"` + ReleasedAt timeutil.TimeUTC `json:"released-at"` + Track string `json:"track"` + Risk string `json:"risk"` + Branch string `json:"branch"` + EffectiveChannel string `json:"effective-channel"` +} + +// ResolveCommit describes the provenance of a *craft recipe. +type ResolveCommit struct { + CreatedAt timeutil.TimeUTC `json:"created-at"` + Remote ResolveRemote `json:"remote"` + GitBranch string `json:"git-branch"` + CommitHash string `json:"commit-hash"` + Version string `json:"version"` +} + +// ResolveRemote describes a VCS remote. +type ResolveRemote struct { + URL string `json:"url"` +} From e458b4f5605766c7d611a15f5fc5085113c63c1f Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Mon, 30 Mar 2026 12:01:38 +1300 Subject: [PATCH 05/17] Add SDK Store download client --- internal/sdkstore/client.go | 14 +- internal/sdkstore/download.go | 141 ++++++++++ .../sdkstore/download_integration_test.go | 47 ++++ internal/sdkstore/download_test.go | 243 ++++++++++++++++++ 4 files changed, 442 insertions(+), 3 deletions(-) create mode 100644 internal/sdkstore/download.go create mode 100644 internal/sdkstore/download_integration_test.go create mode 100644 internal/sdkstore/download_test.go diff --git a/internal/sdkstore/client.go b/internal/sdkstore/client.go index fa17f9343..a7141ab40 100644 --- a/internal/sdkstore/client.go +++ b/internal/sdkstore/client.go @@ -57,6 +57,11 @@ func basePath(base *url.URL) path.Path { return path.MakePath(base).JoinPath(serverVersion, serverEntity) } +// downloadPath returns the base configuration path for downloading SDKs. +func downloadPath(base *url.URL) path.Path { + return path.MakePath(base).JoinPath("api", "v1", serverEntity, "download") +} + // resolvePath returns the configuration path for speaking to the resolve API. func resolvePath(base *url.URL) path.Path { return path.MakePath(base).JoinPath(serverVersion, "revisions", "resolve") @@ -64,6 +69,7 @@ func resolvePath(base *url.URL) path.Path { // Client represents the client side of an SDK store. type Client struct { + *downloadClient *findClient *infoClient *resolveClient @@ -82,6 +88,7 @@ func NewClient(config Config) *Client { } base := basePath(baseURL) + downloadPath := downloadPath(baseURL) findPath := base.JoinPath("find") infoPath := base.JoinPath("info") resolvePath := resolvePath(baseURL) @@ -90,8 +97,9 @@ func NewClient(config Config) *Client { restClient := newHTTPRESTClient(apiRequester) return &Client{ - findClient: newFindClient(findPath, restClient), - infoClient: newInfoClient(infoPath, restClient), - resolveClient: newResolveClient(resolvePath, restClient), + downloadClient: newDownloadClient(downloadPath, httpClient), + findClient: newFindClient(findPath, restClient), + infoClient: newInfoClient(infoPath, restClient), + resolveClient: newResolveClient(resolvePath, restClient), } } diff --git a/internal/sdkstore/download.go b/internal/sdkstore/download.go new file mode 100644 index 000000000..456f225fa --- /dev/null +++ b/internal/sdkstore/download.go @@ -0,0 +1,141 @@ +// Copyright 2020 Canonical Ltd. +// Licensed under the AGPLv3. + +package sdkstore + +import ( + "context" + "crypto/sha3" + "encoding/hex" + "fmt" + "io" + "net/http" + + "github.com/canonical/workshop/internal/https" + "github.com/canonical/workshop/internal/logger" + "github.com/canonical/workshop/internal/progress" + "github.com/canonical/workshop/internal/sdkstore/path" +) + +// DownloadOption to be passed to Download to customize the resulting request. +type DownloadOption func(*downloadOptions) + +type downloadOptions struct { + reporter *progress.Reporter +} + +// WithReporter sets the progress reporter on the option. +func WithReporter(reporter *progress.Reporter) DownloadOption { + return func(options *downloadOptions) { + options.reporter = reporter + } +} + +func newDownloadOptions() *downloadOptions { + return &downloadOptions{} +} + +type downloadClient struct { + path path.Path + httpClient https.HTTPClient +} + +func newDownloadClient(path path.Path, httpClient https.HTTPClient) *downloadClient { + return &downloadClient{ + path: path, + httpClient: httpClient, + } +} + +type SdkArchive struct { + Name string + PackageID string + Revision int + Sha3_384 string +} + +// Download writes the given SDK into the given Writer. +func (c *downloadClient) Download(ctx context.Context, w io.Writer, sdk SdkArchive, options ...DownloadOption) error { + opts := newDownloadOptions() + for _, option := range options { + option(opts) + } + + r, err := c.download(ctx, sdk) + if err != nil { + return err + } + defer func() { + _ = r.Body.Close() + }() + + hash := sha3.New384() + writers := []io.Writer{w, hash} + if opts.reporter != nil { + rw := &reporterWriter{reporter: opts.reporter, total: r.ContentLength} + writers = append(writers, rw) + } + + size, err := io.Copy(io.MultiWriter(writers...), r.Body) + if err != nil { + return err + } + if size != r.ContentLength { + return fmt.Errorf("downloaded size %d does not match expected size %d", size, r.ContentLength) + } + + digest := hex.EncodeToString(hash.Sum(nil)) + if digest != sdk.Sha3_384 { + return fmt.Errorf("corrupted download: expected sha3-384 %q", sdk.Sha3_384) + } + return nil +} + +func (c *downloadClient) download(ctx context.Context, sdk SdkArchive) (resp *http.Response, err error) { + path := c.path.JoinPath(fmt.Sprintf("%s_%v.sdk", sdk.PackageID, sdk.Revision)) + req, err := http.NewRequestWithContext(ctx, "GET", path.String(), nil) + if err != nil { + return nil, fmt.Errorf("cannot make new request: %w", err) + } + + resp, err = c.httpClient.Do(req) + if err != nil { + if resp != nil && resp.Body != nil { + resp.Body.Close() + } + return nil, err + } + // If we get anything but a 200 status code, we don't know how to correctly + // handle that scenario. Return early and deal with the failure later on. + if resp.StatusCode == http.StatusOK { + return resp, nil + } + + logger.Noticef("On download from %q: response code %s", path, resp.Status) + + // Ensure we drain the response body so this connection can be reused. As + // there is no error message, we have no ability other than to check the + // status codes. + _, _ = io.Copy(io.Discard, resp.Body) + resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return nil, fmt.Errorf("%q SDK (%v) not found", sdk.Name, sdk.Revision) + } + + // Server error, nothing we can do other than inform the user that the + // archive was unavailable. + return nil, fmt.Errorf("SDK download failed: %s", resp.Status) +} + +type reporterWriter struct { + reporter *progress.Reporter + done int64 + total int64 +} + +func (r *reporterWriter) Write(p []byte) (n int, err error) { + r.done += int64(len(p)) + r.reporter.Report("download", r.done, r.total) + return len(p), nil +} diff --git a/internal/sdkstore/download_integration_test.go b/internal/sdkstore/download_integration_test.go new file mode 100644 index 000000000..758c9a654 --- /dev/null +++ b/internal/sdkstore/download_integration_test.go @@ -0,0 +1,47 @@ +//go:build integration + +package sdkstore + +import ( + "bytes" + "context" + "crypto/sha3" + "encoding/hex" + + "gopkg.in/check.v1" +) + +type downloadIntegration struct{} + +var _ = check.Suite(&downloadIntegration{}) + +func (f *downloadIntegration) TestDownload(c *check.C) { + client := NewClient(Config{}) + hash := sha3.New384() + sdk := SdkArchive{ + Name: "test-sdk-info", + PackageID: "U9IBEcXiDkuaPLYjyUBpRZMETAr7g39e", + Revision: 1, + Sha3_384: "cf722fc841c72cf53c4b2db88608589efb173fa2a50837ae6f07597ead85e6e30f36a85e98df9ba78d941b3c9e15ab3d", + } + + err := client.Download(context.Background(), hash, sdk) + c.Assert(err, check.IsNil) + digest := hex.EncodeToString(hash.Sum(nil)) + c.Check(digest, check.Equals, sdk.Sha3_384) +} + +func (f *downloadIntegration) TestDownloadNotFound(c *check.C) { + client := NewClient(Config{}) + var buffer bytes.Buffer + sdk := SdkArchive{ + Name: "not-found", + PackageID: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + Revision: 55555, + Sha3_384: "cf722fc841c72cf53c4b2db88608589efb173fa2a50837ae6f07597ead85e6e30f36a85e98df9ba78d941b3c9e15ab3d", + } + + err := client.Download(context.Background(), &buffer, sdk) + c.Check(err, check.ErrorMatches, `"not-found" SDK \(55555\) not found`) + c.Check(buffer.Bytes(), check.HasLen, 0) +} diff --git a/internal/sdkstore/download_test.go b/internal/sdkstore/download_test.go new file mode 100644 index 000000000..d855633ae --- /dev/null +++ b/internal/sdkstore/download_test.go @@ -0,0 +1,243 @@ +// Copyright 2020 Canonical Ltd. +// Licensed under the AGPLv3. + +package sdkstore + +import ( + "bytes" + "context" + "crypto/sha3" + "encoding/hex" + "encoding/json" + "io" + "math/rand/v2" + "net/http" + "strings" + + "go.uber.org/mock/gomock" + "gopkg.in/check.v1" + + "github.com/canonical/workshop/internal/progress" + "github.com/canonical/workshop/internal/sdkstore/path" + "github.com/canonical/workshop/internal/sdkstore/transport" + "github.com/canonical/workshop/internal/testutil" +) + +type DownloadSuite struct{} + +var _ = check.Suite(&DownloadSuite{}) + +func (s *DownloadSuite) TestDownload(c *check.C) { + ctrl := gomock.NewController(c) + defer ctrl.Finish() + + baseURL := MustParseURL(c, "http://api.foo.bar") + path := path.MakePath(baseURL) + data := make([]byte, 1024) + + httpClient := NewMockHTTPClient(ctrl) + httpClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(r *http.Request) (*http.Response, error) { + suffix := "/JlcaOWmrknI7ku8GgGUuCrJFoajEPfYy_4242.sdk" + c.Check(strings.HasSuffix(r.URL.Path, suffix), check.Equals, true, check.Commentf("URL = %q", r.URL)) + + return &http.Response{ + StatusCode: 200, + Body: io.NopCloser(bytes.NewReader(data)), + ContentLength: int64(len(data)), + }, nil + }) + + client := newDownloadClient(path, httpClient) + + hash := sha3.New384() + sdk := SdkArchive{ + Name: "test-sdk-download", + PackageID: "JlcaOWmrknI7ku8GgGUuCrJFoajEPfYy", + Revision: 4242, + Sha3_384: "122237164f723d2f553d519e9f2389145df3a13856ddd72d41b608b8a505d155222455fe868c952104d83f068883e291", + } + + err := client.Download(context.Background(), hash, sdk) + c.Assert(err, check.IsNil) + digest := hex.EncodeToString(hash.Sum(nil)) + c.Check(digest, check.Equals, sdk.Sha3_384) +} + +func (s *DownloadSuite) TestDownloadWithReporter(c *check.C) { + ctrl := gomock.NewController(c) + defer ctrl.Finish() + + baseURL := MustParseURL(c, "http://api.foo.bar") + path := path.MakePath(baseURL) + size := int64(1024 * 1024) + + httpClient := NewMockHTTPClient(ctrl) + httpClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(r *http.Request) (*http.Response, error) { + rng := rand.NewChaCha8([32]byte{}) + return &http.Response{ + StatusCode: 200, + Body: io.NopCloser(io.LimitReader(rng, size)), + ContentLength: size, + }, nil + }) + + client := newDownloadClient(path, httpClient) + + hash := sha3.New384() + sdk := SdkArchive{ + Name: "test-sdk-download", + PackageID: "JlcaOWmrknI7ku8GgGUuCrJFoajEPfYy", + Revision: 4242, + Sha3_384: "3a5ae5a90e035abd9a1e923a9fa36196d1b4f0da317089ab899083d473fa9ef0778afac4347b804443b849e6170ffede", + } + var dones, totals []int64 + reporter := &progress.Reporter{ + Name: "name", + Report: func(label string, done, total int64) { + dones = append(dones, done) + totals = append(totals, total) + }, + } + + err := client.Download(context.Background(), hash, sdk, WithReporter(reporter)) + c.Assert(err, check.IsNil) + digest := hex.EncodeToString(hash.Sum(nil)) + c.Check(digest, check.Equals, sdk.Sha3_384) + + c.Check(totals, check.Not(check.HasLen), 0) + c.Check(dones, check.HasLen, len(totals)) + for _, total := range totals { + c.Check(total, check.Equals, size) + } + minDone := 0 + for _, done := range dones { + c.Check(int(done), testutil.IntGreaterEqual, minDone) + c.Check(int(done), testutil.IntLessEqual, int(size)) + minDone = int(done) + } +} + +func (s *DownloadSuite) TestDownloadTruncated(c *check.C) { + ctrl := gomock.NewController(c) + defer ctrl.Finish() + + baseURL := MustParseURL(c, "http://api.foo.bar") + path := path.MakePath(baseURL) + data := make([]byte, 512) + + httpClient := NewMockHTTPClient(ctrl) + httpClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(r *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: 200, + Body: io.NopCloser(bytes.NewReader(data)), + ContentLength: 1024, + }, nil + }) + + client := newDownloadClient(path, httpClient) + + sdk := SdkArchive{ + Name: "test-sdk-download", + PackageID: "JlcaOWmrknI7ku8GgGUuCrJFoajEPfYy", + Revision: 4242, + Sha3_384: "122237164f723d2f553d519e9f2389145df3a13856ddd72d41b608b8a505d155222455fe868c952104d83f068883e291", + } + + err := client.Download(context.Background(), io.Discard, sdk) + c.Check(err, check.ErrorMatches, "downloaded size 512 does not match expected size 1024") +} + +func (s *DownloadSuite) TestDownloadInvalidHash(c *check.C) { + ctrl := gomock.NewController(c) + defer ctrl.Finish() + + baseURL := MustParseURL(c, "http://api.foo.bar") + path := path.MakePath(baseURL) + data := make([]byte, 1024) + + httpClient := NewMockHTTPClient(ctrl) + httpClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(r *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: 200, + Body: io.NopCloser(bytes.NewReader(data)), + ContentLength: int64(len(data)), + }, nil + }) + + client := newDownloadClient(path, httpClient) + + hash := sha3.New384() + sdk := SdkArchive{ + Name: "test-sdk-download", + PackageID: "JlcaOWmrknI7ku8GgGUuCrJFoajEPfYy", + Revision: 4242, + Sha3_384: "3a5ae5a90e035abd9a1e923a9fa36196d1b4f0da317089ab899083d473fa9ef0778afac4347b804443b849e6170ffede", + } + + err := client.Download(context.Background(), hash, sdk) + c.Check(err, check.ErrorMatches, `corrupted download: expected sha3-384 "3a5ae5a90e035abd9a1e923a9fa36196d1b4f0da317089ab899083d473fa9ef0778afac4347b804443b849e6170ffede"`) +} + +func (s *DownloadSuite) TestDownloadNotFound(c *check.C) { + ctrl := gomock.NewController(c) + defer ctrl.Finish() + + baseURL := MustParseURL(c, "http://api.foo.bar") + path := path.MakePath(baseURL) + data, err := json.Marshal(transport.ErrorResponse{ + ErrorList: []transport.APIError{{ + Code: "resource-not-found", + Message: "Cannot download JlcaOWmrknI7ku8GgGUuCrJFoajEPfYy_4242.sdk.", + }}, + }) + c.Assert(err, check.IsNil) + + httpClient := NewMockHTTPClient(ctrl) + httpClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(r *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: 404, + Body: io.NopCloser(bytes.NewReader(data)), + }, nil + }) + + client := newDownloadClient(path, httpClient) + + sdk := SdkArchive{ + Name: "test-sdk-download", + PackageID: "JlcaOWmrknI7ku8GgGUuCrJFoajEPfYy", + Revision: 4242, + Sha3_384: "122237164f723d2f553d519e9f2389145df3a13856ddd72d41b608b8a505d155222455fe868c952104d83f068883e291", + } + + err = client.Download(context.Background(), io.Discard, sdk) + c.Check(err, check.ErrorMatches, `"test-sdk-download" SDK \(4242\) not found`) +} + +func (s *DownloadSuite) TestDownloadInternalServerError(c *check.C) { + ctrl := gomock.NewController(c) + defer ctrl.Finish() + + baseURL := MustParseURL(c, "http://api.foo.bar") + path := path.MakePath(baseURL) + + httpClient := NewMockHTTPClient(ctrl) + httpClient.EXPECT().Do(gomock.Any()).DoAndReturn(func(r *http.Request) (*http.Response, error) { + return &http.Response{ + Status: "500 " + http.StatusText(http.StatusInternalServerError), + StatusCode: http.StatusInternalServerError, + Body: io.NopCloser(strings.NewReader("")), + }, nil + }) + + client := newDownloadClient(path, httpClient) + + sdk := SdkArchive{ + Name: "test-sdk-download", + PackageID: "JlcaOWmrknI7ku8GgGUuCrJFoajEPfYy", + Revision: 4242, + Sha3_384: "122237164f723d2f553d519e9f2389145df3a13856ddd72d41b608b8a505d155222455fe868c952104d83f068883e291", + } + + err := client.Download(context.Background(), io.Discard, sdk) + c.Check(err, check.ErrorMatches, `SDK download failed: 500 Internal Server Error`) +} From bb8247c6177999f80f34e593daafe898a4acf814 Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Wed, 25 Mar 2026 17:48:59 +1300 Subject: [PATCH 06/17] Use SDK Store to plan launches and refreshes --- go.mod | 2 +- internal/daemon/api_workshops_test.go | 119 +-------------- internal/overlord/workshopstate/manifest.go | 141 ++++++++++++++++-- .../overlord/workshopstate/manifest_test.go | 101 ++++++++++--- internal/overlord/workshopstate/request.go | 2 +- internal/sdk/store.go | 80 ++++++++++ 6 files changed, 297 insertions(+), 148 deletions(-) diff --git a/go.mod b/go.mod index bf36a7501..192624b2e 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/canonical/lxd v0.0.0-20260406200201-8c342a79d3ec github.com/canonical/x-go v0.0.0-20230522092633-7947a7587f5b github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf + github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.12.0 github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.1 @@ -47,7 +48,6 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.7 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/internal/daemon/api_workshops_test.go b/internal/daemon/api_workshops_test.go index 7afd9ce4d..9b361879e 100644 --- a/internal/daemon/api_workshops_test.go +++ b/internal/daemon/api_workshops_test.go @@ -94,14 +94,6 @@ base: ubuntu@22.04 sdks: - name: test-sdk channel: latest/stable -` - manysdks_newchan = `name: manysdks -base: ubuntu@22.04 -sdks: - - name: test-sdk - channel: latest/edge - - name: test-sdk-2 - channel: latest/stable ` manysdks_connsadded = `name: manysdks base: ubuntu@22.04 @@ -196,13 +188,6 @@ sdks: endpoint: 8080 ` - wrongbase = `name: wrongbase -base: ubuntu@24.04 -sdks: - - name: test-sdk-3 - channel: latest/stable -` - workshoptunnels = `name: tunnels base: ubuntu@22.04 sdks: @@ -492,7 +477,7 @@ var apiSuiteSdks = map[string]sdk.Meta{ func (s *apiSuite) launchWorkshop(c *check.C, name, yaml string) { s.createWFile(c, name, yaml) - defer s.gcsStore.SetActionCallback(storeAction)() + defer s.store.SetResolveCallback(sdk.FakeResolve(apiSuiteSdks))() defer s.gcsStore.SetDownloadCallback(storeDownload)() reqbuf := bytes.NewBufferString(fmt.Sprintf(`{"names":["%s"],"action":"launch"}`, name)) @@ -909,7 +894,7 @@ type expectedResp struct { } func (s *apiSuite) runActionTest(c *check.C, buffers []*bytes.Buffer, expected []*expectedResp) { - defer s.gcsStore.SetActionCallback(storeAction)() + defer s.store.SetResolveCallback(sdk.FakeResolve(apiSuiteSdks))() s.vars = map[string]string{"id": s.project.ProjectId} requests := []*http.Request{} @@ -1034,19 +1019,6 @@ func storeDownload(ctx context.Context, setup sdk.Setup, report *progress.Report return &sdk.Meta{Setup: setup, SdkYAML: sdkYaml}, nil } -func storeAction(ctx context.Context, actions []sdk.SdkAction) ([]sdk.Meta, error) { - sdks := make([]sdk.Meta, 0, len(actions)) - for _, act := range actions { - sk, ok := apiSuiteSdks[act.Name] - if !ok { - return nil, fmt.Errorf("%q SDK not found in Store", act.Name) - } - sk.Channel = act.Channel - sdks = append(sdks, sk) - } - return sdks, nil -} - func mockSdk(metadir, hooksdir string, meta string) error { if err := os.MkdirAll(metadir, 0755); err != nil { return err @@ -2361,86 +2333,6 @@ func (s *apiSuite) TestRefreshRemoveSdk(c *check.C) { s.ensureSdkVolumesAfterCooldown(c, []string{"test-sdk-1", "system-1"}) } -func (s *apiSuite) TestRefreshNewSdkChannel(c *check.C) { - s.daemon(c) - s.d.Overlord().Loop() - defer s.d.Overlord().Stop() - // Setup - s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() - - requests := []*bytes.Buffer{ - bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), - } - expected := []*expectedResp{ - { - Type: ResponseTypeAsync, - Status: http.StatusAccepted, - Kind: "launch", - Summary: `Launch "manysdks" workshop`, - }, - } - s.runActionTest(c, requests, expected) - - s.createWFile(c, "manysdks", manysdks_newchan) - - requests = []*bytes.Buffer{ - bytes.NewBufferString(`{"names":["manysdks"],"action":"refresh"}`), - } - expected = []*expectedResp{ - { - Type: ResponseTypeAsync, - Status: http.StatusAccepted, - Kind: "refresh", - Summary: `Refresh "manysdks" workshop`, - }, - } - - s.runActionTest(c, requests, expected) - - want := []expectedWorkshop{{ - name: "manysdks", - base: "ubuntu@22.04", - sdks: []sdk.Setup{ - {Name: sdk.System.String(), Source: sdk.SystemSource, Revision: system.SystemSdkRevision}, - {Name: "test-sdk", PackageID: "t5tqUClfNeHiiOpvPvT29O0HkxeaXBOq", Channel: "latest/edge", Revision: sdk.R(1)}, - {Name: "test-sdk-2", PackageID: "iCJybjjWd2n48hKoMdjGEIWwA3i2TmX7", Channel: "latest/stable", Revision: sdk.R(1)}, - }, - connections: []string{ - "b8639dea/manysdks/test-sdk:data b8639dea/manysdks/system:mount", - "b8639dea/manysdks/test-sdk-2:photos b8639dea/manysdks/system:mount", - "b8639dea/manysdks/test-sdk-2:gpu b8639dea/manysdks/system:gpu", - }, - plugs: []string{ - "test-sdk:data", - "test-sdk-2:photos", - "test-sdk-2:gpu", - }, - slots: []string{ - "test-sdk-2:data-slot", - "system:camera", - "system:desktop", - "system:gpu", - "system:mount", - "system:ssh-agent", - }, - }} - s.ensureWorkshops(c, want) - - s.checkSnapshotCalls(c, "manysdks", []string{ - "system", - "test-sdk", - "test-sdk-2", - "test-sdk", - "test-sdk-2", - }) - - s.checkLaunchOrRebuildCalls(c, "manysdks", []launchOrRebuild{ - {manysdks, nil}, - {manysdks_newchan, []string{"system"}}, - }) -} - func updateSdkStoreRev(name string, rev int, meta string) func() { oldrev := apiSuiteSdks[name] @@ -4146,13 +4038,11 @@ func (s *apiSuite) TestValidateSdkInfo(c *check.C) { s.mockTrySdk(c, "test-sdk", "test-sdk_all.sdk", testsdk) s.mockTrySdk(c, "test-sdk-2", "test-sdk-2_all.sdk", testsdk2_invalid) s.createWFile(c, "manysdks", manysdks_try) - s.createWFile(c, "wrongbase", wrongbase) defer s.gcsStore.SetDownloadCallback(storeDownload)() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["basic"],"action":"launch"}`), bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), - bytes.NewBufferString(`{"names":["wrongbase"],"action":"launch"}`), } expected := []*expectedResp{ @@ -4166,11 +4056,6 @@ func (s *apiSuite) TestValidateSdkInfo(c *check.C) { Status: http.StatusBadRequest, Message: `cannot launch "manysdks": SDK must be named "test-sdk-2" (now: "sdk-2")`, }, - { - Type: ResponseTypeError, - Status: http.StatusBadRequest, - Message: `cannot launch "wrongbase": "test-sdk-3" SDK has "ubuntu@22.04" base; required: "ubuntu@24.04"`, - }, } s.runActionTest(c, requests, expected) diff --git a/internal/overlord/workshopstate/manifest.go b/internal/overlord/workshopstate/manifest.go index a5533b5b9..4faf998d8 100644 --- a/internal/overlord/workshopstate/manifest.go +++ b/internal/overlord/workshopstate/manifest.go @@ -11,6 +11,8 @@ import ( "slices" "strings" + "github.com/google/uuid" + "github.com/canonical/workshop/internal/arch" "github.com/canonical/workshop/internal/osutil" "github.com/canonical/workshop/internal/overlord/conflict" @@ -18,6 +20,7 @@ import ( "github.com/canonical/workshop/internal/revert" "github.com/canonical/workshop/internal/sdk" "github.com/canonical/workshop/internal/sdk/system" + "github.com/canonical/workshop/internal/sdkstore/transport" "github.com/canonical/workshop/internal/workshop" ) @@ -127,7 +130,7 @@ func (a *artifactFinder) launchOrRefreshManifests(ctx context.Context, names []s action = "refresh" } - sto := sdk.GcsStoreService(a.state) + sto := sdk.StoreService(a.state) rev := revert.New() defer rev.Fail() @@ -233,21 +236,141 @@ func (w *WorkshopManager) workshopManifest(ctx context.Context, projectId, name return &Manifest{File: wp.File, Image: wp.Image, Sdks: installed}, nil } -func (a *artifactFinder) findStoreSdks(sto sdk.GcsStore, ctx context.Context, file *workshop.File) ([]sdk.Setup, error) { - acts := make([]sdk.SdkAction, 0, len(file.Sdks)) - for _, sd := range file.Sdks { +func (a *artifactFinder) findStoreSdks(sto sdk.Store, ctx context.Context, file *workshop.File) ([]sdk.Setup, error) { + platform, err := makePlatform(file.Base) + if err != nil { + return nil, err + } + + req, err := makeResolveRequest(platform, file.Sdks) + if err != nil { + return nil, err + } + + resp, err := sto.Resolve(ctx, req) + if err != nil { + return nil, err + } + + sdks := make([]sdk.Setup, 0, len(req.Packages)) + errs := make([]error, 0, len(req.Packages)) + for _, pkg := range req.Packages { + idx := slices.IndexFunc(resp.PackageResults, func(p transport.ResolvePackageResponse) bool { + return p.InstanceKey == pkg.InstanceKey + }) + if idx < 0 { + errs = append(errs, fmt.Errorf("%q SDK: not found in refresh response", pkg.Name)) + continue + } + + if err := storeError(resp.PackageResults[idx]); err != nil { + errs = append(errs, err) + continue + } + + setup, err := resolvedSetup(resp.PackageResults[idx]) + if err != nil { + errs = append(errs, err) + continue + } + + sdks = append(sdks, setup) + } + + if len(errs) == 1 { + return nil, errs[0] + } + if len(errs) > 1 { + for i := range errs { + errs[i] = fmt.Errorf("- %w", errs[i]) + } + return nil, fmt.Errorf("multiple SDK Store errors:\n%w", errors.Join(errs...)) + } + + return sdks, nil +} + +func makePlatform(base string) (transport.Platform, error) { + parts := strings.FieldsFunc(base, func(r rune) bool { return r == '@' }) + if len(parts) != 2 { + return transport.Platform{}, fmt.Errorf("invalid base %q (expected @)", base) + } + + return transport.Platform{ + Name: parts[0], + Channel: parts[1], + Architecture: arch.DpkgArchitecture(), + }, nil +} + +func makeResolveRequest(platform transport.Platform, sdks []workshop.SdkRecord) (transport.ResolveRequest, error) { + req := transport.ResolveRequest{ + Packages: make([]transport.ResolvePackage, 0, len(sdks)), + } + for _, sd := range sdks { if sd.Source != sdk.StoreSource { continue } - act := sdk.SdkAction{ProjectId: a.project.ProjectId, Workshop: file.Name, Name: sd.Name, Base: file.Base, Channel: sd.Channel, Action: sdk.Install} - acts = append(acts, act) + + pkg := transport.ResolvePackage{ + Namespace: "sdk", + Name: sd.Name, + Channel: sd.Channel, + Platform: platform, + } + if pkg.Channel == "" { + pkg.Channel = "stable" + } + + for { + uuid, err := uuid.NewRandom() + if err != nil { + return transport.ResolveRequest{}, err + } + pkg.InstanceKey = uuid.String() + + hasKey := func(p transport.ResolvePackage) bool { + return pkg.InstanceKey == p.InstanceKey + } + if !slices.ContainsFunc(req.Packages, hasKey) { + break + } + } + + req.Packages = append(req.Packages, pkg) + } + return req, nil +} + +func storeError(resp transport.ResolvePackageResponse) error { + if resp.Status != "error" && resp.Error == nil { + return nil } - sdks, err := sto.SdkAction(ctx, acts) + message := "unknown error" + if resp.Error != nil { + message = resp.Error.Message + } + + if resp.Name == "" { + return fmt.Errorf("unknown SDK: %s", message) + } + return fmt.Errorf("%q SDK: %s", resp.Name, message) +} + +func resolvedSetup(resp transport.ResolvePackageResponse) (sdk.Setup, error) { + channel, err := sdk.ParseChannel(resp.Result.Channel.EffectiveChannel) if err != nil { - return nil, err + return sdk.Setup{}, err } - return validateSdkMeta(a.project.ProjectId, file, sdks) + + return sdk.Setup{ + Name: resp.Name, + PackageID: resp.ID, + Channel: channel.Full().Name, + Revision: sdk.Revision{N: resp.Result.Revision.Revision}, + Sha3_384: resp.Result.Revision.Download.Sha3_384, + }, nil } func (a *artifactFinder) findLocalSdks(ctx context.Context, current *Manifest, latest *workshop.File) ([]sdk.Setup, error) { diff --git a/internal/overlord/workshopstate/manifest_test.go b/internal/overlord/workshopstate/manifest_test.go index 8e5c8818e..74cfdeb01 100644 --- a/internal/overlord/workshopstate/manifest_test.go +++ b/internal/overlord/workshopstate/manifest_test.go @@ -11,6 +11,7 @@ import ( "path/filepath" "strings" + "github.com/google/uuid" "gopkg.in/check.v1" "gopkg.in/yaml.v3" @@ -21,6 +22,7 @@ import ( "github.com/canonical/workshop/internal/overlord/workshopstate" "github.com/canonical/workshop/internal/sdk" "github.com/canonical/workshop/internal/sdk/system" + "github.com/canonical/workshop/internal/sdkstore/transport" "github.com/canonical/workshop/internal/testutil" "github.com/canonical/workshop/internal/workshop" "github.com/canonical/workshop/internal/workshop/fakebackend" @@ -29,6 +31,7 @@ import ( type manifestSuite struct { state *state.State backend *fakebackend.FakeWorkshopBackend + store *sdk.FakeStore runner *state.TaskRunner manager *workshopstate.WorkshopManager ctx context.Context @@ -68,9 +71,9 @@ func (s *manifestSuite) SetUpTest(c *check.C) { s.project = *project s.ctx = context.WithValue(ctx, workshop.ContextProjectId, s.project.ProjectId) - store := sdk.NewFakeGcsStore() - store.SetActionCallback(s.storeAction) - sdk.ReplaceGcsStore(s.state, store) + s.store = sdk.NewFakeStore() + s.store.SetResolveCallback(sdk.FakeResolve(storeSdks)) + sdk.ReplaceStore(s.state, s.store) } func (s *manifestSuite) TearDownTest(c *check.C) { @@ -126,19 +129,6 @@ var storeSdks = map[string]sdk.Meta{ }, } -func (s *manifestSuite) storeAction(ctx context.Context, actions []sdk.SdkAction) ([]sdk.Meta, error) { - sdks := make([]sdk.Meta, 0, len(actions)) - for _, act := range actions { - sk, ok := storeSdks[act.Name] - if !ok { - return nil, fmt.Errorf("%q SDK not found in Store", act.Name) - } - sk.Channel = act.Channel - sdks = append(sdks, sk) - } - return sdks, nil -} - func mockSdk(metadir, hooksdir string, meta string) error { if err := os.MkdirAll(metadir, 0755); err != nil { return err @@ -459,18 +449,89 @@ func (s *manifestSuite) TestLaunchMissingStoreSdk(c *check.C) { s.createWFile(c, "test-1", "ubuntu@20.04", sdks) _, err := s.manager.LaunchManifests(s.ctx, s.project, []string{"test-1"}) - c.Assert(err, check.ErrorMatches, `cannot launch "test-1": "nonexistent" SDK not found in Store`) + c.Assert(err, check.ErrorMatches, `cannot launch "test-1": "nonexistent" SDK: Package not found`) } -func (s *manifestSuite) TestLaunchValidatesStoreSdks(c *check.C) { +func (s *manifestSuite) TestLaunchMissingStoreSdks(c *check.C) { s.state.Lock() defer s.state.Unlock() - sdks := []workshop.SdkRecord{{Name: "noble", Channel: "latest/stable"}} + sdks := []workshop.SdkRecord{ + {Name: "nonexistent1", Channel: "latest/edge"}, + {Name: "nonexistent2", Channel: "latest/beta"}, + } s.createWFile(c, "test-1", "ubuntu@20.04", sdks) _, err := s.manager.LaunchManifests(s.ctx, s.project, []string{"test-1"}) - c.Assert(err, check.ErrorMatches, `cannot launch "test-1": "noble" SDK has "ubuntu@24.04" base; required: "ubuntu@20.04"`) + c.Assert(err, check.ErrorMatches, `cannot launch "test-1": multiple SDK Store errors: +- "nonexistent1" SDK: Package not found +- "nonexistent2" SDK: Package not found`) +} + +func (s *manifestSuite) TestLaunchValidRequest(c *check.C) { + s.state.Lock() + defer s.state.Unlock() + + architecture := arch.ArchitectureType(arch.DpkgArchitecture()) + arch.SetArchitecture("mock64") + defer arch.SetArchitecture(architecture) + + sdks := []workshop.SdkRecord{ + {Name: "test"}, + {Name: "node", Channel: "latest/edge"}, + } + s.createWFile(c, "test-1", "ubuntu@20.04", sdks) + + manifests, err := s.manager.LaunchManifests(s.ctx, s.project, []string{"test-1"}) + c.Assert(err, check.IsNil) + + c.Assert(manifests, check.HasLen, 1) + + c.Check(manifests[0].File, check.DeepEquals, &workshop.File{ + Name: "test-1", + Base: "ubuntu@20.04", + Sdks: sdks, + }) + c.Check(manifests[0].Image, check.Equals, workshop.BaseImage{Name: "ubuntu@20.04", Fingerprint: "fakeimage123"}) + + systemSdk, err := system.SystemSdkMeta() + c.Assert(err, check.IsNil) + testSdk := storeSdks["test"] + testSdk.Channel = "latest/stable" + nodeSdk := storeSdks["node"] + nodeSdk.Channel = "latest/edge" + c.Check(manifests[0].Sdks, check.DeepEquals, []sdk.Setup{systemSdk.Setup, testSdk.Setup, nodeSdk.Setup}) + + c.Assert(s.store.ResolveCalls, check.HasLen, 1) + c.Check(s.store.ResolveCalls[0].Crafts, check.HasLen, 0) + + c.Assert(s.store.ResolveCalls[0].Packages, check.HasLen, 2) + packages := []transport.ResolvePackage{{ + InstanceKey: s.store.ResolveCalls[0].Packages[0].InstanceKey, + Namespace: "sdk", + Name: "test", + Channel: "stable", + Platform: transport.Platform{ + Name: "ubuntu", + Channel: "20.04", + Architecture: "mock64", + }, + }, { + InstanceKey: s.store.ResolveCalls[0].Packages[1].InstanceKey, + Namespace: "sdk", + Name: "node", + Channel: "latest/edge", + Platform: transport.Platform{ + Name: "ubuntu", + Channel: "20.04", + Architecture: "mock64", + }, + }} + _, err = uuid.Parse(packages[0].InstanceKey) + c.Assert(err, check.IsNil) + _, err = uuid.Parse(packages[1].InstanceKey) + c.Assert(err, check.IsNil) + c.Check(s.store.ResolveCalls[0].Packages, check.DeepEquals, packages) } func (s *manifestSuite) TestLaunchMissingTrySdkDir(c *check.C) { diff --git a/internal/overlord/workshopstate/request.go b/internal/overlord/workshopstate/request.go index 9bc3ddb53..f896ab336 100644 --- a/internal/overlord/workshopstate/request.go +++ b/internal/overlord/workshopstate/request.go @@ -250,7 +250,7 @@ func (w *WorkshopManager) RefreshMany(project workshop.Project, current, latest } func maybeRefresh(installed, candidate sdk.Setup) bool { - return installed.Source != candidate.Source || installed.Channel != candidate.Channel || installed.Revision != candidate.Revision + return installed.Source != candidate.Source || installed.Revision != candidate.Revision } type refreshPlan struct { diff --git a/internal/sdk/store.go b/internal/sdk/store.go index 182c3eda2..1cf7b558b 100644 --- a/internal/sdk/store.go +++ b/internal/sdk/store.go @@ -4,6 +4,8 @@ import ( "context" "crypto/sha3" "encoding/base64" + "errors" + "math/rand/v2" "regexp" "sync" @@ -61,6 +63,7 @@ func StoreService(st *state.State) Store { type Store interface { Find(ctx context.Context, query string, options ...sdkstore.FindOption) ([]transport.FindResponse, error) Info(ctx context.Context, name string, options ...sdkstore.InfoOption) (transport.InfoResponse, error) + Resolve(ctx context.Context, request transport.ResolveRequest) (transport.ResolveResponse, error) } func NewFakeStore() *FakeStore { @@ -75,6 +78,9 @@ type FakeStore struct { InfoCalls []string InfoCallback func(ctx context.Context, name string, options ...sdkstore.InfoOption) (transport.InfoResponse, error) + + ResolveCalls []transport.ResolveRequest + ResolveCallback func(ctx context.Context, req transport.ResolveRequest) (transport.ResolveResponse, error) } func (f *FakeStore) SetFindCallback(find func(ctx context.Context, query string, options ...sdkstore.FindOption) ([]transport.FindResponse, error)) func() { @@ -123,6 +129,29 @@ func (f *FakeStore) Info(ctx context.Context, name string, options ...sdkstore.I return info(ctx, name, options...) } +func (f *FakeStore) SetResolveCallback(resolve func(ctx context.Context, req transport.ResolveRequest) (transport.ResolveResponse, error)) func() { + f.lock.Lock() + defer f.lock.Unlock() + + old := f.ResolveCallback + f.ResolveCallback = resolve + return func() { + f.ResolveCallback = old + } +} + +func (f *FakeStore) Resolve(ctx context.Context, req transport.ResolveRequest) (transport.ResolveResponse, error) { + f.lock.Lock() + f.ResolveCalls = append(f.ResolveCalls, req) + resolve := f.ResolveCallback + f.lock.Unlock() + + if resolve == nil { + return transport.ResolveResponse{}, errors.New("resolve not implemented") + } + return resolve(ctx, req) +} + var nonAlphanumeric = regexp.MustCompile(`[^a-zA-Z0-9]`) // FakePackageID generates a deterministic string which looks like a package @@ -137,6 +166,57 @@ func FakePackageID(name string) string { return packageID } +func FakeResolve(sdks map[string]Meta) func(ctx context.Context, req transport.ResolveRequest) (transport.ResolveResponse, error) { + return func(ctx context.Context, req transport.ResolveRequest) (transport.ResolveResponse, error) { + return fakeResolve(req, sdks) + } +} + +func fakeResolve(req transport.ResolveRequest, sdks map[string]Meta) (transport.ResolveResponse, error) { + responses := make([]transport.ResolvePackageResponse, 0, len(req.Packages)) + + for _, pkg := range req.Packages { + resp := transport.ResolvePackageResponse{ + InstanceKey: pkg.InstanceKey, + Namespace: "sdk", + Name: pkg.Name, + } + + sk, ok := sdks[pkg.Name] + if ok { + resp.Status = "ok" + resp.ID = sk.PackageID + resp.Result = transport.ResolvePackageResult{ + Channel: transport.ResolvePackageChannel{ + Name: pkg.Channel, + EffectiveChannel: pkg.Channel, + Platform: pkg.Platform, + }, + Revision: transport.ResolveRevision{ + Platforms: []transport.Platform{pkg.Platform}, + Download: transport.Download{ + Sha3_384: sk.Sha3_384, + }, + Revision: sk.Revision.N, + }, + } + } else { + resp.Status = "error" + resp.Error = &transport.APIError{ + Code: "package-not-found", + Message: "Package not found", + } + } + + responses = append(responses, resp) + } + + rand.Shuffle(len(responses), func(i, j int) { + responses[i], responses[j] = responses[j], responses[i] + }) + return transport.ResolveResponse{PackageResults: responses}, nil +} + type cachedGcsStoreKey struct{} // ReplaceGcsStore replaces the GCS store used by the manager. From da19c522a8144058fca41410885531c061dd4f25 Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Tue, 7 Apr 2026 16:29:50 +1200 Subject: [PATCH 07/17] Workaround bug where Store only resolves exact base and architecture --- internal/overlord/workshopstate/manifest.go | 94 ++++++++++++++----- .../overlord/workshopstate/manifest_test.go | 12 ++- internal/sdkstore/resolve_integration_test.go | 67 +++++++++++++ 3 files changed, 146 insertions(+), 27 deletions(-) diff --git a/internal/overlord/workshopstate/manifest.go b/internal/overlord/workshopstate/manifest.go index 4faf998d8..8a2a744f8 100644 --- a/internal/overlord/workshopstate/manifest.go +++ b/internal/overlord/workshopstate/manifest.go @@ -237,16 +237,28 @@ func (w *WorkshopManager) workshopManifest(ctx context.Context, projectId, name } func (a *artifactFinder) findStoreSdks(sto sdk.Store, ctx context.Context, file *workshop.File) ([]sdk.Setup, error) { - platform, err := makePlatform(file.Base) + platforms, err := makePlatforms(file.Base) if err != nil { return nil, err } - req, err := makeResolveRequest(platform, file.Sdks) + req, err := makeResolveRequest(platforms[0], file.Sdks) if err != nil { return nil, err } + // We have to request each SDK 4 times because the Store requires SDK + // platforms to exactly match the request. + pkgs := make([]transport.ResolvePackage, 0, len(platforms)*len(req.Packages)) + for i, platform := range platforms { + for _, pkg := range req.Packages { + pkg.InstanceKey += fmt.Sprint("_", i) + pkg.Platform = platform + pkgs = append(pkgs, pkg) + } + } + pkgs, req.Packages = req.Packages, pkgs + resp, err := sto.Resolve(ctx, req) if err != nil { return nil, err @@ -254,27 +266,51 @@ func (a *artifactFinder) findStoreSdks(sto sdk.Store, ctx context.Context, file sdks := make([]sdk.Setup, 0, len(req.Packages)) errs := make([]error, 0, len(req.Packages)) - for _, pkg := range req.Packages { - idx := slices.IndexFunc(resp.PackageResults, func(p transport.ResolvePackageResponse) bool { - return p.InstanceKey == pkg.InstanceKey - }) - if idx < 0 { - errs = append(errs, fmt.Errorf("%q SDK: not found in refresh response", pkg.Name)) - continue - } + for _, pkg := range pkgs { + var setup sdk.Setup + var errActual, errNotFound error + // Pick the most recent SDK among the compatible variants. Some + // platforms are likely to return revision-not-found errors. We ignore + // all errors if at least one SDK was found; if not we return the first + // unexpected error. If all errors are revision-not-found, we return + // the first one. + for i := range platforms { + key := fmt.Sprint(pkg.InstanceKey, "_", i) + idx := slices.IndexFunc(resp.PackageResults, func(p transport.ResolvePackageResponse) bool { + return p.InstanceKey == key + }) + if idx < 0 { + errActual = cmp.Or(errActual, fmt.Errorf("%q SDK: not found in refresh response", pkg.Name)) + continue + } - if err := storeError(resp.PackageResults[idx]); err != nil { - errs = append(errs, err) - continue - } + if err := storeError(resp.PackageResults[idx]); err != nil { + if resp.PackageResults[idx].Error != nil && resp.PackageResults[idx].Error.Code == "revision-not-found" { + errNotFound = cmp.Or(errNotFound, err) + } else { + errActual = cmp.Or(errActual, err) + } + continue + } - setup, err := resolvedSetup(resp.PackageResults[idx]) - if err != nil { - errs = append(errs, err) - continue + s, err := resolvedSetup(resp.PackageResults[idx]) + if err != nil { + errActual = cmp.Or(errActual, err) + continue + } + + if s.Revision.N > setup.Revision.N { + setup = s + } } - sdks = append(sdks, setup) + if !setup.Revision.Unset() { + sdks = append(sdks, setup) + } else if errActual != nil { + errs = append(errs, errActual) + } else if errNotFound != nil { + errs = append(errs, errNotFound) + } } if len(errs) == 1 { @@ -290,17 +326,29 @@ func (a *artifactFinder) findStoreSdks(sto sdk.Store, ctx context.Context, file return sdks, nil } -func makePlatform(base string) (transport.Platform, error) { +func makePlatforms(base string) ([]transport.Platform, error) { parts := strings.FieldsFunc(base, func(r rune) bool { return r == '@' }) if len(parts) != 2 { - return transport.Platform{}, fmt.Errorf("invalid base %q (expected @)", base) + return nil, fmt.Errorf("invalid base %q (expected @)", base) } - return transport.Platform{ + return []transport.Platform{{ Name: parts[0], Channel: parts[1], Architecture: arch.DpkgArchitecture(), - }, nil + }, { + Name: "all", + Channel: "all", + Architecture: arch.DpkgArchitecture(), + }, { + Name: parts[0], + Channel: parts[1], + Architecture: "all", + }, { + Name: "all", + Channel: "all", + Architecture: "all", + }}, nil } func makeResolveRequest(platform transport.Platform, sdks []workshop.SdkRecord) (transport.ResolveRequest, error) { diff --git a/internal/overlord/workshopstate/manifest_test.go b/internal/overlord/workshopstate/manifest_test.go index 74cfdeb01..59e018325 100644 --- a/internal/overlord/workshopstate/manifest_test.go +++ b/internal/overlord/workshopstate/manifest_test.go @@ -505,7 +505,7 @@ func (s *manifestSuite) TestLaunchValidRequest(c *check.C) { c.Assert(s.store.ResolveCalls, check.HasLen, 1) c.Check(s.store.ResolveCalls[0].Crafts, check.HasLen, 0) - c.Assert(s.store.ResolveCalls[0].Packages, check.HasLen, 2) + c.Assert(s.store.ResolveCalls[0].Packages, check.HasLen, 8) packages := []transport.ResolvePackage{{ InstanceKey: s.store.ResolveCalls[0].Packages[0].InstanceKey, Namespace: "sdk", @@ -527,11 +527,15 @@ func (s *manifestSuite) TestLaunchValidRequest(c *check.C) { Architecture: "mock64", }, }} - _, err = uuid.Parse(packages[0].InstanceKey) + key, found := strings.CutSuffix(packages[0].InstanceKey, "_0") + c.Assert(found, check.Equals, true) + _, err = uuid.Parse(key) c.Assert(err, check.IsNil) - _, err = uuid.Parse(packages[1].InstanceKey) + key, found = strings.CutSuffix(packages[1].InstanceKey, "_0") + c.Assert(found, check.Equals, true) + _, err = uuid.Parse(key) c.Assert(err, check.IsNil) - c.Check(s.store.ResolveCalls[0].Packages, check.DeepEquals, packages) + c.Check(s.store.ResolveCalls[0].Packages[:2], check.DeepEquals, packages) } func (s *manifestSuite) TestLaunchMissingTrySdkDir(c *check.C) { diff --git a/internal/sdkstore/resolve_integration_test.go b/internal/sdkstore/resolve_integration_test.go index fecd3d525..bbd62dcd0 100644 --- a/internal/sdkstore/resolve_integration_test.go +++ b/internal/sdkstore/resolve_integration_test.go @@ -5,10 +5,12 @@ package sdkstore import ( "context" "encoding/json" + "fmt" "gopkg.in/check.v1" "github.com/canonical/workshop/internal/sdkstore/transport" + "github.com/canonical/workshop/internal/testutil" ) type resolveIntegration struct{} @@ -92,3 +94,68 @@ func (f *resolveIntegration) TestResolveNotFound(c *check.C) { c.Assert(err, check.IsNil) c.Check(response, check.DeepEquals, expected) } + +func (f *resolveIntegration) TestResolvePlatformWorkaround(c *check.C) { + req := transport.ResolveRequest{ + Packages: []transport.ResolvePackage{{ + InstanceKey: "baseAndArch", + Namespace: "sdk", + Name: "go", + Channel: "1.25/stable", + Platform: transport.Platform{ + Name: "ubuntu", + Channel: "24.04", + Architecture: "amd64", + }, + }, { + InstanceKey: "baseOnly", + Namespace: "sdk", + Name: "go", + Channel: "1.25/stable", + Platform: transport.Platform{ + Name: "ubuntu", + Channel: "24.04", + Architecture: "all", + }, + }, { + InstanceKey: "archOnly", + Namespace: "sdk", + Name: "go", + Channel: "1.25/stable", + Platform: transport.Platform{ + Name: "all", + Channel: "all", + Architecture: "amd64", + }, + }, { + InstanceKey: "neither", + Namespace: "sdk", + Name: "go", + Channel: "1.25/stable", + Platform: transport.Platform{ + Name: "all", + Channel: "all", + Architecture: "all", + }, + }}, + } + + client := NewClient(Config{}) + resp, err := client.Resolve(context.Background(), req) + c.Assert(err, check.IsNil) + + var summary []string + for _, pkg := range resp.PackageResults { + var code string + if pkg.Error != nil { + code = string(pkg.Error.Code) + } + summary = append(summary, fmt.Sprintf("%s: %s%s", pkg.InstanceKey, pkg.Result.Channel.EffectiveChannel, code)) + } + c.Check(summary, testutil.DeepUnsortedMatches, []string{ + "baseAndArch: revision-not-found", + "baseOnly: revision-not-found", + "archOnly: 1.25/stable", + "neither: revision-not-found", + }) +} From 7a3ba4d231f967d2a44ff51e0b5cafaf2d193ed1 Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Mon, 30 Mar 2026 18:13:26 +1300 Subject: [PATCH 08/17] Use SDK Store to download SDKs --- internal/daemon/api_test.go | 9 +- internal/daemon/api_workshops_test.go | 201 ++++++++++++-------- internal/overlord/sdkstate/handlers.go | 183 +++++++++++++----- internal/overlord/sdkstate/handlers_test.go | 77 +------- internal/sdk/store.go | 29 +++ internal/sdk/system/system.go | 45 +---- internal/sdk/system/system_test.go | 29 +-- internal/workshop/fakebackend/backend.go | 17 +- 8 files changed, 326 insertions(+), 264 deletions(-) diff --git a/internal/daemon/api_test.go b/internal/daemon/api_test.go index 531995865..72919fa6e 100644 --- a/internal/daemon/api_test.go +++ b/internal/daemon/api_test.go @@ -29,7 +29,6 @@ import ( "github.com/canonical/workshop/internal/osutil" "github.com/canonical/workshop/internal/overlord" "github.com/canonical/workshop/internal/overlord/ifacestate" - "github.com/canonical/workshop/internal/progress" "github.com/canonical/workshop/internal/sdk" "github.com/canonical/workshop/internal/sdk/system" "github.com/canonical/workshop/internal/testutil" @@ -44,7 +43,6 @@ type apiSuite struct { b *fakebackend.FakeWorkshopBackend secBackend *ifacetest.TestSecurityBackend store *sdk.FakeStore - gcsStore *sdk.FakeGcsStore workshopDir string user *user.User @@ -82,11 +80,7 @@ func (s *apiSuite) SetUpTest(c *check.C) { } s.store = sdk.NewFakeStore() - s.gcsStore = &sdk.FakeGcsStore{} - retrieveSystemSdk := func(setup sdk.Setup, report *progress.Reporter) (*sdk.Meta, error) { - return s.gcsStore.DownloadSdk(context.TODO(), setup, report) - } - s.restoreRetrieve = system.FakeRetrieveSystemSdk(retrieveSystemSdk) + s.restoreRetrieve = system.FakeRetrieveSystemSdk(systemDownload(c)) s.b, err = fakebackend.New(c.MkDir()) c.Check(err, check.IsNil) @@ -150,7 +144,6 @@ func (s *apiSuite) daemon(c *check.C) *Daemon { c.Assert(err, check.IsNil) sdk.ReplaceStore(d.state, s.store) - sdk.ReplaceGcsStore(d.state, s.gcsStore) c.Assert(d.overlord.StartUp(), check.IsNil) d.addRoutes() diff --git a/internal/daemon/api_workshops_test.go b/internal/daemon/api_workshops_test.go index 9b361879e..44d47217d 100644 --- a/internal/daemon/api_workshops_test.go +++ b/internal/daemon/api_workshops_test.go @@ -8,6 +8,7 @@ import ( _ "embed" "errors" "fmt" + "io" "io/fs" "net/http" "os" @@ -23,7 +24,6 @@ import ( "github.com/canonical/workshop/internal/fsutil" "github.com/canonical/workshop/internal/interfaces" - "github.com/canonical/workshop/internal/osutil" "github.com/canonical/workshop/internal/overlord/conflict" "github.com/canonical/workshop/internal/overlord/hookstate" "github.com/canonical/workshop/internal/overlord/sdkstate" @@ -31,6 +31,7 @@ import ( "github.com/canonical/workshop/internal/progress" "github.com/canonical/workshop/internal/sdk" "github.com/canonical/workshop/internal/sdk/system" + "github.com/canonical/workshop/internal/sdkstore" "github.com/canonical/workshop/internal/testutil" "github.com/canonical/workshop/internal/workshop" "github.com/canonical/workshop/internal/workshop/fakebackend" @@ -188,6 +189,13 @@ sdks: endpoint: 8080 ` + wrongbase = `name: wrongbase +base: ubuntu@24.04 +sdks: + - name: test-sdk-3 + channel: latest/stable +` + workshoptunnels = `name: tunnels base: ubuntu@22.04 sdks: @@ -478,7 +486,7 @@ func (s *apiSuite) launchWorkshop(c *check.C, name, yaml string) { s.createWFile(c, name, yaml) defer s.store.SetResolveCallback(sdk.FakeResolve(apiSuiteSdks))() - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() reqbuf := bytes.NewBufferString(fmt.Sprintf(`{"names":["%s"],"action":"launch"}`, name)) s.vars = map[string]string{"id": s.project.ProjectId} @@ -970,53 +978,71 @@ func (s *apiSuite) createWFile(c *check.C, name, yaml string) { c.Assert(err, check.IsNil) } -func storeDownloadWithSaveRestore(ctx context.Context, setup sdk.Setup, report *progress.Reporter) (*sdk.Meta, error) { - meta, err := storeDownload(ctx, setup, report) - if err != nil || setup.Source == sdk.SystemSource { - return meta, err - } - hooksdir := filepath.Join(setup.Filepath(), "sdk", "hooks") - if err := os.WriteFile(filepath.Join(hooksdir, "save-state"), nil, 0o644); err != nil { - return nil, err - } - if err := os.WriteFile(filepath.Join(hooksdir, "restore-state"), nil, 0o644); err != nil { - return nil, err +func systemDownload(c *check.C) func(file *os.File, setup sdk.Setup, report *progress.Reporter) error { + return func(file *os.File, setup sdk.Setup, report *progress.Reporter) error { + vfs := c.MkDir() + if err := os.CopyFS(vfs, system.SystemSdkFs); err != nil { + return err + } + + // The fake backend wants an SDK directory, but retrieve-sdk + // already opened a file for us. We create the SDK directory + // somewhere else and record its location in the file. + if _, err := file.WriteString(vfs); err != nil { + return err + } + + // Cache sdk.yaml so that retrieve-sdk doesn't try to extract it. + meta := filepath.Join(vfs, "meta", "sdk.yaml") + return os.Symlink(meta, setup.Filepath()+".yaml") } - return meta, nil } -func storeDownload(ctx context.Context, setup sdk.Setup, report *progress.Reporter) (*sdk.Meta, error) { - // Emulate store action behaviour which would reuse the existing SDK if - // present. - sdkdir := setup.Filepath() - _, isDir, err := osutil.ExistsIsDir(sdkdir) - if isDir { - sdkYaml := "name: " + setup.Name - content, err := os.ReadFile(filepath.Join(sdkdir, "meta", "sdk.yaml")) - if err == nil { - sdkYaml = string(content) +func storeDownloadWithSaveRestore(c *check.C) func(ctx context.Context, w io.Writer, sdk sdkstore.SdkArchive, options ...sdkstore.DownloadOption) error { + return func(ctx context.Context, w io.Writer, sdk sdkstore.SdkArchive, options ...sdkstore.DownloadOption) error { + sdkdir, err := storeDownloadImpl(c, w, sdk) + if err != nil { + return err } - return &sdk.Meta{Setup: setup, SdkYAML: sdkYaml}, nil - } - if err != nil { - return nil, err + + hooksdir := filepath.Join(sdkdir, "sdk", "hooks") + err1 := os.WriteFile(filepath.Join(hooksdir, "save-state"), nil, 0o644) + err2 := os.WriteFile(filepath.Join(hooksdir, "restore-state"), nil, 0o644) + return cmp.Or(err1, err2) } +} - if setup.Source == sdk.SystemSource { - if err := os.CopyFS(sdkdir, system.SystemSdkFs); err != nil { - return nil, err - } - return system.SystemSdkMeta() +func storeDownload(c *check.C) func(ctx context.Context, w io.Writer, sdk sdkstore.SdkArchive, options ...sdkstore.DownloadOption) error { + return func(ctx context.Context, w io.Writer, sdk sdkstore.SdkArchive, options ...sdkstore.DownloadOption) error { + _, err := storeDownloadImpl(c, w, sdk) + return err } +} +func storeDownloadImpl(c *check.C, w io.Writer, sk sdkstore.SdkArchive) (string, error) { + sdkdir := c.MkDir() metadir := filepath.Join(sdkdir, "meta") hooksdir := filepath.Join(sdkdir, "sdk", "hooks") - sdkYaml := apiSuiteSdks[setup.Name].SdkYAML + sdkYaml := apiSuiteSdks[sk.Name].SdkYAML if err := mockSdk(metadir, hooksdir, sdkYaml); err != nil { - return nil, err + return "", err + } + + // The fake backend wants an SDK directory, but retrieve-sdk + // already opened a file for us. We create the SDK directory + // somewhere else and record its location in the file. + if _, err := io.WriteString(w, sdkdir); err != nil { + return "", err + } + + // Cache sdk.yaml so that retrieve-sdk doesn't try to extract it. + meta := filepath.Join(metadir, "sdk.yaml") + setup := sdk.Setup{Name: sk.Name, Revision: sdk.Revision{N: sk.Revision}} + if err := os.Symlink(meta, setup.Filepath()+".yaml"); err != nil { + return "", err } - return &sdk.Meta{Setup: setup, SdkYAML: sdkYaml}, nil + return sdkdir, nil } func mockSdk(metadir, hooksdir string, meta string) error { @@ -1072,7 +1098,7 @@ func (s *apiSuite) TestLaunchWorkshopBasic(c *check.C) { // Setup s.createWFile(c, "basic", basic) s.createWFile(c, "basic-invalid", basic_invalid) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["basic", "basic", "basic"],"action":"launch"}`), @@ -1145,7 +1171,7 @@ func (s *apiSuite) TestLaunchWorkshopWithSlotOK(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "workshopslot", workshopslot) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["workshopslot"],"action":"launch"}`), @@ -1175,7 +1201,7 @@ func (s *apiSuite) TestLaunchWorkshopFailed(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() s.secBackend.SetupCallback = func(context context.Context, sdkRef sdk.Ref, repo *interfaces.Repository) error { return fmt.Errorf(`cannot assign profile to "manysdks"`) @@ -1228,7 +1254,7 @@ func (s *apiSuite) TestSnapshotFormat(c *check.C) { s.d.Overlord().Loop() defer s.d.Overlord().Stop() - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() s.createWFile(c, "manysdks", manysdks_diverse) s.mockTrySdk(c, "test-sdk-2", "test-sdk-2_all.sdk", testsdk2) @@ -1480,7 +1506,7 @@ func (s *apiSuite) TestLaunchWorkshopPlugBindsSuccess(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "somebound", somebound) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["somebound"],"action":"launch"}`), @@ -1516,7 +1542,7 @@ func (s *apiSuite) TestLaunchWorkshopBindPlugNoMasterPlug(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "masterunknown", masterunknown) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["masterunknown"],"action":"launch"}`), @@ -1541,7 +1567,7 @@ func (s *apiSuite) TestLaunchWorkshopBindPlugNoSlavePlug(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "slaveunknown", slaveunknown) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["slaveunknown"],"action":"launch"}`), @@ -1566,7 +1592,7 @@ func (s *apiSuite) TestLaunchWorkshopBindPlugIncompatibleIface(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "bindincompatible", bindincompatible) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["bindincompatible"],"action":"launch"}`), @@ -1592,7 +1618,7 @@ func (s *apiSuite) TestLaunchWorkshopWithPlugOK(c *check.C) { // Setup s.createWFile(c, "workshopplug", workshopplug) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["workshopplug"],"action":"launch"}`), @@ -1626,7 +1652,7 @@ func (s *apiSuite) TestLaunchWorkshopPlugAddedAndBound(c *check.C) { // Setup s.createWFile(c, "workshopplugbound", workshopplugbound) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["workshopplugbound"],"action":"launch"}`), @@ -1662,7 +1688,7 @@ func (s *apiSuite) TestWorkshopConnectionsOK(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "workshopconns", workshopconns) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["workshopconns"],"action":"launch"}`), @@ -1717,7 +1743,7 @@ func (s *apiSuite) TestWorkshopConnectionsUnknownPlug(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "workshopbrokenconn", workshopbrokenconn) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["workshopbrokenconn"],"action":"launch"}`), @@ -1750,7 +1776,7 @@ func (s *apiSuite) TestWorkshopConnectionsPlugIsBoundTo(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "connsplugbound", connsplugbound) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["connsplugbound"],"action":"launch"}`), @@ -1972,7 +1998,7 @@ func (s *apiSuite) TestRefreshMany(c *check.C) { s.createWFile(c, "basic", basic) s.createWFile(c, "manysdks", manysdks) s.createWFile(c, "somebound", somebound) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["basic", "manysdks", "somebound"],"action":"launch"}`), @@ -2096,7 +2122,7 @@ func (s *apiSuite) TestRefreshAddSdk(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "basic", basic) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["basic"],"action":"launch"}`), @@ -2178,7 +2204,7 @@ func (s *apiSuite) TestRefreshInsertNewSdk(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -2264,7 +2290,7 @@ func (s *apiSuite) TestRefreshRemoveSdk(c *check.C) { // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -2352,7 +2378,7 @@ func (s *apiSuite) TestRefreshSdkNewRevision(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -2438,7 +2464,7 @@ func (s *apiSuite) TestRefreshSaveAndRestoreState(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownloadWithSaveRestore)() + defer s.store.SetDownloadCallback(storeDownloadWithSaveRestore(c))() // Launch requests := []*bytes.Buffer{ @@ -2577,7 +2603,7 @@ func (s *apiSuite) TestRefreshTrySdk(c *check.C) { s.mockTrySdk(c, "test-sdk", "test-sdk_all.sdk", testsdk) s.mockTrySdk(c, "test-sdk-2", "test-sdk-2_all_ubuntu@22.04.sdk", testsdk2) s.createWFile(c, "manysdks", manysdks_try) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -2690,7 +2716,7 @@ func (s *apiSuite) TestRefreshSdkNewProjectFiles(c *check.C) { s.mockProjectSdk(c, "test-sdk", testsdk) s.mockProjectSdk(c, "test-sdk-2", testsdk2) s.createWFile(c, "manysdks", manysdks_project) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -2800,7 +2826,7 @@ func (s *apiSuite) TestRefreshConnectionsChanged(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -2879,7 +2905,7 @@ func (s *apiSuite) TestRefreshSdkRecordPlugChanged(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -2960,7 +2986,7 @@ func (s *apiSuite) TestRefreshSystemDefinitionExtended(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -3034,7 +3060,7 @@ func (s *apiSuite) TestRefreshSdkRecordPlugRemoved(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "manysdks", manysdks_plugadded) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -3113,7 +3139,7 @@ func (s *apiSuite) TestRefreshNoChanges(c *check.C) { defer func() { _ = s.d.Overlord().Stop() }() // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -3191,7 +3217,7 @@ func (s *apiSuite) TestRefreshRestore(c *check.C) { defer func() { _ = s.d.Overlord().Stop() }() // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -3270,7 +3296,7 @@ func (s *apiSuite) TestRefreshBaseChange(c *check.C) { defer func() { _ = s.d.Overlord().Stop() }() // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -3352,7 +3378,7 @@ func (s *apiSuite) TestRefreshBaseUpdate(c *check.C) { defer func() { _ = s.d.Overlord().Stop() }() // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() oldGetBase := s.b.GetBaseCallback s.b.GetBaseCallback = func(ctx context.Context, base string) (workshop.BaseImage, error) { @@ -3450,7 +3476,7 @@ func (s *apiSuite) TestRefreshSystemSdkInstalledFirst(c *check.C) { defer func() { _ = s.d.Overlord().Stop() }() // Setup s.createWFile(c, "manysdks", manysdks_system) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -3525,7 +3551,7 @@ func (s *apiSuite) TestRefreshAllSdksRemoved(c *check.C) { // Setup s.mockProjectSdk(c, "test-sdk-2", testsdk2) s.createWFile(c, "basic", basic_refreshed) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["basic"],"action":"launch"}`), @@ -3591,7 +3617,7 @@ func (s *apiSuite) TestRefreshRestoreFromStash(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -3671,7 +3697,7 @@ func (s *apiSuite) TestRefreshNoRefreshInProgress(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "basic", basic) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["basic"],"action":"launch"}`), @@ -3706,7 +3732,7 @@ func (s *apiSuite) TestRefreshContinue(c *check.C) { s.d.Overlord().Loop() defer s.d.Overlord().Stop() s.createWFile(c, "basic", basic) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() var errOnce sync.Once s.secBackend.RemoveCallback = func(sdkName string) error { @@ -3771,7 +3797,7 @@ func (s *apiSuite) TestRefreshAbort(c *check.C) { s.d.Overlord().Loop() defer s.d.Overlord().Stop() s.createWFile(c, "basic", basic) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() var errOnce sync.Once s.secBackend.RemoveCallback = func(sdkName string) error { @@ -4038,11 +4064,13 @@ func (s *apiSuite) TestValidateSdkInfo(c *check.C) { s.mockTrySdk(c, "test-sdk", "test-sdk_all.sdk", testsdk) s.mockTrySdk(c, "test-sdk-2", "test-sdk-2_all.sdk", testsdk2_invalid) s.createWFile(c, "manysdks", manysdks_try) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + s.createWFile(c, "wrongbase", wrongbase) + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["basic"],"action":"launch"}`), bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), + bytes.NewBufferString(`{"names":["wrongbase"],"action":"launch"}`), } expected := []*expectedResp{ @@ -4056,6 +4084,13 @@ func (s *apiSuite) TestValidateSdkInfo(c *check.C) { Status: http.StatusBadRequest, Message: `cannot launch "manysdks": SDK must be named "test-sdk-2" (now: "sdk-2")`, }, + { + Type: ResponseTypeAsync, + Status: http.StatusAccepted, + Kind: "launch", + Summary: `Launch "wrongbase" workshop`, + ChangeErr: `(?s).*\("test-sdk-3" SDK has "ubuntu@22\.04" base; required: "ubuntu@24\.04"\)`, + }, } s.runActionTest(c, requests, expected) @@ -4101,7 +4136,7 @@ func (s *apiSuite) TestLaunchWorkshopRefreshLaunchInProgress(c *check.C) { defer s.d.Overlord().Stop() s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() var errOnce sync.Once s.secBackend.SetupCallback = func(context context.Context, sdkRef sdk.Ref, repo *interfaces.Repository) error { @@ -4146,7 +4181,7 @@ func (s *apiSuite) TestLaunchWorkshopContinueSuccess(c *check.C) { defer s.d.Overlord().Stop() s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() var errOnce sync.Once s.secBackend.SetupCallback = func(context context.Context, sdkRef sdk.Ref, repo *interfaces.Repository) error { @@ -4191,7 +4226,7 @@ func (s *apiSuite) TestLaunchWorkshopNoRefreshInProgress(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "basic", basic) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["basic"],"action":"launch"}`), @@ -4227,7 +4262,7 @@ func (s *apiSuite) TestLaunchWorkshopChangeAbort(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() var errOnce sync.Once s.secBackend.SetupCallback = func(context context.Context, sdkRef sdk.Ref, repo *interfaces.Repository) error { @@ -4273,7 +4308,7 @@ func (s *apiSuite) TestRefreshPartialOK(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "manysdks", manysdks) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -4391,7 +4426,7 @@ func (s *apiSuite) TestRefreshConflictChange(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "basic", basic) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() var errOnce sync.Once s.secBackend.RemoveCallback = func(sdkName string) error { @@ -4455,7 +4490,7 @@ func (s *apiSuite) TestSDKInstallationOrder(c *check.C) { defer s.d.Overlord().Stop() // Install test-sdk-2 first. s.createWFile(c, "manysdks", manysdks_reversed) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["manysdks"],"action":"launch"}`), @@ -4517,7 +4552,7 @@ func (s *apiSuite) TestStartWorkshop(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "basic", basic) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["basic"],"action":"launch"}`), @@ -4568,7 +4603,7 @@ func (s *apiSuite) TestStopWorkshop(c *check.C) { defer s.d.Overlord().Stop() // Setup s.createWFile(c, "basic", basic) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["basic"],"action":"launch"}`), @@ -4605,7 +4640,7 @@ func (s *apiSuite) TestRemoveWorkshopSuccess(c *check.C) { // Setup s.createWFile(c, "workshopconns", workshopconns) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["workshopconns"],"action":"launch"}`), @@ -4637,7 +4672,7 @@ func (s *apiSuite) TestRemoveWorkshopNotFound(c *check.C) { // Setup s.createWFile(c, "workshopconns", workshopconns) - defer s.gcsStore.SetDownloadCallback(storeDownload)() + defer s.store.SetDownloadCallback(storeDownload(c))() requests := []*bytes.Buffer{ bytes.NewBufferString(`{"names":["workshopconns"],"action":"remove"}`), diff --git a/internal/overlord/sdkstate/handlers.go b/internal/overlord/sdkstate/handlers.go index 15ba6c599..616838db8 100644 --- a/internal/overlord/sdkstate/handlers.go +++ b/internal/overlord/sdkstate/handlers.go @@ -1,10 +1,13 @@ package sdkstate import ( + "cmp" "context" "errors" "fmt" "os" + "os/exec" + "path/filepath" "slices" "time" @@ -12,12 +15,14 @@ import ( "github.com/canonical/workshop/internal/interfaces/policy" "github.com/canonical/workshop/internal/logger" + "github.com/canonical/workshop/internal/osutil" . "github.com/canonical/workshop/internal/overlord/handlersetup" "github.com/canonical/workshop/internal/overlord/state" "github.com/canonical/workshop/internal/progress" "github.com/canonical/workshop/internal/revert" "github.com/canonical/workshop/internal/sdk" "github.com/canonical/workshop/internal/sdk/system" + "github.com/canonical/workshop/internal/sdkstore" "github.com/canonical/workshop/internal/workshop" ) @@ -67,25 +72,6 @@ func sdkName(task *state.Task) (string, error) { return sk, nil } -// replaceSdkSetup updates a launch or refresh change with the latest SDK -// revision, if it changed between the planning phase and retrieval. TODO: -// remove this once the Store supports downloading a specific revision. -func replaceSdkSetup(change *state.Change, w string, setup sdk.Setup) { - setups, err := WorkshopSdks(change, w) - if err != nil { - // Nothing to replace. - return - } - - for i, s := range setups { - if s.Name == setup.Name { - setups[i] = setup - change.Set(WorkshopSdksKey(w), setups) - break - } - } -} - func (m *SdkManager) doRetrieveSdk(task *state.Task, tomb *tomb.Tomb) error { user, project, w, err := UserProjectWorkshop(task) if err != nil { @@ -97,9 +83,92 @@ func (m *SdkManager) doRetrieveSdk(task *state.Task, tomb *tomb.Tomb) error { return err } + st := task.State() + st.Lock() + base, err := WorkshopBase(task.Change(), w) + st.Unlock() + if err != nil { + return err + } + ctx, cancel := BackendContext(tomb, user, project.ProjectId) defer cancel() + if _, err = m.backend.Sdk(ctx, rec); err == nil { + logger.Debugf("On doRetrieveSdk: reuse existing SDK volume %q", sdk.VolumeName(rec.Name, rec.Revision)) + return nil + } + + fl, err := sdk.OpenLock(rec.Name) + if err != nil { + return err + } + defer fl.Close() + if err := fl.Lock(); err != nil { + return err + } + + if err := m.retrieveSdk(ctx, task, rec); err != nil { + return err + } + + sdkYaml, err := extractSdkYAML(ctx, rec) + if err != nil { + return err + } + if err := workshop.ValidateSdkInfo(project.ProjectId, w, base.Name, rec.Name, sdkYaml); err != nil { + return err + } + meta := sdk.Meta{Setup: rec, SdkYAML: sdkYaml} + + reader, err := os.Open(rec.Filepath()) + if err != nil { + return err + } + defer reader.Close() + + err = m.backend.ImportSdk(ctx, meta, reader) + if errors.Is(err, workshop.ErrVolumeAlreadyExists) { + logger.Debugf("On doRetrieveSdk: reuse existing SDK volume %q", sdk.VolumeName(meta.Name, meta.Revision)) + return nil + } + if err != nil { + return err + } + + // If the SDK was downloaded successfully, remove its previous rev if any. + if err := cleanupSdk(rec); err != nil { + logger.Noticef("On doRetrieveSdk: cannot cleanup previous download: %v", err) + } + return nil +} + +func (m *SdkManager) retrieveSdk(ctx context.Context, task *state.Task, rec sdk.Setup) error { + path := rec.Filepath() + if osutil.FileExists(path) { + logger.Debugf("On doRetrieveSdk: %q SDK found locally: %s", rec.Name, path) + return nil + } + + rev := revert.New() + defer rev.Fail() + + writer, err := osutil.NewAtomicFile(path, 0666, 0, osutil.NoChown, osutil.NoChown) + if err != nil { + return err + } + rev.Add(func() { + if reverr := writer.Cancel(); reverr != nil { + logger.Noticef("On doRetrieveSdk: %v", reverr) + } + }) + + archive := sdkstore.SdkArchive{ + Name: rec.Name, + PackageID: rec.PackageID, + Revision: rec.Revision.N, + Sha3_384: rec.Sha3_384, + } st := task.State() reporter := &progress.Reporter{ Name: task.ID(), @@ -110,47 +179,67 @@ func (m *SdkManager) doRetrieveSdk(task *state.Task, tomb *tomb.Tomb) error { }, } - var meta *sdk.Meta if rec.Source == sdk.SystemSource { - meta, err = system.RetrieveSystemSdk(rec, reporter) + err = system.RetrieveSystemSdk(writer.File, rec, reporter) } else { - st.Lock() - store := sdk.GcsStoreService(st) - st.Unlock() - - meta, err = store.DownloadSdk(ctx, rec, reporter) + err = m.store.Download(ctx, writer, archive, sdkstore.WithReporter(reporter)) } if err != nil { return err } - // TODO: We should be downloading a specific revision. Remove this when - // the Store supports that (it will probably have to be removed anyway - // since DownloadSdk won't return metadata after that). - if meta.Revision != rec.Revision { - st.Lock() - change := task.Change() - replaceSdkSetup(change, w, meta.Setup) - base, err := WorkshopBase(change, w) - st.Unlock() - if err != nil { - return err - } + if err := writer.Commit(); err != nil { + return err + } - if err := workshop.ValidateSdkInfo(project.ProjectId, w, base.Name, meta.Name, meta.SdkYAML); err != nil { - return err - } + rev.Success() + return nil +} + +func extractSdkYAML(ctx context.Context, rec sdk.Setup) (string, error) { + path := rec.Filepath() + cache := path + ".yaml" + + content, err := os.ReadFile(cache) + if err == nil { + return string(content), nil } - file, err := os.Open(meta.Filepath()) + cmd := exec.CommandContext(ctx, "tar", + "--extract", + "--to-stdout", + "--force-local", + "--file="+path, + "meta/sdk.yaml", + ) + content, err = cmd.Output() + if err != nil { + return "", err + } + + if err := osutil.AtomicWriteFile(cache, content, 0666, 0); err != nil { + return "", err + } + return string(content), nil +} + +func cleanupSdk(rec sdk.Setup) error { + target := rec.Filepath() + matches, err := filepath.Glob(filepath.Join(filepath.Dir(target), rec.Name+"_*.sdk")) if err != nil { return err } - defer file.Close() - err = m.backend.ImportSdk(ctx, *meta, file) - if errors.Is(err, workshop.ErrVolumeAlreadyExists) { - logger.Debugf("SDK Manager on maybeCreateVolume: reuse existing SDK volume %q", sdk.VolumeName(meta.Name, meta.Revision)) - return nil + + for _, m := range matches { + if m == target { + continue + } + if err1 := os.Remove(m + ".yaml"); err1 != nil && !errors.Is(err1, os.ErrNotExist) { + err = cmp.Or(err, err1) + } + if err1 := os.Remove(m); err1 != nil { + err = cmp.Or(err, err1) + } } return err diff --git a/internal/overlord/sdkstate/handlers_test.go b/internal/overlord/sdkstate/handlers_test.go index 3f8240bad..5ed569a81 100644 --- a/internal/overlord/sdkstate/handlers_test.go +++ b/internal/overlord/sdkstate/handlers_test.go @@ -21,10 +21,8 @@ import ( "github.com/canonical/workshop/internal/interfaces/ifacetest" "github.com/canonical/workshop/internal/osutil" "github.com/canonical/workshop/internal/overlord" - "github.com/canonical/workshop/internal/overlord/handlersetup" "github.com/canonical/workshop/internal/overlord/sdkstate" "github.com/canonical/workshop/internal/overlord/state" - "github.com/canonical/workshop/internal/progress" "github.com/canonical/workshop/internal/sdk" "github.com/canonical/workshop/internal/sdk/system" "github.com/canonical/workshop/internal/testutil" @@ -413,8 +411,6 @@ func (s *sdkStateSuite) TestUndoInstallSdkSuccess(c *check.C) { } func (s *sdkStateSuite) TestRetrieveSystemSdkSuccess(c *check.C) { - sdk.ReplaceGcsStore(s.state, sdk.NewFakeGcsStore()) - s.state.Lock() defer s.state.Unlock() @@ -430,6 +426,7 @@ func (s *sdkStateSuite) TestRetrieveSystemSdkSuccess(c *check.C) { chg := s.state.NewChange("sample", "...") setWorkshopProject("ws", s.project, t) chg.Set("user", "testuser") + chg.Set("ws_base", workshop.BaseOnly("ubuntu@22.04", "fakeimage123")) chg.Set("ws_sdks", []sdk.Setup{newSdk}) chg.AddTask(t) @@ -438,7 +435,7 @@ func (s *sdkStateSuite) TestRetrieveSystemSdkSuccess(c *check.C) { s.se.Wait() s.state.Lock() - c.Check(chg.Err(), check.IsNil) + c.Assert(chg.Err(), check.IsNil) f, err := os.Open(newSdk.Filepath()) c.Assert(err, check.IsNil) @@ -469,76 +466,10 @@ func (s *sdkStateSuite) TestRetrieveSystemSdkSuccess(c *check.C) { } c.Check(entries, check.DeepEquals, []string{"drwxr-xr-x meta/", "-rw-r--r-- meta/sdk.yaml"}) -} - -// This tests that we correctly handle the case where the Store is updated -// after SdkAction but before DownloadSdk. Should be able to remove when we -// switch over to the real Store. -func (s *sdkStateSuite) TestRetrieveUpdatedSdk(c *check.C) { - store := sdk.NewFakeGcsStore() - sdk.ReplaceGcsStore(s.state, store) - - s.state.Lock() - defer s.state.Unlock() - - oldSdks := []sdk.Setup{{ - Name: "dependency", - PackageID: "86SlSqwM289qTuVvbPixQLM4K2pCWzEZ", - Channel: "latest/stable", - Revision: sdk.R(10), - Sha3_384: "71843b99f85547fbe99fec9caf39f9ead64bc59de11447f9c2065597af88ccc5d5239f3b83a7e82a79582eff5f3868e8", - }, { - Name: "test", - PackageID: "a9J51jhjzpckN8VxhqoZ8dNKcZ7pOrBb", - Channel: "6/edge", - Revision: sdk.R(100), - Sha3_384: "e516dabb23b6e30026863543282780a3ae0dccf05551cf0295178d7ff0f1b41eecb9db3ff219007c4e097260d58621bd", - }, { - Name: "project", - Source: sdk.ProjectSource, - Revision: sdk.R(-5), - Sha3_384: "6b1715cb90ce493a4f7f0c6745ad8155eac1874075d06c23fcba628b1276b6a0b093ef1b1969be891a1cfbc9345ffc5a", - }} - newSdk := sdk.Setup{ - Name: "test", - PackageID: "a9J51jhjzpckN8VxhqoZ8dNKcZ7pOrBb", - Channel: "6/edge", - Revision: sdk.R(101), - Sha3_384: "805b5d3a5b935a255100653612b26c117ee280e3f522b69743ade2b907e566e716a8c8dcd706255577b0bc7dcd9eeeeb", - } - - restore := store.SetDownloadCallback(func(ctx context.Context, setup sdk.Setup, report *progress.Reporter) (*sdk.Meta, error) { - if setup == oldSdks[1] { - if err := os.MkdirAll(newSdk.Filepath(), 0755); err != nil { - return nil, err - } - return &sdk.Meta{Setup: newSdk, SdkYAML: "name: test\n"}, nil - } - return nil, os.ErrNotExist - }) - defer restore() - - t := s.state.NewTask("retrieve-sdk", "retrieve") - t.Set("sdk", oldSdks[1].Name) - - chg := s.state.NewChange("sample", "...") - setWorkshopProject("ws", s.project, t) - chg.Set("user", "testuser") - chg.Set("ws_base", workshop.BaseImage{Name: "ubuntu@24.04", Fingerprint: "fakeimage123"}) - chg.Set("ws_sdks", oldSdks) - chg.AddTask(t) - - s.state.Unlock() - c.Assert(s.se.Ensure(), check.IsNil) - s.se.Wait() - s.state.Lock() - c.Assert(chg.Err(), check.IsNil) - sdks, err := handlersetup.WorkshopSdks(chg, "ws") + expected, err := system.SystemSdkFs.ReadFile("meta/sdk.yaml") c.Assert(err, check.IsNil) - c.Check(sdks[0], check.Equals, oldSdks[0]) - c.Check(sdks[1], check.Equals, newSdk) - c.Check(sdks[2], check.Equals, oldSdks[2]) + c.Check(newSdk.Filepath()+".yaml", testutil.FileEquals, expected) } func (s *sdkStateSuite) TestSDKVolumeRemovedAfterCooldownOK(c *check.C) { diff --git a/internal/sdk/store.go b/internal/sdk/store.go index 1cf7b558b..767ed1615 100644 --- a/internal/sdk/store.go +++ b/internal/sdk/store.go @@ -5,6 +5,8 @@ import ( "crypto/sha3" "encoding/base64" "errors" + "fmt" + "io" "math/rand/v2" "regexp" "sync" @@ -61,6 +63,7 @@ func StoreService(st *state.State) Store { } type Store interface { + Download(ctx context.Context, w io.Writer, sdk sdkstore.SdkArchive, options ...sdkstore.DownloadOption) error Find(ctx context.Context, query string, options ...sdkstore.FindOption) ([]transport.FindResponse, error) Info(ctx context.Context, name string, options ...sdkstore.InfoOption) (transport.InfoResponse, error) Resolve(ctx context.Context, request transport.ResolveRequest) (transport.ResolveResponse, error) @@ -73,6 +76,9 @@ func NewFakeStore() *FakeStore { type FakeStore struct { lock sync.Mutex + DownloadCalls []sdkstore.SdkArchive + DownloadCallback func(ctx context.Context, w io.Writer, sdk sdkstore.SdkArchive, options ...sdkstore.DownloadOption) error + FindCalls []string FindCallback func(ctx context.Context, query string, options ...sdkstore.FindOption) ([]transport.FindResponse, error) @@ -83,6 +89,29 @@ type FakeStore struct { ResolveCallback func(ctx context.Context, req transport.ResolveRequest) (transport.ResolveResponse, error) } +func (f *FakeStore) SetDownloadCallback(download func(ctx context.Context, w io.Writer, sdk sdkstore.SdkArchive, options ...sdkstore.DownloadOption) error) func() { + f.lock.Lock() + defer f.lock.Unlock() + + old := f.DownloadCallback + f.DownloadCallback = download + return func() { + f.DownloadCallback = old + } +} + +func (f *FakeStore) Download(ctx context.Context, w io.Writer, sdk sdkstore.SdkArchive, options ...sdkstore.DownloadOption) error { + f.lock.Lock() + f.DownloadCalls = append(f.DownloadCalls, sdk) + download := f.DownloadCallback + f.lock.Unlock() + + if download == nil { + return fmt.Errorf("%q SDK (%v) not found", sdk.Name, sdk.Revision) + } + return download(ctx, w, sdk, options...) +} + func (f *FakeStore) SetFindCallback(find func(ctx context.Context, query string, options ...sdkstore.FindOption) ([]transport.FindResponse, error)) func() { f.lock.Lock() defer f.lock.Unlock() diff --git a/internal/sdk/system/system.go b/internal/sdk/system/system.go index ba4b95fb2..fdc759a92 100644 --- a/internal/sdk/system/system.go +++ b/internal/sdk/system/system.go @@ -10,9 +10,7 @@ import ( "os" "github.com/canonical/workshop/internal/logger" - "github.com/canonical/workshop/internal/osutil" "github.com/canonical/workshop/internal/progress" - "github.com/canonical/workshop/internal/revert" "github.com/canonical/workshop/internal/sdk" ) @@ -42,47 +40,17 @@ func SystemSdkMeta() (*sdk.Meta, error) { return &sdk.Meta{Setup: setup, SdkYAML: string(sdkYaml)}, nil } -func retrieveSystemSdk(setup sdk.Setup, report *progress.Reporter) (*sdk.Meta, error) { - fl, err := sdk.OpenLock(setup.Name) - if err != nil { - return nil, err - } - if err = fl.Lock(); err != nil { - return nil, err - } - defer fl.Close() - - target := setup.Filepath() - if osutil.FileExists(target) { - logger.Debugf("System SDK on Retrieve: SDK found locally: %s", target) - return SystemSdkMeta() - } - +func retrieveSystemSdk(file *os.File, setup sdk.Setup, report *progress.Reporter) error { if setup.Revision != SystemSdkRevision { - return nil, fmt.Errorf("system SDK (%s) not available", setup.Revision) + return fmt.Errorf("system SDK (%s) not available", setup.Revision) } - r := revert.New() - defer r.Fail() - - // TODO: remove old system SDKs when no longer in use. - file, err := os.Create(target) - if err != nil { - return nil, err - } - defer file.Close() - r.Add(func() { - if err1 := os.Remove(target); err1 != nil { - logger.Noticef("System SDK on Retrieve: Cannot remove %q on a failed download: %v", target, err1) - } - }) - writer := tar.NewWriter(file) if err := addWritableFS(writer, SystemSdkFs); err != nil { - return nil, err + return err } if err := writer.Close(); err != nil { - return nil, err + return err } if report != nil { @@ -96,8 +64,7 @@ func retrieveSystemSdk(setup sdk.Setup, report *progress.Reporter) (*sdk.Meta, e report.Report("download", size, size) } - r.Success() - return SystemSdkMeta() + return nil } // Like w.AddFs(fsys) but ensures the user always has write permissions. @@ -155,7 +122,7 @@ func addWritableFS(w *tar.Writer, fsys fs.FS) error { }) } -func FakeRetrieveSystemSdk(f func(setup sdk.Setup, report *progress.Reporter) (*sdk.Meta, error)) func() { +func FakeRetrieveSystemSdk(f func(file *os.File, setup sdk.Setup, report *progress.Reporter) error) func() { oldRetrieveSystemSdk := RetrieveSystemSdk RetrieveSystemSdk = f return func() { RetrieveSystemSdk = oldRetrieveSystemSdk } diff --git a/internal/sdk/system/system_test.go b/internal/sdk/system/system_test.go index c22fb5b8f..e4de7f667 100644 --- a/internal/sdk/system/system_test.go +++ b/internal/sdk/system/system_test.go @@ -39,31 +39,34 @@ func (f *systemSdk) TearDownSuite(c *check.C) { } func (s *systemSdk) TestRetrieveSystemSdkSuccess(c *check.C) { - var done, total int64 - report := &progress.Reporter{Name: "1", Report: func(label string, d, t int64) { - done = d - total = t - }} setup := sdk.Setup{ Name: sdk.System.String(), Source: sdk.SystemSource, Revision: system.SystemSdkRevision, Sha3_384: system.SystemSdkDigest, } - result, err := system.RetrieveSystemSdk(setup, report) + file, err := os.Create(setup.Filepath()) + c.Assert(err, check.IsNil) + defer file.Close() + + var done, total int64 + report := &progress.Reporter{Name: "1", Report: func(label string, d, t int64) { + done = d + total = t + }} + + err = system.RetrieveSystemSdk(file, setup, report) c.Assert(err, check.IsNil) - c.Check(result.Setup, check.Equals, setup) - c.Check(result.SdkYAML, check.Not(check.Equals), "") c.Check(int(done), testutil.IntGreaterThan, 0) c.Check(int(total), testutil.IntGreaterThan, 0) c.Check(done, check.Equals, total) - r, err := os.Open(setup.Filepath()) + file.Close() + file, err = os.Open(setup.Filepath()) c.Assert(err, check.IsNil) - defer r.Close() hash := sha3.New384() - _, err = r.WriteTo(hash) + _, err = file.WriteTo(hash) c.Assert(err, check.IsNil) digest := hex.EncodeToString(hash.Sum(nil)) @@ -78,10 +81,10 @@ func (s *systemSdk) TestRetrieveSystemSdkWrongRevision(c *check.C) { Revision: sdk.R(system.SystemSdkRevision.N - 1), Sha3_384: "6b499970ebf370d4dbc4e9a005c042dee003c19a9420a78944bcbf32653d257f80f7c56bad55b4c967dca68a1ea92be7", } - _, err := system.RetrieveSystemSdk(setup, nil) + err := system.RetrieveSystemSdk(nil, setup, nil) c.Check(err, check.ErrorMatches, fmt.Sprintf(`system SDK \(%s\) not available`, setup.Revision)) setup.Revision.N += 2 - _, err = system.RetrieveSystemSdk(setup, nil) + err = system.RetrieveSystemSdk(nil, setup, nil) c.Check(err, check.ErrorMatches, fmt.Sprintf(`system SDK \(%s\) not available`, setup.Revision)) } diff --git a/internal/workshop/fakebackend/backend.go b/internal/workshop/fakebackend/backend.go index bf2037d5b..6e11f9e46 100644 --- a/internal/workshop/fakebackend/backend.go +++ b/internal/workshop/fakebackend/backend.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "maps" "net/http" "os" @@ -569,7 +570,21 @@ func (s *FakeWorkshopBackend) ImportSdk(ctx context.Context, meta sdk.Meta, tarb return workshop.ErrVolumeAlreadyExists } - s.Volumes[name] = FakeVolume{Kind: "sdk", What: tarball.Name()} + info, err := tarball.Stat() + if err != nil { + return err + } + + what := tarball.Name() + if !info.IsDir() { + content, err := io.ReadAll(tarball) + if err != nil { + return err + } + what = string(content) + } + + s.Volumes[name] = FakeVolume{Kind: "sdk", What: what} s.SdkVolumes[name] = meta return nil } From 78257b06751c64456f778d0981ce8c99e8869da4 Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Wed, 1 Apr 2026 18:28:36 +1300 Subject: [PATCH 09/17] Migrate test SDKs to SDKcraft --- internal/sdkstore/test-sdk-info.json | 67 +++++++++++++++---- internal/sdkstore/test-sdk-info.raw.json | 67 +++++++++++++++---- .../docs-how-to/connect-vscode/workshop.yaml | 2 +- tests/docs-how-to/debug-issues/workshop.yaml | 2 +- tests/docs-how-to/git/workshop.yaml | 2 +- .../docs-tutorial/part-1/workshop-part-1.yaml | 2 +- tests/docs-tutorial/part-2/task.yaml | 2 +- .../docs-tutorial/part-2/workshop-part-2.yaml | 2 +- .../docs-tutorial/part-3/workshop-part-3.yaml | 4 +- .../latest/edge/{sdk => }/hooks/setup-base | 0 .../edge/{meta/sdk.yaml => sdkcraft.yaml} | 8 +-- .../latest/stable/{sdk => }/hooks/setup-base | 0 .../stable/{meta/sdk.yaml => sdkcraft.yaml} | 8 +-- .../stable/{meta/sdk.yaml => sdkcraft.yaml} | 9 ++- .../latest/stable/{sdk => }/hooks/setup-base | 0 .../stable/{meta/sdk.yaml => sdkcraft.yaml} | 9 ++- .../latest/stable/{sdk => }/hooks/setup-base | 0 .../stable/{meta/sdk.yaml => sdkcraft.yaml} | 8 +-- .../sdk/test-sdk-go/foxy/stable/meta/sdk.yaml | 16 ----- .../foxy/stable/sdk/hooks/setup-base | 2 - .../test-sdk-go/jammy/stable/meta/sdk.yaml | 16 ----- .../jammy/stable/sdk/hooks/setup-base | 2 - .../test-sdk-go/latest/stable/meta/sdk.yaml | 16 ----- .../latest/stable/sdk/hooks/setup-base | 2 - .../test-sdk-go/noble/stable/meta/sdk.yaml | 16 ----- .../noble/stable/sdk/hooks/setup-base | 2 - .../stable/{meta/sdk.yaml => sdkcraft.yaml} | 9 ++- .../stable/{sdk => }/hooks/check-health | 0 .../latest/stable/{sdk => }/hooks/setup-base | 0 .../stable/{meta/sdk.yaml => sdkcraft.yaml} | 9 ++- .../stable/{sdk => }/hooks/check-health | 0 .../latest/stable/{sdk => }/hooks/setup-base | 0 .../stable/{meta/sdk.yaml => sdkcraft.yaml} | 9 ++- .../stable/{sdk => }/hooks/check-health | 0 .../latest/stable/{sdk => }/hooks/setup-base | 0 .../stable/{meta/sdk.yaml => sdkcraft.yaml} | 9 ++- .../latest/edge/{sdk => }/hooks/setup-base | 0 .../test-sdk-info/latest/edge/meta/sdk.yaml | 10 --- .../test-sdk-info/latest/edge/sdkcraft.yaml | 14 ++++ .../latest/stable/{sdk => }/hooks/setup-base | 0 .../test-sdk-info/latest/stable/meta/sdk.yaml | 10 --- .../test-sdk-info/latest/stable/sdkcraft.yaml | 14 ++++ .../24.04/stable/sdk/jobs/job.sh | 0 .../sdk => latest/edge}/hooks/setup-base | 0 .../latest/edge/sdk/jobs/job.sh | 0 .../edge/{meta/sdk.yaml => sdkcraft.yaml} | 18 +++-- .../{edge/sdk => stable}/hooks/setup-base | 0 .../latest/stable/meta/sdk.yaml | 20 ------ .../latest/stable/sdk/hooks/setup-base | 1 - .../latest/stable/sdk/jobs/job.sh | 0 .../sdk.yaml => latest/stable/sdkcraft.yaml} | 18 +++-- .../latest/stable/{sdk => }/hooks/setup-base | 0 .../stable/{meta/sdk.yaml => sdkcraft.yaml} | 8 +-- .../latest/stable/{sdk => }/hooks/setup-base | 0 .../stable/{meta/sdk.yaml => sdkcraft.yaml} | 9 ++- .../latest/stable/{sdk => }/hooks/setup-base | 0 .../stable/{meta/sdk.yaml => sdkcraft.yaml} | 9 ++- .../stable/{meta/sdk.yaml => sdkcraft.yaml} | 8 +-- tests/main/disconnect/task.yaml | 2 +- .../hooks/restore-state | 0 .../test-sdk-mount-broken}/hooks/save-state | 0 .../test-sdk-mount-broken}/hooks/setup-base | 0 .../.workshop/test-sdk-mount-broken}/sdk.yaml | 2 - tests/main/launch-abort/task.yaml | 5 +- .../launch-abort/workshop.mount-broken.yaml | 4 ++ ...workshop.yaml => workshop.setup-fail.yaml} | 0 tests/main/launch-sdk/task.yaml | 2 +- tests/main/sdk-info/task.yaml | 8 +-- tests/main/try-sdks/task.yaml | 20 +++--- tests/main/try-sdks/unpacked/1/meta/sdk.yaml | 8 +++ .../try-sdks/unpacked/1/sdk/hooks/setup-base | 1 + tests/main/try-sdks/unpacked/2/meta/sdk.yaml | 8 +++ .../try-sdks/unpacked/2/sdk/hooks/setup-base | 1 + 73 files changed, 261 insertions(+), 239 deletions(-) rename tests/lib/sdk/test-sdk-basic/latest/edge/{sdk => }/hooks/setup-base (100%) rename tests/lib/sdk/test-sdk-basic/latest/edge/{meta/sdk.yaml => sdkcraft.yaml} (59%) rename tests/lib/sdk/test-sdk-basic/latest/stable/{sdk => }/hooks/setup-base (100%) rename tests/lib/sdk/test-sdk-basic/latest/stable/{meta/sdk.yaml => sdkcraft.yaml} (59%) rename tests/lib/sdk/test-sdk-camera/latest/stable/{meta/sdk.yaml => sdkcraft.yaml} (55%) rename tests/lib/sdk/test-sdk-desktop/latest/stable/{sdk => }/hooks/setup-base (100%) rename tests/lib/sdk/test-sdk-desktop/latest/stable/{meta/sdk.yaml => sdkcraft.yaml} (55%) rename tests/lib/sdk/test-sdk-environment/latest/stable/{sdk => }/hooks/setup-base (100%) rename tests/lib/sdk/test-sdk-environment/latest/stable/{meta/sdk.yaml => sdkcraft.yaml} (61%) delete mode 100644 tests/lib/sdk/test-sdk-go/foxy/stable/meta/sdk.yaml delete mode 100755 tests/lib/sdk/test-sdk-go/foxy/stable/sdk/hooks/setup-base delete mode 100644 tests/lib/sdk/test-sdk-go/jammy/stable/meta/sdk.yaml delete mode 100755 tests/lib/sdk/test-sdk-go/jammy/stable/sdk/hooks/setup-base delete mode 100644 tests/lib/sdk/test-sdk-go/latest/stable/meta/sdk.yaml delete mode 100755 tests/lib/sdk/test-sdk-go/latest/stable/sdk/hooks/setup-base delete mode 100644 tests/lib/sdk/test-sdk-go/noble/stable/meta/sdk.yaml delete mode 100755 tests/lib/sdk/test-sdk-go/noble/stable/sdk/hooks/setup-base rename tests/lib/sdk/test-sdk-gpu/latest/stable/{meta/sdk.yaml => sdkcraft.yaml} (54%) rename tests/lib/sdk/test-sdk-health-error/latest/stable/{sdk => }/hooks/check-health (100%) rename tests/lib/sdk/test-sdk-health-error/latest/stable/{sdk => }/hooks/setup-base (100%) rename tests/lib/sdk/test-sdk-health-error/latest/stable/{meta/sdk.yaml => sdkcraft.yaml} (58%) rename tests/lib/sdk/test-sdk-health-ok/latest/stable/{sdk => }/hooks/check-health (100%) rename tests/lib/sdk/test-sdk-health-ok/latest/stable/{sdk => }/hooks/setup-base (100%) rename tests/lib/sdk/test-sdk-health-ok/latest/stable/{meta/sdk.yaml => sdkcraft.yaml} (58%) rename tests/lib/sdk/test-sdk-health-waiting/latest/stable/{sdk => }/hooks/check-health (100%) rename tests/lib/sdk/test-sdk-health-waiting/latest/stable/{sdk => }/hooks/setup-base (100%) rename tests/lib/sdk/test-sdk-health-waiting/latest/stable/{meta/sdk.yaml => sdkcraft.yaml} (59%) rename tests/lib/sdk/test-sdk-info/latest/edge/{sdk => }/hooks/setup-base (100%) delete mode 100644 tests/lib/sdk/test-sdk-info/latest/edge/meta/sdk.yaml create mode 100644 tests/lib/sdk/test-sdk-info/latest/edge/sdkcraft.yaml rename tests/lib/sdk/test-sdk-info/latest/stable/{sdk => }/hooks/setup-base (100%) delete mode 100644 tests/lib/sdk/test-sdk-info/latest/stable/meta/sdk.yaml create mode 100644 tests/lib/sdk/test-sdk-info/latest/stable/sdkcraft.yaml delete mode 100644 tests/lib/sdk/test-sdk-mount/24.04/stable/sdk/jobs/job.sh rename tests/lib/sdk/test-sdk-mount/{24.04/stable/sdk => latest/edge}/hooks/setup-base (100%) delete mode 100644 tests/lib/sdk/test-sdk-mount/latest/edge/sdk/jobs/job.sh rename tests/lib/sdk/test-sdk-mount/latest/edge/{meta/sdk.yaml => sdkcraft.yaml} (57%) rename tests/lib/sdk/test-sdk-mount/latest/{edge/sdk => stable}/hooks/setup-base (100%) delete mode 100644 tests/lib/sdk/test-sdk-mount/latest/stable/meta/sdk.yaml delete mode 100755 tests/lib/sdk/test-sdk-mount/latest/stable/sdk/hooks/setup-base delete mode 100644 tests/lib/sdk/test-sdk-mount/latest/stable/sdk/jobs/job.sh rename tests/lib/sdk/test-sdk-mount/{24.04/stable/meta/sdk.yaml => latest/stable/sdkcraft.yaml} (51%) rename tests/lib/sdk/test-sdk-multiple-plugs/latest/stable/{sdk => }/hooks/setup-base (100%) rename tests/lib/sdk/test-sdk-multiple-plugs/latest/stable/{meta/sdk.yaml => sdkcraft.yaml} (76%) rename tests/lib/sdk/test-sdk-setup-fail/latest/stable/{sdk => }/hooks/setup-base (100%) rename tests/lib/sdk/test-sdk-setup-fail/latest/stable/{meta/sdk.yaml => sdkcraft.yaml} (58%) rename tests/lib/sdk/test-sdk-ssh-agent/latest/stable/{sdk => }/hooks/setup-base (100%) rename tests/lib/sdk/test-sdk-ssh-agent/latest/stable/{meta/sdk.yaml => sdkcraft.yaml} (55%) rename tests/lib/sdk/test-sdk-tunnel/latest/stable/{meta/sdk.yaml => sdkcraft.yaml} (85%) rename tests/{lib/sdk/test-sdk-mount-broken/latest/stable/sdk => main/launch-abort/.workshop/test-sdk-mount-broken}/hooks/restore-state (100%) rename tests/{lib/sdk/test-sdk-mount-broken/latest/stable/sdk => main/launch-abort/.workshop/test-sdk-mount-broken}/hooks/save-state (100%) rename tests/{lib/sdk/test-sdk-mount-broken/latest/stable/sdk => main/launch-abort/.workshop/test-sdk-mount-broken}/hooks/setup-base (100%) rename tests/{lib/sdk/test-sdk-mount-broken/latest/stable/meta => main/launch-abort/.workshop/test-sdk-mount-broken}/sdk.yaml (88%) create mode 100644 tests/main/launch-abort/workshop.mount-broken.yaml rename tests/main/launch-abort/{workshop.yaml => workshop.setup-fail.yaml} (100%) create mode 100644 tests/main/try-sdks/unpacked/1/meta/sdk.yaml create mode 100755 tests/main/try-sdks/unpacked/1/sdk/hooks/setup-base create mode 100644 tests/main/try-sdks/unpacked/2/meta/sdk.yaml create mode 100755 tests/main/try-sdks/unpacked/2/sdk/hooks/setup-base diff --git a/internal/sdkstore/test-sdk-info.json b/internal/sdkstore/test-sdk-info.json index 2858b12e9..13d0fdcc7 100644 --- a/internal/sdkstore/test-sdk-info.json +++ b/internal/sdkstore/test-sdk-info.json @@ -5,15 +5,55 @@ "description": "This is test-sdk-info's description. You have a paragraph or two to tell the\nmost important story about it. Keep it under 100 words though,\nwe live in tweetspace.\n", "license": "GPL-3.0", "publisher": { - "display-name": "Jonathan Conder", - "id": "YnyjkYo9nA6AlXsTKnpxrCGxNZgmSdmu", - "username":"usso-jeconder", + "display-name": "Dmitry Lyfar", + "id": "eXipbnGB0LFt3v2YD8KTEMx5inT575ju", + "username":"usso-dlyfar", "validation":"unproven" }, "summary": "Test SDK which supports all bases and architectures", - "title": "test-sdk-info" + "title": "SDK info" }, "channel-map": [ + { + "channel": { + "name": "latest/stable", + "track": "latest", + "risk": "stable", + "platform": { + "architecture": "all", + "channel": "all", + "name": "all" + }, + "released-at": "2026-04-02T01:29:06.948542Z" + }, + "revision": { + "created-at": "2026-04-02T01:29:04.183727Z", + "download": { + "sha3-384": "51779c9264f26baf29facfdaac39286712073b5ed3a5f3e63ab03cc3aa6275165d0d50d30d87beb45068c8c3ce70053a", + "size": 596, + "url": "https://api.staging.pkg.store/api/v1/sdks/download/U9IBEcXiDkuaPLYjyUBpRZMETAr7g39e_3.sdk" + }, + "platforms": [ + { + "architecture": "all", + "channel": "all", + "name": "all" + } + ], + "revision": 3, + "sdk-yaml": { + "architecture": "all", + "description": "This is test-sdk-info's description. You have a paragraph or two to tell the\nmost important story about it. Keep it under 100 words though,\nwe live in tweetspace.\n", + "license": "GPL-3.0", + "name": "test-sdk-info", + "sdkcraft-started-at": "2026-04-02T01:28:56.859021Z", + "summary": "Test SDK which supports all bases and architectures", + "title": "SDK info", + "version": "1.0" + }, + "version": "1.0" + } + }, { "channel": { "name": "latest/edge", @@ -24,14 +64,14 @@ "channel": "all", "name": "all" }, - "released-at": "2026-03-10T06:23:47.372123Z" + "released-at": "2026-04-02T01:28:47.872298Z" }, "revision": { - "created-at": "2026-03-10T06:23:16.908418Z", + "created-at": "2026-04-02T01:28:46.134132Z", "download": { - "sha3-384": "cf722fc841c72cf53c4b2db88608589efb173fa2a50837ae6f07597ead85e6e30f36a85e98df9ba78d941b3c9e15ab3d", - "size": 677, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/U9IBEcXiDkuaPLYjyUBpRZMETAr7g39e_1.sdk" + "sha3-384": "47b440b2facd5176c7d346f32e59499d0b7afe268b8cd174ef5e07bf564147669d836e6d93c2ba1efe3b2e0f5cf94eee", + "size": 593, + "url": "https://api.staging.pkg.store/api/v1/sdks/download/U9IBEcXiDkuaPLYjyUBpRZMETAr7g39e_2.sdk" }, "platforms": [ { @@ -40,17 +80,18 @@ "name": "all" } ], - "revision": 1, + "revision": 2, "sdk-yaml": { "architecture": "all", "description": "This is test-sdk-info's description. You have a paragraph or two to tell the\nmost important story about it. Keep it under 100 words though,\nwe live in tweetspace.\n", "license": "GPL-3.0", "name": "test-sdk-info", - "sdkcraft-started-at": "2026-03-10T06:22:30.544271Z", + "sdkcraft-started-at": "2026-04-02T01:28:40.615633Z", "summary": "Test SDK which supports all bases and architectures", - "version": "0.1" + "title": "SDK info", + "version": "2.0" }, - "version": "0.1" + "version": "2.0" } } ] diff --git a/internal/sdkstore/test-sdk-info.raw.json b/internal/sdkstore/test-sdk-info.raw.json index f817990b3..772bd7139 100644 --- a/internal/sdkstore/test-sdk-info.raw.json +++ b/internal/sdkstore/test-sdk-info.raw.json @@ -5,15 +5,55 @@ "description": "This is test-sdk-info's description. You have a paragraph or two to tell the\nmost important story about it. Keep it under 100 words though,\nwe live in tweetspace.\n", "license": "GPL-3.0", "publisher": { - "display-name": "Jonathan Conder", - "id": "YnyjkYo9nA6AlXsTKnpxrCGxNZgmSdmu", - "username":"usso-jeconder", + "display-name": "Dmitry Lyfar", + "id": "eXipbnGB0LFt3v2YD8KTEMx5inT575ju", + "username":"usso-dlyfar", "validation":"unproven" }, "summary": "Test SDK which supports all bases and architectures", - "title": "test-sdk-info" + "title": "SDK info" }, "channel-map": [ + { + "channel": { + "name": "latest/stable", + "track": "latest", + "risk": "stable", + "platform": { + "architecture": "all", + "channel": "all", + "name": "all" + }, + "released-at": "2026-04-02T01:29:06.948542+00:00" + }, + "revision": { + "created-at": "2026-04-02T01:29:04.183727+00:00", + "download": { + "sha3-384": "51779c9264f26baf29facfdaac39286712073b5ed3a5f3e63ab03cc3aa6275165d0d50d30d87beb45068c8c3ce70053a", + "size": 596, + "url": "https://api.staging.pkg.store/api/v1/sdks/download/U9IBEcXiDkuaPLYjyUBpRZMETAr7g39e_3.sdk" + }, + "platforms": [ + { + "architecture": "all", + "channel": "all", + "name": "all" + } + ], + "revision": 3, + "sdk-yaml": { + "architecture": "all", + "description": "This is test-sdk-info's description. You have a paragraph or two to tell the\nmost important story about it. Keep it under 100 words though,\nwe live in tweetspace.\n", + "license": "GPL-3.0", + "name": "test-sdk-info", + "sdkcraft-started-at": "2026-04-02T01:28:56.859021Z", + "summary": "Test SDK which supports all bases and architectures", + "title": "SDK info", + "version": "1.0" + }, + "version": "1.0" + } + }, { "channel": { "name": "latest/edge", @@ -24,14 +64,14 @@ "channel": "all", "name": "all" }, - "released-at": "2026-03-10T06:23:47.372123+00:00" + "released-at": "2026-04-02T01:28:47.872298+00:00" }, "revision": { - "created-at": "2026-03-10T06:23:16.908418+00:00", + "created-at": "2026-04-02T01:28:46.134132+00:00", "download": { - "sha3-384": "cf722fc841c72cf53c4b2db88608589efb173fa2a50837ae6f07597ead85e6e30f36a85e98df9ba78d941b3c9e15ab3d", - "size": 677, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/U9IBEcXiDkuaPLYjyUBpRZMETAr7g39e_1.sdk" + "sha3-384": "47b440b2facd5176c7d346f32e59499d0b7afe268b8cd174ef5e07bf564147669d836e6d93c2ba1efe3b2e0f5cf94eee", + "size": 593, + "url": "https://api.staging.pkg.store/api/v1/sdks/download/U9IBEcXiDkuaPLYjyUBpRZMETAr7g39e_2.sdk" }, "platforms": [ { @@ -40,17 +80,18 @@ "name": "all" } ], - "revision": 1, + "revision": 2, "sdk-yaml": { "architecture": "all", "description": "This is test-sdk-info's description. You have a paragraph or two to tell the\nmost important story about it. Keep it under 100 words though,\nwe live in tweetspace.\n", "license": "GPL-3.0", "name": "test-sdk-info", - "sdkcraft-started-at": "2026-03-10T06:22:30.544271Z", + "sdkcraft-started-at": "2026-04-02T01:28:40.615633Z", "summary": "Test SDK which supports all bases and architectures", - "version": "0.1" + "title": "SDK info", + "version": "2.0" }, - "version": "0.1" + "version": "2.0" } } ], diff --git a/tests/docs-how-to/connect-vscode/workshop.yaml b/tests/docs-how-to/connect-vscode/workshop.yaml index e2b2c4d4d..6003cc294 100644 --- a/tests/docs-how-to/connect-vscode/workshop.yaml +++ b/tests/docs-how-to/connect-vscode/workshop.yaml @@ -2,4 +2,4 @@ name: dev base: ubuntu@24.04 sdks: - name: vscode-remote - channel: 24.04/edge + channel: latest/edge diff --git a/tests/docs-how-to/debug-issues/workshop.yaml b/tests/docs-how-to/debug-issues/workshop.yaml index ed8c49dbb..756fd1aad 100644 --- a/tests/docs-how-to/debug-issues/workshop.yaml +++ b/tests/docs-how-to/debug-issues/workshop.yaml @@ -2,4 +2,4 @@ name: dev-volatile base: ubuntu@22.04 sdks: - name: go - channel: all/edge + channel: latest/stable diff --git a/tests/docs-how-to/git/workshop.yaml b/tests/docs-how-to/git/workshop.yaml index e3407ceed..295f2b4fd 100644 --- a/tests/docs-how-to/git/workshop.yaml +++ b/tests/docs-how-to/git/workshop.yaml @@ -2,4 +2,4 @@ name: dev base: ubuntu@22.04 sdks: - name: go - channel: all/edge + channel: latest/stable diff --git a/tests/docs-tutorial/part-1/workshop-part-1.yaml b/tests/docs-tutorial/part-1/workshop-part-1.yaml index b48de3142..32dddc786 100644 --- a/tests/docs-tutorial/part-1/workshop-part-1.yaml +++ b/tests/docs-tutorial/part-1/workshop-part-1.yaml @@ -2,4 +2,4 @@ name: dev base: ubuntu@22.04 sdks: - name: ollama - channel: 22.04/edge + channel: latest/edge diff --git a/tests/docs-tutorial/part-2/task.yaml b/tests/docs-tutorial/part-2/task.yaml index 8611c4ff5..280d1fc4d 100644 --- a/tests/docs-tutorial/part-2/task.yaml +++ b/tests/docs-tutorial/part-2/task.yaml @@ -35,7 +35,7 @@ execute: | cat >> workshop.yaml < info.log diff -u header.txt <(head --lines="$lines" info.log) MATCH '[[:space:]]+latest/stable[[:space:]]+1\.0[[:space:]]+[0-9]{4}-[0-9]{2}-[0-9]{2}[[:space:]]+all[[:space:]]+[0-9]+[[:space:]]+[0-9]+B' < info.log - MATCH "$PWD[[:space:]]+edge[[:space:]]+latest/edge[[:space:]]+2\\.0[[:space:]]+ubuntu@22.04[[:space:]]+[[:digit:]]+$" < info.log + MATCH "$PWD[[:space:]]+edge[[:space:]]+latest/edge[[:space:]]+2\\.0[[:space:]]+all[[:space:]]+[[:digit:]]+$" < info.log echo "Test SDK info with two volumes" workshop_exec launch stable @@ -40,8 +40,8 @@ execute: | sdk_exec info test-sdk-info > info.log diff -u header.txt <(head --lines="$lines" info.log) MATCH '[[:space:]]+latest/edge[[:space:]]+2\.0[[:space:]]+[0-9]{4}-[0-9]{2}-[0-9]{2}[[:space:]]+all[[:space:]]+[0-9]+[[:space:]]+[0-9]+B' < info.log - MATCH "$PWD[[:space:]]+edge[[:space:]]+latest/edge[[:space:]]+2\\.0[[:space:]]+ubuntu@22.04[[:space:]]+[[:digit:]]+$" < info.log - MATCH "[[:space:]]+stable[[:space:]]+latest/stable[[:space:]]+1\\.0[[:space:]]+ubuntu@22.04[[:space:]]+[[:digit:]]+$" < info.log + MATCH "$PWD[[:space:]]+edge[[:space:]]+latest/edge[[:space:]]+2\\.0[[:space:]]+all[[:space:]]+[[:digit:]]+$" < info.log + MATCH "[[:space:]]+stable[[:space:]]+latest/stable[[:space:]]+1\\.0[[:space:]]+all[[:space:]]+[[:digit:]]+$" < info.log echo "Test SDK info with two volumes but one workshop" workshop_exec remove edge @@ -50,4 +50,4 @@ execute: | sdk_exec info test-sdk-info > info.log diff -u header.txt <(head --lines="$lines" info.log) - MATCH "$PWD[[:space:]]+stable[[:space:]]+latest/stable[[:space:]]+1\\.0[[:space:]]+ubuntu@22.04[[:space:]]+[[:digit:]]+$" < info.log + MATCH "$PWD[[:space:]]+stable[[:space:]]+latest/stable[[:space:]]+1\\.0[[:space:]]+all[[:space:]]+[[:digit:]]+$" < info.log diff --git a/tests/main/try-sdks/task.yaml b/tests/main/try-sdks/task.yaml index 12c447db1..f4db73090 100644 --- a/tests/main/try-sdks/task.yaml +++ b/tests/main/try-sdks/task.yaml @@ -7,14 +7,19 @@ restore: | execute: | . "$TESTSLIB"/utils.sh - echo "Launch workshop with SDKs from try area" - store_area="$SDK_STORE_BUCKET_DIR/test-sdk-basic" + sdk_try() { + (cd "$1" && sudo -u ubuntu tar cf "$2/test-sdk-basic_all.sdk" *) + openssl sha3-384 < "$2/test-sdk-basic_all.sdk" | cut -d' ' -f2 | sudo -u ubuntu tee "$2/test-sdk-basic_all.sdk.sha3-384" + sudo -u ubuntu cp "$1/meta/sdk.yaml" "$2/test-sdk-basic_all.sdk.yaml" + } + + echo "Copy test-sdk-basic (1) to try area" try_area='/home/ubuntu/.local/share/workshop/try/test-sdk-basic' + rm -rf "$try_area" sudo -u ubuntu mkdir -p "$try_area" - sudo -u ubuntu cp "$store_area/latest/stable/test-sdk-basic.sdk" "$try_area/test-sdk-basic_all.sdk" - echo 50aae25a070e742e9ea72a4bd3ed419f5a0a34391e294c4b619ed5659b041b7bfc872c4070811e4fba4084bcf15d8fe8 | sudo -u ubuntu tee "$try_area/test-sdk-basic_all.sdk.sha3-384" - sudo -u ubuntu cp "$store_area/latest/stable/sdk.yaml" "$try_area/test-sdk-basic_all.sdk.yaml" + sdk_try unpacked/1 "$try_area" + echo "Launch workshop with SDKs from try area" workshop_exec launch workshop_exec exec -- findmnt /var/lib/workshop/sdk/test-sdk-basic | MATCH 'workshop/custom/default_test-sdk-basic-x1' try_area_contracted="~${try_area#*ubuntu}" @@ -35,10 +40,7 @@ execute: | workshop_exec info | yq '.sdks["test-sdk-basic"].installed' | MATCH '\(x1\)$' echo "Check SDKs in try area can be updated" - sudo -u ubuntu cp "$store_area/latest/edge/test-sdk-basic.sdk" "$try_area/test-sdk-basic_all.sdk" - echo fccdbd97fa7d51053e0e6a9db8e3fc81bbce19aaa4d578072b9bb53527b45a80f30781c97c9fb830670b9141fdf8df19 | sudo -u ubuntu tee "$try_area/test-sdk-basic_all.sdk.sha3-384" - sudo -u ubuntu cp "$store_area/latest/edge/sdk.yaml" "$try_area/test-sdk-basic_all.sdk.yaml" - + sdk_try unpacked/2 "$try_area" workshop_exec refresh workshop_exec info | yq '.sdks["test-sdk-basic"].installed' | MATCH '\(x2\)$' workshop_exec exec -- cat /var/lib/workshop/sdk/test-sdk-basic/sdk/hooks/setup-base | MATCH 'setup-base v2' diff --git a/tests/main/try-sdks/unpacked/1/meta/sdk.yaml b/tests/main/try-sdks/unpacked/1/meta/sdk.yaml new file mode 100644 index 000000000..2fcab0a60 --- /dev/null +++ b/tests/main/try-sdks/unpacked/1/meta/sdk.yaml @@ -0,0 +1,8 @@ +name: test-sdk-basic +version: '1.0' +summary: test-sdk-basic SDK +description: | + test-sdk-basic SDK +base: ubuntu@22.04 +architecture: amd64 +sdkcraft-started-at: '2026-04-01T23:27:53.386284Z' diff --git a/tests/main/try-sdks/unpacked/1/sdk/hooks/setup-base b/tests/main/try-sdks/unpacked/1/sdk/hooks/setup-base new file mode 100755 index 000000000..6a2cf062d --- /dev/null +++ b/tests/main/try-sdks/unpacked/1/sdk/hooks/setup-base @@ -0,0 +1 @@ +echo "setup-base ran successfully!" diff --git a/tests/main/try-sdks/unpacked/2/meta/sdk.yaml b/tests/main/try-sdks/unpacked/2/meta/sdk.yaml new file mode 100644 index 000000000..1d935cd39 --- /dev/null +++ b/tests/main/try-sdks/unpacked/2/meta/sdk.yaml @@ -0,0 +1,8 @@ +name: test-sdk-basic +version: '2.0' +summary: test-sdk-basic SDK +description: | + test-sdk-basic SDK +base: ubuntu@22.04 +architecture: amd64 +sdkcraft-started-at: '2026-04-01T23:30:01.223415Z' diff --git a/tests/main/try-sdks/unpacked/2/sdk/hooks/setup-base b/tests/main/try-sdks/unpacked/2/sdk/hooks/setup-base new file mode 100755 index 000000000..d84a2724c --- /dev/null +++ b/tests/main/try-sdks/unpacked/2/sdk/hooks/setup-base @@ -0,0 +1 @@ +echo "setup-base v2 ran successfully!" From e6bab67bbd09579425d81d38e22d99f6e8c3e193 Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Thu, 9 Apr 2026 12:09:17 +1200 Subject: [PATCH 10/17] Doc: Update SDK channels Co-authored-by: Dmitry Lyfar <69887876+dmitry-lyfar@users.noreply.github.com> --- docs/coding-style-guide.md | 2 +- docs/doc-style-guide.md | 8 ++++---- docs/explanation/interfaces/tunnel-interface.rst | 4 ++-- docs/explanation/sdks/sdk-vs-dockerfile.rst | 1 - docs/explanation/workshops/concepts.rst | 11 ++++------- docs/how-to/customize-workshops/add-actions.rst | 2 +- .../how-to/customize-workshops/forward-ports.rst | 1 - .../how-to/customize-workshops/move-projects.rst | 2 +- .../use-multiple-workshops.rst | 12 ++++++------ .../develop-with-workshops/connect-vscode.rst | 3 +-- .../run-github-actions-locally.rst | 3 +-- .../run-jupyterlab-in-browser.rst | 3 +-- .../run-vscode-in-browser.rst | 3 +-- docs/how-to/develop-with-workshops/use-git.rst | 4 ++-- .../use-workshops-with-ai-agents.rst | 2 -- docs/how-to/fix-workshops/debug-issues.rst | 4 ++-- .../fix-workshops/resolve-plug-conflicts.rst | 6 +----- docs/readme.rst | 3 +-- docs/reference/definition-files/schema.json | 8 ++++---- .../definition-files/workshop-definition.rst | 16 +++++++--------- docs/reference/sdks.rst | 7 ++++++- docs/tutorial/part-1-get-started.rst | 16 +++++++++------- docs/tutorial/part-2-work-with-interfaces.rst | 12 +++++------- docs/tutorial/part-3-sketch-sdks.rst | 3 +-- docs/tutorial/part-4-craft-sdks.rst | 2 +- tests/docs-how-to/connect-vscode/workshop.yaml | 1 - tests/docs-how-to/debug-issues/workshop.yaml | 2 +- tests/docs-how-to/git/workshop.yaml | 2 +- tests/docs-tutorial/part-1/task.yaml | 2 +- tests/docs-tutorial/part-1/workshop-part-1.yaml | 2 +- tests/docs-tutorial/part-2/task.yaml | 1 - tests/docs-tutorial/part-2/workshop-part-2.yaml | 2 +- tests/docs-tutorial/part-3/workshop-part-3.yaml | 3 +-- 33 files changed, 68 insertions(+), 85 deletions(-) diff --git a/docs/coding-style-guide.md b/docs/coding-style-guide.md index 512f5ad31..a3f6f71e1 100644 --- a/docs/coding-style-guide.md +++ b/docs/coding-style-guide.md @@ -593,7 +593,7 @@ testWorkshop := Workshop{ Base: "ubuntu@22.04", Status: "Ready", SDKs: []SDK{ - {Name: "go", Channel: "22.04/stable"}, + {Name: "go", Channel: "latest/stable"}, }, } ``` diff --git a/docs/doc-style-guide.md b/docs/doc-style-guide.md index a78ad6881..b8ba194d7 100644 --- a/docs/doc-style-guide.md +++ b/docs/doc-style-guide.md @@ -418,13 +418,13 @@ With emphasis: ```restructuredtext .. code-block:: yaml :caption: workshop.yaml - :emphasize-lines: 7-11 + :emphasize-lines: 7-9 name: dev base: ubuntu@22.04 sdks: - name: go - channel: 22.04/stable + channel: 1.26 actions: lint: | @@ -701,7 +701,7 @@ Example with options: base: ubuntu@22.04 sdks: - name: go - channel: 22.04/stable + channel: 1.26 ``` **Spacing and formatting** @@ -1022,7 +1022,7 @@ Always include caption when it can be deduced from context: base: ubuntu@22.04 sdks: - name: go - channel: 22.04/stable + channel: 1.26 ``` Indentation: Use commonly recognized formatting: diff --git a/docs/explanation/interfaces/tunnel-interface.rst b/docs/explanation/interfaces/tunnel-interface.rst index 33014bd48..7af6ee07e 100644 --- a/docs/explanation/interfaces/tunnel-interface.rst +++ b/docs/explanation/interfaces/tunnel-interface.rst @@ -197,14 +197,14 @@ provided by :samp:`service-sdk`. from: 0.0.0.0:8081/tcp to: 127.0.0.1:8080/tcp client-sdk: - tracking: 22.04/stable + tracking: latest/stable installed: 2024-03-02 (1) tunnels: shared: from: [::1]:1080/tcp to: 127.0.0.1:18080/tcp service-sdk: - tracking: 22.04/edge + tracking: latest/edge installed: 2025-06-07 (2) diff --git a/docs/explanation/sdks/sdk-vs-dockerfile.rst b/docs/explanation/sdks/sdk-vs-dockerfile.rst index c5dcb182b..022a1b30f 100644 --- a/docs/explanation/sdks/sdk-vs-dockerfile.rst +++ b/docs/explanation/sdks/sdk-vs-dockerfile.rst @@ -239,7 +239,6 @@ Here's a corresponding workshop definition: base: ubuntu@22.04 sdks: - name: data-science - channel: 22.04/stable The default host location diff --git a/docs/explanation/workshops/concepts.rst b/docs/explanation/workshops/concepts.rst index dd4ef260b..f655400f3 100644 --- a/docs/explanation/workshops/concepts.rst +++ b/docs/explanation/workshops/concepts.rst @@ -100,7 +100,7 @@ A simple definition might look like this: base: ubuntu@22.04 sdks: - name: go - channel: 22.04/stable + channel: 1.26 .. @artefact SDK @@ -215,29 +215,26 @@ eventually, all interface connections are 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 two connections to its SDKs: +This example adds a slot, a plug, and a connection to its SDKs: .. code-block:: yaml :caption: .workshop/dev.yaml - :emphasize-lines: 6-9, 12-14 + :emphasize-lines: 5-8, 10-13, 15-17 base: ubuntu@22.04 name: dev sdks: - name: tensorflow - channel: 22.04/stable plugs: cuda: interface: mount workshop-target: /usr/local/cuda/lib64 - name: imagenet - channel: 22.04/stable slots: images: interface: mount workshop-source: $SDK/images - name: cuda - channel: 22.04/stable connections: - plug: tensorflow:cuda slot: cuda:libs @@ -294,7 +291,7 @@ intended as utility helpers for a development environment: base: ubuntu@24.04 sdks: - name: go - channel: 22.04/stable + channel: 1.26 actions: lint: | golangci-lint run --out-format=colored-line-number -c .golangci.yaml diff --git a/docs/how-to/customize-workshops/add-actions.rst b/docs/how-to/customize-workshops/add-actions.rst index 15b45d515..a57652180 100644 --- a/docs/how-to/customize-workshops/add-actions.rst +++ b/docs/how-to/customize-workshops/add-actions.rst @@ -35,7 +35,7 @@ from the :ref:`tut_sketch_sdks` tutorial section: base: ubuntu@22.04 sdks: - name: go - channel: 22.04/stable + channel: 1.26 actions: lint: | diff --git a/docs/how-to/customize-workshops/forward-ports.rst b/docs/how-to/customize-workshops/forward-ports.rst index 935e9518d..dd7bca7e5 100644 --- a/docs/how-to/customize-workshops/forward-ports.rst +++ b/docs/how-to/customize-workshops/forward-ports.rst @@ -78,7 +78,6 @@ The example shares the host's PostgreSQL server sdks: - name: mlflow - channel: 22.04/stable plugs: postgres: interface: tunnel diff --git a/docs/how-to/customize-workshops/move-projects.rst b/docs/how-to/customize-workshops/move-projects.rst index 308bf6c19..bb2ef9ab9 100644 --- a/docs/how-to/customize-workshops/move-projects.rst +++ b/docs/how-to/customize-workshops/move-projects.rst @@ -31,7 +31,7 @@ Things change *after* you run :command:`workshop launch`: base: ubuntu@22.04 sdks: - name: go - channel: 22.04/stable + channel: 1.26 .. @artefact workshop --project .. @artefact workshop launch diff --git a/docs/how-to/customize-workshops/use-multiple-workshops.rst b/docs/how-to/customize-workshops/use-multiple-workshops.rst index 70519f681..7d30bf51b 100644 --- a/docs/how-to/customize-workshops/use-multiple-workshops.rst +++ b/docs/how-to/customize-workshops/use-multiple-workshops.rst @@ -60,7 +60,7 @@ for the browser-facing part of the project: base: ubuntu@24.04 sdks: - name: node - channel: 24.04/stable + channel: 24 actions: build: | npm run build @@ -76,7 +76,7 @@ for the server-side code: base: ubuntu@22.04 sdks: - name: go - channel: 22.04/stable + channel: 1.26 actions: test: | go test ./... @@ -218,7 +218,7 @@ Both workshops can then include it: base: ubuntu@24.04 sdks: - name: node - channel: 24.04/stable + channel: 24 - name: project-common-tools @@ -230,7 +230,7 @@ Both workshops can then include it: base: ubuntu@22.04 sdks: - name: go - channel: 22.04/stable + channel: 1.26 - name: project-common-tools @@ -271,7 +271,7 @@ by pairing a :samp:`system` plug with a regular SDK slot: base: ubuntu@22.04 sdks: - name: go - channel: 22.04/stable + channel: 1.26 slots: api: interface: tunnel @@ -294,7 +294,7 @@ by pairing a regular SDK plug with a :samp:`system` slot: base: ubuntu@24.04 sdks: - name: node - channel: 24.04/stable + channel: 24 plugs: api: interface: tunnel diff --git a/docs/how-to/develop-with-workshops/connect-vscode.rst b/docs/how-to/develop-with-workshops/connect-vscode.rst index 2a4462cca..ca6c9f008 100644 --- a/docs/how-to/develop-with-workshops/connect-vscode.rst +++ b/docs/how-to/develop-with-workshops/connect-vscode.rst @@ -22,13 +22,12 @@ After that, add the :samp:`vscode-remote` SDK to your workshop definition: .. code-block:: yaml :caption: workshop.yaml - :emphasize-lines: 4-5 + :emphasize-lines: 4 name: dev base: ubuntu@24.04 sdks: - name: vscode-remote - channel: 24.04/edge Launch the workshop. diff --git a/docs/how-to/develop-with-workshops/run-github-actions-locally.rst b/docs/how-to/develop-with-workshops/run-github-actions-locally.rst index 0c746c8a9..89ed36e1c 100644 --- a/docs/how-to/develop-with-workshops/run-github-actions-locally.rst +++ b/docs/how-to/develop-with-workshops/run-github-actions-locally.rst @@ -52,13 +52,12 @@ to include the :samp:`github-runner` SDK: .. code-block:: yaml :caption: workshop.yaml - :emphasize-lines: 4-5 + :emphasize-lines: 4 name: ci base: ubuntu@24.04 sdks: - name: github-runner - channel: latest/stable This installs the official `Runner `_ client diff --git a/docs/how-to/develop-with-workshops/run-jupyterlab-in-browser.rst b/docs/how-to/develop-with-workshops/run-jupyterlab-in-browser.rst index 5b8873f1f..c6290098e 100644 --- a/docs/how-to/develop-with-workshops/run-jupyterlab-in-browser.rst +++ b/docs/how-to/develop-with-workshops/run-jupyterlab-in-browser.rst @@ -20,7 +20,7 @@ and configure a tunnel interface plug for the :samp:`system` SDK: .. code-block:: yaml :caption: workshop.yaml - :emphasize-lines: 5-10 + :emphasize-lines: 4-9 name: dev base: ubuntu@24.04 @@ -31,7 +31,6 @@ and configure a tunnel interface plug for the :samp:`system` SDK: interface: tunnel endpoint: 127.0.0.1:8989 - name: jupyter - channel: 24.04/edge Launch the workshop. diff --git a/docs/how-to/develop-with-workshops/run-vscode-in-browser.rst b/docs/how-to/develop-with-workshops/run-vscode-in-browser.rst index 6a8dd1e44..eafd46462 100644 --- a/docs/how-to/develop-with-workshops/run-vscode-in-browser.rst +++ b/docs/how-to/develop-with-workshops/run-vscode-in-browser.rst @@ -20,7 +20,7 @@ and configure a tunnel interface plug for the :samp:`system` SDK: .. code-block:: yaml :caption: workshop.yaml - :emphasize-lines: 5-10 + :emphasize-lines: 4-9 name: dev base: ubuntu@24.04 @@ -31,7 +31,6 @@ and configure a tunnel interface plug for the :samp:`system` SDK: interface: tunnel endpoint: 8090 - name: code-server - channel: 24.04/stable Launch the workshop. diff --git a/docs/how-to/develop-with-workshops/use-git.rst b/docs/how-to/develop-with-workshops/use-git.rst index fc416afc0..37453755f 100644 --- a/docs/how-to/develop-with-workshops/use-git.rst +++ b/docs/how-to/develop-with-workshops/use-git.rst @@ -35,7 +35,7 @@ in your repository: base: ubuntu@22.04 sdks: - name: go - channel: 22.04/stable + channel: 1.26 Next, @@ -167,7 +167,7 @@ to change the base image: base: ubuntu@24.04 sdks: - name: go - channel: 24.04/stable + channel: 1.26 Next, launch the redefined workshop to work on the problem: diff --git a/docs/how-to/develop-with-workshops/use-workshops-with-ai-agents.rst b/docs/how-to/develop-with-workshops/use-workshops-with-ai-agents.rst index 6b8f2f7b4..decec405c 100644 --- a/docs/how-to/develop-with-workshops/use-workshops-with-ai-agents.rst +++ b/docs/how-to/develop-with-workshops/use-workshops-with-ai-agents.rst @@ -147,9 +147,7 @@ Create a workshop definition file in the project root: base: ubuntu@24.04 sdks: - name: claude-code - channel: all/edge - name: copilot-cli - channel: all/edge actions: claude-auto: | diff --git a/docs/how-to/fix-workshops/debug-issues.rst b/docs/how-to/fix-workshops/debug-issues.rst index c644c1601..a58624414 100644 --- a/docs/how-to/fix-workshops/debug-issues.rst +++ b/docs/how-to/fix-workshops/debug-issues.rst @@ -32,7 +32,7 @@ from the :samp:`latest/edge` channel: base: ubuntu@22.04 sdks: - name: go - channel: 22.04/edge + channel: edge Suppose something goes wrong during :command:`workshop refresh`: @@ -83,7 +83,7 @@ this time supplying the change ID as the argument: $ workshop tasks 81 Status Duration Summary - Done 59ms Retrieve "go" SDK from channel "latest/stable" + Done 59ms Retrieve "go" SDK from channel "latest/edge" Undone 42ms Create SDK state storage Done 28ms Run hook "save-state" for "go" SDK Done 31ms Disconnect interfaces of "go" SDK diff --git a/docs/how-to/fix-workshops/resolve-plug-conflicts.rst b/docs/how-to/fix-workshops/resolve-plug-conflicts.rst index 18f9ac76e..fd417a2c9 100644 --- a/docs/how-to/fix-workshops/resolve-plug-conflicts.rst +++ b/docs/how-to/fix-workshops/resolve-plug-conflicts.rst @@ -30,9 +30,7 @@ where the SDKs store their models base: ubuntu@22.04 sdks: - name: torchaudio - channel: 22.04/stable - name: torchvision - channel: 22.04/stable Launching this workshop would cause a conflict @@ -47,15 +45,13 @@ where the SDKs store their models .. code-block:: yaml :caption: .workshop/digits.yaml - :emphasize-lines: 10 + :emphasize-lines: 8 name: digits base: ubuntu@22.04 sdks: - name: torchaudio - channel: 22.04/stable - name: torchvision - channel: 22.04/stable plugs: hub: bind: torchaudio:hub diff --git a/docs/readme.rst b/docs/readme.rst index 0f2ee9259..162a57249 100644 --- a/docs/readme.rst +++ b/docs/readme.rst @@ -47,7 +47,6 @@ then run ``workshop launch``: base: ubuntu@24.04 sdks: - name: go - channel: all/edge .. code-block:: console @@ -92,7 +91,7 @@ using the `--classic `_ option: sudo snap install --classic workshop To get the newest features, install from the edge channel: -``sudo snap install --classic --channel=latest/edge workshop``. +``sudo snap install --classic --edge workshop``. If this command fails, you may need an invitation; contact Dmitry Lyfar (dmitry.lyfar@canonical.com, @dlyfar on Mattermost). diff --git a/docs/reference/definition-files/schema.json b/docs/reference/definition-files/schema.json index 5765a368f..c46c552c7 100644 --- a/docs/reference/definition-files/schema.json +++ b/docs/reference/definition-files/schema.json @@ -56,9 +56,9 @@ }, "channel": { "type": "string", - "description": "Channel used for the SDK. May be empty when the SDK is installed from a local source.", - "pattern": "^(?:[a-z0-9](?:[.-]?[a-z0-9])*/(?:stable|candidate|beta|edge)|)$", - "errorMessage": "Channel must be empty or follow the pattern / (e.g., 'latest/stable')." + "description": "Channel used for the SDK. Default is latest/stable unless the SDK is installed from a local source.", + "pattern": "^(?:(?:[a-zA-Z0-9](?:[_.-]?[a-zA-Z0-9])*/)?(?:stable|candidate|beta|edge)(?:/[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9])?|[a-zA-Z0-9](?:[_.-]?[a-zA-Z0-9])*|)$", + "errorMessage": "Channel must look like [/][/] or []." }, "plugs": { "type": "object", @@ -162,4 +162,4 @@ "base": "The 'base' field is required." } } -} \ No newline at end of file +} diff --git a/docs/reference/definition-files/workshop-definition.rst b/docs/reference/definition-files/workshop-definition.rst index 261272d39..dd97c9466 100644 --- a/docs/reference/definition-files/workshop-definition.rst +++ b/docs/reference/definition-files/workshop-definition.rst @@ -130,10 +130,11 @@ Each SDK is described with the following keys: It uses a `snap-like format `__ - of :samp:`/` - without the :samp:`` part. + of :samp:`//`. + The default is :samp:`latest/stable` + (with no branch). - Required for SDKs from the Store. + Only applies to SDKs from the Store. * - :samp:`plugs` - object @@ -467,7 +468,7 @@ Examples This YAML file defines a :samp:`golang` workshop with a single :samp:`go` SDK -from the :samp:`latest/stable` channel, +from the :samp:`1.26/stable` channel, and some useful actions: .. code-block:: yaml @@ -477,7 +478,7 @@ and some useful actions: base: ubuntu@22.04 sdks: - name: go - channel: 22.04/stable + channel: 1.26 actions: lint: | go vet @@ -498,7 +499,7 @@ is bound to the :samp:`mod-cache` plug of the :samp:`go` SDK: base: ubuntu@22.04 sdks: - name: go - channel: 22.04/stable + channel: edge - name: project-tunnel plugs: data: @@ -522,19 +523,16 @@ and two connections: name: digits-cuda sdks: - name: tensorflow - channel: 22.04/stable plugs: cuda: interface: mount workshop-target: /usr/local/cuda/lib64 - name: imagenet - channel: 22.04/stable slots: images: interface: mount workshop-source: $SDK/images - name: cuda - channel: 22.04/stable connections: - plug: tensorflow:cuda slot: cuda:libs diff --git a/docs/reference/sdks.rst b/docs/reference/sdks.rst index 932435818..76e7cd639 100644 --- a/docs/reference/sdks.rst +++ b/docs/reference/sdks.rst @@ -555,7 +555,8 @@ SDK channels When SDKs are published by their creators and consumed by workshops, different versions and releases are tracked through the use of channels. -A channel is a combination of a track and a risk, e.g., :samp:`latest/beta`. +A channel is a combination of a track, a risk, and an optional branch, +e.g., :samp:`latest/beta`. Tracks allow multiple published versions of an SDK to exist in parallel; while no specific scheme is enforced, @@ -574,6 +575,10 @@ Risks represent a choice of maturity levels for a particular track: - :samp:`edge`: for unstable software that's still in active development; nothing is guaranteed +Branches are short-lived subdivisions of a channel +intended for experimentation, e.g. `1.2.3/edge/issue-56789`. +After 30 days of no activity, a branch will be closed automatically. + .. attention:: SDK channels should not be confused with SDK revisions. diff --git a/docs/tutorial/part-1-get-started.rst b/docs/tutorial/part-1-get-started.rst index b01b2840a..1fb88965c 100644 --- a/docs/tutorial/part-1-get-started.rst +++ b/docs/tutorial/part-1-get-started.rst @@ -96,7 +96,7 @@ using the `--classic `_ option: To get the newest features, install from the edge channel: -:command:`sudo snap install --classic --channel=latest/edge workshop`. +:command:`sudo snap install --classic --edge workshop`. .. warning:: @@ -162,18 +162,20 @@ create a workshop definition named :file:`workshop.yaml`: .. code-block:: yaml :caption: workshop.yaml - :emphasize-lines: 4,5 + :emphasize-lines: 4-5 name: dev base: ubuntu@22.04 sdks: - name: ollama - channel: 22.04/edge + channel: cpu Here, the SDK is referenced as :samp:`ollama`, and the specific version to retrieve from the SDK Store -comes from the :samp:`22.04/edge` channel. +comes from the :samp:`cpu` track of the :samp:`cpu/stable` channel. +Other tracks for :samp:`ollama` include :samp:`cuda`, :samp:`rocm`, and :samp:`vulkan`. +These enable various levels of hardware acceleration. To confirm that |ws_markup| sees the definition, list the workshops in the project directory: @@ -245,7 +247,7 @@ to see what went into your workshop: system: installed: (1) ollama: - tracking: 22.04/edge + tracking: cpu/stable installed: 0.9.6 2025-11-19 (981) mounts: models: @@ -355,7 +357,7 @@ and refresh the workshop: base: ubuntu@24.04 sdks: - name: ollama - channel: 24.04/edge + channel: vulkan .. @artefact workshop refresh @@ -524,7 +526,7 @@ pass the change ID to the command: Status Duration Summary Done 2m17.389s Download "ubuntu@24.04" base image Done 113ms Retrieve "system" SDK - Done 2m59.777s Retrieve "ollama" SDK from channel "24.04/edge" + Done 2m59.777s Retrieve "ollama" SDK from channel "vulkan/stable" Done 443ms Create SDK state storage Done 581ms Run hook "save-state" for "system" SDK Done 449ms Run hook "save-state" for "ollama" SDK diff --git a/docs/tutorial/part-2-work-with-interfaces.rst b/docs/tutorial/part-2-work-with-interfaces.rst index ae0f3a768..1eaffca53 100644 --- a/docs/tutorial/part-2-work-with-interfaces.rst +++ b/docs/tutorial/part-2-work-with-interfaces.rst @@ -109,7 +109,7 @@ you can remount to a directory in your home: system: installed: (1) ollama: - tracking: 24.04/edge + tracking: vulkan/stable installed: 0.9.6 2025-11-19 (214) mounts: models: @@ -140,15 +140,14 @@ to run Jupyter notebooks with the Ollama models: .. code-block:: yaml :caption: workshop.yaml - :emphasize-lines: 6,7 + :emphasize-lines: 6 name: dev base: ubuntu@24.04 sdks: - name: ollama - channel: 24.04/edge + channel: vulkan/stable - name: jupyter - channel: 24.04/edge .. code-block:: console @@ -167,15 +166,14 @@ to the host system at a port of your choice (here, :samp:`8989`): .. code-block:: yaml :caption: workshop.yaml - :emphasize-lines: 8-12 + :emphasize-lines: 7-11 name: dev base: ubuntu@24.04 sdks: - name: ollama - channel: 24.04/edge + channel: vulkan/stable - name: jupyter - channel: 24.04/edge - name: system plugs: jupyter: diff --git a/docs/tutorial/part-3-sketch-sdks.rst b/docs/tutorial/part-3-sketch-sdks.rst index 25fbd3acc..f2383335a 100644 --- a/docs/tutorial/part-3-sketch-sdks.rst +++ b/docs/tutorial/part-3-sketch-sdks.rst @@ -48,9 +48,8 @@ when we discussed :ref:`tut_work_with_interfaces`: base: ubuntu@24.04 sdks: - name: ollama - channel: 24.04/edge + channel: vulkan/stable - name: jupyter - channel: 24.04/edge - name: system plugs: jupyter: diff --git a/docs/tutorial/part-4-craft-sdks.rst b/docs/tutorial/part-4-craft-sdks.rst index 186e8d9b6..f94e1c6d2 100644 --- a/docs/tutorial/part-4-craft-sdks.rst +++ b/docs/tutorial/part-4-craft-sdks.rst @@ -40,7 +40,7 @@ using the `--classic `_ option: To get the newest features, install from the edge channel: -:command:`sudo snap install --classic --channel=latest/edge sdkcraft`. +:command:`sudo snap install --classic --edge sdkcraft`. Prerequisites ~~~~~~~~~~~~~ diff --git a/tests/docs-how-to/connect-vscode/workshop.yaml b/tests/docs-how-to/connect-vscode/workshop.yaml index 6003cc294..4aba5ac15 100644 --- a/tests/docs-how-to/connect-vscode/workshop.yaml +++ b/tests/docs-how-to/connect-vscode/workshop.yaml @@ -2,4 +2,3 @@ name: dev base: ubuntu@24.04 sdks: - name: vscode-remote - channel: latest/edge diff --git a/tests/docs-how-to/debug-issues/workshop.yaml b/tests/docs-how-to/debug-issues/workshop.yaml index 756fd1aad..18563224b 100644 --- a/tests/docs-how-to/debug-issues/workshop.yaml +++ b/tests/docs-how-to/debug-issues/workshop.yaml @@ -2,4 +2,4 @@ name: dev-volatile base: ubuntu@22.04 sdks: - name: go - channel: latest/stable + channel: edge diff --git a/tests/docs-how-to/git/workshop.yaml b/tests/docs-how-to/git/workshop.yaml index 295f2b4fd..f0d3410b1 100644 --- a/tests/docs-how-to/git/workshop.yaml +++ b/tests/docs-how-to/git/workshop.yaml @@ -2,4 +2,4 @@ name: dev base: ubuntu@22.04 sdks: - name: go - channel: latest/stable + channel: 1.26 diff --git a/tests/docs-tutorial/part-1/task.yaml b/tests/docs-tutorial/part-1/task.yaml index 280314c1e..5d7b4a59e 100644 --- a/tests/docs-tutorial/part-1/task.yaml +++ b/tests/docs-tutorial/part-1/task.yaml @@ -34,7 +34,7 @@ execute: | ## Refresh a workshop - sed -i "s/22.04/24.04/g" workshop.yaml + sed -e "s/22.04/24.04/g" -e "s/cpu/vulkan/g" -i workshop.yaml workshop_exec refresh diff --git a/tests/docs-tutorial/part-1/workshop-part-1.yaml b/tests/docs-tutorial/part-1/workshop-part-1.yaml index 32dddc786..d276f4e8b 100644 --- a/tests/docs-tutorial/part-1/workshop-part-1.yaml +++ b/tests/docs-tutorial/part-1/workshop-part-1.yaml @@ -2,4 +2,4 @@ name: dev base: ubuntu@22.04 sdks: - name: ollama - channel: latest/edge + channel: cpu diff --git a/tests/docs-tutorial/part-2/task.yaml b/tests/docs-tutorial/part-2/task.yaml index 280d1fc4d..92efd144c 100644 --- a/tests/docs-tutorial/part-2/task.yaml +++ b/tests/docs-tutorial/part-2/task.yaml @@ -35,7 +35,6 @@ execute: | cat >> workshop.yaml < Date: Wed, 1 Apr 2026 09:43:41 +1300 Subject: [PATCH 11/17] Build and upload test SDKs before running end-to-end tests --- .github/workflows/build-deps.yaml | 163 ++++++++++++++++++ .github/workflows/cover.yaml | 1 + .github/workflows/lxd-candidate-check.yaml | 1 + .github/workflows/spread.yaml | 5 + .github/workflows/zizmor.yaml | 2 + .spread.yaml | 1 + tests/lib/utils.sh | 8 + .../{ws-error.yaml => ws-error.yaml.in} | 2 +- .../.workshop/{ws-ok.yaml => ws-ok.yaml.in} | 2 +- .../{ws-waiting.yaml => ws-waiting.yaml.in} | 2 +- tests/main/check-health/task.yaml | 3 +- .../{ws-comp.yaml => ws-comp.yaml.in} | 4 +- tests/main/completion/task.yaml | 1 + tests/main/connect/task.yaml | 1 + tests/main/connect/workshop.yaml | 7 - tests/main/connect/workshop.yaml.in | 7 + .../{ws-conns-1.yaml => ws-conns-1.yaml.in} | 2 +- .../{ws-conns-2.yaml => ws-conns-2.yaml.in} | 2 +- tests/main/connections/task.yaml | 1 + tests/main/disconnect/task.yaml | 1 + .../{workshop.yaml => workshop.yaml.in} | 2 +- tests/main/exec/task.yaml | 1 + .../exec/{workshop.yaml => workshop.yaml.in} | 4 +- tests/main/info-sdk-health/task.yaml | 1 + .../{workshop.yaml => workshop.yaml.in} | 2 +- ...amera-fail.yaml => ws-camera-fail.yaml.in} | 2 +- .../{ws-camera.yaml => ws-camera.yaml.in} | 2 +- tests/main/interface-camera/task.yaml | 1 + ...ktop-fail.yaml => ws-desktop-fail.yaml.in} | 2 +- .../{ws-desktop.yaml => ws-desktop.yaml.in} | 2 +- tests/main/interface-desktop/task.yaml | 1 + .../{ws-gpu-fail.yaml => ws-gpu-fail.yaml.in} | 2 +- .../.workshop/{ws-gpu.yaml => ws-gpu.yaml.in} | 2 +- tests/main/interface-gpu/task.yaml | 1 + tests/main/interface-mount/task.yaml | 1 + .../{workshop.yaml => workshop.yaml.in} | 2 +- tests/main/interface-plug-binding/task.yaml | 1 + .../{workshop.yaml => workshop.yaml.in} | 6 +- .../{ws-ssh-fail.yaml => ws-ssh-fail.yaml.in} | 2 +- .../.workshop/{ws-ssh.yaml => ws-ssh.yaml.in} | 2 +- tests/main/interface-ssh-agent/task.yaml | 1 + .../{.workshop.yaml => .workshop.yaml.in} | 2 +- tests/main/interface-tunnel/task.yaml | 1 + tests/main/launch-abort/task.yaml | 1 + ...-fail.yaml => workshop.setup-fail.yaml.in} | 2 +- tests/main/launch-continue/task.yaml | 1 + .../{workshop.yaml => workshop.yaml.in} | 2 +- .../.workshop/{ws-one.yaml => ws-one.yaml.in} | 2 +- .../{ws-three.yaml => ws-three.yaml.in} | 2 +- .../.workshop/{ws-two.yaml => ws-two.yaml.in} | 2 +- tests/main/launch-multiple/task.yaml | 1 + tests/main/launch-sdk/task.yaml | 1 + .../{workshop.yaml => workshop.yaml.in} | 2 +- tests/main/refresh-abort/task.yaml | 1 + .../{workshop.yaml => workshop.yaml.in} | 2 +- tests/main/refresh-continue/task.yaml | 1 + .../{workshop.yaml => workshop.yaml.in} | 2 +- ...efresh-sdk.yaml => ws-refresh-sdk.yaml.in} | 2 +- .../multi/.workshop/ws-refresh-snapshots.yaml | 9 - .../.workshop/ws-refresh-snapshots.yaml.in | 9 + tests/main/refresh/task.yaml | 3 +- tests/main/remount/task.yaml | 1 + .../{workshop.yaml => workshop.yaml.in} | 2 +- ...remove-2.yaml => ws-snap-remove-2.yaml.in} | 2 +- ...move-2a.yaml => ws-snap-remove-2a.yaml.in} | 2 +- tests/main/remove-snap/task.yaml | 1 + ...ove-mount.yaml => ws-remove-mount.yaml.in} | 2 +- tests/main/remove/task.yaml | 5 +- .../.workshop/{edge.yaml => edge.yaml.in} | 2 +- .../.workshop/{stable.yaml => stable.yaml.in} | 2 +- tests/main/sdk-info/task.yaml | 1 + tests/main/shell/task.yaml | 1 + .../shell/{workshop.yaml => workshop.yaml.in} | 2 +- tests/main/sketch-sdk/task.yaml | 1 + .../{workshop.yaml => workshop.yaml.in} | 2 +- tests/main/start/task.yaml | 1 + .../start/{workshop.yaml => workshop.yaml.in} | 2 +- tests/main/stop/task.yaml | 1 + .../stop/{workshop.yaml => workshop.yaml.in} | 2 +- ...-user-conns.yaml => ws-user-conns.yaml.in} | 4 +- ...nns.yaml.new => ws-user-conns.yaml.new.in} | 4 +- tests/main/workshop-connections/task.yaml | 1 + 82 files changed, 278 insertions(+), 67 deletions(-) rename tests/main/check-health/.workshop/{ws-error.yaml => ws-error.yaml.in} (61%) rename tests/main/check-health/.workshop/{ws-ok.yaml => ws-ok.yaml.in} (59%) rename tests/main/check-health/.workshop/{ws-waiting.yaml => ws-waiting.yaml.in} (62%) rename tests/main/completion/.workshop/{ws-comp.yaml => ws-comp.yaml.in} (50%) delete mode 100644 tests/main/connect/workshop.yaml create mode 100644 tests/main/connect/workshop.yaml.in rename tests/main/connections/.workshop/{ws-conns-1.yaml => ws-conns-1.yaml.in} (59%) rename tests/main/connections/.workshop/{ws-conns-2.yaml => ws-conns-2.yaml.in} (59%) rename tests/main/disconnect/{workshop.yaml => workshop.yaml.in} (59%) rename tests/main/exec/{workshop.yaml => workshop.yaml.in} (70%) rename tests/main/info-sdk-health/{workshop.yaml => workshop.yaml.in} (63%) rename tests/main/interface-camera/.workshop/{ws-camera-fail.yaml => ws-camera-fail.yaml.in} (76%) rename tests/main/interface-camera/.workshop/{ws-camera.yaml => ws-camera.yaml.in} (59%) rename tests/main/interface-desktop/.workshop/{ws-desktop-fail.yaml => ws-desktop-fail.yaml.in} (76%) rename tests/main/interface-desktop/.workshop/{ws-desktop.yaml => ws-desktop.yaml.in} (75%) rename tests/main/interface-gpu/.workshop/{ws-gpu-fail.yaml => ws-gpu-fail.yaml.in} (74%) rename tests/main/interface-gpu/.workshop/{ws-gpu.yaml => ws-gpu.yaml.in} (57%) rename tests/main/interface-mount/{workshop.yaml => workshop.yaml.in} (92%) rename tests/main/interface-plug-binding/{workshop.yaml => workshop.yaml.in} (70%) rename tests/main/interface-ssh-agent/.workshop/{ws-ssh-fail.yaml => ws-ssh-fail.yaml.in} (76%) rename tests/main/interface-ssh-agent/.workshop/{ws-ssh.yaml => ws-ssh.yaml.in} (76%) rename tests/main/interface-tunnel/{.workshop.yaml => .workshop.yaml.in} (93%) rename tests/main/launch-abort/{workshop.setup-fail.yaml => workshop.setup-fail.yaml.in} (63%) rename tests/main/launch-continue/{workshop.yaml => workshop.yaml.in} (62%) rename tests/main/launch-multiple/.workshop/{ws-one.yaml => ws-one.yaml.in} (58%) rename tests/main/launch-multiple/.workshop/{ws-three.yaml => ws-three.yaml.in} (59%) rename tests/main/launch-multiple/.workshop/{ws-two.yaml => ws-two.yaml.in} (58%) rename tests/main/launch-sdk/{workshop.yaml => workshop.yaml.in} (59%) rename tests/main/refresh-abort/{workshop.yaml => workshop.yaml.in} (61%) rename tests/main/refresh-continue/{workshop.yaml => workshop.yaml.in} (61%) rename tests/main/refresh/multi/.workshop/{ws-refresh-sdk.yaml => ws-refresh-sdk.yaml.in} (61%) delete mode 100644 tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml create mode 100644 tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml.in rename tests/main/remount/{workshop.yaml => workshop.yaml.in} (59%) rename tests/main/remove-snap/project-2/.workshop/{ws-snap-remove-2.yaml => ws-snap-remove-2.yaml.in} (61%) rename tests/main/remove-snap/project-2/.workshop/{ws-snap-remove-2a.yaml => ws-snap-remove-2a.yaml.in} (62%) rename tests/main/remove/.workshop/{ws-remove-mount.yaml => ws-remove-mount.yaml.in} (61%) rename tests/main/sdk-info/.workshop/{edge.yaml => edge.yaml.in} (62%) rename tests/main/sdk-info/.workshop/{stable.yaml => stable.yaml.in} (62%) rename tests/main/shell/{workshop.yaml => workshop.yaml.in} (59%) rename tests/main/sketch-sdk/{workshop.yaml => workshop.yaml.in} (59%) rename tests/main/start/{workshop.yaml => workshop.yaml.in} (59%) rename tests/main/stop/{workshop.yaml => workshop.yaml.in} (58%) rename tests/main/workshop-connections/.workshop/{ws-user-conns.yaml => ws-user-conns.yaml.in} (82%) rename tests/main/workshop-connections/.workshop/{ws-user-conns.yaml.new => ws-user-conns.yaml.new.in} (75%) diff --git a/.github/workflows/build-deps.yaml b/.github/workflows/build-deps.yaml index ddbb2a480..66247075b 100644 --- a/.github/workflows/build-deps.yaml +++ b/.github/workflows/build-deps.yaml @@ -4,10 +4,18 @@ on: secrets: GH_SDKCRAFT: required: true + SDKCRAFT_STORE_CREDENTIALS: + required: true outputs: snap_workshop: description: "Workshop snap filename" value: ${{ jobs.build-workshop.outputs.snap_workshop }} + test_sdk_branch: + description: Store branch for test SDKs + value: ${{ jobs.detect-sdk-changes.outputs.branch }} + +permissions: + contents: read jobs: build-workshop: @@ -120,3 +128,158 @@ jobs: with: name: spread_binary path: spread/spread + + detect-sdk-changes: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' || github.event_name == 'push' + outputs: + branch: ${{ steps.base.outputs.branch }} + modified: ${{ steps.detect.outputs.modified }} + unchanged: ${{ steps.detect.outputs.unchanged }} + steps: + - name: Checkout Workshop + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + filter: 'tree:0' + persist-credentials: true + + - name: Checkout base commit + id: base + run: | + case "$GITHUB_EVENT_NAME" in + 'pull_request') + git fetch --filter=tree:0 origin "+${GITHUB_BASE_REF}:${GITHUB_BASE_REF}" + + printf '[command]git rev-parse %s\n' "$GITHUB_BASE_REF" + rev=$(git rev-parse "$GITHUB_BASE_REF") + printf '%s\n' "$rev" + + printf 'range=%s...HEAD\n' "$rev" >>"$GITHUB_OUTPUT" + + printf 'branch=pr-%s\n' "$GITHUB_EVENT_NUMBER" >>"$GITHUB_OUTPUT" + ;; + 'push') + git fetch --filter=tree:0 origin "$GITHUB_EVENT_BEFORE" + + printf 'range=%s..HEAD\n' "$GITHUB_EVENT_BEFORE" >>"$GITHUB_OUTPUT" + + printf 'branch=\n' >>"$GITHUB_OUTPUT" + ;; + *) + printf '::error::This workflow only supports push and pull_request events.\n' + exit 1 + ;; + esac + env: + GITHUB_EVENT_BEFORE: ${{ github.event.before }} + GITHUB_EVENT_NUMBER: ${{ github.event.number }} + + - name: Detect SDK changes + id: detect + run: | + cd tests/lib/sdk + + modified=() + unchanged=() + for path in */*/*/; do + if git diff --quiet "${RANGE}" -- "$path"; then + unchanged+=("${path%/}") + else + modified+=("${path%/}") + fi + done + + maybe_list() { + message="$1" + shift + if [ $# -gt 0 ]; then + printf '%s:\n' "$message" + for path in "$@"; do + printf ' %s\n' "$path" + done + fi + } + + maybe_list 'Modified SDKs' "${modified[@]}" + maybe_list 'Unmodified SDKs' "${unchanged[@]}" + + print_matrix() { + printf '%s=' "$1" + shift + for path in "$@"; do + IFS='/' read -r -a parts <<<"$path" + + platforms='["amd64"]' + if [ "${parts[0]}" = test-sdk-info ]; then + platforms='["all"]' + fi + + jq --null-input \ + --arg name "${parts[0]}" \ + --arg track "${parts[1]}" \ + --arg risk "${parts[2]}" \ + --arg platforms "$platforms" \ + '$ARGS.named' + done | jq --compact-output --slurp . + } + + print_matrix modified "${modified[@]}" >> "$GITHUB_OUTPUT" + print_matrix unchanged "${unchanged[@]}" >> "$GITHUB_OUTPUT" + shell: bash + env: + RANGE: ${{ steps.base.outputs.range }} + + build-test-sdk: + needs: detect-sdk-changes + if: needs.detect-sdk-changes.outputs.modified != '[]' + strategy: + matrix: + include: ${{ fromJSON(needs.detect-sdk-changes.outputs.modified) }} + fail-fast: false + uses: canonical/sdkcraft-actions/.github/workflows/upload.yml@92f5df60c0878aea4844a7380fdfd24063f89ac9 + with: + platforms: ${{ matrix.platforms }} + subdirectory: tests/lib/sdk/${{ matrix.name }}/${{ matrix.track }}/${{ matrix.risk }} + track: ${{ matrix.track }} + risk: ${{ matrix.risk }} + branch: ${{ needs.detect-sdk-changes.outputs.branch }} + secrets: + SDKCRAFT_STORE_CREDENTIALS: ${{ secrets.SDKCRAFT_STORE_CREDENTIALS }} + + release-test-sdks: + needs: detect-sdk-changes + if: github.event_name == 'pull_request' && needs.detect-sdk-changes.outputs.unchanged != '[]' + runs-on: [self-hosted, linux, noble, x64, xlarge] + steps: + - name: Install sdkcraft + run: sudo snap install sdkcraft --edge --classic + + - name: Release test SDKs + run: | + mapfile -d '' -t sdks < <(jq --compact-output --raw-output0 '.[]' <<<"$SDKS") + + for sdk in "${sdks[@]}"; do + name=$(jq --raw-output .name <<<"$sdk") + track=$(jq --raw-output .track <<<"$sdk") + risk=$(jq --raw-output .risk <<<"$sdk") + + printf '::group::Releasing %s SDK to %s/%s/%s\n' "$name" "$track" "$risk" "$BRANCH" + + curl -fsSLo info.json --retry 5 "https://api.staging.pkg.store/v2/sdks/info/$name?fields=channel-map,revision" + + filter='."channel-map"[] | select(.channel.name == $channel) | .revision.revision' + jq --arg channel "${track}/${risk}" --raw-output0 "$filter" < info.json | sort --numeric-sort --zero-terminated > revisions + + mapfile -d '' -t revisions < revisions + for rev in "${revisions[@]}"; do + sdkcraft release "$name" "$rev" "${track}/${risk}/${BRANCH}" + done + + printf '::endgroup::\n' + done + shell: bash + env: + SDKS: ${{ needs.detect-sdk-changes.outputs.unchanged }} + BRANCH: ${{ needs.detect-sdk-changes.outputs.branch }} + SDKCRAFT_STORE_CREDENTIALS: ${{ secrets.SDKCRAFT_STORE_CREDENTIALS }} # zizmor: ignore[secrets-outside-env] diff --git a/.github/workflows/cover.yaml b/.github/workflows/cover.yaml index 2a69c7c13..c97ede971 100644 --- a/.github/workflows/cover.yaml +++ b/.github/workflows/cover.yaml @@ -27,6 +27,7 @@ jobs: secrets: GH_SDKCRAFT: ${{ secrets.GH_SDKCRAFT }} STORE_CREDENTIALS: ${{ secrets.STORE_CREDENTIALS }} + SDKCRAFT_STORE_CREDENTIALS: ${{ secrets.SDKCRAFT_STORE_CREDENTIALS_STAGING }} unit-tests: uses: ./.github/workflows/unit-tests.yaml diff --git a/.github/workflows/lxd-candidate-check.yaml b/.github/workflows/lxd-candidate-check.yaml index e4731532e..45dad5267 100644 --- a/.github/workflows/lxd-candidate-check.yaml +++ b/.github/workflows/lxd-candidate-check.yaml @@ -12,6 +12,7 @@ jobs: uses: ./.github/workflows/build-deps.yaml secrets: GH_SDKCRAFT: ${{ secrets.GH_SDKCRAFT }} + SDKCRAFT_STORE_CREDENTIALS: ${{ secrets.SDKCRAFT_STORE_CREDENTIALS_STAGING }} snap-tests: needs: [build-deps] strategy: diff --git a/.github/workflows/spread.yaml b/.github/workflows/spread.yaml index 6f7dd8a45..d3b3b293a 100644 --- a/.github/workflows/spread.yaml +++ b/.github/workflows/spread.yaml @@ -6,6 +6,8 @@ on: required: true STORE_CREDENTIALS: required: true + SDKCRAFT_STORE_CREDENTIALS: + required: true workflow_dispatch: permissions: @@ -16,6 +18,7 @@ jobs: uses: ./.github/workflows/build-deps.yaml secrets: GH_SDKCRAFT: ${{ secrets.GH_SDKCRAFT }} + SDKCRAFT_STORE_CREDENTIALS: ${{ secrets.SDKCRAFT_STORE_CREDENTIALS }} snap-tests: needs: [build-deps] strategy: @@ -76,6 +79,8 @@ jobs: run: | mkdir ${{ github.workspace }}.cover spread -artifacts=${{ github.workspace }}.cover tests/${{matrix.suite}} + env: + TEST_SDK_BRANCH: ${{ needs.build-deps.outputs.test_sdk_branch }} - name: Trim trailing slash from matrix.suite id: sanitize env: diff --git a/.github/workflows/zizmor.yaml b/.github/workflows/zizmor.yaml index 1d1871e1b..3ec8e2f16 100644 --- a/.github/workflows/zizmor.yaml +++ b/.github/workflows/zizmor.yaml @@ -23,3 +23,5 @@ jobs: uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2 with: advanced-security: false + # TODO: enable this when we go public + online-audits: false diff --git a/.spread.yaml b/.spread.yaml index 38c14d478..ef287393b 100644 --- a/.spread.yaml +++ b/.spread.yaml @@ -7,6 +7,7 @@ environment: PROJECT_PATH: /workshop TESTSLIB: $PROJECT_PATH/tests/lib TESTS_SDKS: $PROJECT_PATH/tests/lib/sdk + TEST_SDK_BRANCH: $(HOST:printf %s "${TEST_SDK_BRANCH:+/$TEST_SDK_BRANCH}") SDK_STORE_BUCKET_DIR: /data/sdkstore IMAGE_SERVER: $(HOST:echo -n $WORKSHOP_IMAGE_SERVER) repack: | diff --git a/tests/lib/utils.sh b/tests/lib/utils.sh index e47bedfc6..9a483f1bd 100644 --- a/tests/lib/utils.sh +++ b/tests/lib/utils.sh @@ -188,3 +188,11 @@ function run_sdkcraft() { function install_sdkcraft() { snap install --dangerous --classic /workshop/tests/sdkcraft_*.snap } + +function resolve_branch() { + local file + for file in "$@"; do + # shellcheck disable=SC2016 + envsubst '${TEST_SDK_BRANCH}' < "${file}.in" > "$file" + done +} diff --git a/tests/main/check-health/.workshop/ws-error.yaml b/tests/main/check-health/.workshop/ws-error.yaml.in similarity index 61% rename from tests/main/check-health/.workshop/ws-error.yaml rename to tests/main/check-health/.workshop/ws-error.yaml.in index 77322c236..d7b07d7cb 100644 --- a/tests/main/check-health/.workshop/ws-error.yaml +++ b/tests/main/check-health/.workshop/ws-error.yaml.in @@ -2,4 +2,4 @@ name: ws-error base: ubuntu@22.04 sdks: - name: test-sdk-health-error - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/check-health/.workshop/ws-ok.yaml b/tests/main/check-health/.workshop/ws-ok.yaml.in similarity index 59% rename from tests/main/check-health/.workshop/ws-ok.yaml rename to tests/main/check-health/.workshop/ws-ok.yaml.in index 69edd0708..43aff7560 100644 --- a/tests/main/check-health/.workshop/ws-ok.yaml +++ b/tests/main/check-health/.workshop/ws-ok.yaml.in @@ -2,4 +2,4 @@ name: ws-ok base: ubuntu@22.04 sdks: - name: test-sdk-health-ok - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/check-health/.workshop/ws-waiting.yaml b/tests/main/check-health/.workshop/ws-waiting.yaml.in similarity index 62% rename from tests/main/check-health/.workshop/ws-waiting.yaml rename to tests/main/check-health/.workshop/ws-waiting.yaml.in index de923a96a..9856d5ffc 100644 --- a/tests/main/check-health/.workshop/ws-waiting.yaml +++ b/tests/main/check-health/.workshop/ws-waiting.yaml.in @@ -2,4 +2,4 @@ name: ws-waiting base: ubuntu@22.04 sdks: - name: test-sdk-health-waiting - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/check-health/task.yaml b/tests/main/check-health/task.yaml index e1731cc65..13f44d752 100644 --- a/tests/main/check-health/task.yaml +++ b/tests/main/check-health/task.yaml @@ -1,7 +1,8 @@ summary: Check check-health hook and set-health reporting execute: | . "$TESTSLIB"/utils.sh - + + resolve_branch .workshop/ws-ok.yaml .workshop/ws-error.yaml .workshop/ws-waiting.yaml workshop_exec launch ws-ok echo "Check health reported okay" diff --git a/tests/main/completion/.workshop/ws-comp.yaml b/tests/main/completion/.workshop/ws-comp.yaml.in similarity index 50% rename from tests/main/completion/.workshop/ws-comp.yaml rename to tests/main/completion/.workshop/ws-comp.yaml.in index 0f54f4ac1..27eda6686 100644 --- a/tests/main/completion/.workshop/ws-comp.yaml +++ b/tests/main/completion/.workshop/ws-comp.yaml.in @@ -2,6 +2,6 @@ name: ws-comp base: ubuntu@22.04 sdks: - name: test-sdk-desktop - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} - name: test-sdk-mount - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/completion/task.yaml b/tests/main/completion/task.yaml index 24b1dc945..c65fb1f85 100644 --- a/tests/main/completion/task.yaml +++ b/tests/main/completion/task.yaml @@ -1,6 +1,7 @@ summary: Check that shell completion is installed with the snap prepare: | . "$TESTSLIB"/utils.sh + resolve_branch .workshop/ws-comp.yaml workshop_exec launch ws-comp restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/connect/task.yaml b/tests/main/connect/task.yaml index 998b9c5ac..3f3ac0990 100644 --- a/tests/main/connect/task.yaml +++ b/tests/main/connect/task.yaml @@ -1,6 +1,7 @@ summary: Check that "workshop connect" works as expected prepare: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/connect/workshop.yaml b/tests/main/connect/workshop.yaml deleted file mode 100644 index 6b981a7d8..000000000 --- a/tests/main/connect/workshop.yaml +++ /dev/null @@ -1,7 +0,0 @@ -name: ws-conn -base: ubuntu@22.04 -sdks: - - name: test-sdk-mount - channel: latest/stable - - name: test-sdk-gpu - channel: latest/stable diff --git a/tests/main/connect/workshop.yaml.in b/tests/main/connect/workshop.yaml.in new file mode 100644 index 000000000..783821b03 --- /dev/null +++ b/tests/main/connect/workshop.yaml.in @@ -0,0 +1,7 @@ +name: ws-conn +base: ubuntu@22.04 +sdks: + - name: test-sdk-mount + channel: latest/stable${TEST_SDK_BRANCH} + - name: test-sdk-gpu + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/connections/.workshop/ws-conns-1.yaml b/tests/main/connections/.workshop/ws-conns-1.yaml.in similarity index 59% rename from tests/main/connections/.workshop/ws-conns-1.yaml rename to tests/main/connections/.workshop/ws-conns-1.yaml.in index 5a01c88c4..c2f6602a5 100644 --- a/tests/main/connections/.workshop/ws-conns-1.yaml +++ b/tests/main/connections/.workshop/ws-conns-1.yaml.in @@ -2,4 +2,4 @@ name: ws-conns-1 base: ubuntu@22.04 sdks: - name: test-sdk-mount - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/connections/.workshop/ws-conns-2.yaml b/tests/main/connections/.workshop/ws-conns-2.yaml.in similarity index 59% rename from tests/main/connections/.workshop/ws-conns-2.yaml rename to tests/main/connections/.workshop/ws-conns-2.yaml.in index cb091e877..d7518bd8d 100644 --- a/tests/main/connections/.workshop/ws-conns-2.yaml +++ b/tests/main/connections/.workshop/ws-conns-2.yaml.in @@ -2,4 +2,4 @@ name: ws-conns-2 base: ubuntu@22.04 sdks: - name: test-sdk-mount - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/connections/task.yaml b/tests/main/connections/task.yaml index 9d3b7fb63..e991cb673 100644 --- a/tests/main/connections/task.yaml +++ b/tests/main/connections/task.yaml @@ -1,6 +1,7 @@ summary: Check that "workshop connections" works as expected prepare: | . "$TESTSLIB"/utils.sh + resolve_branch .workshop/ws-conns-1.yaml .workshop/ws-conns-2.yaml workshop_exec launch ws-conns-1 ws-conns-2 restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/disconnect/task.yaml b/tests/main/disconnect/task.yaml index 1fa3398e4..18e14938f 100644 --- a/tests/main/disconnect/task.yaml +++ b/tests/main/disconnect/task.yaml @@ -1,6 +1,7 @@ summary: Check that "workshop disconnect" works as expected prepare: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/disconnect/workshop.yaml b/tests/main/disconnect/workshop.yaml.in similarity index 59% rename from tests/main/disconnect/workshop.yaml rename to tests/main/disconnect/workshop.yaml.in index 2d85d08dd..48bead8d1 100644 --- a/tests/main/disconnect/workshop.yaml +++ b/tests/main/disconnect/workshop.yaml.in @@ -2,4 +2,4 @@ name: ws-disconn base: ubuntu@22.04 sdks: - name: test-sdk-mount - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/exec/task.yaml b/tests/main/exec/task.yaml index e55de568a..10a509d01 100644 --- a/tests/main/exec/task.yaml +++ b/tests/main/exec/task.yaml @@ -1,6 +1,7 @@ summary: Check major workshop exec scenarios prepare: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/exec/workshop.yaml b/tests/main/exec/workshop.yaml.in similarity index 70% rename from tests/main/exec/workshop.yaml rename to tests/main/exec/workshop.yaml.in index 9b0d013cd..59559936c 100644 --- a/tests/main/exec/workshop.yaml +++ b/tests/main/exec/workshop.yaml.in @@ -2,9 +2,9 @@ name: ws-exec base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} - name: test-sdk-environment - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} actions: oneline: echo one line multiline: | diff --git a/tests/main/info-sdk-health/task.yaml b/tests/main/info-sdk-health/task.yaml index 3ec6b6782..15fac0bb3 100644 --- a/tests/main/info-sdk-health/task.yaml +++ b/tests/main/info-sdk-health/task.yaml @@ -8,6 +8,7 @@ restore: | apt remove -y expect execute: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.yaml workshop_exec launch --no-wait sudo -u ubuntu 2>&1 -- expect -f ./health.exp workshop_exec remove diff --git a/tests/main/info-sdk-health/workshop.yaml b/tests/main/info-sdk-health/workshop.yaml.in similarity index 63% rename from tests/main/info-sdk-health/workshop.yaml rename to tests/main/info-sdk-health/workshop.yaml.in index 7218435b8..2a79961aa 100644 --- a/tests/main/info-sdk-health/workshop.yaml +++ b/tests/main/info-sdk-health/workshop.yaml.in @@ -2,4 +2,4 @@ name: ws-sdk-health base: ubuntu@22.04 sdks: - name: test-sdk-health-waiting - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/interface-camera/.workshop/ws-camera-fail.yaml b/tests/main/interface-camera/.workshop/ws-camera-fail.yaml.in similarity index 76% rename from tests/main/interface-camera/.workshop/ws-camera-fail.yaml rename to tests/main/interface-camera/.workshop/ws-camera-fail.yaml.in index e19f839ca..8b8e5b8c0 100644 --- a/tests/main/interface-camera/.workshop/ws-camera-fail.yaml +++ b/tests/main/interface-camera/.workshop/ws-camera-fail.yaml.in @@ -6,4 +6,4 @@ sdks: user-camera: interface: camera - name: test-sdk-camera - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/interface-camera/.workshop/ws-camera.yaml b/tests/main/interface-camera/.workshop/ws-camera.yaml.in similarity index 59% rename from tests/main/interface-camera/.workshop/ws-camera.yaml rename to tests/main/interface-camera/.workshop/ws-camera.yaml.in index 27c8bcf93..94cf77bc9 100644 --- a/tests/main/interface-camera/.workshop/ws-camera.yaml +++ b/tests/main/interface-camera/.workshop/ws-camera.yaml.in @@ -2,4 +2,4 @@ name: ws-camera base: ubuntu@22.04 sdks: - name: test-sdk-camera - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/interface-camera/task.yaml b/tests/main/interface-camera/task.yaml index 93ecb03d0..d71319f95 100644 --- a/tests/main/interface-camera/task.yaml +++ b/tests/main/interface-camera/task.yaml @@ -1,6 +1,7 @@ summary: Check camera interface scenarios prepare: | . "$TESTSLIB"/utils.sh + resolve_branch .workshop/ws-camera.yaml .workshop/ws-camera-fail.yaml workshop_exec launch ws-camera restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/interface-desktop/.workshop/ws-desktop-fail.yaml b/tests/main/interface-desktop/.workshop/ws-desktop-fail.yaml.in similarity index 76% rename from tests/main/interface-desktop/.workshop/ws-desktop-fail.yaml rename to tests/main/interface-desktop/.workshop/ws-desktop-fail.yaml.in index bc2d1fdaa..597a46f64 100644 --- a/tests/main/interface-desktop/.workshop/ws-desktop-fail.yaml +++ b/tests/main/interface-desktop/.workshop/ws-desktop-fail.yaml.in @@ -6,4 +6,4 @@ sdks: user-desktop: interface: desktop - name: test-sdk-desktop - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/interface-desktop/.workshop/ws-desktop.yaml b/tests/main/interface-desktop/.workshop/ws-desktop.yaml.in similarity index 75% rename from tests/main/interface-desktop/.workshop/ws-desktop.yaml rename to tests/main/interface-desktop/.workshop/ws-desktop.yaml.in index 2df12e21a..49f152b0f 100644 --- a/tests/main/interface-desktop/.workshop/ws-desktop.yaml +++ b/tests/main/interface-desktop/.workshop/ws-desktop.yaml.in @@ -2,7 +2,7 @@ name: ws-desktop base: ubuntu@22.04 sdks: - name: test-sdk-desktop - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} connections: - plug: test-sdk-desktop:desktop slot: system:desktop diff --git a/tests/main/interface-desktop/task.yaml b/tests/main/interface-desktop/task.yaml index 2ae8638af..4e25ca2fa 100644 --- a/tests/main/interface-desktop/task.yaml +++ b/tests/main/interface-desktop/task.yaml @@ -1,6 +1,7 @@ summary: Check desktop interface scenarios prepare: | . "$TESTSLIB"/utils.sh + resolve_branch .workshop/ws-desktop.yaml .workshop/ws-desktop-fail.yaml workshop_exec launch ws-desktop restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/interface-gpu/.workshop/ws-gpu-fail.yaml b/tests/main/interface-gpu/.workshop/ws-gpu-fail.yaml.in similarity index 74% rename from tests/main/interface-gpu/.workshop/ws-gpu-fail.yaml rename to tests/main/interface-gpu/.workshop/ws-gpu-fail.yaml.in index b516f0785..59c8eaba4 100644 --- a/tests/main/interface-gpu/.workshop/ws-gpu-fail.yaml +++ b/tests/main/interface-gpu/.workshop/ws-gpu-fail.yaml.in @@ -6,4 +6,4 @@ sdks: user-gpu: interface: gpu - name: test-sdk-gpu - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/interface-gpu/.workshop/ws-gpu.yaml b/tests/main/interface-gpu/.workshop/ws-gpu.yaml.in similarity index 57% rename from tests/main/interface-gpu/.workshop/ws-gpu.yaml rename to tests/main/interface-gpu/.workshop/ws-gpu.yaml.in index 543ad9ec2..1c7f4f7a7 100644 --- a/tests/main/interface-gpu/.workshop/ws-gpu.yaml +++ b/tests/main/interface-gpu/.workshop/ws-gpu.yaml.in @@ -2,4 +2,4 @@ name: ws-gpu base: ubuntu@22.04 sdks: - name: test-sdk-gpu - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/interface-gpu/task.yaml b/tests/main/interface-gpu/task.yaml index 032c1eb0f..2d87d1161 100644 --- a/tests/main/interface-gpu/task.yaml +++ b/tests/main/interface-gpu/task.yaml @@ -1,6 +1,7 @@ summary: Check GPU interface scenarios prepare: | . "$TESTSLIB"/utils.sh + resolve_branch .workshop/ws-gpu.yaml .workshop/ws-gpu-fail.yaml workshop_exec launch ws-gpu restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/interface-mount/task.yaml b/tests/main/interface-mount/task.yaml index a0392f956..c1f937088 100644 --- a/tests/main/interface-mount/task.yaml +++ b/tests/main/interface-mount/task.yaml @@ -1,6 +1,7 @@ summary: Check mount interface scenarios prepare: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/interface-mount/workshop.yaml b/tests/main/interface-mount/workshop.yaml.in similarity index 92% rename from tests/main/interface-mount/workshop.yaml rename to tests/main/interface-mount/workshop.yaml.in index 94f9a525f..68886e21c 100644 --- a/tests/main/interface-mount/workshop.yaml +++ b/tests/main/interface-mount/workshop.yaml.in @@ -2,7 +2,7 @@ name: ws-mount base: ubuntu@22.04 sdks: - name: test-sdk-mount - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} plugs: jobs: interface: mount diff --git a/tests/main/interface-plug-binding/task.yaml b/tests/main/interface-plug-binding/task.yaml index 2face3c9d..30e88a7f3 100644 --- a/tests/main/interface-plug-binding/task.yaml +++ b/tests/main/interface-plug-binding/task.yaml @@ -1,6 +1,7 @@ summary: Check plug binding prepare: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.yaml workshop_exec launch execute: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/interface-plug-binding/workshop.yaml b/tests/main/interface-plug-binding/workshop.yaml.in similarity index 70% rename from tests/main/interface-plug-binding/workshop.yaml rename to tests/main/interface-plug-binding/workshop.yaml.in index bc277c4f3..eb1f3e387 100644 --- a/tests/main/interface-plug-binding/workshop.yaml +++ b/tests/main/interface-plug-binding/workshop.yaml.in @@ -2,17 +2,17 @@ name: ws-bind base: ubuntu@22.04 sdks: - name: test-sdk-mount - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} plugs: two: bind: test-sdk-multiple-plugs:two - name: test-sdk-multiple-plugs - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} plugs: one: bind: test-sdk-mount:one - name: test-sdk-ssh-agent - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} plugs: ssh-agent: bind: test-sdk-multiple-plugs:ssh-agent diff --git a/tests/main/interface-ssh-agent/.workshop/ws-ssh-fail.yaml b/tests/main/interface-ssh-agent/.workshop/ws-ssh-fail.yaml.in similarity index 76% rename from tests/main/interface-ssh-agent/.workshop/ws-ssh-fail.yaml rename to tests/main/interface-ssh-agent/.workshop/ws-ssh-fail.yaml.in index 9d6980273..e044822b6 100644 --- a/tests/main/interface-ssh-agent/.workshop/ws-ssh-fail.yaml +++ b/tests/main/interface-ssh-agent/.workshop/ws-ssh-fail.yaml.in @@ -6,4 +6,4 @@ sdks: user-ssh: interface: ssh-agent - name: test-sdk-ssh-agent - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/interface-ssh-agent/.workshop/ws-ssh.yaml b/tests/main/interface-ssh-agent/.workshop/ws-ssh.yaml.in similarity index 76% rename from tests/main/interface-ssh-agent/.workshop/ws-ssh.yaml rename to tests/main/interface-ssh-agent/.workshop/ws-ssh.yaml.in index 6f2215932..54225930c 100644 --- a/tests/main/interface-ssh-agent/.workshop/ws-ssh.yaml +++ b/tests/main/interface-ssh-agent/.workshop/ws-ssh.yaml.in @@ -2,7 +2,7 @@ name: ws-ssh base: ubuntu@22.04 sdks: - name: test-sdk-ssh-agent - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} connections: - plug: test-sdk-ssh-agent:ssh-agent slot: system:ssh-agent diff --git a/tests/main/interface-ssh-agent/task.yaml b/tests/main/interface-ssh-agent/task.yaml index f58a5cedc..cf09fe509 100644 --- a/tests/main/interface-ssh-agent/task.yaml +++ b/tests/main/interface-ssh-agent/task.yaml @@ -1,6 +1,7 @@ summary: Check ssh-agent interface scenarios prepare: | . "$TESTSLIB"/utils.sh + resolve_branch .workshop/ws-ssh.yaml .workshop/ws-ssh-fail.yaml workshop_exec launch ws-ssh restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/interface-tunnel/.workshop.yaml b/tests/main/interface-tunnel/.workshop.yaml.in similarity index 93% rename from tests/main/interface-tunnel/.workshop.yaml rename to tests/main/interface-tunnel/.workshop.yaml.in index d03660eb1..3359883f1 100644 --- a/tests/main/interface-tunnel/.workshop.yaml +++ b/tests/main/interface-tunnel/.workshop.yaml.in @@ -23,7 +23,7 @@ sdks: unused: interface: tunnel - name: test-sdk-tunnel - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} connections: - plug: system:test-sdk-http slot: test-sdk-tunnel:http diff --git a/tests/main/interface-tunnel/task.yaml b/tests/main/interface-tunnel/task.yaml index c795314f9..354aa2686 100644 --- a/tests/main/interface-tunnel/task.yaml +++ b/tests/main/interface-tunnel/task.yaml @@ -1,6 +1,7 @@ summary: Check tunnel interface scenarios prepare: | . "$TESTSLIB"/utils.sh + resolve_branch .workshop.yaml workshop_exec launch ws-tunnel restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/launch-abort/task.yaml b/tests/main/launch-abort/task.yaml index 415c7ed06..80ea66293 100644 --- a/tests/main/launch-abort/task.yaml +++ b/tests/main/launch-abort/task.yaml @@ -2,6 +2,7 @@ summary: Check a failed non-transactional launch can be aborted successfully execute: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.setup-fail.yaml cp workshop.setup-fail.yaml workshop.yaml workshop_exec launch --wait-on-error || true diff --git a/tests/main/launch-abort/workshop.setup-fail.yaml b/tests/main/launch-abort/workshop.setup-fail.yaml.in similarity index 63% rename from tests/main/launch-abort/workshop.setup-fail.yaml rename to tests/main/launch-abort/workshop.setup-fail.yaml.in index e0ffe1ff6..cccc7286e 100644 --- a/tests/main/launch-abort/workshop.setup-fail.yaml +++ b/tests/main/launch-abort/workshop.setup-fail.yaml.in @@ -2,4 +2,4 @@ name: ws-launch-abort base: ubuntu@22.04 sdks: - name: test-sdk-setup-fail - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/launch-continue/task.yaml b/tests/main/launch-continue/task.yaml index 6d9e3b958..0eb041d69 100644 --- a/tests/main/launch-continue/task.yaml +++ b/tests/main/launch-continue/task.yaml @@ -5,6 +5,7 @@ restore: | execute: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.yaml workshop_exec launch --wait-on-error || true echo "Check the workshop is in Waiting state" diff --git a/tests/main/launch-continue/workshop.yaml b/tests/main/launch-continue/workshop.yaml.in similarity index 62% rename from tests/main/launch-continue/workshop.yaml rename to tests/main/launch-continue/workshop.yaml.in index 421d9ebf3..bb1cf5894 100644 --- a/tests/main/launch-continue/workshop.yaml +++ b/tests/main/launch-continue/workshop.yaml.in @@ -2,4 +2,4 @@ name: ws-launch-cont base: ubuntu@22.04 sdks: - name: test-sdk-setup-fail - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/launch-multiple/.workshop/ws-one.yaml b/tests/main/launch-multiple/.workshop/ws-one.yaml.in similarity index 58% rename from tests/main/launch-multiple/.workshop/ws-one.yaml rename to tests/main/launch-multiple/.workshop/ws-one.yaml.in index f56eb5a75..b8a801f9e 100644 --- a/tests/main/launch-multiple/.workshop/ws-one.yaml +++ b/tests/main/launch-multiple/.workshop/ws-one.yaml.in @@ -2,4 +2,4 @@ name: ws-one base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/launch-multiple/.workshop/ws-three.yaml b/tests/main/launch-multiple/.workshop/ws-three.yaml.in similarity index 59% rename from tests/main/launch-multiple/.workshop/ws-three.yaml rename to tests/main/launch-multiple/.workshop/ws-three.yaml.in index 34d7a0490..0fddaf8d6 100644 --- a/tests/main/launch-multiple/.workshop/ws-three.yaml +++ b/tests/main/launch-multiple/.workshop/ws-three.yaml.in @@ -2,4 +2,4 @@ name: ws-three base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/launch-multiple/.workshop/ws-two.yaml b/tests/main/launch-multiple/.workshop/ws-two.yaml.in similarity index 58% rename from tests/main/launch-multiple/.workshop/ws-two.yaml rename to tests/main/launch-multiple/.workshop/ws-two.yaml.in index 6cff673cf..12117905d 100644 --- a/tests/main/launch-multiple/.workshop/ws-two.yaml +++ b/tests/main/launch-multiple/.workshop/ws-two.yaml.in @@ -2,4 +2,4 @@ name: ws-two base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/launch-multiple/task.yaml b/tests/main/launch-multiple/task.yaml index ee8b2cf37..4e7381fb5 100644 --- a/tests/main/launch-multiple/task.yaml +++ b/tests/main/launch-multiple/task.yaml @@ -3,6 +3,7 @@ execute: | . "$TESTSLIB"/utils.sh echo "Check workshop can be launched with a basic SDK" + resolve_branch .workshop/ws-one.yaml .workshop/ws-two.yaml .workshop/ws-three.yaml workshop_exec launch ws-one ws-two ws-three workshop_exec list | MATCH "ws-one[[:space:]]*Ready[[:space:]]*-" workshop_exec list | MATCH "ws-two[[:space:]]*Ready[[:space:]]*-" diff --git a/tests/main/launch-sdk/task.yaml b/tests/main/launch-sdk/task.yaml index 5b8b5b70d..5258cd3a6 100644 --- a/tests/main/launch-sdk/task.yaml +++ b/tests/main/launch-sdk/task.yaml @@ -3,6 +3,7 @@ execute: | . "$TESTSLIB"/utils.sh echo "Check workshop can be launched with a basic SDK" + resolve_branch workshop.yaml workshop_exec launch --verbose workshop_exec list | MATCH "ws-basic[[:space:]]*Ready[[:space:]]*-" diff --git a/tests/main/launch-sdk/workshop.yaml b/tests/main/launch-sdk/workshop.yaml.in similarity index 59% rename from tests/main/launch-sdk/workshop.yaml rename to tests/main/launch-sdk/workshop.yaml.in index c67e35bfe..8ae49d1ef 100644 --- a/tests/main/launch-sdk/workshop.yaml +++ b/tests/main/launch-sdk/workshop.yaml.in @@ -2,4 +2,4 @@ name: ws-basic base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/refresh-abort/task.yaml b/tests/main/refresh-abort/task.yaml index c20105689..c4527e567 100644 --- a/tests/main/refresh-abort/task.yaml +++ b/tests/main/refresh-abort/task.yaml @@ -1,6 +1,7 @@ summary: Check a failed non-transactional refresh can be aborted successfully prepare: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/refresh-abort/workshop.yaml b/tests/main/refresh-abort/workshop.yaml.in similarity index 61% rename from tests/main/refresh-abort/workshop.yaml rename to tests/main/refresh-abort/workshop.yaml.in index ee139651f..3d0e8d7f7 100644 --- a/tests/main/refresh-abort/workshop.yaml +++ b/tests/main/refresh-abort/workshop.yaml.in @@ -2,4 +2,4 @@ name: ws-refresh-abort base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/refresh-continue/task.yaml b/tests/main/refresh-continue/task.yaml index 0044aef30..0c3a1ab5b 100644 --- a/tests/main/refresh-continue/task.yaml +++ b/tests/main/refresh-continue/task.yaml @@ -1,6 +1,7 @@ summary: Check a failed refresh can be fixed and continued successfully prepare: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/refresh-continue/workshop.yaml b/tests/main/refresh-continue/workshop.yaml.in similarity index 61% rename from tests/main/refresh-continue/workshop.yaml rename to tests/main/refresh-continue/workshop.yaml.in index 969e7b29b..b0af9c7a1 100644 --- a/tests/main/refresh-continue/workshop.yaml +++ b/tests/main/refresh-continue/workshop.yaml.in @@ -2,4 +2,4 @@ name: ws-refresh-cont base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/refresh/multi/.workshop/ws-refresh-sdk.yaml b/tests/main/refresh/multi/.workshop/ws-refresh-sdk.yaml.in similarity index 61% rename from tests/main/refresh/multi/.workshop/ws-refresh-sdk.yaml rename to tests/main/refresh/multi/.workshop/ws-refresh-sdk.yaml.in index 39d1c3ece..c3781226e 100644 --- a/tests/main/refresh/multi/.workshop/ws-refresh-sdk.yaml +++ b/tests/main/refresh/multi/.workshop/ws-refresh-sdk.yaml.in @@ -2,4 +2,4 @@ name: ws-refresh-sdk base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml b/tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml deleted file mode 100644 index a446c8894..000000000 --- a/tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: ws-refresh-snapshots -base: ubuntu@22.04 -sdks: - - name: test-sdk-basic - channel: latest/stable - - name: test-sdk-mount - channel: latest/stable - - name: test-sdk-gpu - channel: latest/stable diff --git a/tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml.in b/tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml.in new file mode 100644 index 000000000..9c5c27121 --- /dev/null +++ b/tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml.in @@ -0,0 +1,9 @@ +name: ws-refresh-snapshots +base: ubuntu@22.04 +sdks: + - name: test-sdk-basic + channel: latest/stable${TEST_SDK_BRANCH} + - name: test-sdk-mount + channel: latest/stable${TEST_SDK_BRANCH} + - name: test-sdk-gpu + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/refresh/task.yaml b/tests/main/refresh/task.yaml index 4abcf12dc..a667bedd6 100644 --- a/tests/main/refresh/task.yaml +++ b/tests/main/refresh/task.yaml @@ -3,6 +3,7 @@ prepare: | . "$TESTSLIB"/utils.sh apt-add-repository -y universe apt install expect -y --no-install-recommends + resolve_branch multi/.workshop/ws-refresh-sdk.yaml multi/.workshop/ws-refresh-snapshots.yaml workshop_exec launch --project single workshop_exec launch ws-refresh ws-refresh-sdk ws-refresh-snapshots --project multi restore: | @@ -25,7 +26,7 @@ execute: | workshop_exec exec -- sudo test -f /var/cache/apt/archives/sentinel echo "Check workshop refresh --no-wait" - echo -e "sdks:\n - name: test-sdk-health-waiting\n channel: latest/stable" >> workshop.yaml + echo -e "sdks:\n - name: test-sdk-health-waiting\n channel: latest/stable${TEST_SDK_BRANCH}" >> workshop.yaml workshop_exec refresh --no-wait # The refresh process here takes about 10s. diff --git a/tests/main/remount/task.yaml b/tests/main/remount/task.yaml index 1c9852192..43db2ae77 100644 --- a/tests/main/remount/task.yaml +++ b/tests/main/remount/task.yaml @@ -7,6 +7,7 @@ restore: | execute: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.yaml workshop_exec launch echo "Remount: the new source is empty or does not exist" new_source="/home/ubuntu/one" diff --git a/tests/main/remount/workshop.yaml b/tests/main/remount/workshop.yaml.in similarity index 59% rename from tests/main/remount/workshop.yaml rename to tests/main/remount/workshop.yaml.in index af9e748da..38ca87cdb 100644 --- a/tests/main/remount/workshop.yaml +++ b/tests/main/remount/workshop.yaml.in @@ -2,4 +2,4 @@ name: ws-remount base: ubuntu@22.04 sdks: - name: test-sdk-mount - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2.yaml b/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2.yaml.in similarity index 61% rename from tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2.yaml rename to tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2.yaml.in index 7816dde5c..10b54633d 100644 --- a/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2.yaml +++ b/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2.yaml.in @@ -2,4 +2,4 @@ name: ws-snap-remove-2 base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2a.yaml b/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2a.yaml.in similarity index 62% rename from tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2a.yaml rename to tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2a.yaml.in index e71ef5f23..f101690bc 100644 --- a/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2a.yaml +++ b/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2a.yaml.in @@ -2,4 +2,4 @@ name: ws-snap-remove-2a base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/remove-snap/task.yaml b/tests/main/remove-snap/task.yaml index e928e691e..19124eda0 100644 --- a/tests/main/remove-snap/task.yaml +++ b/tests/main/remove-snap/task.yaml @@ -9,6 +9,7 @@ execute: | # shellcheck source=tests/lib/utils.sh . "$TESTSLIB"/utils.sh + resolve_branch project-2/.workshop/ws-snap-remove-2.yaml project-2/.workshop/ws-snap-remove-2a.yaml workshop_exec launch --project $(pwd)/project-1 ws-snap-remove-1 ws-snap-remove-1a workshop_exec launch --project $(pwd)/project-2 ws-snap-remove-2 ws-snap-remove-2a diff --git a/tests/main/remove/.workshop/ws-remove-mount.yaml b/tests/main/remove/.workshop/ws-remove-mount.yaml.in similarity index 61% rename from tests/main/remove/.workshop/ws-remove-mount.yaml rename to tests/main/remove/.workshop/ws-remove-mount.yaml.in index 523ec7fdb..7562329de 100644 --- a/tests/main/remove/.workshop/ws-remove-mount.yaml +++ b/tests/main/remove/.workshop/ws-remove-mount.yaml.in @@ -2,4 +2,4 @@ name: ws-remove-mount base: ubuntu@22.04 sdks: - name: test-sdk-mount - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/remove/task.yaml b/tests/main/remove/task.yaml index d56af1136..72e6b5e08 100644 --- a/tests/main/remove/task.yaml +++ b/tests/main/remove/task.yaml @@ -8,6 +8,7 @@ execute: | . "$TESTSLIB"/utils.sh echo "Launch a workshop" + resolve_branch .workshop/ws-remove-mount.yaml workshop_exec launch ws-remove echo "Modify apt cache for later" @@ -36,11 +37,11 @@ execute: | cat <> .workshop/ws-remove.yaml sdks: - name: test-sdk-setup-fail - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} EOF ! workshop_exec refresh --wait-on-error ws-remove || false workshop_exec remove ws-remove - sed -i '/^sdks:$/,/^ channel: latest\/stable$/d' .workshop/ws-remove.yaml + sed -i '/^sdks:$/,/^ channel: latest\/stable.*$/d' .workshop/ws-remove.yaml workshop_exec list | MATCH "^ws-remove[[:space:]]+Off[[:space:]]+-$" echo "Check the workshop can be removed if in Error" diff --git a/tests/main/sdk-info/.workshop/edge.yaml b/tests/main/sdk-info/.workshop/edge.yaml.in similarity index 62% rename from tests/main/sdk-info/.workshop/edge.yaml rename to tests/main/sdk-info/.workshop/edge.yaml.in index 8ea72f6fa..8277ed2d3 100644 --- a/tests/main/sdk-info/.workshop/edge.yaml +++ b/tests/main/sdk-info/.workshop/edge.yaml.in @@ -2,4 +2,4 @@ name: edge base: ubuntu@22.04 sdks: - name: test-sdk-info - channel: latest/edge + channel: edge${TEST_SDK_BRANCH} diff --git a/tests/main/sdk-info/.workshop/stable.yaml b/tests/main/sdk-info/.workshop/stable.yaml.in similarity index 62% rename from tests/main/sdk-info/.workshop/stable.yaml rename to tests/main/sdk-info/.workshop/stable.yaml.in index b5e914407..a54983bd0 100644 --- a/tests/main/sdk-info/.workshop/stable.yaml +++ b/tests/main/sdk-info/.workshop/stable.yaml.in @@ -2,4 +2,4 @@ name: stable base: ubuntu@22.04 sdks: - name: test-sdk-info - channel: latest/stable + channel: stable${TEST_SDK_BRANCH} diff --git a/tests/main/sdk-info/task.yaml b/tests/main/sdk-info/task.yaml index 6e9cf5a0b..c626e20c2 100644 --- a/tests/main/sdk-info/task.yaml +++ b/tests/main/sdk-info/task.yaml @@ -24,6 +24,7 @@ execute: | MATCH '[[:space:]]+latest/edge[[:space:]]+2\.0[[:space:]]+[0-9]{4}-[0-9]{2}-[0-9]{2}[[:space:]]+all[[:space:]]+[0-9]+[[:space:]]+[0-9]+B' < info.log echo "Test SDK info with one volume" + resolve_branch .workshop/edge.yaml .workshop/stable.yaml workshop_exec launch edge sdk_exec list | MATCH '^test-sdk-info[[:space:]]+2\.0[[:space:]]+[[:digit:]]+[[:space:]]+.*kB$' diff --git a/tests/main/shell/task.yaml b/tests/main/shell/task.yaml index 34de271d7..ce608d97d 100644 --- a/tests/main/shell/task.yaml +++ b/tests/main/shell/task.yaml @@ -1,6 +1,7 @@ summary: Check shell functionality and environment prepare: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.yaml workshop_exec launch ws-shell restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/shell/workshop.yaml b/tests/main/shell/workshop.yaml.in similarity index 59% rename from tests/main/shell/workshop.yaml rename to tests/main/shell/workshop.yaml.in index 137527081..0d7b72c8c 100644 --- a/tests/main/shell/workshop.yaml +++ b/tests/main/shell/workshop.yaml.in @@ -2,4 +2,4 @@ name: ws-shell base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/sketch-sdk/task.yaml b/tests/main/sketch-sdk/task.yaml index 885e81249..3a7c5749c 100644 --- a/tests/main/sketch-sdk/task.yaml +++ b/tests/main/sketch-sdk/task.yaml @@ -1,6 +1,7 @@ summary: Check workshop sketch-sdk prepare: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/sketch-sdk/workshop.yaml b/tests/main/sketch-sdk/workshop.yaml.in similarity index 59% rename from tests/main/sketch-sdk/workshop.yaml rename to tests/main/sketch-sdk/workshop.yaml.in index ab7f66f5d..6b7d62836 100644 --- a/tests/main/sketch-sdk/workshop.yaml +++ b/tests/main/sketch-sdk/workshop.yaml.in @@ -2,4 +2,4 @@ name: ws-sketch base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/start/task.yaml b/tests/main/start/task.yaml index 5d991529b..f5ab44ab6 100644 --- a/tests/main/start/task.yaml +++ b/tests/main/start/task.yaml @@ -1,6 +1,7 @@ summary: Check workshop start prepare: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/start/workshop.yaml b/tests/main/start/workshop.yaml.in similarity index 59% rename from tests/main/start/workshop.yaml rename to tests/main/start/workshop.yaml.in index f3c47d378..59fa1cd0b 100644 --- a/tests/main/start/workshop.yaml +++ b/tests/main/start/workshop.yaml.in @@ -2,4 +2,4 @@ name: ws-start base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/stop/task.yaml b/tests/main/stop/task.yaml index 6f664d671..9f167a52a 100644 --- a/tests/main/stop/task.yaml +++ b/tests/main/stop/task.yaml @@ -1,6 +1,7 @@ summary: Check workshop stop prepare: | . "$TESTSLIB"/utils.sh + resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/stop/workshop.yaml b/tests/main/stop/workshop.yaml.in similarity index 58% rename from tests/main/stop/workshop.yaml rename to tests/main/stop/workshop.yaml.in index 43188a43b..7488eca53 100644 --- a/tests/main/stop/workshop.yaml +++ b/tests/main/stop/workshop.yaml.in @@ -2,4 +2,4 @@ name: ws-stop base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/workshop-connections/.workshop/ws-user-conns.yaml b/tests/main/workshop-connections/.workshop/ws-user-conns.yaml.in similarity index 82% rename from tests/main/workshop-connections/.workshop/ws-user-conns.yaml rename to tests/main/workshop-connections/.workshop/ws-user-conns.yaml.in index 179fba60b..befd841b6 100644 --- a/tests/main/workshop-connections/.workshop/ws-user-conns.yaml +++ b/tests/main/workshop-connections/.workshop/ws-user-conns.yaml.in @@ -2,13 +2,13 @@ name: ws-user-conns base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} slots: user-slot: interface: mount workshop-source: /project/user-data-2 - name: test-sdk-mount - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} plugs: wp-plug: interface: mount diff --git a/tests/main/workshop-connections/.workshop/ws-user-conns.yaml.new b/tests/main/workshop-connections/.workshop/ws-user-conns.yaml.new.in similarity index 75% rename from tests/main/workshop-connections/.workshop/ws-user-conns.yaml.new rename to tests/main/workshop-connections/.workshop/ws-user-conns.yaml.new.in index 0ca095148..83f947b54 100644 --- a/tests/main/workshop-connections/.workshop/ws-user-conns.yaml.new +++ b/tests/main/workshop-connections/.workshop/ws-user-conns.yaml.new.in @@ -2,13 +2,13 @@ name: ws-user-conns base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} slots: user-slot: interface: mount workshop-source: /project/user-data-2 - name: test-sdk-mount - channel: latest/stable + channel: latest/stable${TEST_SDK_BRANCH} connections: - plug: test-sdk-mount:two slot: test-sdk-basic:user-slot diff --git a/tests/main/workshop-connections/task.yaml b/tests/main/workshop-connections/task.yaml index 4c0bcf6c4..147f4ab55 100644 --- a/tests/main/workshop-connections/task.yaml +++ b/tests/main/workshop-connections/task.yaml @@ -2,6 +2,7 @@ summary: Check that "workshop connections" works as expected prepare: | . "$TESTSLIB"/utils.sh mkdir user-data user-data-2 + resolve_branch .workshop/ws-user-conns.yaml .workshop/ws-user-conns.yaml.new workshop_exec launch restore: | . "$TESTSLIB"/utils.sh From b87336680e3c4501fe772cbea967d610f0e3a9da Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Thu, 2 Apr 2026 12:38:51 +1300 Subject: [PATCH 12/17] Remove SDK Store preview disclaimer --- cmd/sdk/info.go | 2 +- cmd/sdk/info_test.go | 10 +++++----- tests/main/sdk-info/header.txt | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/sdk/info.go b/cmd/sdk/info.go index 21744e6d7..e9523fae9 100644 --- a/cmd/sdk/info.go +++ b/cmd/sdk/info.go @@ -118,7 +118,7 @@ func (c *CmdInfo) Run(cmd *cobra.Command, av []string) error { tpl := " %s\t%s\t%s\t" + baseTpl + archTpl + "%*s\t%*s\n" if len(tracks) > 0 { fmt.Fprintln(w) - fmt.Fprintf(w, "%s%s%s (%s%s%s: %s)\n", esc.Bold, "CHANNELS", esc.End, esc.BrightYellow, "SDK Store preview", esc.End, "Workshop won't see these revisions yet") + fmt.Fprintf(w, "%s%s%s\n", esc.Bold, "CHANNELS", esc.End) fmt.Fprintf(w, tpl, "CHANNEL", "VERSION", "BUILD", "BASE", "ARCH", maxRev, "REV", maxSize, "SIZE") } for _, track := range tracks { diff --git a/cmd/sdk/info_test.go b/cmd/sdk/info_test.go index 86f3298ea..b3ced8cf3 100644 --- a/cmd/sdk/info_test.go +++ b/cmd/sdk/info_test.go @@ -190,7 +190,7 @@ license: Apache-2.0 Longer description can be multiline. -CHANNELS (SDK Store preview: Workshop won't see these revisions yet) +CHANNELS CHANNEL VERSION BUILD BASE ARCH REV SIZE latest/stable 2.1-084c8c8 2024-11-25 ubuntu@22.04 amd64 88 123.46kB arm64 89 1.23MB @@ -223,7 +223,7 @@ license: Apache-2.0 Longer description can be multiline. -CHANNELS (SDK Store preview: Workshop won't see these revisions yet) +CHANNELS CHANNEL VERSION BUILD BASE REV SIZE latest/stable 2.1-084c8c8 2024-11-25 ubuntu@22.04 88 123.46kB ubuntu@20.04 85 123B @@ -253,7 +253,7 @@ license: Apache-2.0 Longer description can be multiline. -CHANNELS (SDK Store preview: Workshop won't see these revisions yet) +CHANNELS CHANNEL VERSION BUILD BASE REV SIZE latest/stable 2.2-c8c8084 2024-11-27 ubuntu@22.04 90 12.35MB 2.1-084c8c8 2024-11-25 ubuntu@20.04 87 12.35kB @@ -280,7 +280,7 @@ license: Apache-2.0 Longer description can be multiline. -CHANNELS (SDK Store preview: Workshop won't see these revisions yet) +CHANNELS CHANNEL VERSION BUILD ARCH REV SIZE latest/stable 2.1-084c8c8 2024-11-25 amd64 88 123.46kB arm64 89 1.23MB @@ -309,7 +309,7 @@ license: Apache-2.0 Longer description can be multiline. -CHANNELS (SDK Store preview: Workshop won't see these revisions yet) +CHANNELS CHANNEL VERSION BUILD REV SIZE latest/stable 2.1-084c8c8 2024-11-25 85 123B latest/candidate ^ diff --git a/tests/main/sdk-info/header.txt b/tests/main/sdk-info/header.txt index de9d278b7..617959040 100644 --- a/tests/main/sdk-info/header.txt +++ b/tests/main/sdk-info/header.txt @@ -6,5 +6,5 @@ This is test-sdk-info's description. You have a paragraph or two to tell the most important story about it. Keep it under 100 words though, we live in tweetspace. -CHANNELS (SDK Store preview: Workshop won't see these revisions yet) +CHANNELS CHANNEL VERSION BUILD BASE REV SIZE From f44b78c0e80bcec37b84c6c4b5c500a5d6fdb909 Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Wed, 8 Apr 2026 11:17:54 +1200 Subject: [PATCH 13/17] Remove GCS Store --- .spread.yaml | 10 +- .workshop/dev.yaml | 6 - .workshop/tools/hooks/check-health | 5 - .workshop/tools/hooks/setup-base | 1 - docs/.custom_wordlist.txt | 1 - docs/contributing.rst | 99 ------- internal/gcsstore/export_test.go | 28 -- internal/gcsstore/integration_test.go | 197 ------------- internal/gcsstore/store.go | 407 -------------------------- internal/gcsstore/store_test.go | 94 ------ internal/overlord/overlord.go | 4 - internal/sdk/store.go | 116 -------- snap/local/commands/run_daemon | 3 - tests/integration/gcs-store/task.yaml | 17 -- tests/lib/utils.sh | 36 --- tests/main/autostart/task.yaml | 2 - 16 files changed, 4 insertions(+), 1022 deletions(-) delete mode 100644 internal/gcsstore/export_test.go delete mode 100644 internal/gcsstore/integration_test.go delete mode 100644 internal/gcsstore/store.go delete mode 100644 internal/gcsstore/store_test.go delete mode 100644 tests/integration/gcs-store/task.yaml diff --git a/.spread.yaml b/.spread.yaml index ef287393b..032eecdde 100644 --- a/.spread.yaml +++ b/.spread.yaml @@ -6,9 +6,7 @@ environment: PATH: $PATH:~/go/bin PROJECT_PATH: /workshop TESTSLIB: $PROJECT_PATH/tests/lib - TESTS_SDKS: $PROJECT_PATH/tests/lib/sdk TEST_SDK_BRANCH: $(HOST:printf %s "${TEST_SDK_BRANCH:+/$TEST_SDK_BRANCH}") - SDK_STORE_BUCKET_DIR: /data/sdkstore IMAGE_SERVER: $(HOST:echo -n $WORKSHOP_IMAGE_SERVER) repack: | test -f tests/workshop_*.snap || snapcraft pack -o tests/ @@ -49,13 +47,13 @@ suites: prepare: | . "$TESTSLIB"/utils.sh prepare_environment - setup_workshop true # use real store + setup_workshop install_sdkcraft prepare-each: | date --rfc-3339=seconds > /tmp/spread-job-start restore: | . "$TESTSLIB"/utils.sh - cleanup_workshop true # used real store, no need to clean it + cleanup_workshop debug-each: | journalctl --since="$( /tmp/spread-job-start restore: | . "$TESTSLIB"/utils.sh - cleanup_workshop true # used real store, no need to clean it + cleanup_workshop debug-each: | journalctl --since="$( /dev/null; then - workshopctl set-health error "fake-gcs-server is not installed" - exit 1 -fi - workshopctl set-health okay diff --git a/.workshop/tools/hooks/setup-base b/.workshop/tools/hooks/setup-base index 8c4fe88db..9047394b7 100755 --- a/.workshop/tools/hooks/setup-base +++ b/.workshop/tools/hooks/setup-base @@ -1,6 +1,5 @@ apt-get update apt-get install make python3-venv shellcheck -curl -L https://github.com/fsouza/fake-gcs-server/releases/download/v1.52.3/fake-gcs-server_1.52.3_Linux_amd64.tar.gz | tar -xz -C /usr/local/bin fake-gcs-server curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b /usr/local/bin v2.11.3 git clone --depth 1 https://github.com/canonical/snapd-testing-tools.git /opt/snapd-testing-tools diff --git a/docs/.custom_wordlist.txt b/docs/.custom_wordlist.txt index 54598cb9b..7c321be27 100644 --- a/docs/.custom_wordlist.txt +++ b/docs/.custom_wordlist.txt @@ -51,7 +51,6 @@ fixup fmt func GCP -gcs Gencodo gendocs GH diff --git a/docs/contributing.rst b/docs/contributing.rst index 33a2217eb..fb7b28c8d 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -301,105 +301,6 @@ This can be accomplished by using the `-artifacts` flag when running `spread`. spread -artifacts= tests/integration/ -How to run a local SDK Store ----------------------------- - -To test SDKs with Workshop locally without publishing, -it is possible to run a local instance of SDK Store. -This guide uses the open-source `fake-gcs-server `_. - -.. note:: - - This guide assumes you're familiar with SDKcraft: - see the :ref:`tut_craft_sdks` tutorial section for details. - - - -Create the directory structure -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -SDK Store relies on a directory structure -to determine SDK names and channels. -Therefore, when running a store locally, -the directory structure must mirror that of the real store. - -The 'fake store' directory can be named as preferred; -however, the remainder of the structure and naming convention is mandatory. - -.. code-block:: console - - mkdir -p fake-store/sdkstore/// - -Here: - -- ```` is the SDK name (e.g., ``my-sdk``) - -- ```` is the SDK release (e.g., ``latest``) - -- ```` is the SDK channel (e.g., ``edge``) - - -Copy the SDK -~~~~~~~~~~~~ - -Place the SDK files in the deepest directory from the previous step -(e.g., ``fake-store/sdkstore/my-sdk/latest/edge/my-sdk/``). -Rename the SDK definition (e.g., ``my-sdk.yaml``) to ``sdk.yaml`` -and place it at the same nesting level: - -.. code-block:: console - - ls fake-store/sdkstore/my-sdk/latest/edge - - my-sdk.sdk sdk.yaml - - -Run the local store -~~~~~~~~~~~~~~~~~~~ - -Pass the top-level SDK store directory to this ``go run`` command: - -.. code-block:: console - - go run github.com/fsouza/fake-gcs-server@latest \ - -data fake-store/ \ - -filesystem-root fake-store/ \ - -scheme http -port 8080 \ - -public-host localhost:8080 - - time=1990-01-01T00:00:00.000+00.00 level=INFO msg="server started at http://0.0.0.0:8080" - - -Use the local store with Workshop -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To override the URL that Workshop uses to connect to SDK Store, -configure the Workshop snap -with the address from ``-public-host`` in the step above, -adding ``/storage/v1/`` as the path: - -.. code-block:: console - - sudo snap set workshop gcs.url=http://localhost:8080/storage/v1/ - sudo snap restart workshop - - -Workshop will now use the local store. - - -Revert changes -~~~~~~~~~~~~~~ - -To go back to the default store: - -.. code-block:: console - - sudo snap set workshop gcs.url="" - - -Workshop will now use the default URL. - - Releases -------- diff --git a/internal/gcsstore/export_test.go b/internal/gcsstore/export_test.go deleted file mode 100644 index e6c6ef4f6..000000000 --- a/internal/gcsstore/export_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package gcsstore - -import ( - "context" - - "github.com/canonical/workshop/internal/sdk" -) - -type ( - StoreSdk = storeSdk - SdkReader = sdkReader -) - -func FakeSdkStoreInfo(f func(ctx context.Context, name, channel string) (StoreSdk, error)) (restore func()) { - old := storeSdkInfo - storeSdkInfo = f - return func() { - storeSdkInfo = old - } -} - -func FakeSdkStoreSdkReader(f func(ctx context.Context, setup sdk.Setup) (*SdkReader, error)) (restore func()) { - old := storeSdkReader - storeSdkReader = f - return func() { - storeSdkReader = old - } -} diff --git a/internal/gcsstore/integration_test.go b/internal/gcsstore/integration_test.go deleted file mode 100644 index 40d6bce27..000000000 --- a/internal/gcsstore/integration_test.go +++ /dev/null @@ -1,197 +0,0 @@ -//go:build integration - -package gcsstore_test - -import ( - "context" - "fmt" - "os" - "sync" - - "gopkg.in/check.v1" - - "github.com/canonical/workshop/internal/dirs" - "github.com/canonical/workshop/internal/gcsstore" - "github.com/canonical/workshop/internal/logger" - "github.com/canonical/workshop/internal/progress" - "github.com/canonical/workshop/internal/sdk" - "github.com/canonical/workshop/internal/testutil" -) - -type storeIntegration struct { - oldRoot string - oldCache string -} - -var _ = check.Suite(&storeIntegration{}) - -func (f *storeIntegration) SetUpTest(c *check.C) { - c.Assert(os.Setenv("GCS_STORE_URL", "http://localhost:8080/storage/v1/"), check.IsNil) - f.oldRoot = dirs.BaseDir - f.oldCache = dirs.CacheDir - dirs.SetRootDir(c.MkDir()) - dirs.SetCacheDir(c.MkDir()) - c.Assert(dirs.CreateDirs(), check.IsNil) -} - -func (f *storeIntegration) TearDownTest(c *check.C) { - c.Assert(os.Unsetenv("GCS_STORE_URL"), check.IsNil) - dirs.SetCacheDir(f.oldCache) - dirs.SetRootDir(f.oldRoot) -} - -func (f *storeIntegration) TestStoreDownloadOK(c *check.C) { - s := gcsstore.New() - setup := sdk.Setup{Name: "test-sdk-basic", Channel: "latest/stable", Revision: sdk.R(1)} - result, err := s.DownloadSdk(context.Background(), setup, nil) - c.Assert(err, check.IsNil) - setup.Revision = result.Revision - setup.Sha3_384 = result.Sha3_384 - c.Check(result.Setup, check.Equals, setup) - c.Check(result.Revision, check.Not(check.Equals), sdk.R(0)) - c.Check(result.Sha3_384, check.Not(check.Equals), "") - c.Check(result.SdkYAML, check.Not(check.Equals), "") - c.Assert(result.Filepath(), testutil.FilePresent) -} - -func (f *storeIntegration) TestStoreDownloadProgressReport(c *check.C) { - s := gcsstore.New() - setup := sdk.Setup{Name: "test-sdk-basic", Channel: "latest/stable", Revision: sdk.R(1)} - var done, total int64 - r := &progress.Reporter{Name: "1", Report: func(label string, d, t int64) { - done = d - total = t - }} - result, err := s.DownloadSdk(context.Background(), setup, r) - c.Assert(err, check.IsNil) - c.Assert(result.Filepath(), testutil.FilePresent) - c.Check(int(done), testutil.IntGreaterThan, 0) - c.Check(int(total), testutil.IntGreaterThan, 0) - c.Check(done, check.Equals, total) -} - -func (f *storeIntegration) TestStoreDownloadCleanupPrevious(c *check.C) { - s := gcsstore.New() - setup := sdk.Setup{Name: "test-sdk-basic", Channel: "latest/stable", Revision: sdk.R(1)} - prev := setup - prev.Revision = sdk.R(5) - c.Assert(os.WriteFile(prev.Filepath(), nil, 0644), check.IsNil) - other := setup - other.Revision = sdk.R(2) - c.Assert(os.WriteFile(other.Filepath(), nil, 0644), check.IsNil) - - result, err := s.DownloadSdk(context.Background(), setup, nil) - c.Assert(err, check.IsNil) - c.Check(result.Filepath(), testutil.FilePresent) - if prev.Revision != result.Revision { - c.Check(prev.Filepath(), testutil.FileAbsent) - } - if other.Revision != result.Revision { - c.Check(other.Filepath(), testutil.FileAbsent) - } -} - -func (f *storeIntegration) TestStoreDownloadNotfound(c *check.C) { - s := gcsstore.New() - setup := sdk.Setup{Name: "test-sdk-unknown", Channel: "latest/stable", Revision: sdk.R(1)} - _, err := s.DownloadSdk(context.Background(), setup, nil) - c.Assert(err, check.ErrorMatches, `SDK not found in "latest/stable"`) - c.Check(dirs.SdkDownloads, testutil.DirEquals, []string{}) -} - -func (f *storeIntegration) TestStoreDownloadLocksSDKForExclusiveAccess(c *check.C) { - os.Setenv("WORKSHOP_DEBUG", "1") - defer os.Unsetenv("WORKSHOP_DEBUG") - - m, r := logger.MockLogger() - defer r() - - s := gcsstore.New() - setup := sdk.Setup{ - Name: "test-sdk-basic", - Channel: "latest/stable", - Revision: sdk.Revision{N: 1}, - Sha3_384: "19133c4b2d9157b9edf9128e583245a621f172725abb5965282c8b2d9dfa60eae592d08cb0a0a5d48205ce8f25d2be91", - } - - // Lock the file to emulate a concurrent download is going on - target := setup.Filepath() - fl, err := sdk.OpenLock(setup.Name) - c.Assert(err, check.IsNil) - c.Assert(fl.Lock(), check.IsNil) - - var wg sync.WaitGroup - wg.Go(func() { - result, err := s.DownloadSdk(context.Background(), setup, nil) - c.Assert(err, check.IsNil) - c.Check(result.Setup, check.Equals, setup) - c.Check(result.SdkYAML, check.Equals, "name: test-sdk-basic") - c.Check(m.String(), check.Matches, fmt.Sprintf(`(?s)*.DEBUG: SDK Store on Download: SDK "test-sdk-basic" found locally: %s/test-sdk-basic_1.sdk.*`, dirs.SdkDownloads)) - }) - - // "download" is finished - c.Assert(os.WriteFile(target, nil, 0666), check.IsNil) - c.Assert(os.WriteFile(target+".sha3-384", []byte(setup.Sha3_384), 0666), check.IsNil) - c.Assert(os.WriteFile(target+".yaml", []byte("name: test-sdk-basic"), 0666), check.IsNil) - fl.Close() - wg.Wait() -} - -type failingReader struct{} - -func (*failingReader) Read(p []byte) (n int, err error) { - return 0, context.DeadlineExceeded -} -func (*failingReader) Close() error { return nil } - -func (f *storeIntegration) TestStoreDownloadRemoveUnfinished(c *check.C) { - r := gcsstore.FakeSdkStoreSdkReader(func(ctx context.Context, setup sdk.Setup) (*gcsstore.SdkReader, error) { - return &gcsstore.SdkReader{ReadCloser: &failingReader{}, Revision: setup.Revision}, nil - }) - defer r() - - s := gcsstore.New() - setup := sdk.Setup{Name: "test-sdk-basic", Channel: "latest/stable", Revision: sdk.Revision{N: 55}} - _, err := s.DownloadSdk(context.Background(), setup, nil) - c.Assert(err, check.NotNil) - c.Assert(dirs.SdkDownloads, testutil.DirEquals, []string{}) -} - -func (s *storeIntegration) TestSdkActionInstallStoreError(c *check.C) { - defer sdk.MockSanitizePlugsSlots(func(sdkInfo *sdk.Info) {})() - - store := gcsstore.New() - acts := []sdk.SdkAction{{ - ProjectId: "24242424", - Workshop: "test-workshop", - Action: sdk.Install, - Name: "test-sdk-unknown", - Base: "ubuntu@22.04", - Channel: "latest/stable", - }} - res, err := store.SdkAction(context.Background(), acts) - c.Assert(res, check.HasLen, 0) - c.Assert(err, check.ErrorMatches, `(?s).*SDK not found in "latest/stable".*`) -} - -func (f *storeIntegration) TestSdkActionTimeout(c *check.C) { - defer sdk.MockSanitizePlugsSlots(func(sdkInfo *sdk.Info) {})() - - // Deliberately set to malformed address - c.Assert(os.Setenv("GCS_STORE_URL", "http://localhost:8181/storage/v1/"), check.IsNil) - s := gcsstore.New() - acts := []sdk.SdkAction{{ - ProjectId: "24242424", - Workshop: "test-workshop", - Action: sdk.Install, - Name: "test-sdk-unknown", - Base: "ubuntu@22.04", - Channel: "latest/stable", - }} - _, err := s.SdkAction(context.Background(), acts) - - // Restore address for remaining tests - c.Assert(os.Setenv("GCS_STORE_URL", "http://localhost:8080/storage/v1/"), check.IsNil) - - c.Assert(err, check.ErrorMatches, `(?s).*cannot connect to store.*`) -} diff --git a/internal/gcsstore/store.go b/internal/gcsstore/store.go deleted file mode 100644 index ccb47f061..000000000 --- a/internal/gcsstore/store.go +++ /dev/null @@ -1,407 +0,0 @@ -package gcsstore - -import ( - "bufio" - "bytes" - "context" - "crypto/md5" - "crypto/sha3" - "encoding/hex" - "errors" - "fmt" - "io" - "net" - "net/url" - "os" - "os/exec" - "path/filepath" - "strings" - "time" - - "cloud.google.com/go/storage" - "github.com/googleapis/gax-go/v2" - "google.golang.org/api/option" - - "github.com/canonical/workshop/internal/logger" - "github.com/canonical/workshop/internal/osutil" - "github.com/canonical/workshop/internal/progress" - "github.com/canonical/workshop/internal/revert" - "github.com/canonical/workshop/internal/sdk" -) - -var ( - ErrNoRefreshAvailable = errors.New("SDK has no update available") -) - -var ( - SDK_STORE_BUCKET_NAME = "sdkstore" - - storeSdkInfo = storeSdkInfoImpl - storeSdkReader = storeSdkReaderImpl -) - -func New() *GcsStore { - return &GcsStore{} -} - -type GcsStore struct { -} - -type storeSdk struct { - Name string `json:"name"` - Channel string `json:"channel"` - Revision sdk.Revision `json:"revision"` - SdkYAML string `json:"sdk-yaml"` - Sha3_384 string `json:"sha3-384"` -} - -type sdkReader struct { - io.ReadCloser - Revision sdk.Revision - Size int64 -} - -type SdkActionError struct { - // maps an SDK name to an error - errors map[string]error -} - -func (e SdkActionError) Error() string { - errorMsg := []string{"SDK store action failed:"} - - for name, e := range e.errors { - errorMsg = append(errorMsg, "- "+name+": "+e.Error()) - } - return strings.Join(errorMsg, "\n") -} - -type ClientWrapper struct { - *storage.Client - isTesting bool -} - -func (c *GcsStore) SdkAction(ctx context.Context, actions []sdk.SdkAction) ([]sdk.Meta, error) { - sdks := make([]sdk.Meta, 0, len(actions)) - actError := &SdkActionError{ - errors: make(map[string]error), - } - for _, act := range actions { - switch act.Action { - case sdk.Install: - s, err := storeSdkInfo(ctx, act.Name, act.Channel) - if err != nil { - actError.errors[act.Name] = err - continue - } - - setup := sdk.Setup{Name: s.Name, Channel: s.Channel, Revision: s.Revision, Sha3_384: s.Sha3_384} - sdks = append(sdks, sdk.Meta{Setup: setup, SdkYAML: s.SdkYAML}) - default: - return nil, fmt.Errorf("unknown SDK store action") - } - } - - if len(actError.errors) > 0 { - return sdks, actError - } - - return sdks, nil -} - -type reporterWriter struct { - r *progress.Reporter - done int64 - total int64 -} - -func (r *reporterWriter) Write(p []byte) (n int, err error) { - r.done += int64(len(p)) - r.r.Report("download", r.done, r.total) - return len(p), nil -} - -func (c *GcsStore) DownloadSdk(ctx context.Context, setup sdk.Setup, report *progress.Reporter) (*sdk.Meta, error) { - fl, err := sdk.OpenLock(setup.Name) - if err != nil { - return nil, err - } - if err = fl.Lock(); err != nil { - return nil, err - } - defer fl.Close() - - if osutil.FileExists(setup.Filepath()) { - logger.Debugf("SDK Store on Download: SDK %q found locally: %s", setup.Name, setup.Filepath()) - - // TODO: after a transition period, it should be safe to assume - // that the hash and metadata are stored next to the SDK file. - // Probably not worth changing the code since they will likely - // be pulled from the Store instead of computed client side. - setup.Sha3_384, err = hashSdk(setup) - if err != nil { - return nil, err - } - sdkYaml, err := extractSdkYAML(ctx, setup) - if err != nil { - return nil, err - } - - return &sdk.Meta{Setup: setup, SdkYAML: sdkYaml}, nil - } - - r, err := storeSdkReader(ctx, setup) - if err != nil { - return nil, err - } - defer r.Close() - - setup.Revision = r.Revision - target := setup.Filepath() - - reverter := revert.New() - defer reverter.Fail() - - // TODO: Use a temporary file to prevent corruption if the process is - // killed abruptly. - file, err := os.Create(target) - if err != nil { - return nil, err - } - defer file.Close() - reverter.Add(func() { - // Remove the target as due to the error it may be corrupted. - if err1 := os.Remove(target); err1 != nil { - logger.Noticef("SDK Store on Download: Cannot remove %q on a failed download: %v", target, err1) - } - }) - - hash := md5.New() - writers := []io.Writer{file, hash} - if report != nil { - writers = append(writers, &reporterWriter{r: report, total: r.Size}) - } - - if _, err = io.Copy(io.MultiWriter(writers...), r); err != nil { - return nil, err - } - - setup.Sha3_384 = md5ToSha3(hash.Sum(nil)) - if err := os.WriteFile(target+".sha3-384", []byte(setup.Sha3_384+"\n"), 0666); err != nil { - return nil, err - } - reverter.Add(func() { _ = os.Remove(target + ".sha3-384") }) - - sdkYaml, err := extractSdkYAML(ctx, setup) - if err != nil { - return nil, err - } - reverter.Add(func() { _ = os.Remove(target + ".yaml") }) - - reverter.Success() - // If the SDK was downloaded successfully, remove its previous rev if any. - matches, err := filepath.Glob(filepath.Join(filepath.Dir(target), setup.Name+"_*.sdk")) - if err != nil { - logger.Noticef("SDK Store on Download: Cannot cleanup previous downloads for %q: %v", setup.Name, err) - } - for _, m := range matches { - if m == target { - continue - } - if err := os.Remove(m); err != nil { - logger.Noticef("SDK Store on Download: Cannot cleanup previous download (%s): %v", m, err) - } - if err := os.Remove(m + ".sha3-384"); err != nil && !errors.Is(err, os.ErrNotExist) { - logger.Noticef("SDK Store on Download: Cannot cleanup previous download (%s): %v", m, err) - } - if err := os.Remove(m + ".yaml"); err != nil && !errors.Is(err, os.ErrNotExist) { - logger.Noticef("SDK Store on Download: Cannot cleanup previous download (%s): %v", m, err) - } - } - - return &sdk.Meta{Setup: setup, SdkYAML: sdkYaml}, nil -} - -func hashSdk(setup sdk.Setup) (string, error) { - target := setup.Filepath() - cache := target + ".sha3-384" - - content, err := os.ReadFile(cache) - if err == nil { - return strings.TrimSpace(string(content)), nil - } - - file, err := os.Open(target) - if err != nil { - return "", err - } - defer file.Close() - - hash := md5.New() - if _, err := file.WriteTo(hash); err != nil { - return "", err - } - - digest := md5ToSha3(hash.Sum(nil)) - if err := os.WriteFile(cache, []byte(digest+"\n"), 0666); err != nil { - return "", err - } - return digest, nil -} - -// Since the current Store only supports MD5, but we expect the actual store to -// use SHA3, for now we just hash the md5sum to convert it. -func md5ToSha3(md5sum []byte) string { - sum := sha3.Sum384(append([]byte("md5\x00"), md5sum...)) - return hex.EncodeToString(sum[:]) -} - -func extractSdkYAML(ctx context.Context, setup sdk.Setup) (string, error) { - target := setup.Filepath() - cache := target + ".yaml" - - content, err := os.ReadFile(cache) - if err == nil { - return string(content), nil - } - - cmd := exec.CommandContext(ctx, "tar", - "--extract", - "--to-stdout", - "--force-local", - "--file="+target, - "meta/sdk.yaml", - ) - content, err = cmd.Output() - if err != nil { - return "", err - } - - if err := os.WriteFile(cache, content, 0666); err != nil { - return "", err - } - return string(content), nil -} - -func storeConnect(ctx context.Context) (*ClientWrapper, error) { - opt := option.WithoutAuthentication() - testing := false - if url := os.Getenv("GCS_STORE_URL"); url != "" { // Set STORAGE_EMULATOR_HOST environment variable for GSC. - err := os.Setenv("STORAGE_EMULATOR_HOST", "localhost:8080") - if err != nil { - return nil, err - } - opt = option.WithEndpoint(url) - testing = true - } - client, err := storage.NewClient(ctx, opt) - if err != nil { - return nil, err - } - return &ClientWrapper{client, testing}, nil -} - -func storeSdkInfoImpl(ctx context.Context, name, channel string) (storeSdk, error) { - var sSdk storeSdk - client, err := storeConnect(ctx) - if err != nil { - return sSdk, err - } - defer client.Close() - bkt := client.Bucket(SDK_STORE_BUCKET_NAME) - - var sa = strings.Split(channel, "/") - if len(sa) != 2 { - return sSdk, fmt.Errorf("%q has an invalid channel %q, must take the form /", name, channel) - } - track, risk := sa[0], sa[1] - obj := bkt.Object(fmt.Sprintf("%s/%s/%s/%s.sdk", name, track, risk, name)) - // Max attempts prevents workshop from trying to dial the store indefinitely. - // Behavior is only modified for this specific Object Handle, it is not - // passed on to SDK download behavior - obj = obj.Retryer( - storage.WithMaxAttempts(3), - ) - atr, err := obj.Attrs(ctx) - if err != nil { - if errors.Is(err, storage.ErrObjectNotExist) { - return sSdk, fmt.Errorf("SDK not found in %q", channel) - } - if urlErr, ok := err.(*url.Error); ok { - opErr, ok := urlErr.Err.(*net.OpError) - if ok { - return sSdk, fmt.Errorf("cannot connect to store: %q", opErr) - } - } - return sSdk, err - } - sSdk.Name = name - sSdk.Channel = channel - // A simple modulo to keep revision numbers in a readable form for testing - sSdk.Revision = sdk.Revision{N: int(atr.Generation%1000) + 1} - sSdk.Sha3_384 = md5ToSha3(atr.MD5) - // The test server for the SDK store cannot store metadata. - if !client.isTesting { - if _, ok := atr.Metadata["sdk-yaml"]; !ok { - return sSdk, fmt.Errorf("SDK %q does not have metadata", name) - } - sSdk.SdkYAML = atr.Metadata["sdk-yaml"] - } else { - // Emulate meta data for the test SDKs. We need the name/base pair for - // the e2e scenarios to work. - sSdk.SdkYAML, err = readTestMetadata(ctx, client, name, track, risk) - if err != nil { - return sSdk, err - } - } - return sSdk, nil -} - -// Only relevant for the end to end tests -func readTestMetadata(ctx context.Context, client *ClientWrapper, name, track, risk string) (string, error) { - var b bytes.Buffer - meta := bufio.NewWriter(&b) - - bkt := client.Bucket(SDK_STORE_BUCKET_NAME) - obj := bkt.Object(fmt.Sprintf("%s/%s/%s/sdk.yaml", name, track, risk)) - r, err := obj.NewReader(ctx) - if err != nil { - return "", err - } - defer r.Close() - - if _, err = io.Copy(meta, r); err != nil { - return "", err - } - return b.String(), nil -} - -func storeSdkReaderImpl(ctx context.Context, setup sdk.Setup) (*sdkReader, error) { - client, err := storeConnect(ctx) - if err != nil { - return nil, err - } - defer client.Close() - - var sa = strings.Split(setup.Channel, "/") - if len(sa) != 2 { - return nil, fmt.Errorf("%s has an invalid channel %s, must take the form /", setup.Name, setup.Channel) - } - track, risk := sa[0], sa[1] - bkt := client.Bucket(SDK_STORE_BUCKET_NAME) - - obj := bkt.Object(fmt.Sprintf("%s/%s/%s/%s.sdk", setup.Name, track, risk, setup.Name)).Retryer( - storage.WithBackoff(gax.Backoff{Initial: 2 * time.Second}), - storage.WithPolicy(storage.RetryIdempotent), - ) - r, err := obj.NewReader(ctx) - if err != nil { - if errors.Is(err, storage.ErrObjectNotExist) { - return nil, fmt.Errorf("SDK not found in %q", setup.Channel) - } - return nil, err - } - - // A simple modulo to keep revision numbers in a readable form for testing - revision := sdk.Revision{N: int(r.Attrs.Generation%1000) + 1} - return &sdkReader{ReadCloser: r, Revision: revision, Size: r.Attrs.Size}, nil -} diff --git a/internal/gcsstore/store_test.go b/internal/gcsstore/store_test.go deleted file mode 100644 index ab3c33d9d..000000000 --- a/internal/gcsstore/store_test.go +++ /dev/null @@ -1,94 +0,0 @@ -package gcsstore_test - -import ( - "context" - "testing" - - "gopkg.in/check.v1" - - "github.com/canonical/workshop/internal/gcsstore" - "github.com/canonical/workshop/internal/sdk" -) - -type storeSuite struct { -} - -var _ = check.Suite(&storeSuite{}) - -func Test(t *testing.T) { - check.TestingT(t) -} - -var testSdk = `name: test-sdk -base: ubuntu@20.04 -license: LGPL-2.1 -summary: The Go programming language -description: | - Go is an open source programming language that enables - the production of simple, efficient and reliable software at scale. -plugs: - data: - interface: mount - workshop-target: /test - gpu: - interface: gpu -` - -var testSdkNoBase = `name: test-sdk -` - -func (s *storeSuite) TestSdkActionInstallOK(c *check.C) { - r := gcsstore.FakeSdkStoreInfo(func(ctx context.Context, name, channel string) (gcsstore.StoreSdk, error) { - var s = gcsstore.StoreSdk{ - Name: "test-sdk", - Channel: channel, - Revision: sdk.Revision{N: 123}, - SdkYAML: testSdk, - } - return s, nil - }) - defer r() - defer sdk.MockSanitizePlugsSlots(func(sdkInfo *sdk.Info) {})() - - store := gcsstore.New() - acts := []sdk.SdkAction{{ - ProjectId: "24242424", - Workshop: "test-workshop", - Action: sdk.Install, - Name: "test-sdk", - Base: "ubuntu@20.04", - Channel: "latest/stable", - }, - } - res, err := store.SdkAction(context.Background(), acts) - c.Assert(res, check.HasLen, 1) - c.Assert(err, check.IsNil) -} - -func (s *storeSuite) TestSdkActionInstallNoBase(c *check.C) { - r := gcsstore.FakeSdkStoreInfo(func(ctx context.Context, name, channel string) (gcsstore.StoreSdk, error) { - var s = gcsstore.StoreSdk{ - Name: "test-sdk", - Channel: channel, - Revision: sdk.Revision{N: 123}, - SdkYAML: testSdkNoBase, - } - return s, nil - }) - defer r() - defer sdk.MockSanitizePlugsSlots(func(sdkInfo *sdk.Info) {})() - - store := gcsstore.New() - acts := []sdk.SdkAction{{ - ProjectId: "24242424", - Workshop: "test-workshop", - Action: sdk.Install, - Name: "test-sdk", - Base: "ubuntu@20.04", - Channel: "latest/stable", - }, - } - res, err := store.SdkAction(context.Background(), acts) - c.Assert(res, check.HasLen, 1) - c.Assert(err, check.IsNil) -} diff --git a/internal/overlord/overlord.go b/internal/overlord/overlord.go index 8d912022d..c246c1d2e 100644 --- a/internal/overlord/overlord.go +++ b/internal/overlord/overlord.go @@ -28,7 +28,6 @@ import ( "gopkg.in/tomb.v2" "github.com/canonical/workshop/internal/dirs" - "github.com/canonical/workshop/internal/gcsstore" "github.com/canonical/workshop/internal/logger" "github.com/canonical/workshop/internal/osutil" "github.com/canonical/workshop/internal/overlord/cmdstate" @@ -133,9 +132,6 @@ func New(dir string, restartHandler restart.Handler) (*Overlord, error) { sto := sdkstore.NewClient(storeConfig) sdk.ReplaceStore(s, sto) - gcs := gcsstore.New() - sdk.ReplaceGcsStore(s, gcs) - if workshopBackendOverride != nil { workshop.ReplaceBackend(s, workshopBackendOverride) } else { diff --git a/internal/sdk/store.go b/internal/sdk/store.go index 767ed1615..2a8354b35 100644 --- a/internal/sdk/store.go +++ b/internal/sdk/store.go @@ -12,31 +12,10 @@ import ( "sync" "github.com/canonical/workshop/internal/overlord/state" - "github.com/canonical/workshop/internal/progress" "github.com/canonical/workshop/internal/sdkstore" "github.com/canonical/workshop/internal/sdkstore/transport" ) -type StoreAction int - -const ( - Install StoreAction = iota - Refresh -) - -func (s StoreAction) String() string { - return [...]string{"install", "refresh"}[s] -} - -type SdkAction struct { - ProjectId string - Workshop string - Action StoreAction - Name string - Base string - Channel string -} - type cachedStoreKey struct{} // ReplaceStore replaces the SDK store used by the manager. @@ -245,98 +224,3 @@ func fakeResolve(req transport.ResolveRequest, sdks map[string]Meta) (transport. }) return transport.ResolveResponse{PackageResults: responses}, nil } - -type cachedGcsStoreKey struct{} - -// ReplaceGcsStore replaces the GCS store used by the manager. -func ReplaceGcsStore(state *state.State, store GcsStore) { - state.Lock() - state.Cache(cachedGcsStoreKey{}, store) - state.Unlock() -} - -func cachedGcsStore(st *state.State) GcsStore { - sdkStore := st.Cached(cachedGcsStoreKey{}) - if sdkStore == nil { - return nil - } - return sdkStore.(GcsStore) -} - -// GcsStoreService returns the store service provided by the optional device -// context or the one used by the snapstate package if the former has no -// override. -func GcsStoreService(st *state.State) GcsStore { - if store := cachedGcsStore(st); store != nil { - return store - } - panic("internal error: needing the store before managers have initialized it") -} - -type GcsStore interface { - SdkAction(ctx context.Context, actions []SdkAction) ([]Meta, error) - DownloadSdk(ctx context.Context, setup Setup, report *progress.Reporter) (*Meta, error) -} - -func NewFakeGcsStore() *FakeGcsStore { - return &FakeGcsStore{ - ActionCalls: make([]TestActionCall, 0), - } -} - -type TestActionCall struct { - Actions []SdkAction -} - -type TestDownloadCall struct { - Setup Setup -} - -type FakeGcsStore struct { - ActionCalls []TestActionCall - - downloadLock sync.Mutex - DownloadCalls []TestDownloadCall - - ActionCallback func(ctx context.Context, actions []SdkAction) ([]Meta, error) - DownloadCallback func(ctx context.Context, setup Setup, report *progress.Reporter) (*Meta, error) -} - -func (f *FakeGcsStore) SetActionCallback(fa func(ctx context.Context, actions []SdkAction) ([]Meta, error)) func() { - old := f.ActionCallback - f.ActionCallback = fa - return func() { - f.ActionCallback = old - } -} - -func (f *FakeGcsStore) SetDownloadCallback(fa func(ctx context.Context, setup Setup, report *progress.Reporter) (*Meta, error)) func() { - old := f.DownloadCallback - f.DownloadCallback = fa - return func() { - f.DownloadCallback = old - } -} - -func (f *FakeGcsStore) SdkAction(ctx context.Context, actions []SdkAction) ([]Meta, error) { - f.ActionCalls = append(f.ActionCalls, TestActionCall{ - Actions: actions, - }) - if f.ActionCallback != nil { - return f.ActionCallback(ctx, actions) - } - return nil, nil -} - -func (f *FakeGcsStore) DownloadSdk(ctx context.Context, setup Setup, report *progress.Reporter) (*Meta, error) { - f.downloadLock.Lock() - defer f.downloadLock.Unlock() - f.DownloadCalls = append(f.DownloadCalls, TestDownloadCall{ - Setup: setup, - }) - if f.DownloadCallback != nil { - return f.DownloadCallback(ctx, setup, report) - } - - return &Meta{Setup: setup, SdkYAML: "name: " + setup.Name}, nil -} diff --git a/snap/local/commands/run_daemon b/snap/local/commands/run_daemon index e572a870a..cf7018d70 100755 --- a/snap/local/commands/run_daemon +++ b/snap/local/commands/run_daemon @@ -1,8 +1,5 @@ #!/bin/sh -GCS_STORE_URL=$(snapctl get gcs.url) -export GCS_STORE_URL - SDK_STORE_URL=$(snapctl get store.url) export SDK_STORE_URL diff --git a/tests/integration/gcs-store/task.yaml b/tests/integration/gcs-store/task.yaml deleted file mode 100644 index e9a4fb201..000000000 --- a/tests/integration/gcs-store/task.yaml +++ /dev/null @@ -1,17 +0,0 @@ -summary: Run LXD integration tests for SDK profiles -prepare: | - # shellcheck source=/dev/null - . "$TESTSLIB"/utils.sh - start_sdk_store -restore: | - # shellcheck source=/dev/null - . "$TESTSLIB"/utils.sh - stop_sdk_store -execute: | - rm -rf cover - mkdir cover - - go test -cover "$SPREAD_PATH"/internal/gcsstore/ -timeout=30m -tags=integration -check.v -check.f storeIntegration -coverpkg=github.com/canonical/workshop/internal/gcsstore -args -test.gocoverdir="$PWD"/cover - -artifacts: - - cover diff --git a/tests/lib/utils.sh b/tests/lib/utils.sh index 9a483f1bd..3b6f5b378 100644 --- a/tests/lib/utils.sh +++ b/tests/lib/utils.sh @@ -83,15 +83,8 @@ EOF } function setup_workshop() { - local use_real_store="${1:-false}" - snap install --dangerous --classic /workshop/tests/*.snap - if [ "$use_real_store" != "true" ]; then - snap set workshop gcs.url=http://localhost:8080/storage/v1/ - start_sdk_store - fi - snap set workshop workshop.debug=1 snap set workshop workshop.image.server.url="$IMAGE_SERVER" snap alias workshop.sdk sdk @@ -102,41 +95,12 @@ function setup_workshop() { } function cleanup_workshop() { - local use_real_store="${1:-false}" - - if [ "$use_real_store" != "true" ]; then - stop_sdk_store - fi - snap remove workshop --purge snap remove sdkcraft --purge find /workshop -name .workshop.lock -delete loginctl disable-linger ubuntu } -function start_sdk_store() { - # run fake GCS bucket storage to emulate SDK store - publish_test_sdks "$TESTS_SDKS" "$SDK_STORE_BUCKET_DIR" - # a bug with fake-gcs-server that returns 404 if not owned by the user - chown -R ubuntu.ubuntu /data - mkdir -p /storage - chown -R ubuntu.ubuntu /storage - - go install github.com/fsouza/fake-gcs-server@latest - fake-gcs-server -data /data -scheme http -port 8080 -public-host localhost:8080 >~/fake_sdk_store.log 2>&1 & - - echo "Waiting for the fake SDK store to start on port 8080..." - while ! nc -z localhost 8080; do - sleep 2 - done -} - -function stop_sdk_store() { - pkill -f fake-gcs-server || true - rm -rf /data - rm -rf /storage -} - # Publish test SDKs in the fake SDK Store function publish_test_sdks() { for i in "$1"/*; do diff --git a/tests/main/autostart/task.yaml b/tests/main/autostart/task.yaml index 46903eb6c..50b43f2df 100644 --- a/tests/main/autostart/task.yaml +++ b/tests/main/autostart/task.yaml @@ -5,8 +5,6 @@ prepare: | restore: | . "$TESTSLIB"/utils.sh workshop_exec remove - # We need to restart the store after a spread REBOOT for the next task - start_sdk_store execute: | . "$TESTSLIB"/utils.sh From 65b60253d381a6d7a2780ca06afab553ae05fc55 Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Wed, 15 Apr 2026 16:05:42 +1200 Subject: [PATCH 14/17] Download sdkcraft snap instead of building it --- .github/workflows/build-deps.yaml | 41 ------------------- .github/workflows/cover.yaml | 1 - .github/workflows/lxd-candidate-check.yaml | 6 --- .github/workflows/spread.yaml | 8 ---- .spread.yaml | 1 + tests/lib/utils.sh | 3 +- tests/main/try-sdks/task.yaml | 21 +++------- tests/main/try-sdks/unpacked/1/meta/sdk.yaml | 8 ---- .../try-sdks/unpacked/1/sdk/hooks/setup-base | 1 - tests/main/try-sdks/unpacked/2/meta/sdk.yaml | 8 ---- .../try-sdks/unpacked/2/sdk/hooks/setup-base | 1 - 11 files changed, 8 insertions(+), 91 deletions(-) delete mode 100644 tests/main/try-sdks/unpacked/1/meta/sdk.yaml delete mode 100755 tests/main/try-sdks/unpacked/1/sdk/hooks/setup-base delete mode 100644 tests/main/try-sdks/unpacked/2/meta/sdk.yaml delete mode 100755 tests/main/try-sdks/unpacked/2/sdk/hooks/setup-base diff --git a/.github/workflows/build-deps.yaml b/.github/workflows/build-deps.yaml index 66247075b..aabdb979a 100644 --- a/.github/workflows/build-deps.yaml +++ b/.github/workflows/build-deps.yaml @@ -2,8 +2,6 @@ name: Build Dependencies on: workflow_call: secrets: - GH_SDKCRAFT: - required: true SDKCRAFT_STORE_CREDENTIALS: required: true outputs: @@ -50,45 +48,6 @@ jobs: name: snap_workshop path: ${{ steps.cache-workshop.outputs.cache-hit == 'true' && steps.snapcraft_workshop_cached.outputs.snap || steps.snapcraft_workshop.outputs.snap }} - build-sdkcraft: - runs-on: ubuntu-latest - steps: - - name: Checkout Sdkcraft - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - repository: canonical/sdkcraft - show-progress: true - token: ${{ secrets.GH_SDKCRAFT }} # zizmor: ignore[secrets-outside-env] - path: sdkcraft - fetch-depth: 0 - persist-credentials: false - - name: Get Sdkcraft commit hash - id: sdkcraft-sha - run: echo "sha=$(git -C sdkcraft rev-parse HEAD)" >> $GITHUB_OUTPUT - - name: Cache Sdkcraft snap - id: cache-sdkcraft - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 - with: - path: sdkcraft/sdkcraft_*.snap - key: sdkcraft-snap-${{ steps.sdkcraft-sha.outputs.sha }} - - name: Build Sdkcraft - if: steps.cache-sdkcraft.outputs.cache-hit != 'true' - uses: canonical/action-build@3bdaa03e1ba6bf59a65f84a751d943d549a54e79 # v1.3.0 - id: snapcraft_sdkcraft - with: - path: sdkcraft - - name: Set snap path from cache - if: steps.cache-sdkcraft.outputs.cache-hit == 'true' - id: snapcraft_sdkcraft_cached - run: | - snap_file=$(ls sdkcraft/sdkcraft_*.snap) - echo "snap=$snap_file" >> $GITHUB_OUTPUT - - name: Upload Sdkcraft - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - with: - name: snap_sdkcraft - path: ${{ steps.cache-sdkcraft.outputs.cache-hit == 'true' && steps.snapcraft_sdkcraft_cached.outputs.snap || steps.snapcraft_sdkcraft.outputs.snap }} - build-spread: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/cover.yaml b/.github/workflows/cover.yaml index c97ede971..3a210b496 100644 --- a/.github/workflows/cover.yaml +++ b/.github/workflows/cover.yaml @@ -25,7 +25,6 @@ jobs: spread: uses: ./.github/workflows/spread.yaml secrets: - GH_SDKCRAFT: ${{ secrets.GH_SDKCRAFT }} STORE_CREDENTIALS: ${{ secrets.STORE_CREDENTIALS }} SDKCRAFT_STORE_CREDENTIALS: ${{ secrets.SDKCRAFT_STORE_CREDENTIALS_STAGING }} unit-tests: diff --git a/.github/workflows/lxd-candidate-check.yaml b/.github/workflows/lxd-candidate-check.yaml index 45dad5267..0d8cf4042 100644 --- a/.github/workflows/lxd-candidate-check.yaml +++ b/.github/workflows/lxd-candidate-check.yaml @@ -11,7 +11,6 @@ jobs: build-deps: uses: ./.github/workflows/build-deps.yaml secrets: - GH_SDKCRAFT: ${{ secrets.GH_SDKCRAFT }} SDKCRAFT_STORE_CREDENTIALS: ${{ secrets.SDKCRAFT_STORE_CREDENTIALS_STAGING }} snap-tests: needs: [build-deps] @@ -44,11 +43,6 @@ jobs: with: name: snap_workshop path: ${{ github.workspace }}/tests/ - - name: Download Sdkcraft Snap - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: snap_sdkcraft - path: ${{ github.workspace }}/tests/ - name: Download spread binary uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: diff --git a/.github/workflows/spread.yaml b/.github/workflows/spread.yaml index d3b3b293a..3e86292d8 100644 --- a/.github/workflows/spread.yaml +++ b/.github/workflows/spread.yaml @@ -2,8 +2,6 @@ name: Workshop end-to-end tests on: workflow_call: secrets: - GH_SDKCRAFT: - required: true STORE_CREDENTIALS: required: true SDKCRAFT_STORE_CREDENTIALS: @@ -17,7 +15,6 @@ jobs: build-deps: uses: ./.github/workflows/build-deps.yaml secrets: - GH_SDKCRAFT: ${{ secrets.GH_SDKCRAFT }} SDKCRAFT_STORE_CREDENTIALS: ${{ secrets.SDKCRAFT_STORE_CREDENTIALS }} snap-tests: needs: [build-deps] @@ -60,11 +57,6 @@ jobs: with: name: snap_workshop path: ${{ github.workspace }}/tests/ - - name: Download Sdkcraft Snap - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: snap_sdkcraft - path: ${{ github.workspace }}/tests/ - name: Download spread binary uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: diff --git a/.spread.yaml b/.spread.yaml index 032eecdde..b6c4f2b31 100644 --- a/.spread.yaml +++ b/.spread.yaml @@ -23,6 +23,7 @@ suites: . "$TESTSLIB"/utils.sh prepare_environment setup_workshop + install_sdkcraft prepare-each: | date --rfc-3339=seconds > /tmp/spread-job-start restore: | diff --git a/tests/lib/utils.sh b/tests/lib/utils.sh index 3b6f5b378..24b460822 100644 --- a/tests/lib/utils.sh +++ b/tests/lib/utils.sh @@ -148,9 +148,8 @@ function run_sdkcraft() { sdkcraft "$@" } -# Install sdkcraft from a local snap file function install_sdkcraft() { - snap install --dangerous --classic /workshop/tests/sdkcraft_*.snap + snap install --classic --edge sdkcraft } function resolve_branch() { diff --git a/tests/main/try-sdks/task.yaml b/tests/main/try-sdks/task.yaml index f4db73090..7950f26f8 100644 --- a/tests/main/try-sdks/task.yaml +++ b/tests/main/try-sdks/task.yaml @@ -4,26 +4,17 @@ restore: | workshop_exec remove || true lxc storage volume delete workshop custom/test-sdk-basic-x1 || true lxc storage volume delete workshop custom/test-sdk-basic-x2 || true + (cd "$TESTSLIB/sdk/test-sdk-basic/latest/stable" && run_sdkcraft clean) || true + (cd "$TESTSLIB/sdk/test-sdk-basic/latest/edge" && run_sdkcraft clean) || true + rm -rf /home/ubuntu/.local/share/workshop/try/test-sdk-basic execute: | . "$TESTSLIB"/utils.sh - sdk_try() { - (cd "$1" && sudo -u ubuntu tar cf "$2/test-sdk-basic_all.sdk" *) - openssl sha3-384 < "$2/test-sdk-basic_all.sdk" | cut -d' ' -f2 | sudo -u ubuntu tee "$2/test-sdk-basic_all.sdk.sha3-384" - sudo -u ubuntu cp "$1/meta/sdk.yaml" "$2/test-sdk-basic_all.sdk.yaml" - } - - echo "Copy test-sdk-basic (1) to try area" - try_area='/home/ubuntu/.local/share/workshop/try/test-sdk-basic' - rm -rf "$try_area" - sudo -u ubuntu mkdir -p "$try_area" - sdk_try unpacked/1 "$try_area" - echo "Launch workshop with SDKs from try area" + (cd "$TESTSLIB/sdk/test-sdk-basic/latest/stable" && run_sdkcraft try) workshop_exec launch workshop_exec exec -- findmnt /var/lib/workshop/sdk/test-sdk-basic | MATCH 'workshop/custom/default_test-sdk-basic-x1' - try_area_contracted="~${try_area#*ubuntu}" - workshop_exec info | yq '.sdks["test-sdk-basic"].tracking' | MATCH "^${try_area_contracted}\$" + workshop_exec info | yq '.sdks["test-sdk-basic"].tracking' | MATCH '^~/.local/share/workshop/try/test-sdk-basic$' workshop_exec info | yq '.sdks["test-sdk-basic"].installed' | MATCH '\(x1\)$' echo "Check try and in-project SDKs can share revision numbers" @@ -40,7 +31,7 @@ execute: | workshop_exec info | yq '.sdks["test-sdk-basic"].installed' | MATCH '\(x1\)$' echo "Check SDKs in try area can be updated" - sdk_try unpacked/2 "$try_area" + (cd "$TESTSLIB/sdk/test-sdk-basic/latest/edge" && run_sdkcraft try) workshop_exec refresh workshop_exec info | yq '.sdks["test-sdk-basic"].installed' | MATCH '\(x2\)$' workshop_exec exec -- cat /var/lib/workshop/sdk/test-sdk-basic/sdk/hooks/setup-base | MATCH 'setup-base v2' diff --git a/tests/main/try-sdks/unpacked/1/meta/sdk.yaml b/tests/main/try-sdks/unpacked/1/meta/sdk.yaml deleted file mode 100644 index 2fcab0a60..000000000 --- a/tests/main/try-sdks/unpacked/1/meta/sdk.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: test-sdk-basic -version: '1.0' -summary: test-sdk-basic SDK -description: | - test-sdk-basic SDK -base: ubuntu@22.04 -architecture: amd64 -sdkcraft-started-at: '2026-04-01T23:27:53.386284Z' diff --git a/tests/main/try-sdks/unpacked/1/sdk/hooks/setup-base b/tests/main/try-sdks/unpacked/1/sdk/hooks/setup-base deleted file mode 100755 index 6a2cf062d..000000000 --- a/tests/main/try-sdks/unpacked/1/sdk/hooks/setup-base +++ /dev/null @@ -1 +0,0 @@ -echo "setup-base ran successfully!" diff --git a/tests/main/try-sdks/unpacked/2/meta/sdk.yaml b/tests/main/try-sdks/unpacked/2/meta/sdk.yaml deleted file mode 100644 index 1d935cd39..000000000 --- a/tests/main/try-sdks/unpacked/2/meta/sdk.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: test-sdk-basic -version: '2.0' -summary: test-sdk-basic SDK -description: | - test-sdk-basic SDK -base: ubuntu@22.04 -architecture: amd64 -sdkcraft-started-at: '2026-04-01T23:30:01.223415Z' diff --git a/tests/main/try-sdks/unpacked/2/sdk/hooks/setup-base b/tests/main/try-sdks/unpacked/2/sdk/hooks/setup-base deleted file mode 100755 index d84a2724c..000000000 --- a/tests/main/try-sdks/unpacked/2/sdk/hooks/setup-base +++ /dev/null @@ -1 +0,0 @@ -echo "setup-base v2 ran successfully!" From 7c0bf2261a68583858cd67d152fd14b76df426ab Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Mon, 13 Apr 2026 10:52:23 +1200 Subject: [PATCH 15/17] Switch to production Store --- .github/workflows/build-deps.yaml | 155 ++---- .github/workflows/cover.yaml | 2 +- .github/workflows/lxd-candidate-check.yaml | 2 +- .github/workflows/spread.yaml | 2 - .github/workflows/staging.yaml | 25 + .spread.yaml | 1 - internal/sdkstore/client.go | 2 +- .../sdkstore/download_integration_test.go | 10 +- internal/sdkstore/find_integration_test.go | 24 +- internal/sdkstore/find_test.go | 10 +- internal/sdkstore/info_integration_test.go | 4 +- internal/sdkstore/resolve_integration_test.go | 14 +- internal/sdkstore/test-resolve-id.raw.json | 32 +- internal/sdkstore/test-resolve-name.json | 28 +- internal/sdkstore/test-resolve-name.raw.json | 28 +- internal/sdkstore/test-sdk-find-s390x.json | 20 +- .../sdkstore/test-sdk-find-s390x.raw.json | 20 +- internal/sdkstore/test-sdk-find.json | 46 +- internal/sdkstore/test-sdk-find.raw.json | 46 +- .../sdkstore/test-sdk-info-multi-base.json | 446 ++++++++++------- .../test-sdk-info-multi-base.raw.json | 458 ++++++++++-------- internal/sdkstore/test-sdk-info.json | 42 +- internal/sdkstore/test-sdk-info.raw.json | 42 +- tests/docs-tutorial/part-4/task.yaml | 1 + tests/integration/sdk-store/task.yaml | 10 + tests/lib/sdk/README.md | 25 + .../latest/edge/platforms.json | 1 + .../latest/edge/sdkcraft.yaml | 14 + .../latest/edge/platforms.json | 1 + .../test-sdk-find-2/latest/edge/sdkcraft.yaml | 14 + .../latest/edge/hooks/setup-base | 0 .../latest/edge/platforms.json | 1 + .../latest/edge/sdkcraft.yaml | 2 +- .../latest/stable/hooks/setup-base | 0 .../latest/stable/platforms.json | 1 + .../latest/stable/sdkcraft.yaml | 2 +- .../latest/edge/platforms.json | 1 + .../latest/edge/sdkcraft.yaml | 31 ++ .../latest/stable/platforms.json | 1 + .../latest/stable/sdkcraft.yaml | 29 ++ tests/lib/utils.sh | 10 +- .../{ws-error.yaml.in => ws-error.yaml} | 1 - .../.workshop/{ws-ok.yaml.in => ws-ok.yaml} | 1 - .../{ws-waiting.yaml.in => ws-waiting.yaml} | 1 - tests/main/check-health/task.yaml | 1 - .../{ws-comp.yaml.in => ws-comp.yaml} | 2 - tests/main/completion/task.yaml | 1 - tests/main/connect/task.yaml | 1 - tests/main/connect/workshop.yaml | 5 + tests/main/connect/workshop.yaml.in | 7 - .../{ws-conns-1.yaml.in => ws-conns-1.yaml} | 1 - .../{ws-conns-2.yaml.in => ws-conns-2.yaml} | 1 - tests/main/connections/task.yaml | 1 - tests/main/disconnect/task.yaml | 1 - .../{workshop.yaml.in => workshop.yaml} | 1 - tests/main/exec/task.yaml | 1 - .../exec/{workshop.yaml.in => workshop.yaml} | 2 - tests/main/info-sdk-health/task.yaml | 1 - .../{workshop.yaml.in => workshop.yaml} | 1 - ...amera-fail.yaml.in => ws-camera-fail.yaml} | 1 - .../{ws-camera.yaml.in => ws-camera.yaml} | 1 - tests/main/interface-camera/task.yaml | 1 - ...ktop-fail.yaml.in => ws-desktop-fail.yaml} | 1 - .../{ws-desktop.yaml.in => ws-desktop.yaml} | 1 - tests/main/interface-desktop/task.yaml | 1 - .../{ws-gpu-fail.yaml.in => ws-gpu-fail.yaml} | 1 - .../.workshop/{ws-gpu.yaml.in => ws-gpu.yaml} | 1 - tests/main/interface-gpu/task.yaml | 1 - tests/main/interface-mount/task.yaml | 1 - .../{workshop.yaml.in => workshop.yaml} | 1 - tests/main/interface-plug-binding/task.yaml | 1 - .../{workshop.yaml.in => workshop.yaml} | 4 +- .../{ws-ssh-fail.yaml.in => ws-ssh-fail.yaml} | 1 - .../.workshop/{ws-ssh.yaml.in => ws-ssh.yaml} | 1 - tests/main/interface-ssh-agent/task.yaml | 1 - .../{.workshop.yaml.in => .workshop.yaml} | 1 - tests/main/interface-tunnel/task.yaml | 1 - tests/main/launch-abort/task.yaml | 1 - ...-fail.yaml.in => workshop.setup-fail.yaml} | 1 - tests/main/launch-continue/task.yaml | 1 - .../{workshop.yaml.in => workshop.yaml} | 1 - .../.workshop/{ws-one.yaml.in => ws-one.yaml} | 1 - .../{ws-three.yaml.in => ws-three.yaml} | 1 - .../.workshop/{ws-two.yaml.in => ws-two.yaml} | 1 - tests/main/launch-multiple/task.yaml | 1 - tests/main/launch-sdk/task.yaml | 1 - .../{workshop.yaml.in => workshop.yaml} | 1 - tests/main/refresh-abort/task.yaml | 1 - .../{workshop.yaml.in => workshop.yaml} | 1 - tests/main/refresh-continue/task.yaml | 1 - .../{workshop.yaml.in => workshop.yaml} | 1 - ...efresh-sdk.yaml.in => ws-refresh-sdk.yaml} | 1 - .../multi/.workshop/ws-refresh-snapshots.yaml | 6 + .../.workshop/ws-refresh-snapshots.yaml.in | 9 - tests/main/refresh/task.yaml | 3 +- tests/main/remount/task.yaml | 1 - .../{workshop.yaml.in => workshop.yaml} | 1 - ...remove-2.yaml.in => ws-snap-remove-2.yaml} | 1 - ...move-2a.yaml.in => ws-snap-remove-2a.yaml} | 1 - tests/main/remove-snap/task.yaml | 1 - ...ove-mount.yaml.in => ws-remove-mount.yaml} | 1 - tests/main/remove/task.yaml | 5 +- tests/main/restore/workshop.yaml | 1 - tests/main/sdk-find/task.yaml | 6 +- tests/main/sdk-info/.workshop/edge.yaml | 5 + tests/main/sdk-info/.workshop/edge.yaml.in | 5 - tests/main/sdk-info/.workshop/stable.yaml | 5 + tests/main/sdk-info/.workshop/stable.yaml.in | 5 - tests/main/sdk-info/header.txt | 4 +- tests/main/sdk-info/task.yaml | 21 +- tests/main/shell/task.yaml | 1 - .../shell/{workshop.yaml.in => workshop.yaml} | 1 - tests/main/sketch-sdk/task.yaml | 1 - .../{workshop.yaml.in => workshop.yaml} | 1 - tests/main/start/task.yaml | 1 - .../start/{workshop.yaml.in => workshop.yaml} | 1 - tests/main/stop/task.yaml | 1 - .../stop/{workshop.yaml.in => workshop.yaml} | 1 - tests/main/try-sdks/task.yaml | 8 +- ...-user-conns.yaml.in => ws-user-conns.yaml} | 2 - ...nns.yaml.new.in => ws-user-conns.yaml.new} | 2 - tests/main/workshop-connections/task.yaml | 1 - 122 files changed, 955 insertions(+), 816 deletions(-) create mode 100644 .github/workflows/staging.yaml create mode 100644 tests/integration/sdk-store/task.yaml create mode 100644 tests/lib/sdk/README.md create mode 100644 tests/lib/sdk/test-sdk-find-2-s390x/latest/edge/platforms.json create mode 100644 tests/lib/sdk/test-sdk-find-2-s390x/latest/edge/sdkcraft.yaml create mode 100644 tests/lib/sdk/test-sdk-find-2/latest/edge/platforms.json create mode 100644 tests/lib/sdk/test-sdk-find-2/latest/edge/sdkcraft.yaml rename tests/lib/sdk/{test-sdk-info => test-sdk-info-1}/latest/edge/hooks/setup-base (100%) create mode 100644 tests/lib/sdk/test-sdk-info-1/latest/edge/platforms.json rename tests/lib/sdk/{test-sdk-info => test-sdk-info-1}/latest/edge/sdkcraft.yaml (94%) rename tests/lib/sdk/{test-sdk-info => test-sdk-info-1}/latest/stable/hooks/setup-base (100%) create mode 100644 tests/lib/sdk/test-sdk-info-1/latest/stable/platforms.json rename tests/lib/sdk/{test-sdk-info => test-sdk-info-1}/latest/stable/sdkcraft.yaml (94%) create mode 100644 tests/lib/sdk/test-sdk-info-multi-base-1/latest/edge/platforms.json create mode 100644 tests/lib/sdk/test-sdk-info-multi-base-1/latest/edge/sdkcraft.yaml create mode 100644 tests/lib/sdk/test-sdk-info-multi-base-1/latest/stable/platforms.json create mode 100644 tests/lib/sdk/test-sdk-info-multi-base-1/latest/stable/sdkcraft.yaml rename tests/main/check-health/.workshop/{ws-error.yaml.in => ws-error.yaml} (61%) rename tests/main/check-health/.workshop/{ws-ok.yaml.in => ws-ok.yaml} (59%) rename tests/main/check-health/.workshop/{ws-waiting.yaml.in => ws-waiting.yaml} (62%) rename tests/main/completion/.workshop/{ws-comp.yaml.in => ws-comp.yaml} (50%) create mode 100644 tests/main/connect/workshop.yaml delete mode 100644 tests/main/connect/workshop.yaml.in rename tests/main/connections/.workshop/{ws-conns-1.yaml.in => ws-conns-1.yaml} (59%) rename tests/main/connections/.workshop/{ws-conns-2.yaml.in => ws-conns-2.yaml} (59%) rename tests/main/disconnect/{workshop.yaml.in => workshop.yaml} (59%) rename tests/main/exec/{workshop.yaml.in => workshop.yaml} (70%) rename tests/main/info-sdk-health/{workshop.yaml.in => workshop.yaml} (63%) rename tests/main/interface-camera/.workshop/{ws-camera-fail.yaml.in => ws-camera-fail.yaml} (76%) rename tests/main/interface-camera/.workshop/{ws-camera.yaml.in => ws-camera.yaml} (59%) rename tests/main/interface-desktop/.workshop/{ws-desktop-fail.yaml.in => ws-desktop-fail.yaml} (76%) rename tests/main/interface-desktop/.workshop/{ws-desktop.yaml.in => ws-desktop.yaml} (75%) rename tests/main/interface-gpu/.workshop/{ws-gpu-fail.yaml.in => ws-gpu-fail.yaml} (74%) rename tests/main/interface-gpu/.workshop/{ws-gpu.yaml.in => ws-gpu.yaml} (57%) rename tests/main/interface-mount/{workshop.yaml.in => workshop.yaml} (92%) rename tests/main/interface-plug-binding/{workshop.yaml.in => workshop.yaml} (70%) rename tests/main/interface-ssh-agent/.workshop/{ws-ssh-fail.yaml.in => ws-ssh-fail.yaml} (76%) rename tests/main/interface-ssh-agent/.workshop/{ws-ssh.yaml.in => ws-ssh.yaml} (76%) rename tests/main/interface-tunnel/{.workshop.yaml.in => .workshop.yaml} (93%) rename tests/main/launch-abort/{workshop.setup-fail.yaml.in => workshop.setup-fail.yaml} (63%) rename tests/main/launch-continue/{workshop.yaml.in => workshop.yaml} (62%) rename tests/main/launch-multiple/.workshop/{ws-one.yaml.in => ws-one.yaml} (58%) rename tests/main/launch-multiple/.workshop/{ws-three.yaml.in => ws-three.yaml} (59%) rename tests/main/launch-multiple/.workshop/{ws-two.yaml.in => ws-two.yaml} (58%) rename tests/main/launch-sdk/{workshop.yaml.in => workshop.yaml} (59%) rename tests/main/refresh-abort/{workshop.yaml.in => workshop.yaml} (61%) rename tests/main/refresh-continue/{workshop.yaml.in => workshop.yaml} (61%) rename tests/main/refresh/multi/.workshop/{ws-refresh-sdk.yaml.in => ws-refresh-sdk.yaml} (61%) create mode 100644 tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml delete mode 100644 tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml.in rename tests/main/remount/{workshop.yaml.in => workshop.yaml} (59%) rename tests/main/remove-snap/project-2/.workshop/{ws-snap-remove-2.yaml.in => ws-snap-remove-2.yaml} (61%) rename tests/main/remove-snap/project-2/.workshop/{ws-snap-remove-2a.yaml.in => ws-snap-remove-2a.yaml} (62%) rename tests/main/remove/.workshop/{ws-remove-mount.yaml.in => ws-remove-mount.yaml} (61%) create mode 100644 tests/main/sdk-info/.workshop/edge.yaml delete mode 100644 tests/main/sdk-info/.workshop/edge.yaml.in create mode 100644 tests/main/sdk-info/.workshop/stable.yaml delete mode 100644 tests/main/sdk-info/.workshop/stable.yaml.in rename tests/main/shell/{workshop.yaml.in => workshop.yaml} (59%) rename tests/main/sketch-sdk/{workshop.yaml.in => workshop.yaml} (59%) rename tests/main/start/{workshop.yaml.in => workshop.yaml} (59%) rename tests/main/stop/{workshop.yaml.in => workshop.yaml} (58%) rename tests/main/workshop-connections/.workshop/{ws-user-conns.yaml.in => ws-user-conns.yaml} (82%) rename tests/main/workshop-connections/.workshop/{ws-user-conns.yaml.new.in => ws-user-conns.yaml.new} (75%) diff --git a/.github/workflows/build-deps.yaml b/.github/workflows/build-deps.yaml index aabdb979a..8fa62eb44 100644 --- a/.github/workflows/build-deps.yaml +++ b/.github/workflows/build-deps.yaml @@ -8,9 +8,6 @@ on: snap_workshop: description: "Workshop snap filename" value: ${{ jobs.build-workshop.outputs.snap_workshop }} - test_sdk_branch: - description: Store branch for test SDKs - value: ${{ jobs.detect-sdk-changes.outputs.branch }} permissions: contents: read @@ -90,11 +87,9 @@ jobs: detect-sdk-changes: runs-on: ubuntu-latest - if: github.event_name == 'pull_request' || github.event_name == 'push' + if: github.event_name == 'pull_request' outputs: - branch: ${{ steps.base.outputs.branch }} - modified: ${{ steps.detect.outputs.modified }} - unchanged: ${{ steps.detect.outputs.unchanged }} + sdks: ${{ steps.detect.outputs.sdks }} steps: - name: Checkout Workshop uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -103,142 +98,68 @@ jobs: filter: 'tree:0' persist-credentials: true - - name: Checkout base commit - id: base - run: | - case "$GITHUB_EVENT_NAME" in - 'pull_request') - git fetch --filter=tree:0 origin "+${GITHUB_BASE_REF}:${GITHUB_BASE_REF}" - - printf '[command]git rev-parse %s\n' "$GITHUB_BASE_REF" - rev=$(git rev-parse "$GITHUB_BASE_REF") - printf '%s\n' "$rev" - - printf 'range=%s...HEAD\n' "$rev" >>"$GITHUB_OUTPUT" - - printf 'branch=pr-%s\n' "$GITHUB_EVENT_NUMBER" >>"$GITHUB_OUTPUT" - ;; - 'push') - git fetch --filter=tree:0 origin "$GITHUB_EVENT_BEFORE" - - printf 'range=%s..HEAD\n' "$GITHUB_EVENT_BEFORE" >>"$GITHUB_OUTPUT" - - printf 'branch=\n' >>"$GITHUB_OUTPUT" - ;; - *) - printf '::error::This workflow only supports push and pull_request events.\n' - exit 1 - ;; - esac - env: - GITHUB_EVENT_BEFORE: ${{ github.event.before }} - GITHUB_EVENT_NUMBER: ${{ github.event.number }} - - - name: Detect SDK changes + - name: Detect staged SDKs id: detect run: | - cd tests/lib/sdk + git fetch --filter=tree:0 origin "+${GITHUB_BASE_REF}:${GITHUB_BASE_REF}" + base=$(git merge-base "$GITHUB_BASE_REF" HEAD) - modified=() - unchanged=() + cd tests/lib/sdk for path in */*/*/; do - if git diff --quiet "${RANGE}" -- "$path"; then - unchanged+=("${path%/}") - else - modified+=("${path%/}") + if git diff --quiet "${base}..HEAD" -- "$path"; then + continue fi - done - - maybe_list() { - message="$1" - shift - if [ $# -gt 0 ]; then - printf '%s:\n' "$message" - for path in "$@"; do - printf ' %s\n' "$path" - done + if git cat-file -t "${base}:./${path}" &> /dev/null; then + printf '::error::%s SDK is immutable, create a new SDK instead.\n' "${path%/}" + exit 1 fi - } - - maybe_list 'Modified SDKs' "${modified[@]}" - maybe_list 'Unmodified SDKs' "${unchanged[@]}" + done + cd .. + + sdks=() + if [ -d staging ]; then + cd staging + for path in */*/*/; do + if git cat-file -t "${base}:../sdk/${path}" &> /dev/null; then + printf '::error::%s SDK is immutable, create a new SDK instead.\n' "${path%/}" + exit 1 + fi + sdks+=("${path%/}") + done + fi - print_matrix() { - printf '%s=' "$1" - shift - for path in "$@"; do - IFS='/' read -r -a parts <<<"$path" + { + printf 'sdks=' + for sdk in "${sdks[@]}"; do + IFS='/' read -r -a parts <<<"$sdk" platforms='["amd64"]' - if [ "${parts[0]}" = test-sdk-info ]; then - platforms='["all"]' + if [ -f "${sdk}/platforms.json" ]; then + platforms=$(< "${sdk}/platforms.json") fi jq --null-input \ - --arg name "${parts[0]}" \ + --arg path "tests/lib/staging/$sdk" \ --arg track "${parts[1]}" \ --arg risk "${parts[2]}" \ - --arg platforms "$platforms" \ + --argjson platforms "$platforms" \ '$ARGS.named' done | jq --compact-output --slurp . - } - - print_matrix modified "${modified[@]}" >> "$GITHUB_OUTPUT" - print_matrix unchanged "${unchanged[@]}" >> "$GITHUB_OUTPUT" + } >> "$GITHUB_OUTPUT" shell: bash - env: - RANGE: ${{ steps.base.outputs.range }} build-test-sdk: needs: detect-sdk-changes - if: needs.detect-sdk-changes.outputs.modified != '[]' + if: needs.detect-sdk-changes.outputs.sdks != '[]' strategy: matrix: - include: ${{ fromJSON(needs.detect-sdk-changes.outputs.modified) }} + include: ${{ fromJSON(needs.detect-sdk-changes.outputs.sdks) }} fail-fast: false uses: canonical/sdkcraft-actions/.github/workflows/upload.yml@92f5df60c0878aea4844a7380fdfd24063f89ac9 with: - platforms: ${{ matrix.platforms }} - subdirectory: tests/lib/sdk/${{ matrix.name }}/${{ matrix.track }}/${{ matrix.risk }} + platforms: ${{ toJSON(matrix.platforms) }} + subdirectory: ${{ matrix.path }} track: ${{ matrix.track }} risk: ${{ matrix.risk }} - branch: ${{ needs.detect-sdk-changes.outputs.branch }} secrets: SDKCRAFT_STORE_CREDENTIALS: ${{ secrets.SDKCRAFT_STORE_CREDENTIALS }} - - release-test-sdks: - needs: detect-sdk-changes - if: github.event_name == 'pull_request' && needs.detect-sdk-changes.outputs.unchanged != '[]' - runs-on: [self-hosted, linux, noble, x64, xlarge] - steps: - - name: Install sdkcraft - run: sudo snap install sdkcraft --edge --classic - - - name: Release test SDKs - run: | - mapfile -d '' -t sdks < <(jq --compact-output --raw-output0 '.[]' <<<"$SDKS") - - for sdk in "${sdks[@]}"; do - name=$(jq --raw-output .name <<<"$sdk") - track=$(jq --raw-output .track <<<"$sdk") - risk=$(jq --raw-output .risk <<<"$sdk") - - printf '::group::Releasing %s SDK to %s/%s/%s\n' "$name" "$track" "$risk" "$BRANCH" - - curl -fsSLo info.json --retry 5 "https://api.staging.pkg.store/v2/sdks/info/$name?fields=channel-map,revision" - - filter='."channel-map"[] | select(.channel.name == $channel) | .revision.revision' - jq --arg channel "${track}/${risk}" --raw-output0 "$filter" < info.json | sort --numeric-sort --zero-terminated > revisions - - mapfile -d '' -t revisions < revisions - for rev in "${revisions[@]}"; do - sdkcraft release "$name" "$rev" "${track}/${risk}/${BRANCH}" - done - - printf '::endgroup::\n' - done - shell: bash - env: - SDKS: ${{ needs.detect-sdk-changes.outputs.unchanged }} - BRANCH: ${{ needs.detect-sdk-changes.outputs.branch }} - SDKCRAFT_STORE_CREDENTIALS: ${{ secrets.SDKCRAFT_STORE_CREDENTIALS }} # zizmor: ignore[secrets-outside-env] diff --git a/.github/workflows/cover.yaml b/.github/workflows/cover.yaml index 3a210b496..c8b5670fb 100644 --- a/.github/workflows/cover.yaml +++ b/.github/workflows/cover.yaml @@ -26,7 +26,7 @@ jobs: uses: ./.github/workflows/spread.yaml secrets: STORE_CREDENTIALS: ${{ secrets.STORE_CREDENTIALS }} - SDKCRAFT_STORE_CREDENTIALS: ${{ secrets.SDKCRAFT_STORE_CREDENTIALS_STAGING }} + SDKCRAFT_STORE_CREDENTIALS: ${{ secrets.SDKCRAFT_STORE_CREDENTIALS_PROD }} unit-tests: uses: ./.github/workflows/unit-tests.yaml diff --git a/.github/workflows/lxd-candidate-check.yaml b/.github/workflows/lxd-candidate-check.yaml index 0d8cf4042..fbb1e746e 100644 --- a/.github/workflows/lxd-candidate-check.yaml +++ b/.github/workflows/lxd-candidate-check.yaml @@ -11,7 +11,7 @@ jobs: build-deps: uses: ./.github/workflows/build-deps.yaml secrets: - SDKCRAFT_STORE_CREDENTIALS: ${{ secrets.SDKCRAFT_STORE_CREDENTIALS_STAGING }} + SDKCRAFT_STORE_CREDENTIALS: ${{ secrets.SDKCRAFT_STORE_CREDENTIALS_PROD }} snap-tests: needs: [build-deps] strategy: diff --git a/.github/workflows/spread.yaml b/.github/workflows/spread.yaml index 3e86292d8..80568e7c4 100644 --- a/.github/workflows/spread.yaml +++ b/.github/workflows/spread.yaml @@ -71,8 +71,6 @@ jobs: run: | mkdir ${{ github.workspace }}.cover spread -artifacts=${{ github.workspace }}.cover tests/${{matrix.suite}} - env: - TEST_SDK_BRANCH: ${{ needs.build-deps.outputs.test_sdk_branch }} - name: Trim trailing slash from matrix.suite id: sanitize env: diff --git a/.github/workflows/staging.yaml b/.github/workflows/staging.yaml new file mode 100644 index 000000000..7e69ba675 --- /dev/null +++ b/.github/workflows/staging.yaml @@ -0,0 +1,25 @@ +name: Check for staged SDKs + +on: + pull_request: + +permissions: + contents: read + +jobs: + check: + name: Check for staged SDKs + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Check for staged SDKs + run: | + if [ -d tests/lib/staging ]; then + printf '::error::Found staged SDKs, please move them to tests/lib/sdk before merging.\n' + exit 1 + fi diff --git a/.spread.yaml b/.spread.yaml index b6c4f2b31..43cab6482 100644 --- a/.spread.yaml +++ b/.spread.yaml @@ -6,7 +6,6 @@ environment: PATH: $PATH:~/go/bin PROJECT_PATH: /workshop TESTSLIB: $PROJECT_PATH/tests/lib - TEST_SDK_BRANCH: $(HOST:printf %s "${TEST_SDK_BRANCH:+/$TEST_SDK_BRANCH}") IMAGE_SERVER: $(HOST:echo -n $WORKSHOP_IMAGE_SERVER) repack: | test -f tests/workshop_*.snap || snapcraft pack -o tests/ diff --git a/internal/sdkstore/client.go b/internal/sdkstore/client.go index a7141ab40..8fad09d0a 100644 --- a/internal/sdkstore/client.go +++ b/internal/sdkstore/client.go @@ -17,7 +17,7 @@ const ( // DefaultServerURL is the default location of the global SDK Store API. // An alternate location can be configured by changing the URL // field in the Config struct. - DefaultServerURL = "https://api.staging.pkg.store" + DefaultServerURL = "https://api.pkg.store" // RefreshTimeout is the timout callers should use for Refresh calls. RefreshTimeout = 10 * time.Second diff --git a/internal/sdkstore/download_integration_test.go b/internal/sdkstore/download_integration_test.go index 758c9a654..9b6a074ad 100644 --- a/internal/sdkstore/download_integration_test.go +++ b/internal/sdkstore/download_integration_test.go @@ -19,10 +19,10 @@ func (f *downloadIntegration) TestDownload(c *check.C) { client := NewClient(Config{}) hash := sha3.New384() sdk := SdkArchive{ - Name: "test-sdk-info", - PackageID: "U9IBEcXiDkuaPLYjyUBpRZMETAr7g39e", - Revision: 1, - Sha3_384: "cf722fc841c72cf53c4b2db88608589efb173fa2a50837ae6f07597ead85e6e30f36a85e98df9ba78d941b3c9e15ab3d", + Name: "test-sdk-info-1", + PackageID: "96TKss360WoMRcFySGMOMhXhwbDdZh0E", + Revision: 2, + Sha3_384: "22e773fd5f052fbfcb077788e5a13b81415ff6f8f7bfd0a65cc19f3ce854054fd1090c04937d420cde5645c2940e29e3", } err := client.Download(context.Background(), hash, sdk) @@ -38,7 +38,7 @@ func (f *downloadIntegration) TestDownloadNotFound(c *check.C) { Name: "not-found", PackageID: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", Revision: 55555, - Sha3_384: "cf722fc841c72cf53c4b2db88608589efb173fa2a50837ae6f07597ead85e6e30f36a85e98df9ba78d941b3c9e15ab3d", + Sha3_384: "22e773fd5f052fbfcb077788e5a13b81415ff6f8f7bfd0a65cc19f3ce854054fd1090c04937d420cde5645c2940e29e3", } err := client.Download(context.Background(), &buffer, sdk) diff --git a/internal/sdkstore/find_integration_test.go b/internal/sdkstore/find_integration_test.go index 3f0d06523..02e13b45f 100644 --- a/internal/sdkstore/find_integration_test.go +++ b/internal/sdkstore/find_integration_test.go @@ -5,6 +5,8 @@ package sdkstore import ( "context" "encoding/json" + "reflect" + "slices" "gopkg.in/check.v1" @@ -18,7 +20,7 @@ var _ = check.Suite(&findIntegration{}) func (f *findIntegration) TestSdkFind(c *check.C) { client := NewClient(Config{}) var response any - err := client.find(context.Background(), &response, "test-sdk-find-1") + err := client.find(context.Background(), &response, "test-sdk-find-2") c.Assert(err, check.IsNil) var expected any @@ -36,7 +38,7 @@ func (f *findIntegration) TestSdkFindWithPlatform(c *check.C) { client := NewClient(Config{}) var response any - err := client.find(context.Background(), &response, "test-sdk-find-1", WithFindFields(allFindFields), WithFindPlatforms(platforms)) + err := client.find(context.Background(), &response, "test-sdk-find-2", WithFindFields(allFindFields), WithFindPlatforms(platforms)) c.Assert(err, check.IsNil) var expected any @@ -54,20 +56,24 @@ func (f *findIntegration) TestSdkFindByPublisher(c *check.C) { }} client := NewClient(Config{}) - var response any - err := client.find(context.Background(), &response, "jeconder", WithFindFields(allFindFields), WithFindPlatforms(platforms)) + responses, err := client.Find(context.Background(), "dlyfar", WithFindFields(allFindFields), WithFindPlatforms(platforms)) c.Assert(err, check.IsNil) - var expected any + var expected transport.FindResponses err = json.Unmarshal(testSdkFindS390XRaw, &expected) c.Assert(err, check.IsNil) - c.Check(response, check.DeepEquals, expected) + c.Assert(expected.Results, check.HasLen, 1) + + found := slices.ContainsFunc(responses, func(r transport.FindResponse) bool { + return reflect.DeepEqual(r, expected.Results[0]) + }) + c.Check(found, check.Equals, true) } func (f *findIntegration) TestSdkFindByTitle(c *check.C) { client := NewClient(Config{}) var response any - err := client.find(context.Background(), &response, "Test SDK s390x title", WithFindFields(allFindFields)) + err := client.find(context.Background(), &response, "Test SDK find 2 s390x title", WithFindFields(allFindFields)) c.Assert(err, check.IsNil) var expected any @@ -79,7 +85,7 @@ func (f *findIntegration) TestSdkFindByTitle(c *check.C) { func (f *findIntegration) TestSdkFindBySummary(c *check.C) { client := NewClient(Config{}) var response any - err := client.find(context.Background(), &response, "Test SDK s390x summary", WithFindFields(allFindFields)) + err := client.find(context.Background(), &response, "Test SDK find 2 s390x summary", WithFindFields(allFindFields)) c.Assert(err, check.IsNil) var expected any @@ -91,7 +97,7 @@ func (f *findIntegration) TestSdkFindBySummary(c *check.C) { func (f *findIntegration) TestSdkFindByDescription(c *check.C) { client := NewClient(Config{}) var response any - err := client.find(context.Background(), &response, "Test SDK s390x description", WithFindFields(allFindFields)) + err := client.find(context.Background(), &response, "Test SDK find 2 s390x description", WithFindFields(allFindFields)) c.Assert(err, check.IsNil) var expected any diff --git a/internal/sdkstore/find_test.go b/internal/sdkstore/find_test.go index c4c3d05e9..25891e98d 100644 --- a/internal/sdkstore/find_test.go +++ b/internal/sdkstore/find_test.go @@ -43,7 +43,7 @@ func (s *FindSuite) TestFind(c *check.C) { baseURL := MustParseURL(c, "http://api.foo.bar") path := path.MakePath(baseURL) - query := "test-sdk-find" + query := "test-sdk-find-2" restClient := NewMockRESTClient(ctrl) s.expectGet(c, restClient, path, query) @@ -62,7 +62,7 @@ func (s *FindSuite) TestFindWithOptions(c *check.C) { baseURL := MustParseURL(c, "http://api.foo.bar") path := path.MakePath(baseURL) - query := "test-sdk-find" + query := "test-sdk-find-2" expect, err := path.Query("categories", "ide,language") c.Assert(err, check.IsNil) @@ -91,7 +91,7 @@ func (s *FindSuite) TestFindFailure(c *check.C) { baseURL := MustParseURL(c, "http://api.foo.bar") path := path.MakePath(baseURL) - query := "test-sdk-find" + query := "test-sdk-find-2" restClient := NewMockRESTClient(ctrl) s.expectGetFailure(restClient) @@ -125,7 +125,7 @@ func (s *FindSuite) expectGetFailure(client *MockRESTClient) { func (s *FindSuite) TestFindRequestPayload(c *check.C) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.Check(r.URL.Path, check.Equals, "/v2/sdks/find") - c.Check(r.URL.Query()["q"], check.DeepEquals, []string{"test-sdk-find"}) + c.Check(r.URL.Query()["q"], check.DeepEquals, []string{"test-sdk-find-2"}) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _, err := w.Write(testSdkFindRaw) @@ -142,7 +142,7 @@ func (s *FindSuite) TestFindRequestPayload(c *check.C) { restClient := newHTTPRESTClient(apiRequester) client := newFindClient(findPath, restClient) - responses, err := client.Find(context.Background(), "test-sdk-find") + responses, err := client.Find(context.Background(), "test-sdk-find-2") c.Assert(err, check.IsNil) var expected any diff --git a/internal/sdkstore/info_integration_test.go b/internal/sdkstore/info_integration_test.go index b1717150a..e1855dc28 100644 --- a/internal/sdkstore/info_integration_test.go +++ b/internal/sdkstore/info_integration_test.go @@ -16,7 +16,7 @@ var _ = check.Suite(&infoIntegration{}) func (f *infoIntegration) TestSdkInfo(c *check.C) { client := NewClient(Config{}) var response any - err := client.info(context.Background(), &response, "test-sdk-info") + err := client.info(context.Background(), &response, "test-sdk-info-1") c.Assert(err, check.IsNil) var expected any @@ -28,7 +28,7 @@ func (f *infoIntegration) TestSdkInfo(c *check.C) { func (f *infoIntegration) TestSdkInfoMultiBase(c *check.C) { client := NewClient(Config{}) var response any - err := client.info(context.Background(), &response, "test-sdk-info-multi-base", WithInfoFields(allInfoFields)) + err := client.info(context.Background(), &response, "test-sdk-info-multi-base-1", WithInfoFields(allInfoFields)) c.Assert(err, check.IsNil) var expected any diff --git a/internal/sdkstore/resolve_integration_test.go b/internal/sdkstore/resolve_integration_test.go index bbd62dcd0..8a8c5b0dd 100644 --- a/internal/sdkstore/resolve_integration_test.go +++ b/internal/sdkstore/resolve_integration_test.go @@ -22,12 +22,12 @@ func (f *resolveIntegration) TestResolveByName(c *check.C) { Packages: []transport.ResolvePackage{{ InstanceKey: "random123", Namespace: "sdk", - Name: "test-sdk-info-multi-base", - Channel: "latest/stable", + Name: "test-sdk-info-multi-base-1", + Channel: "latest/edge", Platform: transport.Platform{ Name: "ubuntu", Channel: "22.04", - Architecture: "amd64", + Architecture: "riscv64", }, }}, } @@ -48,12 +48,12 @@ func (f *resolveIntegration) TestResolveByID(c *check.C) { Packages: []transport.ResolvePackage{{ InstanceKey: "random456", Namespace: "sdk", - ID: "ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1", - Channel: "latest/edge", + ID: "x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG", + Channel: "latest/stable", Platform: transport.Platform{ Name: "ubuntu", - Channel: "24.04", - Architecture: "riscv64", + Channel: "20.04", + Architecture: "amd64", }, }}, } diff --git a/internal/sdkstore/test-resolve-id.raw.json b/internal/sdkstore/test-resolve-id.raw.json index 11df6ac03..7ceb8cb0d 100644 --- a/internal/sdkstore/test-resolve-id.raw.json +++ b/internal/sdkstore/test-resolve-id.raw.json @@ -4,38 +4,38 @@ "instance-key": "random456", "status": "ok", "namespace": "sdk", - "name": "test-sdk-info-multi-base", - "id": "ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1", + "name": "test-sdk-info-multi-base-1", + "id": "x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG", "result": { "channel": { - "name": "edge", - "released-at": "2026-03-11T00:13:19.621866", + "name": "stable", + "released-at": "2026-04-15T04:03:38.694727", "track": null, - "risk": "edge", + "risk": "stable", "branch": null, - "effective-channel": "edge", + "effective-channel": "stable", "platform": { "name": "ubuntu", - "channel": "24.04", - "architecture": "riscv64" + "channel": "20.04", + "architecture": "amd64" } }, "revision": { - "created-at": "2026-03-11T00:13:15.679272", + "created-at": "2026-04-15T04:03:37.224647", "platforms": [ { "name": "ubuntu", - "channel": "24.04", - "architecture": "riscv64" + "channel": "20.04", + "architecture": "amd64" } ], "download": { - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_13.sdk", - "size": 480, - "sha3-384": "6e4067003e140090d017872a8a730a1205679d388efde0e2eff6ac4e89e639593ae0f4665f361bb52d704e8ec670be06" + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_19.sdk", + "size": 456, + "sha3-384": "4099fb92c71c5075364fdba4e451b545a8cb1ee0946bacaddef5f0682f2070385de9cf8898571eb91b14c99d0f07ea98" }, - "revision": 13, - "version": "0.3.1" + "revision": 19, + "version": "0.2.1" } } } diff --git a/internal/sdkstore/test-resolve-name.json b/internal/sdkstore/test-resolve-name.json index bcf9ee7a7..45e4e74aa 100644 --- a/internal/sdkstore/test-resolve-name.json +++ b/internal/sdkstore/test-resolve-name.json @@ -4,38 +4,38 @@ "instance-key": "random123", "status": "ok", "namespace": "sdk", - "name": "test-sdk-info-multi-base", - "id": "ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1", + "name": "test-sdk-info-multi-base-1", + "id": "x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG", "result": { "channel": { - "name": "stable", - "released-at": "2026-03-10T23:48:44.327082Z", + "name": "edge", + "released-at": "2026-04-15T03:46:16.565275Z", "track": "", - "risk": "stable", + "risk": "edge", "branch": "", - "effective-channel": "stable", + "effective-channel": "edge", "platform": { "name": "ubuntu", "channel": "22.04", - "architecture": "amd64" + "architecture": "riscv64" } }, "revision": { - "created-at": "2026-03-10T23:16:31.917435Z", + "created-at": "2026-04-15T03:46:15.158932Z", "platforms": [ { "name": "ubuntu", "channel": "22.04", - "architecture": "amd64" + "architecture": "riscv64" } ], "download": { - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_2.sdk", - "size": 464, - "sha3-384": "59606df612d0290225f427935ac300437542a3c3024c52a0fb2bc5ae486ff801855f571ab8ecfe5f14f16b051d97673e" + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_17.sdk", + "size": 460, + "sha3-384": "8a002dbaa099217fd304cfc04c828afd9b35e079cb27961c3537ec83e422ee0e9ddc0df43d9a98e8a59f55db32c5ea4f" }, - "revision": 2, - "version": "0.2" + "revision": 17, + "version": "0.3" } } } diff --git a/internal/sdkstore/test-resolve-name.raw.json b/internal/sdkstore/test-resolve-name.raw.json index 12ec458a7..3805df8f3 100644 --- a/internal/sdkstore/test-resolve-name.raw.json +++ b/internal/sdkstore/test-resolve-name.raw.json @@ -4,38 +4,38 @@ "instance-key": "random123", "status": "ok", "namespace": "sdk", - "name": "test-sdk-info-multi-base", - "id": "ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1", + "name": "test-sdk-info-multi-base-1", + "id": "x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG", "result": { "channel": { - "name": "stable", - "released-at": "2026-03-10T23:48:44.327082", + "name": "edge", + "released-at": "2026-04-15T03:46:16.565275", "track": null, - "risk": "stable", + "risk": "edge", "branch": null, - "effective-channel": "stable", + "effective-channel": "edge", "platform": { "name": "ubuntu", "channel": "22.04", - "architecture": "amd64" + "architecture": "riscv64" } }, "revision": { - "created-at": "2026-03-10T23:16:31.917435", + "created-at": "2026-04-15T03:46:15.158932", "platforms": [ { "name": "ubuntu", "channel": "22.04", - "architecture": "amd64" + "architecture": "riscv64" } ], "download": { - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_2.sdk", - "size": 464, - "sha3-384": "59606df612d0290225f427935ac300437542a3c3024c52a0fb2bc5ae486ff801855f571ab8ecfe5f14f16b051d97673e" + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_17.sdk", + "size": 460, + "sha3-384": "8a002dbaa099217fd304cfc04c828afd9b35e079cb27961c3537ec83e422ee0e9ddc0df43d9a98e8a59f55db32c5ea4f" }, - "revision": 2, - "version": "0.2" + "revision": 17, + "version": "0.3" } } } diff --git a/internal/sdkstore/test-sdk-find-s390x.json b/internal/sdkstore/test-sdk-find-s390x.json index 467855962..ae28c06ad 100644 --- a/internal/sdkstore/test-sdk-find-s390x.json +++ b/internal/sdkstore/test-sdk-find-s390x.json @@ -1,17 +1,17 @@ [ { - "name": "test-sdk-find-1-s390x", - "package-id": "dP9SNAfRBf8IL0w1OD8tuGEViVSBKji6", + "name": "test-sdk-find-2-s390x", + "package-id": "2HCXsaC6jxhgGL9As3FT6xvXShlQLV8D", "metadata": { - "description": "Test SDK s390x description", + "description": "Test SDK find 2 s390x description", "license": "AGPL-3.0", "publisher": { - "display-name": "Jonathan Conder", - "id": "YnyjkYo9nA6AlXsTKnpxrCGxNZgmSdmu", - "username":"usso-jeconder", - "validation":"unproven" + "display-name": "Dmitry Lyfar", + "id": "rerCzOoMKcwXbFbziPbLdrxjmF36PCRd", + "username": "dlyfar", + "validation": "unproven" }, - "summary": "Test SDK s390x summary" + "summary": "Test SDK find 2 s390x summary" }, "default-release": { "channel": { @@ -23,10 +23,10 @@ "channel": "all", "name": "all" }, - "released-at": "2026-03-19T08:24:40.960058Z" + "released-at": "2026-04-16T00:53:58.154365Z" }, "revision": 1, - "version": "0.1+s390x" + "version": "0.2+s390x" } } ] diff --git a/internal/sdkstore/test-sdk-find-s390x.raw.json b/internal/sdkstore/test-sdk-find-s390x.raw.json index b6e78c56e..63276d1cb 100644 --- a/internal/sdkstore/test-sdk-find-s390x.raw.json +++ b/internal/sdkstore/test-sdk-find-s390x.raw.json @@ -1,21 +1,21 @@ { "results": [ { - "name": "test-sdk-find-1-s390x", - "package-id": "dP9SNAfRBf8IL0w1OD8tuGEViVSBKji6", + "name": "test-sdk-find-2-s390x", + "package-id": "2HCXsaC6jxhgGL9As3FT6xvXShlQLV8D", "metadata": { "contact": "", - "description": "Test SDK s390x description", + "description": "Test SDK find 2 s390x description", "license": "AGPL-3.0", "links": {}, "media": [], "publisher": { - "display-name": "Jonathan Conder", - "id": "YnyjkYo9nA6AlXsTKnpxrCGxNZgmSdmu", - "username":"usso-jeconder", - "validation":"unproven" + "display-name": "Dmitry Lyfar", + "id": "rerCzOoMKcwXbFbziPbLdrxjmF36PCRd", + "username": "dlyfar", + "validation": "unproven" }, - "summary": "Test SDK s390x summary" + "summary": "Test SDK find 2 s390x summary" }, "default-release": { "channel": { @@ -27,10 +27,10 @@ "channel": "all", "name": "all" }, - "released-at": "2026-03-19T08:24:40.960058+00:00" + "released-at": "2026-04-16T00:53:58.154365+00:00" }, "revision": 1, - "version": "0.1+s390x" + "version": "0.2+s390x" } } ] diff --git a/internal/sdkstore/test-sdk-find.json b/internal/sdkstore/test-sdk-find.json index 03a3eb355..13ed0b9ff 100644 --- a/internal/sdkstore/test-sdk-find.json +++ b/internal/sdkstore/test-sdk-find.json @@ -1,17 +1,17 @@ [ { - "name": "test-sdk-find-1", - "package-id": "geGY07WPXyvnQahmRP1oOegGUyjurXrY", + "name": "test-sdk-find-2", + "package-id": "n7hVZOSBZPKduUovbKF8UEIGzxQkcvr5", "metadata": { - "description": "Test SDK description", + "description": "Test SDK find 2 description", "license": "AGPL-3.0", "publisher": { - "display-name": "Jonathan Conder", - "id": "YnyjkYo9nA6AlXsTKnpxrCGxNZgmSdmu", - "username":"usso-jeconder", - "validation":"unproven" + "display-name": "Dmitry Lyfar", + "id": "rerCzOoMKcwXbFbziPbLdrxjmF36PCRd", + "username": "dlyfar", + "validation": "unproven" }, - "summary": "Test SDK summary" + "summary": "Test SDK find 2 summary" }, "default-release": { "channel": { @@ -19,29 +19,29 @@ "track": "latest", "risk": "edge", "platform": { - "architecture": "amd64", - "channel": "22.04", - "name": "ubuntu" + "architecture": "all", + "channel": "all", + "name": "all" }, - "released-at": "2026-03-19T07:54:22.067952Z" + "released-at": "2026-04-16T01:03:09.395553Z" }, "revision": 1, - "version": "0.1.1" + "version": "0.2" } }, { - "name": "test-sdk-find-1-s390x", - "package-id": "dP9SNAfRBf8IL0w1OD8tuGEViVSBKji6", + "name": "test-sdk-find-2-s390x", + "package-id": "2HCXsaC6jxhgGL9As3FT6xvXShlQLV8D", "metadata": { - "description": "Test SDK s390x description", + "description": "Test SDK find 2 s390x description", "license": "AGPL-3.0", "publisher": { - "display-name": "Jonathan Conder", - "id": "YnyjkYo9nA6AlXsTKnpxrCGxNZgmSdmu", - "username":"usso-jeconder", - "validation":"unproven" + "display-name": "Dmitry Lyfar", + "id": "rerCzOoMKcwXbFbziPbLdrxjmF36PCRd", + "username": "dlyfar", + "validation": "unproven" }, - "summary": "Test SDK s390x summary" + "summary": "Test SDK find 2 s390x summary" }, "default-release": { "channel": { @@ -53,10 +53,10 @@ "channel": "all", "name": "all" }, - "released-at": "2026-03-19T08:24:40.960058Z" + "released-at": "2026-04-16T00:53:58.154365Z" }, "revision": 1, - "version": "0.1+s390x" + "version": "0.2+s390x" } } ] diff --git a/internal/sdkstore/test-sdk-find.raw.json b/internal/sdkstore/test-sdk-find.raw.json index 41bfe8fb3..d97a6aa18 100644 --- a/internal/sdkstore/test-sdk-find.raw.json +++ b/internal/sdkstore/test-sdk-find.raw.json @@ -1,18 +1,18 @@ { "results": [ { - "name": "test-sdk-find-1", - "package-id": "geGY07WPXyvnQahmRP1oOegGUyjurXrY", + "name": "test-sdk-find-2", + "package-id": "n7hVZOSBZPKduUovbKF8UEIGzxQkcvr5", "metadata": { - "description": "Test SDK description", + "description": "Test SDK find 2 description", "license": "AGPL-3.0", "publisher": { - "display-name": "Jonathan Conder", - "id": "YnyjkYo9nA6AlXsTKnpxrCGxNZgmSdmu", - "username":"usso-jeconder", - "validation":"unproven" + "display-name": "Dmitry Lyfar", + "id": "rerCzOoMKcwXbFbziPbLdrxjmF36PCRd", + "username": "dlyfar", + "validation": "unproven" }, - "summary": "Test SDK summary" + "summary": "Test SDK find 2 summary" }, "default-release": { "channel": { @@ -20,29 +20,29 @@ "track": "latest", "risk": "edge", "platform": { - "architecture": "amd64", - "channel": "22.04", - "name": "ubuntu" + "architecture": "all", + "channel": "all", + "name": "all" }, - "released-at": "2026-03-19T07:54:22.067952+00:00" + "released-at": "2026-04-16T01:03:09.395553+00:00" }, "revision": 1, - "version": "0.1.1" + "version": "0.2" } }, { - "name": "test-sdk-find-1-s390x", - "package-id": "dP9SNAfRBf8IL0w1OD8tuGEViVSBKji6", + "name": "test-sdk-find-2-s390x", + "package-id": "2HCXsaC6jxhgGL9As3FT6xvXShlQLV8D", "metadata": { - "description": "Test SDK s390x description", + "description": "Test SDK find 2 s390x description", "license": "AGPL-3.0", "publisher": { - "display-name": "Jonathan Conder", - "id": "YnyjkYo9nA6AlXsTKnpxrCGxNZgmSdmu", - "username":"usso-jeconder", - "validation":"unproven" + "display-name": "Dmitry Lyfar", + "id": "rerCzOoMKcwXbFbziPbLdrxjmF36PCRd", + "username": "dlyfar", + "validation": "unproven" }, - "summary": "Test SDK s390x summary" + "summary": "Test SDK find 2 s390x summary" }, "default-release": { "channel": { @@ -54,10 +54,10 @@ "channel": "all", "name": "all" }, - "released-at": "2026-03-19T08:24:40.960058+00:00" + "released-at": "2026-04-16T00:53:58.154365+00:00" }, "revision": 1, - "version": "0.1+s390x" + "version": "0.2+s390x" } } ] diff --git a/internal/sdkstore/test-sdk-info-multi-base.json b/internal/sdkstore/test-sdk-info-multi-base.json index e8aff3425..0a8dbdde3 100644 --- a/internal/sdkstore/test-sdk-info-multi-base.json +++ b/internal/sdkstore/test-sdk-info-multi-base.json @@ -1,37 +1,23 @@ { - "name": "test-sdk-info-multi-base", - "package-id": "ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1", - "metadata": { - "description": "Short description", - "license": "LGPL-3.0", - "publisher": { - "display-name": "Jonathan Conder", - "id": "YnyjkYo9nA6AlXsTKnpxrCGxNZgmSdmu", - "username":"usso-jeconder", - "validation":"unproven" - }, - "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title" - }, "channel-map": [ { "channel": { "name": "latest/stable", - "track": "latest", - "risk": "stable", "platform": { "architecture": "amd64", "channel": "20.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:48:34.474879Z" + "released-at": "2026-04-15T04:03:38.694727Z", + "risk": "stable", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:16:19.894258Z", + "created-at": "2026-04-15T04:03:37.224647Z", "download": { - "sha3-384": "f0ed20952899d915e1796cadf28b9b03894c5e70a0900ac07e869fc6e5c35b11ace2374eab7ffb70bc7f9120aedb3d67", - "size": 466, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_1.sdk" + "sha3-384": "4099fb92c71c5075364fdba4e451b545a8cb1ee0946bacaddef5f0682f2070385de9cf8898571eb91b14c99d0f07ea98", + "size": 456, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_19.sdk" }, "platforms": [ { @@ -40,42 +26,48 @@ "name": "ubuntu" } ], - "revision": 1, + "revision": 19, "sdk-yaml": { "architecture": "amd64", "base": "ubuntu@20.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:13:46.850996Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T03:59:51.904474Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", - "version": "0.2" + "title": "Test SDK info multi-base title", + "version": "0.2.1" }, - "version": "0.2" + "version": "0.2.1" } }, { "channel": { "name": "latest/stable", - "track": "latest", - "risk": "stable", "platform": { "architecture": "amd64", "channel": "22.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:48:44.327082Z" + "released-at": "2026-04-15T04:03:41.880103Z", + "risk": "stable", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:16:31.917435Z", + "created-at": "2026-04-15T04:03:40.049099Z", "download": { - "sha3-384": "59606df612d0290225f427935ac300437542a3c3024c52a0fb2bc5ae486ff801855f571ab8ecfe5f14f16b051d97673e", - "size": 464, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_2.sdk" + "sha3-384": "18b9c9274861a17251ecfd64c2d161f48b5eb50e97507ed8cb9d16dc79d9c721b66d80de8ac2d89b5ac413f811fb720b", + "size": 454, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_20.sdk" }, "platforms": [ { @@ -84,19 +76,25 @@ "name": "ubuntu" } ], - "revision": 2, + "revision": 20, "sdk-yaml": { "architecture": "amd64", "base": "ubuntu@22.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:14:01.728232Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T04:01:54.141587Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.2" }, "version": "0.2" @@ -105,21 +103,21 @@ { "channel": { "name": "latest/stable", - "track": "latest", - "risk": "stable", "platform": { "architecture": "amd64", "channel": "24.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:48:51.554637Z" + "released-at": "2026-04-15T04:03:45.57885Z", + "risk": "stable", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:16:42.684241Z", + "created-at": "2026-04-15T04:03:44.05346Z", "download": { - "sha3-384": "4a571977571287d12a0de8efd0fb9bdd63d57bf733a79ed191914e95db3718dcc976450b2da4d3831782fd5747031b66", - "size": 463, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_3.sdk" + "sha3-384": "675fba79b17b63f9b0bf01cfae25feb53f057a6b803600fe6834015e345d4edc8824554f47666bc915aa83f141cc7314", + "size": 452, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_21.sdk" }, "platforms": [ { @@ -128,19 +126,25 @@ "name": "ubuntu" } ], - "revision": 3, + "revision": 21, "sdk-yaml": { "architecture": "amd64", "base": "ubuntu@24.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:14:16.910778Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T04:03:34.056652Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.2" }, "version": "0.2" @@ -149,21 +153,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "amd64", "channel": "20.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:49:09.397396Z" + "released-at": "2026-04-15T04:18:53.921839Z", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:49:07.605786Z", + "created-at": "2026-04-15T04:18:52.23704Z", "download": { - "sha3-384": "79b3bb7b80a2c575748dda0c97a8d947ee567cef1ea5ac7e5cf9ddf8a32032e19be461bd9374188b5b0b1fbef59b4120", - "size": 486, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_4.sdk" + "sha3-384": "1959ab8624b4293eef74ad8e85dc3f34a03142b957174baa465d36fe8833ca94c23250dc957ab087828d6e9902c9a252", + "size": 460, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_22.sdk" }, "platforms": [ { @@ -172,19 +176,25 @@ "name": "ubuntu" } ], - "revision": 4, + "revision": 22, "sdk-yaml": { "architecture": "amd64", "base": "ubuntu@20.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:31:44.177639Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T04:15:50.994937Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -193,21 +203,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "arm64", "channel": "20.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:49:37.506334Z" + "released-at": "2026-04-15T03:29:56.027154Z", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:49:33.890123Z", + "created-at": "2026-04-15T03:29:54.409356Z", "download": { - "sha3-384": "c17e31dd829abb3914c09b6e5d45b3b84c960232310c643987659b939204d11b474f1f9e1c04c829b65494e3056301e3", - "size": 468, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_7.sdk" + "sha3-384": "50d670000d05a468cfadd4da1fd1ce316ff752dd3a4f7f47243c127a04d7d7489c2be83dbe839af380580892a0af92eb", + "size": 467, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_13.sdk" }, "platforms": [ { @@ -216,19 +226,25 @@ "name": "ubuntu" } ], - "revision": 7, + "revision": 13, "sdk-yaml": { "architecture": "arm64", "base": "ubuntu@20.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:32:09.984597Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T03:26:56.879397Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -237,21 +253,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "riscv64", "channel": "20.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:50:09.178916Z" + "released-at": "2026-04-15T03:46:13.344116Z", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:50:05.426185Z", + "created-at": "2026-04-15T03:46:11.814555Z", "download": { - "sha3-384": "03ac4741a33fbce17646dc4e8acb691bf2131a814234de08ce376ca88af3d995cc52e37609d5efe51e16f40fcd763d8d", - "size": 469, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_10.sdk" + "sha3-384": "7ed098fe9e4cec8978714c5c2f34d5e9b620ba4f88cb650aaac680cda97539d08eb3f119e068da24a9c6968d862d2c56", + "size": 459, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_16.sdk" }, "platforms": [ { @@ -260,19 +276,25 @@ "name": "ubuntu" } ], - "revision": 10, + "revision": 16, "sdk-yaml": { "architecture": "riscv64", "base": "ubuntu@20.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:32:52.730823Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T03:42:25.358095Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -281,21 +303,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "amd64", "channel": "22.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:49:18.751516Z" + "released-at": "2026-04-15T04:18:57.140822Z", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:49:14.930412Z", + "created-at": "2026-04-15T04:18:55.473393Z", "download": { - "sha3-384": "5c17154e8881e275abc908456aad707c9fe824ed37639789499fbad7b271a5a87442e6ec8071aeb8a2656c0a3f02dc62", - "size": 481, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_5.sdk" + "sha3-384": "6daa7a92c7359377b04d67bf2f84691cae51fa74f1018a7667eaa174cddf760a1d76d19ab36feb3df42f2b5d65c64ba4", + "size": 460, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_23.sdk" }, "platforms": [ { @@ -304,19 +326,25 @@ "name": "ubuntu" } ], - "revision": 5, + "revision": 23, "sdk-yaml": { "architecture": "amd64", "base": "ubuntu@22.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:31:53.381455Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T04:17:21.492902Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -325,21 +353,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "arm64", "channel": "22.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:49:48.030485Z" + "released-at": "2026-04-15T03:29:59.046984Z", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:49:43.827456Z", + "created-at": "2026-04-15T03:29:57.589235Z", "download": { - "sha3-384": "649083022dfa1e0cbbd84a862ed6f699817abe5138fe369d861433cb20fc3a3a31032793a42a28222c0b1a3b1b7eb545", - "size": 463, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_8.sdk" + "sha3-384": "05e4202546cbb136af9e62c37b7e9a84e2d122126c55dd11a4134091573d7c84e09ae0b2ac84d6e20bab00ed5d630ac1", + "size": 460, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_14.sdk" }, "platforms": [ { @@ -348,19 +376,25 @@ "name": "ubuntu" } ], - "revision": 8, + "revision": 14, "sdk-yaml": { "architecture": "arm64", "base": "ubuntu@22.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:32:27.432551Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T03:28:23.988341Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -369,21 +403,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "riscv64", "channel": "22.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:50:16.955362Z" + "released-at": "2026-04-15T03:46:16.565275Z", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:50:15.292817Z", + "created-at": "2026-04-15T03:46:15.158932Z", "download": { - "sha3-384": "68f2f2519184122b91e6adf7538fd0dd77a6116892ef7ab345e7c5bd629ca49ebce6a6eb3de9b355686cad9c13cb814c", - "size": 468, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_11.sdk" + "sha3-384": "8a002dbaa099217fd304cfc04c828afd9b35e079cb27961c3537ec83e422ee0e9ddc0df43d9a98e8a59f55db32c5ea4f", + "size": 460, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_17.sdk" }, "platforms": [ { @@ -392,19 +426,25 @@ "name": "ubuntu" } ], - "revision": 11, + "revision": 17, "sdk-yaml": { "architecture": "riscv64", "base": "ubuntu@22.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:33:07.546695Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T03:44:23.336534Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -413,21 +453,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "amd64", "channel": "24.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:49:28.111011Z" + "released-at": "2026-04-15T04:19:00.10426Z", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:49:24.736009Z", + "created-at": "2026-04-15T04:18:58.671959Z", "download": { - "sha3-384": "2c8cf78cd0468d548af2c59378e07751cc1e04644e598f9bb324eb8c42773e68f8cc1fa6f76035b37a2c42877969756a", - "size": 473, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_6.sdk" + "sha3-384": "9c5ab6e74cce46a4186ac443c1eee2470e0e364d2f46d7543f30c66cd09b7c1cd616808196af0fc32b1ce74ad27b82c8", + "size": 455, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_24.sdk" }, "platforms": [ { @@ -436,19 +476,25 @@ "name": "ubuntu" } ], - "revision": 6, + "revision": 24, "sdk-yaml": { "architecture": "amd64", "base": "ubuntu@24.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:32:01.959553Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T04:18:48.686398Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -457,21 +503,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "arm64", "channel": "24.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:49:57.942362Z" + "released-at": "2026-04-15T03:30:02.28829Z", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:49:53.081669Z", + "created-at": "2026-04-15T03:30:00.639952Z", "download": { - "sha3-384": "6c25fd9a2653a2ab6ca97a42590ef140c6088baf85211f36cd575a93a4ea07f78a3ad4a6b3c4d9ba7a79d6edc880e64f", - "size": 457, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_9.sdk" + "sha3-384": "ba23c469d265c6da40a24164d8b4a496e15f3c06bfa9eb5436ecdca391b684587f0b3fdd2719dd6441241bc929796ea6", + "size": 453, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_15.sdk" }, "platforms": [ { @@ -480,19 +526,25 @@ "name": "ubuntu" } ], - "revision": 9, + "revision": 15, "sdk-yaml": { "architecture": "arm64", "base": "ubuntu@24.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:32:43.817115Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T03:29:50.934537Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -501,21 +553,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "riscv64", "channel": "24.04", "name": "ubuntu" }, - "released-at": "2026-03-11T00:13:19.621866Z" + "released-at": "2026-04-15T03:46:19.816194Z", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-11T00:13:15.679272Z", + "created-at": "2026-04-15T03:46:18.296025Z", "download": { - "sha3-384": "6e4067003e140090d017872a8a730a1205679d388efde0e2eff6ac4e89e639593ae0f4665f361bb52d704e8ec670be06", - "size": 480, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_13.sdk" + "sha3-384": "776fd3dc73b4be2c05fb670dd734212c52de755625ed542c9be116142080b94052ae49ef886d4c2a398c5d436a8ae988", + "size": 450, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_18.sdk" }, "platforms": [ { @@ -524,23 +576,43 @@ "name": "ubuntu" } ], - "revision": 13, + "revision": 18, "sdk-yaml": { "architecture": "riscv64", "base": "ubuntu@24.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-11T00:10:49.646904Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T03:46:07.836107Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", - "version": "0.3.1" + "title": "Test SDK info multi-base title", + "version": "0.3" }, - "version": "0.3.1" + "version": "0.3" } } - ] + ], + "metadata": { + "description": "Test SDK info multi-base description", + "license": "LGPL-3.0", + "publisher": { + "display-name": "Dmitry Lyfar", + "id": "rerCzOoMKcwXbFbziPbLdrxjmF36PCRd", + "username": "dlyfar", + "validation": "unproven" + }, + "summary": "Test SDK which supports multiple bases and architectures", + "title": "Test SDK info multi-base title" + }, + "name": "test-sdk-info-multi-base-1", + "package-id": "x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG" } diff --git a/internal/sdkstore/test-sdk-info-multi-base.raw.json b/internal/sdkstore/test-sdk-info-multi-base.raw.json index 3a919bebc..675b6cb2e 100644 --- a/internal/sdkstore/test-sdk-info-multi-base.raw.json +++ b/internal/sdkstore/test-sdk-info-multi-base.raw.json @@ -1,43 +1,23 @@ { - "name": "test-sdk-info-multi-base", - "package-id": "ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1", - "metadata": { - "categories": [], - "contact": "", - "description": "Short description", - "license": "LGPL-3.0", - "links": {}, - "media": [], - "private": false, - "publisher": { - "display-name": "Jonathan Conder", - "id": "YnyjkYo9nA6AlXsTKnpxrCGxNZgmSdmu", - "username":"usso-jeconder", - "validation":"unproven" - }, - "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", - "website": null - }, "channel-map": [ { "channel": { "name": "latest/stable", - "track": "latest", - "risk": "stable", "platform": { "architecture": "amd64", "channel": "20.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:48:34.474879+00:00" + "released-at": "2026-04-15T04:03:38.694727+00:00", + "risk": "stable", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:16:19.894258+00:00", + "created-at": "2026-04-15T04:03:37.224647+00:00", "download": { - "sha3-384": "f0ed20952899d915e1796cadf28b9b03894c5e70a0900ac07e869fc6e5c35b11ace2374eab7ffb70bc7f9120aedb3d67", - "size": 466, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_1.sdk" + "sha3-384": "4099fb92c71c5075364fdba4e451b545a8cb1ee0946bacaddef5f0682f2070385de9cf8898571eb91b14c99d0f07ea98", + "size": 456, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_19.sdk" }, "platforms": [ { @@ -46,42 +26,48 @@ "name": "ubuntu" } ], - "revision": 1, + "revision": 19, "sdk-yaml": { "architecture": "amd64", "base": "ubuntu@20.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:13:46.850996Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T03:59:51.904474Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", - "version": "0.2" + "title": "Test SDK info multi-base title", + "version": "0.2.1" }, - "version": "0.2" + "version": "0.2.1" } }, { "channel": { "name": "latest/stable", - "track": "latest", - "risk": "stable", "platform": { "architecture": "amd64", "channel": "22.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:48:44.327082+00:00" + "released-at": "2026-04-15T04:03:41.880103+00:00", + "risk": "stable", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:16:31.917435+00:00", + "created-at": "2026-04-15T04:03:40.049099+00:00", "download": { - "sha3-384": "59606df612d0290225f427935ac300437542a3c3024c52a0fb2bc5ae486ff801855f571ab8ecfe5f14f16b051d97673e", - "size": 464, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_2.sdk" + "sha3-384": "18b9c9274861a17251ecfd64c2d161f48b5eb50e97507ed8cb9d16dc79d9c721b66d80de8ac2d89b5ac413f811fb720b", + "size": 454, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_20.sdk" }, "platforms": [ { @@ -90,19 +76,25 @@ "name": "ubuntu" } ], - "revision": 2, + "revision": 20, "sdk-yaml": { "architecture": "amd64", "base": "ubuntu@22.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:14:01.728232Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T04:01:54.141587Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.2" }, "version": "0.2" @@ -111,21 +103,21 @@ { "channel": { "name": "latest/stable", - "track": "latest", - "risk": "stable", "platform": { "architecture": "amd64", "channel": "24.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:48:51.554637+00:00" + "released-at": "2026-04-15T04:03:45.578850+00:00", + "risk": "stable", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:16:42.684241+00:00", + "created-at": "2026-04-15T04:03:44.053460+00:00", "download": { - "sha3-384": "4a571977571287d12a0de8efd0fb9bdd63d57bf733a79ed191914e95db3718dcc976450b2da4d3831782fd5747031b66", - "size": 463, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_3.sdk" + "sha3-384": "675fba79b17b63f9b0bf01cfae25feb53f057a6b803600fe6834015e345d4edc8824554f47666bc915aa83f141cc7314", + "size": 452, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_21.sdk" }, "platforms": [ { @@ -134,19 +126,25 @@ "name": "ubuntu" } ], - "revision": 3, + "revision": 21, "sdk-yaml": { "architecture": "amd64", "base": "ubuntu@24.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:14:16.910778Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T04:03:34.056652Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.2" }, "version": "0.2" @@ -155,21 +153,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "amd64", "channel": "20.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:49:09.397396+00:00" + "released-at": "2026-04-15T04:18:53.921839+00:00", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:49:07.605786+00:00", + "created-at": "2026-04-15T04:18:52.237040+00:00", "download": { - "sha3-384": "79b3bb7b80a2c575748dda0c97a8d947ee567cef1ea5ac7e5cf9ddf8a32032e19be461bd9374188b5b0b1fbef59b4120", - "size": 486, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_4.sdk" + "sha3-384": "1959ab8624b4293eef74ad8e85dc3f34a03142b957174baa465d36fe8833ca94c23250dc957ab087828d6e9902c9a252", + "size": 460, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_22.sdk" }, "platforms": [ { @@ -178,19 +176,25 @@ "name": "ubuntu" } ], - "revision": 4, + "revision": 22, "sdk-yaml": { "architecture": "amd64", "base": "ubuntu@20.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:31:44.177639Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T04:15:50.994937Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -199,21 +203,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "arm64", "channel": "20.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:49:37.506334+00:00" + "released-at": "2026-04-15T03:29:56.027154+00:00", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:49:33.890123+00:00", + "created-at": "2026-04-15T03:29:54.409356+00:00", "download": { - "sha3-384": "c17e31dd829abb3914c09b6e5d45b3b84c960232310c643987659b939204d11b474f1f9e1c04c829b65494e3056301e3", - "size": 468, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_7.sdk" + "sha3-384": "50d670000d05a468cfadd4da1fd1ce316ff752dd3a4f7f47243c127a04d7d7489c2be83dbe839af380580892a0af92eb", + "size": 467, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_13.sdk" }, "platforms": [ { @@ -222,19 +226,25 @@ "name": "ubuntu" } ], - "revision": 7, + "revision": 13, "sdk-yaml": { "architecture": "arm64", "base": "ubuntu@20.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:32:09.984597Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T03:26:56.879397Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -243,21 +253,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "riscv64", "channel": "20.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:50:09.178916+00:00" + "released-at": "2026-04-15T03:46:13.344116+00:00", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:50:05.426185+00:00", + "created-at": "2026-04-15T03:46:11.814555+00:00", "download": { - "sha3-384": "03ac4741a33fbce17646dc4e8acb691bf2131a814234de08ce376ca88af3d995cc52e37609d5efe51e16f40fcd763d8d", - "size": 469, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_10.sdk" + "sha3-384": "7ed098fe9e4cec8978714c5c2f34d5e9b620ba4f88cb650aaac680cda97539d08eb3f119e068da24a9c6968d862d2c56", + "size": 459, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_16.sdk" }, "platforms": [ { @@ -266,19 +276,25 @@ "name": "ubuntu" } ], - "revision": 10, + "revision": 16, "sdk-yaml": { "architecture": "riscv64", "base": "ubuntu@20.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:32:52.730823Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T03:42:25.358095Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -287,21 +303,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "amd64", "channel": "22.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:49:18.751516+00:00" + "released-at": "2026-04-15T04:18:57.140822+00:00", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:49:14.930412+00:00", + "created-at": "2026-04-15T04:18:55.473393+00:00", "download": { - "sha3-384": "5c17154e8881e275abc908456aad707c9fe824ed37639789499fbad7b271a5a87442e6ec8071aeb8a2656c0a3f02dc62", - "size": 481, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_5.sdk" + "sha3-384": "6daa7a92c7359377b04d67bf2f84691cae51fa74f1018a7667eaa174cddf760a1d76d19ab36feb3df42f2b5d65c64ba4", + "size": 460, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_23.sdk" }, "platforms": [ { @@ -310,19 +326,25 @@ "name": "ubuntu" } ], - "revision": 5, + "revision": 23, "sdk-yaml": { "architecture": "amd64", "base": "ubuntu@22.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:31:53.381455Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T04:17:21.492902Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -331,21 +353,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "arm64", "channel": "22.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:49:48.030485+00:00" + "released-at": "2026-04-15T03:29:59.046984+00:00", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:49:43.827456+00:00", + "created-at": "2026-04-15T03:29:57.589235+00:00", "download": { - "sha3-384": "649083022dfa1e0cbbd84a862ed6f699817abe5138fe369d861433cb20fc3a3a31032793a42a28222c0b1a3b1b7eb545", - "size": 463, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_8.sdk" + "sha3-384": "05e4202546cbb136af9e62c37b7e9a84e2d122126c55dd11a4134091573d7c84e09ae0b2ac84d6e20bab00ed5d630ac1", + "size": 460, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_14.sdk" }, "platforms": [ { @@ -354,19 +376,25 @@ "name": "ubuntu" } ], - "revision": 8, + "revision": 14, "sdk-yaml": { "architecture": "arm64", "base": "ubuntu@22.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:32:27.432551Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T03:28:23.988341Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -375,21 +403,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "riscv64", "channel": "22.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:50:16.955362+00:00" + "released-at": "2026-04-15T03:46:16.565275+00:00", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:50:15.292817+00:00", + "created-at": "2026-04-15T03:46:15.158932+00:00", "download": { - "sha3-384": "68f2f2519184122b91e6adf7538fd0dd77a6116892ef7ab345e7c5bd629ca49ebce6a6eb3de9b355686cad9c13cb814c", - "size": 468, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_11.sdk" + "sha3-384": "8a002dbaa099217fd304cfc04c828afd9b35e079cb27961c3537ec83e422ee0e9ddc0df43d9a98e8a59f55db32c5ea4f", + "size": 460, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_17.sdk" }, "platforms": [ { @@ -398,19 +426,25 @@ "name": "ubuntu" } ], - "revision": 11, + "revision": 17, "sdk-yaml": { "architecture": "riscv64", "base": "ubuntu@22.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:33:07.546695Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T03:44:23.336534Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -419,21 +453,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "amd64", "channel": "24.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:49:28.111011+00:00" + "released-at": "2026-04-15T04:19:00.104260+00:00", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:49:24.736009+00:00", + "created-at": "2026-04-15T04:18:58.671959+00:00", "download": { - "sha3-384": "2c8cf78cd0468d548af2c59378e07751cc1e04644e598f9bb324eb8c42773e68f8cc1fa6f76035b37a2c42877969756a", - "size": 473, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_6.sdk" + "sha3-384": "9c5ab6e74cce46a4186ac443c1eee2470e0e364d2f46d7543f30c66cd09b7c1cd616808196af0fc32b1ce74ad27b82c8", + "size": 455, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_24.sdk" }, "platforms": [ { @@ -442,19 +476,25 @@ "name": "ubuntu" } ], - "revision": 6, + "revision": 24, "sdk-yaml": { "architecture": "amd64", "base": "ubuntu@24.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:32:01.959553Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T04:18:48.686398Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -463,21 +503,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "arm64", "channel": "24.04", "name": "ubuntu" }, - "released-at": "2026-03-10T23:49:57.942362+00:00" + "released-at": "2026-04-15T03:30:02.288290+00:00", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-10T23:49:53.081669+00:00", + "created-at": "2026-04-15T03:30:00.639952+00:00", "download": { - "sha3-384": "6c25fd9a2653a2ab6ca97a42590ef140c6088baf85211f36cd575a93a4ea07f78a3ad4a6b3c4d9ba7a79d6edc880e64f", - "size": 457, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_9.sdk" + "sha3-384": "ba23c469d265c6da40a24164d8b4a496e15f3c06bfa9eb5436ecdca391b684587f0b3fdd2719dd6441241bc929796ea6", + "size": 453, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_15.sdk" }, "platforms": [ { @@ -486,19 +526,25 @@ "name": "ubuntu" } ], - "revision": 9, + "revision": 15, "sdk-yaml": { "architecture": "arm64", "base": "ubuntu@24.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-10T23:32:43.817115Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T03:29:50.934537Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", + "title": "Test SDK info multi-base title", "version": "0.3" }, "version": "0.3" @@ -507,21 +553,21 @@ { "channel": { "name": "latest/edge", - "track": "latest", - "risk": "edge", "platform": { "architecture": "riscv64", "channel": "24.04", "name": "ubuntu" }, - "released-at": "2026-03-11T00:13:19.621866+00:00" + "released-at": "2026-04-15T03:46:19.816194+00:00", + "risk": "edge", + "track": "latest" }, "revision": { - "created-at": "2026-03-11T00:13:15.679272+00:00", + "created-at": "2026-04-15T03:46:18.296025+00:00", "download": { - "sha3-384": "6e4067003e140090d017872a8a730a1205679d388efde0e2eff6ac4e89e639593ae0f4665f361bb52d704e8ec670be06", - "size": 480, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/ZeW8fMKBPHZBsaSm6LBPbpDZDpVcIHy1_13.sdk" + "sha3-384": "776fd3dc73b4be2c05fb670dd734212c52de755625ed542c9be116142080b94052ae49ef886d4c2a398c5d436a8ae988", + "size": 450, + "url": "https://api.pkg.store/api/v1/sdks/download/x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG_18.sdk" }, "platforms": [ { @@ -530,24 +576,50 @@ "name": "ubuntu" } ], - "revision": 13, + "revision": 18, "sdk-yaml": { "architecture": "riscv64", "base": "ubuntu@24.04", - "contact": ["contact@example.com", "https://example.com/contact"], - "description": "Short description", - "issues": ["issues@example.com", "https://example.com/issues"], + "contact": [ + "contact@example.com", + "https://example.com/contact" + ], + "description": "Test SDK info multi-base description", + "issues": [ + "issues@example.com", + "https://example.com/issues" + ], "license": "LGPL-3.0", - "name": "test-sdk-info-multi-base", - "sdkcraft-started-at": "2026-03-11T00:10:49.646904Z", + "name": "test-sdk-info-multi-base-1", + "sdkcraft-started-at": "2026-04-15T03:46:07.836107Z", "source-code": "https://example.com/source-code", "summary": "Test SDK which supports multiple bases and architectures", - "title": "Custom title", - "version": "0.3.1" + "title": "Test SDK info multi-base title", + "version": "0.3" }, - "version": "0.3.1" + "version": "0.3" } } ], - "default-track": null + "default-track": null, + "metadata": { + "categories": [], + "contact": "", + "description": "Test SDK info multi-base description", + "license": "LGPL-3.0", + "links": {}, + "media": [], + "private": false, + "publisher": { + "display-name": "Dmitry Lyfar", + "id": "rerCzOoMKcwXbFbziPbLdrxjmF36PCRd", + "username": "dlyfar", + "validation": "unproven" + }, + "summary": "Test SDK which supports multiple bases and architectures", + "title": "Test SDK info multi-base title", + "website": null + }, + "name": "test-sdk-info-multi-base-1", + "package-id": "x2hQwbuopxeELn5p1G9h3b9ZB3XM0JeG" } diff --git a/internal/sdkstore/test-sdk-info.json b/internal/sdkstore/test-sdk-info.json index 13d0fdcc7..de5f9e8da 100644 --- a/internal/sdkstore/test-sdk-info.json +++ b/internal/sdkstore/test-sdk-info.json @@ -1,14 +1,14 @@ { - "name": "test-sdk-info", - "package-id": "U9IBEcXiDkuaPLYjyUBpRZMETAr7g39e", + "name": "test-sdk-info-1", + "package-id": "96TKss360WoMRcFySGMOMhXhwbDdZh0E", "metadata": { "description": "This is test-sdk-info's description. You have a paragraph or two to tell the\nmost important story about it. Keep it under 100 words though,\nwe live in tweetspace.\n", "license": "GPL-3.0", "publisher": { "display-name": "Dmitry Lyfar", - "id": "eXipbnGB0LFt3v2YD8KTEMx5inT575ju", - "username":"usso-dlyfar", - "validation":"unproven" + "id": "rerCzOoMKcwXbFbziPbLdrxjmF36PCRd", + "username": "dlyfar", + "validation": "unproven" }, "summary": "Test SDK which supports all bases and architectures", "title": "SDK info" @@ -24,14 +24,14 @@ "channel": "all", "name": "all" }, - "released-at": "2026-04-02T01:29:06.948542Z" + "released-at": "2026-04-15T23:33:32.161874Z" }, "revision": { - "created-at": "2026-04-02T01:29:04.183727Z", + "created-at": "2026-04-15T23:33:30.166917Z", "download": { - "sha3-384": "51779c9264f26baf29facfdaac39286712073b5ed3a5f3e63ab03cc3aa6275165d0d50d30d87beb45068c8c3ce70053a", - "size": 596, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/U9IBEcXiDkuaPLYjyUBpRZMETAr7g39e_3.sdk" + "sha3-384": "22e773fd5f052fbfcb077788e5a13b81415ff6f8f7bfd0a65cc19f3ce854054fd1090c04937d420cde5645c2940e29e3", + "size": 626, + "url": "https://api.pkg.store/api/v1/sdks/download/96TKss360WoMRcFySGMOMhXhwbDdZh0E_2.sdk" }, "platforms": [ { @@ -40,13 +40,13 @@ "name": "all" } ], - "revision": 3, + "revision": 2, "sdk-yaml": { "architecture": "all", "description": "This is test-sdk-info's description. You have a paragraph or two to tell the\nmost important story about it. Keep it under 100 words though,\nwe live in tweetspace.\n", "license": "GPL-3.0", - "name": "test-sdk-info", - "sdkcraft-started-at": "2026-04-02T01:28:56.859021Z", + "name": "test-sdk-info-1", + "sdkcraft-started-at": "2026-04-15T23:33:25.815047Z", "summary": "Test SDK which supports all bases and architectures", "title": "SDK info", "version": "1.0" @@ -64,14 +64,14 @@ "channel": "all", "name": "all" }, - "released-at": "2026-04-02T01:28:47.872298Z" + "released-at": "2026-04-15T23:08:52.533822Z" }, "revision": { - "created-at": "2026-04-02T01:28:46.134132Z", + "created-at": "2026-04-15T23:08:50.73701Z", "download": { - "sha3-384": "47b440b2facd5176c7d346f32e59499d0b7afe268b8cd174ef5e07bf564147669d836e6d93c2ba1efe3b2e0f5cf94eee", - "size": 593, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/U9IBEcXiDkuaPLYjyUBpRZMETAr7g39e_2.sdk" + "sha3-384": "0834579597dd61dd2ca3ff998716980f7bedd11ad36bbd15d013530d2bd8c1bc2645a03505cca378d242b228e3ffa9db", + "size": 597, + "url": "https://api.pkg.store/api/v1/sdks/download/96TKss360WoMRcFySGMOMhXhwbDdZh0E_1.sdk" }, "platforms": [ { @@ -80,13 +80,13 @@ "name": "all" } ], - "revision": 2, + "revision": 1, "sdk-yaml": { "architecture": "all", "description": "This is test-sdk-info's description. You have a paragraph or two to tell the\nmost important story about it. Keep it under 100 words though,\nwe live in tweetspace.\n", "license": "GPL-3.0", - "name": "test-sdk-info", - "sdkcraft-started-at": "2026-04-02T01:28:40.615633Z", + "name": "test-sdk-info-1", + "sdkcraft-started-at": "2026-04-15T23:08:46.868775Z", "summary": "Test SDK which supports all bases and architectures", "title": "SDK info", "version": "2.0" diff --git a/internal/sdkstore/test-sdk-info.raw.json b/internal/sdkstore/test-sdk-info.raw.json index 772bd7139..e7e345c9c 100644 --- a/internal/sdkstore/test-sdk-info.raw.json +++ b/internal/sdkstore/test-sdk-info.raw.json @@ -1,14 +1,14 @@ { - "name": "test-sdk-info", - "package-id": "U9IBEcXiDkuaPLYjyUBpRZMETAr7g39e", + "name": "test-sdk-info-1", + "package-id": "96TKss360WoMRcFySGMOMhXhwbDdZh0E", "metadata": { "description": "This is test-sdk-info's description. You have a paragraph or two to tell the\nmost important story about it. Keep it under 100 words though,\nwe live in tweetspace.\n", "license": "GPL-3.0", "publisher": { "display-name": "Dmitry Lyfar", - "id": "eXipbnGB0LFt3v2YD8KTEMx5inT575ju", - "username":"usso-dlyfar", - "validation":"unproven" + "id": "rerCzOoMKcwXbFbziPbLdrxjmF36PCRd", + "username": "dlyfar", + "validation": "unproven" }, "summary": "Test SDK which supports all bases and architectures", "title": "SDK info" @@ -24,14 +24,14 @@ "channel": "all", "name": "all" }, - "released-at": "2026-04-02T01:29:06.948542+00:00" + "released-at": "2026-04-15T23:33:32.161874+00:00" }, "revision": { - "created-at": "2026-04-02T01:29:04.183727+00:00", + "created-at": "2026-04-15T23:33:30.166917+00:00", "download": { - "sha3-384": "51779c9264f26baf29facfdaac39286712073b5ed3a5f3e63ab03cc3aa6275165d0d50d30d87beb45068c8c3ce70053a", - "size": 596, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/U9IBEcXiDkuaPLYjyUBpRZMETAr7g39e_3.sdk" + "sha3-384": "22e773fd5f052fbfcb077788e5a13b81415ff6f8f7bfd0a65cc19f3ce854054fd1090c04937d420cde5645c2940e29e3", + "size": 626, + "url": "https://api.pkg.store/api/v1/sdks/download/96TKss360WoMRcFySGMOMhXhwbDdZh0E_2.sdk" }, "platforms": [ { @@ -40,13 +40,13 @@ "name": "all" } ], - "revision": 3, + "revision": 2, "sdk-yaml": { "architecture": "all", "description": "This is test-sdk-info's description. You have a paragraph or two to tell the\nmost important story about it. Keep it under 100 words though,\nwe live in tweetspace.\n", "license": "GPL-3.0", - "name": "test-sdk-info", - "sdkcraft-started-at": "2026-04-02T01:28:56.859021Z", + "name": "test-sdk-info-1", + "sdkcraft-started-at": "2026-04-15T23:33:25.815047Z", "summary": "Test SDK which supports all bases and architectures", "title": "SDK info", "version": "1.0" @@ -64,14 +64,14 @@ "channel": "all", "name": "all" }, - "released-at": "2026-04-02T01:28:47.872298+00:00" + "released-at": "2026-04-15T23:08:52.533822+00:00" }, "revision": { - "created-at": "2026-04-02T01:28:46.134132+00:00", + "created-at": "2026-04-15T23:08:50.737010+00:00", "download": { - "sha3-384": "47b440b2facd5176c7d346f32e59499d0b7afe268b8cd174ef5e07bf564147669d836e6d93c2ba1efe3b2e0f5cf94eee", - "size": 593, - "url": "https://api.staging.pkg.store/api/v1/sdks/download/U9IBEcXiDkuaPLYjyUBpRZMETAr7g39e_2.sdk" + "sha3-384": "0834579597dd61dd2ca3ff998716980f7bedd11ad36bbd15d013530d2bd8c1bc2645a03505cca378d242b228e3ffa9db", + "size": 597, + "url": "https://api.pkg.store/api/v1/sdks/download/96TKss360WoMRcFySGMOMhXhwbDdZh0E_1.sdk" }, "platforms": [ { @@ -80,13 +80,13 @@ "name": "all" } ], - "revision": 2, + "revision": 1, "sdk-yaml": { "architecture": "all", "description": "This is test-sdk-info's description. You have a paragraph or two to tell the\nmost important story about it. Keep it under 100 words though,\nwe live in tweetspace.\n", "license": "GPL-3.0", - "name": "test-sdk-info", - "sdkcraft-started-at": "2026-04-02T01:28:40.615633Z", + "name": "test-sdk-info-1", + "sdkcraft-started-at": "2026-04-15T23:08:46.868775Z", "summary": "Test SDK which supports all bases and architectures", "title": "SDK info", "version": "2.0" diff --git a/tests/docs-tutorial/part-4/task.yaml b/tests/docs-tutorial/part-4/task.yaml index 5d871e04e..709db8110 100644 --- a/tests/docs-tutorial/part-4/task.yaml +++ b/tests/docs-tutorial/part-4/task.yaml @@ -11,6 +11,7 @@ execute: | run_sdkcraft --help mkdir ollama/ && cd ollama/ + chown ubuntu:ubuntu . run_sdkcraft init rm hooks/* mv ../{sdkcraft.yaml,ollama.service} . diff --git a/tests/integration/sdk-store/task.yaml b/tests/integration/sdk-store/task.yaml new file mode 100644 index 000000000..d242ac310 --- /dev/null +++ b/tests/integration/sdk-store/task.yaml @@ -0,0 +1,10 @@ +summary: Run SDK Store integration tests + +execute: | + rm -rf cover + mkdir cover + + go test -cover "$SPREAD_PATH"/internal/sdkstore -timeout=30m -tags=integration -check.v -check.f '.*Integration' -coverpkg=github.com/canonical/workshop/internal/sdkstore -args -test.gocoverdir="$PWD"/cover + +artifacts: + - cover diff --git a/tests/lib/sdk/README.md b/tests/lib/sdk/README.md new file mode 100644 index 000000000..2c2ebc1b5 --- /dev/null +++ b/tests/lib/sdk/README.md @@ -0,0 +1,25 @@ +# Test SDKs + +These SDKs are used in the `main` end-to-end tests +and the SDK Store integration tests. +They are intended to be immutable, +to keep the old tests working +while updated ones are under development. + +New SDKs can be added by placing them in `tests/lib/staging`. +A GitHub workflow will build and publish them before running integration tests. +The `build-for` architectures can be customized by listing them in `tests/lib/staging//platforms.json`. +By default only `amd64` is built. + +After finalizing the SDK, +it should be moved to `tests/lib/sdk`. +A separate workflow prevents merging a PR with staged SDKs, +but no automation touches `tests/lib/sdk`. +It serves purely as a record of which SDKs are used in the tests. + +Test SDKs can be built manually, +but ideally they should be published by an independent entity. +In any case the SDK definition should be stored in `tests/lib/sdk`. + +An SDK can be removed at any time, +but this should only happen after all tests no longer use it. diff --git a/tests/lib/sdk/test-sdk-find-2-s390x/latest/edge/platforms.json b/tests/lib/sdk/test-sdk-find-2-s390x/latest/edge/platforms.json new file mode 100644 index 000000000..52ef19fdd --- /dev/null +++ b/tests/lib/sdk/test-sdk-find-2-s390x/latest/edge/platforms.json @@ -0,0 +1 @@ +["s390x"] diff --git a/tests/lib/sdk/test-sdk-find-2-s390x/latest/edge/sdkcraft.yaml b/tests/lib/sdk/test-sdk-find-2-s390x/latest/edge/sdkcraft.yaml new file mode 100644 index 000000000..2ebe62cc9 --- /dev/null +++ b/tests/lib/sdk/test-sdk-find-2-s390x/latest/edge/sdkcraft.yaml @@ -0,0 +1,14 @@ +name: test-sdk-find-2-s390x +title: Test SDK find 2 s390x title +version: "0.2+s390x" +summary: Test SDK find 2 s390x summary +description: Test SDK find 2 s390x description +license: AGPL-3.0 +contact: [contact@example.com, https://example.com/contact] +issues: [issues@example.com, https://example.com/issues] +source-code: https://example.com/source-code +build-base: ubuntu@24.04 +platforms: + s390x: + build-on: amd64 + build-for: s390x diff --git a/tests/lib/sdk/test-sdk-find-2/latest/edge/platforms.json b/tests/lib/sdk/test-sdk-find-2/latest/edge/platforms.json new file mode 100644 index 000000000..c43a86bc8 --- /dev/null +++ b/tests/lib/sdk/test-sdk-find-2/latest/edge/platforms.json @@ -0,0 +1 @@ +["all"] diff --git a/tests/lib/sdk/test-sdk-find-2/latest/edge/sdkcraft.yaml b/tests/lib/sdk/test-sdk-find-2/latest/edge/sdkcraft.yaml new file mode 100644 index 000000000..a234161c3 --- /dev/null +++ b/tests/lib/sdk/test-sdk-find-2/latest/edge/sdkcraft.yaml @@ -0,0 +1,14 @@ +name: test-sdk-find-2 +title: Test SDK find 2 title +version: "0.2" +summary: Test SDK find 2 summary +description: Test SDK find 2 description +license: AGPL-3.0 +contact: [contact@example.com, https://example.com/contact] +issues: [issues@example.com, https://example.com/issues] +source-code: https://example.com/source-code +build-base: ubuntu@24.04 +platforms: + all: + build-on: amd64 + build-for: all diff --git a/tests/lib/sdk/test-sdk-info/latest/edge/hooks/setup-base b/tests/lib/sdk/test-sdk-info-1/latest/edge/hooks/setup-base similarity index 100% rename from tests/lib/sdk/test-sdk-info/latest/edge/hooks/setup-base rename to tests/lib/sdk/test-sdk-info-1/latest/edge/hooks/setup-base diff --git a/tests/lib/sdk/test-sdk-info-1/latest/edge/platforms.json b/tests/lib/sdk/test-sdk-info-1/latest/edge/platforms.json new file mode 100644 index 000000000..c43a86bc8 --- /dev/null +++ b/tests/lib/sdk/test-sdk-info-1/latest/edge/platforms.json @@ -0,0 +1 @@ +["all"] diff --git a/tests/lib/sdk/test-sdk-info/latest/edge/sdkcraft.yaml b/tests/lib/sdk/test-sdk-info-1/latest/edge/sdkcraft.yaml similarity index 94% rename from tests/lib/sdk/test-sdk-info/latest/edge/sdkcraft.yaml rename to tests/lib/sdk/test-sdk-info-1/latest/edge/sdkcraft.yaml index 048a12554..351f1a742 100644 --- a/tests/lib/sdk/test-sdk-info/latest/edge/sdkcraft.yaml +++ b/tests/lib/sdk/test-sdk-info-1/latest/edge/sdkcraft.yaml @@ -1,4 +1,4 @@ -name: test-sdk-info +name: test-sdk-info-1 title: SDK info version: "2.0" summary: Test SDK which supports all bases and architectures diff --git a/tests/lib/sdk/test-sdk-info/latest/stable/hooks/setup-base b/tests/lib/sdk/test-sdk-info-1/latest/stable/hooks/setup-base similarity index 100% rename from tests/lib/sdk/test-sdk-info/latest/stable/hooks/setup-base rename to tests/lib/sdk/test-sdk-info-1/latest/stable/hooks/setup-base diff --git a/tests/lib/sdk/test-sdk-info-1/latest/stable/platforms.json b/tests/lib/sdk/test-sdk-info-1/latest/stable/platforms.json new file mode 100644 index 000000000..c43a86bc8 --- /dev/null +++ b/tests/lib/sdk/test-sdk-info-1/latest/stable/platforms.json @@ -0,0 +1 @@ +["all"] diff --git a/tests/lib/sdk/test-sdk-info/latest/stable/sdkcraft.yaml b/tests/lib/sdk/test-sdk-info-1/latest/stable/sdkcraft.yaml similarity index 94% rename from tests/lib/sdk/test-sdk-info/latest/stable/sdkcraft.yaml rename to tests/lib/sdk/test-sdk-info-1/latest/stable/sdkcraft.yaml index 43f2b5b4a..ff4e80fa3 100644 --- a/tests/lib/sdk/test-sdk-info/latest/stable/sdkcraft.yaml +++ b/tests/lib/sdk/test-sdk-info-1/latest/stable/sdkcraft.yaml @@ -1,4 +1,4 @@ -name: test-sdk-info +name: test-sdk-info-1 title: SDK info version: "1.0" summary: Test SDK which supports all bases and architectures diff --git a/tests/lib/sdk/test-sdk-info-multi-base-1/latest/edge/platforms.json b/tests/lib/sdk/test-sdk-info-multi-base-1/latest/edge/platforms.json new file mode 100644 index 000000000..52ca8901f --- /dev/null +++ b/tests/lib/sdk/test-sdk-info-multi-base-1/latest/edge/platforms.json @@ -0,0 +1 @@ +["amd64", "arm64", "riscv64"] diff --git a/tests/lib/sdk/test-sdk-info-multi-base-1/latest/edge/sdkcraft.yaml b/tests/lib/sdk/test-sdk-info-multi-base-1/latest/edge/sdkcraft.yaml new file mode 100644 index 000000000..285a320b4 --- /dev/null +++ b/tests/lib/sdk/test-sdk-info-multi-base-1/latest/edge/sdkcraft.yaml @@ -0,0 +1,31 @@ +name: test-sdk-info-multi-base-1 +title: Test SDK info multi-base title +version: "0.3" +summary: Test SDK which supports multiple bases and architectures +description: Test SDK info multi-base description +license: LGPL-3.0 +contact: [contact@example.com, https://example.com/contact] +issues: [issues@example.com, https://example.com/issues] +source-code: https://example.com/source-code +platforms: + ubuntu@20.04:amd64: + ubuntu@22.04:amd64: + ubuntu@24.04:amd64: + ubuntu@20.04:arm64: + build-on: [ubuntu@20.04:amd64] + build-for: [ubuntu@20.04:arm64] + ubuntu@22.04:arm64: + build-on: [ubuntu@22.04:amd64] + build-for: [ubuntu@22.04:arm64] + ubuntu@24.04:arm64: + build-on: [ubuntu@24.04:amd64] + build-for: [ubuntu@24.04:arm64] + ubuntu@20.04:riscv64: + build-on: [ubuntu@20.04:amd64] + build-for: [ubuntu@20.04:riscv64] + ubuntu@22.04:riscv64: + build-on: [ubuntu@22.04:amd64] + build-for: [ubuntu@22.04:riscv64] + ubuntu@24.04:riscv64: + build-on: [ubuntu@24.04:amd64] + build-for: [ubuntu@24.04:riscv64] diff --git a/tests/lib/sdk/test-sdk-info-multi-base-1/latest/stable/platforms.json b/tests/lib/sdk/test-sdk-info-multi-base-1/latest/stable/platforms.json new file mode 100644 index 000000000..6b9d0f8d1 --- /dev/null +++ b/tests/lib/sdk/test-sdk-info-multi-base-1/latest/stable/platforms.json @@ -0,0 +1 @@ +["amd64"] diff --git a/tests/lib/sdk/test-sdk-info-multi-base-1/latest/stable/sdkcraft.yaml b/tests/lib/sdk/test-sdk-info-multi-base-1/latest/stable/sdkcraft.yaml new file mode 100644 index 000000000..cafddd01f --- /dev/null +++ b/tests/lib/sdk/test-sdk-info-multi-base-1/latest/stable/sdkcraft.yaml @@ -0,0 +1,29 @@ +name: test-sdk-info-multi-base-1 +title: Test SDK info multi-base title +adopt-info: version +summary: Test SDK which supports multiple bases and architectures +description: Test SDK info multi-base description +license: LGPL-3.0 +contact: [contact@example.com, https://example.com/contact] +issues: [issues@example.com, https://example.com/issues] +source-code: https://example.com/source-code +platforms: + ubuntu@20.04:amd64: + ubuntu@22.04:amd64: + ubuntu@24.04:amd64: + +parts: + version: + plugin: nil + override-pull: | + craftctl default + + case "$CRAFT_PLATFORM" in + 'ubuntu@20.04':*) + version='0.2.1' + ;; + *) + version='0.2' + ;; + esac + craftctl set version="$version" diff --git a/tests/lib/utils.sh b/tests/lib/utils.sh index 24b460822..741b10011 100644 --- a/tests/lib/utils.sh +++ b/tests/lib/utils.sh @@ -145,17 +145,9 @@ function sdk_exec() { } function run_sdkcraft() { - sdkcraft "$@" + sudo -u ubuntu -- sdkcraft "$@" } function install_sdkcraft() { snap install --classic --edge sdkcraft } - -function resolve_branch() { - local file - for file in "$@"; do - # shellcheck disable=SC2016 - envsubst '${TEST_SDK_BRANCH}' < "${file}.in" > "$file" - done -} diff --git a/tests/main/check-health/.workshop/ws-error.yaml.in b/tests/main/check-health/.workshop/ws-error.yaml similarity index 61% rename from tests/main/check-health/.workshop/ws-error.yaml.in rename to tests/main/check-health/.workshop/ws-error.yaml index d7b07d7cb..0972c3ac5 100644 --- a/tests/main/check-health/.workshop/ws-error.yaml.in +++ b/tests/main/check-health/.workshop/ws-error.yaml @@ -2,4 +2,3 @@ name: ws-error base: ubuntu@22.04 sdks: - name: test-sdk-health-error - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/check-health/.workshop/ws-ok.yaml.in b/tests/main/check-health/.workshop/ws-ok.yaml similarity index 59% rename from tests/main/check-health/.workshop/ws-ok.yaml.in rename to tests/main/check-health/.workshop/ws-ok.yaml index 43aff7560..ea7a5f138 100644 --- a/tests/main/check-health/.workshop/ws-ok.yaml.in +++ b/tests/main/check-health/.workshop/ws-ok.yaml @@ -2,4 +2,3 @@ name: ws-ok base: ubuntu@22.04 sdks: - name: test-sdk-health-ok - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/check-health/.workshop/ws-waiting.yaml.in b/tests/main/check-health/.workshop/ws-waiting.yaml similarity index 62% rename from tests/main/check-health/.workshop/ws-waiting.yaml.in rename to tests/main/check-health/.workshop/ws-waiting.yaml index 9856d5ffc..c16cdca2b 100644 --- a/tests/main/check-health/.workshop/ws-waiting.yaml.in +++ b/tests/main/check-health/.workshop/ws-waiting.yaml @@ -2,4 +2,3 @@ name: ws-waiting base: ubuntu@22.04 sdks: - name: test-sdk-health-waiting - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/check-health/task.yaml b/tests/main/check-health/task.yaml index 13f44d752..056050de1 100644 --- a/tests/main/check-health/task.yaml +++ b/tests/main/check-health/task.yaml @@ -2,7 +2,6 @@ summary: Check check-health hook and set-health reporting execute: | . "$TESTSLIB"/utils.sh - resolve_branch .workshop/ws-ok.yaml .workshop/ws-error.yaml .workshop/ws-waiting.yaml workshop_exec launch ws-ok echo "Check health reported okay" diff --git a/tests/main/completion/.workshop/ws-comp.yaml.in b/tests/main/completion/.workshop/ws-comp.yaml similarity index 50% rename from tests/main/completion/.workshop/ws-comp.yaml.in rename to tests/main/completion/.workshop/ws-comp.yaml index 27eda6686..14f4d00cd 100644 --- a/tests/main/completion/.workshop/ws-comp.yaml.in +++ b/tests/main/completion/.workshop/ws-comp.yaml @@ -2,6 +2,4 @@ name: ws-comp base: ubuntu@22.04 sdks: - name: test-sdk-desktop - channel: latest/stable${TEST_SDK_BRANCH} - name: test-sdk-mount - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/completion/task.yaml b/tests/main/completion/task.yaml index c65fb1f85..24b1dc945 100644 --- a/tests/main/completion/task.yaml +++ b/tests/main/completion/task.yaml @@ -1,7 +1,6 @@ summary: Check that shell completion is installed with the snap prepare: | . "$TESTSLIB"/utils.sh - resolve_branch .workshop/ws-comp.yaml workshop_exec launch ws-comp restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/connect/task.yaml b/tests/main/connect/task.yaml index 3f3ac0990..998b9c5ac 100644 --- a/tests/main/connect/task.yaml +++ b/tests/main/connect/task.yaml @@ -1,7 +1,6 @@ summary: Check that "workshop connect" works as expected prepare: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/connect/workshop.yaml b/tests/main/connect/workshop.yaml new file mode 100644 index 000000000..7773d5c37 --- /dev/null +++ b/tests/main/connect/workshop.yaml @@ -0,0 +1,5 @@ +name: ws-conn +base: ubuntu@22.04 +sdks: + - name: test-sdk-mount + - name: test-sdk-gpu diff --git a/tests/main/connect/workshop.yaml.in b/tests/main/connect/workshop.yaml.in deleted file mode 100644 index 783821b03..000000000 --- a/tests/main/connect/workshop.yaml.in +++ /dev/null @@ -1,7 +0,0 @@ -name: ws-conn -base: ubuntu@22.04 -sdks: - - name: test-sdk-mount - channel: latest/stable${TEST_SDK_BRANCH} - - name: test-sdk-gpu - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/connections/.workshop/ws-conns-1.yaml.in b/tests/main/connections/.workshop/ws-conns-1.yaml similarity index 59% rename from tests/main/connections/.workshop/ws-conns-1.yaml.in rename to tests/main/connections/.workshop/ws-conns-1.yaml index c2f6602a5..da1d1d687 100644 --- a/tests/main/connections/.workshop/ws-conns-1.yaml.in +++ b/tests/main/connections/.workshop/ws-conns-1.yaml @@ -2,4 +2,3 @@ name: ws-conns-1 base: ubuntu@22.04 sdks: - name: test-sdk-mount - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/connections/.workshop/ws-conns-2.yaml.in b/tests/main/connections/.workshop/ws-conns-2.yaml similarity index 59% rename from tests/main/connections/.workshop/ws-conns-2.yaml.in rename to tests/main/connections/.workshop/ws-conns-2.yaml index d7518bd8d..f50fe2d6f 100644 --- a/tests/main/connections/.workshop/ws-conns-2.yaml.in +++ b/tests/main/connections/.workshop/ws-conns-2.yaml @@ -2,4 +2,3 @@ name: ws-conns-2 base: ubuntu@22.04 sdks: - name: test-sdk-mount - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/connections/task.yaml b/tests/main/connections/task.yaml index e991cb673..9d3b7fb63 100644 --- a/tests/main/connections/task.yaml +++ b/tests/main/connections/task.yaml @@ -1,7 +1,6 @@ summary: Check that "workshop connections" works as expected prepare: | . "$TESTSLIB"/utils.sh - resolve_branch .workshop/ws-conns-1.yaml .workshop/ws-conns-2.yaml workshop_exec launch ws-conns-1 ws-conns-2 restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/disconnect/task.yaml b/tests/main/disconnect/task.yaml index 18e14938f..1fa3398e4 100644 --- a/tests/main/disconnect/task.yaml +++ b/tests/main/disconnect/task.yaml @@ -1,7 +1,6 @@ summary: Check that "workshop disconnect" works as expected prepare: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/disconnect/workshop.yaml.in b/tests/main/disconnect/workshop.yaml similarity index 59% rename from tests/main/disconnect/workshop.yaml.in rename to tests/main/disconnect/workshop.yaml index 48bead8d1..fa84c62d0 100644 --- a/tests/main/disconnect/workshop.yaml.in +++ b/tests/main/disconnect/workshop.yaml @@ -2,4 +2,3 @@ name: ws-disconn base: ubuntu@22.04 sdks: - name: test-sdk-mount - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/exec/task.yaml b/tests/main/exec/task.yaml index 10a509d01..e55de568a 100644 --- a/tests/main/exec/task.yaml +++ b/tests/main/exec/task.yaml @@ -1,7 +1,6 @@ summary: Check major workshop exec scenarios prepare: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/exec/workshop.yaml.in b/tests/main/exec/workshop.yaml similarity index 70% rename from tests/main/exec/workshop.yaml.in rename to tests/main/exec/workshop.yaml index 59559936c..a9fb097b0 100644 --- a/tests/main/exec/workshop.yaml.in +++ b/tests/main/exec/workshop.yaml @@ -2,9 +2,7 @@ name: ws-exec base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} - name: test-sdk-environment - channel: latest/stable${TEST_SDK_BRANCH} actions: oneline: echo one line multiline: | diff --git a/tests/main/info-sdk-health/task.yaml b/tests/main/info-sdk-health/task.yaml index 15fac0bb3..3ec6b6782 100644 --- a/tests/main/info-sdk-health/task.yaml +++ b/tests/main/info-sdk-health/task.yaml @@ -8,7 +8,6 @@ restore: | apt remove -y expect execute: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.yaml workshop_exec launch --no-wait sudo -u ubuntu 2>&1 -- expect -f ./health.exp workshop_exec remove diff --git a/tests/main/info-sdk-health/workshop.yaml.in b/tests/main/info-sdk-health/workshop.yaml similarity index 63% rename from tests/main/info-sdk-health/workshop.yaml.in rename to tests/main/info-sdk-health/workshop.yaml index 2a79961aa..9b8ba02a1 100644 --- a/tests/main/info-sdk-health/workshop.yaml.in +++ b/tests/main/info-sdk-health/workshop.yaml @@ -2,4 +2,3 @@ name: ws-sdk-health base: ubuntu@22.04 sdks: - name: test-sdk-health-waiting - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/interface-camera/.workshop/ws-camera-fail.yaml.in b/tests/main/interface-camera/.workshop/ws-camera-fail.yaml similarity index 76% rename from tests/main/interface-camera/.workshop/ws-camera-fail.yaml.in rename to tests/main/interface-camera/.workshop/ws-camera-fail.yaml index 8b8e5b8c0..143295d86 100644 --- a/tests/main/interface-camera/.workshop/ws-camera-fail.yaml.in +++ b/tests/main/interface-camera/.workshop/ws-camera-fail.yaml @@ -6,4 +6,3 @@ sdks: user-camera: interface: camera - name: test-sdk-camera - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/interface-camera/.workshop/ws-camera.yaml.in b/tests/main/interface-camera/.workshop/ws-camera.yaml similarity index 59% rename from tests/main/interface-camera/.workshop/ws-camera.yaml.in rename to tests/main/interface-camera/.workshop/ws-camera.yaml index 94cf77bc9..02f2dbbc0 100644 --- a/tests/main/interface-camera/.workshop/ws-camera.yaml.in +++ b/tests/main/interface-camera/.workshop/ws-camera.yaml @@ -2,4 +2,3 @@ name: ws-camera base: ubuntu@22.04 sdks: - name: test-sdk-camera - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/interface-camera/task.yaml b/tests/main/interface-camera/task.yaml index d71319f95..93ecb03d0 100644 --- a/tests/main/interface-camera/task.yaml +++ b/tests/main/interface-camera/task.yaml @@ -1,7 +1,6 @@ summary: Check camera interface scenarios prepare: | . "$TESTSLIB"/utils.sh - resolve_branch .workshop/ws-camera.yaml .workshop/ws-camera-fail.yaml workshop_exec launch ws-camera restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/interface-desktop/.workshop/ws-desktop-fail.yaml.in b/tests/main/interface-desktop/.workshop/ws-desktop-fail.yaml similarity index 76% rename from tests/main/interface-desktop/.workshop/ws-desktop-fail.yaml.in rename to tests/main/interface-desktop/.workshop/ws-desktop-fail.yaml index 597a46f64..9d0b8a340 100644 --- a/tests/main/interface-desktop/.workshop/ws-desktop-fail.yaml.in +++ b/tests/main/interface-desktop/.workshop/ws-desktop-fail.yaml @@ -6,4 +6,3 @@ sdks: user-desktop: interface: desktop - name: test-sdk-desktop - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/interface-desktop/.workshop/ws-desktop.yaml.in b/tests/main/interface-desktop/.workshop/ws-desktop.yaml similarity index 75% rename from tests/main/interface-desktop/.workshop/ws-desktop.yaml.in rename to tests/main/interface-desktop/.workshop/ws-desktop.yaml index 49f152b0f..ae11d3ae5 100644 --- a/tests/main/interface-desktop/.workshop/ws-desktop.yaml.in +++ b/tests/main/interface-desktop/.workshop/ws-desktop.yaml @@ -2,7 +2,6 @@ name: ws-desktop base: ubuntu@22.04 sdks: - name: test-sdk-desktop - channel: latest/stable${TEST_SDK_BRANCH} connections: - plug: test-sdk-desktop:desktop slot: system:desktop diff --git a/tests/main/interface-desktop/task.yaml b/tests/main/interface-desktop/task.yaml index 4e25ca2fa..2ae8638af 100644 --- a/tests/main/interface-desktop/task.yaml +++ b/tests/main/interface-desktop/task.yaml @@ -1,7 +1,6 @@ summary: Check desktop interface scenarios prepare: | . "$TESTSLIB"/utils.sh - resolve_branch .workshop/ws-desktop.yaml .workshop/ws-desktop-fail.yaml workshop_exec launch ws-desktop restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/interface-gpu/.workshop/ws-gpu-fail.yaml.in b/tests/main/interface-gpu/.workshop/ws-gpu-fail.yaml similarity index 74% rename from tests/main/interface-gpu/.workshop/ws-gpu-fail.yaml.in rename to tests/main/interface-gpu/.workshop/ws-gpu-fail.yaml index 59c8eaba4..b701b55c7 100644 --- a/tests/main/interface-gpu/.workshop/ws-gpu-fail.yaml.in +++ b/tests/main/interface-gpu/.workshop/ws-gpu-fail.yaml @@ -6,4 +6,3 @@ sdks: user-gpu: interface: gpu - name: test-sdk-gpu - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/interface-gpu/.workshop/ws-gpu.yaml.in b/tests/main/interface-gpu/.workshop/ws-gpu.yaml similarity index 57% rename from tests/main/interface-gpu/.workshop/ws-gpu.yaml.in rename to tests/main/interface-gpu/.workshop/ws-gpu.yaml index 1c7f4f7a7..29fdbf47c 100644 --- a/tests/main/interface-gpu/.workshop/ws-gpu.yaml.in +++ b/tests/main/interface-gpu/.workshop/ws-gpu.yaml @@ -2,4 +2,3 @@ name: ws-gpu base: ubuntu@22.04 sdks: - name: test-sdk-gpu - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/interface-gpu/task.yaml b/tests/main/interface-gpu/task.yaml index 2d87d1161..032c1eb0f 100644 --- a/tests/main/interface-gpu/task.yaml +++ b/tests/main/interface-gpu/task.yaml @@ -1,7 +1,6 @@ summary: Check GPU interface scenarios prepare: | . "$TESTSLIB"/utils.sh - resolve_branch .workshop/ws-gpu.yaml .workshop/ws-gpu-fail.yaml workshop_exec launch ws-gpu restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/interface-mount/task.yaml b/tests/main/interface-mount/task.yaml index c1f937088..a0392f956 100644 --- a/tests/main/interface-mount/task.yaml +++ b/tests/main/interface-mount/task.yaml @@ -1,7 +1,6 @@ summary: Check mount interface scenarios prepare: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/interface-mount/workshop.yaml.in b/tests/main/interface-mount/workshop.yaml similarity index 92% rename from tests/main/interface-mount/workshop.yaml.in rename to tests/main/interface-mount/workshop.yaml index 68886e21c..541075c95 100644 --- a/tests/main/interface-mount/workshop.yaml.in +++ b/tests/main/interface-mount/workshop.yaml @@ -2,7 +2,6 @@ name: ws-mount base: ubuntu@22.04 sdks: - name: test-sdk-mount - channel: latest/stable${TEST_SDK_BRANCH} plugs: jobs: interface: mount diff --git a/tests/main/interface-plug-binding/task.yaml b/tests/main/interface-plug-binding/task.yaml index 30e88a7f3..2face3c9d 100644 --- a/tests/main/interface-plug-binding/task.yaml +++ b/tests/main/interface-plug-binding/task.yaml @@ -1,7 +1,6 @@ summary: Check plug binding prepare: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.yaml workshop_exec launch execute: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/interface-plug-binding/workshop.yaml.in b/tests/main/interface-plug-binding/workshop.yaml similarity index 70% rename from tests/main/interface-plug-binding/workshop.yaml.in rename to tests/main/interface-plug-binding/workshop.yaml index eb1f3e387..df3cd4719 100644 --- a/tests/main/interface-plug-binding/workshop.yaml.in +++ b/tests/main/interface-plug-binding/workshop.yaml @@ -2,17 +2,15 @@ name: ws-bind base: ubuntu@22.04 sdks: - name: test-sdk-mount - channel: latest/stable${TEST_SDK_BRANCH} + channel: stable plugs: two: bind: test-sdk-multiple-plugs:two - name: test-sdk-multiple-plugs - channel: latest/stable${TEST_SDK_BRANCH} plugs: one: bind: test-sdk-mount:one - name: test-sdk-ssh-agent - channel: latest/stable${TEST_SDK_BRANCH} plugs: ssh-agent: bind: test-sdk-multiple-plugs:ssh-agent diff --git a/tests/main/interface-ssh-agent/.workshop/ws-ssh-fail.yaml.in b/tests/main/interface-ssh-agent/.workshop/ws-ssh-fail.yaml similarity index 76% rename from tests/main/interface-ssh-agent/.workshop/ws-ssh-fail.yaml.in rename to tests/main/interface-ssh-agent/.workshop/ws-ssh-fail.yaml index e044822b6..68a9b4998 100644 --- a/tests/main/interface-ssh-agent/.workshop/ws-ssh-fail.yaml.in +++ b/tests/main/interface-ssh-agent/.workshop/ws-ssh-fail.yaml @@ -6,4 +6,3 @@ sdks: user-ssh: interface: ssh-agent - name: test-sdk-ssh-agent - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/interface-ssh-agent/.workshop/ws-ssh.yaml.in b/tests/main/interface-ssh-agent/.workshop/ws-ssh.yaml similarity index 76% rename from tests/main/interface-ssh-agent/.workshop/ws-ssh.yaml.in rename to tests/main/interface-ssh-agent/.workshop/ws-ssh.yaml index 54225930c..36625acf2 100644 --- a/tests/main/interface-ssh-agent/.workshop/ws-ssh.yaml.in +++ b/tests/main/interface-ssh-agent/.workshop/ws-ssh.yaml @@ -2,7 +2,6 @@ name: ws-ssh base: ubuntu@22.04 sdks: - name: test-sdk-ssh-agent - channel: latest/stable${TEST_SDK_BRANCH} connections: - plug: test-sdk-ssh-agent:ssh-agent slot: system:ssh-agent diff --git a/tests/main/interface-ssh-agent/task.yaml b/tests/main/interface-ssh-agent/task.yaml index cf09fe509..f58a5cedc 100644 --- a/tests/main/interface-ssh-agent/task.yaml +++ b/tests/main/interface-ssh-agent/task.yaml @@ -1,7 +1,6 @@ summary: Check ssh-agent interface scenarios prepare: | . "$TESTSLIB"/utils.sh - resolve_branch .workshop/ws-ssh.yaml .workshop/ws-ssh-fail.yaml workshop_exec launch ws-ssh restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/interface-tunnel/.workshop.yaml.in b/tests/main/interface-tunnel/.workshop.yaml similarity index 93% rename from tests/main/interface-tunnel/.workshop.yaml.in rename to tests/main/interface-tunnel/.workshop.yaml index 3359883f1..343c910a2 100644 --- a/tests/main/interface-tunnel/.workshop.yaml.in +++ b/tests/main/interface-tunnel/.workshop.yaml @@ -23,7 +23,6 @@ sdks: unused: interface: tunnel - name: test-sdk-tunnel - channel: latest/stable${TEST_SDK_BRANCH} connections: - plug: system:test-sdk-http slot: test-sdk-tunnel:http diff --git a/tests/main/interface-tunnel/task.yaml b/tests/main/interface-tunnel/task.yaml index 354aa2686..c795314f9 100644 --- a/tests/main/interface-tunnel/task.yaml +++ b/tests/main/interface-tunnel/task.yaml @@ -1,7 +1,6 @@ summary: Check tunnel interface scenarios prepare: | . "$TESTSLIB"/utils.sh - resolve_branch .workshop.yaml workshop_exec launch ws-tunnel restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/launch-abort/task.yaml b/tests/main/launch-abort/task.yaml index 80ea66293..415c7ed06 100644 --- a/tests/main/launch-abort/task.yaml +++ b/tests/main/launch-abort/task.yaml @@ -2,7 +2,6 @@ summary: Check a failed non-transactional launch can be aborted successfully execute: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.setup-fail.yaml cp workshop.setup-fail.yaml workshop.yaml workshop_exec launch --wait-on-error || true diff --git a/tests/main/launch-abort/workshop.setup-fail.yaml.in b/tests/main/launch-abort/workshop.setup-fail.yaml similarity index 63% rename from tests/main/launch-abort/workshop.setup-fail.yaml.in rename to tests/main/launch-abort/workshop.setup-fail.yaml index cccc7286e..b152e441f 100644 --- a/tests/main/launch-abort/workshop.setup-fail.yaml.in +++ b/tests/main/launch-abort/workshop.setup-fail.yaml @@ -2,4 +2,3 @@ name: ws-launch-abort base: ubuntu@22.04 sdks: - name: test-sdk-setup-fail - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/launch-continue/task.yaml b/tests/main/launch-continue/task.yaml index 0eb041d69..6d9e3b958 100644 --- a/tests/main/launch-continue/task.yaml +++ b/tests/main/launch-continue/task.yaml @@ -5,7 +5,6 @@ restore: | execute: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.yaml workshop_exec launch --wait-on-error || true echo "Check the workshop is in Waiting state" diff --git a/tests/main/launch-continue/workshop.yaml.in b/tests/main/launch-continue/workshop.yaml similarity index 62% rename from tests/main/launch-continue/workshop.yaml.in rename to tests/main/launch-continue/workshop.yaml index bb1cf5894..c3dd41a00 100644 --- a/tests/main/launch-continue/workshop.yaml.in +++ b/tests/main/launch-continue/workshop.yaml @@ -2,4 +2,3 @@ name: ws-launch-cont base: ubuntu@22.04 sdks: - name: test-sdk-setup-fail - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/launch-multiple/.workshop/ws-one.yaml.in b/tests/main/launch-multiple/.workshop/ws-one.yaml similarity index 58% rename from tests/main/launch-multiple/.workshop/ws-one.yaml.in rename to tests/main/launch-multiple/.workshop/ws-one.yaml index b8a801f9e..f8d00b6e5 100644 --- a/tests/main/launch-multiple/.workshop/ws-one.yaml.in +++ b/tests/main/launch-multiple/.workshop/ws-one.yaml @@ -2,4 +2,3 @@ name: ws-one base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/launch-multiple/.workshop/ws-three.yaml.in b/tests/main/launch-multiple/.workshop/ws-three.yaml similarity index 59% rename from tests/main/launch-multiple/.workshop/ws-three.yaml.in rename to tests/main/launch-multiple/.workshop/ws-three.yaml index 0fddaf8d6..3ed5f8d5c 100644 --- a/tests/main/launch-multiple/.workshop/ws-three.yaml.in +++ b/tests/main/launch-multiple/.workshop/ws-three.yaml @@ -2,4 +2,3 @@ name: ws-three base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/launch-multiple/.workshop/ws-two.yaml.in b/tests/main/launch-multiple/.workshop/ws-two.yaml similarity index 58% rename from tests/main/launch-multiple/.workshop/ws-two.yaml.in rename to tests/main/launch-multiple/.workshop/ws-two.yaml index 12117905d..275bd7e1c 100644 --- a/tests/main/launch-multiple/.workshop/ws-two.yaml.in +++ b/tests/main/launch-multiple/.workshop/ws-two.yaml @@ -2,4 +2,3 @@ name: ws-two base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/launch-multiple/task.yaml b/tests/main/launch-multiple/task.yaml index 4e7381fb5..ee8b2cf37 100644 --- a/tests/main/launch-multiple/task.yaml +++ b/tests/main/launch-multiple/task.yaml @@ -3,7 +3,6 @@ execute: | . "$TESTSLIB"/utils.sh echo "Check workshop can be launched with a basic SDK" - resolve_branch .workshop/ws-one.yaml .workshop/ws-two.yaml .workshop/ws-three.yaml workshop_exec launch ws-one ws-two ws-three workshop_exec list | MATCH "ws-one[[:space:]]*Ready[[:space:]]*-" workshop_exec list | MATCH "ws-two[[:space:]]*Ready[[:space:]]*-" diff --git a/tests/main/launch-sdk/task.yaml b/tests/main/launch-sdk/task.yaml index 5258cd3a6..5b8b5b70d 100644 --- a/tests/main/launch-sdk/task.yaml +++ b/tests/main/launch-sdk/task.yaml @@ -3,7 +3,6 @@ execute: | . "$TESTSLIB"/utils.sh echo "Check workshop can be launched with a basic SDK" - resolve_branch workshop.yaml workshop_exec launch --verbose workshop_exec list | MATCH "ws-basic[[:space:]]*Ready[[:space:]]*-" diff --git a/tests/main/launch-sdk/workshop.yaml.in b/tests/main/launch-sdk/workshop.yaml similarity index 59% rename from tests/main/launch-sdk/workshop.yaml.in rename to tests/main/launch-sdk/workshop.yaml index 8ae49d1ef..178ba6787 100644 --- a/tests/main/launch-sdk/workshop.yaml.in +++ b/tests/main/launch-sdk/workshop.yaml @@ -2,4 +2,3 @@ name: ws-basic base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/refresh-abort/task.yaml b/tests/main/refresh-abort/task.yaml index c4527e567..c20105689 100644 --- a/tests/main/refresh-abort/task.yaml +++ b/tests/main/refresh-abort/task.yaml @@ -1,7 +1,6 @@ summary: Check a failed non-transactional refresh can be aborted successfully prepare: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/refresh-abort/workshop.yaml.in b/tests/main/refresh-abort/workshop.yaml similarity index 61% rename from tests/main/refresh-abort/workshop.yaml.in rename to tests/main/refresh-abort/workshop.yaml index 3d0e8d7f7..91ec44451 100644 --- a/tests/main/refresh-abort/workshop.yaml.in +++ b/tests/main/refresh-abort/workshop.yaml @@ -2,4 +2,3 @@ name: ws-refresh-abort base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/refresh-continue/task.yaml b/tests/main/refresh-continue/task.yaml index 0c3a1ab5b..0044aef30 100644 --- a/tests/main/refresh-continue/task.yaml +++ b/tests/main/refresh-continue/task.yaml @@ -1,7 +1,6 @@ summary: Check a failed refresh can be fixed and continued successfully prepare: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/refresh-continue/workshop.yaml.in b/tests/main/refresh-continue/workshop.yaml similarity index 61% rename from tests/main/refresh-continue/workshop.yaml.in rename to tests/main/refresh-continue/workshop.yaml index b0af9c7a1..f984dc23c 100644 --- a/tests/main/refresh-continue/workshop.yaml.in +++ b/tests/main/refresh-continue/workshop.yaml @@ -2,4 +2,3 @@ name: ws-refresh-cont base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/refresh/multi/.workshop/ws-refresh-sdk.yaml.in b/tests/main/refresh/multi/.workshop/ws-refresh-sdk.yaml similarity index 61% rename from tests/main/refresh/multi/.workshop/ws-refresh-sdk.yaml.in rename to tests/main/refresh/multi/.workshop/ws-refresh-sdk.yaml index c3781226e..50ec626e3 100644 --- a/tests/main/refresh/multi/.workshop/ws-refresh-sdk.yaml.in +++ b/tests/main/refresh/multi/.workshop/ws-refresh-sdk.yaml @@ -2,4 +2,3 @@ name: ws-refresh-sdk base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml b/tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml new file mode 100644 index 000000000..73bf9a69f --- /dev/null +++ b/tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml @@ -0,0 +1,6 @@ +name: ws-refresh-snapshots +base: ubuntu@22.04 +sdks: + - name: test-sdk-basic + - name: test-sdk-mount + - name: test-sdk-gpu diff --git a/tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml.in b/tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml.in deleted file mode 100644 index 9c5c27121..000000000 --- a/tests/main/refresh/multi/.workshop/ws-refresh-snapshots.yaml.in +++ /dev/null @@ -1,9 +0,0 @@ -name: ws-refresh-snapshots -base: ubuntu@22.04 -sdks: - - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} - - name: test-sdk-mount - channel: latest/stable${TEST_SDK_BRANCH} - - name: test-sdk-gpu - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/refresh/task.yaml b/tests/main/refresh/task.yaml index a667bedd6..8bf40974b 100644 --- a/tests/main/refresh/task.yaml +++ b/tests/main/refresh/task.yaml @@ -3,7 +3,6 @@ prepare: | . "$TESTSLIB"/utils.sh apt-add-repository -y universe apt install expect -y --no-install-recommends - resolve_branch multi/.workshop/ws-refresh-sdk.yaml multi/.workshop/ws-refresh-snapshots.yaml workshop_exec launch --project single workshop_exec launch ws-refresh ws-refresh-sdk ws-refresh-snapshots --project multi restore: | @@ -26,7 +25,7 @@ execute: | workshop_exec exec -- sudo test -f /var/cache/apt/archives/sentinel echo "Check workshop refresh --no-wait" - echo -e "sdks:\n - name: test-sdk-health-waiting\n channel: latest/stable${TEST_SDK_BRANCH}" >> workshop.yaml + echo -e "sdks:\n - name: test-sdk-health-waiting" >> workshop.yaml workshop_exec refresh --no-wait # The refresh process here takes about 10s. diff --git a/tests/main/remount/task.yaml b/tests/main/remount/task.yaml index 43db2ae77..1c9852192 100644 --- a/tests/main/remount/task.yaml +++ b/tests/main/remount/task.yaml @@ -7,7 +7,6 @@ restore: | execute: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.yaml workshop_exec launch echo "Remount: the new source is empty or does not exist" new_source="/home/ubuntu/one" diff --git a/tests/main/remount/workshop.yaml.in b/tests/main/remount/workshop.yaml similarity index 59% rename from tests/main/remount/workshop.yaml.in rename to tests/main/remount/workshop.yaml index 38ca87cdb..fc60c5e7d 100644 --- a/tests/main/remount/workshop.yaml.in +++ b/tests/main/remount/workshop.yaml @@ -2,4 +2,3 @@ name: ws-remount base: ubuntu@22.04 sdks: - name: test-sdk-mount - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2.yaml.in b/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2.yaml similarity index 61% rename from tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2.yaml.in rename to tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2.yaml index 10b54633d..20ceb0a7c 100644 --- a/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2.yaml.in +++ b/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2.yaml @@ -2,4 +2,3 @@ name: ws-snap-remove-2 base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2a.yaml.in b/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2a.yaml similarity index 62% rename from tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2a.yaml.in rename to tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2a.yaml index f101690bc..cf82fe14e 100644 --- a/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2a.yaml.in +++ b/tests/main/remove-snap/project-2/.workshop/ws-snap-remove-2a.yaml @@ -2,4 +2,3 @@ name: ws-snap-remove-2a base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/remove-snap/task.yaml b/tests/main/remove-snap/task.yaml index 19124eda0..e928e691e 100644 --- a/tests/main/remove-snap/task.yaml +++ b/tests/main/remove-snap/task.yaml @@ -9,7 +9,6 @@ execute: | # shellcheck source=tests/lib/utils.sh . "$TESTSLIB"/utils.sh - resolve_branch project-2/.workshop/ws-snap-remove-2.yaml project-2/.workshop/ws-snap-remove-2a.yaml workshop_exec launch --project $(pwd)/project-1 ws-snap-remove-1 ws-snap-remove-1a workshop_exec launch --project $(pwd)/project-2 ws-snap-remove-2 ws-snap-remove-2a diff --git a/tests/main/remove/.workshop/ws-remove-mount.yaml.in b/tests/main/remove/.workshop/ws-remove-mount.yaml similarity index 61% rename from tests/main/remove/.workshop/ws-remove-mount.yaml.in rename to tests/main/remove/.workshop/ws-remove-mount.yaml index 7562329de..ed0f16d32 100644 --- a/tests/main/remove/.workshop/ws-remove-mount.yaml.in +++ b/tests/main/remove/.workshop/ws-remove-mount.yaml @@ -2,4 +2,3 @@ name: ws-remove-mount base: ubuntu@22.04 sdks: - name: test-sdk-mount - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/remove/task.yaml b/tests/main/remove/task.yaml index 72e6b5e08..9521d491e 100644 --- a/tests/main/remove/task.yaml +++ b/tests/main/remove/task.yaml @@ -8,7 +8,6 @@ execute: | . "$TESTSLIB"/utils.sh echo "Launch a workshop" - resolve_branch .workshop/ws-remove-mount.yaml workshop_exec launch ws-remove echo "Modify apt cache for later" @@ -34,14 +33,14 @@ execute: | echo "Check the workshop can be removed if in Waiting" workshop_exec launch ws-remove + cp .workshop/ws-remove.yaml .workshop/ws-remove.yaml.old cat <> .workshop/ws-remove.yaml sdks: - name: test-sdk-setup-fail - channel: latest/stable${TEST_SDK_BRANCH} EOF ! workshop_exec refresh --wait-on-error ws-remove || false workshop_exec remove ws-remove - sed -i '/^sdks:$/,/^ channel: latest\/stable.*$/d' .workshop/ws-remove.yaml + mv .workshop/ws-remove.yaml.old .workshop/ws-remove.yaml workshop_exec list | MATCH "^ws-remove[[:space:]]+Off[[:space:]]+-$" echo "Check the workshop can be removed if in Error" diff --git a/tests/main/restore/workshop.yaml b/tests/main/restore/workshop.yaml index f52890b72..b12a54582 100644 --- a/tests/main/restore/workshop.yaml +++ b/tests/main/restore/workshop.yaml @@ -2,4 +2,3 @@ name: ws-restore base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable diff --git a/tests/main/sdk-find/task.yaml b/tests/main/sdk-find/task.yaml index 219746598..73aa00868 100644 --- a/tests/main/sdk-find/task.yaml +++ b/tests/main/sdk-find/task.yaml @@ -3,6 +3,6 @@ execute: | . "$TESTSLIB"/utils.sh echo "Test SDK find" - sdk_exec find test-sdk-find-1 > find.log - MATCH 'test-sdk-find-1[[:space:]]+0\.1\.1[[:space:]]+Jonathan Conder[[:space:]]+Test SDK summary' < find.log - MATCH 'test-sdk-find-1-s390x[[:space:]]+0\.1\+s390x[[:space:]]+Jonathan Conder[[:space:]]+Test SDK s390x summary' < find.log + sdk_exec find test-sdk-find-2 > find.log + MATCH 'test-sdk-find-2[[:space:]]+0\.2[[:space:]]+Dmitry Lyfar[[:space:]]+Test SDK find 2 summary' < find.log + MATCH 'test-sdk-find-2-s390x[[:space:]]+0\.2\+s390x[[:space:]]+Dmitry Lyfar[[:space:]]+Test SDK find 2 s390x summary' < find.log diff --git a/tests/main/sdk-info/.workshop/edge.yaml b/tests/main/sdk-info/.workshop/edge.yaml new file mode 100644 index 000000000..538693dc3 --- /dev/null +++ b/tests/main/sdk-info/.workshop/edge.yaml @@ -0,0 +1,5 @@ +name: edge +base: ubuntu@22.04 +sdks: + - name: test-sdk-info-1 + channel: edge diff --git a/tests/main/sdk-info/.workshop/edge.yaml.in b/tests/main/sdk-info/.workshop/edge.yaml.in deleted file mode 100644 index 8277ed2d3..000000000 --- a/tests/main/sdk-info/.workshop/edge.yaml.in +++ /dev/null @@ -1,5 +0,0 @@ -name: edge -base: ubuntu@22.04 -sdks: - - name: test-sdk-info - channel: edge${TEST_SDK_BRANCH} diff --git a/tests/main/sdk-info/.workshop/stable.yaml b/tests/main/sdk-info/.workshop/stable.yaml new file mode 100644 index 000000000..dfeb15c67 --- /dev/null +++ b/tests/main/sdk-info/.workshop/stable.yaml @@ -0,0 +1,5 @@ +name: stable +base: ubuntu@22.04 +sdks: + - name: test-sdk-info-1 + channel: stable diff --git a/tests/main/sdk-info/.workshop/stable.yaml.in b/tests/main/sdk-info/.workshop/stable.yaml.in deleted file mode 100644 index a54983bd0..000000000 --- a/tests/main/sdk-info/.workshop/stable.yaml.in +++ /dev/null @@ -1,5 +0,0 @@ -name: stable -base: ubuntu@22.04 -sdks: - - name: test-sdk-info - channel: stable${TEST_SDK_BRANCH} diff --git a/tests/main/sdk-info/header.txt b/tests/main/sdk-info/header.txt index 617959040..c4af518d3 100644 --- a/tests/main/sdk-info/header.txt +++ b/tests/main/sdk-info/header.txt @@ -1,5 +1,5 @@ -name: test-sdk-info -publisher: Dmitry Lyfar (usso-dlyfar) +name: test-sdk-info-1 +publisher: Dmitry Lyfar (dlyfar) license: GPL-3.0 This is test-sdk-info's description. You have a paragraph or two to tell the diff --git a/tests/main/sdk-info/task.yaml b/tests/main/sdk-info/task.yaml index c626e20c2..3b893bc47 100644 --- a/tests/main/sdk-info/task.yaml +++ b/tests/main/sdk-info/task.yaml @@ -1,7 +1,7 @@ summary: Test sdk info output prepare: | sdk_info_volumes() { - lxc storage volume list --columns n --format csv workshop config.user.sdk.name=test-sdk-info + lxc storage volume list --columns n --format csv workshop config.user.sdk.name=test-sdk-info-1 } sdk_info_volumes | xargs -n1 -r lxc storage volume delete workshop restore: | @@ -15,7 +15,7 @@ execute: | lines=$(wc -l < header.txt) echo "Test SDK info without any volumes" - sdk_exec info test-sdk-info > info.log + sdk_exec info test-sdk-info-1 > info.log diff -u header.txt <(head --lines="$lines" info.log) MATCH '[[:space:]]+latest/stable[[:space:]]+1\.0[[:space:]]+[0-9]{4}-[0-9]{2}-[0-9]{2}[[:space:]]+all[[:space:]]+[0-9]+[[:space:]]+[0-9]+B' < info.log @@ -24,21 +24,20 @@ execute: | MATCH '[[:space:]]+latest/edge[[:space:]]+2\.0[[:space:]]+[0-9]{4}-[0-9]{2}-[0-9]{2}[[:space:]]+all[[:space:]]+[0-9]+[[:space:]]+[0-9]+B' < info.log echo "Test SDK info with one volume" - resolve_branch .workshop/edge.yaml .workshop/stable.yaml workshop_exec launch edge - sdk_exec list | MATCH '^test-sdk-info[[:space:]]+2\.0[[:space:]]+[[:digit:]]+[[:space:]]+.*kB$' + sdk_exec list | MATCH '^test-sdk-info-1[[:space:]]+2\.0[[:space:]]+[[:digit:]]+[[:space:]]+.*kB$' - sdk_exec info test-sdk-info > info.log + sdk_exec info test-sdk-info-1 > info.log diff -u header.txt <(head --lines="$lines" info.log) MATCH '[[:space:]]+latest/stable[[:space:]]+1\.0[[:space:]]+[0-9]{4}-[0-9]{2}-[0-9]{2}[[:space:]]+all[[:space:]]+[0-9]+[[:space:]]+[0-9]+B' < info.log MATCH "$PWD[[:space:]]+edge[[:space:]]+latest/edge[[:space:]]+2\\.0[[:space:]]+all[[:space:]]+[[:digit:]]+$" < info.log echo "Test SDK info with two volumes" workshop_exec launch stable - sdk_exec list | MATCH '^test-sdk-info[[:space:]]+1\.0[[:space:]]+[[:digit:]]+[[:space:]]+.*kB$' - sdk_exec list | MATCH '^test-sdk-info[[:space:]]+2\.0[[:space:]]+[[:digit:]]+[[:space:]]+.*kB$' + sdk_exec list | MATCH '^test-sdk-info-1[[:space:]]+1\.0[[:space:]]+[[:digit:]]+[[:space:]]+.*kB$' + sdk_exec list | MATCH '^test-sdk-info-1[[:space:]]+2\.0[[:space:]]+[[:digit:]]+[[:space:]]+.*kB$' - sdk_exec info test-sdk-info > info.log + sdk_exec info test-sdk-info-1 > info.log diff -u header.txt <(head --lines="$lines" info.log) MATCH '[[:space:]]+latest/edge[[:space:]]+2\.0[[:space:]]+[0-9]{4}-[0-9]{2}-[0-9]{2}[[:space:]]+all[[:space:]]+[0-9]+[[:space:]]+[0-9]+B' < info.log MATCH "$PWD[[:space:]]+edge[[:space:]]+latest/edge[[:space:]]+2\\.0[[:space:]]+all[[:space:]]+[[:digit:]]+$" < info.log @@ -46,9 +45,9 @@ execute: | echo "Test SDK info with two volumes but one workshop" workshop_exec remove edge - sdk_exec list | MATCH '^test-sdk-info[[:space:]]+1\.0[[:space:]]+[[:digit:]]+[[:space:]]+.*kB$' - sdk_exec list | MATCH '^test-sdk-info[[:space:]]+2\.0[[:space:]]+[[:digit:]]+[[:space:]]+.*kB$' + sdk_exec list | MATCH '^test-sdk-info-1[[:space:]]+1\.0[[:space:]]+[[:digit:]]+[[:space:]]+.*kB$' + sdk_exec list | MATCH '^test-sdk-info-1[[:space:]]+2\.0[[:space:]]+[[:digit:]]+[[:space:]]+.*kB$' - sdk_exec info test-sdk-info > info.log + sdk_exec info test-sdk-info-1 > info.log diff -u header.txt <(head --lines="$lines" info.log) MATCH "$PWD[[:space:]]+stable[[:space:]]+latest/stable[[:space:]]+1\\.0[[:space:]]+all[[:space:]]+[[:digit:]]+$" < info.log diff --git a/tests/main/shell/task.yaml b/tests/main/shell/task.yaml index ce608d97d..34de271d7 100644 --- a/tests/main/shell/task.yaml +++ b/tests/main/shell/task.yaml @@ -1,7 +1,6 @@ summary: Check shell functionality and environment prepare: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.yaml workshop_exec launch ws-shell restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/shell/workshop.yaml.in b/tests/main/shell/workshop.yaml similarity index 59% rename from tests/main/shell/workshop.yaml.in rename to tests/main/shell/workshop.yaml index 0d7b72c8c..c0386cda1 100644 --- a/tests/main/shell/workshop.yaml.in +++ b/tests/main/shell/workshop.yaml @@ -2,4 +2,3 @@ name: ws-shell base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/sketch-sdk/task.yaml b/tests/main/sketch-sdk/task.yaml index 3a7c5749c..885e81249 100644 --- a/tests/main/sketch-sdk/task.yaml +++ b/tests/main/sketch-sdk/task.yaml @@ -1,7 +1,6 @@ summary: Check workshop sketch-sdk prepare: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/sketch-sdk/workshop.yaml.in b/tests/main/sketch-sdk/workshop.yaml similarity index 59% rename from tests/main/sketch-sdk/workshop.yaml.in rename to tests/main/sketch-sdk/workshop.yaml index 6b7d62836..ae89c854a 100644 --- a/tests/main/sketch-sdk/workshop.yaml.in +++ b/tests/main/sketch-sdk/workshop.yaml @@ -2,4 +2,3 @@ name: ws-sketch base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/start/task.yaml b/tests/main/start/task.yaml index f5ab44ab6..5d991529b 100644 --- a/tests/main/start/task.yaml +++ b/tests/main/start/task.yaml @@ -1,7 +1,6 @@ summary: Check workshop start prepare: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/start/workshop.yaml.in b/tests/main/start/workshop.yaml similarity index 59% rename from tests/main/start/workshop.yaml.in rename to tests/main/start/workshop.yaml index 59fa1cd0b..6f73258ef 100644 --- a/tests/main/start/workshop.yaml.in +++ b/tests/main/start/workshop.yaml @@ -2,4 +2,3 @@ name: ws-start base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/stop/task.yaml b/tests/main/stop/task.yaml index 9f167a52a..6f664d671 100644 --- a/tests/main/stop/task.yaml +++ b/tests/main/stop/task.yaml @@ -1,7 +1,6 @@ summary: Check workshop stop prepare: | . "$TESTSLIB"/utils.sh - resolve_branch workshop.yaml workshop_exec launch restore: | . "$TESTSLIB"/utils.sh diff --git a/tests/main/stop/workshop.yaml.in b/tests/main/stop/workshop.yaml similarity index 58% rename from tests/main/stop/workshop.yaml.in rename to tests/main/stop/workshop.yaml index 7488eca53..e9d67eea0 100644 --- a/tests/main/stop/workshop.yaml.in +++ b/tests/main/stop/workshop.yaml @@ -2,4 +2,3 @@ name: ws-stop base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} diff --git a/tests/main/try-sdks/task.yaml b/tests/main/try-sdks/task.yaml index 7950f26f8..238615f7c 100644 --- a/tests/main/try-sdks/task.yaml +++ b/tests/main/try-sdks/task.yaml @@ -10,8 +10,12 @@ restore: | execute: | . "$TESTSLIB"/utils.sh + mkdir work + chown ubuntu:ubuntu work + sudo -u ubuntu -- cp -r "$TESTSLIB/sdk/test-sdk-basic/latest"/{stable,edge} work/ + echo "Launch workshop with SDKs from try area" - (cd "$TESTSLIB/sdk/test-sdk-basic/latest/stable" && run_sdkcraft try) + (cd work/stable && run_sdkcraft try) workshop_exec launch workshop_exec exec -- findmnt /var/lib/workshop/sdk/test-sdk-basic | MATCH 'workshop/custom/default_test-sdk-basic-x1' workshop_exec info | yq '.sdks["test-sdk-basic"].tracking' | MATCH '^~/.local/share/workshop/try/test-sdk-basic$' @@ -31,7 +35,7 @@ execute: | workshop_exec info | yq '.sdks["test-sdk-basic"].installed' | MATCH '\(x1\)$' echo "Check SDKs in try area can be updated" - (cd "$TESTSLIB/sdk/test-sdk-basic/latest/edge" && run_sdkcraft try) + (cd work/edge && run_sdkcraft try) workshop_exec refresh workshop_exec info | yq '.sdks["test-sdk-basic"].installed' | MATCH '\(x2\)$' workshop_exec exec -- cat /var/lib/workshop/sdk/test-sdk-basic/sdk/hooks/setup-base | MATCH 'setup-base v2' diff --git a/tests/main/workshop-connections/.workshop/ws-user-conns.yaml.in b/tests/main/workshop-connections/.workshop/ws-user-conns.yaml similarity index 82% rename from tests/main/workshop-connections/.workshop/ws-user-conns.yaml.in rename to tests/main/workshop-connections/.workshop/ws-user-conns.yaml index befd841b6..7f8a652ca 100644 --- a/tests/main/workshop-connections/.workshop/ws-user-conns.yaml.in +++ b/tests/main/workshop-connections/.workshop/ws-user-conns.yaml @@ -2,13 +2,11 @@ name: ws-user-conns base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} slots: user-slot: interface: mount workshop-source: /project/user-data-2 - name: test-sdk-mount - channel: latest/stable${TEST_SDK_BRANCH} plugs: wp-plug: interface: mount diff --git a/tests/main/workshop-connections/.workshop/ws-user-conns.yaml.new.in b/tests/main/workshop-connections/.workshop/ws-user-conns.yaml.new similarity index 75% rename from tests/main/workshop-connections/.workshop/ws-user-conns.yaml.new.in rename to tests/main/workshop-connections/.workshop/ws-user-conns.yaml.new index 83f947b54..b421306ce 100644 --- a/tests/main/workshop-connections/.workshop/ws-user-conns.yaml.new.in +++ b/tests/main/workshop-connections/.workshop/ws-user-conns.yaml.new @@ -2,13 +2,11 @@ name: ws-user-conns base: ubuntu@22.04 sdks: - name: test-sdk-basic - channel: latest/stable${TEST_SDK_BRANCH} slots: user-slot: interface: mount workshop-source: /project/user-data-2 - name: test-sdk-mount - channel: latest/stable${TEST_SDK_BRANCH} connections: - plug: test-sdk-mount:two slot: test-sdk-basic:user-slot diff --git a/tests/main/workshop-connections/task.yaml b/tests/main/workshop-connections/task.yaml index 147f4ab55..4c0bcf6c4 100644 --- a/tests/main/workshop-connections/task.yaml +++ b/tests/main/workshop-connections/task.yaml @@ -2,7 +2,6 @@ summary: Check that "workshop connections" works as expected prepare: | . "$TESTSLIB"/utils.sh mkdir user-data user-data-2 - resolve_branch .workshop/ws-user-conns.yaml .workshop/ws-user-conns.yaml.new workshop_exec launch restore: | . "$TESTSLIB"/utils.sh From 9762e3601e52e6c2f5c1473439f5fa43fcf2ad0c Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Fri, 17 Apr 2026 08:55:52 +1200 Subject: [PATCH 16/17] Workaround SDKcraft environment issue --- tests/lib/utils.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lib/utils.sh b/tests/lib/utils.sh index 741b10011..5360b6ae4 100644 --- a/tests/lib/utils.sh +++ b/tests/lib/utils.sh @@ -145,7 +145,7 @@ function sdk_exec() { } function run_sdkcraft() { - sudo -u ubuntu -- sdkcraft "$@" + sudo XDG_RUNTIME_DIR="/run/user/$(id -u ubuntu)" -u ubuntu -- sdkcraft "$@" } function install_sdkcraft() { From dfe36f93af9cfa8a7095ba65c462efdd3bd160c8 Mon Sep 17 00:00:00 2001 From: Jonathan Conder Date: Thu, 16 Apr 2026 17:43:22 +1200 Subject: [PATCH 17/17] Temporarily disable immutability check for existing SDKs --- .github/workflows/build-deps.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-deps.yaml b/.github/workflows/build-deps.yaml index 8fa62eb44..c4b83f09f 100644 --- a/.github/workflows/build-deps.yaml +++ b/.github/workflows/build-deps.yaml @@ -109,10 +109,11 @@ jobs: if git diff --quiet "${base}..HEAD" -- "$path"; then continue fi - if git cat-file -t "${base}:./${path}" &> /dev/null; then - printf '::error::%s SDK is immutable, create a new SDK instead.\n' "${path%/}" - exit 1 - fi + # TODO: enable this after merging https://github.com/canonical/workshop/pull/689 + # if git cat-file -t "${base}:./${path}" &> /dev/null; then + # printf '::error::%s SDK is immutable, create a new SDK instead.\n' "${path%/}" + # exit 1 + # fi done cd ..