├── .circleci └── config.yml ├── .github ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.yml │ └── feature_request.md ├── pull_request_template.md └── workflows │ └── semantic.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── api ├── authorizations.go ├── authorizations_e2e_test.go ├── buckets.go ├── buckets_e2e_test.go ├── checks_test.go ├── data_to_point.go ├── data_to_point_test.go ├── delete.go ├── delete_e2e_test.go ├── doc.go ├── examples_test.go ├── http │ ├── error.go │ ├── error_test.go │ ├── options.go │ ├── options_test.go │ ├── service.go │ └── service_test.go ├── labels.go ├── labels_e2e_test.go ├── organizations.go ├── organizations_e2e_test.go ├── paging.go ├── paging_test.go ├── query.go ├── query │ ├── table.go │ └── table_test.go ├── query_test.go ├── reflection.go ├── tasks.go ├── tasks_e2e_test.go ├── users.go ├── users_e2e_test.go ├── write.go ├── write │ ├── ext.go │ ├── options.go │ ├── options_test.go │ ├── point.go │ └── point_test.go ├── writeAPIBlocking.go ├── writeAPIBlocking_test.go └── write_test.go ├── client.go ├── client_e2e_test.go ├── client_test.go ├── compatibility.go ├── domain ├── Readme.md ├── checks.client.go ├── checks.types.go ├── client.gen.go ├── oss.yml ├── templates │ ├── client-with-responses.tmpl │ ├── client.tmpl │ ├── imports.tmpl │ ├── param-types.tmpl │ └── request-bodies.tmpl └── types.gen.go ├── example_ua_set_test.go ├── examples_test.go ├── go.mod ├── go.sum ├── internal ├── examples │ └── fakeclient.go ├── gzip │ ├── gzip.go │ └── gzip_test.go ├── http │ └── userAgent.go ├── log │ ├── logger.go │ └── logger_test.go ├── test │ ├── generators.go │ └── http_service.go └── write │ ├── queue.go │ ├── queue_test.go │ ├── service.go │ └── service_test.go ├── log ├── logger.go └── logger_test.go ├── options.go ├── options_test.go ├── scripts ├── influxdb-restart.sh └── influxdb.conf └── version.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Golang CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-go/ for more details 4 | version: 2.1 5 | jobs: 6 | build: 7 | machine: 8 | image: ubuntu-2204:current 9 | environment: 10 | ENV: CI 11 | GO111MODULE: "on" 12 | INFLUXDB2_URL: "http://localhost:8086" 13 | INFLUXDB_URL: "http://localhost:8087" 14 | INFLUXDB2_ONBOARDING_URL: "http://localhost:8089" 15 | steps: 16 | - checkout 17 | - run: 18 | name: "Create a temp directory for artifacts" 19 | command: | 20 | mkdir -p /tmp/artifacts 21 | mkdir -p /tmp/test-results 22 | - run: sudo rm -rf /usr/local/go 23 | - run: wget https://golang.org/dl/go1.22.2.linux-amd64.tar.gz -O /tmp/go.tgz 24 | - run: sudo tar -C /usr/local -xzf /tmp/go.tgz 25 | - run: go version 26 | - run: go get -v -t -d ./... 27 | - run: make lint 28 | - run: 29 | name: "Start InfluxDB service" 30 | command: make server 31 | - run: 32 | command: make coverage 33 | - run: 34 | name: Collecting coverage reports 35 | command: | 36 | curl -Os https://uploader.codecov.io/latest/linux/codecov 37 | curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM 38 | curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM.sig 39 | curl -s https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --no-default-keyring --keyring trustedkeys.gpg --import 40 | gpgv codecov.SHA256SUM.sig codecov.SHA256SUM 41 | shasum -a 256 -c codecov.SHA256SUM 42 | chmod +x ./codecov 43 | ./codecov 44 | - store_artifacts: 45 | path: /tmp/artifacts 46 | - store_artifacts: 47 | path: /tmp/test-results 48 | destination: raw-test-output 49 | - store_test_results: 50 | path: /tmp/test-results 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a bug report to help us improve 3 | labels: ["bug"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking time to fill out this bug report! We reserve this repository issues for bugs with reproducible problems. 9 | Please redirect any questions about the Go client usage to our [Community Slack](https://influxdata.com/slack) or [Community Page](https://community.influxdata.com/) we have a lot of talented community members there who could help answer your question more quickly. 10 | 11 | * Please add a :+1: or comment on a similar existing bug report instead of opening a new one. 12 | * Please check whether the bug can be reproduced with the latest release. 13 | - type: textarea 14 | id: specifications 15 | attributes: 16 | label: Specifications 17 | description: Describe the steps to reproduce the bug. 18 | value: | 19 | * Client Version: 20 | * InfluxDB Version: 21 | * Platform: 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: reproduce 26 | attributes: 27 | label: Steps to reproduce 28 | description: Describe the steps to reproduce the bug. 29 | value: | 30 | 1. 31 | 2. 32 | 3. 33 | ... 34 | validations: 35 | required: true 36 | - type: textarea 37 | id: expected-behavior 38 | attributes: 39 | label: Expected behavior 40 | description: Describe what you expected to happen when you performed the above steps. 41 | validations: 42 | required: true 43 | - type: textarea 44 | id: actual-behavior 45 | attributes: 46 | label: Actual behavior 47 | description: Describe what actually happened when you performed the above steps. 48 | validations: 49 | required: true 50 | - type: textarea 51 | id: additional-info 52 | attributes: 53 | label: Additional info 54 | description: Include gist of relevant config, logs, etc. 55 | validations: 56 | required: false 57 | 58 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Opening a feature request kicks off a discussion 4 | --- 5 | 6 | 14 | 15 | __Proposal:__ 16 | Short summary of the feature. 17 | 18 | __Current behavior:__ 19 | Describe what currently happens. 20 | 21 | __Desired behavior:__ 22 | Describe what you want. 23 | 24 | __Alternatives considered:__ 25 | Describe other solutions or features you considered. 26 | 27 | __Use case:__ 28 | Why is this important (helps with prioritizing requests)? 29 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Closes # 2 | 3 | ## Proposed Changes 4 | 5 | _Briefly describe your proposed changes:_ 6 | 7 | ## Checklist 8 | 9 | 10 | 11 | - [ ] CHANGELOG.md updated 12 | - [ ] Rebased/mergeable 13 | - [ ] A test has been added if appropriate 14 | - [ ] Tests pass 15 | - [ ] Commit messages are in [semantic format](https://seesparkbox.com/foundry/semantic_commit_messages) 16 | - [ ] Sign [CLA](https://www.influxdata.com/legal/cla/) (if not already signed) 17 | -------------------------------------------------------------------------------- /.github/workflows/semantic.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Semantic PR and Commit Messages" 3 | 4 | on: 5 | pull_request: 6 | types: [opened, reopened, synchronize, edited] 7 | branches: 8 | - master 9 | 10 | jobs: 11 | semantic: 12 | uses: influxdata/validate-semantic-github-messages/.github/workflows/semantic.yml@main 13 | with: 14 | CHECK_PR_TITLE_OR_ONE_COMMIT: true 15 | 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.bat 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | # IntelliJ IDEA 19 | .IDEA 20 | *.IML 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2021 Influxdata, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | artifacts_path := /tmp/artifacts 2 | 3 | help: 4 | @echo 'Targets:' 5 | @echo ' all - runs lint, server, coverage' 6 | @echo ' lint - runs code style checks' 7 | @echo ' shorttest - runs unit and integration tests' 8 | @echo ' test - runs all tests, including e2e tests - requires running influxdb 2 server' 9 | @echo ' coverage - runs all tests, including e2e tests, with coverage report - requires running influxdb 2 server' 10 | @echo ' server - prepares InfluxDB in docker environment' 11 | 12 | lint: 13 | go vet ./... 14 | go install honnef.co/go/tools/cmd/staticcheck@latest && staticcheck --checks='all' --tags e2e ./... 15 | go install golang.org/x/lint/golint@latest && golint ./... 16 | 17 | shorttest: 18 | go test -race -v -count=1 ./... 19 | 20 | test: 21 | go test -race -v -count=1 --tags e2e ./... 22 | 23 | coverage: 24 | go install gotest.tools/gotestsum@latest && gotestsum --junitfile /tmp/test-results/unit-tests.xml -- -race -coverprofile=coverage.txt -covermode=atomic -coverpkg '.,./api/...,./internal/.../,./log/...' -tags e2e ./... 25 | if test ! -e $(artifacts_path); then mkdir $(artifacts_path); fi 26 | go tool cover -html=coverage.txt -o $(artifacts_path)/coverage.html 27 | 28 | server: 29 | ./scripts/influxdb-restart.sh 30 | 31 | all: lint server coverage 32 | -------------------------------------------------------------------------------- /api/authorizations.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package api 6 | 7 | import ( 8 | "context" 9 | 10 | "github.com/influxdata/influxdb-client-go/v2/domain" 11 | ) 12 | 13 | // AuthorizationsAPI provides methods for organizing Authorization in a InfluxDB server 14 | type AuthorizationsAPI interface { 15 | // GetAuthorizations returns all authorizations 16 | GetAuthorizations(ctx context.Context) (*[]domain.Authorization, error) 17 | // FindAuthorizationsByUserName returns all authorizations for given userName 18 | FindAuthorizationsByUserName(ctx context.Context, userName string) (*[]domain.Authorization, error) 19 | // FindAuthorizationsByUserID returns all authorizations for given userID 20 | FindAuthorizationsByUserID(ctx context.Context, userID string) (*[]domain.Authorization, error) 21 | // FindAuthorizationsByOrgName returns all authorizations for given organization name 22 | FindAuthorizationsByOrgName(ctx context.Context, orgName string) (*[]domain.Authorization, error) 23 | // FindAuthorizationsByOrgID returns all authorizations for given organization id 24 | FindAuthorizationsByOrgID(ctx context.Context, orgID string) (*[]domain.Authorization, error) 25 | // CreateAuthorization creates new authorization 26 | CreateAuthorization(ctx context.Context, authorization *domain.Authorization) (*domain.Authorization, error) 27 | // CreateAuthorizationWithOrgID creates new authorization with given permissions scoped to given orgID 28 | CreateAuthorizationWithOrgID(ctx context.Context, orgID string, permissions []domain.Permission) (*domain.Authorization, error) 29 | // UpdateAuthorizationStatus updates status of authorization 30 | UpdateAuthorizationStatus(ctx context.Context, authorization *domain.Authorization, status domain.AuthorizationUpdateRequestStatus) (*domain.Authorization, error) 31 | // UpdateAuthorizationStatusWithID updates status of authorization with authID 32 | UpdateAuthorizationStatusWithID(ctx context.Context, authID string, status domain.AuthorizationUpdateRequestStatus) (*domain.Authorization, error) 33 | // DeleteAuthorization deletes authorization 34 | DeleteAuthorization(ctx context.Context, authorization *domain.Authorization) error 35 | // DeleteAuthorization deletes authorization with authID 36 | DeleteAuthorizationWithID(ctx context.Context, authID string) error 37 | } 38 | 39 | // authorizationsAPI implements AuthorizationsAPI 40 | type authorizationsAPI struct { 41 | apiClient *domain.Client 42 | } 43 | 44 | // NewAuthorizationsAPI creates new instance of AuthorizationsAPI 45 | func NewAuthorizationsAPI(apiClient *domain.Client) AuthorizationsAPI { 46 | return &authorizationsAPI{ 47 | apiClient: apiClient, 48 | } 49 | } 50 | 51 | func (a *authorizationsAPI) GetAuthorizations(ctx context.Context) (*[]domain.Authorization, error) { 52 | authQuery := &domain.GetAuthorizationsParams{} 53 | return a.listAuthorizations(ctx, authQuery) 54 | } 55 | 56 | func (a *authorizationsAPI) FindAuthorizationsByUserName(ctx context.Context, userName string) (*[]domain.Authorization, error) { 57 | authQuery := &domain.GetAuthorizationsParams{User: &userName} 58 | return a.listAuthorizations(ctx, authQuery) 59 | } 60 | 61 | func (a *authorizationsAPI) FindAuthorizationsByUserID(ctx context.Context, userID string) (*[]domain.Authorization, error) { 62 | authQuery := &domain.GetAuthorizationsParams{UserID: &userID} 63 | return a.listAuthorizations(ctx, authQuery) 64 | } 65 | 66 | func (a *authorizationsAPI) FindAuthorizationsByOrgName(ctx context.Context, orgName string) (*[]domain.Authorization, error) { 67 | authQuery := &domain.GetAuthorizationsParams{Org: &orgName} 68 | return a.listAuthorizations(ctx, authQuery) 69 | } 70 | 71 | func (a *authorizationsAPI) FindAuthorizationsByOrgID(ctx context.Context, orgID string) (*[]domain.Authorization, error) { 72 | authQuery := &domain.GetAuthorizationsParams{OrgID: &orgID} 73 | return a.listAuthorizations(ctx, authQuery) 74 | } 75 | 76 | func (a *authorizationsAPI) listAuthorizations(ctx context.Context, query *domain.GetAuthorizationsParams) (*[]domain.Authorization, error) { 77 | response, err := a.apiClient.GetAuthorizations(ctx, query) 78 | if err != nil { 79 | return nil, err 80 | } 81 | return response.Authorizations, nil 82 | } 83 | 84 | func (a *authorizationsAPI) CreateAuthorization(ctx context.Context, authorization *domain.Authorization) (*domain.Authorization, error) { 85 | params := &domain.PostAuthorizationsAllParams{ 86 | Body: domain.PostAuthorizationsJSONRequestBody{ 87 | AuthorizationUpdateRequest: authorization.AuthorizationUpdateRequest, 88 | OrgID: authorization.OrgID, 89 | Permissions: authorization.Permissions, 90 | UserID: authorization.UserID, 91 | }, 92 | } 93 | return a.apiClient.PostAuthorizations(ctx, params) 94 | } 95 | 96 | func (a *authorizationsAPI) CreateAuthorizationWithOrgID(ctx context.Context, orgID string, permissions []domain.Permission) (*domain.Authorization, error) { 97 | status := domain.AuthorizationUpdateRequestStatusActive 98 | auth := &domain.Authorization{ 99 | AuthorizationUpdateRequest: domain.AuthorizationUpdateRequest{Status: &status}, 100 | OrgID: &orgID, 101 | Permissions: &permissions, 102 | } 103 | return a.CreateAuthorization(ctx, auth) 104 | } 105 | 106 | func (a *authorizationsAPI) UpdateAuthorizationStatusWithID(ctx context.Context, authID string, status domain.AuthorizationUpdateRequestStatus) (*domain.Authorization, error) { 107 | params := &domain.PatchAuthorizationsIDAllParams{ 108 | Body: domain.PatchAuthorizationsIDJSONRequestBody{Status: &status}, 109 | AuthID: authID, 110 | } 111 | return a.apiClient.PatchAuthorizationsID(ctx, params) 112 | } 113 | 114 | func (a *authorizationsAPI) UpdateAuthorizationStatus(ctx context.Context, authorization *domain.Authorization, status domain.AuthorizationUpdateRequestStatus) (*domain.Authorization, error) { 115 | return a.UpdateAuthorizationStatusWithID(ctx, *authorization.Id, status) 116 | } 117 | 118 | func (a *authorizationsAPI) DeleteAuthorization(ctx context.Context, authorization *domain.Authorization) error { 119 | return a.DeleteAuthorizationWithID(ctx, *authorization.Id) 120 | } 121 | 122 | func (a *authorizationsAPI) DeleteAuthorizationWithID(ctx context.Context, authID string) error { 123 | params := &domain.DeleteAuthorizationsIDAllParams{ 124 | AuthID: authID, 125 | } 126 | return a.apiClient.DeleteAuthorizationsID(ctx, params) 127 | } 128 | -------------------------------------------------------------------------------- /api/authorizations_e2e_test.go: -------------------------------------------------------------------------------- 1 | // +build e2e 2 | 3 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 4 | // Use of this source code is governed by MIT 5 | // license that can be found in the LICENSE file. 6 | 7 | package api_test 8 | 9 | import ( 10 | "context" 11 | "os" 12 | "testing" 13 | 14 | influxdb2 "github.com/influxdata/influxdb-client-go/v2" 15 | "github.com/influxdata/influxdb-client-go/v2/domain" 16 | "github.com/stretchr/testify/assert" 17 | "github.com/stretchr/testify/require" 18 | ) 19 | 20 | var authToken string 21 | var serverURL string 22 | 23 | func getEnvValue(key, defVal string) string { 24 | if val, ok := os.LookupEnv(key); ok { 25 | return val 26 | } else { 27 | return defVal 28 | } 29 | } 30 | 31 | func init() { 32 | authToken = getEnvValue("INFLUXDB2_TOKEN", "my-token") 33 | serverURL = getEnvValue("INFLUXDB2_URL", "http://localhost:8086") 34 | } 35 | 36 | func TestAuthorizationsAPI(t *testing.T) { 37 | client := influxdb2.NewClient(serverURL, authToken) 38 | authAPI := client.AuthorizationsAPI() 39 | listRes, err := authAPI.GetAuthorizations(context.Background()) 40 | require.Nil(t, err) 41 | require.NotNil(t, listRes) 42 | assert.Len(t, *listRes, 1) 43 | 44 | orgName := "my-org" 45 | org, err := client.OrganizationsAPI().FindOrganizationByName(context.Background(), orgName) 46 | require.Nil(t, err) 47 | require.NotNil(t, org) 48 | assert.Equal(t, orgName, org.Name) 49 | 50 | permission := &domain.Permission{ 51 | Action: domain.PermissionActionWrite, 52 | Resource: domain.Resource{ 53 | Type: domain.ResourceTypeBuckets, 54 | }, 55 | } 56 | permissions := []domain.Permission{*permission} 57 | 58 | auth, err := authAPI.CreateAuthorizationWithOrgID(context.Background(), *org.Id, permissions) 59 | require.Nil(t, err) 60 | require.NotNil(t, auth) 61 | assert.Equal(t, domain.AuthorizationUpdateRequestStatusActive, *auth.Status, *auth.Status) 62 | 63 | listRes, err = authAPI.GetAuthorizations(context.Background()) 64 | require.Nil(t, err) 65 | require.NotNil(t, listRes) 66 | assert.Len(t, *listRes, 2) 67 | 68 | listRes, err = authAPI.FindAuthorizationsByUserName(context.Background(), "my-user") 69 | require.Nil(t, err) 70 | require.NotNil(t, listRes) 71 | assert.Len(t, *listRes, 2) 72 | 73 | listRes, err = authAPI.FindAuthorizationsByOrgID(context.Background(), *org.Id) 74 | require.Nil(t, err) 75 | require.NotNil(t, listRes) 76 | assert.Len(t, *listRes, 2) 77 | 78 | listRes, err = authAPI.FindAuthorizationsByOrgName(context.Background(), "my-org") 79 | require.Nil(t, err) 80 | require.NotNil(t, listRes) 81 | assert.Len(t, *listRes, 2) 82 | 83 | user, err := client.UsersAPI().FindUserByName(context.Background(), "my-user") 84 | require.Nil(t, err) 85 | require.NotNil(t, user) 86 | 87 | listRes, err = authAPI.FindAuthorizationsByUserID(context.Background(), *user.Id) 88 | require.Nil(t, err) 89 | require.NotNil(t, listRes) 90 | assert.Len(t, *listRes, 2) 91 | 92 | listRes, err = authAPI.FindAuthorizationsByOrgName(context.Background(), "not-existent-org") 93 | require.Nil(t, listRes) 94 | require.NotNil(t, err) 95 | //assert.Len(t, *listRes, 0) 96 | 97 | auth, err = authAPI.UpdateAuthorizationStatus(context.Background(), auth, domain.AuthorizationUpdateRequestStatusInactive) 98 | require.Nil(t, err) 99 | require.NotNil(t, auth) 100 | assert.Equal(t, domain.AuthorizationUpdateRequestStatusInactive, *auth.Status, *auth.Status) 101 | 102 | listRes, err = authAPI.GetAuthorizations(context.Background()) 103 | require.Nil(t, err) 104 | require.NotNil(t, listRes) 105 | assert.Len(t, *listRes, 2) 106 | 107 | err = authAPI.DeleteAuthorization(context.Background(), auth) 108 | require.Nil(t, err) 109 | 110 | listRes, err = authAPI.GetAuthorizations(context.Background()) 111 | require.Nil(t, err) 112 | require.NotNil(t, listRes) 113 | assert.Len(t, *listRes, 1) 114 | 115 | } 116 | 117 | func TestAuthorizationsAPI_Failing(t *testing.T) { 118 | client := influxdb2.NewClient(serverURL, authToken) 119 | authAPI := client.AuthorizationsAPI() 120 | invalidID := "xcv" 121 | notExistentID := "100000000000000" 122 | 123 | listRes, err := authAPI.FindAuthorizationsByUserName(context.Background(), "invalid-user") 124 | assert.NotNil(t, err) 125 | assert.Nil(t, listRes) 126 | 127 | listRes, err = authAPI.FindAuthorizationsByUserID(context.Background(), invalidID) 128 | assert.NotNil(t, err) 129 | assert.Nil(t, listRes) 130 | 131 | listRes, err = authAPI.FindAuthorizationsByOrgID(context.Background(), notExistentID) 132 | assert.NotNil(t, err) 133 | assert.Nil(t, listRes) 134 | 135 | listRes, err = authAPI.FindAuthorizationsByOrgName(context.Background(), "not-existing-org") 136 | assert.NotNil(t, err) 137 | assert.Nil(t, listRes) 138 | 139 | org, err := client.OrganizationsAPI().FindOrganizationByName(context.Background(), "my-org") 140 | require.Nil(t, err) 141 | require.NotNil(t, org) 142 | 143 | auth, err := authAPI.CreateAuthorizationWithOrgID(context.Background(), *org.Id, nil) 144 | assert.NotNil(t, err) 145 | assert.Nil(t, auth) 146 | 147 | permission := &domain.Permission{ 148 | Action: domain.PermissionActionWrite, 149 | Resource: domain.Resource{ 150 | Type: domain.ResourceTypeBuckets, 151 | }, 152 | } 153 | permissions := []domain.Permission{*permission} 154 | 155 | auth, err = authAPI.CreateAuthorizationWithOrgID(context.Background(), notExistentID, permissions) 156 | assert.NotNil(t, err) 157 | assert.Nil(t, auth) 158 | 159 | auth, err = authAPI.UpdateAuthorizationStatusWithID(context.Background(), notExistentID, domain.AuthorizationUpdateRequestStatusInactive) 160 | assert.NotNil(t, err) 161 | assert.Nil(t, auth) 162 | 163 | err = authAPI.DeleteAuthorizationWithID(context.Background(), notExistentID) 164 | assert.NotNil(t, err) 165 | } 166 | 167 | func TestAuthorizationsAPI_requestFailing(t *testing.T) { 168 | 169 | client := influxdb2.NewClientWithOptions("htp://localhost:9910", authToken, influxdb2.DefaultOptions().SetHTTPRequestTimeout(1)) 170 | authAPI := client.AuthorizationsAPI() 171 | 172 | listRes, err := authAPI.GetAuthorizations(context.Background()) 173 | assert.NotNil(t, err) 174 | assert.Nil(t, listRes) 175 | 176 | permission := &domain.Permission{ 177 | Action: domain.PermissionActionWrite, 178 | Resource: domain.Resource{ 179 | Type: domain.ResourceTypeBuckets, 180 | }, 181 | } 182 | permissions := []domain.Permission{*permission} 183 | 184 | auth, err := authAPI.CreateAuthorizationWithOrgID(context.Background(), "1000000000000000000", permissions) 185 | assert.NotNil(t, err) 186 | assert.Nil(t, auth) 187 | 188 | auth, err = authAPI.UpdateAuthorizationStatusWithID(context.Background(), "1000000000000000000", domain.AuthorizationUpdateRequestStatusInactive) 189 | assert.NotNil(t, err) 190 | assert.Nil(t, auth) 191 | 192 | err = authAPI.DeleteAuthorizationWithID(context.Background(), "1000000000000000000") 193 | assert.NotNil(t, err) 194 | } 195 | -------------------------------------------------------------------------------- /api/data_to_point.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "time" 8 | 9 | "github.com/influxdata/influxdb-client-go/v2/api/write" 10 | ) 11 | 12 | // DataToPoint converts custom point structures into a Point. 13 | // Each visible field of the point on input must be annotated with 14 | // 'lp' prefix and values measurement,tag, field or timestamp. 15 | // Valid point must contain measurement and at least one field. 16 | // 17 | // A field with timestamp must be of a type time.Time 18 | // 19 | // type TemperatureSensor struct { 20 | // Measurement string `lp:"measurement"` 21 | // Sensor string `lp:"tag,sensor"` 22 | // ID string `lp:"tag,device_id"` 23 | // Temp float64 `lp:"field,temperature"` 24 | // Hum int `lp:"field,humidity"` 25 | // Time time.Time `lp:"timestamp,temperature"` 26 | // Description string `lp:"-"` 27 | // } 28 | func DataToPoint(x interface{}) (*write.Point, error) { 29 | t := reflect.TypeOf(x) 30 | v := reflect.ValueOf(x) 31 | if t.Kind() == reflect.Ptr { 32 | t = t.Elem() 33 | v = v.Elem() 34 | } 35 | if t.Kind() != reflect.Struct { 36 | return nil, fmt.Errorf("cannot use %v as point", t) 37 | } 38 | fields := reflect.VisibleFields(t) 39 | 40 | var measurement = "" 41 | var lpTags = make(map[string]string) 42 | var lpFields = make(map[string]interface{}) 43 | var lpTime time.Time 44 | 45 | for _, f := range fields { 46 | name := f.Name 47 | if tag, ok := f.Tag.Lookup("lp"); ok { 48 | if tag == "-" { 49 | continue 50 | } 51 | parts := strings.Split(tag, ",") 52 | if len(parts) > 2 { 53 | return nil, fmt.Errorf("multiple tag attributes are not supported") 54 | } 55 | typ := parts[0] 56 | if len(parts) == 2 { 57 | name = parts[1] 58 | } 59 | t := getFieldType(v.FieldByIndex(f.Index)) 60 | if !validFieldType(t) { 61 | return nil, fmt.Errorf("cannot use field '%s' of type '%v' as to create a point", f.Name, t) 62 | } 63 | switch typ { 64 | case "measurement": 65 | if measurement != "" { 66 | return nil, fmt.Errorf("multiple measurement fields") 67 | } 68 | measurement = v.FieldByIndex(f.Index).String() 69 | case "tag": 70 | if name == "" { 71 | return nil, fmt.Errorf("cannot use field '%s': invalid lp tag name \"\"", f.Name) 72 | } 73 | lpTags[name] = v.FieldByIndex(f.Index).String() 74 | case "field": 75 | if name == "" { 76 | return nil, fmt.Errorf("cannot use field '%s': invalid lp field name \"\"", f.Name) 77 | } 78 | lpFields[name] = v.FieldByIndex(f.Index).Interface() 79 | case "timestamp": 80 | if f.Type != timeType { 81 | return nil, fmt.Errorf("cannot use field '%s' as a timestamp", f.Name) 82 | } 83 | lpTime = v.FieldByIndex(f.Index).Interface().(time.Time) 84 | default: 85 | return nil, fmt.Errorf("invalid tag %s", typ) 86 | } 87 | } 88 | } 89 | if measurement == "" { 90 | return nil, fmt.Errorf("no struct field with tag 'measurement'") 91 | } 92 | if len(lpFields) == 0 { 93 | return nil, fmt.Errorf("no struct field with tag 'field'") 94 | } 95 | return write.NewPoint(measurement, lpTags, lpFields, lpTime), nil 96 | } 97 | -------------------------------------------------------------------------------- /api/data_to_point_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | "time" 8 | 9 | "github.com/influxdata/influxdb-client-go/v2/api/write" 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | 13 | lp "github.com/influxdata/line-protocol" 14 | ) 15 | 16 | func TestDataToPoint(t *testing.T) { 17 | pointToLine := func(point *write.Point) string { 18 | var buffer bytes.Buffer 19 | e := lp.NewEncoder(&buffer) 20 | e.SetFieldTypeSupport(lp.UintSupport) 21 | e.FailOnFieldErr(true) 22 | _, err := e.Encode(point) 23 | if err != nil { 24 | panic(err) 25 | } 26 | return buffer.String() 27 | } 28 | now := time.Now() 29 | tests := []struct { 30 | name string 31 | s interface{} 32 | line string 33 | error string 34 | }{{ 35 | name: "test normal structure", 36 | s: struct { 37 | Measurement string `lp:"measurement"` 38 | Sensor string `lp:"tag,sensor"` 39 | ID string `lp:"tag,device_id"` 40 | Temp float64 `lp:"field,temperature"` 41 | Hum int `lp:"field,humidity"` 42 | Time time.Time `lp:"timestamp"` 43 | Description string `lp:"-"` 44 | }{ 45 | "air", 46 | "SHT31", 47 | "10", 48 | 23.5, 49 | 55, 50 | now, 51 | "Room temp", 52 | }, 53 | line: fmt.Sprintf("air,device_id=10,sensor=SHT31 humidity=55i,temperature=23.5 %d\n", now.UnixNano()), 54 | }, 55 | { 56 | name: "test pointer to normal structure", 57 | s: &struct { 58 | Measurement string `lp:"measurement"` 59 | Sensor string `lp:"tag,sensor"` 60 | ID string `lp:"tag,device_id"` 61 | Temp float64 `lp:"field,temperature"` 62 | Hum int `lp:"field,humidity"` 63 | Time time.Time `lp:"timestamp"` 64 | Description string `lp:"-"` 65 | }{ 66 | "air", 67 | "SHT31", 68 | "10", 69 | 23.5, 70 | 55, 71 | now, 72 | "Room temp", 73 | }, 74 | line: fmt.Sprintf("air,device_id=10,sensor=SHT31 humidity=55i,temperature=23.5 %d\n", now.UnixNano()), 75 | }, { 76 | name: "test no tag, no timestamp", 77 | s: &struct { 78 | Measurement string `lp:"measurement"` 79 | Temp float64 `lp:"field,temperature"` 80 | }{ 81 | "air", 82 | 23.5, 83 | }, 84 | line: "air temperature=23.5\n", 85 | }, 86 | { 87 | name: "test default struct field name", 88 | s: &struct { 89 | Measurement string `lp:"measurement"` 90 | Sensor string `lp:"tag"` 91 | Temp float64 `lp:"field"` 92 | }{ 93 | "air", 94 | "SHT31", 95 | 23.5, 96 | }, 97 | line: "air,Sensor=SHT31 Temp=23.5\n", 98 | }, 99 | { 100 | name: "test missing struct field tag name", 101 | s: &struct { 102 | Measurement string `lp:"measurement"` 103 | Sensor string `lp:"tag,"` 104 | Temp float64 `lp:"field"` 105 | }{ 106 | "air", 107 | "SHT31", 108 | 23.5, 109 | }, 110 | error: `cannot use field 'Sensor': invalid lp tag name ""`, 111 | }, 112 | { 113 | name: "test missing struct field field name", 114 | s: &struct { 115 | Measurement string `lp:"measurement"` 116 | Temp float64 `lp:"field,"` 117 | }{ 118 | "air", 119 | 23.5, 120 | }, 121 | error: `cannot use field 'Temp': invalid lp field name ""`, 122 | }, 123 | { 124 | name: "test missing measurement", 125 | s: &struct { 126 | Measurement string `lp:"tag"` 127 | Sensor string `lp:"tag"` 128 | Temp float64 `lp:"field"` 129 | }{ 130 | "air", 131 | "SHT31", 132 | 23.5, 133 | }, 134 | error: `no struct field with tag 'measurement'`, 135 | }, 136 | { 137 | name: "test no field", 138 | s: &struct { 139 | Measurement string `lp:"measurement"` 140 | Sensor string `lp:"tag"` 141 | Temp float64 `lp:"tag"` 142 | }{ 143 | "air", 144 | "SHT31", 145 | 23.5, 146 | }, 147 | error: `no struct field with tag 'field'`, 148 | }, 149 | { 150 | name: "test double measurement", 151 | s: &struct { 152 | Measurement string `lp:"measurement"` 153 | Sensor string `lp:"measurement"` 154 | Temp float64 `lp:"field,a"` 155 | Hum float64 `lp:"field,a"` 156 | }{ 157 | "air", 158 | "SHT31", 159 | 23.5, 160 | 43.1, 161 | }, 162 | error: `multiple measurement fields`, 163 | }, 164 | { 165 | name: "test multiple tag attributes", 166 | s: &struct { 167 | Measurement string `lp:"measurement"` 168 | Sensor string `lp:"tag,a,a"` 169 | Temp float64 `lp:"field,a"` 170 | Hum float64 `lp:"field,a"` 171 | }{ 172 | "air", 173 | "SHT31", 174 | 23.5, 175 | 43.1, 176 | }, 177 | error: `multiple tag attributes are not supported`, 178 | }, 179 | { 180 | name: "test wrong timestamp type", 181 | s: &struct { 182 | Measurement string `lp:"measurement"` 183 | Sensor string `lp:"tag,sensor"` 184 | Temp float64 `lp:"field,a"` 185 | Hum float64 `lp:"timestamp"` 186 | }{ 187 | "air", 188 | "SHT31", 189 | 23.5, 190 | 43.1, 191 | }, 192 | error: `cannot use field 'Hum' as a timestamp`, 193 | }, 194 | { 195 | name: "test map", 196 | s: map[string]interface{}{ 197 | "measurement": "air", 198 | "sensor": "SHT31", 199 | "temp": 23.5, 200 | }, 201 | error: `cannot use map[string]interface {} as point`, 202 | }, 203 | { 204 | name: "test unsupported field type", 205 | s: &struct { 206 | Measurement string `lp:"measurement"` 207 | Temp complex64 `lp:"field,a"` 208 | }{ 209 | "air", 210 | complex(1, 1), 211 | }, 212 | error: `cannot use field 'Temp' of type 'complex64' as to create a point`, 213 | }, 214 | { 215 | name: "test unsupported lp tag value", 216 | s: &struct { 217 | Measurement string `lp:"measurement"` 218 | Temp float64 `lp:"data,a"` 219 | }{ 220 | "air", 221 | 1.0, 222 | }, 223 | error: `invalid tag data`, 224 | }, 225 | } 226 | for _, ts := range tests { 227 | t.Run(ts.name, func(t *testing.T) { 228 | point, err := DataToPoint(ts.s) 229 | if ts.error == "" { 230 | require.NoError(t, err) 231 | assert.Equal(t, ts.line, pointToLine(point)) 232 | } else { 233 | require.Error(t, err) 234 | assert.Equal(t, ts.error, err.Error()) 235 | } 236 | }) 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /api/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package api 6 | 7 | import ( 8 | "context" 9 | "github.com/influxdata/influxdb-client-go/v2/domain" 10 | "time" 11 | ) 12 | 13 | // DeleteAPI provides methods for deleting time series data from buckets. 14 | // Deleted series are selected by the time range specified by start and stop arguments and optional predicate string which contains condition for selecting data for deletion, such as: 15 | // tag1="value1" and (tag2="value2" and tag3!="value3") 16 | // Empty predicate string means all data from the given time range will be deleted. See https://v2.docs.influxdata.com/v2.0/reference/syntax/delete-predicate/ 17 | // for more info about predicate syntax. 18 | type DeleteAPI interface { 19 | // Delete deletes series selected by the time range specified by start and stop arguments and optional predicate string from the bucket bucket belonging to the organization org. 20 | Delete(ctx context.Context, org *domain.Organization, bucket *domain.Bucket, start, stop time.Time, predicate string) error 21 | // DeleteWithID deletes series selected by the time range specified by start and stop arguments and optional predicate string from the bucket with ID bucketID belonging to the organization with ID orgID. 22 | DeleteWithID(ctx context.Context, orgID, bucketID string, start, stop time.Time, predicate string) error 23 | // DeleteWithName deletes series selected by the time range specified by start and stop arguments and optional predicate string from the bucket with name bucketName belonging to the organization with name orgName. 24 | DeleteWithName(ctx context.Context, orgName, bucketName string, start, stop time.Time, predicate string) error 25 | } 26 | 27 | // deleteAPI implements DeleteAPI 28 | type deleteAPI struct { 29 | apiClient *domain.Client 30 | } 31 | 32 | // NewDeleteAPI creates new instance of DeleteAPI 33 | func NewDeleteAPI(apiClient *domain.Client) DeleteAPI { 34 | return &deleteAPI{ 35 | apiClient: apiClient, 36 | } 37 | } 38 | 39 | func (d *deleteAPI) delete(ctx context.Context, params *domain.PostDeleteParams, conditions *domain.DeletePredicateRequest) error { 40 | allParams := &domain.PostDeleteAllParams{ 41 | PostDeleteParams: *params, 42 | Body: domain.PostDeleteJSONRequestBody(*conditions), 43 | } 44 | return d.apiClient.PostDelete(ctx, allParams) 45 | } 46 | 47 | func (d *deleteAPI) Delete(ctx context.Context, org *domain.Organization, bucket *domain.Bucket, start, stop time.Time, predicate string) error { 48 | params := &domain.PostDeleteParams{ 49 | OrgID: org.Id, 50 | BucketID: bucket.Id, 51 | } 52 | conditions := &domain.DeletePredicateRequest{ 53 | Predicate: &predicate, 54 | Start: start, 55 | Stop: stop, 56 | } 57 | return d.delete(ctx, params, conditions) 58 | } 59 | 60 | func (d *deleteAPI) DeleteWithID(ctx context.Context, orgID, bucketID string, start, stop time.Time, predicate string) error { 61 | params := &domain.PostDeleteParams{ 62 | OrgID: &orgID, 63 | BucketID: &bucketID, 64 | } 65 | conditions := &domain.DeletePredicateRequest{ 66 | Predicate: &predicate, 67 | Start: start, 68 | Stop: stop, 69 | } 70 | return d.delete(ctx, params, conditions) 71 | } 72 | 73 | func (d *deleteAPI) DeleteWithName(ctx context.Context, orgName, bucketName string, start, stop time.Time, predicate string) error { 74 | params := &domain.PostDeleteParams{ 75 | Org: &orgName, 76 | Bucket: &bucketName, 77 | } 78 | conditions := &domain.DeletePredicateRequest{ 79 | Predicate: &predicate, 80 | Start: start, 81 | Stop: stop, 82 | } 83 | return d.delete(ctx, params, conditions) 84 | } 85 | -------------------------------------------------------------------------------- /api/delete_e2e_test.go: -------------------------------------------------------------------------------- 1 | // +build e2e 2 | 3 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 4 | // Use of this source code is governed by MIT 5 | // license that can be found in the LICENSE file. 6 | 7 | package api_test 8 | 9 | import ( 10 | "context" 11 | "strconv" 12 | "strings" 13 | "testing" 14 | "time" 15 | 16 | influxdb2 "github.com/influxdata/influxdb-client-go/v2" 17 | "github.com/influxdata/influxdb-client-go/v2/api/write" 18 | "github.com/influxdata/influxdb-client-go/v2/domain" 19 | "github.com/stretchr/testify/assert" 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestDeleteAPI(t *testing.T) { 24 | ctx := context.Background() 25 | client := influxdb2.NewClient(serverURL, authToken) 26 | writeAPI := client.WriteAPIBlocking("my-org", "my-bucket") 27 | queryAPI := client.QueryAPI("my-org") 28 | tmStart := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) 29 | writeF := func(start time.Time, count int64) time.Time { 30 | tm := start 31 | for i, f := int64(0), 0.0; i < count; i++ { 32 | p := write.NewPoint("test", 33 | map[string]string{"a": strconv.FormatInt(i%2, 10), "b": "static"}, 34 | map[string]interface{}{"f": f, "i": i}, 35 | tm) 36 | err := writeAPI.WritePoint(ctx, p) 37 | require.NoError(t, err) 38 | f += 1.2 39 | tm = tm.Add(time.Minute) 40 | } 41 | return tm 42 | } 43 | countF := func(start, stop time.Time) int64 { 44 | result, err := queryAPI.Query(ctx, `from(bucket:"my-bucket")|> range(start: `+start.Format(time.RFC3339)+`, stop:`+stop.Format(time.RFC3339)+`) 45 | |> filter(fn: (r) => r._measurement == "test" and r._field == "f") 46 | |> drop(columns: ["a", "b"]) 47 | |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value") 48 | |> count(column: "f")`) 49 | 50 | require.Nil(t, err, err) 51 | count := int64(0) 52 | if result.Next() { 53 | require.NotNil(t, result.Record().ValueByKey("f")) 54 | count = result.Record().ValueByKey("f").(int64) 55 | } 56 | return count 57 | } 58 | tmEnd := writeF(tmStart, 100) 59 | assert.Equal(t, int64(100), countF(tmStart, tmEnd)) 60 | deleteAPI := client.DeleteAPI() 61 | 62 | org, err := client.OrganizationsAPI().FindOrganizationByName(ctx, "my-org") 63 | require.Nil(t, err, err) 64 | require.NotNil(t, org) 65 | 66 | bucket, err := client.BucketsAPI().FindBucketByName(ctx, "my-bucket") 67 | require.Nil(t, err, err) 68 | require.NotNil(t, bucket) 69 | 70 | err = deleteAPI.DeleteWithName(ctx, "my-org", "my-bucket", tmStart, tmEnd, "") 71 | require.Nil(t, err, err) 72 | assert.Equal(t, int64(0), countF(tmStart, tmEnd)) 73 | 74 | tmEnd = writeF(tmStart, 100) 75 | assert.Equal(t, int64(100), countF(tmStart, tmEnd)) 76 | 77 | err = deleteAPI.DeleteWithID(ctx, *org.Id, *bucket.Id, tmStart, tmEnd, "a=1") 78 | require.Nil(t, err, err) 79 | assert.Equal(t, int64(50), countF(tmStart, tmEnd)) 80 | 81 | err = deleteAPI.Delete(ctx, org, bucket, tmStart.Add(50*time.Minute), tmEnd, "b=static") 82 | require.Nil(t, err, err) 83 | assert.Equal(t, int64(25), countF(tmStart, tmEnd)) 84 | 85 | err = deleteAPI.DeleteWithName(ctx, "org", "my-bucket", tmStart.Add(50*time.Minute), tmEnd, "b=static") 86 | require.NotNil(t, err, err) 87 | assert.True(t, strings.Contains(err.Error(), "not found")) 88 | 89 | err = deleteAPI.DeleteWithName(ctx, "my-org", "bucket", tmStart.Add(50*time.Minute), tmEnd, "b=static") 90 | require.NotNil(t, err, err) 91 | assert.True(t, strings.Contains(err.Error(), "not found")) 92 | } 93 | 94 | func TestDeleteAPI_failing(t *testing.T) { 95 | ctx := context.Background() 96 | client := influxdb2.NewClient(serverURL, authToken) 97 | deleteAPI := client.DeleteAPI() 98 | 99 | invalidID := "xcv" 100 | notExistentID := "1000000000000000" 101 | 102 | tmStart := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) 103 | tmEnd := tmStart.Add(time.Second) 104 | err := deleteAPI.DeleteWithID(ctx, notExistentID, invalidID, tmStart, tmEnd, "a=1") 105 | assert.NotNil(t, err) 106 | 107 | err = deleteAPI.DeleteWithID(ctx, notExistentID, notExistentID, tmStart, tmEnd, "a=1") 108 | assert.NotNil(t, err) 109 | 110 | org, err := client.OrganizationsAPI().FindOrganizationByName(ctx, "my-org") 111 | assert.Nil(t, err, err) 112 | assert.NotNil(t, org) 113 | 114 | bucket, err := client.BucketsAPI().FindBucketByName(ctx, "my-bucket") 115 | assert.Nil(t, err, err) 116 | assert.NotNil(t, bucket) 117 | 118 | org, err = client.OrganizationsAPI().CreateOrganizationWithName(ctx, "org1") 119 | require.Nil(t, err) 120 | require.NotNil(t, org) 121 | 122 | permission := &domain.Permission{ 123 | Action: domain.PermissionActionWrite, 124 | Resource: domain.Resource{ 125 | Type: domain.ResourceTypeOrgs, 126 | }, 127 | } 128 | permissions := []domain.Permission{*permission} 129 | 130 | //create authorization for new org 131 | auth, err := client.AuthorizationsAPI().CreateAuthorizationWithOrgID(context.Background(), *org.Id, permissions) 132 | require.Nil(t, err) 133 | require.NotNil(t, auth) 134 | 135 | // create client with new auth token without permission for buckets 136 | clientOrg2 := influxdb2.NewClient(serverURL, *auth.Token) 137 | // test 403 138 | err = clientOrg2.DeleteAPI().Delete(ctx, org, bucket, tmStart, tmStart.Add(50*time.Minute), "b=static") 139 | assert.NotNil(t, err) 140 | 141 | err = client.AuthorizationsAPI().DeleteAuthorization(ctx, auth) 142 | assert.Nil(t, err) 143 | 144 | err = client.OrganizationsAPI().DeleteOrganization(ctx, org) 145 | assert.Nil(t, err) 146 | } 147 | 148 | func TestDeleteAPI_requestFailing(t *testing.T) { 149 | ctx := context.Background() 150 | client := influxdb2.NewClient("serverURL", authToken) 151 | deleteAPI := client.DeleteAPI() 152 | 153 | anID := "1000000000000000" 154 | 155 | err := deleteAPI.DeleteWithName(ctx, anID, anID, time.Now(), time.Now(), "") 156 | assert.NotNil(t, err) 157 | } 158 | -------------------------------------------------------------------------------- /api/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package api provides clients for InfluxDB server APIs. 6 | package api 7 | -------------------------------------------------------------------------------- /api/http/error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | import ( 8 | "fmt" 9 | "net/http" 10 | "strconv" 11 | ) 12 | 13 | // Error represent error response from InfluxDBServer or http error 14 | type Error struct { 15 | StatusCode int 16 | Code string 17 | Message string 18 | Err error 19 | RetryAfter uint 20 | Header http.Header 21 | } 22 | 23 | // Error fulfils error interface 24 | func (e *Error) Error() string { 25 | switch { 26 | case e.Err != nil: 27 | return e.Err.Error() 28 | case e.Code != "" && e.Message != "": 29 | return fmt.Sprintf("%s: %s", e.Code, e.Message) 30 | default: 31 | return "Unexpected status code " + strconv.Itoa(e.StatusCode) 32 | } 33 | } 34 | 35 | func (e *Error) Unwrap() error { 36 | if e.Err != nil { 37 | return e.Err 38 | } 39 | return nil 40 | } 41 | 42 | // HeaderToString generates a string value from the Header property. Useful in logging. 43 | func (e *Error) HeaderToString(selected []string) string { 44 | headerString := "" 45 | if len(selected) == 0 { 46 | for key := range e.Header { 47 | k := http.CanonicalHeaderKey(key) 48 | headerString += fmt.Sprintf("%s: %s\r\n", k, e.Header.Get(k)) 49 | } 50 | } else { 51 | for _, candidate := range selected { 52 | c := http.CanonicalHeaderKey(candidate) 53 | if e.Header.Get(c) != "" { 54 | headerString += fmt.Sprintf("%s: %s\n", c, e.Header.Get(c)) 55 | } 56 | } 57 | } 58 | return headerString 59 | } 60 | 61 | // NewError returns newly created Error initialised with nested error and default values 62 | func NewError(err error) *Error { 63 | return &Error{ 64 | StatusCode: 0, 65 | Code: "", 66 | Message: "", 67 | Err: err, 68 | RetryAfter: 0, 69 | Header: http.Header{}, 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /api/http/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | import ( 8 | "fmt" 9 | ihttp "net/http" 10 | 11 | "github.com/stretchr/testify/assert" 12 | 13 | "testing" 14 | ) 15 | 16 | func TestWriteErrorHeaderToString(t *testing.T) { 17 | header := ihttp.Header{ 18 | "Date": []string{"2024-08-07T12:00:00.009"}, 19 | "Content-Length": []string{"12"}, 20 | "Content-Type": []string{"application/json", "encoding UTF-8"}, 21 | "X-Test-Value1": []string{"SaturnV"}, 22 | "X-Test-Value2": []string{"Apollo11"}, 23 | "Retry-After": []string{"2044"}, 24 | "Trace-Id": []string{"123456789ABCDEF0"}, 25 | } 26 | 27 | err := Error{ 28 | StatusCode: ihttp.StatusBadRequest, 29 | Code: "bad request", 30 | Message: "this is just a test", 31 | Err: nil, 32 | RetryAfter: 2044, 33 | Header: header, 34 | } 35 | 36 | fullString := err.HeaderToString([]string{}) 37 | 38 | // write order is not guaranteed 39 | assert.Contains(t, fullString, "Date: 2024-08-07T12:00:00.009") 40 | assert.Contains(t, fullString, "Content-Length: 12") 41 | assert.Contains(t, fullString, "Content-Type: application/json") 42 | assert.Contains(t, fullString, "X-Test-Value1: SaturnV") 43 | assert.Contains(t, fullString, "X-Test-Value2: Apollo11") 44 | assert.Contains(t, fullString, "Retry-After: 2044") 45 | assert.Contains(t, fullString, "Trace-Id: 123456789ABCDEF0") 46 | 47 | filterString := err.HeaderToString([]string{"date", "trace-id", "x-test-value1", "x-test-value2"}) 48 | 49 | // write order will follow filter arguments 50 | assert.Equal(t, filterString, 51 | "Date: 2024-08-07T12:00:00.009\nTrace-Id: 123456789ABCDEF0\nX-Test-Value1: SaturnV\nX-Test-Value2: Apollo11\n", 52 | ) 53 | assert.NotContains(t, filterString, "Content-Type: application/json") 54 | assert.NotContains(t, filterString, "Retry-After: 2044") 55 | } 56 | 57 | func TestErrorIfaceError(t *testing.T) { 58 | tests := []struct { 59 | name string 60 | statusCode int 61 | err error 62 | code string 63 | message string 64 | expected string 65 | }{ 66 | {name: "TestNestedErrorNotNilCode0Message0", 67 | statusCode: 418, 68 | err: fmt.Errorf("original test message"), 69 | code: "", 70 | message: "", 71 | expected: "original test message"}, 72 | {name: "TestNestedErrorNotNilCodeXMessageX", 73 | statusCode: 418, 74 | err: fmt.Errorf("original test message"), 75 | code: "bad request", 76 | message: "is this a teapot?", 77 | expected: "original test message"}, 78 | {name: "TestNestedErrorNilCodeXMessageX", 79 | statusCode: 418, 80 | err: nil, 81 | code: "bad request", 82 | message: "is this a teapot?", 83 | expected: "bad request: is this a teapot?"}, 84 | {name: "TestNesterErrorNilCodeXMessage0", 85 | statusCode: 418, 86 | err: nil, 87 | code: "I'm a teapot", 88 | message: "", 89 | expected: "Unexpected status code 418"}, 90 | } 91 | 92 | for _, test := range tests { 93 | t.Run(test.name, func(t *testing.T) { 94 | err := Error{ 95 | StatusCode: test.statusCode, 96 | Code: test.code, 97 | Message: test.message, 98 | Err: test.err, 99 | RetryAfter: 0, 100 | Header: ihttp.Header{}, 101 | } 102 | assert.Equal(t, test.expected, err.Error()) 103 | }) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /api/http/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | import ( 8 | "crypto/tls" 9 | "net" 10 | "net/http" 11 | "time" 12 | ) 13 | 14 | // Options holds http configuration properties for communicating with InfluxDB server 15 | type Options struct { 16 | // HTTP client. Default is http.DefaultClient. 17 | httpClient *http.Client 18 | // doer is an http Doer - if set it overrides httpClient 19 | doer Doer 20 | // Flag whether http client was created internally 21 | ownClient bool 22 | // TLS configuration for secure connection. Default nil 23 | tlsConfig *tls.Config 24 | // HTTP request timeout in sec. Default 20 25 | httpRequestTimeout uint 26 | // Application name in the User-Agent HTTP header string 27 | appName string 28 | } 29 | 30 | // HTTPClient returns the http.Client that is configured to be used 31 | // for HTTP requests. It will return the one that has been set using 32 | // SetHTTPClient or it will construct a default client using the 33 | // other configured options. 34 | // HTTPClient panics if SetHTTPDoer was called. 35 | func (o *Options) HTTPClient() *http.Client { 36 | if o.doer != nil { 37 | panic("HTTPClient called after SetHTTPDoer") 38 | } 39 | if o.httpClient == nil { 40 | o.httpClient = &http.Client{ 41 | Timeout: time.Second * time.Duration(o.HTTPRequestTimeout()), 42 | Transport: &http.Transport{ 43 | Proxy: http.ProxyFromEnvironment, 44 | DialContext: (&net.Dialer{ 45 | Timeout: 5 * time.Second, 46 | }).DialContext, 47 | TLSHandshakeTimeout: 5 * time.Second, 48 | TLSClientConfig: o.TLSConfig(), 49 | MaxIdleConns: 100, 50 | MaxIdleConnsPerHost: 100, 51 | IdleConnTimeout: 90 * time.Second, 52 | }, 53 | } 54 | o.ownClient = true 55 | } 56 | return o.httpClient 57 | } 58 | 59 | // SetHTTPClient will configure the http.Client that is used 60 | // for HTTP requests. If set to nil, an HTTPClient will be 61 | // generated. 62 | // 63 | // Setting the HTTPClient will cause the other HTTP options 64 | // to be ignored. 65 | // In case of UsersAPI.SignIn() is used, HTTPClient.Jar will be used for storing session cookie. 66 | func (o *Options) SetHTTPClient(c *http.Client) *Options { 67 | o.httpClient = c 68 | o.ownClient = false 69 | return o 70 | } 71 | 72 | // OwnHTTPClient returns true of HTTP client was created internally. False if it was set externally. 73 | func (o *Options) OwnHTTPClient() bool { 74 | return o.ownClient 75 | } 76 | 77 | // Doer allows proving custom Do for HTTP operations 78 | type Doer interface { 79 | Do(*http.Request) (*http.Response, error) 80 | } 81 | 82 | // SetHTTPDoer will configure the http.Client that is used 83 | // for HTTP requests. If set to nil, this has no effect. 84 | // 85 | // Setting the HTTPDoer will cause the other HTTP options 86 | // to be ignored. 87 | func (o *Options) SetHTTPDoer(d Doer) *Options { 88 | if d != nil { 89 | o.doer = d 90 | o.ownClient = false 91 | } 92 | return o 93 | } 94 | 95 | // HTTPDoer returns actual Doer if set, or http.Client 96 | func (o *Options) HTTPDoer() Doer { 97 | if o.doer != nil { 98 | return o.doer 99 | } 100 | return o.HTTPClient() 101 | } 102 | 103 | // TLSConfig returns tls.Config 104 | func (o *Options) TLSConfig() *tls.Config { 105 | return o.tlsConfig 106 | } 107 | 108 | // SetTLSConfig sets TLS configuration for secure connection 109 | func (o *Options) SetTLSConfig(tlsConfig *tls.Config) *Options { 110 | o.tlsConfig = tlsConfig 111 | return o 112 | } 113 | 114 | // HTTPRequestTimeout returns HTTP request timeout 115 | func (o *Options) HTTPRequestTimeout() uint { 116 | return o.httpRequestTimeout 117 | } 118 | 119 | // SetHTTPRequestTimeout sets HTTP request timeout in sec 120 | func (o *Options) SetHTTPRequestTimeout(httpRequestTimeout uint) *Options { 121 | o.httpRequestTimeout = httpRequestTimeout 122 | return o 123 | } 124 | 125 | // ApplicationName returns application name used in the User-Agent HTTP header 126 | func (o *Options) ApplicationName() string { 127 | return o.appName 128 | } 129 | 130 | // SetApplicationName sets an application name to the User-Agent HTTP header 131 | func (o *Options) SetApplicationName(appName string) *Options { 132 | o.appName = appName 133 | return o 134 | } 135 | 136 | // DefaultOptions returns Options object with default values 137 | func DefaultOptions() *Options { 138 | return &Options{httpRequestTimeout: 20} 139 | } 140 | -------------------------------------------------------------------------------- /api/http/options_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package http_test 6 | 7 | import ( 8 | "crypto/tls" 9 | "github.com/stretchr/testify/require" 10 | nethttp "net/http" 11 | "testing" 12 | "time" 13 | 14 | "github.com/influxdata/influxdb-client-go/v2/api/http" 15 | "github.com/stretchr/testify/assert" 16 | ) 17 | 18 | func TestDefaultOptions(t *testing.T) { 19 | opts := http.DefaultOptions() 20 | assert.Equal(t, (*tls.Config)(nil), opts.TLSConfig()) 21 | assert.Equal(t, uint(20), opts.HTTPRequestTimeout()) 22 | assert.NotNil(t, opts.HTTPClient()) 23 | assert.True(t, opts.OwnHTTPClient()) 24 | transport, ok := opts.HTTPClient().Transport.(*nethttp.Transport) 25 | require.True(t, ok) 26 | assert.NotNil(t, transport.Proxy) 27 | assert.EqualValues(t, "", opts.ApplicationName()) 28 | } 29 | 30 | func TestOptionsSetting(t *testing.T) { 31 | tlsConfig := &tls.Config{ 32 | InsecureSkipVerify: true, 33 | } 34 | opts := http.DefaultOptions(). 35 | SetTLSConfig(tlsConfig). 36 | SetHTTPRequestTimeout(50). 37 | SetApplicationName("Monitor/1.1") 38 | assert.Equal(t, tlsConfig, opts.TLSConfig()) 39 | assert.Equal(t, uint(50), opts.HTTPRequestTimeout()) 40 | assert.EqualValues(t, "Monitor/1.1", opts.ApplicationName()) 41 | if client := opts.HTTPClient(); assert.NotNil(t, client) { 42 | assert.Equal(t, 50*time.Second, client.Timeout) 43 | assert.Equal(t, tlsConfig, client.Transport.(*nethttp.Transport).TLSClientConfig) 44 | assert.True(t, opts.OwnHTTPClient()) 45 | } 46 | 47 | client := &nethttp.Client{ 48 | Transport: &nethttp.Transport{}, 49 | } 50 | opts = http.DefaultOptions() 51 | opts.SetHTTPClient(client) 52 | assert.Equal(t, client, opts.HTTPClient()) 53 | assert.False(t, opts.OwnHTTPClient()) 54 | } 55 | -------------------------------------------------------------------------------- /api/http/service.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package http provides HTTP servicing related code. 6 | // 7 | // Important type is Service which handles HTTP operations. It is internally used by library and it is not necessary to use it directly for common operations. 8 | // It can be useful when creating custom InfluxDB2 server API calls using generated code from the domain package, that are not yet exposed by API of this library. 9 | // 10 | // Service can be obtained from client using HTTPService() method. 11 | // It can be also created directly. To instantiate a Service use NewService(). Remember, the authorization param is in form "Token your-auth-token". e.g. "Token DXnd7annkGteV5Wqx9G3YjO9Ezkw87nHk8OabcyHCxF5451kdBV0Ag2cG7OmZZgCUTHroagUPdxbuoyen6TSPw==". 12 | // 13 | // srv := http.NewService("http://localhost:8086", "Token my-token", http.DefaultOptions()) 14 | package http 15 | 16 | import ( 17 | "context" 18 | "encoding/json" 19 | "io" 20 | "mime" 21 | "net/http" 22 | "net/url" 23 | "strconv" 24 | 25 | http2 "github.com/influxdata/influxdb-client-go/v2/internal/http" 26 | "github.com/influxdata/influxdb-client-go/v2/internal/log" 27 | ) 28 | 29 | // RequestCallback defines function called after a request is created before any call 30 | type RequestCallback func(req *http.Request) 31 | 32 | // ResponseCallback defines function called after a successful response was received 33 | type ResponseCallback func(resp *http.Response) error 34 | 35 | // Service handles HTTP operations with taking care of mandatory request headers and known errors 36 | type Service interface { 37 | // DoPostRequest sends HTTP POST request to the given url with body 38 | DoPostRequest(ctx context.Context, url string, body io.Reader, requestCallback RequestCallback, responseCallback ResponseCallback) *Error 39 | // DoHTTPRequest sends given HTTP request and handles response 40 | DoHTTPRequest(req *http.Request, requestCallback RequestCallback, responseCallback ResponseCallback) *Error 41 | // DoHTTPRequestWithResponse sends given HTTP request and returns response 42 | DoHTTPRequestWithResponse(req *http.Request, requestCallback RequestCallback) (*http.Response, error) 43 | // SetAuthorization sets the authorization header value 44 | SetAuthorization(authorization string) 45 | // Authorization returns current authorization header value 46 | Authorization() string 47 | // ServerAPIURL returns URL to InfluxDB2 server API space 48 | ServerAPIURL() string 49 | // ServerURL returns URL to InfluxDB2 server 50 | ServerURL() string 51 | } 52 | 53 | // service implements Service interface 54 | type service struct { 55 | serverAPIURL string 56 | serverURL string 57 | authorization string 58 | client Doer 59 | userAgent string 60 | } 61 | 62 | // NewService creates instance of http Service with given parameters 63 | func NewService(serverURL, authorization string, httpOptions *Options) Service { 64 | apiURL, err := url.Parse(serverURL) 65 | serverAPIURL := serverURL 66 | if err == nil { 67 | apiURL, err = apiURL.Parse("api/v2/") 68 | if err == nil { 69 | serverAPIURL = apiURL.String() 70 | } 71 | } 72 | return &service{ 73 | serverAPIURL: serverAPIURL, 74 | serverURL: serverURL, 75 | authorization: authorization, 76 | client: httpOptions.HTTPDoer(), 77 | userAgent: http2.FormatUserAgent(httpOptions.ApplicationName()), 78 | } 79 | } 80 | 81 | func (s *service) ServerAPIURL() string { 82 | return s.serverAPIURL 83 | } 84 | 85 | func (s *service) ServerURL() string { 86 | return s.serverURL 87 | } 88 | 89 | func (s *service) SetAuthorization(authorization string) { 90 | s.authorization = authorization 91 | } 92 | 93 | func (s *service) Authorization() string { 94 | return s.authorization 95 | } 96 | 97 | func (s *service) DoPostRequest(ctx context.Context, url string, body io.Reader, requestCallback RequestCallback, responseCallback ResponseCallback) *Error { 98 | return s.doHTTPRequestWithURL(ctx, http.MethodPost, url, body, requestCallback, responseCallback) 99 | } 100 | 101 | func (s *service) doHTTPRequestWithURL(ctx context.Context, method, url string, body io.Reader, requestCallback RequestCallback, responseCallback ResponseCallback) *Error { 102 | req, err := http.NewRequestWithContext(ctx, method, url, body) 103 | if err != nil { 104 | return NewError(err) 105 | } 106 | return s.DoHTTPRequest(req, requestCallback, responseCallback) 107 | } 108 | 109 | func (s *service) DoHTTPRequest(req *http.Request, requestCallback RequestCallback, responseCallback ResponseCallback) *Error { 110 | resp, err := s.DoHTTPRequestWithResponse(req, requestCallback) 111 | if err != nil { 112 | return NewError(err) 113 | } 114 | 115 | if resp.StatusCode < 200 || resp.StatusCode >= 300 { 116 | return s.parseHTTPError(resp) 117 | } 118 | if responseCallback != nil { 119 | err := responseCallback(resp) 120 | if err != nil { 121 | return NewError(err) 122 | } 123 | } 124 | return nil 125 | } 126 | 127 | func (s *service) DoHTTPRequestWithResponse(req *http.Request, requestCallback RequestCallback) (*http.Response, error) { 128 | log.Infof("HTTP %s req to %s", req.Method, req.URL.String()) 129 | if len(s.authorization) > 0 { 130 | req.Header.Set("Authorization", s.authorization) 131 | } 132 | if req.Header.Get("User-Agent") == "" { 133 | req.Header.Set("User-Agent", s.userAgent) 134 | } 135 | if requestCallback != nil { 136 | requestCallback(req) 137 | } 138 | return s.client.Do(req) 139 | } 140 | 141 | func (s *service) parseHTTPError(r *http.Response) *Error { 142 | // successful status code range 143 | if r.StatusCode >= 200 && r.StatusCode < 300 { 144 | return nil 145 | } 146 | defer func() { 147 | // discard body so connection can be reused 148 | _, _ = io.Copy(io.Discard, r.Body) 149 | _ = r.Body.Close() 150 | }() 151 | 152 | perror := NewError(nil) 153 | perror.StatusCode = r.StatusCode 154 | perror.Header = r.Header 155 | 156 | if v := r.Header.Get("Retry-After"); v != "" { 157 | r, err := strconv.ParseUint(v, 10, 32) 158 | if err == nil { 159 | perror.RetryAfter = uint(r) 160 | } 161 | } 162 | 163 | // json encoded error 164 | ctype, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type")) 165 | if ctype == "application/json" { 166 | perror.Err = json.NewDecoder(r.Body).Decode(perror) 167 | } else { 168 | body, err := io.ReadAll(r.Body) 169 | if err != nil { 170 | perror.Err = err 171 | return perror 172 | } 173 | 174 | perror.Code = r.Status 175 | perror.Message = string(body) 176 | } 177 | 178 | if perror.Code == "" && perror.Message == "" { 179 | switch r.StatusCode { 180 | case http.StatusTooManyRequests: 181 | perror.Code = "too many requests" 182 | perror.Message = "exceeded rate limit" 183 | case http.StatusServiceUnavailable: 184 | perror.Code = "unavailable" 185 | perror.Message = "service temporarily unavailable" 186 | default: 187 | perror.Code = r.Status 188 | perror.Message = r.Header.Get("X-Influxdb-Error") 189 | } 190 | } 191 | 192 | return perror 193 | } 194 | -------------------------------------------------------------------------------- /api/http/service_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package http 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestService(t *testing.T) { 14 | srv := NewService("http://localhost:8086/aa/", "Token my-token", DefaultOptions()) 15 | assert.Equal(t, "http://localhost:8086/aa/", srv.ServerURL()) 16 | assert.Equal(t, "http://localhost:8086/aa/api/v2/", srv.ServerAPIURL()) 17 | assert.Equal(t, "Token my-token", srv.Authorization()) 18 | } 19 | -------------------------------------------------------------------------------- /api/labels.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package api 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | "github.com/influxdata/influxdb-client-go/v2/domain" 12 | ) 13 | 14 | // LabelsAPI provides methods for managing labels in a InfluxDB server. 15 | type LabelsAPI interface { 16 | // GetLabels returns all labels. 17 | GetLabels(ctx context.Context) (*[]domain.Label, error) 18 | // FindLabelsByOrg returns labels belonging to organization org. 19 | FindLabelsByOrg(ctx context.Context, org *domain.Organization) (*[]domain.Label, error) 20 | // FindLabelsByOrgID returns labels belonging to organization with id orgID. 21 | FindLabelsByOrgID(ctx context.Context, orgID string) (*[]domain.Label, error) 22 | // FindLabelByID returns a label with labelID. 23 | FindLabelByID(ctx context.Context, labelID string) (*domain.Label, error) 24 | // FindLabelByName returns a label with name labelName under an organization orgID. 25 | FindLabelByName(ctx context.Context, orgID, labelName string) (*domain.Label, error) 26 | // CreateLabel creates a new label. 27 | CreateLabel(ctx context.Context, label *domain.LabelCreateRequest) (*domain.Label, error) 28 | // CreateLabelWithName creates a new label with label labelName and properties, under the organization org. 29 | // Properties example: {"color": "ffb3b3", "description": "this is a description"}. 30 | CreateLabelWithName(ctx context.Context, org *domain.Organization, labelName string, properties map[string]string) (*domain.Label, error) 31 | // CreateLabelWithNameWithID creates a new label with label labelName and properties, under the organization with id orgID. 32 | // Properties example: {"color": "ffb3b3", "description": "this is a description"}. 33 | CreateLabelWithNameWithID(ctx context.Context, orgID, labelName string, properties map[string]string) (*domain.Label, error) 34 | // UpdateLabel updates the label. 35 | // Properties can be removed by sending an update with an empty value. 36 | UpdateLabel(ctx context.Context, label *domain.Label) (*domain.Label, error) 37 | // DeleteLabelWithID deletes a label with labelID. 38 | DeleteLabelWithID(ctx context.Context, labelID string) error 39 | // DeleteLabel deletes a label. 40 | DeleteLabel(ctx context.Context, label *domain.Label) error 41 | } 42 | 43 | // labelsAPI implements LabelsAPI 44 | type labelsAPI struct { 45 | apiClient *domain.Client 46 | } 47 | 48 | // NewLabelsAPI creates new instance of LabelsAPI 49 | func NewLabelsAPI(apiClient *domain.Client) LabelsAPI { 50 | return &labelsAPI{ 51 | apiClient: apiClient, 52 | } 53 | } 54 | 55 | func (u *labelsAPI) GetLabels(ctx context.Context) (*[]domain.Label, error) { 56 | params := &domain.GetLabelsParams{} 57 | return u.getLabels(ctx, params) 58 | } 59 | 60 | func (u *labelsAPI) getLabels(ctx context.Context, params *domain.GetLabelsParams) (*[]domain.Label, error) { 61 | response, err := u.apiClient.GetLabels(ctx, params) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return (*[]domain.Label)(response.Labels), nil 66 | } 67 | 68 | func (u *labelsAPI) FindLabelsByOrg(ctx context.Context, org *domain.Organization) (*[]domain.Label, error) { 69 | return u.FindLabelsByOrgID(ctx, *org.Id) 70 | } 71 | 72 | func (u *labelsAPI) FindLabelsByOrgID(ctx context.Context, orgID string) (*[]domain.Label, error) { 73 | params := &domain.GetLabelsParams{OrgID: &orgID} 74 | return u.getLabels(ctx, params) 75 | } 76 | 77 | func (u *labelsAPI) FindLabelByID(ctx context.Context, labelID string) (*domain.Label, error) { 78 | params := &domain.GetLabelsIDAllParams{ 79 | LabelID: labelID, 80 | } 81 | response, err := u.apiClient.GetLabelsID(ctx, params) 82 | if err != nil { 83 | return nil, err 84 | } 85 | return response.Label, nil 86 | } 87 | 88 | func (u *labelsAPI) FindLabelByName(ctx context.Context, orgID, labelName string) (*domain.Label, error) { 89 | labels, err := u.FindLabelsByOrgID(ctx, orgID) 90 | if err != nil { 91 | return nil, err 92 | } 93 | var label *domain.Label 94 | for _, u := range *labels { 95 | if *u.Name == labelName { 96 | label = &u 97 | break 98 | } 99 | } 100 | if label == nil { 101 | return nil, fmt.Errorf("label '%s' not found", labelName) 102 | } 103 | return label, nil 104 | } 105 | 106 | func (u *labelsAPI) CreateLabelWithName(ctx context.Context, org *domain.Organization, labelName string, properties map[string]string) (*domain.Label, error) { 107 | return u.CreateLabelWithNameWithID(ctx, *org.Id, labelName, properties) 108 | } 109 | 110 | func (u *labelsAPI) CreateLabelWithNameWithID(ctx context.Context, orgID, labelName string, properties map[string]string) (*domain.Label, error) { 111 | props := &domain.LabelCreateRequest_Properties{AdditionalProperties: properties} 112 | label := &domain.LabelCreateRequest{Name: labelName, OrgID: orgID, Properties: props} 113 | return u.CreateLabel(ctx, label) 114 | } 115 | 116 | func (u *labelsAPI) CreateLabel(ctx context.Context, label *domain.LabelCreateRequest) (*domain.Label, error) { 117 | params := &domain.PostLabelsAllParams{ 118 | Body: domain.PostLabelsJSONRequestBody(*label), 119 | } 120 | response, err := u.apiClient.PostLabels(ctx, params) 121 | if err != nil { 122 | return nil, err 123 | } 124 | return response.Label, nil 125 | } 126 | 127 | func (u *labelsAPI) UpdateLabel(ctx context.Context, label *domain.Label) (*domain.Label, error) { 128 | var props *domain.LabelUpdate_Properties 129 | if label.Properties != nil { 130 | props = &domain.LabelUpdate_Properties{AdditionalProperties: label.Properties.AdditionalProperties} 131 | } 132 | params := &domain.PatchLabelsIDAllParams{ 133 | Body: domain.PatchLabelsIDJSONRequestBody(domain.LabelUpdate{ 134 | Name: label.Name, 135 | Properties: props, 136 | }), 137 | LabelID: *label.Id, 138 | } 139 | response, err := u.apiClient.PatchLabelsID(ctx, params) 140 | if err != nil { 141 | return nil, err 142 | } 143 | return response.Label, nil 144 | } 145 | 146 | func (u *labelsAPI) DeleteLabel(ctx context.Context, label *domain.Label) error { 147 | return u.DeleteLabelWithID(ctx, *label.Id) 148 | } 149 | 150 | func (u *labelsAPI) DeleteLabelWithID(ctx context.Context, labelID string) error { 151 | params := &domain.DeleteLabelsIDAllParams{ 152 | LabelID: labelID, 153 | } 154 | return u.apiClient.DeleteLabelsID(ctx, params) 155 | } 156 | -------------------------------------------------------------------------------- /api/labels_e2e_test.go: -------------------------------------------------------------------------------- 1 | // +build e2e 2 | 3 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 4 | // Use of this source code is governed by MIT 5 | // license that can be found in the LICENSE file. 6 | 7 | package api_test 8 | 9 | import ( 10 | "context" 11 | "testing" 12 | 13 | influxdb2 "github.com/influxdata/influxdb-client-go/v2" 14 | "github.com/influxdata/influxdb-client-go/v2/domain" 15 | "github.com/stretchr/testify/assert" 16 | "github.com/stretchr/testify/require" 17 | ) 18 | 19 | func TestLabelsAPI(t *testing.T) { 20 | client := influxdb2.NewClientWithOptions(serverURL, authToken, influxdb2.DefaultOptions().SetLogLevel(3)) 21 | labelsAPI := client.LabelsAPI() 22 | orgAPI := client.OrganizationsAPI() 23 | 24 | ctx := context.Background() 25 | 26 | myorg, err := orgAPI.FindOrganizationByName(ctx, "my-org") 27 | require.Nil(t, err, err) 28 | require.NotNil(t, myorg) 29 | 30 | labels, err := labelsAPI.GetLabels(ctx) 31 | require.Nil(t, err, err) 32 | require.NotNil(t, labels) 33 | assert.Len(t, *labels, 0) 34 | 35 | labelName := "Active State" 36 | props := map[string]string{"color": "#33ffddd", "description": "Marks org active"} 37 | label, err := labelsAPI.CreateLabelWithName(ctx, myorg, labelName, props) 38 | require.Nil(t, err, err) 39 | require.NotNil(t, label) 40 | assert.Equal(t, labelName, *label.Name) 41 | require.NotNil(t, label.Properties) 42 | assert.Equal(t, props, label.Properties.AdditionalProperties) 43 | 44 | //remove properties 45 | label.Properties.AdditionalProperties = map[string]string{"color": "", "description": ""} 46 | label2, err := labelsAPI.UpdateLabel(ctx, label) 47 | require.Nil(t, err, err) 48 | require.NotNil(t, label2) 49 | assert.Equal(t, labelName, *label2.Name) 50 | assert.Nil(t, label2.Properties) 51 | 52 | label2, err = labelsAPI.FindLabelByID(ctx, *label.Id) 53 | require.Nil(t, err, err) 54 | require.NotNil(t, label2) 55 | assert.Equal(t, labelName, *label2.Name) 56 | 57 | label2, err = labelsAPI.FindLabelByName(ctx, *myorg.Id, labelName) 58 | require.Nil(t, err, err) 59 | require.NotNil(t, label2) 60 | assert.Equal(t, labelName, *label2.Name) 61 | 62 | label2, err = labelsAPI.FindLabelByName(ctx, *myorg.Id, "wrong label") 63 | assert.NotNil(t, err, err) 64 | assert.Nil(t, label2) 65 | 66 | labels, err = labelsAPI.GetLabels(ctx) 67 | require.Nil(t, err, err) 68 | require.NotNil(t, labels) 69 | assert.Len(t, *labels, 1) 70 | 71 | labels, err = labelsAPI.FindLabelsByOrg(ctx, myorg) 72 | require.Nil(t, err, err) 73 | require.NotNil(t, labels) 74 | assert.Len(t, *labels, 1) 75 | 76 | labels, err = labelsAPI.FindLabelsByOrgID(ctx, *myorg.Id) 77 | require.Nil(t, err, err) 78 | require.NotNil(t, labels) 79 | assert.Len(t, *labels, 1) 80 | 81 | // duplicate label 82 | label2, err = labelsAPI.CreateLabelWithName(ctx, myorg, labelName, nil) 83 | assert.NotNil(t, err) 84 | assert.Nil(t, label2) 85 | 86 | err = labelsAPI.DeleteLabel(ctx, label) 87 | require.Nil(t, err, err) 88 | // 89 | err = labelsAPI.DeleteLabel(ctx, label) 90 | assert.NotNil(t, err, err) 91 | } 92 | 93 | func TestLabelsAPI_failing(t *testing.T) { 94 | client := influxdb2.NewClient(serverURL, authToken) 95 | clientUnAuth := influxdb2.NewClient(serverURL, "invalid_token") 96 | labelsAPI := client.LabelsAPI() 97 | orgAPI := client.OrganizationsAPI() 98 | ctx := context.Background() 99 | 100 | invalidID := "xyz" 101 | wrongID := "1000000000000000" 102 | 103 | var label = &domain.Label{ 104 | Id: &wrongID, 105 | } 106 | 107 | org, err := orgAPI.FindOrganizationByName(ctx, "my-org") 108 | require.Nil(t, err, err) 109 | require.NotNil(t, org) 110 | 111 | label, err = labelsAPI.UpdateLabel(ctx, label) 112 | assert.NotNil(t, err) 113 | assert.Nil(t, label) 114 | 115 | label, err = labelsAPI.FindLabelByID(ctx, wrongID) 116 | assert.NotNil(t, err) 117 | assert.Nil(t, label) 118 | 119 | labels, err := clientUnAuth.LabelsAPI().FindLabelsByOrgID(ctx, invalidID) 120 | assert.NotNil(t, err) 121 | assert.Nil(t, labels) 122 | 123 | err = labelsAPI.DeleteLabelWithID(ctx, invalidID) 124 | assert.NotNil(t, err) 125 | } 126 | 127 | func TestLabelsAPI_requestFailing(t *testing.T) { 128 | client := influxdb2.NewClient("serverURL", authToken) 129 | labelsAPI := client.LabelsAPI() 130 | ctx := context.Background() 131 | 132 | anID := "1000000000000000" 133 | 134 | label := &domain.Label{Id: &anID} 135 | 136 | _, err := labelsAPI.GetLabels(ctx) 137 | assert.NotNil(t, err) 138 | 139 | _, err = labelsAPI.FindLabelByName(ctx, anID, "name") 140 | assert.NotNil(t, err) 141 | 142 | _, err = labelsAPI.FindLabelByID(ctx, anID) 143 | assert.NotNil(t, err) 144 | 145 | _, err = labelsAPI.CreateLabelWithNameWithID(ctx, anID, "name", nil) 146 | assert.NotNil(t, err) 147 | 148 | _, err = labelsAPI.UpdateLabel(ctx, label) 149 | assert.NotNil(t, err) 150 | 151 | err = labelsAPI.DeleteLabel(ctx, label) 152 | assert.NotNil(t, err) 153 | } 154 | -------------------------------------------------------------------------------- /api/paging.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package api 6 | 7 | import "github.com/influxdata/influxdb-client-go/v2/domain" 8 | 9 | // PagingOption is the function type for applying paging option 10 | type PagingOption func(p *Paging) 11 | 12 | // Paging holds pagination parameters for various Get* functions of InfluxDB 2 API 13 | // Not the all options are usable for some Get* functions 14 | type Paging struct { 15 | // Starting offset for returning items 16 | // Default 0. 17 | offset domain.Offset 18 | // Maximum number of items returned. 19 | // Default 0 - not applied 20 | limit domain.Limit 21 | // What field should be used for sorting 22 | sortBy string 23 | // Changes sorting direction 24 | descending domain.Descending 25 | // The last resource ID from which to seek from (but not including). 26 | // This is to be used instead of `offset`. 27 | after domain.After 28 | } 29 | 30 | // defaultPagingOptions returns default paging options: offset 0, limit 0 (not applied), default sorting, ascending 31 | func defaultPaging() *Paging { 32 | return &Paging{limit: 0, offset: 0, sortBy: "", descending: false, after: ""} 33 | } 34 | 35 | // PagingWithLimit sets limit option - maximum number of items returned. 36 | func PagingWithLimit(limit int) PagingOption { 37 | return func(p *Paging) { 38 | p.limit = domain.Limit(limit) 39 | } 40 | } 41 | 42 | // PagingWithOffset set starting offset for returning items. Default 0. 43 | func PagingWithOffset(offset int) PagingOption { 44 | return func(p *Paging) { 45 | p.offset = domain.Offset(offset) 46 | } 47 | } 48 | 49 | // PagingWithSortBy sets field name which should be used for sorting 50 | func PagingWithSortBy(sortBy string) PagingOption { 51 | return func(p *Paging) { 52 | p.sortBy = sortBy 53 | } 54 | } 55 | 56 | // PagingWithDescending changes sorting direction 57 | func PagingWithDescending(descending bool) PagingOption { 58 | return func(p *Paging) { 59 | p.descending = domain.Descending(descending) 60 | } 61 | } 62 | 63 | // PagingWithAfter set after option - the last resource ID from which to seek from (but not including). 64 | // This is to be used instead of `offset`. 65 | func PagingWithAfter(after string) PagingOption { 66 | return func(p *Paging) { 67 | p.after = domain.After(after) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /api/paging_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package api 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/influxdata/influxdb-client-go/v2/domain" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestPaging(t *testing.T) { 15 | paging := &Paging{} 16 | PagingWithOffset(10)(paging) 17 | PagingWithLimit(100)(paging) 18 | PagingWithSortBy("name")(paging) 19 | PagingWithDescending(true)(paging) 20 | PagingWithAfter("1111")(paging) 21 | assert.True(t, bool(paging.descending)) 22 | assert.Equal(t, domain.Limit(100), paging.limit) 23 | assert.Equal(t, domain.Offset(10), paging.offset) 24 | assert.Equal(t, "name", paging.sortBy) 25 | assert.Equal(t, domain.After("1111"), paging.after) 26 | 27 | paging = &Paging{} 28 | PagingWithLimit(0)(paging) 29 | assert.Equal(t, domain.Limit(0), paging.limit) 30 | 31 | paging = &Paging{} 32 | PagingWithLimit(1000)(paging) 33 | assert.Equal(t, domain.Limit(1000), paging.limit) 34 | } 35 | -------------------------------------------------------------------------------- /api/query/table.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package query defined types for representing flux query result 6 | package query 7 | 8 | import ( 9 | "fmt" 10 | "sort" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | // FluxTableMetadata holds flux query result table information represented by collection of columns. 16 | // Each new table is introduced by annotations 17 | type FluxTableMetadata struct { 18 | position int 19 | columns []*FluxColumn 20 | } 21 | 22 | // FluxColumn holds flux query table column properties 23 | type FluxColumn struct { 24 | index int 25 | name string 26 | dataType string 27 | group bool 28 | defaultValue string 29 | } 30 | 31 | // FluxRecord represents row in the flux query result table 32 | type FluxRecord struct { 33 | table int 34 | values map[string]interface{} 35 | } 36 | 37 | // NewFluxTableMetadata creates FluxTableMetadata for the table on position 38 | func NewFluxTableMetadata(position int) *FluxTableMetadata { 39 | return NewFluxTableMetadataFull(position, make([]*FluxColumn, 0, 10)) 40 | } 41 | 42 | // NewFluxTableMetadataFull creates FluxTableMetadata 43 | func NewFluxTableMetadataFull(position int, columns []*FluxColumn) *FluxTableMetadata { 44 | return &FluxTableMetadata{position: position, columns: columns} 45 | } 46 | 47 | // Position returns position of the table in the flux query result 48 | func (f *FluxTableMetadata) Position() int { 49 | return f.position 50 | } 51 | 52 | // Columns returns slice of flux query result table 53 | func (f *FluxTableMetadata) Columns() []*FluxColumn { 54 | return f.columns 55 | } 56 | 57 | // AddColumn adds column definition to table metadata 58 | func (f *FluxTableMetadata) AddColumn(column *FluxColumn) *FluxTableMetadata { 59 | f.columns = append(f.columns, column) 60 | return f 61 | } 62 | 63 | // Column returns flux table column by index. 64 | // Returns nil if index is out of the bounds. 65 | func (f *FluxTableMetadata) Column(index int) *FluxColumn { 66 | if len(f.columns) == 0 || index < 0 || index >= len(f.columns) { 67 | return nil 68 | } 69 | return f.columns[index] 70 | } 71 | 72 | // String returns FluxTableMetadata string dump 73 | func (f *FluxTableMetadata) String() string { 74 | var buffer strings.Builder 75 | for i, c := range f.columns { 76 | if i > 0 { 77 | buffer.WriteString(",") 78 | } 79 | buffer.WriteString("col") 80 | buffer.WriteString(c.String()) 81 | } 82 | return buffer.String() 83 | } 84 | 85 | // NewFluxColumn creates FluxColumn for position 86 | func NewFluxColumn(index int) *FluxColumn { 87 | return &FluxColumn{index: index} 88 | } 89 | 90 | // NewFluxColumnFull creates FluxColumn 91 | func NewFluxColumnFull(dataType string, defaultValue string, name string, group bool, index int) *FluxColumn { 92 | return &FluxColumn{index: index, name: name, dataType: dataType, group: group, defaultValue: defaultValue} 93 | } 94 | 95 | // SetDefaultValue sets default value for the column 96 | func (f *FluxColumn) SetDefaultValue(defaultValue string) { 97 | f.defaultValue = defaultValue 98 | } 99 | 100 | // SetGroup set group flag for the column 101 | func (f *FluxColumn) SetGroup(group bool) { 102 | f.group = group 103 | } 104 | 105 | // SetDataType sets data type for the column 106 | func (f *FluxColumn) SetDataType(dataType string) { 107 | f.dataType = dataType 108 | } 109 | 110 | // SetName sets name of the column 111 | func (f *FluxColumn) SetName(name string) { 112 | f.name = name 113 | } 114 | 115 | // DefaultValue returns default value of the column 116 | func (f *FluxColumn) DefaultValue() string { 117 | return f.defaultValue 118 | } 119 | 120 | // IsGroup return true if the column is grouping column 121 | func (f *FluxColumn) IsGroup() bool { 122 | return f.group 123 | } 124 | 125 | // DataType returns data type of the column 126 | func (f *FluxColumn) DataType() string { 127 | return f.dataType 128 | } 129 | 130 | // Name returns name of the column 131 | func (f *FluxColumn) Name() string { 132 | return f.name 133 | } 134 | 135 | // Index returns index of the column 136 | func (f *FluxColumn) Index() int { 137 | return f.index 138 | } 139 | 140 | // String returns FluxColumn string dump 141 | func (f *FluxColumn) String() string { 142 | return fmt.Sprintf("{%d: name: %s, datatype: %s, defaultValue: %s, group: %v}", f.index, f.name, f.dataType, f.defaultValue, f.group) 143 | } 144 | 145 | // NewFluxRecord returns new record for the table with values 146 | func NewFluxRecord(table int, values map[string]interface{}) *FluxRecord { 147 | return &FluxRecord{table: table, values: values} 148 | } 149 | 150 | // Table returns value of the table column 151 | // It returns zero if the table column is not found 152 | func (r *FluxRecord) Table() int { 153 | return int(intValue(r.values, "table")) 154 | } 155 | 156 | // Start returns the inclusive lower time bound of all records in the current table. 157 | // Returns empty time.Time if there is no column "_start". 158 | func (r *FluxRecord) Start() time.Time { 159 | return timeValue(r.values, "_start") 160 | } 161 | 162 | // Stop returns the exclusive upper time bound of all records in the current table. 163 | // Returns empty time.Time if there is no column "_stop". 164 | func (r *FluxRecord) Stop() time.Time { 165 | return timeValue(r.values, "_stop") 166 | } 167 | 168 | // Time returns the time of the record. 169 | // Returns empty time.Time if there is no column "_time". 170 | func (r *FluxRecord) Time() time.Time { 171 | return timeValue(r.values, "_time") 172 | } 173 | 174 | // Value returns the default _value column value or nil if not present 175 | func (r *FluxRecord) Value() interface{} { 176 | return r.ValueByKey("_value") 177 | } 178 | 179 | // Field returns the field name. 180 | // Returns empty string if there is no column "_field". 181 | func (r *FluxRecord) Field() string { 182 | return stringValue(r.values, "_field") 183 | } 184 | 185 | // Result returns the value of the _result column, which represents result name. 186 | // Returns empty string if there is no column "result". 187 | func (r *FluxRecord) Result() string { 188 | return stringValue(r.values, "result") 189 | } 190 | 191 | // Measurement returns the measurement name of the record 192 | // Returns empty string if there is no column "_measurement". 193 | func (r *FluxRecord) Measurement() string { 194 | return stringValue(r.values, "_measurement") 195 | } 196 | 197 | // Values returns map of the values where key is the column name 198 | func (r *FluxRecord) Values() map[string]interface{} { 199 | return r.values 200 | } 201 | 202 | // ValueByKey returns value for given column key for the record or nil of result has no value the column key 203 | func (r *FluxRecord) ValueByKey(key string) interface{} { 204 | return r.values[key] 205 | } 206 | 207 | // String returns FluxRecord string dump 208 | func (r *FluxRecord) String() string { 209 | if len(r.values) == 0 { 210 | return "" 211 | } 212 | 213 | i := 0 214 | keys := make([]string, len(r.values)) 215 | for k := range r.values { 216 | keys[i] = k 217 | i++ 218 | } 219 | sort.Strings(keys) 220 | var buffer strings.Builder 221 | buffer.WriteString(fmt.Sprintf("%s:%v", keys[0], r.values[keys[0]])) 222 | for _, k := range keys[1:] { 223 | buffer.WriteString(",") 224 | buffer.WriteString(fmt.Sprintf("%s:%v", k, r.values[k])) 225 | } 226 | return buffer.String() 227 | } 228 | 229 | // timeValue returns time.Time value from values map according to the key 230 | // Empty time.Time value is returned if key is not found 231 | func timeValue(values map[string]interface{}, key string) time.Time { 232 | if val, ok := values[key]; ok { 233 | if t, ok := val.(time.Time); ok { 234 | return t 235 | } 236 | } 237 | return time.Time{} 238 | } 239 | 240 | // stringValue returns string value from values map according to the key 241 | // Empty string is returned if key is not found 242 | func stringValue(values map[string]interface{}, key string) string { 243 | if val, ok := values[key]; ok { 244 | if s, ok := val.(string); ok { 245 | return s 246 | } 247 | } 248 | return "" 249 | } 250 | 251 | // intValue returns int64 value from values map according to the key 252 | // Zero value is returned if key is not found 253 | func intValue(values map[string]interface{}, key string) int64 { 254 | if val, ok := values[key]; ok { 255 | if i, ok := val.(int64); ok { 256 | return i 257 | } 258 | } 259 | return 0 260 | } 261 | -------------------------------------------------------------------------------- /api/query/table_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package query 6 | 7 | import ( 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func mustParseTime(s string) time.Time { 16 | t, err := time.Parse(time.RFC3339, s) 17 | if err != nil { 18 | panic(err) 19 | } 20 | return t 21 | } 22 | 23 | func TestTable(t *testing.T) { 24 | table := &FluxTableMetadata{position: 1} 25 | table.AddColumn(&FluxColumn{dataType: "string", defaultValue: "_result", name: "result", group: false, index: 0}) 26 | table.AddColumn(&FluxColumn{dataType: "long", defaultValue: "10", name: "_table", group: false, index: 1}) 27 | table.AddColumn(&FluxColumn{dataType: "dateTime:RFC3339", defaultValue: "", name: "_start", group: true, index: 2}) 28 | table.AddColumn(&FluxColumn{dataType: "double", defaultValue: "1.1", name: "_value", group: false, index: 3}) 29 | table.AddColumn(&FluxColumn{dataType: "string", defaultValue: "", name: "_field", group: true, index: 4}) 30 | require.Len(t, table.columns, 5) 31 | 32 | assert.Equal(t, 1, table.Position()) 33 | require.NotNil(t, table.Column(0)) 34 | assert.Equal(t, "_result", table.Column(0).DefaultValue()) 35 | assert.Equal(t, "string", table.Column(0).DataType()) 36 | assert.Equal(t, "result", table.Column(0).Name()) 37 | assert.Equal(t, 0, table.Column(0).Index()) 38 | assert.Equal(t, false, table.Column(0).IsGroup()) 39 | 40 | require.NotNil(t, table.Column(1)) 41 | assert.Equal(t, "10", table.Column(1).DefaultValue()) 42 | assert.Equal(t, "long", table.Column(1).DataType()) 43 | assert.Equal(t, "_table", table.Column(1).Name()) 44 | assert.Equal(t, 1, table.Column(1).Index()) 45 | assert.Equal(t, false, table.Column(1).IsGroup()) 46 | 47 | require.NotNil(t, table.Column(2)) 48 | assert.Equal(t, "", table.Column(2).DefaultValue()) 49 | assert.Equal(t, "dateTime:RFC3339", table.Column(2).DataType()) 50 | assert.Equal(t, "_start", table.Column(2).Name()) 51 | assert.Equal(t, 2, table.Column(2).Index()) 52 | assert.Equal(t, true, table.Column(2).IsGroup()) 53 | 54 | require.NotNil(t, table.Column(3)) 55 | assert.Equal(t, "1.1", table.Column(3).DefaultValue()) 56 | assert.Equal(t, "double", table.Column(3).DataType()) 57 | assert.Equal(t, "_value", table.Column(3).Name()) 58 | assert.Equal(t, 3, table.Column(3).Index()) 59 | assert.Equal(t, false, table.Column(3).IsGroup()) 60 | 61 | require.NotNil(t, table.Column(4)) 62 | assert.Equal(t, "", table.Column(4).DefaultValue()) 63 | assert.Equal(t, "string", table.Column(4).DataType()) 64 | assert.Equal(t, "_field", table.Column(4).Name()) 65 | assert.Equal(t, 4, table.Column(4).Index()) 66 | assert.Equal(t, true, table.Column(4).IsGroup()) 67 | } 68 | 69 | func TestRecord(t *testing.T) { 70 | record := &FluxRecord{table: 2, 71 | values: map[string]interface{}{ 72 | "result": "_result", 73 | "table": int64(2), 74 | "_start": mustParseTime("2020-02-17T22:19:49.747562847Z"), 75 | "_stop": mustParseTime("2020-02-18T22:19:49.747562847Z"), 76 | "_time": mustParseTime("2020-02-18T10:34:08.135814545Z"), 77 | "_value": 1.4, 78 | "_field": "f", 79 | "_measurement": "test", 80 | "a": "1", 81 | "b": "adsfasdf", 82 | }, 83 | } 84 | require.Len(t, record.values, 10) 85 | assert.Equal(t, mustParseTime("2020-02-17T22:19:49.747562847Z"), record.Start()) 86 | assert.Equal(t, mustParseTime("2020-02-18T22:19:49.747562847Z"), record.Stop()) 87 | assert.Equal(t, mustParseTime("2020-02-18T10:34:08.135814545Z"), record.Time()) 88 | assert.Equal(t, "_result", record.Result()) 89 | assert.Equal(t, "f", record.Field()) 90 | assert.Equal(t, 1.4, record.Value()) 91 | assert.Equal(t, "test", record.Measurement()) 92 | assert.Equal(t, 2, record.Table()) 93 | 94 | agRec := &FluxRecord{table: 0, 95 | values: map[string]interface{}{ 96 | "room": "bathroom", 97 | "sensor": "SHT", 98 | "temp": 24.3, 99 | "hum": 42, 100 | }, 101 | } 102 | require.Len(t, agRec.values, 4) 103 | assert.Equal(t, time.Time{}, agRec.Start()) 104 | assert.Equal(t, time.Time{}, agRec.Stop()) 105 | assert.Equal(t, time.Time{}, agRec.Time()) 106 | assert.Equal(t, "", agRec.Field()) 107 | assert.Equal(t, "", agRec.Result()) 108 | assert.Nil(t, agRec.Value()) 109 | assert.Equal(t, "", agRec.Measurement()) 110 | assert.Equal(t, 0, agRec.Table()) 111 | assert.Equal(t, 24.3, agRec.ValueByKey("temp")) 112 | assert.Equal(t, 42, agRec.ValueByKey("hum")) 113 | assert.Nil(t, agRec.ValueByKey("notexist")) 114 | } 115 | -------------------------------------------------------------------------------- /api/reflection.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "reflect" 5 | "time" 6 | ) 7 | 8 | // getFieldType extracts type of value 9 | func getFieldType(v reflect.Value) reflect.Type { 10 | t := v.Type() 11 | if t.Kind() == reflect.Ptr { 12 | t = t.Elem() 13 | v = v.Elem() 14 | } 15 | if t.Kind() == reflect.Interface && !v.IsNil() { 16 | t = reflect.ValueOf(v.Interface()).Type() 17 | } 18 | return t 19 | } 20 | 21 | // timeType is the exact type for the Time 22 | var timeType = reflect.TypeOf(time.Time{}) 23 | 24 | // validFieldType validates that t is primitive type or string or interface 25 | func validFieldType(t reflect.Type) bool { 26 | return (t.Kind() > reflect.Invalid && t.Kind() < reflect.Complex64) || 27 | t.Kind() == reflect.String || 28 | t == timeType 29 | } 30 | -------------------------------------------------------------------------------- /api/users.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package api 6 | 7 | import ( 8 | "context" 9 | "encoding/base64" 10 | "fmt" 11 | nethttp "net/http" 12 | "net/http/cookiejar" 13 | "sync" 14 | 15 | "github.com/influxdata/influxdb-client-go/v2/api/http" 16 | "github.com/influxdata/influxdb-client-go/v2/domain" 17 | "golang.org/x/net/publicsuffix" 18 | ) 19 | 20 | // UsersAPI provides methods for managing users in a InfluxDB server 21 | type UsersAPI interface { 22 | // GetUsers returns all users 23 | GetUsers(ctx context.Context) (*[]domain.User, error) 24 | // FindUserByID returns user with userID 25 | FindUserByID(ctx context.Context, userID string) (*domain.User, error) 26 | // FindUserByName returns user with name userName 27 | FindUserByName(ctx context.Context, userName string) (*domain.User, error) 28 | // CreateUser creates new user 29 | CreateUser(ctx context.Context, user *domain.User) (*domain.User, error) 30 | // CreateUserWithName creates new user with userName 31 | CreateUserWithName(ctx context.Context, userName string) (*domain.User, error) 32 | // UpdateUser updates user 33 | UpdateUser(ctx context.Context, user *domain.User) (*domain.User, error) 34 | // UpdateUserPassword sets password for a user 35 | UpdateUserPassword(ctx context.Context, user *domain.User, password string) error 36 | // UpdateUserPasswordWithID sets password for a user with userID 37 | UpdateUserPasswordWithID(ctx context.Context, userID string, password string) error 38 | // DeleteUserWithID deletes an user with userID 39 | DeleteUserWithID(ctx context.Context, userID string) error 40 | // DeleteUser deletes an user 41 | DeleteUser(ctx context.Context, user *domain.User) error 42 | // Me returns actual user 43 | Me(ctx context.Context) (*domain.User, error) 44 | // MeUpdatePassword set password of actual user 45 | MeUpdatePassword(ctx context.Context, oldPassword, newPassword string) error 46 | // SignIn exchanges username and password credentials to establish an authenticated session with the InfluxDB server. The Client's authentication token is then ignored, it can be empty. 47 | SignIn(ctx context.Context, username, password string) error 48 | // SignOut signs out previously signed-in user 49 | SignOut(ctx context.Context) error 50 | } 51 | 52 | // usersAPI implements UsersAPI 53 | type usersAPI struct { 54 | apiClient *domain.Client 55 | httpService http.Service 56 | httpClient *nethttp.Client 57 | deleteCookieJar bool 58 | lock sync.Mutex 59 | } 60 | 61 | // NewUsersAPI creates new instance of UsersAPI 62 | func NewUsersAPI(apiClient *domain.Client, httpService http.Service, httpClient *nethttp.Client) UsersAPI { 63 | return &usersAPI{ 64 | apiClient: apiClient, 65 | httpService: httpService, 66 | httpClient: httpClient, 67 | } 68 | } 69 | 70 | func (u *usersAPI) GetUsers(ctx context.Context) (*[]domain.User, error) { 71 | params := &domain.GetUsersParams{} 72 | response, err := u.apiClient.GetUsers(ctx, params) 73 | if err != nil { 74 | return nil, err 75 | } 76 | return userResponsesToUsers(response.Users), nil 77 | } 78 | 79 | func (u *usersAPI) FindUserByID(ctx context.Context, userID string) (*domain.User, error) { 80 | params := &domain.GetUsersIDAllParams{ 81 | UserID: userID, 82 | } 83 | response, err := u.apiClient.GetUsersID(ctx, params) 84 | if err != nil { 85 | return nil, err 86 | } 87 | return userResponseToUser(response), nil 88 | } 89 | 90 | func (u *usersAPI) FindUserByName(ctx context.Context, userName string) (*domain.User, error) { 91 | users, err := u.GetUsers(ctx) 92 | if err != nil { 93 | return nil, err 94 | } 95 | var user *domain.User 96 | for _, u := range *users { 97 | if u.Name == userName { 98 | user = &u 99 | break 100 | } 101 | } 102 | if user == nil { 103 | return nil, fmt.Errorf("user '%s' not found", userName) 104 | } 105 | return user, nil 106 | } 107 | 108 | func (u *usersAPI) CreateUserWithName(ctx context.Context, userName string) (*domain.User, error) { 109 | user := &domain.User{Name: userName} 110 | return u.CreateUser(ctx, user) 111 | } 112 | 113 | func (u *usersAPI) CreateUser(ctx context.Context, user *domain.User) (*domain.User, error) { 114 | params := &domain.PostUsersAllParams{ 115 | Body: domain.PostUsersJSONRequestBody(*user), 116 | } 117 | response, err := u.apiClient.PostUsers(ctx, params) 118 | if err != nil { 119 | return nil, err 120 | } 121 | return userResponseToUser(response), nil 122 | } 123 | 124 | func (u *usersAPI) UpdateUser(ctx context.Context, user *domain.User) (*domain.User, error) { 125 | params := &domain.PatchUsersIDAllParams{ 126 | Body: domain.PatchUsersIDJSONRequestBody(*user), 127 | UserID: *user.Id, 128 | } 129 | response, err := u.apiClient.PatchUsersID(ctx, params) 130 | if err != nil { 131 | return nil, err 132 | } 133 | return userResponseToUser(response), nil 134 | } 135 | 136 | func (u *usersAPI) UpdateUserPassword(ctx context.Context, user *domain.User, password string) error { 137 | return u.UpdateUserPasswordWithID(ctx, *user.Id, password) 138 | } 139 | 140 | func (u *usersAPI) UpdateUserPasswordWithID(ctx context.Context, userID string, password string) error { 141 | params := &domain.PostUsersIDPasswordAllParams{ 142 | UserID: userID, 143 | Body: domain.PostUsersIDPasswordJSONRequestBody(domain.PasswordResetBody{Password: password}), 144 | } 145 | return u.apiClient.PostUsersIDPassword(ctx, params) 146 | } 147 | 148 | func (u *usersAPI) DeleteUser(ctx context.Context, user *domain.User) error { 149 | return u.DeleteUserWithID(ctx, *user.Id) 150 | } 151 | 152 | func (u *usersAPI) DeleteUserWithID(ctx context.Context, userID string) error { 153 | params := &domain.DeleteUsersIDAllParams{ 154 | UserID: userID, 155 | } 156 | return u.apiClient.DeleteUsersID(ctx, params) 157 | } 158 | 159 | func (u *usersAPI) Me(ctx context.Context) (*domain.User, error) { 160 | params := &domain.GetMeParams{} 161 | response, err := u.apiClient.GetMe(ctx, params) 162 | if err != nil { 163 | return nil, err 164 | } 165 | return userResponseToUser(response), nil 166 | } 167 | 168 | func (u *usersAPI) MeUpdatePassword(ctx context.Context, oldPassword, newPassword string) error { 169 | u.lock.Lock() 170 | defer u.lock.Unlock() 171 | me, err := u.Me(ctx) 172 | if err != nil { 173 | return err 174 | } 175 | creds := base64.StdEncoding.EncodeToString([]byte(me.Name + ":" + oldPassword)) 176 | auth := u.httpService.Authorization() 177 | defer u.httpService.SetAuthorization(auth) 178 | u.httpService.SetAuthorization("Basic " + creds) 179 | params := &domain.PutMePasswordAllParams{ 180 | Body: domain.PutMePasswordJSONRequestBody(domain.PasswordResetBody{Password: newPassword}), 181 | } 182 | return u.apiClient.PutMePassword(ctx, params) 183 | } 184 | 185 | func (u *usersAPI) SignIn(ctx context.Context, username, password string) error { 186 | u.lock.Lock() 187 | defer u.lock.Unlock() 188 | if u.httpClient.Jar == nil { 189 | jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) 190 | if err != nil { 191 | return err 192 | } 193 | u.httpClient.Jar = jar 194 | u.deleteCookieJar = true 195 | } 196 | creds := base64.StdEncoding.EncodeToString([]byte(username + ":" + password)) 197 | u.httpService.SetAuthorization("Basic " + creds) 198 | defer u.httpService.SetAuthorization("") 199 | return u.apiClient.PostSignin(ctx, &domain.PostSigninParams{}) 200 | } 201 | 202 | func (u *usersAPI) SignOut(ctx context.Context) error { 203 | u.lock.Lock() 204 | defer u.lock.Unlock() 205 | err := u.apiClient.PostSignout(ctx, &domain.PostSignoutParams{}) 206 | if u.deleteCookieJar { 207 | u.httpClient.Jar = nil 208 | } 209 | return err 210 | } 211 | 212 | func userResponseToUser(ur *domain.UserResponse) *domain.User { 213 | if ur == nil { 214 | return nil 215 | } 216 | user := &domain.User{ 217 | Id: ur.Id, 218 | Name: ur.Name, 219 | Status: userResponseStatusToUserStatus(ur.Status), 220 | } 221 | return user 222 | } 223 | 224 | func userResponseStatusToUserStatus(urs *domain.UserResponseStatus) *domain.UserStatus { 225 | if urs == nil { 226 | return nil 227 | } 228 | us := domain.UserStatus(*urs) 229 | return &us 230 | } 231 | 232 | func userResponsesToUsers(urs *[]domain.UserResponse) *[]domain.User { 233 | if urs == nil { 234 | return nil 235 | } 236 | us := make([]domain.User, len(*urs)) 237 | for i, ur := range *urs { 238 | us[i] = *userResponseToUser(&ur) 239 | } 240 | return &us 241 | } 242 | -------------------------------------------------------------------------------- /api/users_e2e_test.go: -------------------------------------------------------------------------------- 1 | // +build e2e 2 | 3 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 4 | // Use of this source code is governed by MIT 5 | // license that can be found in the LICENSE file. 6 | 7 | package api_test 8 | 9 | import ( 10 | "context" 11 | "testing" 12 | 13 | influxdb2 "github.com/influxdata/influxdb-client-go/v2" 14 | "github.com/influxdata/influxdb-client-go/v2/domain" 15 | "github.com/stretchr/testify/assert" 16 | "github.com/stretchr/testify/require" 17 | ) 18 | 19 | func TestUsersAPI(t *testing.T) { 20 | client := influxdb2.NewClient(serverURL, authToken) 21 | usersAPI := client.UsersAPI() 22 | ctx := context.Background() 23 | 24 | me, err := usersAPI.Me(ctx) 25 | require.Nil(t, err) 26 | require.NotNil(t, me) 27 | 28 | users, err := usersAPI.GetUsers(ctx) 29 | require.Nil(t, err) 30 | require.NotNil(t, users) 31 | assert.Len(t, *users, 1) 32 | 33 | user, err := usersAPI.CreateUserWithName(ctx, "user-01") 34 | require.Nil(t, err) 35 | require.NotNil(t, user) 36 | 37 | // create duplicate user 38 | user2, err := usersAPI.CreateUserWithName(ctx, "user-01") 39 | assert.NotNil(t, err) 40 | assert.Nil(t, user2) 41 | 42 | users, err = usersAPI.GetUsers(ctx) 43 | require.Nil(t, err) 44 | require.NotNil(t, users) 45 | assert.Len(t, *users, 2) 46 | 47 | status := domain.UserStatusInactive 48 | user.Status = &status 49 | user, err = usersAPI.UpdateUser(ctx, user) 50 | require.Nil(t, err) 51 | require.NotNil(t, user) 52 | assert.Equal(t, status, *user.Status) 53 | 54 | user2 = &domain.User{ 55 | Id: user.Id, 56 | Name: "my-user", 57 | } 58 | //update username to existing user 59 | user2, err = usersAPI.UpdateUser(ctx, user2) 60 | assert.NotNil(t, err) 61 | assert.Nil(t, user2) 62 | 63 | user, err = usersAPI.FindUserByID(ctx, *user.Id) 64 | require.Nil(t, err) 65 | require.NotNil(t, user) 66 | 67 | err = usersAPI.UpdateUserPassword(ctx, user, "my-password") 68 | require.Nil(t, err) 69 | 70 | err = usersAPI.DeleteUser(ctx, user) 71 | require.Nil(t, err) 72 | 73 | users, err = usersAPI.GetUsers(ctx) 74 | require.Nil(t, err) 75 | require.NotNil(t, users) 76 | assert.Len(t, *users, 1) 77 | 78 | // it fails, https://github.com/influxdata/influxdb/pull/15981 79 | //err = usersAPI.MeUpdatePassword(ctx, "my-password", "my-new-password") 80 | //assert.Nil(t, err) 81 | 82 | //err = usersAPI.MeUpdatePassword(ctx, "my-new-password", "my-password") 83 | //assert.Nil(t, err) 84 | } 85 | 86 | func TestUsersAPI_failing(t *testing.T) { 87 | client := influxdb2.NewClient(serverURL, authToken) 88 | usersAPI := client.UsersAPI() 89 | ctx := context.Background() 90 | 91 | invalidID := "aaaaaa" 92 | 93 | user, err := usersAPI.FindUserByID(ctx, invalidID) 94 | assert.NotNil(t, err) 95 | assert.Nil(t, user) 96 | 97 | user, err = usersAPI.FindUserByName(ctx, "not-existing-name") 98 | assert.NotNil(t, err) 99 | assert.Nil(t, user) 100 | 101 | err = usersAPI.DeleteUserWithID(ctx, invalidID) 102 | assert.NotNil(t, err) 103 | 104 | err = usersAPI.UpdateUserPasswordWithID(ctx, invalidID, "pass") 105 | assert.NotNil(t, err) 106 | } 107 | 108 | func TestUsersAPI_requestFailing(t *testing.T) { 109 | client := influxdb2.NewClient("serverURL", authToken) 110 | usersAPI := client.UsersAPI() 111 | ctx := context.Background() 112 | 113 | invalidID := "aaaaaa" 114 | 115 | user := &domain.User{ 116 | Id: &invalidID, 117 | } 118 | _, err := usersAPI.GetUsers(ctx) 119 | assert.NotNil(t, err) 120 | 121 | _, err = usersAPI.FindUserByID(ctx, invalidID) 122 | assert.NotNil(t, err) 123 | 124 | _, err = usersAPI.FindUserByName(ctx, "not-existing-name") 125 | assert.NotNil(t, err) 126 | 127 | _, err = usersAPI.CreateUserWithName(ctx, "not-existing-name") 128 | assert.NotNil(t, err) 129 | 130 | _, err = usersAPI.UpdateUser(ctx, user) 131 | assert.NotNil(t, err) 132 | 133 | err = usersAPI.UpdateUserPasswordWithID(ctx, invalidID, "pass") 134 | assert.NotNil(t, err) 135 | 136 | err = usersAPI.DeleteUserWithID(ctx, invalidID) 137 | assert.NotNil(t, err) 138 | 139 | _, err = usersAPI.Me(ctx) 140 | assert.NotNil(t, err) 141 | 142 | err = usersAPI.MeUpdatePassword(ctx, "my-password", "my-new-password") 143 | assert.NotNil(t, err) 144 | 145 | err = usersAPI.SignIn(ctx, "user", "my-password") 146 | assert.NotNil(t, err) 147 | 148 | err = usersAPI.SignOut(ctx) 149 | assert.NotNil(t, err) 150 | } 151 | 152 | func TestSignInOut(t *testing.T) { 153 | ctx := context.Background() 154 | client := influxdb2.NewClient("http://localhost:8086", "") 155 | 156 | usersAPI := client.UsersAPI() 157 | 158 | err := usersAPI.SignIn(ctx, "my-user", "my-password") 159 | require.Nil(t, err) 160 | 161 | // try authorized calls 162 | orgs, err := client.OrganizationsAPI().GetOrganizations(ctx) 163 | assert.Nil(t, err) 164 | assert.NotNil(t, orgs) 165 | 166 | // try authorized calls 167 | buckets, err := client.BucketsAPI().GetBuckets(ctx) 168 | assert.Nil(t, err) 169 | assert.NotNil(t, buckets) 170 | 171 | // try authorized calls 172 | err = client.WriteAPIBlocking("my-org", "my-bucket").WriteRecord(ctx, "test,a=rock,b=local f=1.2,i=-5i") 173 | assert.NoError(t, err) 174 | 175 | res, err := client.QueryAPI("my-org").QueryRaw(context.Background(), `from(bucket:"my-bucket")|> range(start: -24h) |> filter(fn: (r) => r._measurement == "test")`, influxdb2.DefaultDialect()) 176 | assert.Nil(t, err) 177 | assert.NotNil(t, res) 178 | 179 | err = usersAPI.SignOut(ctx) 180 | assert.Nil(t, err) 181 | 182 | // unauthorized signout 183 | err = usersAPI.SignOut(ctx) 184 | assert.NotNil(t, err) 185 | 186 | // Unauthorized call 187 | _, err = client.OrganizationsAPI().GetOrganizations(ctx) 188 | assert.NotNil(t, err) 189 | 190 | // test wrong credentials 191 | err = usersAPI.SignIn(ctx, "my-user", "password") 192 | assert.NotNil(t, err) 193 | 194 | client.HTTPService().SetAuthorization("Token my-token") 195 | 196 | user, err := usersAPI.CreateUserWithName(ctx, "user-01") 197 | require.Nil(t, err) 198 | require.NotNil(t, user) 199 | 200 | // 2nd client to use for new user auth 201 | client2 := influxdb2.NewClient("http://localhost:8086", "") 202 | 203 | err = usersAPI.UpdateUserPassword(ctx, user, "123password") 204 | assert.Nil(t, err) 205 | 206 | err = client2.UsersAPI().SignIn(ctx, "user-01", "123password") 207 | assert.Nil(t, err) 208 | 209 | err = client2.UsersAPI().SignOut(ctx) 210 | assert.Nil(t, err) 211 | 212 | status := domain.UserStatusInactive 213 | user.Status = &status 214 | u, err := usersAPI.UpdateUser(ctx, user) 215 | assert.Nil(t, err) 216 | assert.NotNil(t, u) 217 | 218 | // log in inactive user, 219 | //err = client2.SignIn(ctx, "user-01", "123password") 220 | //assert.NotNil(t, err) 221 | 222 | err = usersAPI.DeleteUser(ctx, user) 223 | assert.Nil(t, err) 224 | 225 | client.Close() 226 | client2.Close() 227 | } 228 | -------------------------------------------------------------------------------- /api/write.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package api 6 | 7 | import ( 8 | "context" 9 | "strings" 10 | "sync" 11 | "sync/atomic" 12 | "time" 13 | 14 | http2 "github.com/influxdata/influxdb-client-go/v2/api/http" 15 | "github.com/influxdata/influxdb-client-go/v2/api/write" 16 | "github.com/influxdata/influxdb-client-go/v2/internal/log" 17 | iwrite "github.com/influxdata/influxdb-client-go/v2/internal/write" 18 | ) 19 | 20 | // WriteFailedCallback is synchronously notified in case non-blocking write fails. 21 | // batch contains complete payload, error holds detailed error information, 22 | // retryAttempts means number of retries, 0 if it failed during first write. 23 | // It must return true if WriteAPI should continue with retrying, false will discard the batch. 24 | type WriteFailedCallback func(batch string, error http2.Error, retryAttempts uint) bool 25 | 26 | // WriteAPI is Write client interface with non-blocking methods for writing time series data asynchronously in batches into an InfluxDB server. 27 | // WriteAPI can be used concurrently. 28 | // When using multiple goroutines for writing, use a single WriteAPI instance in all goroutines. 29 | type WriteAPI interface { 30 | // WriteRecord writes asynchronously line protocol record into bucket. 31 | // WriteRecord adds record into the buffer which is sent on the background when it reaches the batch size. 32 | // Blocking alternative is available in the WriteAPIBlocking interface 33 | WriteRecord(line string) 34 | // WritePoint writes asynchronously Point into bucket. 35 | // WritePoint adds Point into the buffer which is sent on the background when it reaches the batch size. 36 | // Blocking alternative is available in the WriteAPIBlocking interface 37 | WritePoint(point *write.Point) 38 | // Flush forces all pending writes from the buffer to be sent 39 | Flush() 40 | // Errors returns a channel for reading errors which occurs during async writes. 41 | // Must be called before performing any writes for errors to be collected. 42 | // The chan is unbuffered and must be drained or the writer will block. 43 | Errors() <-chan error 44 | // SetWriteFailedCallback sets callback allowing custom handling of failed writes. 45 | // If callback returns true, failed batch will be retried, otherwise discarded. 46 | SetWriteFailedCallback(cb WriteFailedCallback) 47 | } 48 | 49 | // WriteAPIImpl provides main implementation for WriteAPI 50 | type WriteAPIImpl struct { 51 | service *iwrite.Service 52 | writeBuffer []string 53 | 54 | errCh chan error 55 | writeCh chan *iwrite.Batch 56 | bufferCh chan string 57 | writeStop chan struct{} 58 | bufferStop chan struct{} 59 | bufferFlush chan struct{} 60 | doneCh chan struct{} 61 | bufferInfoCh chan writeBuffInfoReq 62 | writeInfoCh chan writeBuffInfoReq 63 | writeOptions *write.Options 64 | closingMu *sync.Mutex 65 | // more appropriate Bool type from sync/atomic cannot be used because it is available since go 1.19 66 | isErrChReader int32 67 | } 68 | 69 | type writeBuffInfoReq struct { 70 | writeBuffLen int 71 | } 72 | 73 | // NewWriteAPI returns new non-blocking write client for writing data to bucket belonging to org 74 | func NewWriteAPI(org string, bucket string, service http2.Service, writeOptions *write.Options) *WriteAPIImpl { 75 | w := &WriteAPIImpl{ 76 | service: iwrite.NewService(org, bucket, service, writeOptions), 77 | errCh: make(chan error, 1), 78 | writeBuffer: make([]string, 0, writeOptions.BatchSize()+1), 79 | writeCh: make(chan *iwrite.Batch), 80 | bufferCh: make(chan string), 81 | bufferStop: make(chan struct{}), 82 | writeStop: make(chan struct{}), 83 | bufferFlush: make(chan struct{}), 84 | doneCh: make(chan struct{}), 85 | bufferInfoCh: make(chan writeBuffInfoReq), 86 | writeInfoCh: make(chan writeBuffInfoReq), 87 | writeOptions: writeOptions, 88 | closingMu: &sync.Mutex{}, 89 | } 90 | 91 | go w.bufferProc() 92 | go w.writeProc() 93 | 94 | return w 95 | } 96 | 97 | // SetWriteFailedCallback sets callback allowing custom handling of failed writes. 98 | // If callback returns true, failed batch will be retried, otherwise discarded. 99 | func (w *WriteAPIImpl) SetWriteFailedCallback(cb WriteFailedCallback) { 100 | w.service.SetBatchErrorCallback(func(batch *iwrite.Batch, error2 http2.Error) bool { 101 | return cb(batch.Batch, error2, batch.RetryAttempts) 102 | }) 103 | } 104 | 105 | // Errors returns a channel for reading errors which occurs during async writes. 106 | // Must be called before performing any writes for errors to be collected. 107 | // New error is skipped when channel is not read. 108 | func (w *WriteAPIImpl) Errors() <-chan error { 109 | w.setErrChanRead() 110 | return w.errCh 111 | } 112 | 113 | // Flush forces all pending writes from the buffer to be sent. 114 | // Flush also tries sending batches from retry queue without additional retrying. 115 | func (w *WriteAPIImpl) Flush() { 116 | w.bufferFlush <- struct{}{} 117 | w.waitForFlushing() 118 | w.service.Flush() 119 | } 120 | 121 | func (w *WriteAPIImpl) waitForFlushing() { 122 | for { 123 | w.bufferInfoCh <- writeBuffInfoReq{} 124 | writeBuffInfo := <-w.bufferInfoCh 125 | if writeBuffInfo.writeBuffLen == 0 { 126 | break 127 | } 128 | log.Info("Waiting buffer is flushed") 129 | <-time.After(time.Millisecond) 130 | } 131 | for { 132 | w.writeInfoCh <- writeBuffInfoReq{} 133 | writeBuffInfo := <-w.writeInfoCh 134 | if writeBuffInfo.writeBuffLen == 0 { 135 | break 136 | } 137 | log.Info("Waiting buffer is flushed") 138 | <-time.After(time.Millisecond) 139 | } 140 | } 141 | 142 | func (w *WriteAPIImpl) bufferProc() { 143 | log.Info("Buffer proc started") 144 | ticker := time.NewTicker(time.Duration(w.writeOptions.FlushInterval()) * time.Millisecond) 145 | x: 146 | for { 147 | select { 148 | case line := <-w.bufferCh: 149 | w.writeBuffer = append(w.writeBuffer, line) 150 | if len(w.writeBuffer) == int(w.writeOptions.BatchSize()) { 151 | w.flushBuffer() 152 | } 153 | case <-ticker.C: 154 | w.flushBuffer() 155 | case <-w.bufferFlush: 156 | w.flushBuffer() 157 | case <-w.bufferStop: 158 | ticker.Stop() 159 | w.flushBuffer() 160 | break x 161 | case buffInfo := <-w.bufferInfoCh: 162 | buffInfo.writeBuffLen = len(w.bufferInfoCh) 163 | w.bufferInfoCh <- buffInfo 164 | } 165 | } 166 | log.Info("Buffer proc finished") 167 | w.doneCh <- struct{}{} 168 | } 169 | 170 | func (w *WriteAPIImpl) flushBuffer() { 171 | if len(w.writeBuffer) > 0 { 172 | log.Info("sending batch") 173 | batch := iwrite.NewBatch(buffer(w.writeBuffer), w.writeOptions.MaxRetryTime()) 174 | w.writeCh <- batch 175 | w.writeBuffer = w.writeBuffer[:0] 176 | } 177 | } 178 | func (w *WriteAPIImpl) isErrChanRead() bool { 179 | return atomic.LoadInt32(&w.isErrChReader) > 0 180 | } 181 | 182 | func (w *WriteAPIImpl) setErrChanRead() { 183 | atomic.StoreInt32(&w.isErrChReader, 1) 184 | } 185 | 186 | func (w *WriteAPIImpl) writeProc() { 187 | log.Info("Write proc started") 188 | x: 189 | for { 190 | select { 191 | case batch := <-w.writeCh: 192 | err := w.service.HandleWrite(context.Background(), batch) 193 | if err != nil && w.isErrChanRead() { 194 | select { 195 | case w.errCh <- err: 196 | default: 197 | log.Warn("Cannot write error to error channel, it is not read") 198 | } 199 | } 200 | case <-w.writeStop: 201 | log.Info("Write proc: received stop") 202 | break x 203 | case buffInfo := <-w.writeInfoCh: 204 | buffInfo.writeBuffLen = len(w.writeCh) 205 | w.writeInfoCh <- buffInfo 206 | } 207 | } 208 | log.Info("Write proc finished") 209 | w.doneCh <- struct{}{} 210 | } 211 | 212 | // Close finishes outstanding write operations, 213 | // stop background routines and closes all channels 214 | func (w *WriteAPIImpl) Close() { 215 | w.closingMu.Lock() 216 | defer w.closingMu.Unlock() 217 | if w.writeCh != nil { 218 | // Flush outstanding metrics 219 | w.Flush() 220 | 221 | // stop and wait for buffer proc 222 | close(w.bufferStop) 223 | <-w.doneCh 224 | 225 | close(w.bufferFlush) 226 | close(w.bufferCh) 227 | 228 | // stop and wait for write proc 229 | close(w.writeStop) 230 | <-w.doneCh 231 | 232 | close(w.writeCh) 233 | close(w.writeInfoCh) 234 | close(w.bufferInfoCh) 235 | w.writeCh = nil 236 | 237 | close(w.errCh) 238 | w.errCh = nil 239 | } 240 | } 241 | 242 | // WriteRecord writes asynchronously line protocol record into bucket. 243 | // WriteRecord adds record into the buffer which is sent on the background when it reaches the batch size. 244 | // Blocking alternative is available in the WriteAPIBlocking interface 245 | func (w *WriteAPIImpl) WriteRecord(line string) { 246 | b := []byte(line) 247 | b = append(b, 0xa) 248 | w.bufferCh <- string(b) 249 | } 250 | 251 | // WritePoint writes asynchronously Point into bucket. 252 | // WritePoint adds Point into the buffer which is sent on the background when it reaches the batch size. 253 | // Blocking alternative is available in the WriteAPIBlocking interface 254 | func (w *WriteAPIImpl) WritePoint(point *write.Point) { 255 | line, err := w.service.EncodePoints(point) 256 | if err != nil { 257 | log.Errorf("point encoding error: %s\n", err.Error()) 258 | if w.errCh != nil { 259 | w.errCh <- err 260 | } 261 | } else { 262 | w.bufferCh <- line 263 | } 264 | } 265 | 266 | func buffer(lines []string) string { 267 | return strings.Join(lines, "") 268 | } 269 | -------------------------------------------------------------------------------- /api/write/ext.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package write 6 | 7 | import ( 8 | "fmt" 9 | "strconv" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | // Point extension methods for test 15 | 16 | // PointToLineProtocolBuffer creates InfluxDB line protocol string from the Point, converting associated timestamp according to precision 17 | // and write result to the string builder 18 | func PointToLineProtocolBuffer(p *Point, sb *strings.Builder, precision time.Duration) { 19 | escapeKey(sb, p.Name(), false) 20 | sb.WriteRune(',') 21 | for i, t := range p.TagList() { 22 | if i > 0 { 23 | sb.WriteString(",") 24 | } 25 | escapeKey(sb, t.Key, true) 26 | sb.WriteString("=") 27 | escapeKey(sb, t.Value, true) 28 | } 29 | sb.WriteString(" ") 30 | for i, f := range p.FieldList() { 31 | if i > 0 { 32 | sb.WriteString(",") 33 | } 34 | escapeKey(sb, f.Key, true) 35 | sb.WriteString("=") 36 | switch f.Value.(type) { 37 | case string: 38 | sb.WriteString(`"`) 39 | escapeValue(sb, f.Value.(string)) 40 | sb.WriteString(`"`) 41 | default: 42 | sb.WriteString(fmt.Sprintf("%v", f.Value)) 43 | } 44 | switch f.Value.(type) { 45 | case int64: 46 | sb.WriteString("i") 47 | case uint64: 48 | sb.WriteString("u") 49 | } 50 | } 51 | if !p.Time().IsZero() { 52 | sb.WriteString(" ") 53 | switch precision { 54 | case time.Microsecond: 55 | sb.WriteString(strconv.FormatInt(p.Time().UnixNano()/1000, 10)) 56 | case time.Millisecond: 57 | sb.WriteString(strconv.FormatInt(p.Time().UnixNano()/1000000, 10)) 58 | case time.Second: 59 | sb.WriteString(strconv.FormatInt(p.Time().Unix(), 10)) 60 | default: 61 | sb.WriteString(strconv.FormatInt(p.Time().UnixNano(), 10)) 62 | } 63 | } 64 | sb.WriteString("\n") 65 | } 66 | 67 | // PointToLineProtocol creates InfluxDB line protocol string from the Point, converting associated timestamp according to precision 68 | func PointToLineProtocol(p *Point, precision time.Duration) string { 69 | var sb strings.Builder 70 | sb.Grow(1024) 71 | PointToLineProtocolBuffer(p, &sb, precision) 72 | return sb.String() 73 | } 74 | 75 | func escapeKey(sb *strings.Builder, key string, escapeEqual bool) { 76 | for _, r := range key { 77 | switch r { 78 | case '\n': 79 | sb.WriteString(`\\n`) 80 | continue 81 | case '\r': 82 | sb.WriteString(`\\r`) 83 | continue 84 | case '\t': 85 | sb.WriteString(`\\t`) 86 | continue 87 | case ' ', ',': 88 | sb.WriteString(`\`) 89 | case '=': 90 | if escapeEqual { 91 | sb.WriteString(`\`) 92 | } 93 | } 94 | sb.WriteRune(r) 95 | } 96 | } 97 | 98 | func escapeValue(sb *strings.Builder, value string) { 99 | for _, r := range value { 100 | switch r { 101 | case '\\', '"': 102 | sb.WriteString(`\`) 103 | } 104 | sb.WriteRune(r) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /api/write/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package write 6 | 7 | import ( 8 | "time" 9 | ) 10 | 11 | // Options holds write configuration properties 12 | type Options struct { 13 | // Maximum number of points sent to server in single request. Default 5000 14 | batchSize uint 15 | // Interval, in ms, in which is buffer flushed if it has not been already written (by reaching batch size) . Default 1000ms 16 | flushInterval uint 17 | // Precision to use in writes for timestamp. In unit of duration: time.Nanosecond, time.Microsecond, time.Millisecond, time.Second 18 | // Default time.Nanosecond 19 | precision time.Duration 20 | // Whether to use GZip compression in requests. Default false 21 | useGZip bool 22 | // Tags added to each point during writing. If a point already has a tag with the same key, it is left unchanged. 23 | defaultTags map[string]string 24 | // Default retry interval in ms, if not sent by server. Default 5,000. 25 | retryInterval uint 26 | // Maximum count of retry attempts of failed writes, default 5. 27 | maxRetries uint 28 | // Maximum number of points to keep for retry. Should be multiple of BatchSize. Default 50,000. 29 | retryBufferLimit uint 30 | // The maximum delay between each retry attempt in milliseconds, default 125,000. 31 | maxRetryInterval uint 32 | // The maximum total retry timeout in millisecond, default 180,000. 33 | maxRetryTime uint 34 | // The base for the exponential retry delay 35 | exponentialBase uint 36 | // InfluxDB Enterprise write consistency as explained in https://docs.influxdata.com/enterprise_influxdb/v1.9/concepts/clustering/#write-consistency 37 | consistency Consistency 38 | } 39 | 40 | const ( 41 | // ConsistencyOne requires at least one data node acknowledged a write. 42 | ConsistencyOne Consistency = "one" 43 | 44 | // ConsistencyAll requires all data nodes to acknowledge a write. 45 | ConsistencyAll Consistency = "all" 46 | 47 | // ConsistencyQuorum requires a quorum of data nodes to acknowledge a write. 48 | ConsistencyQuorum Consistency = "quorum" 49 | 50 | // ConsistencyAny allows for hinted hand off, potentially no write happened yet. 51 | ConsistencyAny Consistency = "any" 52 | ) 53 | 54 | // Consistency defines enum for allows consistency values for InfluxDB Enterprise, as explained https://docs.influxdata.com/enterprise_influxdb/v1.9/concepts/clustering/#write-consistency 55 | type Consistency string 56 | 57 | // BatchSize returns size of batch 58 | func (o *Options) BatchSize() uint { 59 | return o.batchSize 60 | } 61 | 62 | // SetBatchSize sets number of points sent in single request 63 | func (o *Options) SetBatchSize(batchSize uint) *Options { 64 | o.batchSize = batchSize 65 | return o 66 | } 67 | 68 | // FlushInterval returns flush interval in ms 69 | func (o *Options) FlushInterval() uint { 70 | return o.flushInterval 71 | } 72 | 73 | // SetFlushInterval sets flush interval in ms in which is buffer flushed if it has not been already written 74 | func (o *Options) SetFlushInterval(flushIntervalMs uint) *Options { 75 | o.flushInterval = flushIntervalMs 76 | return o 77 | } 78 | 79 | // RetryInterval returns the default retry interval in ms, if not sent by server. Default 5,000. 80 | func (o *Options) RetryInterval() uint { 81 | return o.retryInterval 82 | } 83 | 84 | // SetRetryInterval sets the time to wait before retry unsuccessful write in ms, if not sent by server 85 | func (o *Options) SetRetryInterval(retryIntervalMs uint) *Options { 86 | o.retryInterval = retryIntervalMs 87 | return o 88 | } 89 | 90 | // MaxRetries returns maximum count of retry attempts of failed writes, default 5. 91 | func (o *Options) MaxRetries() uint { 92 | return o.maxRetries 93 | } 94 | 95 | // SetMaxRetries sets maximum count of retry attempts of failed writes. 96 | // Setting zero value disables retry strategy. 97 | func (o *Options) SetMaxRetries(maxRetries uint) *Options { 98 | o.maxRetries = maxRetries 99 | return o 100 | } 101 | 102 | // RetryBufferLimit returns retry buffer limit. 103 | func (o *Options) RetryBufferLimit() uint { 104 | return o.retryBufferLimit 105 | } 106 | 107 | // SetRetryBufferLimit sets maximum number of points to keep for retry. Should be multiple of BatchSize. 108 | func (o *Options) SetRetryBufferLimit(retryBufferLimit uint) *Options { 109 | o.retryBufferLimit = retryBufferLimit 110 | return o 111 | } 112 | 113 | // MaxRetryInterval returns the maximum delay between each retry attempt in milliseconds, default 125,000. 114 | func (o *Options) MaxRetryInterval() uint { 115 | return o.maxRetryInterval 116 | } 117 | 118 | // SetMaxRetryInterval sets the maximum delay between each retry attempt in millisecond 119 | func (o *Options) SetMaxRetryInterval(maxRetryIntervalMs uint) *Options { 120 | o.maxRetryInterval = maxRetryIntervalMs 121 | return o 122 | } 123 | 124 | // MaxRetryTime returns the maximum total retry timeout in millisecond, default 180,000. 125 | func (o *Options) MaxRetryTime() uint { 126 | return o.maxRetryTime 127 | } 128 | 129 | // SetMaxRetryTime sets the maximum total retry timeout in millisecond. 130 | func (o *Options) SetMaxRetryTime(maxRetryTimeMs uint) *Options { 131 | o.maxRetryTime = maxRetryTimeMs 132 | return o 133 | } 134 | 135 | // ExponentialBase returns the base for the exponential retry delay. Default 2. 136 | func (o *Options) ExponentialBase() uint { 137 | return o.exponentialBase 138 | } 139 | 140 | // SetExponentialBase sets the base for the exponential retry delay. 141 | func (o *Options) SetExponentialBase(retryExponentialBase uint) *Options { 142 | o.exponentialBase = retryExponentialBase 143 | return o 144 | } 145 | 146 | // Precision returns time precision for writes 147 | func (o *Options) Precision() time.Duration { 148 | return o.precision 149 | } 150 | 151 | // SetPrecision sets time precision to use in writes for timestamp. In unit of duration: time.Nanosecond, time.Microsecond, time.Millisecond, time.Second 152 | func (o *Options) SetPrecision(precision time.Duration) *Options { 153 | o.precision = precision 154 | return o 155 | } 156 | 157 | // UseGZip returns true if write request are gzip`ed 158 | func (o *Options) UseGZip() bool { 159 | return o.useGZip 160 | } 161 | 162 | // SetUseGZip specifies whether to use GZip compression in write requests. 163 | func (o *Options) SetUseGZip(useGZip bool) *Options { 164 | o.useGZip = useGZip 165 | return o 166 | } 167 | 168 | // AddDefaultTag adds a default tag. DefaultTags are added to each written point. 169 | // If a tag with the same key already exist it is overwritten. 170 | // If a point already defines such a tag, it is left unchanged. 171 | func (o *Options) AddDefaultTag(key, value string) *Options { 172 | o.DefaultTags()[key] = value 173 | return o 174 | } 175 | 176 | // DefaultTags returns set of default tags 177 | func (o *Options) DefaultTags() map[string]string { 178 | if o.defaultTags == nil { 179 | o.defaultTags = make(map[string]string) 180 | } 181 | return o.defaultTags 182 | } 183 | 184 | // Consistency returns consistency for param value 185 | func (o *Options) Consistency() Consistency { 186 | return o.consistency 187 | } 188 | 189 | // SetConsistency allows setting InfluxDB Enterprise write consistency, as explained in https://docs.influxdata.com/enterprise_influxdb/v1.9/concepts/clustering/#write-consistency */ 190 | func (o *Options) SetConsistency(consistency Consistency) *Options { 191 | o.consistency = consistency 192 | return o 193 | } 194 | 195 | // DefaultOptions returns Options object with default values 196 | func DefaultOptions() *Options { 197 | return &Options{batchSize: 5_000, flushInterval: 1_000, precision: time.Nanosecond, useGZip: false, retryBufferLimit: 50_000, defaultTags: make(map[string]string), 198 | maxRetries: 5, retryInterval: 5_000, maxRetryInterval: 125_000, maxRetryTime: 180_000, exponentialBase: 2} 199 | } 200 | -------------------------------------------------------------------------------- /api/write/options_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package write_test 6 | 7 | import ( 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | "time" 11 | 12 | "github.com/influxdata/influxdb-client-go/v2/api/write" 13 | ) 14 | 15 | func TestDefaultOptions(t *testing.T) { 16 | opts := write.DefaultOptions() 17 | assert.EqualValues(t, 5_000, opts.BatchSize()) 18 | assert.EqualValues(t, false, opts.UseGZip()) 19 | assert.EqualValues(t, 1_000, opts.FlushInterval()) 20 | assert.EqualValues(t, time.Nanosecond, opts.Precision()) 21 | assert.EqualValues(t, 50_000, opts.RetryBufferLimit()) 22 | assert.EqualValues(t, 5_000, opts.RetryInterval()) 23 | assert.EqualValues(t, 5, opts.MaxRetries()) 24 | assert.EqualValues(t, 125_000, opts.MaxRetryInterval()) 25 | assert.EqualValues(t, 180_000, opts.MaxRetryTime()) 26 | assert.EqualValues(t, 2, opts.ExponentialBase()) 27 | assert.EqualValues(t, "", opts.Consistency()) 28 | assert.Len(t, opts.DefaultTags(), 0) 29 | } 30 | 31 | func TestSettingsOptions(t *testing.T) { 32 | opts := write.DefaultOptions(). 33 | SetBatchSize(5). 34 | SetUseGZip(true). 35 | SetFlushInterval(5_000). 36 | SetPrecision(time.Millisecond). 37 | SetRetryBufferLimit(5). 38 | SetRetryInterval(1_000). 39 | SetMaxRetries(7). 40 | SetMaxRetryInterval(150_000). 41 | SetExponentialBase(3). 42 | SetMaxRetryTime(200_000). 43 | AddDefaultTag("a", "1"). 44 | AddDefaultTag("b", "2"). 45 | SetConsistency(write.ConsistencyOne) 46 | assert.EqualValues(t, 5, opts.BatchSize()) 47 | assert.EqualValues(t, true, opts.UseGZip()) 48 | assert.EqualValues(t, 5000, opts.FlushInterval()) 49 | assert.EqualValues(t, time.Millisecond, opts.Precision()) 50 | assert.EqualValues(t, 5, opts.RetryBufferLimit()) 51 | assert.EqualValues(t, 1000, opts.RetryInterval()) 52 | assert.EqualValues(t, 7, opts.MaxRetries()) 53 | assert.EqualValues(t, 150_000, opts.MaxRetryInterval()) 54 | assert.EqualValues(t, 200_000, opts.MaxRetryTime()) 55 | assert.EqualValues(t, 3, opts.ExponentialBase()) 56 | assert.EqualValues(t, "one", opts.Consistency()) 57 | assert.Len(t, opts.DefaultTags(), 2) 58 | } 59 | -------------------------------------------------------------------------------- /api/write/point.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package write provides the Point struct 6 | package write 7 | 8 | import ( 9 | "fmt" 10 | "sort" 11 | "time" 12 | 13 | lp "github.com/influxdata/line-protocol" 14 | ) 15 | 16 | // Point is represents InfluxDB time series point, holding tags and fields 17 | type Point struct { 18 | measurement string 19 | tags []*lp.Tag 20 | fields []*lp.Field 21 | timestamp time.Time 22 | } 23 | 24 | // TagList returns a slice containing tags of a Point. 25 | func (m *Point) TagList() []*lp.Tag { 26 | return m.tags 27 | } 28 | 29 | // FieldList returns a slice containing the fields of a Point. 30 | func (m *Point) FieldList() []*lp.Field { 31 | return m.fields 32 | } 33 | 34 | // SetTime set timestamp for a Point. 35 | func (m *Point) SetTime(timestamp time.Time) *Point { 36 | m.timestamp = timestamp 37 | return m 38 | } 39 | 40 | // Time is the timestamp of a Point. 41 | func (m *Point) Time() time.Time { 42 | return m.timestamp 43 | } 44 | 45 | // SortTags orders the tags of a point alphanumerically by key. 46 | // This is just here as a helper, to make it easy to keep tags sorted if you are creating a Point manually. 47 | func (m *Point) SortTags() *Point { 48 | sort.Slice(m.tags, func(i, j int) bool { return m.tags[i].Key < m.tags[j].Key }) 49 | return m 50 | } 51 | 52 | // SortFields orders the fields of a point alphanumerically by key. 53 | func (m *Point) SortFields() *Point { 54 | sort.Slice(m.fields, func(i, j int) bool { return m.fields[i].Key < m.fields[j].Key }) 55 | return m 56 | } 57 | 58 | // AddTag adds a tag to a point. 59 | func (m *Point) AddTag(k, v string) *Point { 60 | for i, tag := range m.tags { 61 | if k == tag.Key { 62 | m.tags[i].Value = v 63 | return m 64 | } 65 | } 66 | m.tags = append(m.tags, &lp.Tag{Key: k, Value: v}) 67 | return m 68 | } 69 | 70 | // AddField adds a field to a point. 71 | func (m *Point) AddField(k string, v interface{}) *Point { 72 | for i, field := range m.fields { 73 | if k == field.Key { 74 | m.fields[i].Value = v 75 | return m 76 | } 77 | } 78 | m.fields = append(m.fields, &lp.Field{Key: k, Value: convertField(v)}) 79 | return m 80 | } 81 | 82 | // Name returns the name of measurement of a point. 83 | func (m *Point) Name() string { 84 | return m.measurement 85 | } 86 | 87 | // NewPointWithMeasurement creates a empty Point 88 | // Use AddTag and AddField to fill point with data 89 | func NewPointWithMeasurement(measurement string) *Point { 90 | return &Point{measurement: measurement} 91 | } 92 | 93 | // NewPoint creates a Point from measurement name, tags, fields and a timestamp. 94 | func NewPoint( 95 | measurement string, 96 | tags map[string]string, 97 | fields map[string]interface{}, 98 | ts time.Time, 99 | ) *Point { 100 | m := &Point{ 101 | measurement: measurement, 102 | tags: nil, 103 | fields: nil, 104 | timestamp: ts, 105 | } 106 | 107 | if len(tags) > 0 { 108 | m.tags = make([]*lp.Tag, 0, len(tags)) 109 | for k, v := range tags { 110 | m.tags = append(m.tags, 111 | &lp.Tag{Key: k, Value: v}) 112 | } 113 | } 114 | 115 | m.fields = make([]*lp.Field, 0, len(fields)) 116 | for k, v := range fields { 117 | v := convertField(v) 118 | if v == nil { 119 | continue 120 | } 121 | m.fields = append(m.fields, &lp.Field{Key: k, Value: v}) 122 | } 123 | m.SortFields() 124 | m.SortTags() 125 | return m 126 | } 127 | 128 | // convertField converts any primitive type to types supported by line protocol 129 | func convertField(v interface{}) interface{} { 130 | switch v := v.(type) { 131 | case bool, int64, string, float64: 132 | return v 133 | case int: 134 | return int64(v) 135 | case uint: 136 | return uint64(v) 137 | case uint64: 138 | return v 139 | case []byte: 140 | return string(v) 141 | case int32: 142 | return int64(v) 143 | case int16: 144 | return int64(v) 145 | case int8: 146 | return int64(v) 147 | case uint32: 148 | return uint64(v) 149 | case uint16: 150 | return uint64(v) 151 | case uint8: 152 | return uint64(v) 153 | case float32: 154 | return float64(v) 155 | case time.Time: 156 | return v.Format(time.RFC3339Nano) 157 | case time.Duration: 158 | return v.String() 159 | default: 160 | return fmt.Sprintf("%v", v) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /api/writeAPIBlocking.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package api 6 | 7 | import ( 8 | "context" 9 | "strings" 10 | "sync" 11 | "sync/atomic" 12 | 13 | http2 "github.com/influxdata/influxdb-client-go/v2/api/http" 14 | "github.com/influxdata/influxdb-client-go/v2/api/write" 15 | iwrite "github.com/influxdata/influxdb-client-go/v2/internal/write" 16 | ) 17 | 18 | // WriteAPIBlocking offers blocking methods for writing time series data synchronously into an InfluxDB server. 19 | // It doesn't implicitly create batches of points by default. Batches are created from array of points/records. 20 | // 21 | // Implicit batching is enabled with EnableBatching(). In this mode, each call to WritePoint or WriteRecord adds a line 22 | // to internal buffer. If length of the buffer is equal to the batch-size (set in write.Options), the buffer is sent to the server 23 | // and the result of the operation is returned. 24 | // When a point is written to the buffer, nil error is always returned. 25 | // Flush() can be used to trigger sending of batch when it doesn't have the batch-size. 26 | // 27 | // Synchronous writing is intended to use for writing less frequent data, such as a weather sensing, or if there is a need to have explicit control of failed batches. 28 | 29 | // 30 | // WriteAPIBlocking can be used concurrently. 31 | // When using multiple goroutines for writing, use a single WriteAPIBlocking instance in all goroutines. 32 | type WriteAPIBlocking interface { 33 | // WriteRecord writes line protocol record(s) into bucket. 34 | // WriteRecord writes lines without implicit batching by default, batch is created from given number of records. 35 | // Automatic batching can be enabled by EnableBatching() 36 | // Individual arguments can also be batches (multiple records separated by newline). 37 | // Non-blocking alternative is available in the WriteAPI interface 38 | WriteRecord(ctx context.Context, line ...string) error 39 | // WritePoint data point into bucket. 40 | // WriteRecord writes points without implicit batching by default, batch is created from given number of points. 41 | // Automatic batching can be enabled by EnableBatching(). 42 | // Non-blocking alternative is available in the WriteAPI interface 43 | WritePoint(ctx context.Context, point ...*write.Point) error 44 | // EnableBatching turns on implicit batching 45 | // Batch size is controlled via write.Options 46 | EnableBatching() 47 | // Flush forces write of buffer if batching is enabled, even buffer doesn't have the batch-size. 48 | Flush(ctx context.Context) error 49 | } 50 | 51 | // writeAPIBlocking implements WriteAPIBlocking interface 52 | type writeAPIBlocking struct { 53 | service *iwrite.Service 54 | writeOptions *write.Options 55 | // more appropriate Bool type from sync/atomic cannot be used because it is available since go 1.19 56 | batching int32 57 | batch []string 58 | mu sync.Mutex 59 | } 60 | 61 | // NewWriteAPIBlocking creates new instance of blocking write client for writing data to bucket belonging to org 62 | func NewWriteAPIBlocking(org string, bucket string, service http2.Service, writeOptions *write.Options) WriteAPIBlocking { 63 | return &writeAPIBlocking{service: iwrite.NewService(org, bucket, service, writeOptions), writeOptions: writeOptions} 64 | } 65 | 66 | // NewWriteAPIBlockingWithBatching creates new instance of blocking write client for writing data to bucket belonging to org with batching enabled 67 | func NewWriteAPIBlockingWithBatching(org string, bucket string, service http2.Service, writeOptions *write.Options) WriteAPIBlocking { 68 | api := &writeAPIBlocking{service: iwrite.NewService(org, bucket, service, writeOptions), writeOptions: writeOptions} 69 | api.EnableBatching() 70 | return api 71 | } 72 | 73 | func (w *writeAPIBlocking) EnableBatching() { 74 | if atomic.LoadInt32(&w.batching) == 0 { 75 | w.mu.Lock() 76 | w.batching = 1 77 | w.batch = make([]string, 0, w.writeOptions.BatchSize()) 78 | w.mu.Unlock() 79 | } 80 | } 81 | 82 | func (w *writeAPIBlocking) write(ctx context.Context, line string) error { 83 | if atomic.LoadInt32(&w.batching) > 0 { 84 | w.mu.Lock() 85 | defer w.mu.Unlock() 86 | w.batch = append(w.batch, line) 87 | if len(w.batch) == int(w.writeOptions.BatchSize()) { 88 | return w.flush(ctx) 89 | } 90 | return nil 91 | } 92 | err := w.service.WriteBatch(ctx, iwrite.NewBatch(line, w.writeOptions.MaxRetryTime())) 93 | if err != nil { 94 | return err 95 | } 96 | return nil 97 | } 98 | 99 | func (w *writeAPIBlocking) WriteRecord(ctx context.Context, line ...string) error { 100 | if len(line) == 0 { 101 | return nil 102 | } 103 | return w.write(ctx, strings.Join(line, "\n")) 104 | } 105 | 106 | func (w *writeAPIBlocking) WritePoint(ctx context.Context, point ...*write.Point) error { 107 | line, err := w.service.EncodePoints(point...) 108 | if err != nil { 109 | return err 110 | } 111 | return w.write(ctx, line) 112 | } 113 | 114 | // flush is unsychronized helper for creating and sending batch 115 | // Must be called from synchronized block 116 | func (w *writeAPIBlocking) flush(ctx context.Context) error { 117 | if len(w.batch) > 0 { 118 | body := strings.Join(w.batch, "\n") 119 | w.batch = w.batch[:0] 120 | b := iwrite.NewBatch(body, w.writeOptions.MaxRetryTime()) 121 | if err:= w.service.WriteBatch(ctx, b); err != nil { 122 | return err 123 | } 124 | } 125 | return nil 126 | } 127 | 128 | func (w *writeAPIBlocking) Flush(ctx context.Context) error { 129 | if atomic.LoadInt32(&w.batching) > 0 { 130 | w.mu.Lock() 131 | defer w.mu.Unlock() 132 | return w.flush(ctx) 133 | } 134 | return nil 135 | } 136 | -------------------------------------------------------------------------------- /api/writeAPIBlocking_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package api 6 | 7 | import ( 8 | "context" 9 | "net" 10 | "net/http" 11 | "strings" 12 | "sync" 13 | "testing" 14 | "time" 15 | 16 | http2 "github.com/influxdata/influxdb-client-go/v2/api/http" 17 | "github.com/influxdata/influxdb-client-go/v2/api/write" 18 | "github.com/influxdata/influxdb-client-go/v2/internal/test" 19 | "github.com/stretchr/testify/assert" 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestWritePoint(t *testing.T) { 24 | service := test.NewTestService(t, "http://localhost:8888") 25 | opts := write.DefaultOptions().SetBatchSize(5) 26 | writeAPI := NewWriteAPIBlocking("my-org", "my-bucket", service, opts) 27 | points := test.GenPoints(10) 28 | err := writeAPI.WritePoint(context.Background(), points...) 29 | require.Nil(t, err) 30 | require.Len(t, service.Lines(), 10) 31 | for i, p := range points { 32 | line := write.PointToLineProtocol(p, opts.Precision()) 33 | //cut off last \n char 34 | line = line[:len(line)-1] 35 | assert.Equal(t, service.Lines()[i], line) 36 | } 37 | } 38 | 39 | func TestWriteRecord(t *testing.T) { 40 | service := test.NewTestService(t, "http://localhost:8888") 41 | writeAPI := NewWriteAPIBlocking("my-org", "my-bucket", service, write.DefaultOptions().SetBatchSize(5)) 42 | lines := test.GenRecords(10) 43 | for _, line := range lines { 44 | err := writeAPI.WriteRecord(context.Background(), line) 45 | require.Nil(t, err) 46 | } 47 | require.Len(t, service.Lines(), 10) 48 | require.Equal(t, 10, service.Requests()) 49 | for i, l := range lines { 50 | assert.Equal(t, l, service.Lines()[i]) 51 | } 52 | service.Close() 53 | 54 | err := writeAPI.WriteRecord(context.Background(), lines...) 55 | require.Nil(t, err) 56 | require.Equal(t, 1, service.Requests()) 57 | for i, l := range lines { 58 | assert.Equal(t, l, service.Lines()[i]) 59 | } 60 | service.Close() 61 | 62 | err = writeAPI.WriteRecord(context.Background()) 63 | require.Nil(t, err) 64 | require.Len(t, service.Lines(), 0) 65 | 66 | service.SetReplyError(&http2.Error{Code: "invalid", Message: "data"}) 67 | err = writeAPI.WriteRecord(context.Background(), lines...) 68 | require.NotNil(t, err) 69 | require.Equal(t, "invalid: data", err.Error()) 70 | } 71 | 72 | func TestWriteRecordBatch(t *testing.T) { 73 | service := test.NewTestService(t, "http://localhost:8888") 74 | writeAPI := NewWriteAPIBlocking("my-org", "my-bucket", service, write.DefaultOptions().SetBatchSize(5)) 75 | lines := test.GenRecords(10) 76 | batch := strings.Join(lines, "\n") 77 | err := writeAPI.WriteRecord(context.Background(), batch) 78 | require.Nil(t, err) 79 | require.Len(t, service.Lines(), 10) 80 | for i, l := range lines { 81 | assert.Equal(t, l, service.Lines()[i]) 82 | } 83 | service.Close() 84 | } 85 | 86 | func TestWriteParallel(t *testing.T) { 87 | service := test.NewTestService(t, "http://localhost:8888") 88 | writeAPI := NewWriteAPIBlocking("my-org", "my-bucket", service, write.DefaultOptions().SetBatchSize(5)) 89 | lines := test.GenRecords(1000) 90 | 91 | chanLine := make(chan string) 92 | var wg sync.WaitGroup 93 | for i := 0; i < 10; i++ { 94 | wg.Add(1) 95 | go func() { 96 | for l := range chanLine { 97 | err := writeAPI.WriteRecord(context.Background(), l) 98 | assert.Nil(t, err) 99 | } 100 | wg.Done() 101 | }() 102 | } 103 | for _, l := range lines { 104 | chanLine <- l 105 | } 106 | close(chanLine) 107 | wg.Wait() 108 | assert.Len(t, service.Lines(), len(lines)) 109 | 110 | service.Close() 111 | } 112 | 113 | func TestWriteErrors(t *testing.T) { 114 | service := http2.NewService("http://locl:866", "", http2.DefaultOptions().SetHTTPClient(&http.Client{ 115 | Timeout: 100 * time.Millisecond, 116 | Transport: &http.Transport{ 117 | DialContext: (&net.Dialer{ 118 | Timeout: 100 * time.Millisecond, 119 | }).DialContext, 120 | }, 121 | })) 122 | writeAPI := NewWriteAPIBlocking("my-org", "my-bucket", service, write.DefaultOptions().SetBatchSize(5)) 123 | points := test.GenPoints(10) 124 | errors := 0 125 | for _, p := range points { 126 | err := writeAPI.WritePoint(context.Background(), p) 127 | if assert.Error(t, err) { 128 | errors++ 129 | } 130 | } 131 | require.Equal(t, 10, errors) 132 | 133 | } 134 | 135 | func TestWriteBatchIng(t *testing.T) { 136 | service := test.NewTestService(t, "http://localhost:8888") 137 | writeAPI := NewWriteAPIBlockingWithBatching("my-org", "my-bucket", service, write.DefaultOptions().SetBatchSize(5)) 138 | lines := test.GenRecords(10) 139 | for i, line := range lines { 140 | err := writeAPI.WriteRecord(context.Background(), line) 141 | require.Nil(t, err) 142 | if i == 4 || i == 9 { 143 | assert.Equal(t, 1, service.Requests()) 144 | require.Len(t, service.Lines(), 5) 145 | 146 | service.Close() 147 | } 148 | } 149 | 150 | for i := 0; i < 4; i++ { 151 | err := writeAPI.WriteRecord(context.Background(), lines[i]) 152 | require.Nil(t, err) 153 | } 154 | assert.Equal(t, 0, service.Requests()) 155 | require.Len(t, service.Lines(), 0) 156 | err := writeAPI.Flush(context.Background()) 157 | require.Nil(t, err) 158 | assert.Equal(t, 1, service.Requests()) 159 | require.Len(t, service.Lines(), 4) 160 | } 161 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package influxdb2 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | ilog "github.com/influxdata/influxdb-client-go/v2/log" 11 | "log" 12 | "net/http" 13 | "net/http/httptest" 14 | "runtime" 15 | "strings" 16 | "testing" 17 | "time" 18 | 19 | ihttp "github.com/influxdata/influxdb-client-go/v2/api/http" 20 | "github.com/influxdata/influxdb-client-go/v2/domain" 21 | http2 "github.com/influxdata/influxdb-client-go/v2/internal/http" 22 | iwrite "github.com/influxdata/influxdb-client-go/v2/internal/write" 23 | "github.com/stretchr/testify/assert" 24 | "github.com/stretchr/testify/require" 25 | ) 26 | 27 | func TestUrls(t *testing.T) { 28 | urls := []struct { 29 | serverURL string 30 | serverAPIURL string 31 | writeURLPrefix string 32 | }{ 33 | {"http://host:8086", "http://host:8086/api/v2/", "http://host:8086/api/v2/write"}, 34 | {"http://host:8086/", "http://host:8086/api/v2/", "http://host:8086/api/v2/write"}, 35 | {"http://host:8086/path", "http://host:8086/path/api/v2/", "http://host:8086/path/api/v2/write"}, 36 | {"http://host:8086/path/", "http://host:8086/path/api/v2/", "http://host:8086/path/api/v2/write"}, 37 | {"http://host:8086/path1/path2/path3", "http://host:8086/path1/path2/path3/api/v2/", "http://host:8086/path1/path2/path3/api/v2/write"}, 38 | {"http://host:8086/path1/path2/path3/", "http://host:8086/path1/path2/path3/api/v2/", "http://host:8086/path1/path2/path3/api/v2/write"}, 39 | } 40 | for _, url := range urls { 41 | t.Run(url.serverURL, func(t *testing.T) { 42 | c := NewClient(url.serverURL, "x") 43 | ci := c.(*clientImpl) 44 | assert.Equal(t, url.serverURL, ci.serverURL) 45 | assert.Equal(t, url.serverAPIURL, ci.httpService.ServerAPIURL()) 46 | ws := iwrite.NewService("org", "bucket", ci.httpService, c.Options().WriteOptions()) 47 | wu := ws.WriteURL() 48 | assert.Equal(t, url.writeURLPrefix+"?bucket=bucket&org=org&precision=ns", wu) 49 | }) 50 | } 51 | } 52 | 53 | func TestWriteAPIManagement(t *testing.T) { 54 | data := []struct { 55 | org string 56 | bucket string 57 | expectedCout int 58 | }{ 59 | {"o1", "b1", 1}, 60 | {"o1", "b2", 2}, 61 | {"o1", "b1", 2}, 62 | {"o2", "b1", 3}, 63 | {"o2", "b2", 4}, 64 | {"o1", "b2", 4}, 65 | {"o1", "b3", 5}, 66 | {"o2", "b2", 5}, 67 | } 68 | c := NewClient("http://localhost", "x").(*clientImpl) 69 | for i, d := range data { 70 | t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 71 | w := c.WriteAPI(d.org, d.bucket) 72 | assert.NotNil(t, w) 73 | assert.Len(t, c.writeAPIs, d.expectedCout) 74 | wb := c.WriteAPIBlocking(d.org, d.bucket) 75 | assert.NotNil(t, wb) 76 | assert.Len(t, c.syncWriteAPIs, d.expectedCout) 77 | }) 78 | } 79 | c.Close() 80 | assert.Len(t, c.writeAPIs, 0) 81 | assert.Len(t, c.syncWriteAPIs, 0) 82 | } 83 | 84 | func TestUserAgentBase(t *testing.T) { 85 | ua := fmt.Sprintf("influxdb-client-go/%s (%s; %s)", Version, runtime.GOOS, runtime.GOARCH) 86 | assert.Equal(t, ua, http2.UserAgentBase) 87 | 88 | } 89 | 90 | type doer struct { 91 | userAgent string 92 | doer ihttp.Doer 93 | } 94 | 95 | func (d *doer) Do(req *http.Request) (*http.Response, error) { 96 | req.Header.Set("User-Agent", d.userAgent) 97 | return d.doer.Do(req) 98 | } 99 | 100 | func TestUserAgent(t *testing.T) { 101 | ua := http2.UserAgentBase 102 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 103 | <-time.After(100 * time.Millisecond) 104 | if r.Header.Get("User-Agent") == ua { 105 | w.WriteHeader(http.StatusNoContent) 106 | } else { 107 | w.WriteHeader(http.StatusNotFound) 108 | } 109 | })) 110 | 111 | defer server.Close() 112 | var sb strings.Builder 113 | log.SetOutput(&sb) 114 | log.SetFlags(0) 115 | c := NewClientWithOptions(server.URL, "x", DefaultOptions().SetLogLevel(ilog.WarningLevel)) 116 | assert.True(t, strings.Contains(sb.String(), "Application name is not set")) 117 | up, err := c.Ping(context.Background()) 118 | require.NoError(t, err) 119 | assert.True(t, up) 120 | 121 | err = c.WriteAPIBlocking("o", "b").WriteRecord(context.Background(), "a,a=a a=1i") 122 | assert.NoError(t, err) 123 | 124 | c.Close() 125 | sb.Reset() 126 | // Test setting application name 127 | c = NewClientWithOptions(server.URL, "x", DefaultOptions().SetApplicationName("Monitor/1.1")) 128 | ua = fmt.Sprintf("influxdb-client-go/%s (%s; %s) Monitor/1.1", Version, runtime.GOOS, runtime.GOARCH) 129 | assert.False(t, strings.Contains(sb.String(), "Application name is not set")) 130 | up, err = c.Ping(context.Background()) 131 | require.NoError(t, err) 132 | assert.True(t, up) 133 | 134 | err = c.WriteAPIBlocking("o", "b").WriteRecord(context.Background(), "a,a=a a=1i") 135 | assert.NoError(t, err) 136 | c.Close() 137 | 138 | ua = "Monitor/1.1" 139 | opts := DefaultOptions() 140 | opts.HTTPOptions().SetHTTPDoer(&doer{ 141 | userAgent: ua, 142 | doer: http.DefaultClient, 143 | }) 144 | 145 | //Create client with custom user agent setter 146 | c = NewClientWithOptions(server.URL, "x", opts) 147 | up, err = c.Ping(context.Background()) 148 | require.NoError(t, err) 149 | assert.True(t, up) 150 | 151 | err = c.WriteAPIBlocking("o", "b").WriteRecord(context.Background(), "a,a=a a=1i") 152 | assert.NoError(t, err) 153 | c.Close() 154 | } 155 | 156 | func TestServerError429(t *testing.T) { 157 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 158 | <-time.After(100 * time.Millisecond) 159 | w.Header().Set("Retry-After", "1") 160 | w.Header().Set("Content-Type", "application/json") 161 | w.WriteHeader(http.StatusTooManyRequests) 162 | _, _ = w.Write([]byte(`{"code":"too many requests", "message":"exceeded rate limit"}`)) 163 | })) 164 | 165 | defer server.Close() 166 | c := NewClient(server.URL, "x") 167 | err := c.WriteAPIBlocking("o", "b").WriteRecord(context.Background(), "a,a=a a=1i") 168 | require.Error(t, err) 169 | assert.Equal(t, "too many requests: exceeded rate limit", err.Error()) 170 | } 171 | 172 | func TestServerOnPath(t *testing.T) { 173 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 174 | if r.URL.Path == "/proxy/0:0/influx/api/v2/write" { 175 | w.WriteHeader(http.StatusNoContent) 176 | } else { 177 | w.WriteHeader(http.StatusInternalServerError) 178 | _, _ = w.Write([]byte(fmt.Sprintf(`{"code":"internal server error", "message":"%s"}`, r.URL.Path))) 179 | } 180 | })) 181 | 182 | defer server.Close() 183 | c := NewClient(server.URL+"/proxy/0:0/influx/", "x") 184 | err := c.WriteAPIBlocking("o", "b").WriteRecord(context.Background(), "a,a=a a=1i") 185 | require.NoError(t, err) 186 | } 187 | 188 | func TestServerErrorNonJSON(t *testing.T) { 189 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 190 | <-time.After(100 * time.Millisecond) 191 | w.WriteHeader(http.StatusInternalServerError) 192 | _, _ = w.Write([]byte(`internal server error`)) 193 | })) 194 | 195 | defer server.Close() 196 | c := NewClient(server.URL, "x") 197 | //Test non JSON error in custom code 198 | err := c.WriteAPIBlocking("o", "b").WriteRecord(context.Background(), "a,a=a a=1i") 199 | require.Error(t, err) 200 | assert.Equal(t, "500 Internal Server Error: internal server error", err.Error()) 201 | 202 | // Test non JSON error from generated code 203 | params := &domain.GetBucketsParams{} 204 | b, err := c.APIClient().GetBuckets(context.Background(), params) 205 | assert.Nil(t, b) 206 | require.Error(t, err) 207 | assert.Equal(t, "500 Internal Server Error: internal server error", err.Error()) 208 | 209 | } 210 | 211 | func TestServerErrorInflux1_8(t *testing.T) { 212 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 213 | w.Header().Set("X-Influxdb-Error", "bruh moment") 214 | w.Header().Set("Content-Type", "application/json") 215 | w.WriteHeader(404) 216 | _, _ = w.Write([]byte(`{"error": "bruh moment"}`)) 217 | })) 218 | 219 | defer server.Close() 220 | c := NewClient(server.URL, "x") 221 | err := c.WriteAPIBlocking("o", "b").WriteRecord(context.Background(), "a,a=a a=1i") 222 | require.Error(t, err) 223 | assert.Equal(t, "404 Not Found: bruh moment", err.Error()) 224 | } 225 | 226 | func TestServerErrorEmptyBody(t *testing.T) { 227 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 228 | w.WriteHeader(404) 229 | })) 230 | 231 | defer server.Close() 232 | c := NewClient(server.URL, "x") 233 | err := c.WriteAPIBlocking("o", "b").WriteRecord(context.Background(), "a,a=a a=1i") 234 | require.Error(t, err) 235 | assert.Equal(t, "Unexpected status code 404", err.Error()) 236 | } 237 | 238 | func TestReadyFail(t *testing.T) { 239 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 240 | w.WriteHeader(200) 241 | w.Write([]byte(``)) 242 | })) 243 | 244 | defer server.Close() 245 | c := NewClient(server.URL, "x") 246 | r, err := c.Ready(context.Background()) 247 | assert.Error(t, err) 248 | assert.Nil(t, r) 249 | } 250 | 251 | func TestHealthFail(t *testing.T) { 252 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 253 | w.WriteHeader(200) 254 | w.Write([]byte(``)) 255 | })) 256 | 257 | defer server.Close() 258 | c := NewClient(server.URL, "x") 259 | h, err := c.Health(context.Background()) 260 | assert.Error(t, err) 261 | assert.Nil(t, h) 262 | } 263 | -------------------------------------------------------------------------------- /compatibility.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package influxdb2 6 | 7 | import ( 8 | "github.com/influxdata/influxdb-client-go/v2/api" 9 | "github.com/influxdata/influxdb-client-go/v2/api/write" 10 | "github.com/influxdata/influxdb-client-go/v2/domain" 11 | "time" 12 | ) 13 | 14 | // Proxy methods for backward compatibility 15 | 16 | // NewPointWithMeasurement creates a empty Point 17 | // Use AddTag and AddField to fill point with data 18 | func NewPointWithMeasurement(measurement string) *write.Point { 19 | return write.NewPointWithMeasurement(measurement) 20 | } 21 | 22 | // NewPoint creates a Point from measurement name, tags, fields and a timestamp. 23 | func NewPoint( 24 | measurement string, 25 | tags map[string]string, 26 | fields map[string]interface{}, 27 | ts time.Time, 28 | ) *write.Point { 29 | return write.NewPoint(measurement, tags, fields, ts) 30 | } 31 | 32 | // DefaultDialect return flux query Dialect with full annotations (datatype, group, default), header and comma char as a delimiter 33 | func DefaultDialect() *domain.Dialect { 34 | return api.DefaultDialect() 35 | } 36 | -------------------------------------------------------------------------------- /domain/Readme.md: -------------------------------------------------------------------------------- 1 | # Generated types and API client 2 | 3 | `oss.yml`must be periodically synced with latest changes and types and client must be re-generated 4 | to maintain full compatibility with the latest InfluxDB release 5 | 6 | 7 | ## Install oapi generator 8 | `git clone git@github.com:bonitoo-io/oapi-codegen.git` 9 | `cd oapi-codegen` 10 | `git checkout feat/template_helpers` 11 | `go install ./cmd/oapi-codegen/oapi-codegen.go` 12 | 13 | ## Download latest swagger 14 | `wget https://raw.githubusercontent.com/influxdata/openapi/master/contracts/oss.yml` 15 | `cd domain` 16 | 17 | ## Generate 18 | ### Generate types 19 | `oapi-codegen -generate types -exclude-tags Checks -o types.gen.go -package domain -templates .\templates oss.yml` 20 | 21 | ### Generate client 22 | `oapi-codegen -generate client -exclude-tags Checks -o client.gen.go -package domain -templates .\templates oss.yml` 23 | 24 | -------------------------------------------------------------------------------- /domain/templates/client-with-responses.tmpl: -------------------------------------------------------------------------------- 1 | {{/* Generate client methods */}} 2 | {{range .}} 3 | {{if (hasValidRequestAndResponse .) -}}{{/* skip non-JSON bodies*/}} 4 | {{$hasParams := .RequiresParamObject -}} 5 | {{$pathParams := .PathParams -}} 6 | {{$opid := .OperationId -}} 7 | {{$hasResponse := hasSingle2xxJSONResponse . -}} 8 | {{$lenServers := 0 -}} 9 | {{if .Spec.Servers}} 10 | {{$lenServers = len .Spec.Servers -}} 11 | {{end}} 12 | {{$response2xx := get2xxResponseTypeDefinition . -}} 13 | {{$hasBodyOrPathParams := or .HasBody (gt (len .PathParams) 0) -}} 14 | {{$pathStr := genParamFmtString .Path -}} 15 | 16 | // {{$opid}} calls the {{.Method}} on {{.Path}} 17 | // {{.Summary}} 18 | func (c *Client) {{$opid}}(ctx context.Context, {{if $hasBodyOrPathParams}}params *{{$opid}}AllParams{{else}}{{if $hasParams}} params *{{$opid}}Params{{end}}{{end}}) ({{if $hasResponse}}*{{$response2xx.Schema.TypeDecl}},{{end}} error) { 19 | var err error 20 | {{if .HasBody -}} 21 | var bodyReader io.Reader 22 | buf, err := json.Marshal(params.Body) 23 | if err != nil { 24 | return {{if $hasResponse}}nil, {{end}}err 25 | } 26 | bodyReader = bytes.NewReader(buf) 27 | {{end}} 28 | {{range $paramIdx, $param := .PathParams}} 29 | var pathParam{{$paramIdx}} string 30 | {{if .IsPassThrough}} 31 | pathParam{{$paramIdx}} = params.{{.GoVariableName|ucFirst}} 32 | {{end}} 33 | {{if .IsJson}} 34 | var pathParamBuf{{$paramIdx}} []byte 35 | pathParamBuf{{$paramIdx}}, err = json.Marshal(params.{{.GoVariableName|ucFirst}}) 36 | if err != nil { 37 | return {{if $hasResponse}}nil, {{end}}err 38 | } 39 | pathParam{{$paramIdx}} = string(pathParamBuf{{$paramIdx}}) 40 | {{end}} 41 | {{if .IsStyled}} 42 | pathParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationPath, params.{{.GoVariableName|ucFirst}}) 43 | if err != nil { 44 | return {{if $hasResponse}}nil, {{end}}err 45 | } 46 | {{end}} 47 | {{end}} 48 | serverURL, err := url.Parse(c.{{if eq $lenServers 0}}APIEndpoint{{else}}Server{{end}}) 49 | if err != nil { 50 | return {{if $hasResponse}}nil, {{end}}err 51 | } 52 | 53 | operationPath := fmt.Sprintf("{{if eq (index $pathStr 0) '/'}}.{{end}}{{$pathStr}}"{{range $paramIdx, $param := .PathParams}}, pathParam{{$paramIdx}}{{end}}) 54 | 55 | queryURL, err := serverURL.Parse(operationPath) 56 | if err != nil { 57 | return {{if $hasResponse}}nil, {{end}}err 58 | } 59 | 60 | {{if .QueryParams}} 61 | queryValues := queryURL.Query() 62 | {{range $paramIdx, $param := .QueryParams}} 63 | {{if not .Required}} if params.{{.GoName}} != nil { {{end}} 64 | {{if .IsPassThrough}} 65 | queryValues.Add("{{.ParamName}}", {{if not .Required}}*{{end}}params.{{.GoName}}) 66 | {{end}} 67 | {{if .IsJson}} 68 | if queryParamBuf, err := json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}); err != nil { 69 | return {{if $hasResponse}}nil, {{end}}err 70 | } else { 71 | queryValues.Add("{{.ParamName}}", string(queryParamBuf)) 72 | } 73 | 74 | {{end}} 75 | {{if .IsStyled}} 76 | if queryFrag, err := runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationQuery, {{if not .Required}}*{{end}}params.{{.GoName}}); err != nil { 77 | return {{if $hasResponse}}nil, {{end}}err 78 | } else if parsed, err := url.ParseQuery(queryFrag); err != nil { 79 | return {{if $hasResponse}}nil, {{end}}err 80 | } else { 81 | for k, v := range parsed { 82 | for _, v2 := range v { 83 | queryValues.Add(k, v2) 84 | } 85 | } 86 | } 87 | {{end}} 88 | {{if not .Required}}}{{end}} 89 | {{end}} 90 | queryURL.RawQuery = queryValues.Encode() 91 | {{end}}{{/* if .QueryParams */}} 92 | req, err := http.NewRequest("{{.Method}}", queryURL.String(), {{if .HasBody}}bodyReader{{else}}nil{{end}}) 93 | if err != nil { 94 | return {{if $hasResponse}}nil, {{end}}err 95 | } 96 | 97 | {{if .HasBody}}req.Header.Add("Content-Type", "{{(index .Bodies 0).ContentType}}"){{end}} 98 | {{range $paramIdx, $param := .HeaderParams}} 99 | {{if not .Required}} if params.{{.GoName}} != nil { {{end}} 100 | var headerParam{{$paramIdx}} string 101 | {{if .IsPassThrough}} 102 | headerParam{{$paramIdx}} = {{if not .Required}}*{{end}}params.{{.GoName}} 103 | {{end}} 104 | {{if .IsJson}} 105 | var headerParamBuf{{$paramIdx}} []byte 106 | headerParamBuf{{$paramIdx}}, err = json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}) 107 | if err != nil { 108 | return {{if $hasResponse}}nil, {{end}}err 109 | } 110 | headerParam{{$paramIdx}} = string(headerParamBuf{{$paramIdx}}) 111 | {{end}} 112 | {{if .IsStyled}} 113 | headerParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("{{.Style}}", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationHeader, {{if not .Required}}*{{end}}params.{{.GoName}}) 114 | if err != nil { 115 | return {{if $hasResponse}}nil, {{end}}err 116 | } 117 | {{end}} 118 | req.Header.Set("{{.ParamName}}", headerParam{{$paramIdx}}) 119 | {{if not .Required}}}{{end}} 120 | {{end}} 121 | 122 | {{range $paramIdx, $param := .CookieParams}} 123 | {{if not .Required}} if params.{{.GoName}} != nil { {{end}} 124 | var cookieParam{{$paramIdx}} string 125 | {{if .IsPassThrough}} 126 | cookieParam{{$paramIdx}} = {{if not .Required}}*{{end}}params.{{.GoName}} 127 | {{end}} 128 | {{if .IsJson}} 129 | var cookieParamBuf{{$paramIdx}} []byte 130 | cookieParamBuf{{$paramIdx}}, err = json.Marshal({{if not .Required}}*{{end}}params.{{.GoName}}) 131 | if err != nil { 132 | return {{if $hasResponse}}nil, {{end}}err 133 | } 134 | cookieParam{{$paramIdx}} = url.QueryEscape(string(cookieParamBuf{{$paramIdx}})) 135 | {{end}} 136 | {{if .IsStyled}} 137 | cookieParam{{$paramIdx}}, err = runtime.StyleParamWithLocation("simple", {{.Explode}}, "{{.ParamName}}", runtime.ParamLocationCookie, {{if not .Required}}*{{end}}params.{{.GoName}}) 138 | if err != nil { 139 | return {{if $hasResponse}}nil, {{end}}err 140 | } 141 | {{end}} 142 | cookie{{$paramIdx}} := &http.Cookie{ 143 | Name:"{{.ParamName}}", 144 | Value:cookieParam{{$paramIdx}}, 145 | } 146 | req.AddCookie(cookie{{$paramIdx}}) 147 | {{if not .Required}}}{{end}} 148 | {{end}} 149 | 150 | req = req.WithContext(ctx) 151 | rsp, err := c.Client.Do(req) 152 | if err != nil { 153 | return {{if $hasResponse}}nil, {{end}}err 154 | } 155 | {{if $hasResponse -}} 156 | bodyBytes, err := io.ReadAll(rsp.Body) 157 | {{end}} 158 | defer func() { _ = rsp.Body.Close() }() 159 | {{if $hasResponse -}} 160 | if err != nil { 161 | return nil, err 162 | } 163 | 164 | response := &{{$response2xx.Schema.TypeDecl}}{} 165 | 166 | switch(rsp.StatusCode) { 167 | case {{$response2xx.ResponseName}}: 168 | if err := unmarshalJSONResponse(bodyBytes, &response); err != nil { 169 | return nil, err 170 | } 171 | default: 172 | return nil, decodeError(bodyBytes, rsp) 173 | } 174 | return response, nil 175 | {{else}} 176 | if rsp.StatusCode > 299 { 177 | bodyBytes, err := io.ReadAll(rsp.Body) 178 | if err != nil { 179 | return err 180 | } 181 | return decodeError(bodyBytes, rsp) 182 | } 183 | return nil 184 | {{end}} 185 | } 186 | 187 | {{end}}{{/* if */}} 188 | {{end}}{{/* Range */}} 189 | 190 | /* 191 | 192 | */ -------------------------------------------------------------------------------- /domain/templates/client.tmpl: -------------------------------------------------------------------------------- 1 | 2 | // Doer performs HTTP requests. 3 | // 4 | // The standard http.Client implements this interface. 5 | type HTTPRequestDoer interface { 6 | Do(req *http.Request) (*http.Response, error) 7 | } 8 | 9 | // Client which conforms to the OpenAPI3 specification for this service. 10 | type Client struct { 11 | // The endpoint of the server conforming to this interface, with scheme, 12 | // https://api.deepmap.com for example. This can contain a path relative 13 | // to the server, such as https://api.deepmap.com/dev-test, and all the 14 | // paths in the swagger spec will be appended to the server. 15 | Server string 16 | 17 | // Server + /api/v2/ 18 | APIEndpoint string 19 | 20 | // Doer for performing requests, typically a *http.Client with any 21 | // customized settings, such as certificate chains. 22 | Client HTTPRequestDoer 23 | } 24 | 25 | 26 | // Creates a new Client, with reasonable defaults 27 | func NewClient(server string, doer HTTPRequestDoer) (*Client, error) { 28 | // create a client with sane default values 29 | client := Client{ 30 | Server: server, 31 | Client: doer, 32 | } 33 | // ensure the server URL always has a trailing slash 34 | if !strings.HasSuffix(client.Server, "/") { 35 | client.Server += "/" 36 | } 37 | // API endpoint 38 | client.APIEndpoint = client.Server + "api/v2/" 39 | 40 | // create httpClient, if not already present 41 | if client.Client == nil { 42 | client.Client = &http.Client{} 43 | } 44 | return &client, nil 45 | } 46 | 47 | func(e *Error) Error() error { 48 | return fmt.Errorf("%s: %s", string(e.Code), *e.Message) 49 | } 50 | 51 | func unmarshalJSONResponse(bodyBytes []byte, obj interface{}) error { 52 | if err := json.Unmarshal(bodyBytes, obj); err != nil { 53 | return err 54 | } 55 | return nil 56 | } 57 | 58 | func isJSON(rsp *http.Response) bool { 59 | ctype, _, _ := mime.ParseMediaType(rsp.Header.Get("Content-Type")) 60 | return ctype == "application/json" 61 | } 62 | 63 | func decodeError(body []byte, rsp *http.Response) error { 64 | if isJSON(rsp) { 65 | var serverError struct { 66 | Error 67 | V1Error *string `json:"error,omitempty"` 68 | } 69 | err := json.Unmarshal(body, &serverError) 70 | if err != nil { 71 | message := fmt.Sprintf("cannot decode error response: %v", err) 72 | serverError.Message = &message 73 | } 74 | if serverError.V1Error != nil { 75 | serverError.Message = serverError.V1Error 76 | serverError.Code = ErrorCodeInvalid 77 | } 78 | if serverError.Message == nil && serverError.Code == "" { 79 | serverError.Message = &rsp.Status 80 | } 81 | return serverError.Error.Error() 82 | } else { 83 | message := rsp.Status 84 | if len(body) > 0 { 85 | message = message + ": " + string(body) 86 | } 87 | return errors.New(message) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /domain/templates/imports.tmpl: -------------------------------------------------------------------------------- 1 | // Package {{.PackageName}} provides primitives to interact with the openapi HTTP API. 2 | // 3 | // Code generated by {{.ModuleName}} version {{.Version}} DO NOT EDIT. 4 | package {{.PackageName}} 5 | 6 | import ( 7 | "bytes" 8 | "compress/gzip" 9 | "context" 10 | "encoding/base64" 11 | "encoding/json" 12 | "encoding/xml" 13 | "errors" 14 | "fmt" 15 | "gopkg.in/yaml.v3" 16 | "io" 17 | "net/http" 18 | "net/url" 19 | "path" 20 | "strings" 21 | "time" 22 | 23 | "github.com/oapi-codegen/runtime" 24 | openapi_types "github.com/oapi-codegen/runtime/types" 25 | ihttp "github.com/influxdata/influxdb-client-go/v2/api/http" 26 | "github.com/getkin/kin-openapi/openapi3" 27 | "github.com/go-chi/chi/v5" 28 | "github.com/labstack/echo/v4" 29 | {{- range .ExternalImports}} 30 | {{ . }} 31 | {{- end}} 32 | ) 33 | -------------------------------------------------------------------------------- /domain/templates/param-types.tmpl: -------------------------------------------------------------------------------- 1 | {{range .}}{{$opid := .OperationId}} 2 | {{if (hasValidRequestAndResponse .) -}}{{/* skip non-JSON bodies*/}} 3 | {{range .TypeDefinitions}} 4 | // {{.TypeName}} defines parameters for {{$opid}}. 5 | type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.TypeDecl}} 6 | {{end}} 7 | {{if or .HasBody (gt (len .PathParams) 0) -}} 8 | // {{$opid}}AllParams defines type for all parameters for {{$opid}}. 9 | type {{$opid}}AllParams struct { 10 | {{if .RequiresParamObject -}} 11 | {{$opid}}Params 12 | {{end}} 13 | {{range .PathParams}} 14 | {{.GoVariableName|ucFirst}} {{.TypeDef}} 15 | {{end}} 16 | {{if (and .HasBody (len .Bodies))}} 17 | Body {{$opid}}{{(index .Bodies 0).NameTag}}RequestBody 18 | {{end}} 19 | } 20 | {{end}}{{/* if */}} 21 | {{end}}{{/* if */}} 22 | {{end}}{{/* Range */}} -------------------------------------------------------------------------------- /domain/templates/request-bodies.tmpl: -------------------------------------------------------------------------------- 1 | {{range .}}{{$opid := .OperationId}} 2 | {{if (hasValidRequestAndResponse .) -}}{{/* skip non-JSON bodies*/}} 3 | {{range .Bodies}} 4 | {{with .TypeDef $opid}} 5 | // {{.TypeName}} defines body for {{$opid}} for application/json ContentType. 6 | type {{.TypeName}} {{if and (opts.AliasTypes) (.CanAlias)}}={{end}} {{.Schema.TypeDecl}} 7 | {{end}} 8 | {{end}} 9 | {{end}} 10 | {{end}} 11 | -------------------------------------------------------------------------------- /example_ua_set_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package influxdb2_test 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "net/http" 11 | 12 | "github.com/influxdata/influxdb-client-go/v2" 13 | ihttp "github.com/influxdata/influxdb-client-go/v2/api/http" 14 | ) 15 | 16 | // UserAgentSetter is the implementation of Doer interface for setting User-Agent header 17 | type UserAgentSetter struct { 18 | UserAgent string 19 | RequestDoer ihttp.Doer 20 | } 21 | 22 | // Do fulfills the Doer interface 23 | func (u *UserAgentSetter) Do(req *http.Request) (*http.Response, error) { 24 | // Set User-Agent header to request 25 | req.Header.Set("User-Agent", u.UserAgent) 26 | // Call original Doer to proceed with request 27 | return u.RequestDoer.Do(req) 28 | } 29 | 30 | func ExampleClient_customUserAgentHeader() { 31 | // Set custom Doer to HTTPOptions 32 | opts := influxdb2.DefaultOptions() 33 | opts.HTTPOptions().SetHTTPDoer(&UserAgentSetter{ 34 | UserAgent: "NetMonitor/1.1", 35 | RequestDoer: http.DefaultClient, 36 | }) 37 | 38 | //Create client with customized options 39 | client := influxdb2.NewClientWithOptions("http://localhost:8086", "my-token", opts) 40 | 41 | // Always close client at the end 42 | defer client.Close() 43 | 44 | // Issue a call with custom User-Agent header 45 | resp, err := client.Ping(context.Background()) 46 | if err != nil { 47 | panic(err) 48 | } 49 | if resp { 50 | fmt.Println("Server is up") 51 | } else { 52 | fmt.Println("Server is down") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package influxdb2_test 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "github.com/influxdata/influxdb-client-go/v2" 11 | "github.com/influxdata/influxdb-client-go/v2/domain" 12 | ) 13 | 14 | func ExampleClient_newClient() { 15 | // Create a new client using an InfluxDB server base URL and an authentication token 16 | client := influxdb2.NewClient("http://localhost:8086", "my-token") 17 | 18 | // Always close client at the end 19 | defer client.Close() 20 | } 21 | 22 | func ExampleClient_newClientWithOptions() { 23 | // Create a new client using an InfluxDB server base URL and an authentication token 24 | // Create client and set batch size to 20 25 | client := influxdb2.NewClientWithOptions("http://localhost:8086", "my-token", 26 | influxdb2.DefaultOptions().SetBatchSize(20)) 27 | 28 | // Always close client at the end 29 | defer client.Close() 30 | } 31 | 32 | func ExampleClient_customServerAPICall() { 33 | // This example shows how to perform custom server API invocation for any endpoint 34 | // Here we will create a DBRP mapping which allows using buckets in legacy write and query (InfluxQL) endpoints 35 | 36 | // Create client. You need an admin token for creating DBRP mapping 37 | client := influxdb2.NewClient("http://localhost:8086", "my-token") 38 | 39 | // Always close client at the end 40 | defer client.Close() 41 | 42 | // Get generated client for server API calls 43 | apiClient := client.APIClient() 44 | ctx := context.Background() 45 | 46 | // Get a bucket we would like to query using InfluxQL 47 | b, err := client.BucketsAPI().FindBucketByName(ctx, "my-bucket") 48 | if err != nil { 49 | panic(err) 50 | } 51 | // Get an organization that will own the mapping 52 | o, err := client.OrganizationsAPI().FindOrganizationByName(ctx, "my-org") 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | yes := true 58 | // Fill required fields of the DBRP struct 59 | dbrp := domain.DBRPCreate{ 60 | BucketID: *b.Id, 61 | Database: "my-bucket", 62 | Default: &yes, 63 | OrgID: o.Id, 64 | RetentionPolicy: "autogen", 65 | } 66 | 67 | params := &domain.PostDBRPAllParams{ 68 | Body: domain.PostDBRPJSONRequestBody(dbrp), 69 | } 70 | // Call server API 71 | newDbrp, err := apiClient.PostDBRP(ctx, params) 72 | if err != nil { 73 | panic(err) 74 | } 75 | // Check generated response errors 76 | 77 | fmt.Printf("Created DBRP: %#v\n", newDbrp) 78 | } 79 | 80 | func ExampleClient_checkAPICall() { 81 | // This example shows how to perform custom server API invocation for checks API 82 | 83 | // Create client. You need an admin token for creating DBRP mapping 84 | client := influxdb2.NewClient("http://localhost:8086", "my-token") 85 | 86 | // Always close client at the end 87 | defer client.Close() 88 | 89 | ctx := context.Background() 90 | 91 | // Create a new threshold check 92 | greater := domain.GreaterThreshold{} 93 | greater.Value = 10.0 94 | lc := domain.CheckStatusLevelCRIT 95 | greater.Level = &lc 96 | greater.AllValues = &[]bool{true}[0] 97 | 98 | lesser := domain.LesserThreshold{} 99 | lesser.Value = 1.0 100 | lo := domain.CheckStatusLevelOK 101 | lesser.Level = &lo 102 | 103 | rang := domain.RangeThreshold{} 104 | rang.Min = 3.0 105 | rang.Max = 8.0 106 | lw := domain.CheckStatusLevelWARN 107 | rang.Level = &lw 108 | 109 | thresholds := []domain.Threshold{&greater, &lesser, &rang} 110 | 111 | // Get organization where check will be created 112 | org, err := client.OrganizationsAPI().FindOrganizationByName(ctx, "my-org") 113 | if err != nil { 114 | panic(err) 115 | } 116 | 117 | // Prepare necessary parameters 118 | msg := "Check: ${ r._check_name } is: ${ r._level }" 119 | flux := `from(bucket: "foo") |> range(start: -1d, stop: now()) |> aggregateWindow(every: 1m, fn: mean) |> filter(fn: (r) => r._field == "usage_user") |> yield()` 120 | every := "1h" 121 | offset := "0s" 122 | 123 | c := domain.ThresholdCheck{ 124 | CheckBaseExtend: domain.CheckBaseExtend{ 125 | CheckBase: domain.CheckBase{ 126 | Name: "My threshold check", 127 | OrgID: *org.Id, 128 | Query: domain.DashboardQuery{Text: &flux}, 129 | Status: domain.TaskStatusTypeActive, 130 | }, 131 | Every: &every, 132 | Offset: &offset, 133 | StatusMessageTemplate: &msg, 134 | }, 135 | Thresholds: &thresholds, 136 | } 137 | params := domain.CreateCheckAllParams{ 138 | Body: &c, 139 | } 140 | // Call checks API using internal API client 141 | check, err := client.APIClient().CreateCheck(context.Background(), ¶ms) 142 | if err != nil { 143 | panic(err) 144 | } 145 | // Optionally verify type 146 | if check.Type() != string(domain.ThresholdCheckTypeThreshold) { 147 | panic("Check type is not threshold") 148 | } 149 | // Cast check to threshold check 150 | thresholdCheck := check.(*domain.ThresholdCheck) 151 | fmt.Printf("Created threshold check with id %s\n", *thresholdCheck.Id) 152 | } 153 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/influxdata/influxdb-client-go/v2 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.8 6 | 7 | require ( 8 | github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 9 | github.com/oapi-codegen/runtime v1.1.1 10 | github.com/stretchr/testify v1.8.4 // test dependency 11 | golang.org/x/net v0.38.0 12 | ) 13 | 14 | require ( 15 | github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect 16 | github.com/davecgh/go-spew v1.1.1 // indirect 17 | github.com/google/uuid v1.5.0 // indirect 18 | github.com/pmezard/go-difflib v1.0.0 // indirect 19 | gopkg.in/yaml.v3 v3.0.1 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= 2 | github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= 3 | github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= 4 | github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= 9 | github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 10 | github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= 11 | github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= 12 | github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= 13 | github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= 14 | github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= 15 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 16 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 17 | github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= 18 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 19 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 20 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 21 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 22 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 23 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 24 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 25 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 26 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 27 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 28 | -------------------------------------------------------------------------------- /internal/examples/fakeclient.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package examples contains fake client with the same interface as real client to overcome import-cycle problem 6 | // to allow real E2E examples for apis in this package 7 | package examples 8 | 9 | import ( 10 | "context" 11 | "github.com/influxdata/influxdb-client-go/v2/api" 12 | "github.com/influxdata/influxdb-client-go/v2/domain" 13 | ) 14 | 15 | // Options is fake options to satisfy Client interface 16 | type Options struct { 17 | } 18 | 19 | // SetBatchSize to emulate fake options 20 | func (o *Options) SetBatchSize(_ uint) *Options { 21 | return o 22 | } 23 | 24 | // FakeClient emulates Client for allowing using client in api examples 25 | type FakeClient struct { 26 | } 27 | 28 | // NewClient returns new FakeClient 29 | func NewClient(_ string, _ string) *FakeClient { 30 | client := &FakeClient{} 31 | return client 32 | } 33 | 34 | // Options returns nil 35 | func (c *FakeClient) Options() *Options { 36 | return nil 37 | } 38 | 39 | // ServerURL returns empty server URL 40 | func (c *FakeClient) ServerURL() string { 41 | return "" 42 | } 43 | 44 | // Ready does nothing 45 | func (c *FakeClient) Ready(_ context.Context) (bool, error) { 46 | return true, nil 47 | } 48 | 49 | // Setup does nothing 50 | func (c *FakeClient) Setup(_ context.Context, _, _, _, _ string, _ int) (*domain.OnboardingResponse, error) { 51 | return nil, nil 52 | } 53 | 54 | // Health does nothing 55 | func (c *FakeClient) Health(_ context.Context) (*domain.HealthCheck, error) { 56 | return nil, nil 57 | } 58 | 59 | // WriteAPI does nothing 60 | func (c *FakeClient) WriteAPI(_, _ string) api.WriteAPI { 61 | return nil 62 | } 63 | 64 | // WriteAPIBlocking does nothing 65 | func (c *FakeClient) WriteAPIBlocking(_, _ string) api.WriteAPIBlocking { 66 | return nil 67 | } 68 | 69 | // Close does nothing 70 | func (c *FakeClient) Close() { 71 | } 72 | 73 | // QueryAPI returns nil 74 | func (c *FakeClient) QueryAPI(_ string) api.QueryAPI { 75 | return nil 76 | } 77 | 78 | // AuthorizationsAPI returns nil 79 | func (c *FakeClient) AuthorizationsAPI() api.AuthorizationsAPI { 80 | return nil 81 | } 82 | 83 | // OrganizationsAPI returns nil 84 | func (c *FakeClient) OrganizationsAPI() api.OrganizationsAPI { 85 | return nil 86 | } 87 | 88 | // UsersAPI returns nil 89 | func (c *FakeClient) UsersAPI() api.UsersAPI { 90 | return nil 91 | } 92 | 93 | // DeleteAPI returns nil 94 | func (c *FakeClient) DeleteAPI() api.DeleteAPI { 95 | return nil 96 | } 97 | 98 | // BucketsAPI returns nil 99 | func (c *FakeClient) BucketsAPI() api.BucketsAPI { 100 | return nil 101 | } 102 | 103 | // LabelsAPI returns nil 104 | func (c *FakeClient) LabelsAPI() api.LabelsAPI { 105 | return nil 106 | } 107 | 108 | // TasksAPI returns nil 109 | func (c *FakeClient) TasksAPI() api.TasksAPI { 110 | return nil 111 | } 112 | -------------------------------------------------------------------------------- /internal/gzip/gzip.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc.. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package gzip provides GZip related functionality 6 | package gzip 7 | 8 | import ( 9 | "compress/gzip" 10 | "io" 11 | "sync" 12 | ) 13 | 14 | // ReadWaitCloser is ReadCloser that waits for finishing underlying reader 15 | type ReadWaitCloser struct { 16 | pipeReader *io.PipeReader 17 | wg sync.WaitGroup 18 | } 19 | 20 | // Close closes underlying reader and waits for finishing operations 21 | func (r *ReadWaitCloser) Close() error { 22 | err := r.pipeReader.Close() 23 | r.wg.Wait() // wait for the gzip goroutine finish 24 | return err 25 | } 26 | 27 | // CompressWithGzip takes an io.Reader as input and pipes 28 | // it through a gzip.Writer returning an io.Reader containing 29 | // the gzipped data. 30 | // An error is returned if passing data to the gzip.Writer fails 31 | // this is shamelessly stolen from https://github.com/influxdata/telegraf 32 | func CompressWithGzip(data io.Reader) (io.ReadCloser, error) { 33 | pipeReader, pipeWriter := io.Pipe() 34 | gzipWriter := gzip.NewWriter(pipeWriter) 35 | 36 | rc := &ReadWaitCloser{ 37 | pipeReader: pipeReader, 38 | } 39 | 40 | rc.wg.Add(1) 41 | var err error 42 | go func() { 43 | _, err = io.Copy(gzipWriter, data) 44 | gzipWriter.Close() 45 | // subsequent reads from the read half of the pipe will 46 | // return no bytes and the error err, or EOF if err is nil. 47 | pipeWriter.CloseWithError(err) 48 | rc.wg.Done() 49 | }() 50 | 51 | return pipeReader, err 52 | } 53 | -------------------------------------------------------------------------------- /internal/gzip/gzip_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc.. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package gzip_test 6 | 7 | import ( 8 | "bytes" 9 | egzip "compress/gzip" 10 | "io" 11 | "testing" 12 | 13 | "github.com/influxdata/influxdb-client-go/v2/internal/gzip" 14 | ) 15 | 16 | func TestGzip(t *testing.T) { 17 | text := `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.` 18 | inputBuffer := bytes.NewBuffer([]byte(text)) 19 | r, err := gzip.CompressWithGzip(inputBuffer) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | ur, err := egzip.NewReader(r) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | res, err := io.ReadAll(ur) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | if string(res) != text { 32 | t.Fatal("text did not encode or possibly decode properly") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/http/userAgent.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package http hold internal HTTP related stuff 6 | package http 7 | 8 | import ( 9 | "fmt" 10 | ) 11 | 12 | // UserAgentBase keeps once created base User-Agent string 13 | var UserAgentBase string 14 | 15 | // FormatUserAgent creates User-Agent header value for application name 16 | func FormatUserAgent(appName string) string { 17 | if appName != "" { 18 | return fmt.Sprintf("%s %s", UserAgentBase, appName) 19 | } 20 | return UserAgentBase 21 | } 22 | -------------------------------------------------------------------------------- /internal/log/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package log provides internal logging infrastructure 6 | package log 7 | 8 | import ( 9 | ilog "github.com/influxdata/influxdb-client-go/v2/log" 10 | ) 11 | 12 | // Debugf writes formatted debug message to the Logger instance 13 | func Debugf(format string, v ...interface{}) { 14 | if ilog.Log != nil { 15 | ilog.Log.Debugf(format, v...) 16 | } 17 | } 18 | 19 | // Debug writes debug message message to the Logger instance 20 | func Debug(msg string) { 21 | if ilog.Log != nil { 22 | ilog.Log.Debug(msg) 23 | } 24 | } 25 | 26 | // Infof writes formatted info message to the Logger instance 27 | func Infof(format string, v ...interface{}) { 28 | if ilog.Log != nil { 29 | ilog.Log.Infof(format, v...) 30 | } 31 | } 32 | 33 | // Info writes info message message to the Logger instance 34 | func Info(msg string) { 35 | if ilog.Log != nil { 36 | ilog.Log.Info(msg) 37 | } 38 | } 39 | 40 | // Warnf writes formatted warning message to the Logger instance 41 | func Warnf(format string, v ...interface{}) { 42 | if ilog.Log != nil { 43 | ilog.Log.Warnf(format, v...) 44 | } 45 | } 46 | 47 | // Warn writes warning message message to the Logger instance 48 | func Warn(msg string) { 49 | if ilog.Log != nil { 50 | ilog.Log.Warn(msg) 51 | } 52 | } 53 | 54 | // Errorf writes formatted error message to the Logger instance 55 | func Errorf(format string, v ...interface{}) { 56 | if ilog.Log != nil { 57 | ilog.Log.Errorf(format, v...) 58 | } 59 | } 60 | 61 | // Error writes error message message to the Logger instance 62 | func Error(msg string) { 63 | if ilog.Log != nil { 64 | ilog.Log.Error(msg) 65 | } 66 | } 67 | 68 | // Level retrieves current logging level form the Logger instance 69 | func Level() uint { 70 | if ilog.Log != nil { 71 | return ilog.Log.LogLevel() 72 | } 73 | return ilog.ErrorLevel 74 | } 75 | -------------------------------------------------------------------------------- /internal/log/logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package log_test 6 | 7 | import ( 8 | "log" 9 | "strings" 10 | "testing" 11 | 12 | ilog "github.com/influxdata/influxdb-client-go/v2/internal/log" 13 | dlog "github.com/influxdata/influxdb-client-go/v2/log" 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | func TestLogging(t *testing.T) { 18 | var sb strings.Builder 19 | log.SetOutput(&sb) 20 | dlog.Log.SetLogLevel(dlog.DebugLevel) 21 | assert.Equal(t, dlog.DebugLevel, dlog.Log.LogLevel()) 22 | assert.Equal(t, dlog.DebugLevel, ilog.Level()) 23 | //test default settings 24 | ilog.Debug("Debug") 25 | ilog.Debugf("Debugf %s %d", "message", 1) 26 | ilog.Info("Info") 27 | ilog.Infof("Infof %s %d", "message", 2) 28 | ilog.Warn("Warn") 29 | ilog.Warnf("Warnf %s %d", "message", 3) 30 | ilog.Error("Error") 31 | ilog.Errorf("Errorf %s %d", "message", 4) 32 | assert.True(t, strings.Contains(sb.String(), "Debug")) 33 | assert.True(t, strings.Contains(sb.String(), "Debugf message 1")) 34 | assert.True(t, strings.Contains(sb.String(), "Info")) 35 | assert.True(t, strings.Contains(sb.String(), "Infof message 2")) 36 | assert.True(t, strings.Contains(sb.String(), "Warn")) 37 | assert.True(t, strings.Contains(sb.String(), "Warnf message 3")) 38 | assert.True(t, strings.Contains(sb.String(), "Error")) 39 | assert.True(t, strings.Contains(sb.String(), "Errorf message 4")) 40 | 41 | sb.Reset() 42 | 43 | dlog.Log = nil 44 | ilog.Debug("Debug") 45 | ilog.Debugf("Debugf %s %d", "message", 1) 46 | ilog.Info("Info") 47 | ilog.Infof("Infof %s %d", "message", 2) 48 | ilog.Warn("Warn") 49 | ilog.Warnf("Warnf %s %d", "message", 3) 50 | ilog.Error("Error") 51 | ilog.Errorf("Errorf %s %d", "message", 4) 52 | assert.True(t, len(sb.String()) == 0) 53 | } 54 | -------------------------------------------------------------------------------- /internal/test/generators.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/influxdata/influxdb-client-go/v2/api/write" 9 | ) 10 | 11 | // GenPoints generates num points 12 | func GenPoints(num int) []*write.Point { 13 | points := make([]*write.Point, num) 14 | 15 | t := time.Now() 16 | for i := 0; i < len(points); i++ { 17 | points[i] = write.NewPoint( 18 | "test", 19 | map[string]string{ 20 | "id": fmt.Sprintf("rack_%v", i%10), 21 | "vendor": "AWS", 22 | "hostname": fmt.Sprintf("host_%v", i%100), 23 | }, 24 | map[string]interface{}{ 25 | "temperature": rand.Float64() * 80.0, 26 | "disk_free": rand.Float64() * 1000.0, 27 | "disk_total": (i/10 + 1) * 1000000, 28 | "mem_total": (i/100 + 1) * 10000000, 29 | "mem_free": rand.Uint64(), 30 | }, 31 | t) 32 | if i%10 == 0 { 33 | t = t.Add(time.Second) 34 | } 35 | } 36 | return points 37 | } 38 | 39 | // GenRecords generates num points 40 | func GenRecords(num int) []string { 41 | lines := make([]string, num) 42 | 43 | t := time.Now() 44 | for i := 0; i < len(lines); i++ { 45 | lines[i] = fmt.Sprintf("test,id=rack_%v,vendor=AWS,hostname=host_%v temperature=%v,disk_free=%v,disk_total=%vi,mem_total=%vi,mem_free=%vu %v", 46 | i%10, i%100, rand.Float64()*80.0, rand.Float64()*1000.0, (i/10+1)*1000000, (i/100+1)*10000000, rand.Uint64(), t.UnixNano()) 47 | if i%10 == 0 { 48 | t = t.Add(time.Nanosecond) 49 | } 50 | } 51 | return lines 52 | } 53 | -------------------------------------------------------------------------------- /internal/test/http_service.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package test provides shared test utils 6 | package test 7 | 8 | import ( 9 | "compress/gzip" 10 | "context" 11 | "fmt" 12 | "io" 13 | "net/http" 14 | "strings" 15 | "sync" 16 | "testing" 17 | 18 | http2 "github.com/influxdata/influxdb-client-go/v2/api/http" 19 | "github.com/stretchr/testify/assert" 20 | ) 21 | 22 | // HTTPService is http.Service implementation for tests 23 | type HTTPService struct { 24 | serverURL string 25 | authorization string 26 | lines []string 27 | t *testing.T 28 | wasGzip bool 29 | requestHandler func(url string, body io.Reader) error 30 | replyError *http2.Error 31 | lock sync.Mutex 32 | requests int 33 | } 34 | 35 | // WasGzip returns true of request was in GZip format 36 | func (t *HTTPService) WasGzip() bool { 37 | return t.wasGzip 38 | } 39 | 40 | // SetWasGzip sets wasGzip flag 41 | func (t *HTTPService) SetWasGzip(wasGzip bool) { 42 | t.wasGzip = wasGzip 43 | } 44 | 45 | // SetRequestHandler sets custom handler for requests 46 | func (t *HTTPService) SetRequestHandler(fn func(url string, body io.Reader) error) { 47 | t.requestHandler = fn 48 | } 49 | 50 | // ServerURL returns testing URL 51 | func (t *HTTPService) ServerURL() string { 52 | return t.serverURL 53 | } 54 | 55 | // ServerAPIURL returns testing URL 56 | func (t *HTTPService) ServerAPIURL() string { 57 | return t.serverURL 58 | } 59 | 60 | // Authorization returns current authorization header value 61 | func (t *HTTPService) Authorization() string { 62 | return t.authorization 63 | } 64 | 65 | // HTTPClient returns nil for this service 66 | func (t *HTTPService) HTTPClient() *http.Client { 67 | return nil 68 | } 69 | 70 | // Requests returns number of requests 71 | func (t *HTTPService) Requests() int { 72 | return t.requests 73 | } 74 | 75 | // Close clears instance 76 | func (t *HTTPService) Close() { 77 | t.lock.Lock() 78 | if len(t.lines) > 0 { 79 | t.lines = t.lines[:0] 80 | } 81 | t.wasGzip = false 82 | t.replyError = nil 83 | t.requestHandler = nil 84 | t.requests = 0 85 | t.lock.Unlock() 86 | } 87 | 88 | // SetReplyError sets Error that will be returned as a response 89 | func (t *HTTPService) SetReplyError(replyError *http2.Error) { 90 | t.lock.Lock() 91 | defer t.lock.Unlock() 92 | t.replyError = replyError 93 | } 94 | 95 | // ReplyError returns current reply error 96 | func (t *HTTPService) ReplyError() *http2.Error { 97 | t.lock.Lock() 98 | defer t.lock.Unlock() 99 | return t.replyError 100 | } 101 | 102 | // SetAuthorization sets authorization string 103 | func (t *HTTPService) SetAuthorization(_ string) { 104 | } 105 | 106 | // GetRequest does nothing for this service 107 | func (t *HTTPService) GetRequest(_ context.Context, _ string, _ http2.RequestCallback, _ http2.ResponseCallback) *http2.Error { 108 | return nil 109 | } 110 | 111 | // DoHTTPRequest does nothing for this service 112 | func (t *HTTPService) DoHTTPRequest(_ *http.Request, _ http2.RequestCallback, _ http2.ResponseCallback) *http2.Error { 113 | return nil 114 | } 115 | 116 | // DoHTTPRequestWithResponse does nothing for this service 117 | func (t *HTTPService) DoHTTPRequestWithResponse(_ *http.Request, _ http2.RequestCallback) (*http.Response, error) { 118 | return nil, nil 119 | } 120 | 121 | // DoPostRequest reads http request, validates URL and stores data in the request 122 | func (t *HTTPService) DoPostRequest(_ context.Context, url string, body io.Reader, requestCallback http2.RequestCallback, _ http2.ResponseCallback) *http2.Error { 123 | req, err := http.NewRequest("POST", url, nil) 124 | t.lock.Lock() 125 | t.requests++ 126 | t.lock.Unlock() 127 | if err != nil { 128 | return http2.NewError(err) 129 | } 130 | if requestCallback != nil { 131 | requestCallback(req) 132 | } 133 | if req.Header.Get("Content-Encoding") == "gzip" { 134 | body, _ = gzip.NewReader(body) 135 | t.wasGzip = true 136 | } 137 | if t.t != nil { 138 | assert.Equal(t.t, fmt.Sprintf("%swrite?bucket=my-bucket&org=my-org&precision=ns", t.serverURL), url) 139 | } 140 | 141 | if t.ReplyError() != nil { 142 | return t.ReplyError() 143 | } 144 | if t.requestHandler != nil { 145 | err = t.requestHandler(url, body) 146 | } else { 147 | err = t.DecodeLines(body) 148 | } 149 | 150 | if err != nil { 151 | return http2.NewError(err) 152 | } 153 | return nil 154 | } 155 | 156 | // DecodeLines parses request body for lines 157 | func (t *HTTPService) DecodeLines(body io.Reader) error { 158 | bytes, err := io.ReadAll(body) 159 | if err != nil { 160 | return err 161 | } 162 | lines := strings.Split(string(bytes), "\n") 163 | if lines[len(lines)-1] == "" { 164 | lines = lines[:len(lines)-1] 165 | } 166 | t.lock.Lock() 167 | t.lines = append(t.lines, lines...) 168 | t.lock.Unlock() 169 | return nil 170 | } 171 | 172 | // Lines returns decoded lines from request 173 | func (t *HTTPService) Lines() []string { 174 | t.lock.Lock() 175 | defer t.lock.Unlock() 176 | return t.lines 177 | } 178 | 179 | // NewTestService creates new test HTTP service 180 | func NewTestService(t *testing.T, serverURL string) *HTTPService { 181 | return &HTTPService{ 182 | t: t, 183 | serverURL: serverURL + "/api/v2/", 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /internal/write/queue.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package write 6 | 7 | import ( 8 | "container/list" 9 | ) 10 | 11 | type queue struct { 12 | list *list.List 13 | limit int 14 | } 15 | 16 | func newQueue(limit int) *queue { 17 | return &queue{list: list.New(), limit: limit} 18 | } 19 | func (q *queue) push(batch *Batch) bool { 20 | overWrite := false 21 | if q.list.Len() == q.limit { 22 | q.pop() 23 | overWrite = true 24 | } 25 | q.list.PushBack(batch) 26 | return overWrite 27 | } 28 | 29 | func (q *queue) pop() *Batch { 30 | el := q.list.Front() 31 | if el != nil { 32 | q.list.Remove(el) 33 | batch := el.Value.(*Batch) 34 | batch.Evicted = true 35 | return batch 36 | } 37 | return nil 38 | } 39 | 40 | func (q *queue) first() *Batch { 41 | el := q.list.Front() 42 | if el != nil { 43 | return el.Value.(*Batch) 44 | } 45 | return nil 46 | } 47 | 48 | func (q *queue) isEmpty() bool { 49 | return q.list.Len() == 0 50 | } 51 | -------------------------------------------------------------------------------- /internal/write/queue_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package write 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestQueue(t *testing.T) { 14 | que := newQueue(2) 15 | assert.True(t, que.isEmpty()) 16 | assert.Nil(t, que.first()) 17 | assert.Nil(t, que.pop()) 18 | b := &Batch{Batch: "batch", RetryAttempts: 3} 19 | que.push(b) 20 | assert.False(t, que.isEmpty()) 21 | b2 := que.pop() 22 | assert.Equal(t, b, b2) 23 | assert.True(t, que.isEmpty()) 24 | 25 | que.push(b) 26 | que.push(b) 27 | assert.True(t, que.push(b)) 28 | assert.False(t, que.isEmpty()) 29 | que.pop() 30 | que.pop() 31 | assert.True(t, que.isEmpty()) 32 | assert.Nil(t, que.pop()) 33 | assert.True(t, que.isEmpty()) 34 | } 35 | -------------------------------------------------------------------------------- /log/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package log defines Logging API. 6 | // The global Log variable contains the actual logger. Set it to own implementation to override logging. Set it to nil to disable logging 7 | package log 8 | 9 | import ( 10 | "fmt" 11 | "log" 12 | "sync" 13 | ) 14 | 15 | // Log is the library wide logger. Setting to nil disables logging. 16 | var Log Logger = &logger{logLevel: ErrorLevel, prefix: "influxdb2client"} 17 | 18 | // Log levels 19 | const ( 20 | ErrorLevel uint = iota 21 | WarningLevel 22 | InfoLevel 23 | DebugLevel 24 | ) 25 | 26 | // Logger defines interface for logging 27 | type Logger interface { 28 | // Writes formatted debug message if debug logLevel is enabled. 29 | Debugf(format string, v ...interface{}) 30 | // Writes debug message if debug is enabled. 31 | Debug(msg string) 32 | // Writes formatted info message if info logLevel is enabled. 33 | Infof(format string, v ...interface{}) 34 | // Writes info message if info logLevel is enabled 35 | Info(msg string) 36 | // Writes formatted warning message if warning logLevel is enabled. 37 | Warnf(format string, v ...interface{}) 38 | // Writes warning message if warning logLevel is enabled. 39 | Warn(msg string) 40 | // Writes formatted error message 41 | Errorf(format string, v ...interface{}) 42 | // Writes error message 43 | Error(msg string) 44 | // SetLogLevel sets allowed logging level. 45 | SetLogLevel(logLevel uint) 46 | // LogLevel retrieves current logging level 47 | LogLevel() uint 48 | // SetPrefix sets logging prefix. 49 | SetPrefix(prefix string) 50 | } 51 | 52 | // logger provides default implementation for Logger. It logs using Go log API 53 | // mutex is needed in cases when multiple clients run concurrently 54 | type logger struct { 55 | prefix string 56 | logLevel uint 57 | lock sync.Mutex 58 | } 59 | 60 | func (l *logger) SetLogLevel(logLevel uint) { 61 | l.lock.Lock() 62 | defer l.lock.Unlock() 63 | l.logLevel = logLevel 64 | } 65 | 66 | func (l *logger) LogLevel() uint { 67 | l.lock.Lock() 68 | defer l.lock.Unlock() 69 | return l.logLevel 70 | } 71 | 72 | func (l *logger) SetPrefix(prefix string) { 73 | l.lock.Lock() 74 | defer l.lock.Unlock() 75 | l.prefix = prefix 76 | } 77 | 78 | func (l *logger) Debugf(format string, v ...interface{}) { 79 | l.lock.Lock() 80 | defer l.lock.Unlock() 81 | if l.logLevel >= DebugLevel { 82 | log.Print(l.prefix, " D! ", fmt.Sprintf(format, v...)) 83 | } 84 | } 85 | func (l *logger) Debug(msg string) { 86 | l.lock.Lock() 87 | defer l.lock.Unlock() 88 | if l.logLevel >= DebugLevel { 89 | log.Print(l.prefix, " D! ", msg) 90 | } 91 | } 92 | 93 | func (l *logger) Infof(format string, v ...interface{}) { 94 | l.lock.Lock() 95 | defer l.lock.Unlock() 96 | if l.logLevel >= InfoLevel { 97 | log.Print(l.prefix, " I! ", fmt.Sprintf(format, v...)) 98 | } 99 | } 100 | func (l *logger) Info(msg string) { 101 | l.lock.Lock() 102 | defer l.lock.Unlock() 103 | if l.logLevel >= DebugLevel { 104 | log.Print(l.prefix, " I! ", msg) 105 | } 106 | } 107 | 108 | func (l *logger) Warnf(format string, v ...interface{}) { 109 | l.lock.Lock() 110 | defer l.lock.Unlock() 111 | if l.logLevel >= WarningLevel { 112 | log.Print(l.prefix, " W! ", fmt.Sprintf(format, v...)) 113 | } 114 | } 115 | func (l *logger) Warn(msg string) { 116 | l.lock.Lock() 117 | defer l.lock.Unlock() 118 | if l.logLevel >= WarningLevel { 119 | log.Print(l.prefix, " W! ", msg) 120 | } 121 | } 122 | 123 | func (l *logger) Errorf(format string, v ...interface{}) { 124 | l.lock.Lock() 125 | defer l.lock.Unlock() 126 | log.Print(l.prefix, " E! ", fmt.Sprintf(format, v...)) 127 | } 128 | 129 | func (l *logger) Error(msg string) { 130 | l.lock.Lock() 131 | defer l.lock.Unlock() 132 | log.Print(l.prefix, " E! ", msg) 133 | } 134 | -------------------------------------------------------------------------------- /log/logger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package log_test 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "strings" 11 | "testing" 12 | 13 | logi "github.com/influxdata/influxdb-client-go/v2/internal/log" 14 | dlog "github.com/influxdata/influxdb-client-go/v2/log" 15 | "github.com/stretchr/testify/assert" 16 | ) 17 | 18 | func logMessages() { 19 | dlog.Log.Debug("Debug") 20 | dlog.Log.Debugf("Debugf %s %d", "message", 1) 21 | dlog.Log.Info("Info") 22 | dlog.Log.Infof("Infof %s %d", "message", 2) 23 | dlog.Log.Warn("Warn") 24 | dlog.Log.Warnf("Warnf %s %d", "message", 3) 25 | dlog.Log.Error("Error") 26 | dlog.Log.Errorf("Errorf %s %d", "message", 4) 27 | } 28 | 29 | func verifyLogs(t *testing.T, sb *strings.Builder, logLevel uint, prefix string) { 30 | if logLevel >= dlog.DebugLevel { 31 | assert.True(t, strings.Contains(sb.String(), prefix+" D! Debug")) 32 | assert.True(t, strings.Contains(sb.String(), prefix+" D! Debugf message 1")) 33 | } else { 34 | assert.False(t, strings.Contains(sb.String(), prefix+" D! Debug")) 35 | assert.False(t, strings.Contains(sb.String(), prefix+" D! Debugf message 1")) 36 | } 37 | if logLevel >= dlog.InfoLevel { 38 | assert.True(t, strings.Contains(sb.String(), prefix+" I! Info")) 39 | assert.True(t, strings.Contains(sb.String(), prefix+" I! Infof message 2")) 40 | } else { 41 | assert.False(t, strings.Contains(sb.String(), prefix+" I! Info")) 42 | assert.False(t, strings.Contains(sb.String(), prefix+" I! Infof message 2")) 43 | 44 | } 45 | if logLevel >= dlog.WarningLevel { 46 | assert.True(t, strings.Contains(sb.String(), prefix+" W! Warn")) 47 | assert.True(t, strings.Contains(sb.String(), prefix+" W! Warnf message 3")) 48 | } else { 49 | assert.False(t, strings.Contains(sb.String(), prefix+" W! Warn")) 50 | assert.False(t, strings.Contains(sb.String(), prefix+" W! Warnf message 3")) 51 | } 52 | if logLevel >= dlog.ErrorLevel { 53 | assert.True(t, strings.Contains(sb.String(), prefix+" E! Error")) 54 | assert.True(t, strings.Contains(sb.String(), prefix+" E! Errorf message 4")) 55 | } 56 | } 57 | 58 | func TestLogging(t *testing.T) { 59 | var sb strings.Builder 60 | log.SetOutput(&sb) 61 | log.SetFlags(0) 62 | //test default settings 63 | logMessages() 64 | verifyLogs(t, &sb, dlog.ErrorLevel, "influxdb2client") 65 | 66 | sb.Reset() 67 | dlog.Log.SetLogLevel(dlog.WarningLevel) 68 | logMessages() 69 | verifyLogs(t, &sb, dlog.WarningLevel, "influxdb2client") 70 | 71 | sb.Reset() 72 | dlog.Log.SetLogLevel(dlog.InfoLevel) 73 | logMessages() 74 | verifyLogs(t, &sb, dlog.InfoLevel, "influxdb2client") 75 | 76 | sb.Reset() 77 | dlog.Log.SetLogLevel(dlog.DebugLevel) 78 | logMessages() 79 | verifyLogs(t, &sb, dlog.DebugLevel, "influxdb2client") 80 | 81 | sb.Reset() 82 | dlog.Log.SetPrefix("client") 83 | logMessages() 84 | verifyLogs(t, &sb, dlog.DebugLevel, "client") 85 | } 86 | 87 | func TestCustomLogger(t *testing.T) { 88 | var sb strings.Builder 89 | log.SetOutput(&sb) 90 | log.SetFlags(0) 91 | dlog.Log = &testLogger{} 92 | //test default settings 93 | logMessages() 94 | verifyLogs(t, &sb, dlog.DebugLevel, "testlogger") 95 | } 96 | 97 | type testLogger struct { 98 | } 99 | 100 | func (l *testLogger) LogLevel() uint { 101 | return 0 102 | } 103 | 104 | func (l *testLogger) SetLogLevel(_ uint) { 105 | } 106 | 107 | func (l *testLogger) SetPrefix(_ string) { 108 | } 109 | 110 | func (l *testLogger) Debugf(format string, v ...interface{}) { 111 | log.Print("testlogger", " D! ", fmt.Sprintf(format, v...)) 112 | } 113 | func (l *testLogger) Debug(msg string) { 114 | log.Print("testlogger", " D! ", msg) 115 | } 116 | 117 | func (l *testLogger) Infof(format string, v ...interface{}) { 118 | log.Print("testlogger", " I! ", fmt.Sprintf(format, v...)) 119 | } 120 | func (l *testLogger) Info(msg string) { 121 | log.Print("testlogger", " I! ", msg) 122 | } 123 | 124 | func (l *testLogger) Warnf(format string, v ...interface{}) { 125 | log.Print("testlogger", " W! ", fmt.Sprintf(format, v...)) 126 | } 127 | func (l *testLogger) Warn(msg string) { 128 | log.Print("testlogger", " W! ", msg) 129 | } 130 | 131 | func (l *testLogger) Errorf(format string, v ...interface{}) { 132 | log.Print("testlogger", " E! ", fmt.Sprintf(format, v...)) 133 | } 134 | 135 | func (l *testLogger) Error(msg string) { 136 | log.Print("testlogger", " [E]! ", msg) 137 | } 138 | 139 | type nullWriter struct{} 140 | 141 | func (m *nullWriter) Write(p []byte) (n int, err error) { 142 | return len(p), nil 143 | } 144 | 145 | func BenchmarkLogger(b *testing.B) { 146 | // run the Fib function b.N times 147 | log.SetOutput(&nullWriter{}) 148 | dlog.Log.SetLogLevel(dlog.DebugLevel) 149 | for n := 0; n < b.N; n++ { 150 | for i := 0; i < 5; i++ { 151 | go logi.Debug("Log nothing") 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package influxdb2 6 | 7 | import ( 8 | "crypto/tls" 9 | nethttp "net/http" 10 | "time" 11 | 12 | "github.com/influxdata/influxdb-client-go/v2/api/http" 13 | "github.com/influxdata/influxdb-client-go/v2/api/write" 14 | ) 15 | 16 | // Options holds configuration properties for communicating with InfluxDB server 17 | type Options struct { 18 | // LogLevel to filter log messages. Each level mean to log all categories bellow. 0 error, 1 - warning, 2 - info, 3 - debug 19 | logLevel uint 20 | // Writing options 21 | writeOptions *write.Options 22 | // Http options 23 | httpOptions *http.Options 24 | } 25 | 26 | // BatchSize returns size of batch 27 | func (o *Options) BatchSize() uint { 28 | return o.WriteOptions().BatchSize() 29 | } 30 | 31 | // SetBatchSize sets number of points sent in single request 32 | func (o *Options) SetBatchSize(batchSize uint) *Options { 33 | o.WriteOptions().SetBatchSize(batchSize) 34 | return o 35 | } 36 | 37 | // FlushInterval returns flush interval in ms 38 | func (o *Options) FlushInterval() uint { 39 | return o.WriteOptions().FlushInterval() 40 | } 41 | 42 | // SetFlushInterval sets flush interval in ms in which is buffer flushed if it has not been already written 43 | func (o *Options) SetFlushInterval(flushIntervalMs uint) *Options { 44 | o.WriteOptions().SetFlushInterval(flushIntervalMs) 45 | return o 46 | } 47 | 48 | // RetryInterval returns the retry interval in ms 49 | func (o *Options) RetryInterval() uint { 50 | return o.WriteOptions().RetryInterval() 51 | } 52 | 53 | // SetRetryInterval sets retry interval in ms, which is set if not sent by server 54 | func (o *Options) SetRetryInterval(retryIntervalMs uint) *Options { 55 | o.WriteOptions().SetRetryInterval(retryIntervalMs) 56 | return o 57 | } 58 | 59 | // MaxRetries returns maximum count of retry attempts of failed writes, default 5. 60 | func (o *Options) MaxRetries() uint { 61 | return o.WriteOptions().MaxRetries() 62 | } 63 | 64 | // SetMaxRetries sets maximum count of retry attempts of failed writes. 65 | // Setting zero value disables retry strategy. 66 | func (o *Options) SetMaxRetries(maxRetries uint) *Options { 67 | o.WriteOptions().SetMaxRetries(maxRetries) 68 | return o 69 | } 70 | 71 | // RetryBufferLimit returns retry buffer limit 72 | func (o *Options) RetryBufferLimit() uint { 73 | return o.WriteOptions().RetryBufferLimit() 74 | } 75 | 76 | // SetRetryBufferLimit sets maximum number of points to keep for retry. Should be multiple of BatchSize. 77 | func (o *Options) SetRetryBufferLimit(retryBufferLimit uint) *Options { 78 | o.WriteOptions().SetRetryBufferLimit(retryBufferLimit) 79 | return o 80 | } 81 | 82 | // MaxRetryInterval returns the maximum delay between each retry attempt in milliseconds, default 125,000. 83 | func (o *Options) MaxRetryInterval() uint { 84 | return o.WriteOptions().MaxRetryInterval() 85 | } 86 | 87 | // SetMaxRetryInterval sets the maximum delay between each retry attempt in millisecond. 88 | func (o *Options) SetMaxRetryInterval(maxRetryIntervalMs uint) *Options { 89 | o.WriteOptions().SetMaxRetryInterval(maxRetryIntervalMs) 90 | return o 91 | } 92 | 93 | // MaxRetryTime returns the maximum total retry timeout in millisecond, default 180,000. 94 | func (o *Options) MaxRetryTime() uint { 95 | return o.WriteOptions().MaxRetryTime() 96 | } 97 | 98 | // SetMaxRetryTime sets the maximum total retry timeout in millisecond. 99 | func (o *Options) SetMaxRetryTime(maxRetryTimeMs uint) *Options { 100 | o.WriteOptions().SetMaxRetryTime(maxRetryTimeMs) 101 | return o 102 | } 103 | 104 | // ExponentialBase returns the base for the exponential retry delay. Default 2. 105 | func (o *Options) ExponentialBase() uint { 106 | return o.WriteOptions().ExponentialBase() 107 | } 108 | 109 | // SetExponentialBase sets the base for the exponential retry delay. 110 | func (o *Options) SetExponentialBase(exponentialBase uint) *Options { 111 | o.WriteOptions().SetExponentialBase(exponentialBase) 112 | return o 113 | } 114 | 115 | // LogLevel returns log level 116 | func (o *Options) LogLevel() uint { 117 | return o.logLevel 118 | } 119 | 120 | // SetLogLevel set level to filter log messages. Each level mean to log all categories bellow. Default is ErrorLevel. 121 | // There are four level constant int the log package in this library: 122 | // - ErrorLevel 123 | // - WarningLevel 124 | // - InfoLevel 125 | // - DebugLevel 126 | // The DebugLevel will print also content of writen batches, queries. 127 | // The InfoLevel prints HTTP requests info, among others. 128 | // Set log.Log to nil in order to completely disable logging. 129 | func (o *Options) SetLogLevel(logLevel uint) *Options { 130 | o.logLevel = logLevel 131 | return o 132 | } 133 | 134 | // Precision returns time precision for writes 135 | func (o *Options) Precision() time.Duration { 136 | return o.WriteOptions().Precision() 137 | } 138 | 139 | // SetPrecision sets time precision to use in writes for timestamp. In unit of duration: time.Nanosecond, time.Microsecond, time.Millisecond, time.Second 140 | func (o *Options) SetPrecision(precision time.Duration) *Options { 141 | o.WriteOptions().SetPrecision(precision) 142 | return o 143 | } 144 | 145 | // UseGZip returns true if write request are gzip`ed 146 | func (o *Options) UseGZip() bool { 147 | return o.WriteOptions().UseGZip() 148 | } 149 | 150 | // SetUseGZip specifies whether to use GZip compression in write requests. 151 | func (o *Options) SetUseGZip(useGZip bool) *Options { 152 | o.WriteOptions().SetUseGZip(useGZip) 153 | return o 154 | } 155 | 156 | // HTTPClient returns the http.Client that is configured to be used 157 | // for HTTP requests. It will return the one that has been set using 158 | // SetHTTPClient or it will construct a default client using the 159 | // other configured options. 160 | func (o *Options) HTTPClient() *nethttp.Client { 161 | return o.httpOptions.HTTPClient() 162 | } 163 | 164 | // SetHTTPClient will configure the http.Client that is used 165 | // for HTTP requests. If set to nil, an HTTPClient will be 166 | // generated. 167 | // 168 | // Setting the HTTPClient will cause the other HTTP options 169 | // to be ignored. 170 | // In case of UsersAPI.SignIn() is used, HTTPClient.Jar will be used for storing session cookie. 171 | func (o *Options) SetHTTPClient(c *nethttp.Client) *Options { 172 | o.httpOptions.SetHTTPClient(c) 173 | return o 174 | } 175 | 176 | // TLSConfig returns TLS config 177 | func (o *Options) TLSConfig() *tls.Config { 178 | return o.HTTPOptions().TLSConfig() 179 | } 180 | 181 | // SetTLSConfig sets TLS configuration for secure connection 182 | func (o *Options) SetTLSConfig(tlsConfig *tls.Config) *Options { 183 | o.HTTPOptions().SetTLSConfig(tlsConfig) 184 | return o 185 | } 186 | 187 | // HTTPRequestTimeout returns HTTP request timeout 188 | func (o *Options) HTTPRequestTimeout() uint { 189 | return o.HTTPOptions().HTTPRequestTimeout() 190 | } 191 | 192 | // SetHTTPRequestTimeout sets HTTP request timeout in sec 193 | func (o *Options) SetHTTPRequestTimeout(httpRequestTimeout uint) *Options { 194 | o.HTTPOptions().SetHTTPRequestTimeout(httpRequestTimeout) 195 | return o 196 | } 197 | 198 | // WriteOptions returns write related options 199 | func (o *Options) WriteOptions() *write.Options { 200 | if o.writeOptions == nil { 201 | o.writeOptions = write.DefaultOptions() 202 | } 203 | return o.writeOptions 204 | } 205 | 206 | // HTTPOptions returns HTTP related options 207 | func (o *Options) HTTPOptions() *http.Options { 208 | if o.httpOptions == nil { 209 | o.httpOptions = http.DefaultOptions() 210 | } 211 | return o.httpOptions 212 | } 213 | 214 | // AddDefaultTag adds a default tag. DefaultTags are added to each written point. 215 | // If a tag with the same key already exist it is overwritten. 216 | // If a point already defines such a tag, it is left unchanged 217 | func (o *Options) AddDefaultTag(key, value string) *Options { 218 | o.WriteOptions().AddDefaultTag(key, value) 219 | return o 220 | } 221 | 222 | // ApplicationName returns application name used in the User-Agent HTTP header 223 | func (o *Options) ApplicationName() string { 224 | return o.HTTPOptions().ApplicationName() 225 | } 226 | 227 | // SetApplicationName sets an application name to the User-Agent HTTP header 228 | func (o *Options) SetApplicationName(appName string) *Options { 229 | o.HTTPOptions().SetApplicationName(appName) 230 | return o 231 | } 232 | 233 | // DefaultOptions returns Options object with default values 234 | func DefaultOptions() *Options { 235 | return &Options{logLevel: 0, writeOptions: write.DefaultOptions(), httpOptions: http.DefaultOptions()} 236 | } 237 | -------------------------------------------------------------------------------- /options_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package influxdb2_test 6 | 7 | import ( 8 | "context" 9 | "crypto/tls" 10 | "net/http" 11 | "net/http/httptest" 12 | "strings" 13 | "testing" 14 | "time" 15 | 16 | influxdb2 "github.com/influxdata/influxdb-client-go/v2" 17 | "github.com/stretchr/testify/assert" 18 | "github.com/stretchr/testify/require" 19 | ) 20 | 21 | func TestDefaultOptions(t *testing.T) { 22 | opts := influxdb2.DefaultOptions() 23 | assert.EqualValues(t, 5_000, opts.BatchSize()) 24 | assert.EqualValues(t, false, opts.UseGZip()) 25 | assert.EqualValues(t, 1_000, opts.FlushInterval()) 26 | assert.EqualValues(t, time.Nanosecond, opts.Precision()) 27 | assert.EqualValues(t, 50_000, opts.RetryBufferLimit()) 28 | assert.EqualValues(t, 5_000, opts.RetryInterval()) 29 | assert.EqualValues(t, 5, opts.MaxRetries()) 30 | assert.EqualValues(t, 125_000, opts.MaxRetryInterval()) 31 | assert.EqualValues(t, 180_000, opts.MaxRetryTime()) 32 | assert.EqualValues(t, 2, opts.ExponentialBase()) 33 | assert.EqualValues(t, (*tls.Config)(nil), opts.TLSConfig()) 34 | assert.EqualValues(t, 20, opts.HTTPRequestTimeout()) 35 | assert.EqualValues(t, 0, opts.LogLevel()) 36 | assert.EqualValues(t, "", opts.ApplicationName()) 37 | } 38 | 39 | func TestSettingsOptions(t *testing.T) { 40 | tlsConfig := &tls.Config{ 41 | InsecureSkipVerify: true, 42 | } 43 | opts := influxdb2.DefaultOptions(). 44 | SetBatchSize(5). 45 | SetUseGZip(true). 46 | SetFlushInterval(5_000). 47 | SetPrecision(time.Millisecond). 48 | SetRetryBufferLimit(5). 49 | SetRetryInterval(1_000). 50 | SetMaxRetryInterval(10_000). 51 | SetMaxRetries(7). 52 | SetMaxRetryTime(500_000). 53 | SetExponentialBase(5). 54 | SetTLSConfig(tlsConfig). 55 | SetHTTPRequestTimeout(50). 56 | SetLogLevel(3). 57 | AddDefaultTag("t", "a"). 58 | SetApplicationName("Monitor/1.1") 59 | assert.EqualValues(t, 5, opts.BatchSize()) 60 | assert.EqualValues(t, true, opts.UseGZip()) 61 | assert.EqualValues(t, 5_000, opts.FlushInterval()) 62 | assert.EqualValues(t, time.Millisecond, opts.Precision()) 63 | assert.EqualValues(t, 5, opts.RetryBufferLimit()) 64 | assert.EqualValues(t, 1_000, opts.RetryInterval()) 65 | assert.EqualValues(t, 10_000, opts.MaxRetryInterval()) 66 | assert.EqualValues(t, 7, opts.MaxRetries()) 67 | assert.EqualValues(t, 500_000, opts.MaxRetryTime()) 68 | assert.EqualValues(t, 5, opts.ExponentialBase()) 69 | assert.EqualValues(t, tlsConfig, opts.TLSConfig()) 70 | assert.EqualValues(t, 50, opts.HTTPRequestTimeout()) 71 | assert.EqualValues(t, "Monitor/1.1", opts.ApplicationName()) 72 | if client := opts.HTTPClient(); assert.NotNil(t, client) { 73 | assert.EqualValues(t, 50*time.Second, client.Timeout) 74 | assert.Equal(t, tlsConfig, client.Transport.(*http.Transport).TLSClientConfig) 75 | } 76 | assert.EqualValues(t, 3, opts.LogLevel()) 77 | assert.Len(t, opts.WriteOptions().DefaultTags(), 1) 78 | 79 | client := &http.Client{ 80 | Transport: &http.Transport{}, 81 | } 82 | opts.SetHTTPClient(client) 83 | assert.Equal(t, client, opts.HTTPClient()) 84 | } 85 | 86 | func TestTimeout(t *testing.T) { 87 | response := `,result,table,_start,_stop,_time,_value,_field,_measurement,a,b, 88 | ,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,1.4,f,test,1,adsfasdf 89 | ,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,6.6,f,test,1,adsfasdf 90 | ` 91 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 92 | <-time.After(100 * time.Millisecond) 93 | if r.Method == http.MethodPost { 94 | w.Header().Set("Content-Type", "text/csv") 95 | w.WriteHeader(http.StatusOK) 96 | <-time.After(2 * time.Second) 97 | _, err := w.Write([]byte(response)) 98 | if err != nil { 99 | w.WriteHeader(http.StatusInternalServerError) 100 | _, _ = w.Write([]byte(err.Error())) 101 | } 102 | } else { 103 | w.WriteHeader(http.StatusNotFound) 104 | } 105 | })) 106 | defer server.Close() 107 | client := influxdb2.NewClientWithOptions(server.URL, "a", influxdb2.DefaultOptions().SetHTTPRequestTimeout(1)) 108 | queryAPI := client.QueryAPI("org") 109 | 110 | _, err := queryAPI.QueryRaw(context.Background(), "flux", nil) 111 | require.NotNil(t, err) 112 | assert.True(t, strings.Contains(err.Error(), "Client.Timeout exceeded")) 113 | 114 | client = influxdb2.NewClientWithOptions(server.URL, "a", influxdb2.DefaultOptions().SetHTTPRequestTimeout(5)) 115 | queryAPI = client.QueryAPI("org") 116 | 117 | result, err := queryAPI.QueryRaw(context.Background(), "flux", nil) 118 | require.Nil(t, err) 119 | assert.Equal(t, response, result) 120 | 121 | } 122 | -------------------------------------------------------------------------------- /scripts/influxdb-restart.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # The MIT License 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | 24 | set -e 25 | 26 | DEFAULT_INFLUXDB_VERSION="1.8.3" 27 | INFLUXDB_VERSION="${INFLUXDB_VERSION:-$DEFAULT_INFLUXDB_VERSION}" 28 | INFLUXDB_IMAGE=influxdb:${INFLUXDB_VERSION}-alpine 29 | 30 | DEFAULT_INFLUXDB_V2_VERSION="latest" 31 | INFLUXDB_V2_VERSION="${INFLUXDB_V2_VERSION:-$DEFAULT_INFLUXDB_V2_VERSION}" 32 | INFLUXDB_V2_IMAGE=influxdb:${INFLUXDB_V2_VERSION} 33 | 34 | SCRIPT_PATH="$( cd "$(dirname "$0")" ; pwd -P )" 35 | 36 | docker kill influxdb || true 37 | docker rm influxdb || true 38 | docker kill influxdb_v2 || true 39 | docker rm influxdb_v2 || true 40 | docker kill influxdb_v2_onboarding || true 41 | docker rm influxdb_v2_onboarding || true 42 | docker network rm influx_network || true 43 | docker network create -d bridge influx_network --subnet 192.168.0.0/24 --gateway 192.168.0.1 44 | 45 | 46 | echo 47 | echo "Restarting InfluxDB [${INFLUXDB_IMAGE}] ..." 48 | echo 49 | 50 | # 51 | # InfluxDB 1.8 52 | # 53 | 54 | docker pull ${INFLUXDB_IMAGE} || true 55 | docker run \ 56 | --detach \ 57 | --name influxdb \ 58 | --network influx_network \ 59 | --publish 8087:8086 \ 60 | --volume ${SCRIPT_PATH}/influxdb.conf:/etc/influxdb/influxdb.conf \ 61 | ${INFLUXDB_IMAGE} 62 | 63 | echo "Wait to start InfluxDB" 64 | wget -S --spider --tries=20 --retry-connrefused --waitretry=5 http://localhost:8087/ping 65 | echo 66 | echo "Post with create dabase" 67 | echo 68 | curl -X POST localhost:8087/query --data-urlencode "q=create database mydb" 69 | # 70 | # InfluxDB 2.0 71 | # 72 | echo 73 | echo "Restarting InfluxDB 2.0 [${INFLUXDB_V2_IMAGE}] ... " 74 | echo 75 | 76 | docker pull ${INFLUXDB_V2_IMAGE} || true 77 | docker run \ 78 | --detach \ 79 | --name influxdb_v2 \ 80 | --network influx_network \ 81 | --publish 8086:8086 \ 82 | ${INFLUXDB_V2_IMAGE} 83 | 84 | echo "Wait to start InfluxDB 2.0" 85 | wget -S --spider --tries=20 --retry-connrefused --waitretry=5 http://localhost:8086/metrics 86 | 87 | echo 88 | echo "Post onBoarding request, to setup initial user (my-user@my-password), org (my-org) and bucketSetup (my-bucket)" 89 | echo 90 | curl -i -X POST http://localhost:8086/api/v2/setup -H 'accept: application/json' \ 91 | -d '{ 92 | "username": "my-user", 93 | "password": "my-password", 94 | "org": "my-org", 95 | "bucket": "my-bucket", 96 | "token": "my-token" 97 | }' 98 | 99 | # 100 | # InfluxDB 2.0 101 | # 102 | echo 103 | echo "Restarting InfluxDB 2.0 for onboarding test... " 104 | echo 105 | 106 | docker run \ 107 | --detach \ 108 | --name influxdb_v2_onboarding \ 109 | --network influx_network \ 110 | --publish 8089:8086 \ 111 | ${INFLUXDB_V2_IMAGE} 112 | 113 | -------------------------------------------------------------------------------- /scripts/influxdb.conf: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | # 22 | 23 | 24 | [meta] 25 | dir = "/var/lib/influxdb/meta" 26 | 27 | [data] 28 | dir = "/var/lib/influxdb/data" 29 | engine = "tsm1" 30 | wal-dir = "/var/lib/influxdb/wal" 31 | 32 | 33 | [http] 34 | flux-enabled = true 35 | 36 | # These next lines control how batching works. You should have this enabled 37 | # otherwise you could get dropped metrics or poor performance. Batching 38 | # will buffer points in memory if you have many coming in. 39 | 40 | batch-size = 1000 # will flush if this many points get buffered 41 | batch-pending = 5 # number of batches that may be pending in memory 42 | batch-timeout = "1s" # will flush at least this often even if we haven't hit buffer limit 43 | read-buffer = 0 # UDP Read buffer size, 0 means OS default. UDP listener will fail if set above OS max. 44 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2021 InfluxData, Inc. All rights reserved. 2 | // Use of this source code is governed by MIT 3 | // license that can be found in the LICENSE file. 4 | 5 | package influxdb2 6 | 7 | import ( 8 | "fmt" 9 | "runtime" 10 | 11 | "github.com/influxdata/influxdb-client-go/v2/internal/http" 12 | ) 13 | 14 | const ( 15 | // Version defines current version 16 | Version = "2.14.0" 17 | ) 18 | 19 | func init() { 20 | http.UserAgentBase = fmt.Sprintf("influxdb-client-go/%s (%s; %s)", Version, runtime.GOOS, runtime.GOARCH) 21 | } 22 | --------------------------------------------------------------------------------