Skip to content

Commit 4e8f6ab

Browse files
committed
feat: add CRUD commands for bucket resources.
This change introduces full CRUD support for the Bucket resource in nctl.
1 parent 7e1139a commit 4e8f6ab

File tree

18 files changed

+3933
-4
lines changed

18 files changed

+3933
-4
lines changed

api/util/bucket.go

Lines changed: 980 additions & 0 deletions
Large diffs are not rendered by default.

api/util/bucket_test.go

Lines changed: 727 additions & 0 deletions
Large diffs are not rendered by default.

create/bucket.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package create
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/alecthomas/kong"
9+
meta "github.com/ninech/apis/meta/v1alpha1"
10+
storage "github.com/ninech/apis/storage/v1alpha1"
11+
"github.com/ninech/nctl/api"
12+
"github.com/ninech/nctl/api/util"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/apimachinery/pkg/watch"
15+
)
16+
17+
type bucketCmd struct {
18+
resourceCmd
19+
Location meta.LocationName `placeholder:"${bucket_location_default}" help:"Where the Bucket instance is created. Available locations are: ${bucket_location_options}" required:""`
20+
PublicRead bool `help:"Publicly readable objects." default:"false"`
21+
PublicList bool `help:"Publicly listable objects." default:"false"`
22+
Versioning bool `help:"Enable object versioning." default:"false"`
23+
// The `sep:";"` option disables Kong's default comma-splitting and instead
24+
// allows multiple ROLE=USER[,USER...] segments to be provided in a single flag,
25+
// separated by semicolons. Example:
26+
// --permissions=reader=alice,bob
27+
// --permissions=writer=john;reader=carol
28+
Permissions []string `sep:";" placeholder:"${bucket_permissions_example}" help:"Permissions configure user access to the objects in this Bucket (repeatable: ROLE=USER[,USER...];ROLE=USER[,USER...). Available roles are: ${bucket_role_options}"`
29+
LifecyclePolicy []string `placeholder:"${bucket_lifecycle_policy_example}" help:"LifecyclePolicies allows to define automatic expiry (deletion) of objects using certain rules (repeatable: pass this flag once per policy)."`
30+
CORS []string `sep:";" placeholder:"${bucket_cors_example}" help:"CORS settings for this bucket (repeatable: ORIGINS=ORIGIN[,ORIGIN...];HEADERS=HEADER[,HEADER...];MAX_AGE)."`
31+
CustomHostnames []string `placeholder:"${bucket_custom_hostnames_example}" help:"CustomHostnames are DNS entries under which the bucket should be accessible. This can be used to serve public objects via an own domain name. (repeatable: HOST[,HOST...])."`
32+
}
33+
34+
func (cmd *bucketCmd) Run(ctx context.Context, client *api.Client) error {
35+
bucket, err := cmd.newBucket(client.Project)
36+
if err != nil {
37+
return err
38+
}
39+
40+
c := newCreator(client, bucket, "bucket")
41+
ctx, cancel := context.WithTimeout(ctx, cmd.WaitTimeout)
42+
defer cancel()
43+
44+
if err := c.createResource(ctx); err != nil {
45+
return err
46+
}
47+
if !cmd.Wait {
48+
return nil
49+
}
50+
51+
return c.wait(ctx, waitStage{
52+
objectList: &storage.BucketList{},
53+
onResult: func(event watch.Event) (bool, error) {
54+
if b, ok := event.Object.(*storage.Bucket); ok {
55+
return isAvailable(b), nil
56+
}
57+
return false, nil
58+
},
59+
})
60+
}
61+
62+
func baseBucketParameters() storage.BucketParameters {
63+
return storage.BucketParameters{}
64+
}
65+
66+
func (cmd *bucketCmd) newBucket(project string) (*storage.Bucket, error) {
67+
b := &storage.Bucket{
68+
ObjectMeta: metav1.ObjectMeta{
69+
Name: getName(cmd.Name),
70+
Namespace: project,
71+
},
72+
Spec: storage.BucketSpec{
73+
ForProvider: baseBucketParameters(),
74+
},
75+
}
76+
b.Spec.ForProvider = storage.BucketParameters{
77+
Location: meta.LocationName(cmd.Location),
78+
PublicRead: cmd.PublicRead,
79+
PublicList: cmd.PublicList,
80+
Versioning: cmd.Versioning,
81+
}
82+
83+
permissions, changed, err := util.PatchPermissions(b.Spec.ForProvider.Permissions, cmd.Permissions, nil)
84+
if err != nil {
85+
return nil, fmt.Errorf("patching permissions error: %w", err)
86+
}
87+
if changed {
88+
b.Spec.ForProvider.Permissions = permissions
89+
}
90+
91+
lifecyclePolicies, changed, err := util.PatchLifecyclePolicies(
92+
b.Spec.ForProvider.LifecyclePolicies,
93+
false,
94+
cmd.LifecyclePolicy,
95+
nil,
96+
)
97+
if err != nil {
98+
return nil, fmt.Errorf("patching lifecycle policies error: %w", err)
99+
}
100+
if changed {
101+
b.Spec.ForProvider.LifecyclePolicies = lifecyclePolicies
102+
}
103+
104+
cors, changed, err := util.PatchCORS(b.Spec.ForProvider.CORS, cmd.CORS, nil)
105+
if err != nil {
106+
return nil, fmt.Errorf("patching cors error: %w", err)
107+
}
108+
if changed {
109+
b.Spec.ForProvider.CORS = cors
110+
}
111+
112+
customHostNames, changed, err := util.PatchCustomHostnames(
113+
b.Spec.ForProvider.CustomHostnames,
114+
false,
115+
cmd.CustomHostnames,
116+
nil,
117+
)
118+
if err != nil {
119+
return nil, fmt.Errorf("patching custom hostnames error: %w", err)
120+
}
121+
if changed {
122+
b.Spec.ForProvider.CustomHostnames = customHostNames
123+
}
124+
125+
return b, nil
126+
}
127+
128+
func BucketKongVars() kong.Vars {
129+
result := make(kong.Vars)
130+
// TODO: get from api default location. For now uset BucketUser location:
131+
result["bucket_location_default"] = string(storage.BucketUserLocationDefault)
132+
result["bucket_location_options"] = strings.Join(storage.BucketLocationOptions, ", ")
133+
134+
roles := []storage.BucketRole{storage.BucketRoleReader, storage.BucketRoleWriter}
135+
roleStrings := make([]string, len(roles))
136+
for i, r := range roles {
137+
roleStrings[i] = string(r)
138+
}
139+
result["bucket_role_options"] = strings.Join(roleStrings, ", ")
140+
result["bucket_permissions_example"] = fmt.Sprintf("%s=frontend,analytics;%s=ingest", storage.BucketRoleReader, storage.BucketRoleWriter)
141+
result["bucket_lifecycle_policy_example"] = "prefix=p/;expire-after-days=7;is-live=true"
142+
result["bucket_cors_example"] = "origins=https://a.com,https://b.com;response-headers=X-My-Header,ETag;max-age=3600"
143+
result["bucket_custom_hostnames_example"] = "my-bucket.example.com,your-bucket.example.com"
144+
return result
145+
}

0 commit comments

Comments
 (0)