diff --git a/CHANGELOG.md b/CHANGELOG.md index 99651270..577765a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,14 @@ ### Features 1. [#120](https://github.com/influxdata/influxdb-client-go/pull/120) Health check API 1. [#122](https://github.com/influxdata/influxdb-client-go/pull/122) Delete API +1. [#124](https://github.com/influxdata/influxdb-client-go/pull/124) Buckets API ### Breaking Change - [#107](https://github.com/influxdata/influxdb-client-go/pull/100) Renamed `InfluxDBClient` interface to `Client`, so the full name `influxdb2.Client` suits better to Go naming conventions ### Bug fixes 1. [#108](https://github.com/influxdata/influxdb-client-go/issues/108) Fix default retry interval doc -1. [#110](https://github.com/influxdata/influxdb-client-go/issues/110) Allowing empty (nil) values +1. [#110](https://github.com/influxdata/influxdb-client-go/issues/110) Allowing empty (nil) values in query result ### Documentation - [#112](https://github.com/influxdata/influxdb-client-go/pull/112) Clarify how to use client with InfluxDB 1.8+ diff --git a/api/buckets.go b/api/buckets.go new file mode 100644 index 00000000..000361ee --- /dev/null +++ b/api/buckets.go @@ -0,0 +1,322 @@ +// Copyright 2020 InfluxData, Inc. All rights reserved. +// Use of this source code is governed by MIT +// license that can be found in the LICENSE file. + +package api + +import ( + "context" + "github.com/influxdata/influxdb-client-go/domain" +) + +// BucketsApi provides methods for managing Buckets in a InfluxDB server +type BucketsApi interface { + // GetBuckets returns all buckets, with the specified paging. Empty pagingOptions means the default paging (first 20 results) + GetBuckets(ctx context.Context, pagingOptions ...PagingOption) (*[]domain.Bucket, error) + // FindBucketByName returns a bucket found using bucketName + FindBucketByName(ctx context.Context, bucketName string) (*domain.Bucket, error) + // FindBucketById returns a bucket found using bucketId + FindBucketById(ctx context.Context, bucketId string) (*domain.Bucket, error) + // FindBucketsByOrgId returns buckets belonging to the organization with Id orgId, with the specified paging. Empty pagingOptions means the default paging (first 20 results) + FindBucketsByOrgId(ctx context.Context, orgId string, pagingOptions ...PagingOption) (*[]domain.Bucket, error) + // FindBucketsByOrgName returns buckets belonging to the organization with name orgName, with the specified paging. Empty pagingOptions means the default paging (first 20 results) + FindBucketsByOrgName(ctx context.Context, orgName string, pagingOptions ...PagingOption) (*[]domain.Bucket, error) + // CreateBucket creates new bucket + CreateBucket(ctx context.Context, bucket *domain.Bucket) (*domain.Bucket, error) + // CreateBucketWithName creates new bucket with bucketName in organization org, with retention specified in rules. Empty rules means infinite retention + CreateBucketWithName(ctx context.Context, org *domain.Organization, bucketName string, rules ...domain.RetentionRule) (*domain.Bucket, error) + // CreateBucketWithId creates new bucket with bucketName in organization with orgId, with retention specified in rules. Empty rules means infinite retention + CreateBucketWithId(ctx context.Context, orgId, bucketName string, rules ...domain.RetentionRule) (*domain.Bucket, error) + // UpdateBucket updates a bucket + UpdateBucket(ctx context.Context, bucket *domain.Bucket) (*domain.Bucket, error) + // DeleteBucket deletes a bucket + DeleteBucket(ctx context.Context, bucket *domain.Bucket) error + // DeleteBucketWithId deletes a bucket with bucketId + DeleteBucketWithId(ctx context.Context, bucketId string) error + // GetMembers returns members of a bucket + GetMembers(ctx context.Context, bucket *domain.Bucket) (*[]domain.ResourceMember, error) + // GetMembersWithId returns members of a bucket with bucketId + GetMembersWithId(ctx context.Context, bucketId string) (*[]domain.ResourceMember, error) + // AddMember add a user to a bucket + AddMember(ctx context.Context, bucket *domain.Bucket, user *domain.User) (*domain.ResourceMember, error) + // AddMember add a member with id memberId to a bucket with bucketId + AddMemberWithId(ctx context.Context, bucketId, memberId string) (*domain.ResourceMember, error) + // RemoveMember removes a user from a bucket + RemoveMember(ctx context.Context, bucket *domain.Bucket, user *domain.User) error + // RemoveMember removes a member with id memberId from a bucket with bucketId + RemoveMemberWithId(ctx context.Context, bucketId, memberId string) error + // GetOwners returns members of a bucket + GetOwners(ctx context.Context, bucket *domain.Bucket) (*[]domain.ResourceOwner, error) + // GetOwnersWithId returns members of a bucket with bucketId + GetOwnersWithId(ctx context.Context, bucketId string) (*[]domain.ResourceOwner, error) + // AddOwner add a user to a bucket + AddOwner(ctx context.Context, bucket *domain.Bucket, user *domain.User) (*domain.ResourceOwner, error) + // AddOwner add an owner with id memberId to a bucket with bucketId + AddOwnerWithId(ctx context.Context, bucketId, memberId string) (*domain.ResourceOwner, error) + // RemoveOwner a user from a bucket + RemoveOwner(ctx context.Context, bucket *domain.Bucket, user *domain.User) error + // RemoveOwner removes a member with id memberId from a bucket with bucketId + RemoveOwnerWithId(ctx context.Context, bucketId, memberId string) error + // GetLogs returns operation log entries for a bucket, with the specified paging. Empty pagingOptions means the default paging (first 20 results) + GetLogs(ctx context.Context, bucket *domain.Bucket, pagingOptions ...PagingOption) (*[]domain.OperationLog, error) + //GetLogsWithId returns operation log entries for bucket with id bucketId, with the specified paging. Empty pagingOptions means the default paging (first 20 results) + GetLogsWithId(ctx context.Context, bucketId string, pagingOptions ...PagingOption) (*[]domain.OperationLog, error) +} + +type bucketsApiImpl struct { + apiClient *domain.ClientWithResponses +} + +func NewBucketsApi(apiClient *domain.ClientWithResponses) BucketsApi { + return &bucketsApiImpl{ + apiClient: apiClient, + } +} + +func (b *bucketsApiImpl) GetBuckets(ctx context.Context, pagingOptions ...PagingOption) (*[]domain.Bucket, error) { + return b.getBuckets(ctx, nil, pagingOptions...) +} + +func (b *bucketsApiImpl) getBuckets(ctx context.Context, params *domain.GetBucketsParams, pagingOptions ...PagingOption) (*[]domain.Bucket, error) { + if params == nil { + params = &domain.GetBucketsParams{} + } + options := defaultPaging() + for _, opt := range pagingOptions { + opt(options) + } + params.Limit = &options.limit + params.Offset = &options.offset + + response, err := b.apiClient.GetBucketsWithResponse(ctx, params) + if err != nil { + return nil, err + } + if response.JSONDefault != nil { + return nil, domain.DomainErrorToError(response.JSONDefault, response.StatusCode()) + } + return response.JSON200.Buckets, nil +} + +func (b *bucketsApiImpl) FindBucketByName(ctx context.Context, bucketName string) (*domain.Bucket, error) { + params := &domain.GetBucketsParams{Name: &bucketName} + response, err := b.apiClient.GetBucketsWithResponse(ctx, params) + if err != nil { + return nil, err + } + if response.JSONDefault != nil { + return nil, domain.DomainErrorToError(response.JSONDefault, response.StatusCode()) + } + return &(*response.JSON200.Buckets)[0], nil +} + +func (b *bucketsApiImpl) FindBucketById(ctx context.Context, bucketId string) (*domain.Bucket, error) { + params := &domain.GetBucketsIDParams{} + response, err := b.apiClient.GetBucketsIDWithResponse(ctx, bucketId, params) + if err != nil { + return nil, err + } + if response.JSONDefault != nil { + return nil, domain.DomainErrorToError(response.JSONDefault, response.StatusCode()) + } + return response.JSON200, nil +} + +func (b *bucketsApiImpl) FindBucketsByOrgId(ctx context.Context, orgId string, pagingOptions ...PagingOption) (*[]domain.Bucket, error) { + params := &domain.GetBucketsParams{OrgID: &orgId} + return b.getBuckets(ctx, params, pagingOptions...) +} + +func (b *bucketsApiImpl) FindBucketsByOrgName(ctx context.Context, orgName string, pagingOptions ...PagingOption) (*[]domain.Bucket, error) { + params := &domain.GetBucketsParams{Org: &orgName} + return b.getBuckets(ctx, params, pagingOptions...) +} + +func (b *bucketsApiImpl) CreateBucket(ctx context.Context, bucket *domain.Bucket) (*domain.Bucket, error) { + params := &domain.PostBucketsParams{} + bucketReq := &domain.PostBucketRequest{ + Description: bucket.Description, + Name: bucket.Name, + OrgID: bucket.OrgID, + RetentionRules: bucket.RetentionRules, + Rp: bucket.Rp, + } + response, err := b.apiClient.PostBucketsWithResponse(ctx, params, domain.PostBucketsJSONRequestBody(*bucketReq)) + if err != nil { + return nil, err + } + if response.JSONDefault != nil { + return nil, domain.DomainErrorToError(response.JSONDefault, response.StatusCode()) + } + return response.JSON201, nil +} + +func (b *bucketsApiImpl) CreateBucketWithId(ctx context.Context, orgId, bucketName string, rules ...domain.RetentionRule) (*domain.Bucket, error) { + params := &domain.PostBucketsParams{} + bucket := &domain.PostBucketRequest{Name: bucketName, OrgID: &orgId, RetentionRules: rules} + response, err := b.apiClient.PostBucketsWithResponse(ctx, params, domain.PostBucketsJSONRequestBody(*bucket)) + if err != nil { + return nil, err + } + if response.JSON422 != nil { + return nil, domain.DomainErrorToError(response.JSON422, response.StatusCode()) + } + if response.JSONDefault != nil { + return nil, domain.DomainErrorToError(response.JSONDefault, response.StatusCode()) + } + return response.JSON201, nil +} +func (b *bucketsApiImpl) CreateBucketWithName(ctx context.Context, org *domain.Organization, bucketName string, rules ...domain.RetentionRule) (*domain.Bucket, error) { + return b.CreateBucketWithId(ctx, *org.Id, bucketName, rules...) +} + +func (b *bucketsApiImpl) DeleteBucket(ctx context.Context, bucket *domain.Bucket) error { + return b.DeleteBucketWithId(ctx, *bucket.Id) +} + +func (b *bucketsApiImpl) DeleteBucketWithId(ctx context.Context, bucketId string) error { + params := &domain.DeleteBucketsIDParams{} + response, err := b.apiClient.DeleteBucketsIDWithResponse(ctx, bucketId, params) + if err != nil { + return err + } + if response.JSONDefault != nil { + return domain.DomainErrorToError(response.JSONDefault, response.StatusCode()) + } + if response.JSON404 != nil { + return domain.DomainErrorToError(response.JSON404, response.StatusCode()) + } + return nil +} + +func (b *bucketsApiImpl) UpdateBucket(ctx context.Context, bucket *domain.Bucket) (*domain.Bucket, error) { + params := &domain.PatchBucketsIDParams{} + response, err := b.apiClient.PatchBucketsIDWithResponse(ctx, *bucket.Id, params, domain.PatchBucketsIDJSONRequestBody(*bucket)) + if err != nil { + return nil, err + } + if response.JSONDefault != nil { + return nil, domain.DomainErrorToError(response.JSONDefault, response.StatusCode()) + } + return response.JSON200, nil +} + +func (b *bucketsApiImpl) GetMembers(ctx context.Context, bucket *domain.Bucket) (*[]domain.ResourceMember, error) { + return b.GetMembersWithId(ctx, *bucket.Id) +} + +func (b *bucketsApiImpl) GetMembersWithId(ctx context.Context, bucketId string) (*[]domain.ResourceMember, error) { + params := &domain.GetBucketsIDMembersParams{} + response, err := b.apiClient.GetBucketsIDMembersWithResponse(ctx, bucketId, params) + if err != nil { + return nil, err + } + if response.JSONDefault != nil { + return nil, domain.DomainErrorToError(response.JSONDefault, response.StatusCode()) + } + return response.JSON200.Users, nil +} + +func (b *bucketsApiImpl) AddMember(ctx context.Context, bucket *domain.Bucket, user *domain.User) (*domain.ResourceMember, error) { + return b.AddMemberWithId(ctx, *bucket.Id, *user.Id) +} + +func (b *bucketsApiImpl) AddMemberWithId(ctx context.Context, bucketId, memberId string) (*domain.ResourceMember, error) { + params := &domain.PostBucketsIDMembersParams{} + body := &domain.PostBucketsIDMembersJSONRequestBody{Id: memberId} + response, err := b.apiClient.PostBucketsIDMembersWithResponse(ctx, bucketId, params, *body) + if err != nil { + return nil, err + } + if response.JSONDefault != nil { + return nil, domain.DomainErrorToError(response.JSONDefault, response.StatusCode()) + } + return response.JSON201, nil +} + +func (b *bucketsApiImpl) RemoveMember(ctx context.Context, bucket *domain.Bucket, user *domain.User) error { + return b.RemoveMemberWithId(ctx, *bucket.Id, *user.Id) +} + +func (b *bucketsApiImpl) RemoveMemberWithId(ctx context.Context, bucketId, memberId string) error { + params := &domain.DeleteBucketsIDMembersIDParams{} + response, err := b.apiClient.DeleteBucketsIDMembersIDWithResponse(ctx, bucketId, memberId, params) + if err != nil { + return err + } + if response.JSONDefault != nil { + return domain.DomainErrorToError(response.JSONDefault, response.StatusCode()) + } + return nil +} + +func (b *bucketsApiImpl) GetOwners(ctx context.Context, bucket *domain.Bucket) (*[]domain.ResourceOwner, error) { + return b.GetOwnersWithId(ctx, *bucket.Id) +} + +func (b *bucketsApiImpl) GetOwnersWithId(ctx context.Context, bucketId string) (*[]domain.ResourceOwner, error) { + params := &domain.GetBucketsIDOwnersParams{} + response, err := b.apiClient.GetBucketsIDOwnersWithResponse(ctx, bucketId, params) + if err != nil { + return nil, err + } + if response.JSONDefault != nil { + return nil, domain.DomainErrorToError(response.JSONDefault, response.StatusCode()) + } + return response.JSON200.Users, nil +} + +func (b *bucketsApiImpl) AddOwner(ctx context.Context, bucket *domain.Bucket, user *domain.User) (*domain.ResourceOwner, error) { + return b.AddOwnerWithId(ctx, *bucket.Id, *user.Id) +} + +func (b *bucketsApiImpl) AddOwnerWithId(ctx context.Context, bucketId, ownerId string) (*domain.ResourceOwner, error) { + params := &domain.PostBucketsIDOwnersParams{} + body := &domain.PostBucketsIDOwnersJSONRequestBody{Id: ownerId} + response, err := b.apiClient.PostBucketsIDOwnersWithResponse(ctx, bucketId, params, *body) + if err != nil { + return nil, err + } + if response.JSONDefault != nil { + return nil, domain.DomainErrorToError(response.JSONDefault, response.StatusCode()) + } + return response.JSON201, nil +} + +func (b *bucketsApiImpl) RemoveOwner(ctx context.Context, bucket *domain.Bucket, user *domain.User) error { + return b.RemoveOwnerWithId(ctx, *bucket.Id, *user.Id) +} + +func (b *bucketsApiImpl) RemoveOwnerWithId(ctx context.Context, bucketId, memberId string) error { + params := &domain.DeleteBucketsIDOwnersIDParams{} + response, err := b.apiClient.DeleteBucketsIDOwnersIDWithResponse(ctx, bucketId, memberId, params) + if err != nil { + return err + } + if response.JSONDefault != nil { + return domain.DomainErrorToError(response.JSONDefault, response.StatusCode()) + } + return nil +} + +func (b *bucketsApiImpl) GetLogs(ctx context.Context, bucket *domain.Bucket, pagingOptions ...PagingOption) (*[]domain.OperationLog, error) { + return b.GetLogsWithId(ctx, *bucket.Id, pagingOptions...) +} + +func (b *bucketsApiImpl) GetLogsWithId(ctx context.Context, bucketId string, pagingOptions ...PagingOption) (*[]domain.OperationLog, error) { + params := &domain.GetBucketsIDLogsParams{} + options := defaultPaging() + for _, opt := range pagingOptions { + opt(options) + } + params.Limit = &options.limit + params.Offset = &options.offset + response, err := b.apiClient.GetBucketsIDLogsWithResponse(ctx, bucketId, params) + if err != nil { + return nil, err + } + if response.JSONDefault != nil { + return nil, domain.DomainErrorToError(response.JSONDefault, response.StatusCode()) + } + return response.JSON200.Logs, nil +} diff --git a/api/examples_test.go b/api/examples_test.go new file mode 100644 index 00000000..25c55f60 --- /dev/null +++ b/api/examples_test.go @@ -0,0 +1,38 @@ +package api_test + +import ( + "context" + influxdb2 "github.com/influxdata/influxdb-client-go/api" + "github.com/influxdata/influxdb-client-go/domain" +) + +func ExampleBucketsApi() { + // Create influxdb client + client := influxdb2.NewClient("http://localhost:9999", "my-token") + + ctx := context.Background() + // Get Organizations API client + bucketsApi := client.BucketsApi() + + // Get organization that will own new bucket + org, err := client.OrganizationsApi().FindOrganizationByName(ctx, "my-org") + if err != nil { + panic(err) + } + // Create a bucket with 1 day retention policy + bucket, err := bucketsApi.CreateBucketWithName(ctx, org, "bucket-sensors", domain.RetentionRule{EverySeconds: 3600 * 24}) + if err != nil { + panic(err) + } + + // Update description of the bucket + desc := "Bucket for sensor data" + bucket.Description = &desc + bucket, err = bucketsApi.UpdateBucket(ctx, bucket) + if err != nil { + panic(err) + } + + // Close the client + client.Close() +} diff --git a/api/fakeclient.go b/api/fakeclient.go new file mode 100644 index 00000000..431feccb --- /dev/null +++ b/api/fakeclient.go @@ -0,0 +1,86 @@ +// Copyright 2020 InfluxData, Inc. All rights reserved. +// Use of this source code is governed by MIT +// license that can be found in the LICENSE file. + +package api + +import ( + "context" + "github.com/influxdata/influxdb-client-go/domain" +) + +// This file contains fake client with the same interface as real client to overcome import-cycle problem +// to allow real E2E examples for apis in this package + +// Fake options to satisfy Client itnerface +type Options struct { +} + +type fakeClient struct { +} + +func NewClient(_ string, _ string) *fakeClient { + client := &fakeClient{} + return client +} + +func NewClientWithOptions(_ string, _ string, _ *Options) *fakeClient { + client := &fakeClient{} + return client +} + +func (c *fakeClient) Options() *Options { + return nil +} + +func (c *fakeClient) ServerUrl() string { + return "" +} + +func (c *fakeClient) Ready(_ context.Context) (bool, error) { + return true, nil +} + +func (c *fakeClient) Setup(_ context.Context, _, _, _, _ string, _ int) (*domain.OnboardingResponse, error) { + return nil, nil +} + +func (c *fakeClient) Health(_ context.Context) (*domain.HealthCheck, error) { + return nil, nil +} + +//func (c *fakeClient) WriteApi(org, bucket string) WriteApi { +// return nil +//} + +//func (c *fakeClient) WriteApiBlocking(org, bucket string) WriteApiBlocking { +// w := newWriteApiBlockingImpl(org, bucket, c.httpService, c) +// return w +//} + +func (c *fakeClient) Close() { +} + +//func (c *fakeClient) QueryApi(org string) QueryApi { +// return nil +//} + +func (c *fakeClient) AuthorizationsApi() AuthorizationsApi { + return nil +} + +func (c *fakeClient) OrganizationsApi() OrganizationsApi { + return nil +} + +func (c *fakeClient) UsersApi() UsersApi { + return nil +} + +func (c *fakeClient) DeleteApi() DeleteApi { + return nil +} + +func (c *fakeClient) BucketsApi() BucketsApi { + return nil +} diff --git a/api/paging.go b/api/paging.go new file mode 100644 index 00000000..df1ddb17 --- /dev/null +++ b/api/paging.go @@ -0,0 +1,52 @@ +// Copyright 2020 InfluxData, Inc. All rights reserved. +// Use of this source code is governed by MIT +// license that can be found in the LICENSE file. + +package api + +import "github.com/influxdata/influxdb-client-go/domain" + +type PagingOption func(p *Paging) + +// Paging holds pagination parameters for various Get* functions of InfluxDB 2 API +type Paging struct { + offset domain.Offset + limit domain.Limit + sortBy string + descending bool +} + +// defaultPagingOptions returns default paging options: offset 0, limit 20, default sorting, ascending +func defaultPaging() *Paging { + return &Paging{limit: 20, offset: 0, sortBy: "", descending: false} +} + +func PagingWithLimit(limit int) PagingOption { + return func(p *Paging) { + if limit > 100 { + limit = 100 + } + if limit < 1 { + limit = 1 + } + p.limit = domain.Limit(limit) + } +} + +func PagingWithOffset(offset int) PagingOption { + return func(p *Paging) { + p.offset = domain.Offset(offset) + } +} + +func PagingWithSortBy(sortBy string) PagingOption { + return func(p *Paging) { + p.sortBy = sortBy + } +} + +func PagingWithDescending(descending bool) PagingOption { + return func(p *Paging) { + p.descending = descending + } +} diff --git a/api/paging_test.go b/api/paging_test.go new file mode 100644 index 00000000..7e5b3299 --- /dev/null +++ b/api/paging_test.go @@ -0,0 +1,23 @@ +// Copyright 2020 InfluxData, Inc. All rights reserved. +// Use of this source code is governed by MIT +// license that can be found in the LICENSE file. + +package api + +import ( + "github.com/influxdata/influxdb-client-go/domain" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestPaging(t *testing.T) { + paging := &Paging{} + PagingWithOffset(10)(paging) + PagingWithLimit(100)(paging) + PagingWithSortBy("name")(paging) + PagingWithDescending(true)(paging) + assert.True(t, paging.descending) + assert.Equal(t, domain.Limit(100), paging.limit) + assert.Equal(t, domain.Offset(10), paging.offset) + assert.Equal(t, "name", paging.sortBy) +} diff --git a/client.go b/client.go index c4a745c2..5f5a7df5 100644 --- a/client.go +++ b/client.go @@ -50,6 +50,8 @@ type Client interface { UsersApi() api.UsersApi // DeleteApi returns Delete API client DeleteApi() api.DeleteApi + // BucketsApi returns Delete API client + BucketsApi() api.BucketsApi } // clientImpl implements Client interface @@ -64,6 +66,7 @@ type clientImpl struct { orgApi api.OrganizationsApi usersApi api.UsersApi deleteApi api.DeleteApi + bucketsApi api.BucketsApi } // NewClient creates Client for connecting to given serverUrl with provided authentication token, with the default options. @@ -206,3 +209,12 @@ func (c *clientImpl) DeleteApi() api.DeleteApi { } return c.deleteApi } + +func (c *clientImpl) BucketsApi() api.BucketsApi { + c.lock.Lock() + defer c.lock.Unlock() + if c.bucketsApi == nil { + c.bucketsApi = api.NewBucketsApi(c.apiClient) + } + return c.bucketsApi +} diff --git a/client_e2e_test.go b/client_e2e_test.go index 327bfb7c..d96d3f02 100644 --- a/client_e2e_test.go +++ b/client_e2e_test.go @@ -8,6 +8,7 @@ import ( "context" "flag" "fmt" + "github.com/influxdata/influxdb-client-go/api" "github.com/influxdata/influxdb-client-go/domain" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -404,6 +405,7 @@ func TestDelete(t *testing.T) { if !e2e { t.Skip("e2e not enabled. Launch InfluxDB 2 on localhost and run test with -e2e") } + ctx := context.Background() client := NewClient("http://localhost:9999", authToken) writeApi := client.WriteApiBlocking("my-org", "my-bucket") queryApi := client.QueryApi("my-org") @@ -415,7 +417,7 @@ func TestDelete(t *testing.T) { map[string]string{"a": strconv.FormatInt(i%2, 10), "b": "static"}, map[string]interface{}{"f": f, "i": i}, tm) - err := writeApi.WritePoint(context.Background(), p) + err := writeApi.WritePoint(ctx, p) require.Nil(t, err, err) f += 1.2 tm = tm.Add(time.Minute) @@ -423,7 +425,7 @@ func TestDelete(t *testing.T) { return tm } countF := func(start, stop time.Time) int64 { - result, err := queryApi.Query(context.Background(), `from(bucket:"my-bucket")|> range(start: `+start.Format(time.RFC3339)+`, stop:`+stop.Format(time.RFC3339)+`) + result, err := queryApi.Query(ctx, `from(bucket:"my-bucket")|> range(start: `+start.Format(time.RFC3339)+`, stop:`+stop.Format(time.RFC3339)+`) |> filter(fn: (r) => r._measurement == "test" and r._field == "f") |> drop(columns: ["a", "b"]) |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") @@ -441,27 +443,289 @@ func TestDelete(t *testing.T) { assert.Equal(t, int64(100), countF(tmStart, tmEnd)) deleteApi := client.DeleteApi() - err := deleteApi.DeleteWithName(context.Background(), "my-org", "my-bucket", tmStart, tmEnd, "") + org, err := client.OrganizationsApi().FindOrganizationByName(ctx, "my-org") + require.Nil(t, err, err) + require.NotNil(t, org) + + bucket, err := client.BucketsApi().FindBucketByName(ctx, "my-bucket") + require.Nil(t, err, err) + require.NotNil(t, bucket) + + err = deleteApi.DeleteWithName(ctx, "my-org", "my-bucket", tmStart, tmEnd, "") require.Nil(t, err, err) assert.Equal(t, int64(0), countF(tmStart, tmEnd)) tmEnd = writeF(tmStart, 100) assert.Equal(t, int64(100), countF(tmStart, tmEnd)) - err = deleteApi.DeleteWithName(context.Background(), "my-org", "my-bucket", tmStart, tmEnd, "a=1") + err = deleteApi.DeleteWithId(ctx, *org.Id, *bucket.Id, tmStart, tmEnd, "a=1") require.Nil(t, err, err) assert.Equal(t, int64(50), countF(tmStart, tmEnd)) - err = deleteApi.DeleteWithName(context.Background(), "my-org", "my-bucket", tmStart.Add(50*time.Minute), tmEnd, "b=static") + err = deleteApi.Delete(ctx, org, bucket, tmStart.Add(50*time.Minute), tmEnd, "b=static") require.Nil(t, err, err) assert.Equal(t, int64(25), countF(tmStart, tmEnd)) - err = deleteApi.DeleteWithName(context.Background(), "org", "my-bucket", tmStart.Add(50*time.Minute), tmEnd, "b=static") + err = deleteApi.DeleteWithName(ctx, "org", "my-bucket", tmStart.Add(50*time.Minute), tmEnd, "b=static") require.NotNil(t, err, err) assert.True(t, strings.Contains(err.Error(), "not found")) - err = deleteApi.DeleteWithName(context.Background(), "my-org", "bucket", tmStart.Add(50*time.Minute), tmEnd, "b=static") + err = deleteApi.DeleteWithName(ctx, "my-org", "bucket", tmStart.Add(50*time.Minute), tmEnd, "b=static") require.NotNil(t, err, err) assert.True(t, strings.Contains(err.Error(), "not found")) +} + +func TestBuckets(t *testing.T) { + if !e2e { + t.Skip("e2e not enabled. Launch InfluxDB 2 on localhost and run test with -e2e") + } + ctx := context.Background() + client := NewClient("http://localhost:9999", authToken) + + bucketsApi := client.BucketsApi() + + buckets, err := bucketsApi.GetBuckets(ctx) + require.Nil(t, err, err) + require.NotNil(t, buckets) + //at least three buckets, my-bucket and two system buckets. + assert.True(t, len(*buckets) > 2) + + // test find existing bucket + bucket, err := bucketsApi.FindBucketByName(ctx, "my-bucket") + require.Nil(t, err, err) + require.NotNil(t, bucket) + assert.Equal(t, "my-bucket", bucket.Name) + // test find non-existing bucket, bug - returns system buckets + //bucket, err = bucketsApi.FindBucketByName(ctx, "not existing bucket") + //require.NotNil(t, err) + //require.Nil(t, bucket) + + // create organizatiton for bucket + org, err := client.OrganizationsApi().CreateOrganizationWithName(ctx, "bucket-org") + require.Nil(t, err) + require.NotNil(t, org) + + // collect all buckets including system ones created for new organization + buckets, err = bucketsApi.GetBuckets(ctx) + require.Nil(t, err, err) + //store #all buckets before creating new ones + bucketsNum := len(*buckets) + + name := "bucket-x" + b, err := bucketsApi.CreateBucketWithName(ctx, org, name, domain.RetentionRule{EverySeconds: 3600 * 1}, domain.RetentionRule{EverySeconds: 3600 * 24}) + require.Nil(t, err, err) + require.NotNil(t, b) + assert.Equal(t, name, b.Name) + assert.Len(t, b.RetentionRules, 1) + + // Test update + desc := "bucket description" + b.Description = &desc + b.RetentionRules = []domain.RetentionRule{{EverySeconds: 60}} + b, err = bucketsApi.UpdateBucket(ctx, b) + require.Nil(t, err, err) + require.NotNil(t, b) + assert.Equal(t, name, b.Name) + assert.Equal(t, desc, *b.Description) + assert.Len(t, b.RetentionRules, 1) + + // Test owners + userOwner, err := client.UsersApi().CreateUserWithName(ctx, "bucket-owner") + require.Nil(t, err, err) + require.NotNil(t, userOwner) + + owners, err := bucketsApi.GetOwners(ctx, b) + require.Nil(t, err, err) + require.NotNil(t, owners) + assert.Len(t, *owners, 0) + + owner, err := bucketsApi.AddOwner(ctx, b, userOwner) + require.Nil(t, err, err) + require.NotNil(t, owner) + assert.Equal(t, *userOwner.Id, *owner.Id) + + owners, err = bucketsApi.GetOwners(ctx, b) + require.Nil(t, err, err) + require.NotNil(t, owners) + assert.Len(t, *owners, 1) + + err = bucketsApi.RemoveOwnerWithId(ctx, *b.Id, *(&(*owners)[0]).Id) + require.Nil(t, err, err) + + owners, err = bucketsApi.GetOwners(ctx, b) + require.Nil(t, err, err) + require.NotNil(t, owners) + assert.Len(t, *owners, 0) + + //test failures + _, err = bucketsApi.AddOwnerWithId(ctx, "000000000000000", *userOwner.Id) + assert.NotNil(t, err) + + _, err = bucketsApi.AddOwnerWithId(ctx, *b.Id, "000000000000000") + assert.NotNil(t, err) + + _, err = bucketsApi.GetOwnersWithId(ctx, "000000000000000") + assert.NotNil(t, err) + + err = bucketsApi.RemoveOwnerWithId(ctx, *b.Id, "000000000000000") + assert.NotNil(t, err) + + err = bucketsApi.RemoveOwnerWithId(ctx, "000000000000000", *userOwner.Id) + assert.NotNil(t, err) + // No logs returned https://github.com/influxdata/influxdb/issues/18048 + //logs, err := bucketsApi.GetLogs(ctx, b) + //require.Nil(t, err, err) + //require.NotNil(t, logs) + //assert.Len(t, *logs, 0) + + // Test members + userMember, err := client.UsersApi().CreateUserWithName(ctx, "bucket-member") + require.Nil(t, err, err) + require.NotNil(t, userMember) + + members, err := bucketsApi.GetMembers(ctx, b) + require.Nil(t, err, err) + require.NotNil(t, members) + assert.Len(t, *members, 0) + + member, err := bucketsApi.AddMember(ctx, b, userMember) + require.Nil(t, err, err) + require.NotNil(t, member) + assert.Equal(t, *userMember.Id, *member.Id) + + members, err = bucketsApi.GetMembers(ctx, b) + require.Nil(t, err, err) + require.NotNil(t, members) + assert.Len(t, *members, 1) + + err = bucketsApi.RemoveMemberWithId(ctx, *b.Id, *(&(*members)[0]).Id) + require.Nil(t, err, err) + + members, err = bucketsApi.GetMembers(ctx, b) + require.Nil(t, err, err) + require.NotNil(t, members) + assert.Len(t, *members, 0) + + //test failures + _, err = bucketsApi.AddMemberWithId(ctx, "000000000000000", *userMember.Id) + assert.NotNil(t, err) + + _, err = bucketsApi.AddMemberWithId(ctx, *b.Id, "000000000000000") + assert.NotNil(t, err) + + _, err = bucketsApi.GetMembersWithId(ctx, "000000000000000") + assert.NotNil(t, err) + + err = bucketsApi.RemoveMemberWithId(ctx, *b.Id, "000000000000000") + assert.NotNil(t, err) + + err = bucketsApi.RemoveMemberWithId(ctx, "000000000000000", *userMember.Id) + assert.NotNil(t, err) + + err = bucketsApi.DeleteBucketWithId(ctx, *b.Id) + assert.Nil(t, err, err) + + err = client.UsersApi().DeleteUser(ctx, userOwner) + assert.Nil(t, err, err) + + err = client.UsersApi().DeleteUser(ctx, userMember) + assert.Nil(t, err, err) + + //test failures + x, err := bucketsApi.FindBucketById(ctx, *b.Id) + assert.NotNil(t, err) + assert.Nil(t, x) + + x, err = bucketsApi.UpdateBucket(ctx, b) + assert.NotNil(t, err) + + b.OrgID = b.Id + x, err = bucketsApi.CreateBucket(ctx, b) + assert.NotNil(t, err) + + // create bucket by object + b = &domain.Bucket{ + Description: &desc, + Name: name, + OrgID: org.Id, + RetentionRules: []domain.RetentionRule{{EverySeconds: 3600}}, + } + + b, err = bucketsApi.CreateBucket(ctx, b) + require.Nil(t, err, err) + require.NotNil(t, b) + assert.Equal(t, name, b.Name) + assert.Equal(t, *org.Id, *b.OrgID) + assert.Equal(t, desc, *b.Description) + assert.Len(t, b.RetentionRules, 1) + + // fail duplicit name + _, err = bucketsApi.CreateBucketWithName(ctx, org, b.Name) + assert.NotNil(t, err) + + // fail org not found + _, err = bucketsApi.CreateBucketWithId(ctx, *b.Id, b.Name) + assert.NotNil(t, err) + + err = bucketsApi.DeleteBucketWithId(ctx, *b.Id) + assert.Nil(t, err, err) + + err = bucketsApi.DeleteBucketWithId(ctx, *b.Id) + assert.NotNil(t, err) + + // create new buckets inside org + for i := 0; i < 30; i++ { + name := fmt.Sprintf("bucket-%03d", i) + b, err := bucketsApi.CreateBucketWithName(ctx, org, name) + require.Nil(t, err, err) + require.NotNil(t, b) + assert.Equal(t, name, b.Name) + } + + // test paging, 1st page + buckets, err = bucketsApi.GetBuckets(ctx) + require.Nil(t, err, err) + require.NotNil(t, buckets) + assert.Len(t, *buckets, 20) + // test paging, 2nd, last page + buckets, err = bucketsApi.GetBuckets(ctx, api.PagingWithOffset(20)) + require.Nil(t, err, err) + require.NotNil(t, buckets) + //+2 is a bug, when using offset>4 there are returned also system buckets + assert.Len(t, *buckets, 10+2+bucketsNum) + // test paging with increase limit to cover all buckets + buckets, err = bucketsApi.GetBuckets(ctx, api.PagingWithLimit(100)) + require.Nil(t, err, err) + require.NotNil(t, buckets) + assert.Len(t, *buckets, 30+bucketsNum) + // test filtering buckets by org id + buckets, err = bucketsApi.FindBucketsByOrgId(ctx, *org.Id, api.PagingWithLimit(100)) + require.Nil(t, err, err) + require.NotNil(t, buckets) + assert.Len(t, *buckets, 30+2) + // test filtering buckets by org name + buckets, err = bucketsApi.FindBucketsByOrgName(ctx, org.Name, api.PagingWithLimit(100)) + require.Nil(t, err, err) + require.NotNil(t, buckets) + assert.Len(t, *buckets, 30+2) + // delete buckete + for _, b := range *buckets { + if strings.HasPrefix(b.Name, "bucket-") { + err = bucketsApi.DeleteBucket(ctx, &b) + assert.Nil(t, err, err) + } + } + // check all created buckets deleted + buckets, err = bucketsApi.FindBucketsByOrgName(ctx, org.Name, api.PagingWithLimit(100)) + require.Nil(t, err, err) + require.NotNil(t, buckets) + assert.Len(t, *buckets, 2) + + err = client.OrganizationsApi().DeleteOrganization(ctx, org) + assert.Nil(t, err, err) + + // should fail with org not found + buckets, err = bucketsApi.FindBucketsByOrgName(ctx, org.Name, api.PagingWithLimit(100)) + require.NotNil(t, err) }