Skip to content

Commit f2ba462

Browse files
committed
docs: added few other sections
Signed-off-by: Mateusz Urbanek <[email protected]>
1 parent a58d3fe commit f2ba462

16 files changed

+632
-32
lines changed

docs/book.toml

+4
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@
22
title = "Container Object Storage Interface"
33
description = "This site documents how to develop and deploy a Container Object Storage Interface (COSI) driver on Kubernetes."
44
authors = ["The Kubernetes Authors"]
5+
6+
[output.html]
7+
git-repository-url = "https://github.com/kubernetes-sigs/container-object-storage-interface/tree/main/docs"
8+
edit-url-template = "https://github.com/kubernetes-sigs/container-object-storage-interface/tree/main/docs/{path}"

docs/src/SUMMARY.md

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
# Summary
22

33
- [Introduction](./introduction.md)
4-
- [Developing a COSI Driver for Kubernetes](developing/developing.md)
5-
- [Deploying a COSI Driver on Kubernetes](operations/deployment.md)
6-
- [Troubleshooting](operations/troubleshooting.md)
7-
- [Drivers](drivers.md)
8-
- [API Reference](api.md)
4+
- [Quick Start](./quick-start.md)
5+
- [Tasks](./operations/tasks.md)
6+
- [Installing COSI Driver](./operations/installing-driver.md)
7+
- [Troubleshooting](./operations/troubleshooting.md)
8+
- [Developer guide](./developing/guide.md)
9+
- [Developing "core" COSI](./developing/core.md)
10+
- [Developing a COSI Driver](./developing/drivers.md)
11+
- [Developing client applications](./developing/clients.md)
12+
- [Go](./developing/clients/go.md)
13+
- [Java](./developing/clients/java.md)
14+
- [Python](./developing/clients/python.md)
15+
- [Drivers](./drivers.md)
16+
- [API Reference](./api.md)

