Skip to content

Commit 971d419

Browse files
authored
feat: add update-global-images command (#37)
Add a new command, update-global-images, which updates a Renku instance with the latest global images (see: https://github.com/SwissDataScienceCenter/renku/tree/master/global-images).
1 parent f9cc367 commit 971d419

File tree

13 files changed

+5117
-11
lines changed

13 files changed

+5117
-11
lines changed

Makefile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,17 @@ renku-users-apispec: ## Download the "users" API spec
7777
sed -e 's/- default: "general"//g' pkg/renkuapi/users/api.spec.yaml > pkg/renkuapi/users/api.spec.new.yaml
7878
mv pkg/renkuapi/users/api.spec.new.yaml pkg/renkuapi/users/api.spec.yaml
7979

80+
.PHONY: renku-session-apispec
81+
renku-session-apispec: ## Download the "session" API spec
82+
curl -L -o pkg/renkuapi/session/api.spec.yaml https://raw.githubusercontent.com/SwissDataScienceCenter/renku-data-services/refs/heads/main/components/renku_data_services/session/api.spec.yaml
83+
# sed -e 's/- default: "general"//g' pkg/renkuapi/users/api.spec.yaml > pkg/renkuapi/users/api.spec.new.yaml
84+
# mv pkg/renkuapi/users/api.spec.new.yaml pkg/renkuapi/users/api.spec.yaml
85+
8086
.PHONY: generate
81-
generate: pkg/renkuapi/users/users_gen.go ## Run go generate
87+
generate: pkg/renkuapi/users/users_gen.go pkg/renkuapi/session/session_gen.go ## Run go generate
8288

8389
pkg/renkuapi/users/users_gen.go: pkg/renkuapi/users/api.spec.yaml
8490
go generate pkg/renkuapi/users/users.go
91+
92+
pkg/renkuapi/session/session_gen.go: pkg/renkuapi/session/api.spec.yaml
93+
go generate pkg/renkuapi/session/session.go

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require (
1414
k8s.io/api v0.34.1
1515
k8s.io/apimachinery v0.34.1
1616
k8s.io/client-go v0.34.1
17+
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
1718
)
1819

1920
require (
@@ -71,7 +72,6 @@ require (
7172
gopkg.in/yaml.v3 v3.0.1 // indirect
7273
k8s.io/klog/v2 v2.130.1 // indirect
7374
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
74-
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
7575
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
7676
sigs.k8s.io/randfill v1.0.0 // indirect
7777
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect

pkg/cmd/login.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"github.com/SwissDataScienceCenter/renku-dev-utils/pkg/github"
99
ns "github.com/SwissDataScienceCenter/renku-dev-utils/pkg/namespace"
1010
"github.com/SwissDataScienceCenter/renku-dev-utils/pkg/renkuapi"
11-
"github.com/SwissDataScienceCenter/renku-dev-utils/pkg/renkuapi/users"
1211
"github.com/spf13/cobra"
1312
)
1413

@@ -56,19 +55,19 @@ func login(cmd *cobra.Command, args []string) {
5655

5756
fmt.Printf("Renku URL: %s\n", url)
5857

59-
auth, err := renkuapi.NewRenkuApiAuth(url)
58+
rac, err := renkuapi.NewRenkuApiClient(url)
6059
if err != nil {
6160
fmt.Println(err)
6261
os.Exit(1)
6362
}
6463

65-
err = auth.Login(ctx)
64+
err = rac.Auth().Login(ctx)
6665
if err != nil {
6766
fmt.Println(err)
6867
os.Exit(1)
6968
}
7069

71-
ruc, err := users.NewRenkuUsersClient(url, users.WithRequestEditors(users.RequestEditorFn(auth.RequestEditor())))
70+
ruc, err := rac.Users()
7271
if err != nil {
7372
fmt.Println(err)
7473
os.Exit(1)

pkg/cmd/logout.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ func logout(cmd *cobra.Command, args []string) {
6969

7070
fmt.Printf("Renku URL: %s\n", url)
7171

72-
auth, err := renkuapi.NewRenkuApiAuth(url)
72+
rac, err := renkuapi.NewRenkuApiClient(url)
7373
if err != nil {
7474
fmt.Println(err)
7575
os.Exit(1)
7676
}
7777

78-
err = auth.Logout(ctx)
78+
err = rac.Auth().Logout(ctx)
7979
if err != nil {
8080
fmt.Println(err)
8181
os.Exit(1)

pkg/cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func init() {
3838
rootCmd.AddCommand(makeMeAdminCmd)
3939
rootCmd.AddCommand(namespaceCmd)
4040
rootCmd.AddCommand(openDeploymentCmd)
41+
rootCmd.AddCommand(updateGlobalImagesCmd)
4142
rootCmd.AddCommand(versionCmd)
4243
}
4344

pkg/cmd/updateglobalimages.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
8+
"github.com/SwissDataScienceCenter/renku-dev-utils/pkg/github"
9+
ns "github.com/SwissDataScienceCenter/renku-dev-utils/pkg/namespace"
10+
"github.com/SwissDataScienceCenter/renku-dev-utils/pkg/renkuapi"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
var updateGlobalImagesCmd = &cobra.Command{
15+
Use: "update-global-images",
16+
Short: "Updates the global images",
17+
Run: updateGlobalImages,
18+
}
19+
20+
func updateGlobalImages(cmd *cobra.Command, args []string) {
21+
ctx := context.Background()
22+
23+
release, err := cmd.Flags().GetString("release")
24+
if err != nil {
25+
fmt.Println(err)
26+
os.Exit(1)
27+
}
28+
29+
if release == "" {
30+
cli, err := github.NewGitHubCLI("")
31+
if err != nil {
32+
fmt.Println(err)
33+
os.Exit(1)
34+
}
35+
release, err = cli.GetLatestRenkuRelease(ctx)
36+
if err != nil {
37+
fmt.Println(err)
38+
os.Exit(1)
39+
}
40+
}
41+
42+
fmt.Printf("Renku release: %s\n", release)
43+
44+
url, err := cmd.Flags().GetString("url")
45+
if err != nil {
46+
fmt.Println(err)
47+
os.Exit(1)
48+
}
49+
50+
if url == "" {
51+
namespace, err := cmd.Flags().GetString("namespace")
52+
if err != nil {
53+
fmt.Println(err)
54+
os.Exit(1)
55+
}
56+
if namespace == "" {
57+
cli, err := github.NewGitHubCLI("")
58+
if err != nil {
59+
fmt.Println(err)
60+
os.Exit(1)
61+
}
62+
namespace, err = ns.FindCurrentNamespace(ctx, cli)
63+
if err != nil {
64+
fmt.Println(err)
65+
os.Exit(1)
66+
}
67+
}
68+
69+
deploymentURL, err := ns.GetDeploymentURL(namespace)
70+
if err != nil {
71+
fmt.Println(err)
72+
os.Exit(1)
73+
}
74+
url = deploymentURL.String()
75+
}
76+
77+
fmt.Printf("Renku URL: %s\n", url)
78+
79+
rac, err := renkuapi.NewRenkuApiClient(url)
80+
if err != nil {
81+
fmt.Println(err)
82+
os.Exit(1)
83+
}
84+
85+
if !rac.IsLoggedIn(ctx) {
86+
fmt.Println("Error: not logged in")
87+
showCmd := "rdu login"
88+
if url != "" {
89+
showCmd = showCmd + fmt.Sprintf(" --url %s", url)
90+
}
91+
if namespace != "" {
92+
showCmd = showCmd + fmt.Sprintf(" --namespace %s", namespace)
93+
}
94+
fmt.Printf("Please run \"%s\" before running this command\n", showCmd)
95+
os.Exit(1)
96+
}
97+
98+
if !rac.IsAdmin(ctx) {
99+
fmt.Println("Error: not an admin")
100+
fmt.Println("Please make sure you are a Renku admin before running this command")
101+
fmt.Println("See: rdu make-me-admin --help")
102+
os.Exit(1)
103+
}
104+
105+
rsc, err := rac.Session()
106+
if err != nil {
107+
fmt.Println(err)
108+
os.Exit(1)
109+
}
110+
111+
envs, err := rsc.GetGlobalEnvironments(ctx)
112+
if err != nil {
113+
fmt.Println(err)
114+
os.Exit(1)
115+
}
116+
117+
dryRun, err := cmd.Flags().GetBool("dry-run")
118+
if err != nil {
119+
fmt.Println(err)
120+
os.Exit(1)
121+
}
122+
123+
err = rsc.UpdateGlobalImages(ctx, github.GetGlobalImages(), release, envs, dryRun)
124+
if err != nil {
125+
fmt.Println(err)
126+
os.Exit(1)
127+
}
128+
}
129+
130+
func init() {
131+
updateGlobalImagesCmd.Flags().String("url", "", "instance URL")
132+
updateGlobalImagesCmd.Flags().StringP("namespace", "n", "", "k8s namespace")
133+
updateGlobalImagesCmd.Flags().String("release", "", "renku release")
134+
updateGlobalImagesCmd.Flags().Bool("dry-run", false, "dry run")
135+
}

pkg/github/release.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
)
7+
8+
const renkuRepository = "SwissDataScienceCenter/renku"
9+
10+
func (cli *GitHubCLI) GetLatestRenkuRelease(ctx context.Context) (string, error) {
11+
out, err := cli.RunCmd(ctx, "release", "view", "--repo", renkuRepository, "--json", "tagName")
12+
if err != nil {
13+
return "", err
14+
}
15+
16+
var res gitHubReleaseViewOutput
17+
err = json.Unmarshal(out, &res)
18+
if err != nil {
19+
return "", err
20+
}
21+
22+
return res.TagName, nil
23+
}
24+
25+
type gitHubReleaseViewOutput struct {
26+
TagName string `json:"tagName"`
27+
}

pkg/github/utils.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import "fmt"
55
// Maps repositories to namespace templates
66
var repoToNamespaceTemplateMap map[string]string
77

8+
// Contains the list of Renku global images
9+
var globalImagesSlice []string
10+
811
func DeriveK8sNamespace(repo string, pr int) (string, error) {
912
tpl, found := repoToNamespaceTemplateMap[repo]
1013
if found {
@@ -13,8 +16,13 @@ func DeriveK8sNamespace(repo string, pr int) (string, error) {
1316
return "", fmt.Errorf("could not derive namespace from repository: %s", repo)
1417
}
1518

19+
func GetGlobalImages() []string {
20+
return globalImagesSlice[:]
21+
}
22+
1623
func init() {
1724
initRepoToNamespaceTemplateMap()
25+
initGlobalImagesSlice()
1826
}
1927

2028
func initRepoToNamespaceTemplateMap() {
@@ -25,3 +33,16 @@ func initRepoToNamespaceTemplateMap() {
2533
"SwissDataScienceCenter/renku-ui": "renku-ci-ui-%d",
2634
}
2735
}
36+
37+
func initGlobalImagesSlice() {
38+
// TODO: can we derive this from GitHub API calls?
39+
prefix := "ghcr.io/swissdatasciencecenter/renku"
40+
packageVariants := []string{"basic", "datascience"}
41+
frontendVariants := []string{"jupyterlab", "ttyd", "vscodium"}
42+
globalImagesSlice = make([]string, 0, len(packageVariants)*len(frontendVariants))
43+
for _, packageVariant := range packageVariants {
44+
for _, frontendVariant := range frontendVariants {
45+
globalImagesSlice = append(globalImagesSlice, fmt.Sprintf("%s/py-%s-%s", prefix, packageVariant, frontendVariant))
46+
}
47+
}
48+
}

pkg/renkuapi/client.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package renkuapi
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/url"
7+
8+
"github.com/SwissDataScienceCenter/renku-dev-utils/pkg/renkuapi/session"
9+
"github.com/SwissDataScienceCenter/renku-dev-utils/pkg/renkuapi/users"
10+
)
11+
12+
type RenkuApiClient struct {
13+
baseURL *url.URL
14+
15+
auth *RenkuApiAuth
16+
17+
rsc *session.RenkuSessionClient
18+
ruc *users.RenkuUsersClient
19+
20+
httpClient *http.Client
21+
}
22+
23+
func NewRenkuApiClient(baseURL string) (rac *RenkuApiClient, err error) {
24+
parsedURL, err := url.Parse(baseURL)
25+
if err != nil {
26+
return nil, err
27+
}
28+
if parsedURL.EscapedPath() == "/" {
29+
parsedURL.Path = ""
30+
}
31+
rac = &RenkuApiClient{
32+
baseURL: parsedURL,
33+
}
34+
if rac.httpClient == nil {
35+
rac.httpClient = http.DefaultClient
36+
}
37+
38+
// initialize auth
39+
auth, err := NewRenkuApiAuth(baseURL)
40+
if err != nil {
41+
return nil, err
42+
}
43+
rac.auth = auth
44+
45+
return rac, nil
46+
}
47+
48+
func (rac *RenkuApiClient) Auth() *RenkuApiAuth {
49+
return rac.auth
50+
}
51+
52+
func (rac *RenkuApiClient) IsLoggedIn(ctx context.Context) bool {
53+
token, _ := rac.auth.GetAccessToken(ctx)
54+
return token != ""
55+
}
56+
57+
func (rac *RenkuApiClient) IsAdmin(ctx context.Context) bool {
58+
ruc, err := rac.Users()
59+
if err != nil {
60+
return false
61+
}
62+
userInfo, err := ruc.GetUser(ctx)
63+
if err != nil {
64+
return false
65+
}
66+
return userInfo.IsAdmin
67+
}
68+
69+
func (rac *RenkuApiClient) Session() (rsc *session.RenkuSessionClient, err error) {
70+
if rac.rsc == nil {
71+
rsc, err = session.NewRenkuSessionClient(rac.baseURL.String(), session.WithRequestEditors(session.RequestEditorFn(rac.auth.RequestEditor())))
72+
if err != nil {
73+
return nil, err
74+
}
75+
rac.rsc = rsc
76+
}
77+
return rac.rsc, nil
78+
}
79+
80+
func (rac *RenkuApiClient) Users() (ruc *users.RenkuUsersClient, err error) {
81+
if rac.ruc == nil {
82+
ruc, err = users.NewRenkuUsersClient(rac.baseURL.String(), users.WithRequestEditors(users.RequestEditorFn(rac.auth.RequestEditor())))
83+
if err != nil {
84+
return nil, err
85+
}
86+
rac.ruc = ruc
87+
}
88+
return rac.ruc, nil
89+
}

0 commit comments

Comments
 (0)