Skip to content

Commit

Permalink
samples(storage): add folder samples (#4178)
Browse files Browse the repository at this point in the history
Adds samples to create, get, list, rename, and delete folders.

Closes googleapis/google-cloud-go#10294


## Checklist
- [x] I have followed [Contributing Guidelines from CONTRIBUTING.MD](https://togithub.com/GoogleCloudPlatform/golang-samples/blob/main/CONTRIBUTING.md)
- [x] **Tests** pass:   `go test -v ./..` (see [Testing](https://togithub.com/GoogleCloudPlatform/golang-samples/blob/main/CONTRIBUTING.md#testing))
- [x] **Code formatted**:   `gofmt` (see [Formatting](https://togithub.com/GoogleCloudPlatform/golang-samples/blob/main/CONTRIBUTING.md#formatting))
- [x] **Vetting** pass:   `go vet` (see [Formatting](https://togithub.com/GoogleCloudPlatform/golang-samples/blob/main/CONTRIBUTING.md#formatting))
- [ ] These samples need a new **API enabled** in testing projects to pass (let us know which ones)
- [ ] These samples need a new/updated **env vars** in testing projects set to pass (let us know which ones)
- [ ] This sample adds a new sample directory, and I updated the [CODEOWNERS file](https://togithub.com/GoogleCloudPlatform/golang-samples/blob/main/.github/CODEOWNERS) with the codeowners for this sample
- [ ] This sample adds a new **Product API**, and I updated the [Blunderbuss issue/PR auto-assigner](https://togithub.com/GoogleCloudPlatform/golang-samples/blob/main/.github/blunderbuss.yml) with the codeowners for this sample
- [ ] Please **merge** this PR for me once it is approved
  • Loading branch information
tritone authored Jun 12, 2024
1 parent 2322421 commit eb05481
Show file tree
Hide file tree
Showing 6 changed files with 449 additions and 0 deletions.
56 changes: 56 additions & 0 deletions storage/control/create_folder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package control

// [START storage_control_create_folder]
import (
"context"
"fmt"
"io"
"time"

control "cloud.google.com/go/storage/control/apiv2"
"cloud.google.com/go/storage/control/apiv2/controlpb"
)

// createFolder creates a folder in the bucket with the given name.
func createFolder(w io.Writer, bucket, folder string) error {
// bucket := "bucket-name"
// folder := "folder-name"

ctx := context.Background()
client, err := control.NewStorageControlClient(ctx)
if err != nil {
return fmt.Errorf("NewStorageControlClient: %w", err)
}
defer client.Close()

ctx, cancel := context.WithTimeout(ctx, time.Second*30)
defer cancel()

req := &controlpb.CreateFolderRequest{
Parent: fmt.Sprintf("projects/_/buckets/%v", bucket),
FolderId: folder,
}
f, err := client.CreateFolder(ctx, req)
if err != nil {
return fmt.Errorf("CreateFolder(%q): %w", folder, err)
}

fmt.Fprintf(w, "created folder with path %q", f.Name)
return nil
}

// [END storage_control_create_folder]
57 changes: 57 additions & 0 deletions storage/control/delete_folder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package control

// [START storage_control_delete_folder]
import (
"context"
"fmt"
"io"
"time"

control "cloud.google.com/go/storage/control/apiv2"
"cloud.google.com/go/storage/control/apiv2/controlpb"
)

// deleteFolder deletes the folder with the given name.
func deleteFolder(w io.Writer, bucket, folder string) error {
// bucket := "bucket-name"
// folder := "folder-name"

ctx := context.Background()
client, err := control.NewStorageControlClient(ctx)
if err != nil {
return fmt.Errorf("NewStorageControlClient: %w", err)
}
defer client.Close()

ctx, cancel := context.WithTimeout(ctx, time.Second*30)
defer cancel()

// Construct folder path including the bucket name.
folderPath := fmt.Sprintf("projects/_/buckets/%v/folders/%v", bucket, folder)

req := &controlpb.DeleteFolderRequest{
Name: folderPath,
}
if err := client.DeleteFolder(ctx, req); err != nil {
return fmt.Errorf("DeleteFolder(%q): %w", folderPath, err)
}

fmt.Fprintf(w, "deleted folder %q", folderPath)
return nil
}

// [END storage_control_delete_folder]
146 changes: 146 additions & 0 deletions storage/control/folders_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package control

import (
"bytes"
"context"
"fmt"
"log"
"os"
"strings"
"testing"
"time"

"cloud.google.com/go/storage"
"github.com/GoogleCloudPlatform/golang-samples/internal/testutil"
)

const (
testPrefix = "storage-control-test"
bucketExpiryAge = time.Hour * 24
)

var (
client *storage.Client
)

func TestMain(m *testing.M) {
// Create shared storage client
ctx := context.Background()
c, err := storage.NewClient(ctx)
if err != nil {
log.Fatalf("storage.NewClient: %v", err)
}
defer c.Close()
client = c

// Run tests
exit := m.Run()

// Delete old buckets whose name begins with our test prefix
tc, _ := testutil.ContextMain(m)

if err := testutil.DeleteExpiredBuckets(c, tc.ProjectID, testPrefix, bucketExpiryAge); err != nil {
// Don't fail the test if cleanup fails
log.Printf("Post-test cleanup failed: %v", err)
}
os.Exit(exit)
}

func TestFolders(t *testing.T) {
tc := testutil.SystemTest(t)
ctx := context.Background()

// Create HNS bucket.
bucketName := testutil.UniqueBucketName(testPrefix)
b := client.Bucket(bucketName)
attrs := &storage.BucketAttrs{
HierarchicalNamespace: &storage.HierarchicalNamespace{
Enabled: true,
},
UniformBucketLevelAccess: storage.UniformBucketLevelAccess{
Enabled: true,
},
}
if err := b.Create(ctx, tc.ProjectID, attrs); err != nil {
t.Fatalf("Bucket.Create(%q): %v", bucketName, err)
}
t.Cleanup(func() {
if err := testutil.DeleteBucketIfExists(ctx, client, bucketName); err != nil {
log.Printf("Bucket.Delete(%q): %v", bucketName, err)
}
})

folderName := "foo"
folderPath := fmt.Sprintf("projects/_/buckets/%v/folders/%v", bucketName, folderName)
newFolderName := "bar"
newFolderPath := fmt.Sprintf("projects/_/buckets/%v/folders/%v", bucketName, newFolderName)

// Create folder. Retry because there is no automatic retry in the client
// for this op.
if ok := testutil.Retry(t, 5, time.Second, func(r *testutil.R) {
buf := &bytes.Buffer{}
if err := createFolder(buf, bucketName, folderName); err != nil {
r.Errorf("createFolder: %v", err)
}
if got, want := buf.String(), folderPath; !strings.Contains(got, want) {
r.Errorf("createFolder: got %q, want to contain %q", got, want)
}
}); !ok {
t.Fatalf("failed to create folder; can't continue")
}

// Get folder. Retry because there is no automatic retry in the client
// for this op.
if ok := testutil.Retry(t, 5, time.Second, func(r *testutil.R) {
buf := &bytes.Buffer{}
if err := getFolder(buf, bucketName, folderName); err != nil {
r.Errorf("getFolder: %v", err)
}
if got, want := buf.String(), folderPath; !strings.Contains(got, want) {
r.Errorf("getFolder: got %q, want to contain %q", got, want)
}
}); !ok {
t.Fatalf("failed to get folder; can't continue")
}

// List folders.
buf := &bytes.Buffer{}
if err := listFolders(buf, bucketName); err != nil {
t.Fatalf("listFolders: %v", err)
}
if got, want := buf.String(), folderPath; !strings.Contains(got, want) {
t.Errorf("listFolders: got %q, want to contain %q", got, want)
}

// Rename folder.
buf = &bytes.Buffer{}
if err := renameFolder(buf, bucketName, folderName, newFolderName); err != nil {
t.Fatalf("renameFolder: %v", err)
}
if got, want := buf.String(), newFolderPath; !strings.Contains(got, want) {
t.Errorf("listFolders: got %q, want to contain %q", got, want)
}

// Delete folder.
buf = &bytes.Buffer{}
if err := deleteFolder(buf, bucketName, newFolderName); err != nil {
t.Fatalf("deleteFolder: %v", err)
}
if got, want := buf.String(), newFolderPath; !strings.Contains(got, want) {
t.Errorf("deleteFolder: got %q, want to contain %q", got, want)
}
}
58 changes: 58 additions & 0 deletions storage/control/get_folder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package control

// [START storage_control_get_folder]
import (
"context"
"fmt"
"io"
"time"

control "cloud.google.com/go/storage/control/apiv2"
"cloud.google.com/go/storage/control/apiv2/controlpb"
)

// getFolder gets metadata for the folder with the given name.
func getFolder(w io.Writer, bucket, folder string) error {
// bucket := "bucket-name"
// folder := "folder-name"

ctx := context.Background()
client, err := control.NewStorageControlClient(ctx)
if err != nil {
return fmt.Errorf("NewStorageControlClient: %w", err)
}
defer client.Close()

ctx, cancel := context.WithTimeout(ctx, time.Second*30)
defer cancel()

// Construct folder path including the bucket name.
folderPath := fmt.Sprintf("projects/_/buckets/%v/folders/%v", bucket, folder)

req := &controlpb.GetFolderRequest{
Name: folderPath,
}
f, err := client.GetFolder(ctx, req)
if err != nil {
return fmt.Errorf("GetFolder(%q): %w", folderPath, err)
}

fmt.Fprintf(w, "got folder metadata: %+v", f)
return nil
}

// [END storage_control_get_folder]
66 changes: 66 additions & 0 deletions storage/control/list_folders.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package control

// [START storage_control_list_folders]
import (
"context"
"fmt"
"io"
"time"

control "cloud.google.com/go/storage/control/apiv2"
"cloud.google.com/go/storage/control/apiv2/controlpb"
"google.golang.org/api/iterator"
)

// listFolders lists all folders present in the bucket.
func listFolders(w io.Writer, bucket string) error {
// bucket := "bucket-name"
// folder := "folder-name"

ctx := context.Background()
client, err := control.NewStorageControlClient(ctx)
if err != nil {
return fmt.Errorf("NewStorageControlClient: %w", err)
}
defer client.Close()

ctx, cancel := context.WithTimeout(ctx, time.Second*30)
defer cancel()

// Construct bucket path for a bucket containing folders.
bucketPath := fmt.Sprintf("projects/_/buckets/%v", bucket)

// List all folders present.
req := &controlpb.ListFoldersRequest{
Parent: bucketPath,
}
it := client.ListFolders(ctx, req)
for {
f, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
return fmt.Errorf("ListFolders(%q): %w", bucketPath, err)
}
fmt.Fprintf(w, "got folder %v\n", f.Name)
}

return nil
}

// [END storage_control_list_folders]
Loading

0 comments on commit eb05481

Please sign in to comment.