Skip to content

Commit 7649027

Browse files
author
Davor Gajic
committed
add bucketuser crud
1 parent 7faac97 commit 7649027

File tree

15 files changed

+615
-48
lines changed

15 files changed

+615
-48
lines changed

create/bucketuser.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package create
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/apimachinery/pkg/watch"
10+
11+
"github.com/alecthomas/kong"
12+
runtimev1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
13+
meta "github.com/ninech/apis/meta/v1alpha1"
14+
storage "github.com/ninech/apis/storage/v1alpha1"
15+
16+
"github.com/ninech/nctl/api"
17+
)
18+
19+
type bucketUserCmd struct {
20+
resourceCmd
21+
Location string `placeholder:"${bucketuser_location_default}" help:"Location where the BucketUser instance is created. Available locations are: ${bucketuser_location_options}"`
22+
}
23+
24+
func (cmd *bucketUserCmd) Run(ctx context.Context, client *api.Client) error {
25+
fmt.Println("Creating new bucketuser.")
26+
bucketuser := cmd.newBucketUser(client.Project)
27+
28+
c := newCreator(client, bucketuser, "bucketuser")
29+
ctx, cancel := context.WithTimeout(ctx, cmd.WaitTimeout)
30+
defer cancel()
31+
32+
if err := c.createResource(ctx); err != nil {
33+
return err
34+
}
35+
36+
if !cmd.Wait {
37+
return nil
38+
}
39+
40+
return c.wait(ctx, waitStage{
41+
objectList: &storage.BucketUserList{},
42+
onResult: func(event watch.Event) (bool, error) {
43+
if c, ok := event.Object.(*storage.BucketUser); ok {
44+
return isAvailable(c), nil
45+
}
46+
return false, nil
47+
},
48+
})
49+
}
50+
51+
func (cmd *bucketUserCmd) newBucketUser(namespace string) *storage.BucketUser {
52+
name := getName(cmd.Name)
53+
54+
bucketUser := &storage.BucketUser{
55+
ObjectMeta: metav1.ObjectMeta{
56+
Name: name,
57+
Namespace: namespace,
58+
},
59+
Spec: storage.BucketUserSpec{
60+
ResourceSpec: runtimev1.ResourceSpec{
61+
WriteConnectionSecretToReference: &runtimev1.SecretReference{
62+
Name: "bucketuser-" + name,
63+
Namespace: namespace,
64+
},
65+
},
66+
ForProvider: storage.BucketUserParameters{
67+
Location: meta.LocationName(cmd.Location),
68+
},
69+
},
70+
}
71+
return bucketUser
72+
}
73+
74+
func BucketUserKongVars() kong.Vars {
75+
result := make(kong.Vars)
76+
result["bucketuser_location_default"] = string(storage.BucketUserLocationDefault)
77+
result["bucketuser_location_options"] = strings.Join(storage.BucketUserLocationOptions, ", ")
78+
return result
79+
}

