Skip to content

Commit dbe43d4

Browse files
committed
add initial sidecar Bucket creation reconciliation
Add initial sidecar implementation for dynamic Bucket provisioning. The implementation is consistent with v1alpha2 pre-approval designs. Translating driver-returned gRPC protocol information into user-facing env var format is partially implemented. This is a core part of what COSI needs to get right internally. Scaffolding is set in place, and implementation for S3 translation is implemented. Azure and GCS translations are left as to-do items. Signed-off-by: Blaine Gardner <[email protected]>
1 parent d39731f commit dbe43d4

File tree

20 files changed

+3511
-511
lines changed

20 files changed

+3511
-511
lines changed

internal/predicate/predicate.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ func AnyGeneric() predicate.Funcs {
6666
// where the generation changes. For most resources, a generation change means that the resource
6767
// `spec` has changed, ignoring metadata and status changes.
6868
//
69-
// The predicate does not enqueue requests for any Create/Update/Delete events.
69+
// The predicate does not enqueue requests for any Create/Delete/Generic events.
7070
// This ensures that other predicates can effectively filter out undesired non-Update events.
7171
//
7272
// This is a modified implementation of controller-runtime's GenerationChangedPredicate{} which
73-
// does enqueue requests for all Create/Update/Delete events -- behavior COSI does not always want.
73+
// does enqueue requests for all Create/Delete/Generic events -- behavior COSI does not always want.
7474
func GenerationChangedInUpdateOnly() predicate.Funcs {
7575
funcs := allFalseFuncs()
7676
funcs.UpdateFunc = func(e event.UpdateEvent) bool {
@@ -83,7 +83,7 @@ func GenerationChangedInUpdateOnly() predicate.Funcs {
8383
// where the protection finalizer has been removed. This helps ensure that COSI always has a chance
8484
// to re-apply the protection finalizer when it's needed.
8585
//
86-
// The predicate does not enqueue requests for any Create/Update/Delete events.
86+
// The predicate does not enqueue requests for any Create/Delete/Generic events.
8787
// This ensures that other predicates can effectively filter out undesired non-Update events.
8888
func ProtectionFinalizerRemoved(s *runtime.Scheme) predicate.Funcs {
8989
funcs := allFalseFuncs()

internal/protocol/azure.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package protocol
18+
19+
import (
20+
cosiapi "sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2"
21+
cosiproto "sigs.k8s.io/container-object-storage-interface/proto"
22+
)
23+
24+
// AzureBucketInfoTranslator implements RpcApiTranslator for S3 bucket info.
25+
type AzureBucketInfoTranslator struct{}
26+
27+
// TODO: S3CredentialTranslator implements RpcApiTranslator for S3 credentials.
28+
29+
var _ RpcApiTranslator[*cosiproto.AzureBucketInfo, cosiapi.BucketInfoVar] = AzureBucketInfoTranslator{}
30+
31+
func (AzureBucketInfoTranslator) RpcToApi(b *cosiproto.AzureBucketInfo) map[cosiapi.BucketInfoVar]string {
32+
if b == nil {
33+
return nil
34+
}
35+
36+
out := map[cosiapi.BucketInfoVar]string{
37+
cosiapi.BucketInfoVar_Azure_StorageAccount: "",
38+
}
39+
40+
// TODO: implement
41+
42+
return out
43+
}
44+
45+
func (AzureBucketInfoTranslator) ApiToRpc(vars map[cosiapi.BucketInfoVar]string) *cosiproto.AzureBucketInfo {
46+
if len(vars) == 0 {
47+
return nil
48+
}
49+
50+
// TODO: implement
51+
52+
return nil
53+
}
54+
55+
func (AzureBucketInfoTranslator) Validate(
56+
vars map[cosiapi.BucketInfoVar]string, _ cosiapi.BucketAccessAuthenticationType,
57+
) error {
58+
// TODO: implement
59+
60+
return nil
61+
}

internal/protocol/gcs.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package protocol
18+
19+
import (
20+
cosiapi "sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2"
21+
cosiproto "sigs.k8s.io/container-object-storage-interface/proto"
22+
)
23+
24+
// GcsBucketInfoTranslator implements RpcApiTranslator for S3 bucket info.
25+
type GcsBucketInfoTranslator struct{}
26+
27+
// TODO: S3CredentialTranslator implements RpcApiTranslator for S3 credentials.
28+
29+
var _ RpcApiTranslator[*cosiproto.GcsBucketInfo, cosiapi.BucketInfoVar] = GcsBucketInfoTranslator{}
30+
31+
func (GcsBucketInfoTranslator) RpcToApi(b *cosiproto.GcsBucketInfo) map[cosiapi.BucketInfoVar]string {
32+
if b == nil {
33+
return nil
34+
}
35+
36+
out := map[cosiapi.BucketInfoVar]string{
37+
cosiapi.BucketInfoVar_GCS_BucketName: "",
38+
cosiapi.BucketInfoVar_GCS_ProjectId: "",
39+
}
40+
41+
// TODO: implement
42+
43+
return out
44+
}
45+
46+
func (GcsBucketInfoTranslator) ApiToRpc(vars map[cosiapi.BucketInfoVar]string) *cosiproto.GcsBucketInfo {
47+
if len(vars) == 0 {
48+
return nil
49+
}
50+
51+
// TODO: implement
52+
53+
return nil
54+
}
55+
56+
func (GcsBucketInfoTranslator) Validate(
57+
vars map[cosiapi.BucketInfoVar]string, _ cosiapi.BucketAccessAuthenticationType,
58+
) error {
59+
// TODO: implement
60+
61+
return nil
62+
}

internal/protocol/protocol.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package protocol contains definitions and functions for transforming COSI gRPC spec definitions
18+
// into COSI Kubernetes definitions.
19+
package protocol
20+
21+
import (
22+
"fmt"
23+
24+
cosiapi "sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2"
25+
cosiproto "sigs.k8s.io/container-object-storage-interface/proto"
26+
)
27+
28+
// ObjectProtocolTranslator translates object protocol types between the RPC driver-domain and
29+
// Kubernetes API user-domain.
30+
type ObjectProtocolTranslator struct{}
31+
32+
var (
33+
objectProtocolProtoApiMapping = map[cosiproto.ObjectProtocol_Type]cosiapi.ObjectProtocol{
34+
cosiproto.ObjectProtocol_S3: cosiapi.ObjectProtocolS3,
35+
cosiproto.ObjectProtocol_AZURE: cosiapi.ObjectProtocolAzure,
36+
cosiproto.ObjectProtocol_GCS: cosiapi.ObjectProtocolGcs,
37+
}
38+
)
39+
40+
// RpcToApi translates object protocols from RPC to API.
41+
func (ObjectProtocolTranslator) RpcToApi(in cosiproto.ObjectProtocol_Type) (cosiapi.ObjectProtocol, error) {
42+
a, ok := objectProtocolProtoApiMapping[in]
43+
if !ok {
44+
return cosiapi.ObjectProtocol(""), fmt.Errorf("unknown driver protocol %q", string(in))
45+
}
46+
return a, nil
47+
}
48+
49+
// ApiToRpc translates object protocols from API to RPC.
50+
func (ObjectProtocolTranslator) ApiToRpc(in cosiapi.ObjectProtocol) (cosiproto.ObjectProtocol_Type, error) {
51+
for p, a := range objectProtocolProtoApiMapping {
52+
if a == in {
53+
return p, nil
54+
}
55+
}
56+
return cosiproto.ObjectProtocol_UNKNOWN, fmt.Errorf("unknown api protocol %q", string(in))
57+
}
58+
59+
// An RpcApiTranslator translates types between the RPC driver-domain and Kubernetes API user-domain
60+
// for a particular protocol.
61+
type RpcApiTranslator[RpcType any, ApiType comparable] interface {
62+
// RpcToApi translates bucket info from RPC to API with no validation.
63+
// If the input is nil, the result map MUST be nil.
64+
// All possible API info fields SHOULD be present in the result, even if the corresponding RPC
65+
// info is omitted; an empty string value should be used for keys with no explicit config.
66+
RpcToApi(RpcType) map[ApiType]string
67+
68+
// ApiToRpc translates bucket info from API to RPC with no validation.
69+
// If the input map is nil or empty, this is assumed to mean the protocol is not supported, and
70+
// the result MUST be nil.
71+
ApiToRpc(map[ApiType]string) RpcType
72+
73+
// Validate checks that user-domain API fields meet requirements and expectations.
74+
Validate(map[ApiType]string, cosiapi.BucketAccessAuthenticationType) error
75+
}
76+
77+
// contains is a helper that returns true if the given `list` contains the item `key`.
78+
// Useful for a variety of Validate() implementations.
79+
func contains[T comparable](list []T, key T) bool {
80+
for _, i := range list {
81+
if i == key {
82+
return true
83+
}
84+
}
85+
return false
86+
}

internal/protocol/protocol_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package protocol
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
24+
cosiapi "sigs.k8s.io/container-object-storage-interface/client/apis/objectstorage/v1alpha2"
25+
cosiproto "sigs.k8s.io/container-object-storage-interface/proto"
26+
)
27+
28+
func TestObjectProtocolTranslator(t *testing.T) {
29+
tests := []struct {
30+
name string // description of this test case
31+
api cosiapi.ObjectProtocol // input
32+
wantRpc cosiproto.ObjectProtocol_Type
33+
wantErr bool
34+
}{
35+
{"empty string", cosiapi.ObjectProtocol(""), cosiproto.ObjectProtocol_UNKNOWN, true},
36+
{"unknown proto", cosiapi.ObjectProtocol("evilcorp-proto"), cosiproto.ObjectProtocol_UNKNOWN, true},
37+
{"S3", cosiapi.ObjectProtocolS3, cosiproto.ObjectProtocol_S3, false},
38+
{"Azure", cosiapi.ObjectProtocolAzure, cosiproto.ObjectProtocol_AZURE, false},
39+
{"GCS", cosiapi.ObjectProtocolGcs, cosiproto.ObjectProtocol_GCS, false},
40+
}
41+
for _, tt := range tests {
42+
t.Run(tt.name, func(t *testing.T) {
43+
o := ObjectProtocolTranslator{}
44+
45+
gotRpc, gotErr := o.ApiToRpc(tt.api)
46+
if tt.wantErr {
47+
assert.Error(t, gotErr)
48+
} else {
49+
assert.NoError(t, gotErr)
50+
}
51+
assert.Equal(t, tt.wantRpc, gotRpc)
52+
53+
// Test the round trip. Tests UNKNOWN proto as input for tests where err is expected.
54+
rtGot, rtErr := o.RpcToApi(tt.wantRpc)
55+
if tt.wantErr {
56+
assert.Error(t, rtErr)
57+
assert.Equal(t, cosiapi.ObjectProtocol(""), rtGot)
58+
} else {
59+
assert.NoError(t, rtErr)
60+
assert.Equal(t, tt.api, rtGot)
61+
}
62+
})
63+
}
64+
}

0 commit comments

Comments
 (0)