Skip to content

Commit 2bfbd17

Browse files
authored
Merge branch 'ClusterCockpit:master' into master
2 parents 4047a6e + 96977c6 commit 2bfbd17

File tree

16 files changed

+168
-137
lines changed

16 files changed

+168
-137
lines changed

cmd/cc-backend/cli.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ var (
1212
)
1313

1414
func cliInit() {
15-
flag.BoolVar(&flagInit, "init", false, "Setup var directory, initialize swlite database file, config.json and .env")
15+
flag.BoolVar(&flagInit, "init", false, "Setup var directory, initialize sqlite database file, config.json and .env")
1616
flag.BoolVar(&flagReinitDB, "init-db", false, "Go through job-archive and re-initialize the 'job', 'tag', and 'jobtag' tables (all running jobs will be lost!)")
1717
flag.BoolVar(&flagSyncLDAP, "sync-ldap", false, "Sync the 'hpc_user' table with ldap")
1818
flag.BoolVar(&flagServer, "server", false, "Start a server, continues listening on port after initialization and argument handling")
@@ -24,10 +24,10 @@ func cliInit() {
2424
flag.BoolVar(&flagForceDB, "force-db", false, "Force database version, clear dirty flag and exit")
2525
flag.BoolVar(&flagLogDateTime, "logdate", false, "Set this flag to add date and time to log messages")
2626
flag.StringVar(&flagConfigFile, "config", "./config.json", "Specify alternative path to `config.json`")
27-
flag.StringVar(&flagNewUser, "add-user", "", "Add a new user. Argument format: `<username>:[admin,support,manager,api,user]:<password>`")
28-
flag.StringVar(&flagDelUser, "del-user", "", "Remove user by `username`")
27+
flag.StringVar(&flagNewUser, "add-user", "", "Add a new user. Argument format: <username>:[admin,support,manager,api,user]:<password>")
28+
flag.StringVar(&flagDelUser, "del-user", "", "Remove a existing user. Argument format: <username>")
2929
flag.StringVar(&flagGenJWT, "jwt", "", "Generate and print a JWT for the user specified by its `username`")
3030
flag.StringVar(&flagImportJob, "import-job", "", "Import a job. Argument format: `<path-to-meta.json>:<path-to-data.json>,...`")
31-
flag.StringVar(&flagLogLevel, "loglevel", "warn", "Sets the logging level: `[debug,info,warn (default),err,fatal,crit]`")
31+
flag.StringVar(&flagLogLevel, "loglevel", "warn", "Sets the logging level: `[debug, info (default), warn, err, crit]`")
3232
flag.Parse()
3333
}

cmd/cc-backend/init.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package main
66

77
import (
8-
"fmt"
98
"os"
109

1110
"github.com/ClusterCockpit/cc-backend/internal/repository"
@@ -62,24 +61,23 @@ const configString = `
6261

6362
func initEnv() {
6463
if util.CheckFileExists("var") {
65-
fmt.Print("Directory ./var already exists. Exiting!\n")
66-
os.Exit(0)
64+
log.Exit("Directory ./var already exists. Cautiously exiting application initialization.")
6765
}
6866

6967
if err := os.WriteFile("config.json", []byte(configString), 0o666); err != nil {
70-
log.Fatalf("Writing config.json failed: %s", err.Error())
68+
log.Abortf("Could not write default ./config.json with permissions '0o666'. Application initialization failed, exited.\nError: %s\n", err.Error())
7169
}
7270

7371
if err := os.WriteFile(".env", []byte(envString), 0o666); err != nil {
74-
log.Fatalf("Writing .env failed: %s", err.Error())
72+
log.Abortf("Could not write default ./.env file with permissions '0o666'. Application initialization failed, exited.\nError: %s\n", err.Error())
7573
}
7674

7775
if err := os.Mkdir("var", 0o777); err != nil {
78-
log.Fatalf("Mkdir var failed: %s", err.Error())
76+
log.Abortf("Could not create default ./var folder with permissions '0o777'. Application initialization failed, exited.\nError: %s\n", err.Error())
7977
}
8078

8179
err := repository.MigrateDB("sqlite3", "./var/job.db")
8280
if err != nil {
83-
log.Fatalf("Initialize job.db failed: %s", err.Error())
81+
log.Abortf("Could not initialize default sqlite3 database as './var/job.db'. Application initialization failed, exited.\nError: %s\n", err.Error())
8482
}
8583
}

cmd/cc-backend/main.go

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,23 @@ func main() {
6161
// Apply config flags for pkg/log
6262
log.Init(flagLogLevel, flagLogDateTime)
6363

64+
// If init flag set, run tasks here before any file dependencies cause errors
65+
if flagInit {
66+
initEnv()
67+
log.Exit("Successfully setup environment!\n" +
68+
"Please review config.json and .env and adjust it to your needs.\n" +
69+
"Add your job-archive at ./var/job-archive.")
70+
}
71+
6472
// See https://github.com/google/gops (Runtime overhead is almost zero)
6573
if flagGops {
6674
if err := agent.Listen(agent.Options{}); err != nil {
67-
log.Fatalf("gops/agent.Listen failed: %s", err.Error())
75+
log.Abortf("Could not start gops agent with 'gops/agent.Listen(agent.Options{})'. Application startup failed, exited.\nError: %s\n", err.Error())
6876
}
6977
}
7078

7179
if err := runtimeEnv.LoadEnv("./.env"); err != nil && !os.IsNotExist(err) {
72-
log.Fatalf("parsing './.env' file failed: %s", err.Error())
80+
log.Abortf("Could not parse existing .env file at location './.env'. Application startup failed, exited.\nError: %s\n", err.Error())
7381
}
7482

7583
// Initialize sub-modules and handle command line flags.
@@ -87,119 +95,122 @@ func main() {
8795
if flagMigrateDB {
8896
err := repository.MigrateDB(config.Keys.DBDriver, config.Keys.DB)
8997
if err != nil {
90-
log.Fatal(err)
98+
log.Abortf("MigrateDB Failed: Could not migrate '%s' database at location '%s' to version %d.\nError: %s\n", config.Keys.DBDriver, config.Keys.DB, repository.Version, err.Error())
9199
}
92-
os.Exit(0)
100+
log.Exitf("MigrateDB Success: Migrated '%s' database at location '%s' to version %d.\n", config.Keys.DBDriver, config.Keys.DB, repository.Version)
93101
}
94102

95103
if flagRevertDB {
96104
err := repository.RevertDB(config.Keys.DBDriver, config.Keys.DB)
97105
if err != nil {
98-
log.Fatal(err)
106+
log.Abortf("RevertDB Failed: Could not revert '%s' database at location '%s' to version %d.\nError: %s\n", config.Keys.DBDriver, config.Keys.DB, (repository.Version - 1), err.Error())
99107
}
100-
os.Exit(0)
108+
log.Exitf("RevertDB Success: Reverted '%s' database at location '%s' to version %d.\n", config.Keys.DBDriver, config.Keys.DB, (repository.Version - 1))
101109
}
102110

103111
if flagForceDB {
104112
err := repository.ForceDB(config.Keys.DBDriver, config.Keys.DB)
105113
if err != nil {
106-
log.Fatal(err)
114+
log.Abortf("ForceDB Failed: Could not force '%s' database at location '%s' to version %d.\nError: %s\n", config.Keys.DBDriver, config.Keys.DB, repository.Version, err.Error())
107115
}
108-
os.Exit(0)
116+
log.Exitf("ForceDB Success: Forced '%s' database at location '%s' to version %d.\n", config.Keys.DBDriver, config.Keys.DB, repository.Version)
109117
}
110118

111119
repository.Connect(config.Keys.DBDriver, config.Keys.DB)
112120

113-
if flagInit {
114-
initEnv()
115-
fmt.Print("Successfully setup environment!\n")
116-
fmt.Print("Please review config.json and .env and adjust it to your needs.\n")
117-
fmt.Print("Add your job-archive at ./var/job-archive.\n")
118-
os.Exit(0)
119-
}
120-
121121
if !config.Keys.DisableAuthentication {
122122

123123
auth.Init()
124124

125125
if flagNewUser != "" {
126126
parts := strings.SplitN(flagNewUser, ":", 3)
127127
if len(parts) != 3 || len(parts[0]) == 0 {
128-
log.Fatal("invalid argument format for user creation")
128+
log.Abortf("Add User: Could not parse supplied argument format: No changes.\n"+
129+
"Want: <username>:[admin,support,manager,api,user]:<password>\n"+
130+
"Have: %s\n", flagNewUser)
129131
}
130132

131133
ur := repository.GetUserRepository()
132134
if err := ur.AddUser(&schema.User{
133135
Username: parts[0], Projects: make([]string, 0), Password: parts[2], Roles: strings.Split(parts[1], ","),
134136
}); err != nil {
135-
log.Fatalf("adding '%s' user authentication failed: %v", parts[0], err)
137+
log.Abortf("Add User: Could not add new user authentication for '%s' and roles '%s'.\nError: %s\n", parts[0], parts[1], err.Error())
138+
} else {
139+
log.Printf("Add User: Added new user '%s' with roles '%s'.\n", parts[0], parts[1])
136140
}
137141
}
142+
138143
if flagDelUser != "" {
139144
ur := repository.GetUserRepository()
140145
if err := ur.DelUser(flagDelUser); err != nil {
141-
log.Fatalf("deleting user failed: %v", err)
146+
log.Abortf("Delete User: Could not delete user '%s' from DB.\nError: %s\n", flagDelUser, err.Error())
147+
} else {
148+
log.Printf("Delete User: Deleted user '%s' from DB.\n", flagDelUser)
142149
}
143150
}
144151

145152
authHandle := auth.GetAuthInstance()
146153

147154
if flagSyncLDAP {
148155
if authHandle.LdapAuth == nil {
149-
log.Fatal("cannot sync: LDAP authentication is not configured")
156+
log.Abort("Sync LDAP: LDAP authentication is not configured, could not synchronize. No changes, exited.")
150157
}
151158

152159
if err := authHandle.LdapAuth.Sync(); err != nil {
153-
log.Fatalf("LDAP sync failed: %v", err)
160+
log.Abortf("Sync LDAP: Could not synchronize, failed with error.\nError: %s\n", err.Error())
154161
}
155-
log.Info("LDAP sync successfull")
162+
log.Print("Sync LDAP: LDAP synchronization successfull.")
156163
}
157164

158165
if flagGenJWT != "" {
159166
ur := repository.GetUserRepository()
160167
user, err := ur.GetUser(flagGenJWT)
161168
if err != nil {
162-
log.Fatalf("could not get user from JWT: %v", err)
169+
log.Abortf("JWT: Could not get supplied user '%s' from DB. No changes, exited.\nError: %s\n", flagGenJWT, err.Error())
163170
}
164171

165172
if !user.HasRole(schema.RoleApi) {
166-
log.Warnf("user '%s' does not have the API role", user.Username)
173+
log.Warnf("JWT: User '%s' does not have the role 'api'. REST API endpoints will return error!\n", user.Username)
167174
}
168175

169176
jwt, err := authHandle.JwtAuth.ProvideJWT(user)
170177
if err != nil {
171-
log.Fatalf("failed to provide JWT to user '%s': %v", user.Username, err)
178+
log.Abortf("JWT: User '%s' found in DB, but failed to provide JWT.\nError: %s\n", user.Username, err.Error())
172179
}
173180

174-
fmt.Printf("MAIN > JWT for '%s': %s\n", user.Username, jwt)
181+
log.Printf("JWT: Successfully generated JWT for user '%s': %s\n", user.Username, jwt)
175182
}
176183

177184
} else if flagNewUser != "" || flagDelUser != "" {
178-
log.Fatal("arguments --add-user and --del-user can only be used if authentication is enabled")
185+
log.Abort("Error: Arguments '--add-user' and '--del-user' can only be used if authentication is enabled. No changes, exited.")
179186
}
180187

181188
if err := archive.Init(config.Keys.Archive, config.Keys.DisableArchive); err != nil {
182-
log.Fatalf("failed to initialize archive: %s", err.Error())
189+
log.Abortf("Init: Failed to initialize archive.\nError: %s\n", err.Error())
183190
}
184191

185192
if err := metricdata.Init(); err != nil {
186-
log.Fatalf("failed to initialize metricdata repository: %s", err.Error())
193+
log.Abortf("Init: Failed to initialize metricdata repository.\nError %s\n", err.Error())
187194
}
188195

189196
if flagReinitDB {
190197
if err := importer.InitDB(); err != nil {
191-
log.Fatalf("failed to re-initialize repository DB: %s", err.Error())
198+
log.Abortf("Init DB: Failed to re-initialize repository DB.\nError: %s\n", err.Error())
199+
} else {
200+
log.Print("Init DB: Sucessfully re-initialized repository DB.")
192201
}
193202
}
194203

195204
if flagImportJob != "" {
196205
if err := importer.HandleImportFlag(flagImportJob); err != nil {
197-
log.Fatalf("job import failed: %s", err.Error())
206+
log.Abortf("Import Job: Job import failed.\nError: %s\n", err.Error())
207+
} else {
208+
log.Printf("Import Job: Imported Job '%s' into DB.\n", flagImportJob)
198209
}
199210
}
200211

201212
if !flagServer {
202-
return
213+
log.Exit("No errors, server flag not set. Exiting cc-backend.")
203214
}
204215

205216
archiver.Start(repository.GetJobRepository())

cmd/cc-backend/server.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func serverInit() {
6464
case string:
6565
return fmt.Errorf("MAIN > Panic: %s", e)
6666
case error:
67-
return fmt.Errorf("MAIN > Panic caused by: %w", e)
67+
return fmt.Errorf("MAIN > Panic caused by: %s", e.Error())
6868
}
6969

7070
return errors.New("MAIN > Internal server error (panic)")
@@ -268,7 +268,7 @@ func serverStart() {
268268
// Start http or https server
269269
listener, err := net.Listen("tcp", config.Keys.Addr)
270270
if err != nil {
271-
log.Fatalf("starting http listener failed: %v", err)
271+
log.Abortf("Server Start: Starting http listener on '%s' failed.\nError: %s\n", config.Keys.Addr, err.Error())
272272
}
273273

274274
if !strings.HasSuffix(config.Keys.Addr, ":80") && config.Keys.RedirectHttpTo != "" {
@@ -281,7 +281,7 @@ func serverStart() {
281281
cert, err := tls.LoadX509KeyPair(
282282
config.Keys.HttpsCertFile, config.Keys.HttpsKeyFile)
283283
if err != nil {
284-
log.Fatalf("loading X509 keypair failed: %v", err)
284+
log.Abortf("Server Start: Loading X509 keypair failed. Check options 'https-cert-file' and 'https-key-file' in 'config.json'.\nError: %s\n", err.Error())
285285
}
286286
listener = tls.NewListener(listener, &tls.Config{
287287
Certificates: []tls.Certificate{cert},
@@ -292,20 +292,20 @@ func serverStart() {
292292
MinVersion: tls.VersionTLS12,
293293
PreferServerCipherSuites: true,
294294
})
295-
fmt.Printf("HTTPS server listening at %s...", config.Keys.Addr)
295+
log.Printf("HTTPS server listening at %s...\n", config.Keys.Addr)
296296
} else {
297-
fmt.Printf("HTTP server listening at %s...", config.Keys.Addr)
297+
log.Printf("HTTP server listening at %s...\n", config.Keys.Addr)
298298
}
299299
//
300300
// Because this program will want to bind to a privileged port (like 80), the listener must
301301
// be established first, then the user can be changed, and after that,
302302
// the actual http server can be started.
303303
if err := runtimeEnv.DropPrivileges(config.Keys.Group, config.Keys.User); err != nil {
304-
log.Fatalf("error while preparing server start: %s", err.Error())
304+
log.Abortf("Server Start: Error while preparing server start.\nError: %s\n", err.Error())
305305
}
306306

307307
if err = server.Serve(listener); err != nil && err != http.ErrServerClosed {
308-
log.Fatalf("starting server failed: %v", err)
308+
log.Abortf("Server Start: Starting server failed.\nError: %s\n", err.Error())
309309
}
310310
}
311311

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module github.com/ClusterCockpit/cc-backend
22

33
go 1.23.5
4+
toolchain go1.24.1
45

56
require (
67
github.com/99designs/gqlgen v0.17.66
@@ -10,7 +11,7 @@ require (
1011
github.com/go-co-op/gocron/v2 v2.16.0
1112
github.com/go-ldap/ldap/v3 v3.4.10
1213
github.com/go-sql-driver/mysql v1.9.0
13-
github.com/golang-jwt/jwt/v5 v5.2.1
14+
github.com/golang-jwt/jwt/v5 v5.2.2
1415
github.com/golang-migrate/migrate/v4 v4.18.2
1516
github.com/google/gops v0.3.28
1617
github.com/gorilla/handlers v1.5.2
@@ -78,7 +79,7 @@ require (
7879
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
7980
go.uber.org/atomic v1.11.0 // indirect
8081
golang.org/x/mod v0.23.0 // indirect
81-
golang.org/x/net v0.35.0 // indirect
82+
golang.org/x/net v0.36.0 // indirect
8283
golang.org/x/sync v0.11.0 // indirect
8384
golang.org/x/sys v0.30.0 // indirect
8485
golang.org/x/text v0.22.0 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIx
8383
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
8484
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
8585
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
86-
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
87-
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
86+
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
87+
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
8888
github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8=
8989
github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk=
9090
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -279,8 +279,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
279279
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
280280
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
281281
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
282-
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
283-
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
282+
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
283+
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
284284
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
285285
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
286286
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

internal/api/rest.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,8 +1423,6 @@ func (api *RestApi) updateConfiguration(rw http.ResponseWriter, r *http.Request)
14231423
rw.Header().Set("Content-Type", "text/plain")
14241424
key, value := r.FormValue("key"), r.FormValue("value")
14251425

1426-
// fmt.Printf("REST > KEY: %#v\nVALUE: %#v\n", key, value)
1427-
14281426
if err := repository.GetUserCfgRepo().UpdateConfig(key, value, repository.GetUserFromContext(r.Context())); err != nil {
14291427
http.Error(rw, err.Error(), http.StatusUnprocessableEntity)
14301428
return

internal/config/config.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ package config
77
import (
88
"bytes"
99
"encoding/json"
10-
"log"
1110
"os"
1211

12+
"github.com/ClusterCockpit/cc-backend/pkg/log"
1313
"github.com/ClusterCockpit/cc-backend/pkg/schema"
1414
)
1515

@@ -53,20 +53,20 @@ func Init(flagConfigFile string) {
5353
raw, err := os.ReadFile(flagConfigFile)
5454
if err != nil {
5555
if !os.IsNotExist(err) {
56-
log.Fatalf("CONFIG ERROR: %v", err)
56+
log.Abortf("Config Init: Could not read config file '%s'.\nError: %s\n", flagConfigFile, err.Error())
5757
}
5858
} else {
5959
if err := schema.Validate(schema.Config, bytes.NewReader(raw)); err != nil {
60-
log.Fatalf("Validate config: %v\n", err)
60+
log.Abortf("Config Init: Could not validate config file '%s'.\nError: %s\n", flagConfigFile, err.Error())
6161
}
6262
dec := json.NewDecoder(bytes.NewReader(raw))
6363
dec.DisallowUnknownFields()
6464
if err := dec.Decode(&Keys); err != nil {
65-
log.Fatalf("could not decode: %v", err)
65+
log.Abortf("Config Init: Could not decode config file '%s'.\nError: %s\n", flagConfigFile, err.Error())
6666
}
6767

6868
if Keys.Clusters == nil || len(Keys.Clusters) < 1 {
69-
log.Fatal("At least one cluster required in config!")
69+
log.Abort("Config Init: At least one cluster required in config. Exited with error.")
7070
}
7171
}
7272
}

0 commit comments

Comments
 (0)