Skip to content

Commit

Permalink
Add new couchbase provider resources (#61)
Browse files Browse the repository at this point in the history
After this commit we add new resources:
- couchbase_bucket_scope
- couchbase_bucket_collection

Add documentation for resource:
- couchbase_bucket_scope
- couchbase_bucket_collection

Upgrade:
- go to version 1.22.1
- terraform to version 1.7.4

Co-authored-by: Colin Mullikin <[email protected]>
  • Loading branch information
lukasbudisky and colin-mullikin authored Mar 14, 2024
1 parent 8e5c747 commit ef8f6aa
Show file tree
Hide file tree
Showing 18 changed files with 585 additions and 46 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ jobs:
- name: Setup go
uses: actions/[email protected]
with:
go-version: "1.21.0"
go-version: "1.22.1"
- name: Couchbase Initialization
run: |
ls -als
make cbinit
- name: Couchbase Unit Tests
run: |
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ TEST?=$$(go list ./... | grep -v 'vendor')
HOSTNAME=budisky.com
NAMESPACE=couchbase
NAME=couchbase
VERSION=0.0.6
VERSION=1.1.0
BINARY="terraform-provider-${NAME}_${VERSION}"
OS_ARCH=linux_amd64
CGO_ENABLED=0
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
Terraform provider for Couchbase allow manage resources in couchbase cluster

## Requirements
- terraform 1.4.0
- go 1.21 (for plugin build)
- docker-compose v2.15.1
- docker desktop 4.17.0
- terraform 1.7.4
- go 1.22.1 (for plugin build)
- docker-compose v2.24.6-desktop.1
- docker desktop 4.28.0

## Run couchbase on localhost
In terraform_example folder is docker-compose.yml with couchbase server.
Expand Down Expand Up @@ -60,7 +60,7 @@ terraform {
required_version = ">= 1.4.0"
required_providers {
couchbase = {
version = "~> 0.0.6"
version = "~> 1.1.0"
source = "budisky.com/couchbase/couchbase"
}
}
Expand All @@ -83,7 +83,7 @@ terraform {
required_version = ">= 1.4.0"
required_providers {
couchbase = {
version = "~> 0.0.6"
version = "~> 1.1.0"
source = "budisky.com/couchbase/couchbase"
}
}
Expand Down
11 changes: 11 additions & 0 deletions couchbase/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,20 @@ const (
keyQueryIndexFields = "fields"
keyQueryIndexCondition = "condition"

// Scope resource constants
keyScopeName = "name"
keyScopeBucketName = "bucket"

// Collection resource constants
keyCollectionName = "name"
keyCollectionScopeName = "scope"
keyCollectionBucketName = "bucket"

// Others
queryIndexTimeoutCreate = 300
bucketTimeoutCreate = 300
scopeTimeoutCreate = 300
collectionTimeoutCreate = 300
securityUserTimeoutCreate = 300
securityGroupTimeoutCreate = 300
)
2 changes: 2 additions & 0 deletions couchbase/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ func Provider() *schema.Provider {
"couchbase_security_user": resourceSecurityUser(),
"couchbase_primary_query_index": resourcePrimaryQueryIndex(),
"couchbase_query_index": resourceQueryIndex(),
"couchbase_bucket_scope": resourceScope(),
"couchbase_bucket_collection": resourceCollection(),
},

ConfigureContextFunc: providerConfigure,
Expand Down
5 changes: 2 additions & 3 deletions couchbase/resourceBucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,7 @@ func readBucket(c context.Context, d *schema.ResourceData, m interface{}) diag.D

bucketID := d.Id()

couchbaseConf := m.(*Connection)
couchbase, diags := couchbaseConf.CouchbaseInitialization()
couchbase, diags := m.(*Connection).CouchbaseInitialization()
if diags != nil {
return diags
}
Expand Down Expand Up @@ -277,7 +276,7 @@ func readBucket(c context.Context, d *schema.ResourceData, m interface{}) diag.D
diags = append(diags, *diagForValueSet(keyBucketCompressionMode, bucket.CompressionMode, err))
}

crt, err := couchbaseConf.getBucketConflictResolutionType(bucket.Name)
crt, err := m.(*Connection).getBucketConflictResolutionType(bucket.Name)
if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Expand Down
171 changes: 171 additions & 0 deletions couchbase/resourceCollection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package couchbase

import (
"context"
"errors"
"fmt"
"strings"
"time"

"github.com/couchbase/gocb/v2"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

type ErrCollectionNotFound struct {
name string
}

func (e *ErrCollectionNotFound) Error() string {
return fmt.Sprintf("cannot find collection with name: %s", e.name)
}

func resourceCollection() *schema.Resource {
return &schema.Resource{
CreateContext: createCollection,
ReadContext: readCollection,
DeleteContext: deleteCollection,
Description: "Manage collections in couchbase",
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
keyCollectionBucketName: {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Bucket name",
},
keyCollectionScopeName: {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Scope name",
},
keyCollectionName: {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Collection name",
},
},
}
}

func createCollection(c context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics

couchbase, diags := m.(*Connection).CouchbaseInitialization()
if diags != nil {
return diags
}
defer couchbase.ConnectionCLose()

bucketName := d.Get(keyCollectionBucketName).(string)
scopeName := d.Get(keyCollectionScopeName).(string)
collectionName := d.Get(keyCollectionName).(string)

cm := couchbase.Cluster.Bucket(bucketName).Collections()

collectionSpec := gocb.CollectionSpec{Name: collectionName, ScopeName: scopeName}
if err := cm.CreateCollection(collectionSpec, nil); err != nil {
return diag.FromErr(err)
}

if err := retry.RetryContext(c, time.Duration(collectionTimeoutCreate)*time.Second, func() *retry.RetryError {

target := &ErrCollectionNotFound{}
_, err := findCollection(cm, collectionName, scopeName)
if errors.As(err, &target) {
return retry.RetryableError(target)
}

if err != nil {
return retry.NonRetryableError(fmt.Errorf("can't create collection: %s error: %s", collectionName, err))
}

d.SetId(bucketName + "/" + scopeName + "/" + collectionName)
return nil
}); err != nil {
return diag.FromErr(err)
}

return readCollection(c, d, m)
}

func readCollection(c context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics
names := strings.Split(d.Id(), "/")
if len(names) != 3 {
return diag.Errorf("malformed id for collection: %s", d.Id())
}
bucketName := names[0]
scopeName := names[1]
collectionName := names[2]

couchbase, diags := m.(*Connection).CouchbaseInitialization()
if diags != nil {
return diags
}
defer couchbase.ConnectionCLose()

cm := couchbase.Cluster.Bucket(bucketName).Collections()

collection, err := findCollection(cm, collectionName, scopeName)
if err != nil {
d.SetId("")
return diag.FromErr(err)
}

if err := d.Set(keyCollectionName, collection.Name); err != nil {
diags = append(diags, *diagForValueSet(keyCollectionName, collection.Name, err))
}
if err := d.Set(keyCollectionBucketName, bucketName); err != nil {
diags = append(diags, *diagForValueSet(keyCollectionBucketName, bucketName, err))
}
if err := d.Set(keyCollectionScopeName, scopeName); err != nil {
diags = append(diags, *diagForValueSet(keyCollectionScopeName, scopeName, err))
}

return diags
}

func deleteCollection(c context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics

couchbase, diags := m.(*Connection).CouchbaseInitialization()
if diags != nil {
return diags
}
defer couchbase.ConnectionCLose()

names := strings.Split(d.Id(), "/")
bucketName := names[0]
scopeName := names[1]
collectionName := names[2]

cm := couchbase.Cluster.Bucket(bucketName).Collections()

collectionSpec := gocb.CollectionSpec{Name: collectionName, ScopeName: scopeName}
if err := cm.DropCollection(collectionSpec, nil); err != nil {
return diag.FromErr(err)
}

return diags
}

func findCollection(cm *gocb.CollectionManager, name string, scopeName string) (*gocb.CollectionSpec, error) {
scope, err := findScope(cm, scopeName)
if err != nil {
return nil, err
}

for _, collection := range scope.Collections {
if collection.Name == name {
return &collection, nil
}
}

return nil, &ErrCollectionNotFound{name: name}
}
42 changes: 42 additions & 0 deletions couchbase/resourceCollection_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package couchbase

import (
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

const testAccCollection_basic = `
resource "couchbase_bucket_manager" "bucket" {
name = "testAccCollection_basic_bucket"
ram_quota_mb = 100
}
resource "couchbase_bucket_scope" "scope" {
name = "testAccCollection_basic_scope"
bucket = couchbase_bucket_manager.bucket.name
}
resource "couchbase_bucket_collection" "collection" {
name = "testAccCollection_basic_bucket"
scope = couchbase_bucket_scope.scope.name
bucket = couchbase_bucket_manager.bucket.name
}
`

func TestAccCollection(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCollection_basic,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("couchbase_bucket_collection.collection", "name", "testAccCollection_basic_bucket"),
resource.TestCheckResourceAttr("couchbase_bucket_collection.collection", "bucket", "testAccCollection_basic_bucket"),
resource.TestCheckResourceAttr("couchbase_bucket_collection.collection", "scope", "testAccCollection_basic_scope"),
),
},
},
})
}
4 changes: 2 additions & 2 deletions couchbase/resourceQueryIndex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ resource "couchbase_bucket_manager" "bucket" {
resource "couchbase_query_index" "query_index" {
name = "testAccQueryIndex_extended_query_index_name"
bucket = couchbase_bucket_manager.bucket.name
fields = [
"` + "`" + "action" + "`" + `"
]
num_replica = 0
condition = "(` + "`" + "type" + "`" + " " + `= \"http://example.com\")"
}
Expand Down
Loading

0 comments on commit ef8f6aa

Please sign in to comment.