Skip to content

Commit 5947a2c

Browse files
authored
Merge pull request #63 from thalassa-cloud/projects-api
feat(projects): add project commands and update context handling (beta)
2 parents 1121a8e + 3641087 commit 5947a2c

179 files changed

Lines changed: 1766 additions & 1220 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/e2e-tests.yaml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ on:
44
push:
55
branches:
66
- main
7+
pull_request:
8+
branches:
9+
- main
10+
workflow_dispatch:
711

812
env:
913
TCLOUD_E2E_ORGANISATION: "o-d5obcgg3fspc73f7vqpg"
@@ -13,6 +17,7 @@ env:
1317
jobs:
1418
e2e-tests:
1519
runs-on: ubuntu-latest
20+
environment: e2e-tests
1621
permissions:
1722
id-token: write
1823
contents: read

‎.github/workflows/prs.yaml‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,26 @@ jobs:
3131
args: build --clean --snapshot
3232
env:
3333
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34-
review:
34+
lint:
3535
runs-on: ubuntu-latest
36+
permissions:
37+
contents: read
38+
pull-requests: write
3639
steps:
37-
- uses: reviewdog/action-setup@e04ffabe3898a0af8d0fb1af00c188831c4b5893 # v1.3.2
40+
- name: Check out code into the Go module directory
41+
uses: actions/checkout@v4
42+
with:
43+
fetch-depth: 0
44+
- name: golangci-lint
45+
uses: reviewdog/action-golangci-lint@v2
3846
with:
39-
reviewdog_version: latest
47+
github_token: ${{ secrets.GITHUB_TOKEN }}
48+
golangci_lint_version: v2.12.2
49+
go_version_file: go.mod
50+
reporter: ${{ github.event_name == 'pull_request' && 'github-pr-review' || 'github-check' }}
51+
filter_mode: added
52+
fail_level: error
53+
golangci_lint_flags: >-
54+
--new-from-rev=${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
55+
./...
4056

‎.golangci.yml‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
version: "2"
2+
run:
3+
allow-parallel-runners: true
4+
linters:
5+
default: none
6+
enable:
7+
- copyloopvar
8+
- dupl
9+
- errcheck
10+
- ginkgolinter
11+
- goconst
12+
- gocyclo
13+
- govet
14+
- ineffassign
15+
- lll
16+
- misspell
17+
- nakedret
18+
- prealloc
19+
- revive
20+
- staticcheck
21+
- unconvert
22+
- unparam
23+
- unused
24+
settings:
25+
goconst:
26+
# Test fixtures repeat literals intentionally; production-only checks stay useful.
27+
ignore-tests: true
28+
# K8s condition type names and similar domain strings often repeat only a few times.
29+
min-occurrences: 8
30+
lll:
31+
# Max line length, lines longer will be reported.
32+
# '\t' is counted as 1 character by default, and can be changed with the tab-width option.
33+
# Default: 120.
34+
line-length: 240
35+
# Tab width in spaces.
36+
# Default: 1
37+
tab-width: 1
38+
revive:
39+
rules:
40+
- name: comment-spacings
41+
exclusions:
42+
generated: lax
43+
rules:
44+
- linters:
45+
- lll
46+
path: api/*
47+
- linters:
48+
- dupl
49+
- lll
50+
path: internal/*
51+
# Reconcile helpers often return (ctrl.Result, error) for a uniform call site while
52+
# encoding retries in Result and leaving error nil (controller-runtime does the same).
53+
- linters:
54+
- unparam
55+
text: is always nil
56+
paths:
57+
- third_party$
58+
- builtin$
59+
- examples$
60+
- api/v1/zz_generated.deepcopy.go
61+
formatters:
62+
enable:
63+
- gofmt
64+
- goimports
65+
exclusions:
66+
generated: lax
67+
paths:
68+
- third_party$
69+
- builtin$
70+
- examples$

‎cmd/api/raw.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Requires client-go with RawRequest support.`,
5858
if err != nil {
5959
return fmt.Errorf("request failed: %w", err)
6060
}
61-
defer resp.RawResponse.Body.Close()
61+
defer func() { _ = resp.RawResponse.Body.Close() }()
6262

6363
if rawShowHeaders {
6464
for k, v := range resp.RawResponse.Header {

‎cmd/audit/export.go‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ func splitTimeRange(start, end time.Time, splitPeriod string) [][]time.Time {
147147
}
148148

149149
// exportTimeRange exports audit logs for a specific time range
150-
func exportTimeRange(ctx context.Context, client thalassa.Client, start, end time.Time, filter *audit.AuditLogFilter, chunkIndex int, totalChunks int, outputFile string, writeToStdout bool) ([]audit.AuditLog, error) {
150+
func exportTimeRange(ctx context.Context, client thalassa.Client, start, end time.Time, filter *audit.AuditLogFilter, chunkIndex int, totalChunks int, writeToStdout bool) ([]audit.AuditLog, error) {
151151
if !writeToStdout && totalChunks > 1 {
152152
fmt.Printf("Exporting chunk %d/%d: %s to %s...\n", chunkIndex+1, totalChunks, start.Format(time.RFC3339), end.Format(time.RFC3339))
153153
}
@@ -198,7 +198,7 @@ func exportTimeRange(ctx context.Context, client thalassa.Client, start, end tim
198198
}
199199

200200
// writeExport writes the export data to a file
201-
func writeExport(exportData map[string]interface{}, outputPath string, writeToStdout bool) error {
201+
func writeExport(exportData map[string]any, outputPath string, writeToStdout bool) error {
202202
var file *os.File
203203
var err error
204204

@@ -217,7 +217,7 @@ func writeExport(exportData map[string]interface{}, outputPath string, writeToSt
217217
if err != nil {
218218
return fmt.Errorf("failed to create output file: %w", err)
219219
}
220-
defer file.Close()
220+
defer func() { _ = file.Close() }()
221221
}
222222

223223
encoder := json.NewEncoder(file)
@@ -386,7 +386,7 @@ Examples:
386386
chunkCtx, cancel := context.WithTimeout(cmd.Context(), timeoutDuration)
387387

388388
// Fetch logs for this chunk
389-
chunkLogs, err := exportTimeRange(chunkCtx, client, chunkStart, chunkEnd, filter, i, len(chunks), outputFile, writeToStdout)
389+
chunkLogs, err := exportTimeRange(chunkCtx, client, chunkStart, chunkEnd, filter, i, len(chunks), writeToStdout)
390390

391391
// Always cancel the context when done with this chunk
392392
cancel()
@@ -435,9 +435,9 @@ Examples:
435435
}
436436

437437
// Prepare export data with metadata
438-
exportData := map[string]interface{}{
438+
exportData := map[string]any{
439439
"exportedAt": time.Now().Format(time.RFC3339),
440-
"timeRange": map[string]interface{}{
440+
"timeRange": map[string]any{
441441
"type": rangeType,
442442
"start": chunkStart.Format(time.RFC3339),
443443
"end": chunkEnd.Format(time.RFC3339),
@@ -447,26 +447,26 @@ Examples:
447447
}
448448

449449
if len(chunks) > 1 {
450-
exportData["chunk"] = map[string]interface{}{
450+
exportData["chunk"] = map[string]any{
451451
"index": i + 1,
452452
"total": len(chunks),
453453
}
454454
}
455455

456456
// Add custom range dates if applicable
457457
if rangeType == "custom" {
458-
exportData["timeRange"].(map[string]interface{})["startDate"] = startDate
459-
exportData["timeRange"].(map[string]interface{})["endDate"] = endDate
458+
exportData["timeRange"].(map[string]any)["startDate"] = startDate
459+
exportData["timeRange"].(map[string]any)["endDate"] = endDate
460460
}
461461

462462
// Add duration if using --since
463463
if rangeType == "duration" {
464-
exportData["timeRange"].(map[string]interface{})["since"] = sinceDuration
464+
exportData["timeRange"].(map[string]any)["since"] = sinceDuration
465465
}
466466

467467
// Add filter metadata if filters were applied
468468
if hasFilters {
469-
filterMetadata := map[string]interface{}{}
469+
filterMetadata := map[string]any{}
470470
if searchText != "" {
471471
filterMetadata["searchText"] = searchText
472472
}

‎cmd/cmd.go‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/thalassa-cloud/cli/cmd/me"
2020
"github.com/thalassa-cloud/cli/cmd/objectstorage"
2121
"github.com/thalassa-cloud/cli/cmd/oidc"
22+
"github.com/thalassa-cloud/cli/cmd/projects"
2223
"github.com/thalassa-cloud/cli/cmd/quotas"
2324
"github.com/thalassa-cloud/cli/cmd/registry"
2425
"github.com/thalassa-cloud/cli/cmd/version"
@@ -47,6 +48,7 @@ func handleExecutionError(err error) {
4748

4849
func init() {
4950
RootCmd.PersistentFlags().StringVarP(&contextstate.OrganisationFlag, "organisation", "O", "", "Organisation slug or identity (overrides context)")
51+
RootCmd.PersistentFlags().StringVarP(&contextstate.ProjectFlag, "project", "P", "", "Project identity (overrides context; slug is resolved to identity; use \"root\" for organisation scope)")
5052
RootCmd.PersistentFlags().StringVarP(&contextstate.ContextFlag, "context", "c", "", "Context name")
5153
RootCmd.PersistentFlags().StringVar(&contextstate.EndpointFlag, "api", "", "API endpoint (overrides context)")
5254
RootCmd.PersistentFlags().StringVar(&contextstate.AccessTokenFlag, "access-token", "", "Access Token authentication (overrides context)")
@@ -56,7 +58,8 @@ func init() {
5658
RootCmd.PersistentFlags().BoolVar(&contextstate.DebugFlag, "debug", false, "Debug mode")
5759

5860
// Register completions
59-
RootCmd.RegisterFlagCompletionFunc("organisation", completion.CompleteOrganisation)
61+
_ = RootCmd.RegisterFlagCompletionFunc("organisation", completion.CompleteOrganisation)
62+
_ = RootCmd.RegisterFlagCompletionFunc("project", completion.CompleteProject)
6063

6164
RootCmd.AddCommand(api.ApiCmd)
6265
RootCmd.AddCommand(context.ContextCmd)
@@ -73,6 +76,7 @@ func init() {
7376
RootCmd.AddCommand(dbaas.DbaasCmd)
7477
RootCmd.AddCommand(me.MeCmd)
7578
RootCmd.AddCommand(iam.IamCmd)
79+
RootCmd.AddCommand(projects.ProjectsCmd)
7680
RootCmd.AddCommand(audit.AuditCmd)
7781
RootCmd.AddCommand(registry.RegistryCmd)
7882
RootCmd.AddCommand(oidc.OidcCmd)

‎cmd/context/context.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
var ContextCmd = &cobra.Command{
99
Use: "context",
1010
Short: "Manage context",
11-
Long: "Manage context for the CLI. Contexts are used to manage multiple organisations and APIs.",
11+
Long: "Manage context for the CLI. Contexts are used to manage multiple organisations, projects, and APIs.",
1212
}
1313

1414
func init() {

‎cmd/context/create.go‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func createNewContext() error {
111111
return err
112112
}
113113

114-
defaultContext := newDefaultContext(contextName, contextstate.OrganisationFlag, u.Host)
114+
defaultContext := newDefaultContext(contextName, contextstate.OrganisationFlag, contextstate.ProjectFlag, u.Host)
115115
err = contextstate.GlobalConfigManager().AddOrMergeContext(defaultContext)
116116
if err != nil {
117117
return err
@@ -130,10 +130,11 @@ func init() {
130130
createCmd.Flags().StringVar(&contextName, "name", "default", "name of the context")
131131
}
132132

133-
func newDefaultContext(contextName, organisation, apiName string) contextstate.Context {
133+
func newDefaultContext(contextName, organisation, project, apiName string) contextstate.Context {
134134
return contextstate.Context{
135135
Name: contextName,
136136
Organisation: organisation,
137+
Project: project,
137138
Servers: contextstate.Servers{
138139
Name: apiName,
139140
API: contextstate.API{

‎cmd/context/list.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ var listContextCmd = &cobra.Command{
3838
if showCurrent && name == currentContextName {
3939
name = fmt.Sprintf("* %s", name)
4040
}
41-
body = append(body, []string{name, c.Context.Organisation, c.Context.User, c.Context.API})
41+
body = append(body, []string{name, c.Context.Organisation, c.Context.Project, c.Context.User, c.Context.API})
4242
}
4343

4444
if noHeader {
4545
table.Print(nil, body)
4646
} else {
47-
table.Print([]string{"Name", "Organisation", "User", "Endpoint"}, body)
47+
table.Print([]string{"Name", "Organisation", "Project", "User", "Endpoint"}, body)
4848
}
4949
return nil
5050
},

‎cmd/context/login.go‎

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ package context
22

33
import (
44
"errors"
5-
"fmt"
65
"strings"
76

87
"github.com/spf13/cobra"
98

109
"github.com/thalassa-cloud/cli/internal/config/contextstate"
11-
"github.com/thalassa-cloud/client-go/pkg/client"
1210
)
1311

1412
// createCmd represents the create command
@@ -29,24 +27,6 @@ var loginCmd = &cobra.Command{
2927
oidcClientID := contextstate.ClientIdOrFlag()
3028
oidcClientSecret := contextstate.ClientSecretOrFlag()
3129

32-
tokenURL := fmt.Sprintf("%s/oidc/token", apiURL)
33-
34-
opts := []client.Option{}
35-
if oidcClientID != "" && oidcClientSecret != "" {
36-
opts = append(opts, client.WithAuthOIDC(oidcClientID, oidcClientSecret, tokenURL))
37-
} else if accessToken != "" {
38-
opts = append(opts, client.WithToken(accessToken))
39-
} else if token != "" {
40-
opts = append(opts, client.WithAuthPersonalToken(token))
41-
}
42-
if len(opts) == 0 {
43-
return errors.New("no authentication method provided")
44-
}
45-
opts = append(opts, client.WithBaseURL(apiURL))
46-
if contextstate.Organisation() != "" {
47-
opts = append(opts, client.WithOrganisation(contextstate.Organisation()))
48-
}
49-
5030
if oidcClientID != "" && oidcClientSecret != "" {
5131
return contextstate.LoginWithAPIEndpointOidc(cmd.Context(), oidcClientID, oidcClientSecret, apiURL)
5232
}
@@ -56,6 +36,9 @@ var loginCmd = &cobra.Command{
5636
}
5737
return contextstate.LoginWithAccessToken(cmd.Context(), accessToken, apiURL)
5838
}
39+
if token == "" {
40+
return errors.New("no authentication method provided")
41+
}
5942
return contextstate.LoginWithAPIEndpoint(cmd.Context(), token, apiURL)
6043
},
6144
}

0 commit comments

Comments
 (0)