docs/src/developing/clients.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Developing client applications
2+
3+
## No official client libraries
4+
5+
We do not provide official client libraries for interacting with COSI secrets. Instead, we encourage users to build their own clients using standard tools and APIs.
6+
7+
- Different users have different needs, and maintaining an official client library might limit their ability to customize or optimize for specific use cases.
8+
- Providing and maintaining client libraries across multiple languages is a significant effort, requiring continuous updates and support.
9+
- By relying on standard APIs, users can integrate directly with COSI without additional abstraction layers that may introduce unnecessary complexity.
10+
11+
## Stability and breaking changes
12+
13+
We follow a strict versioning policy to ensure stability while allowing for necessary improvements.
14+
- **Patch Releases (`v1alphaX`)**: No breaking changes are introduced between patch versions.
15+
- **Version Upgrades (`v1alpha1` to `v1alpha2`)**: Breaking changes, including format modifications, may occur between these versions.
16+
17+
For more details, refer to the [Kubernetes Versioning and Deprecation Policy](https://kubernetes.io/docs/reference/using-api/deprecation-policy/).
18+
19+
## Existing Guides
20+
21+
For guidance on developing clients, refer to our language-specific documentation:
22+
- [Go Client Guide](./clients/go.md)
23+
- [Java Client Guide](./clients/java.md)
24+
- [Python Client Guide](./clients/python.md)
25+
26+
If additional client guides are needed, we welcome contributions from the community.
27+

docs/src/developing/clients/go.md

+264
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
# Developing Client Apps in Go
2+
3+
## Configuration Structure
4+
5+
The `Config` struct is the primary configuration object for the storage package. It encapsulates all necessary settings for interacting with different storage providers.
6+
7+
```go
8+
// import "example.com/pkg/storage"
9+
package storage
10+
11+
type Config struct {
12+
Spec Spec `json:"spec"`
13+
}
14+
15+
type Spec struct {
16+
BucketName string `json:"bucketName"`
17+
AuthenticationType string `json:"authenticationType"`
18+
Protocols []string `json:"protocols"`
19+
SecretS3 *s3.SecretS3 `json:"secretS3,omitempty"`
20+
SecretAzure *azure.SecretAzure `json:"secretAzure,omitempty"`
21+
}
22+
```
23+
24+
## Azure Secret Structure
25+
26+
The `SecretAzure` struct holds authentication credentials for accessing Azure-based storage services.
27+
28+
```go
29+
// import "example.com/pkg/storage/azure"
30+
package azure
31+
32+
type SecretAzure struct {
33+
AccessToken string `json:"accessToken"`
34+
ExpiryTimestamp time.Time `json:"expiryTimeStamp"`
35+
}
36+
```
37+
38+
## S3 Secret Structure
39+
40+
The `SecretS3` struct holds authentication credentials for accessing S3-compatible storage services.
41+
42+
```go
43+
// import "example.com/pkg/storage/s3"
44+
package s3
45+
46+
type SecretS3 struct {
47+
Endpoint string `json:"endpoint"`
48+
Region string `json:"region"`
49+
AccessKeyID string `json:"accessKeyID"`
50+
AccessSecretKey string `json:"accessSecretKey"`
51+
}
52+
```
53+
54+
## Factory
55+
56+
The [factory pattern](https://en.wikipedia.org/wiki/Factory_method_pattern) is used to instantiate the appropriate storage backend based on the provided configuration. We will hide the implementation behind the interface.
57+
58+
Here is a minimal interface that supports only basic `Delete`/`Get`/`Put` operations:
59+
60+
```go
61+
type Storage interface {
62+
Delete(ctx context.Context, key string) error
63+
Get(ctx context.Context, key string, wr io.Writer) error
64+
Put(ctx context.Context, key string, data io.Reader, size int64) error
65+
}
66+
```
67+
68+
Our implementation of factory method can be defined as following:
69+
70+
```go
71+
// import "example.com/pkg/storage"
72+
package storage
73+
74+
import (
75+
"fmt"
76+
"slices"
77+
"strings"
78+
79+
"example.com/pkg/storage/azure"
80+
"example.com/pkg/storage/s3"
81+
)
82+
83+
func New(config Config, ssl bool) (Storage, error) {
84+
if slices.ContainsFunc(config.Spec.Protocols, func(s string) bool { return strings.EqualFold(s, "s3") }) {
85+
if !strings.EqualFold(config.Spec.AuthenticationType, "key") {
86+
return nil, fmt.Errorf("%w: invalid authentication type for s3", ErrInvalidConfig)
87+
}
88+
89+
s3secret := config.Spec.SecretS3
90+
if s3secret == nil {
91+
return nil, fmt.Errorf("%w: s3 secret missing", ErrInvalidConfig)
92+
}
93+
94+
return s3.New(config.Spec.BucketName, *s3secret, ssl)
95+
}
96+
97+
if slices.ContainsFunc(config.Spec.Protocols, func(s string) bool { return strings.EqualFold(s, "azure") }) {
98+
if !strings.EqualFold(config.Spec.AuthenticationType, "key") {
99+
return nil, fmt.Errorf("%w: invalid authentication type for azure", ErrInvalidConfig)
100+
}
101+
102+
azureSecret := config.Spec.SecretAzure
103+
if azureSecret == nil {
104+
return nil, fmt.Errorf("%w: azure secret missing", ErrInvalidConfig)
105+
}
106+
107+
return azure.New(config.Spec.BucketName, *azureSecret)
108+
}
109+
110+
return nil, fmt.Errorf("%w: invalid protocol (%v)", ErrInvalidConfig, config.Spec.Protocols)
111+
}
112+
```
113+
114+
## Clients
115+
116+
As we alredy defined the factory and uppermost configuration, let's get into the details of the clients, that will implement the `Storage` interface.
117+
118+
### S3
119+
120+
In the implementation of S3 client, we will use [MinIO](https://github.com/minio/minio-go) client library, as it's more lightweight than AWS SDK.
121+
122+
```go
123+
// import "example.com/pkg/storage/s3"
124+
package s3
125+
126+
import (
127+
"context"
128+
"fmt"
129+
"io"
130+
"net/http"
131+
"time"
132+
133+
"github.com/minio/minio-go/v7"
134+
"github.com/minio/minio-go/v7/pkg/credentials"
135+
)
136+
137+
type Client struct {
138+
s3cli *minio.Client
139+
bucketName string
140+
}
141+
142+
func New(bucketName string, s3secret SecretS3, ssl bool) (*Client, error) {
143+
s3cli, err := minio.New(s3secret.Endpoint, &minio.Options{
144+
Creds: credentials.NewStaticV4(s3secret.AccessKeyID, s3secret.AccessSecretKey, ""),
145+
Region: s3secret.Region,
146+
Secure: ssl,
147+
})
148+
if err != nil {
149+
return nil, fmt.Errorf("unable to create client: %w", err)
150+
}
151+
152+
return &Client{
153+
s3cli: s3cli,
154+
bucketName: bucketName,
155+
}, nil
156+
}
157+
158+
func (c *Client) Delete(ctx context.Context, key string) error {
159+
return c.s3cli.RemoveObject(ctx, c.bucketName, key, minio.RemoveObjectOptions{})
160+
}
161+
162+
func (c *Client) Get(ctx context.Context, key string, wr io.Writer) error {
163+
obj, err := c.s3cli.GetObject(ctx, c.bucketName, key, minio.GetObjectOptions{})
164+
if err != nil {
165+
return err
166+
}
167+
_, err = io.Copy(wr, obj)
168+
return err
169+
}
170+
171+
func (c *Client) Put(ctx context.Context, key string, data io.Reader, size int64) error {
172+
_, err := c.s3cli.PutObject(ctx, c.bucketName, key, data, size, minio.PutObjectOptions{})
173+
return err
174+
}
175+
```
176+
177+
### Azure Blob
178+
179+
In the implementation of Azure client, we will use [Azure SDK](https://github.com/Azure/azure-sdk-for-go) client library. Note, that the configuration is done with `NoCredentials` client, as the Azure secret contains shared access signatures (SAS).
180+
181+
```go
182+
// import "example.com/pkg/storage/azure"
183+
package azure
184+
185+
import (
186+
"context"
187+
"errors"
188+
"fmt"
189+
"io"
190+
"time"
191+
192+
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
193+
)
194+
195+
type Client struct {
196+
azCli *azblob.Client
197+
containerName string
198+
}
199+
200+
func New(containerName string, azureSecret SecretAzure) (*Client, error) {
201+
azCli, err := azblob.NewClientWithNoCredential(azureSecret.AccessToken, nil)
202+
if err != nil {
203+
return nil, fmt.Errorf("unable to create client: %w", err)
204+
}
205+
206+
return &Client{
207+
azCli: azCli,
208+
containerName: containerName,
209+
}, nil
210+
}
211+
212+
func (c *Client) Delete(ctx context.Context, blobName string) error {
213+
_, err := c.azCli.DeleteBlob(ctx, c.containerName, blobName, nil)
214+
return err
215+
}
216+
217+
func (c *Client) Get(ctx context.Context, blobName string, wr io.Writer) error {
218+
stream, err := c.azCli.DownloadStream(ctx, c.containerName, blobName, nil)
219+
if err != nil {
220+
return fmt.Errorf("unable to get download stream: %w", err)
221+
}
222+
_, err = io.Copy(wr, stream.Body)
223+
return err
224+
}
225+
226+
func (c *Client) Put(ctx context.Context, blobName string, data io.Reader, size int64) error {
227+
_, err := c.azCli.UploadStream(ctx, c.containerName, blobName, data, nil)
228+
return err
229+
}
230+
```
231+
232+
## Summing up
233+
234+
Once all our code is in place, we can start using it in our app. Reading configuration is as simple as opening file and decoding it using standard `encoding/json` package.
235+
236+
```go
237+
import (
238+
"encoding/json"
239+
"os"
240+
241+
"example.com/pkg/storage"
242+
)
243+
244+
func example() {
245+
f, err := os.Open("/opt/cosi/BucketInfo.json")
246+
if err != nil {
247+
panic(err)
248+
}
249+
defer f.Close()
250+
251+
var cfg storage.Config
252+
if err := json.NewDecoder(f).Decode(&cfg); err != nil {
253+
panic(err)
254+
}
255+
256+
client, err := storage.New(cfg, true)
257+
if err != nil {
258+
panic(err)
259+
}
260+
261+
// use client Put/Get/Delete
262+
// ...
263+
}
264+
```

docs/src/developing/clients/java.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Java
2+
3+
// TODO

docs/src/developing/clients/python.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Python
2+
3+
// TODO

docs/src/developing/core.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Developing "core" COSI
2+
3+
With “core” COSI we refer to the common set of API and controllers that are required to run any COSI driver.
4+
5+
// TODO

docs/src/developing/developing.md

-11
This file was deleted.

0 commit comments

Comments
 (0)