create/bucketuser_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package create
2+
3+
import (
4+
"testing"
5+
6+
storage "github.com/ninech/apis/storage/v1alpha1"
7+
"github.com/ninech/nctl/api"
8+
"github.com/ninech/nctl/internal/test"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestBucketUser(t *testing.T) {
14+
apiClient, err := test.SetupClient()
15+
require.NoError(t, err)
16+
17+
for name, tc := range map[string]struct {
18+
cmd bucketUserCmd
19+
checkBucketUser func(t *testing.T, cmd bucketUserCmd, bu *storage.BucketUser)
20+
}{
21+
"create": {
22+
cmd: bucketUserCmd{
23+
resourceCmd: resourceCmd{Name: "create"},
24+
Location: "nine-es34",
25+
},
26+
checkBucketUser: func(t *testing.T, cmd bucketUserCmd, bu *storage.BucketUser) {
27+
assert.Equal(t, "nine-es34", string(bu.Spec.ForProvider.Location))
28+
},
29+
},
30+
} {
31+
t.Run(name, func(t *testing.T) {
32+
if err != nil {
33+
t.Fatal(err)
34+
}
35+
if err := tc.cmd.Run(t.Context(), apiClient); err != nil {
36+
t.Fatal(err)
37+
}
38+
created := &storage.BucketUser{}
39+
if err := apiClient.Get(t.Context(), api.NamespacedName(tc.cmd.Name, apiClient.Project), created); err != nil {
40+
t.Fatal(err)
41+
}
42+
if tc.checkBucketUser != nil {
43+
tc.checkBucketUser(t, tc.cmd, created)
44+
}
45+
})
46+
}
47+
}

create/create.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type Cmd struct {
3535
OpenSearch openSearchCmd `cmd:"" group:"storage.nine.ch" name:"opensearch" aliases:"os" help:"Create a new OpenSearch cluster."`
3636
CloudVirtualMachine cloudVMCmd `cmd:"" group:"infrastructure.nine.ch" name:"cloudvirtualmachine" aliases:"cloudvm" help:"Create a new CloudVM."`
3737
ServiceConnection serviceConnectionCmd `cmd:"" group:"networking.nine.ch" name:"serviceconnection" aliases:"sc" help:"Create a new ServiceConnection."`
38+
BucketUser bucketUserCmd `cmd:"" group:"storage.nine.ch" name:"bucketuser" aliases:"bu" help:"Create a new BucketUser."`
3839
}
3940

4041
type resourceCmd struct {

delete/bucketuser.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package delete
2+
3+
import (
4+
"context"
5+
6+
storage "github.com/ninech/apis/storage/v1alpha1"
7+
"github.com/ninech/nctl/api"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
)
10+
11+
type bucketUserCmd struct {
12+
resourceCmd
13+
}
14+
15+
func (cmd *bucketUserCmd) Run(ctx context.Context, client *api.Client) error {
16+
ctx, cancel := context.WithTimeout(ctx, cmd.WaitTimeout)
17+
defer cancel()
18+
19+
bu := &storage.BucketUser{ObjectMeta: metav1.ObjectMeta{Name: cmd.Name, Namespace: client.Project}}
20+
return newDeleter(bu, storage.BucketUserKind).deleteResource(ctx, client, cmd.WaitTimeout, cmd.Wait, cmd.Force)
21+
}

delete/bucketuser_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package delete
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
meta "github.com/ninech/apis/meta/v1alpha1"
9+
storage "github.com/ninech/apis/storage/v1alpha1"
10+
"github.com/ninech/nctl/api"
11+
"github.com/ninech/nctl/internal/test"
12+
"github.com/stretchr/testify/require"
13+
"k8s.io/apimachinery/pkg/api/errors"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
)
16+
17+
func TestBucketUser(t *testing.T) {
18+
ctx := context.Background()
19+
cmd := bucketUserCmd{
20+
resourceCmd: resourceCmd{
21+
Name: "test",
22+
Force: true,
23+
Wait: false,
24+
WaitTimeout: time.Second,
25+
},
26+
}
27+
28+
bu := bucketUser("test", test.DefaultProject, "nine-es34")
29+
apiClient, err := test.SetupClient(test.WithObjects(bu))
30+
require.NoError(t, err)
31+
32+
if err := apiClient.Get(ctx, api.ObjectName(bu), bu); err != nil {
33+
t.Fatalf("expected bucketuser to exist, got: %s", err)
34+
}
35+
if err := cmd.Run(ctx, apiClient); err != nil {
36+
t.Fatal(err)
37+
}
38+
err = apiClient.Get(ctx, api.ObjectName(bu), bu)
39+
if err == nil {
40+
t.Fatalf("expected bucketuser to be deleted, but exists")
41+
}
42+
if !errors.IsNotFound(err) {
43+
t.Fatalf("expected bucketuser to be deleted, got: %s", err.Error())
44+
}
45+
}
46+
47+
func bucketUser(name, project, location string) *storage.BucketUser {
48+
return &storage.BucketUser{
49+
ObjectMeta: metav1.ObjectMeta{
50+
Name: name,
51+
Namespace: project,
52+
},
53+
Spec: storage.BucketUserSpec{
54+
ForProvider: storage.BucketUserParameters{
55+
Location: meta.LocationName(location),
56+
},
57+
},
58+
}
59+
}

delete/delete.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type Cmd struct {
2727
OpenSearch openSearchCmd `cmd:"" group:"storage.nine.ch" name:"opensearch" aliases:"os" help:"Delete an OpenSearch cluster."`
2828
CloudVirtualMachine cloudVMCmd `cmd:"" group:"infrastructure.nine.ch" name:"cloudvirtualmachine" aliases:"cloudvm" help:"Delete a CloudVM."`
2929
ServiceConnection serviceConnectionCmd `cmd:"" group:"networking.nine.ch" name:"serviceconnection" aliases:"sc" help:"Delete a ServiceConnection."`
30+
BucketUser bucketUserCmd `cmd:"" group:"storage.nine.ch" name:"bucketuser" aliases:"bu" help:"Update a BucketUser."`
3031
}
3132

3233
type resourceCmd struct {

get/apiserviceaccount.go

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,12 @@ package get
22

33
import (
44
"context"
5-
"encoding/json"
65
"fmt"
7-
"maps"
8-
"slices"
96

107
iam "github.com/ninech/apis/iam/v1alpha1"
118
"github.com/ninech/nctl/api"
129
"github.com/ninech/nctl/internal/format"
1310
"sigs.k8s.io/controller-runtime/pkg/client"
14-
"sigs.k8s.io/yaml"
1511
)
1612

1713
type apiServiceAccountsCmd struct {
@@ -47,7 +43,7 @@ func (asa *apiServiceAccountsCmd) print(ctx context.Context, client *api.Client,
4743
return err
4844
}
4945
if asa.PrintCredentials {
50-
return asa.printCredentials(ctx, client, sa, out)
46+
return asa.printCredentials(ctx, client, sa, out, func(key string) bool { return key == iam.APIServiceAccountKubeconfigKey })
5147
}
5248
key := ""
5349
switch sa.Spec.ForProvider.Version {
@@ -134,43 +130,3 @@ func (asa *apiServiceAccountsCmd) printSecret(ctx context.Context, client *api.C
134130
fmt.Printf("%s\n", data)
135131
return nil
136132
}
137-
138-
func (asa *apiServiceAccountsCmd) printCredentials(ctx context.Context, client *api.Client, sa *iam.APIServiceAccount, out *output) error {
139-
data, err := getConnectionSecretMap(ctx, client, sa)
140-
if err != nil {
141-
return err
142-
}
143-
stringData := map[string]string{}
144-
for k, v := range data {
145-
// skip kubeconfig as it's multiline and it does not format nicely. We
146-
// have a separate flag to print it.
147-
if k == iam.APIServiceAccountKubeconfigKey {
148-
continue
149-
}
150-
stringData[k] = string(v)
151-
}
152-
153-
switch out.Format {
154-
case full:
155-
out.writeTabRow("KEY", "VALUE")
156-
fallthrough
157-
case noHeader:
158-
for _, key := range slices.Sorted(maps.Keys(stringData)) {
159-
out.writeTabRow(key, stringData[key])
160-
}
161-
return out.tabWriter.Flush()
162-
case yamlOut:
163-
b, err := yaml.Marshal(stringData)
164-
if err != nil {
165-
return err
166-
}
167-
fmt.Print(string(b))
168-
case jsonOut:
169-
b, err := json.MarshalIndent(stringData, "", " ")
170-
if err != nil {
171-
return err
172-
}
173-
fmt.Println(string(b))
174-
}
175-
return nil
176-
}

get/bucketuser.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package get
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
storage "github.com/ninech/apis/storage/v1alpha1"
8+
"github.com/ninech/nctl/api"
9+
"github.com/ninech/nctl/internal/format"
10+
"sigs.k8s.io/controller-runtime/pkg/client"
11+
)
12+
13+
type bucketUserCmd struct {
14+
resourceCmd
15+
PrintCredentials bool `help:"Print the credentials of the BucketUser. Requires name to be set." xor:"cred"`
16+
PrintAccessKey bool `help:"Print the access key of the BucketUser. Requires name to be set." xor:"access"`
17+
PrintSecretKey bool `help:"Print the secret key of the BucketUser. Requires name to be set." xor:"secret"`
18+
}
19+
20+
func (bu *bucketUserCmd) Run(ctx context.Context, client *api.Client, get *Cmd) error {
21+
return get.listPrint(ctx, client, bu, api.MatchName(bu.Name))
22+
}
23+
24+
func (bu *bucketUserCmd) list() client.ObjectList {
25+
return &storage.BucketUserList{}
26+
}
27+
28+
func (bu *bucketUserCmd) print(ctx context.Context, client *api.Client, list client.ObjectList, out *output) error {
29+
bucketUserList := list.(*storage.BucketUserList)
30+
31+
if len(bucketUserList.Items) == 0 {
32+
return out.printEmptyMessage(storage.BucketUserKind, client.Project)
33+
}
34+
35+
user := &bucketUserList.Items[0]
36+
37+
if bu.printFlagSet() {
38+
if bu.Name != "" {
39+
return fmt.Errorf("name needs to be set to print bucket user information")
40+
}
41+
42+
if bu.PrintCredentials {
43+
return bu.printCredentials(ctx, client, user, out, nil)
44+
}
45+
46+
key := ""
47+
if bu.PrintAccessKey {
48+
key = storage.BucketUserCredentialAccessKey
49+
}
50+
51+
if bu.PrintSecretKey {
52+
key = storage.BucketUserCredentialSecretKey
53+
}
54+
return bu.printSecret(ctx, client, user, key)
55+
}
56+
57+
switch out.Format {
58+
case full:
59+
return bu.printBucketUserInstances(bucketUserList.Items, out, true)
60+
case noHeader:
61+
return bu.printBucketUserInstances(bucketUserList.Items, out, false)
62+
case yamlOut:
63+
return format.PrettyPrintObjects(bucketUserList.GetItems(), format.PrintOpts{Out: out.writer})
64+
case jsonOut:
65+
return format.PrettyPrintObjects(
66+
bucketUserList.GetItems(),
67+
format.PrintOpts{
68+
Out: out.writer,
69+
Format: format.OutputFormatTypeJSON,
70+
JSONOpts: format.JSONOutputOptions{
71+
PrintSingleItem: bu.Name != "",
72+
},
73+
})
74+
}
75+
76+
return nil
77+
}
78+
79+
func (bu *bucketUserCmd) printBucketUserInstances(list []storage.BucketUser, out *output, header bool) error {
80+
if header {
81+
out.writeHeader("NAME", "LOCATION")
82+
}
83+
84+
for _, bu := range list {
85+
out.writeTabRow(bu.Namespace, bu.Name, string(bu.Spec.ForProvider.Location))
86+
}
87+
88+
return out.tabWriter.Flush()
89+
}
90+
91+
func (bu *bucketUserCmd) printFlagSet() bool {
92+
return bu.PrintCredentials || bu.PrintAccessKey || bu.PrintSecretKey
93+
}
94+
95+
func (bu *bucketUserCmd) printSecret(ctx context.Context, client *api.Client, user *storage.BucketUser, key string) error {
96+
data, err := getConnectionSecret(ctx, client, key, user)
97+
if err != nil {
98+
return err
99+
}
100+
fmt.Printf("%s\n", data)
101+
return nil
102+
}

0 commit comments

Comments
 (0)