From 6ea321bc6327312eb9bcc7806b4a46082f84eea9 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Tue, 30 Apr 2024 19:51:30 -0500 Subject: [PATCH 01/30] initial commit --- .gitignore | 111 ++++++ .pre-commit-config.yaml | 15 + CONTRIBUTING.md | 0 LICENSE | 375 ++++++++++++++++++ README.md | 59 +++ bonsai/bonsai.go | 2 + bonsai/bonsai_test.go | 33 ++ bonsai/client.go | 345 ++++++++++++++++ bonsai/client_impl_test.go | 63 +++ bonsai/client_test.go | 118 ++++++ bonsai/internal/dep/dep.go | 8 + bonsai/io.go | 24 ++ bonsai/io_test.go | 1 + .../github.com/beorn7/perks/quantile/LICENSE | 20 + .../github.com/cespare/xxhash/v2/LICENSE.txt | 22 + .../hetznercloud/hcloud-go/v2/hcloud/LICENSE | 21 + .../client_golang/prometheus/LICENSE | 201 ++++++++++ .../client_golang/prometheus/NOTICE | 23 ++ .../prometheus/client_model/go/LICENSE | 201 ++++++++++ .../prometheus/client_model/go/NOTICE | 5 + .../github.com/prometheus/common/LICENSE | 201 ++++++++++ .../github.com/prometheus/common/NOTICE | 5 + .../bitbucket.org/ww/goautoneg/README.txt | 67 ++++ .../github.com/prometheus/procfs/LICENSE | 201 ++++++++++ .../github.com/prometheus/procfs/NOTICE | 7 + doc/3rd-party-deps/golang.org/x/net/LICENSE | 27 ++ .../golang.org/x/sys/unix/LICENSE | 27 ++ doc/3rd-party-deps/golang.org/x/text/LICENSE | 27 ++ .../google.golang.org/protobuf/LICENSE | 27 ++ go.mod | 25 ++ go.sum | 43 ++ 31 files changed, 2304 insertions(+) create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bonsai/bonsai.go create mode 100644 bonsai/bonsai_test.go create mode 100644 bonsai/client.go create mode 100644 bonsai/client_impl_test.go create mode 100644 bonsai/client_test.go create mode 100644 bonsai/internal/dep/dep.go create mode 100644 bonsai/io.go create mode 100644 bonsai/io_test.go create mode 100644 doc/3rd-party-deps/github.com/beorn7/perks/quantile/LICENSE create mode 100644 doc/3rd-party-deps/github.com/cespare/xxhash/v2/LICENSE.txt create mode 100644 doc/3rd-party-deps/github.com/hetznercloud/hcloud-go/v2/hcloud/LICENSE create mode 100644 doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/LICENSE create mode 100644 doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/NOTICE create mode 100644 doc/3rd-party-deps/github.com/prometheus/client_model/go/LICENSE create mode 100644 doc/3rd-party-deps/github.com/prometheus/client_model/go/NOTICE create mode 100644 doc/3rd-party-deps/github.com/prometheus/common/LICENSE create mode 100644 doc/3rd-party-deps/github.com/prometheus/common/NOTICE create mode 100644 doc/3rd-party-deps/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/README.txt create mode 100644 doc/3rd-party-deps/github.com/prometheus/procfs/LICENSE create mode 100644 doc/3rd-party-deps/github.com/prometheus/procfs/NOTICE create mode 100644 doc/3rd-party-deps/golang.org/x/net/LICENSE create mode 100644 doc/3rd-party-deps/golang.org/x/sys/unix/LICENSE create mode 100644 doc/3rd-party-deps/golang.org/x/text/LICENSE create mode 100644 doc/3rd-party-deps/google.golang.org/protobuf/LICENSE create mode 100644 go.mod create mode 100644 go.sum diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02748af --- /dev/null +++ b/.gitignore @@ -0,0 +1,111 @@ +# via https://github.com/github/gitignore +# License/attribution not required, CC0 + +### +# +# Go +# +### + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# doc/ + +# Go workspace file +go.work + +### +# +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 +# +### + +# We'll ignore the entire idea folder +.idea/ +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..7c1b5ca --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: + - repo: https://github.com/golangci/golangci-lint + rev: v1.57.2 + hooks: + - id: golangci-lint-full + - repo: local + hooks: + - id: go-licenses-save + name: go-licenses-save + description: Discover and save 3rd party dependency licenses + entry: go-licenses save ./... --save_path="doc/3rd-party-deps" --ignore github.com/omc --force + types: [go] + language: golang + require_serial: true + pass_filenames: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e69de29 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..67fdd16 --- /dev/null +++ b/LICENSE @@ -0,0 +1,375 @@ +Copyright (c) 2021 HashiCorp, Inc. + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2f5fb5 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# bonsai-go: Bonsai Cloud Go API Client + +## Installation + + +```shell +go get github.com// +``` + +## Example + +```go +package main + +import ( + "context" + "fmt" + "log" + + "github.com/omc/bonsai-api-go/v1/bonsai" +) + +func main() { + token, err := bonsai.NewToken("TestToken") + if err != nil { + log.Fatal(fmt.Errorf("invalid token: %w", err)) + } + + client := bonsai.NewClient( + bonsai.WithToken(token), + ) + + clusters, _, err := client.Clusters.All(context.Background()) + if err != nil { + log.Fatalf("error listing clusters: %s\n", err) + } + log.Printf("Found %d clusters!\n", len(clusters)) +} +``` + +## Contributing + +### Pre-commit + +This project uses [pre-commit](https://pre-commit.com/) to lint and store 3rd-party dependency licenses. +Installation instructions are available on the [pre-commit](https://pre-commit.com/) website! + +To verify your installation, run this project's pre-commit hooks against all files: + +```shell +pre-commit run --all-files +``` + +#### Go-licenses pre-commit hook + +Windows users will not be able to run the `go-licenses` hook as yet - @momer will be sending through a +PR to that project to resolve the issue, which is to do with OS-agnostic filepath support! + + diff --git a/bonsai/bonsai.go b/bonsai/bonsai.go new file mode 100644 index 0000000..8726c5c --- /dev/null +++ b/bonsai/bonsai.go @@ -0,0 +1,2 @@ +// Package bonsai wraps the Bonsai.io HTTP API to create a Go API Client. +package bonsai diff --git a/bonsai/bonsai_test.go b/bonsai/bonsai_test.go new file mode 100644 index 0000000..e6c35be --- /dev/null +++ b/bonsai/bonsai_test.go @@ -0,0 +1,33 @@ +package bonsai_test + +import ( + "fmt" + "log/slog" + "os" + + _ "github.com/stretchr/testify/require" +) + +func init() { + initLogger() +} + +func initLogger() { + // https://github.com/golang/go/issues/62403 + // https://cs.opensource.google/go/x/exp/+/master:slog/handler.go;l=442 + + logHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + AddSource: true, + ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + if a.Key == slog.SourceKey { + src := a.Value.Any().(*slog.Source) + // Ruby on Rails-ish formatting + a.Value = slog.StringValue(fmt.Sprintf("%s:%d:in '%s'", src.File, src.Line, src.Function)) + } + return a + }, + }) + + logger := slog.New(logHandler) + slog.SetDefault(logger) +} diff --git a/bonsai/client.go b/bonsai/client.go new file mode 100644 index 0000000..42a13c9 --- /dev/null +++ b/bonsai/client.go @@ -0,0 +1,345 @@ +package bonsai + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "reflect" + "strconv" + "strings" + "time" + + "golang.org/x/net/http/httpguts" + "golang.org/x/time/rate" +) + +const ( + // Version reflects this API Client's version + Version = "1.0.0" + // BaseEndpoint is the target API URL base location + BaseEndpoint = "https://api.bonsai.io" + // UserAgent is the internally used value for the User-Agent header + // in all outgoing HTTP requests + UserAgent = "bonsai-api-go/" + Version + + // DefaultClientBurstAllowance is the default Bonsai API request burst allowance + DefaultClientBurstAllowance = 60 + // DefaultClientBurstDuration is the default interval for a token bucket of size DefaultClientBurstAllowance to be refilled. + DefaultClientBurstDuration = 1 * time.Minute + // ProvisionClientBurstAllowance is the default Bonsai API request burst allowance + ProvisionClientBurstAllowance = 5 + // ProvisionClientBurstDuration is the default interval for a token bucket of size ProvisionClientBurstAllowance to be refilled. + ProvisionClientBurstDuration = 1 * time.Minute + + // HeaderRetryAfter holds the number of seconds to delay before making the next request + // ref: https://bonsai.io/docs/api-error-429-too-many-requests + HeaderRetryAfter = "Retry-After" +) + +// ResponseError captures API response errors +// returned as JSON in supported scenarios. +// +// ref: https://bonsai.io/docs/introduction-to-the-api +type ResponseError struct { + Errors []string `json:"errors"` + Status int `json:"status"` +} + +// Error represents ResponseError, which may have multiple Errors +// as a string. +// +// The community is as yet undecided on a great way to handle this +// ref: https://github.com/golang/go/issues/47811 +func (r ResponseError) Error() string { + return strings.Join(r.Errors, "; ") +} + +// listOpts specifies options for listing resources. +// ref: https://bonsai.io/docs/api-result-pagination +type listOpts struct { + Page int // Page number, starting at 1 + Size int // Size of each page, with a max of 100 +} + +// Values returns the listOpts as URL values. +func (l listOpts) values() url.Values { + vals := url.Values{} + if l.Page > 0 { + vals.Add("page", strconv.Itoa(l.Page)) + } + if l.Size > 0 { + vals.Add("size", strconv.Itoa(l.Size)) + } + return vals +} + +type Application struct { + Name string + Version string +} + +func (app Application) String() string { + switch { + case app.Name != "" && app.Version != "": + return app.Name + "/" + app.Version + case app.Name != "" && app.Version == "": + return app.Name + default: + return "" + } +} + +type Token struct { + string +} + +func (t Token) Empty() bool { + return t.string == "" +} + +func (t Token) NotEmpty() bool { + return !t.Empty() +} + +func NewToken(token string) (Token, error) { + t := Token{token} + if ok := t.validHttpValue(); !ok { + return Token{}, errors.New("invalid token") + } + return t, nil +} + +func (t Token) validHttpValue() bool { + return httpguts.ValidHeaderFieldValue(t.string) +} + +// ClientOption is a functional option, used to configure Client. +type ClientOption func(*Client) + +// WithEndpoint configures a Client to use the specified API endpoint. +func WithEndpoint(endpoint string) ClientOption { + return func(c *Client) { + c.endpoint = strings.TrimRight(endpoint, "/") + } + +} + +// WithToken configures a Client to use the specified token for authentication. +func WithToken(token Token) ClientOption { + return func(c *Client) { + c.token = token + } +} + +// WithApplication configures the client to represent itself as +// a particular Application by modifying the User-Agent header +// sent in all requests. +func WithApplication(app Application) ClientOption { + return func(c *Client) { + c.userAgent = app.String() + if c.userAgent == "" { + c.userAgent = UserAgent + } else { + c.userAgent += " " + UserAgent + } + } +} + +// WithDefaultRateLimit configures the default rate limit for client requests +func WithDefaultRateLimit(l *rate.Limiter) ClientOption { + return func(c *Client) { + c.rateLimiter.limiter = l + } +} + +// WithProvisionRateLimit configures the rate limit for client requests to the Provision API +func WithProvisionRateLimit(l *rate.Limiter) ClientOption { + return func(c *Client) { + c.rateLimiter.provisionLimiter = l + } +} + +type limiter = *rate.Limiter +type ClientLimiter struct { + // limiter is an embedded default rate limiter, but not exposed. + limiter + // provisionLimiter is the rate limiter to be used for Provision endpoints + provisionLimiter *rate.Limiter +} + +// Client is the exported client that users interact with. +type Client struct { + httpClient *http.Client + + rateLimiter *ClientLimiter + endpoint string + token Token + userAgent string +} + +func (c *Client) UserAgent() string { + return c.userAgent +} + +// NewRequest creates an HTTP request against the API. The returned request +// is assigned with ctx and has all necessary headers set (auth, user agent, etc.). +func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) { + reqUrl := c.endpoint + path + req, err := http.NewRequest(method, reqUrl, body) + if err != nil { + return nil, err + } + req.Header.Set("User-Agent", c.userAgent) + + if c.token.NotEmpty() { + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token)) + } + + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + + req = req.WithContext(ctx) + + return req, nil +} + +type PaginatedResponse struct { + PageNumber int `json:"page_number"` + PageSize int `json:"page_size"` + TotalRecords int `json:"total_records"` +} + +type Response struct { + *http.Response + + PaginatedResponse +} + +// Do performs an HTTP request against the API. +func (c *Client) Do(ctx context.Context, r *http.Request) (*Response, error) { + reqBuf := new(bytes.Buffer) + + // Capture the original request body + if r.ContentLength > 0 { + _, err := reqBuf.ReadFrom(r.Body) + if err != nil { + return nil, fmt.Errorf("error reading request body: %w", err) + } + + err = IoClose(r.Body, err) + if err != nil { + return nil, err + } + } + + // We only retry in the scenario of http.StatusTooManyRequests (429). + for { + respBuf := new(bytes.Buffer) + // Wrap the buffer in a no-op Closer, such that + // it satisfies the ReadCloser interface + if r.ContentLength > 0 { + r.Body = io.NopCloser(reqBuf) + } + + // Context cancelled, timed-out, burst issue, or other rate limit issue; + // let the callers handle it. + if err := c.rateLimiter.Wait(ctx); err != nil { + return nil, fmt.Errorf("failed while awaiting execution per rate-limit: %w", err) + } + + httpResp, err := c.httpClient.Do(r) + resp := &Response{Response: httpResp} + if err != nil { + return resp, err + } + + _, err = respBuf.ReadFrom(resp.Body) + if err != nil { + return resp, fmt.Errorf("error reading response body: %w", err) + } + + err = IoClose(resp.Body, err) + if err != nil { + return resp, err + } + + resp.Body = io.NopCloser(respBuf) + + if resp.StatusCode >= 400 { + respErr := ResponseError{} + if err = json.Unmarshal(respBuf.Bytes(), &respErr); err != nil { + return resp, fmt.Errorf("error unmarshalling error response: %w", err) + } + + if reflect.ValueOf(respErr).IsZero() { + return resp, fmt.Errorf("unknown error occurred with response status %d", resp.StatusCode) + } else if respErr.Status == http.StatusTooManyRequests { + // We're already blocking on this routine, so sleep inline per the header request. + if retryAfterStr := resp.Header.Get(HeaderRetryAfter); retryAfterStr != "" { + retryAfter, err := strconv.ParseInt(retryAfterStr, 10, 64) + if err != nil { + return resp, fmt.Errorf("error parsing retry-after response: %w", err) + } + time.Sleep(time.Duration(retryAfter) * time.Second) + } + + continue + } else { + return resp, respErr + } + } + + return resp, err + } +} + +func NewClient(options ...ClientOption) *Client { + client := &Client{ + endpoint: BaseEndpoint, + httpClient: &http.Client{}, + rateLimiter: &ClientLimiter{ + limiter: rate.NewLimiter(rate.Every(DefaultClientBurstDuration), DefaultClientBurstAllowance), + provisionLimiter: rate.NewLimiter(rate.Every(ProvisionClientBurstDuration), ProvisionClientBurstAllowance), + }, + } + + for _, option := range options { + option(client) + } + + return client +} + +// all loops through the next page pagination results until empty +// it allows the caller to pass a func (typically a closure) to collect +// results. +func (c *Client) all(ctx context.Context, f func(int) (*Response, error)) error { + var ( + page = 1 + ) + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + resp, err := f(page) + if err != nil { + return err + } + + // The caller is responsible for determining whether or not we've exhausted + // retries. + if reflect.ValueOf(resp.PaginatedResponse).IsZero() || resp.PageNumber == 0 { + return nil + } + // We should be fine with a straight increment, but let's play it safe + page = resp.PageNumber + 1 + } + } +} diff --git a/bonsai/client_impl_test.go b/bonsai/client_impl_test.go new file mode 100644 index 0000000..2e6be71 --- /dev/null +++ b/bonsai/client_impl_test.go @@ -0,0 +1,63 @@ +package bonsai + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "golang.org/x/time/rate" +) + +// ClientImplTestSuite is responsible for testing internal facing items/behavior +// that isn't part of the exposed interface, but is hard to test via that interface. +// Things like the default HTTP Client's rate-limiter (unexposed), and other implementation +// details fall under this umbrella - these test cases should be few. +type ClientImplTestSuite struct { + // Assertions embedded here allows all tests to reach through the suite to access assertion methods + *require.Assertions + // Suite is the testify/suite used for all HTTP request tests + suite.Suite +} + +func (s *ClientImplTestSuite) SetupSuite() { + // configure testify + s.Assertions = require.New(s.T()) +} + +func (s *ClientImplTestSuite) TestClientDefaultRateLimit() { + c := NewClient() + s.Equal(c.rateLimiter.Burst(), DefaultClientBurstAllowance) + s.Equal(c.rateLimiter.Limit(), rate.Every(DefaultClientBurstDuration)) +} + +func (s *ClientImplTestSuite) TestListOptsValues() { + testCases := []struct { + name string + received listOpts + expect string + }{ + { + name: "with populated values", + received: listOpts{ + Page: 3, + Size: 100, + }, + expect: "page=3&size=100", + }, + { + name: "with empty values", + received: listOpts{}, + expect: "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.Equal(tc.received.values().Encode(), tc.expect) + }) + } +} + +func TestClientImplTestSuite(t *testing.T) { + suite.Run(t, new(ClientImplTestSuite)) +} diff --git a/bonsai/client_test.go b/bonsai/client_test.go new file mode 100644 index 0000000..0b7bdc4 --- /dev/null +++ b/bonsai/client_test.go @@ -0,0 +1,118 @@ +package bonsai_test + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" + _ "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/omc/bonsai-api-go/v1/bonsai" +) + +type ClientTestSuite struct { + // Assertions embedded here allows all tests to reach through the suite to access assertion methods + *require.Assertions + // Suite is the testify/suite used for all HTTP request tests + suite.Suite + + // serveMux is the request multiplexer used for tests + serveMux *http.ServeMux + // server is the testing server on some local port + server *httptest.Server + // client allows each test to have a reachable *bonsai.Client for testing + client *bonsai.Client +} + +func (s *ClientTestSuite) SetupSuite() { + // Configure http client and other miscellany + s.serveMux = http.NewServeMux() + s.server = httptest.NewServer(s.serveMux) + token, err := bonsai.NewToken("TestToken") + if err != nil { + log.Fatal(fmt.Errorf("invalid token received: %w", err)) + } + s.client = bonsai.NewClient( + bonsai.WithEndpoint(s.server.URL), + bonsai.WithToken(token), + ) + + // configure testify + s.Assertions = require.New(s.T()) +} + +func (s *ClientTestSuite) TestResponseErrorUnmarshallJson() { + testCases := []struct { + name string + received string + expect bonsai.ResponseError + }{ + { + name: "error example from docs site", + received: "{\n \"errors\": [\n \"This request has failed authentication. Please read the docs or email us at support@bonsai.io.\"\n ],\n \"status\": 401\n}", + expect: bonsai.ResponseError{ + Errors: []string{ + "This request has failed authentication. Please read the docs or email us at support@bonsai.io.", + }, + Status: 401, + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + respErr := bonsai.ResponseError{} + err := json.Unmarshal([]byte(tc.received), &respErr) + s.Nil(err) + s.Equal(tc.expect, respErr) + }) + } +} + +func (s *ClientTestSuite) TestClient_WithApplication() { + testCases := []struct { + name string + received bonsai.Application + expect string + }{ + { + name: "both Application fields filled in", + received: bonsai.Application{ + Name: "withName", + Version: "withVersion", + }, + expect: fmt.Sprintf("%s/%s %s", "withName", "withVersion", bonsai.UserAgent), + }, + { + name: "application name non-empty; version empty", + received: bonsai.Application{ + Name: "withName", + Version: "", + }, + expect: fmt.Sprintf("%s %s", "withName", bonsai.UserAgent), + }, + { + name: "Application fields both empty", + received: bonsai.Application{}, + expect: bonsai.UserAgent, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + c := bonsai.NewClient( + bonsai.WithApplication(tc.received), + ) + s.Equal(tc.expect, c.UserAgent()) + }) + } +} + +func TestClientTestSuite(t *testing.T) { + suite.Run(t, new(ClientTestSuite)) +} diff --git a/bonsai/internal/dep/dep.go b/bonsai/internal/dep/dep.go new file mode 100644 index 0000000..b0af689 --- /dev/null +++ b/bonsai/internal/dep/dep.go @@ -0,0 +1,8 @@ +// Package dep imports unused, but licensed, dependencies +// for capture by tooling like "go mod vendor" and +// github.com/google/go-licenses +package dep + +import ( + _ "github.com/hetznercloud/hcloud-go/v2/hcloud" +) diff --git a/bonsai/io.go b/bonsai/io.go new file mode 100644 index 0000000..f2316e7 --- /dev/null +++ b/bonsai/io.go @@ -0,0 +1,24 @@ +package bonsai + +import ( + "errors" + "fmt" + "io" +) + +// IoClose will catch io.Closer errors and wrap them around the +// previous errors, if any. +// +// Note: due to the Go spec, in order to actually modify the parent's +// error value, the calling function *must* use named result parameters. +// +// ref: https://go.dev/ref/spec#Defer_statements +func IoClose(c io.Closer, err error) error { + cerr := c.Close() + + // Returns nil if all are nil + return errors.Join( + fmt.Errorf("failed to close io.Closer: %w", cerr), + err, + ) +} diff --git a/bonsai/io_test.go b/bonsai/io_test.go new file mode 100644 index 0000000..e6d0436 --- /dev/null +++ b/bonsai/io_test.go @@ -0,0 +1 @@ +package bonsai_test diff --git a/doc/3rd-party-deps/github.com/beorn7/perks/quantile/LICENSE b/doc/3rd-party-deps/github.com/beorn7/perks/quantile/LICENSE new file mode 100644 index 0000000..f1f6712 --- /dev/null +++ b/doc/3rd-party-deps/github.com/beorn7/perks/quantile/LICENSE @@ -0,0 +1,20 @@ +Copyright (C) 2013 Blake Mizerany + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/doc/3rd-party-deps/github.com/cespare/xxhash/v2/LICENSE.txt b/doc/3rd-party-deps/github.com/cespare/xxhash/v2/LICENSE.txt new file mode 100644 index 0000000..056aaef --- /dev/null +++ b/doc/3rd-party-deps/github.com/cespare/xxhash/v2/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2016 Caleb Spare + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/doc/3rd-party-deps/github.com/hetznercloud/hcloud-go/v2/hcloud/LICENSE b/doc/3rd-party-deps/github.com/hetznercloud/hcloud-go/v2/hcloud/LICENSE new file mode 100644 index 0000000..6bc5ff0 --- /dev/null +++ b/doc/3rd-party-deps/github.com/hetznercloud/hcloud-go/v2/hcloud/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018-2020 Hetzner Cloud GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/LICENSE b/doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/LICENSE new file mode 100644 index 0000000..29f81d8 --- /dev/null +++ b/doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/NOTICE b/doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/NOTICE new file mode 100644 index 0000000..63afd35 --- /dev/null +++ b/doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/NOTICE @@ -0,0 +1,23 @@ +Prometheus instrumentation library for Go applications +Copyright 2012-2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). + + +The following components are included in this product: + +perks - a fork of https://github.com/bmizerany/perks +https://github.com/beorn7/perks +Copyright 2013-2015 Blake Mizerany, Björn Rabenstein +See https://github.com/beorn7/perks/blob/master/README.md for license details. + +Go support for Protocol Buffers - Google's data interchange format +http://github.com/golang/protobuf/ +Copyright 2010 The Go Authors +See source code for license details. + +Support for streaming Protocol Buffer messages for the Go language (golang). +https://github.com/matttproud/golang_protobuf_extensions +Copyright 2013 Matt T. Proud +Licensed under the Apache License, Version 2.0 diff --git a/doc/3rd-party-deps/github.com/prometheus/client_model/go/LICENSE b/doc/3rd-party-deps/github.com/prometheus/client_model/go/LICENSE new file mode 100644 index 0000000..29f81d8 --- /dev/null +++ b/doc/3rd-party-deps/github.com/prometheus/client_model/go/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/doc/3rd-party-deps/github.com/prometheus/client_model/go/NOTICE b/doc/3rd-party-deps/github.com/prometheus/client_model/go/NOTICE new file mode 100644 index 0000000..adcd01c --- /dev/null +++ b/doc/3rd-party-deps/github.com/prometheus/client_model/go/NOTICE @@ -0,0 +1,5 @@ +Data model artifacts for Prometheus. +Copyright 2012-2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). diff --git a/doc/3rd-party-deps/github.com/prometheus/common/LICENSE b/doc/3rd-party-deps/github.com/prometheus/common/LICENSE new file mode 100644 index 0000000..29f81d8 --- /dev/null +++ b/doc/3rd-party-deps/github.com/prometheus/common/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/doc/3rd-party-deps/github.com/prometheus/common/NOTICE b/doc/3rd-party-deps/github.com/prometheus/common/NOTICE new file mode 100644 index 0000000..71dba95 --- /dev/null +++ b/doc/3rd-party-deps/github.com/prometheus/common/NOTICE @@ -0,0 +1,5 @@ +Common libraries shared by Prometheus Go components. +Copyright 2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). diff --git a/doc/3rd-party-deps/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/README.txt b/doc/3rd-party-deps/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/README.txt new file mode 100644 index 0000000..89715da --- /dev/null +++ b/doc/3rd-party-deps/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/README.txt @@ -0,0 +1,67 @@ +PACKAGE + +package goautoneg +import "bitbucket.org/ww/goautoneg" + +HTTP Content-Type Autonegotiation. + +The functions in this package implement the behaviour specified in +http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + +Copyright (c) 2011, Open Knowledge Foundation Ltd. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + Neither the name of the Open Knowledge Foundation Ltd. nor the + names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +FUNCTIONS + +func Negotiate(header string, alternatives []string) (content_type string) +Negotiate the most appropriate content_type given the accept header +and a list of alternatives. + +func ParseAccept(header string) (accept []Accept) +Parse an Accept Header string returning a sorted list +of clauses + + +TYPES + +type Accept struct { + Type, SubType string + Q float32 + Params map[string]string +} +Structure to represent a clause in an HTTP Accept Header + + +SUBDIRECTORIES + + .hg diff --git a/doc/3rd-party-deps/github.com/prometheus/procfs/LICENSE b/doc/3rd-party-deps/github.com/prometheus/procfs/LICENSE new file mode 100644 index 0000000..29f81d8 --- /dev/null +++ b/doc/3rd-party-deps/github.com/prometheus/procfs/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/doc/3rd-party-deps/github.com/prometheus/procfs/NOTICE b/doc/3rd-party-deps/github.com/prometheus/procfs/NOTICE new file mode 100644 index 0000000..5ea05e6 --- /dev/null +++ b/doc/3rd-party-deps/github.com/prometheus/procfs/NOTICE @@ -0,0 +1,7 @@ +procfs provides functions to retrieve system, kernel and process +metrics from the pseudo-filesystem proc. + +Copyright 2014-2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). diff --git a/doc/3rd-party-deps/golang.org/x/net/LICENSE b/doc/3rd-party-deps/golang.org/x/net/LICENSE new file mode 100644 index 0000000..d71c2f1 --- /dev/null +++ b/doc/3rd-party-deps/golang.org/x/net/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/3rd-party-deps/golang.org/x/sys/unix/LICENSE b/doc/3rd-party-deps/golang.org/x/sys/unix/LICENSE new file mode 100644 index 0000000..d71c2f1 --- /dev/null +++ b/doc/3rd-party-deps/golang.org/x/sys/unix/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/3rd-party-deps/golang.org/x/text/LICENSE b/doc/3rd-party-deps/golang.org/x/text/LICENSE new file mode 100644 index 0000000..d71c2f1 --- /dev/null +++ b/doc/3rd-party-deps/golang.org/x/text/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/3rd-party-deps/google.golang.org/protobuf/LICENSE b/doc/3rd-party-deps/google.golang.org/protobuf/LICENSE new file mode 100644 index 0000000..ce0d8d3 --- /dev/null +++ b/doc/3rd-party-deps/google.golang.org/protobuf/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2018 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bae3410 --- /dev/null +++ b/go.mod @@ -0,0 +1,25 @@ +module github.com/omc/bonsai-api-go/v1 + +go 1.22 + +require ( + github.com/hetznercloud/hcloud-go/v2 v2.7.2 + github.com/stretchr/testify v1.9.0 + golang.org/x/net v0.24.0 + golang.org/x/time v0.5.0 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.19.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.48.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..35f771d --- /dev/null +++ b/go.sum @@ -0,0 +1,43 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hetznercloud/hcloud-go/v2 v2.7.2 h1:UlE7n1GQZacCfyjv9tDVUN7HZfOXErPIfM/M039u9A0= +github.com/hetznercloud/hcloud-go/v2 v2.7.2/go.mod h1:49tIV+pXRJTUC7fbFZ03s45LKqSQdOPP5y91eOnJo/k= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= +github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 9ff94754f1b2edabd5e2d76ad8419aad59785154 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Tue, 30 Apr 2024 19:58:23 -0500 Subject: [PATCH 02/30] Update README installation instructions --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d2f5fb5..5c4d07e 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,8 @@ ## Installation - ```shell -go get github.com// +go get github.com/omc/bonsai-api-go/v1 ``` ## Example From a4f04716be5b1da34253c228fc89832e53743102 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Wed, 1 May 2024 10:10:26 -0500 Subject: [PATCH 03/30] Housekeeping; organize Client methods --- bonsai/client.go | 58 ++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/bonsai/client.go b/bonsai/client.go index 42a13c9..6f3d17d 100644 --- a/bonsai/client.go +++ b/bonsai/client.go @@ -164,6 +164,18 @@ func WithProvisionRateLimit(l *rate.Limiter) ClientOption { } } +type PaginatedResponse struct { + PageNumber int `json:"page_number"` + PageSize int `json:"page_size"` + TotalRecords int `json:"total_records"` +} + +type Response struct { + *http.Response + + PaginatedResponse +} + type limiter = *rate.Limiter type ClientLimiter struct { // limiter is an embedded default rate limiter, but not exposed. @@ -182,6 +194,23 @@ type Client struct { userAgent string } +func NewClient(options ...ClientOption) *Client { + client := &Client{ + endpoint: BaseEndpoint, + httpClient: &http.Client{}, + rateLimiter: &ClientLimiter{ + limiter: rate.NewLimiter(rate.Every(DefaultClientBurstDuration), DefaultClientBurstAllowance), + provisionLimiter: rate.NewLimiter(rate.Every(ProvisionClientBurstDuration), ProvisionClientBurstAllowance), + }, + } + + for _, option := range options { + option(client) + } + + return client +} + func (c *Client) UserAgent() string { return c.userAgent } @@ -209,18 +238,6 @@ func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Re return req, nil } -type PaginatedResponse struct { - PageNumber int `json:"page_number"` - PageSize int `json:"page_size"` - TotalRecords int `json:"total_records"` -} - -type Response struct { - *http.Response - - PaginatedResponse -} - // Do performs an HTTP request against the API. func (c *Client) Do(ctx context.Context, r *http.Request) (*Response, error) { reqBuf := new(bytes.Buffer) @@ -299,23 +316,6 @@ func (c *Client) Do(ctx context.Context, r *http.Request) (*Response, error) { } } -func NewClient(options ...ClientOption) *Client { - client := &Client{ - endpoint: BaseEndpoint, - httpClient: &http.Client{}, - rateLimiter: &ClientLimiter{ - limiter: rate.NewLimiter(rate.Every(DefaultClientBurstDuration), DefaultClientBurstAllowance), - provisionLimiter: rate.NewLimiter(rate.Every(ProvisionClientBurstDuration), ProvisionClientBurstAllowance), - }, - } - - for _, option := range options { - option(client) - } - - return client -} - // all loops through the next page pagination results until empty // it allows the caller to pass a func (typically a closure) to collect // results. From e888ddb87ceb87a104b02b2dfaeae542b4519804 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Wed, 1 May 2024 11:28:27 -0500 Subject: [PATCH 04/30] Adding testing for Client funcs, some Client refactoring; resolve brainfart bug in IoClose --- bonsai/client.go | 106 ++++++++++++++++++++++++++++++------- bonsai/client_impl_test.go | 90 +++++++++++++++++++++++++++++++ bonsai/client_test.go | 74 ++++++++++++++++++++++++++ bonsai/error.go | 1 + bonsai/io.go | 12 +++-- 5 files changed, 259 insertions(+), 24 deletions(-) create mode 100644 bonsai/error.go diff --git a/bonsai/client.go b/bonsai/client.go index 6f3d17d..744a7ca 100644 --- a/bonsai/client.go +++ b/bonsai/client.go @@ -18,6 +18,7 @@ import ( "golang.org/x/time/rate" ) +// Client representation configuration const ( // Version reflects this API Client's version Version = "1.0.0" @@ -26,7 +27,10 @@ const ( // UserAgent is the internally used value for the User-Agent header // in all outgoing HTTP requests UserAgent = "bonsai-api-go/" + Version +) +// Client rate limiter configuration +const ( // DefaultClientBurstAllowance is the default Bonsai API request burst allowance DefaultClientBurstAllowance = 60 // DefaultClientBurstDuration is the default interval for a token bucket of size DefaultClientBurstAllowance to be refilled. @@ -35,12 +39,26 @@ const ( ProvisionClientBurstAllowance = 5 // ProvisionClientBurstDuration is the default interval for a token bucket of size ProvisionClientBurstAllowance to be refilled. ProvisionClientBurstDuration = 1 * time.Minute +) +// Common API Response headers +const ( // HeaderRetryAfter holds the number of seconds to delay before making the next request // ref: https://bonsai.io/docs/api-error-429-too-many-requests HeaderRetryAfter = "Retry-After" ) +// HTTP Content Types and related Header +const ( + HTTPHeaderContentType = "Content-Type" + HTTPContentTypeJSON string = "application/json" +) + +// HTTP Status Response Errors +var ( + ErrorHTTPStatusNotFound = errors.New("not found") +) + // ResponseError captures API response errors // returned as JSON in supported scenarios. // @@ -56,7 +74,15 @@ type ResponseError struct { // The community is as yet undecided on a great way to handle this // ref: https://github.com/golang/go/issues/47811 func (r ResponseError) Error() string { - return strings.Join(r.Errors, "; ") + return fmt.Sprintf("%v (%d)", r.Errors, r.Status) +} + +func (r ResponseError) Is(target error) bool { + switch r.Status { + case http.StatusNotFound: + return target == ErrorHTTPStatusNotFound + } + return false } // listOpts specifies options for listing resources. @@ -170,10 +196,55 @@ type PaginatedResponse struct { TotalRecords int `json:"total_records"` } +type httpResponse = *http.Response type Response struct { - *http.Response + httpResponse - PaginatedResponse + Body io.ReadCloser + PaginatedResponse `json:"pagination"` +} + +func (r *Response) WithHTTPResponse(httpResp *http.Response) error { + var err error + bodyBuf := new(bytes.Buffer) + r.httpResponse = httpResp + + if httpResp == nil { + return errors.New("received nil http.Response") + } + + _, err = bodyBuf.ReadFrom(httpResp.Body) + if err != nil { + return fmt.Errorf("error reading response body: %w", err) + } + + err = IoClose(httpResp.Body, err) + if err != nil { + return err + } + + r.Body = io.NopCloser(bodyBuf) + + switch httpResp.Header.Get("Content-Type") { + case HTTPContentTypeJSON: + err = json.Unmarshal(bodyBuf.Bytes(), r) + } + if err != nil { + return fmt.Errorf("error unmarshaling response body: %w", err) + } + + return err +} + +func (r *Response) MarkPaginationComplete() { + r.PaginatedResponse = PaginatedResponse{} +} + +// NewResponse reserves this function signature, and is +// the recommended way to instantiate a Response, as its behavior +// may change. +func NewResponse() (*Response, error) { + return &Response{}, nil } type limiter = *rate.Limiter @@ -239,17 +310,17 @@ func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Re } // Do performs an HTTP request against the API. -func (c *Client) Do(ctx context.Context, r *http.Request) (*Response, error) { +func (c *Client) Do(ctx context.Context, req *http.Request) (*Response, error) { reqBuf := new(bytes.Buffer) // Capture the original request body - if r.ContentLength > 0 { - _, err := reqBuf.ReadFrom(r.Body) + if req.ContentLength > 0 { + _, err := reqBuf.ReadFrom(req.Body) if err != nil { return nil, fmt.Errorf("error reading request body: %w", err) } - err = IoClose(r.Body, err) + err = IoClose(req.Body, err) if err != nil { return nil, err } @@ -260,8 +331,8 @@ func (c *Client) Do(ctx context.Context, r *http.Request) (*Response, error) { respBuf := new(bytes.Buffer) // Wrap the buffer in a no-op Closer, such that // it satisfies the ReadCloser interface - if r.ContentLength > 0 { - r.Body = io.NopCloser(reqBuf) + if req.ContentLength > 0 { + req.Body = io.NopCloser(reqBuf) } // Context cancelled, timed-out, burst issue, or other rate limit issue; @@ -270,24 +341,21 @@ func (c *Client) Do(ctx context.Context, r *http.Request) (*Response, error) { return nil, fmt.Errorf("failed while awaiting execution per rate-limit: %w", err) } - httpResp, err := c.httpClient.Do(r) - resp := &Response{Response: httpResp} + httpResp, err := c.httpClient.Do(req) if err != nil { - return resp, err + return nil, fmt.Errorf("http request failed: %w", err) } - _, err = respBuf.ReadFrom(resp.Body) + resp, err := NewResponse() if err != nil { - return resp, fmt.Errorf("error reading response body: %w", err) + return resp, fmt.Errorf("creating new Response") } - err = IoClose(resp.Body, err) + err = resp.WithHTTPResponse(httpResp) if err != nil { - return resp, err + return resp, fmt.Errorf("setting http response: %w", err) } - resp.Body = io.NopCloser(respBuf) - if resp.StatusCode >= 400 { respErr := ResponseError{} if err = json.Unmarshal(respBuf.Bytes(), &respErr); err != nil { @@ -335,7 +403,7 @@ func (c *Client) all(ctx context.Context, f func(int) (*Response, error)) error // The caller is responsible for determining whether or not we've exhausted // retries. - if reflect.ValueOf(resp.PaginatedResponse).IsZero() || resp.PageNumber == 0 { + if reflect.ValueOf(resp.PaginatedResponse).IsZero() || resp.PageNumber <= 0 { return nil } // We should be fine with a straight increment, but let's play it safe diff --git a/bonsai/client_impl_test.go b/bonsai/client_impl_test.go index 2e6be71..b070494 100644 --- a/bonsai/client_impl_test.go +++ b/bonsai/client_impl_test.go @@ -1,6 +1,12 @@ package bonsai import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "net/http/httptest" "testing" "github.com/stretchr/testify/require" @@ -17,9 +23,28 @@ type ClientImplTestSuite struct { *require.Assertions // Suite is the testify/suite used for all HTTP request tests suite.Suite + + // serveMux is the request multiplexer used for tests + serveMux *http.ServeMux + // server is the testing server on some local port + server *httptest.Server + // client allows each test to have a reachable *Client for testing + client *Client } func (s *ClientImplTestSuite) SetupSuite() { + // Configure http client and other miscellany + s.serveMux = http.NewServeMux() + s.server = httptest.NewServer(s.serveMux) + token, err := NewToken("TestToken") + if err != nil { + log.Fatal(fmt.Errorf("invalid token received: %w", err)) + } + s.client = NewClient( + WithEndpoint(s.server.URL), + WithToken(token), + ) + // configure testify s.Assertions = require.New(s.T()) } @@ -58,6 +83,71 @@ func (s *ClientImplTestSuite) TestListOptsValues() { } } +func (s *ClientImplTestSuite) TestClientAll() { + const expectedPageCount = 4 + var ( + ctx = context.Background() + expectedPage = 1 + ) + + s.serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(HTTPHeaderContentType, HTTPContentTypeJSON) + + respBody, _ := NewResponse() + respBody.PaginatedResponse = PaginatedResponse{ + PageNumber: 3, + PageSize: 1, + TotalRecords: 3, + } + + switch page := r.URL.Query().Get("page"); page { + case "", "1": + respBody.PaginatedResponse.PageNumber = 1 + case "2": + respBody.PaginatedResponse.PageNumber = 2 + case "3": + respBody.PaginatedResponse.PageNumber = 3 + default: + s.FailNowf("invalid page parameter", "page parameter: %v", page) + } + + err := json.NewEncoder(w).Encode(respBody) + s.Nil(err, "encode response body") + }) + + // The caller must track results against expected count + // A reminder to the reader: this is the caller. + var resultCount = 0 + err := s.client.all(context.Background(), func(page int) (*Response, error) { + s.Equalf(expectedPage, page, "expected page number (%d) matches actual (%d)", expectedPage, page) + + path := fmt.Sprintf("/?page=%d&size=1", page) + + req, err := s.client.NewRequest(ctx, "GET", path, nil) + s.Nil(err, "new request for path") + + resp, err := s.client.Do(context.Background(), req) + s.Nil(err, "do request") + + expectedPage++ + // A reference of how these funcs should handle this; + // recall, the response may be shorter than max. + // + // Ideally, this count wouldn't be derived from PageSize, + // but rather, from the total count of discovered items + // unmarshaled. + resultCount += max(resp.PageSize, 0) + + if resultCount >= resp.TotalRecords { + resp.MarkPaginationComplete() + } + return resp, err + }) + s.Nil(err, "client.all call") + + s.Equalf(expectedPage, expectedPageCount, "expected page visit count (%d) matches actual visit count (%d)", expectedPageCount-1, expectedPage-1) +} + func TestClientImplTestSuite(t *testing.T) { suite.Run(t, new(ClientImplTestSuite)) } diff --git a/bonsai/client_test.go b/bonsai/client_test.go index 0b7bdc4..972efa3 100644 --- a/bonsai/client_test.go +++ b/bonsai/client_test.go @@ -1,7 +1,9 @@ package bonsai_test import ( + "context" "encoding/json" + "errors" "fmt" "log" "net/http" @@ -15,6 +17,18 @@ import ( "github.com/omc/bonsai-api-go/v1/bonsai" ) +const ( + ResponseErrorHttpStatusNotFound = `{ + "errors": [ + "Cluster doesnotexist-1234 not found.", + "Please review the documentation available at https://docs.bonsai.io", + "Undefined request." + ], + "status": 404 + } + ` +) + type ClientTestSuite struct { // Assertions embedded here allows all tests to reach through the suite to access assertion methods *require.Assertions @@ -74,6 +88,66 @@ func (s *ClientTestSuite) TestResponseErrorUnmarshallJson() { } } +func (s *ClientTestSuite) TestClientResponseError() { + const p = "/clusters/doesnotexist-1234" + + // Configure Servemux to serve the error response at this path + s.serveMux.HandleFunc(p, func(w http.ResponseWriter, r *http.Request) { + var err error + + w.Header().Set("Content-Type", bonsai.HTTPContentTypeJSON) + w.WriteHeader(http.StatusNotFound) + + respErr := &bonsai.ResponseError{} + err = json.Unmarshal([]byte(ResponseErrorHttpStatusNotFound), respErr) + s.Nil(err, "successfully unmarshals json into bonsaiResponseError") + + err = json.NewEncoder(w).Encode(respErr) + s.Nil(err, "encodes http response into ResponseError") + }) + + req, err := s.client.NewRequest(context.Background(), "GET", p, nil) + s.Nil(err, "request creation returns no error") + + resp, err := s.client.Do(context.Background(), req) + s.NotNil(err, "Client.Do returns an error") + + s.Equal(resp.StatusCode, http.StatusNotFound) + s.True(errors.As(err, &bonsai.ResponseError{}), "Client.Do error response type is of ResponseError") + s.True(errors.Is(err, bonsai.ErrorHTTPStatusNotFound), "ResponseError is comparable to bonsai.ErrorHttpResponseStatus") +} + +func (s *ClientTestSuite) TestClientResponseWithPagination() { + s.serveMux.HandleFunc("/clusters", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("RateLimit-Limit", "1000") + w.Header().Set("RateLimit-Remaining", "999") + w.Header().Set("RateLimit-Reset", "1511954577") + w.WriteHeader(http.StatusOK) + _, err := fmt.Fprint(w, ` + { + "foo": "bar", + "pagination": { + "page_number": 1, + "page_size": 20, + "total_records": 255 + } + } + `) + s.Nil(err, "writes json response into response writer") + }) + + req, err := s.client.NewRequest(context.Background(), "GET", "/clusters", nil) + s.Nil(err, "request creation returns no error") + + resp, err := s.client.Do(context.Background(), req) + s.Nil(err, "Client.Do succeeds") + + s.Equal(resp.PaginatedResponse.PageNumber, 1) + s.Equal(resp.PaginatedResponse.PageSize, 20) + s.Equal(resp.PaginatedResponse.TotalRecords, 255) +} + func (s *ClientTestSuite) TestClient_WithApplication() { testCases := []struct { name string diff --git a/bonsai/error.go b/bonsai/error.go new file mode 100644 index 0000000..48de1b7 --- /dev/null +++ b/bonsai/error.go @@ -0,0 +1 @@ +package bonsai diff --git a/bonsai/io.go b/bonsai/io.go index f2316e7..196ae00 100644 --- a/bonsai/io.go +++ b/bonsai/io.go @@ -16,9 +16,11 @@ import ( func IoClose(c io.Closer, err error) error { cerr := c.Close() - // Returns nil if all are nil - return errors.Join( - fmt.Errorf("failed to close io.Closer: %w", cerr), - err, - ) + if cerr != nil { + return errors.Join( + fmt.Errorf("failed to close io.Closer: %w", cerr), + err, + ) + } + return err } From 3dedf76bb4f8ed69ecf7b9a9074be94329ebc45a Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Wed, 1 May 2024 19:33:06 -0500 Subject: [PATCH 05/30] Refactoring due to aggressive linting; add golanglint-ci github action --- .github/workflows/golangci-lint.yml | 41 ++ .golangci.yml | 330 ++++++++++++++ README.md | 6 +- bonsai/bonsai.go | 4 + bonsai/bonsai_test.go | 10 +- bonsai/client.go | 215 ++++++---- bonsai/client_impl_test.go | 22 +- bonsai/client_test.go | 54 +-- bonsai/internal/dep/dep.go | 17 +- .../github.com/beorn7/perks/quantile/LICENSE | 40 +- .../github.com/cespare/xxhash/v2/LICENSE.txt | 44 +- .../hetznercloud/hcloud-go/v2/hcloud/LICENSE | 42 +- .../client_golang/prometheus/LICENSE | 402 +++++++++--------- .../client_golang/prometheus/NOTICE | 46 +- .../prometheus/client_model/go/LICENSE | 402 +++++++++--------- .../prometheus/client_model/go/NOTICE | 10 +- .../github.com/prometheus/common/LICENSE | 402 +++++++++--------- .../github.com/prometheus/common/NOTICE | 10 +- .../bitbucket.org/ww/goautoneg/README.txt | 134 +++--- .../github.com/prometheus/procfs/LICENSE | 201 --------- .../github.com/prometheus/procfs/NOTICE | 7 - doc/3rd-party-deps/golang.org/x/net/LICENSE | 54 +-- .../x/sys/{unix => windows}/LICENSE | 54 +-- doc/3rd-party-deps/golang.org/x/text/LICENSE | 54 +-- .../golang.org/x/time/rate/LICENSE | 27 ++ .../google.golang.org/protobuf/LICENSE | 54 +-- 26 files changed, 1461 insertions(+), 1221 deletions(-) create mode 100644 .github/workflows/golangci-lint.yml create mode 100644 .golangci.yml delete mode 100644 doc/3rd-party-deps/github.com/prometheus/procfs/LICENSE delete mode 100644 doc/3rd-party-deps/github.com/prometheus/procfs/NOTICE rename doc/3rd-party-deps/golang.org/x/sys/{unix => windows}/LICENSE (98%) create mode 100644 doc/3rd-party-deps/golang.org/x/time/rate/LICENSE diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..9cf6e2f --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,41 @@ +name: golangci-lint +on: + push: + branches: + - '**' # Run on all branches + pull_request: + +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + # pull-requests: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + check-latest: true + go-version: '1.22' + - name: golangci-lint + uses: golangci/golangci-lint-action@v5 + with: + version: v1.57 + skip-cache: true + args: --timeout=5m + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + check-latest: true + go-version: '1.22' + - name: Test all + run: go test ./... \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..bd9cb1d --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,330 @@ +# This code is licensed under the terms of the MIT license https://opensource.org/license/mit +# Copyright (c) 2021 Marat Reymers + +## Golden config for golangci-lint v1.57.2 +# +# This is the best config for golangci-lint based on my experience and opinion. +# It is very strict, but not extremely strict. +# Feel free to adapt and change it for your needs. + +run: + # Timeout for analysis, e.g. 30s, 5m. + # Default: 1m + timeout: 3m + + +# This file contains only configs which differ from defaults. +# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml +linters-settings: + cyclop: + # The maximal code complexity to report. + # Default: 10 + max-complexity: 40 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled + # Default: 0.0 + package-average: 10.0 + + errcheck: + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + # Such cases aren't reported by default. + # Default: false + check-type-assertions: true + + exhaustive: + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map + + exhaustruct: + # List of regular expressions to exclude struct packages and their names from checks. + # Regular expressions must match complete canonical struct package/name/structname. + # Default: [] + exclude: + # std libs + - "^net/http.Client$" + - "^net/http.Cookie$" + - "^net/http.Request$" + - "^net/http.Response$" + - "^net/http.Server$" + - "^net/http.Transport$" + - "^net/url.URL$" + - "^os/exec.Cmd$" + - "^reflect.StructField$" + # public libs + - "^github.com/Shopify/sarama.Config$" + - "^github.com/Shopify/sarama.ProducerMessage$" + - "^github.com/mitchellh/mapstructure.DecoderConfig$" + - "^github.com/prometheus/client_golang/.+Opts$" + - "^github.com/spf13/cobra.Command$" + - "^github.com/spf13/cobra.CompletionOptions$" + - "^github.com/stretchr/testify/mock.Mock$" + - "^github.com/testcontainers/testcontainers-go.+Request$" + - "^github.com/testcontainers/testcontainers-go.FromDockerfile$" + - "^golang.org/x/tools/go/analysis.Analyzer$" + - "^google.golang.org/protobuf/.+Options$" + - "^gopkg.in/yaml.v3.Node$" + + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + lines: 100 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 50 + # Ignore comments when counting lines. + # Default false + ignore-comments: true + + gocognit: + # Minimal code complexity to report. + # Default: 30 (but we recommend 10-20) + min-complexity: 40 + + gocritic: + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be find in https://go-critic.github.io/overview. + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + + gomnd: + # List of function patterns to exclude from analysis. + # Values always ignored: `time.Date`, + # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`, + # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`. + # Default: [] + ignored-functions: + - flag.Arg + - flag.Duration.* + - flag.Float.* + - flag.Int.* + - flag.Uint.* + - os.Chmod + - os.Mkdir.* + - os.OpenFile + - os.WriteFile + - prometheus.ExponentialBuckets.* + - prometheus.LinearBuckets + + gomodguard: + blocked: + # List of blocked modules. + # Default: [] + modules: + - github.com/golang/protobuf: + recommendations: + - google.golang.org/protobuf + reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules" + - github.com/satori/go.uuid: + recommendations: + - github.com/google/uuid + reason: "satori's package is not maintained" + - github.com/gofrs/uuid: + recommendations: + - github.com/gofrs/uuid/v5 + reason: "gofrs' package was not go module before v5" + + govet: + # Enable all analyzers. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `go tool vet help` to see all analyzers. + # Default: [] + disable: + - fieldalignment # too strict + # Settings per analyzer. + settings: + shadow: + # Whether to be strict about shadowing; can be noisy. + # Default: false + strict: true + + inamedparam: + # Skips check for interface methods with only a single parameter. + # Default: false + skip-single-param: true + + nakedret: + # Make an issue if func has more lines of code than this setting, and it has naked returns. + # Default: 30 + max-func-lines: 0 + + nolintlint: + # Exclude following linters from requiring an explanation. + # Default: [] + allow-no-explanation: [ funlen, gocognit, lll ] + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. + # Default: false + require-specific: true + + perfsprint: + # Optimizes into strings concatenation. + # Default: true + strconcat: false + + rowserrcheck: + # database/sql is always checked + # Default: [] + packages: + - github.com/jmoiron/sqlx + + tenv: + # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. + # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. + # Default: false + all: true + + +linters: + disable-all: true + enable: + ## enabled by default + - errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases + - gosimple # specializes in simplifying a code + - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # detects when assignments to existing variables are not used + - staticcheck # is a go vet on steroids, applying a ton of static analysis checks + - typecheck # like the front-end of a Go compiler, parses and type-checks Go code + - unused # checks for unused constants, variables, functions and types + ## disabled by default + - asasalint # checks for pass []any as any in variadic func(...any) + - asciicheck # checks that your code does not contain non-ASCII identifiers + - bidichk # checks for dangerous unicode character sequences + - bodyclose # checks whether HTTP response body is closed successfully + - copyloopvar # detects places where loop variables are copied + - cyclop # checks function and package cyclomatic complexity + - dupl # tool for code clone detection + - durationcheck # checks for two durations multiplied together + - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13 + - execinquery # checks query string in Query function which reads your Go src files and warning it finds + - exhaustive # checks exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - forbidigo # forbids identifiers + - funlen # tool for detection of long functions + - gocheckcompilerdirectives # validates go compiler directive comments (//go:) + - gochecknoglobals # checks that no global variables exist +# - gochecknoinits # checks that no init functions are present in Go code + - gochecksumtype # checks exhaustiveness on Go "sum types" + - gocognit # computes and checks the cognitive complexity of functions + - goconst # finds repeated strings that could be replaced by a constant + - gocritic # provides diagnostics that check for bugs, performance and style issues + - gocyclo # computes and checks the cyclomatic complexity of functions + - godot # checks if comments end in a period + - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt + - gomnd # detects magic numbers + - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod + - gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations + - goprintffuncname # checks that printf-like functions are named with f at the end + - gosec # inspects source code for security problems + - intrange # finds places where for loops could make use of an integer range + - lll # reports long lines + - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) + - makezero # finds slice declarations with non-zero initial length + - mirror # reports wrong mirror patterns of bytes/strings usage + - musttag # enforces field tags in (un)marshaled structs + - nakedret # finds naked returns in functions greater than a specified function length + - nestif # reports deeply nested if statements + - nilerr # finds the code that returns nil even if it checks that the error is not nil + - nilnil # checks that there is no simultaneous return of nil error and an invalid value + - noctx # finds sending http request without context.Context + - nolintlint # reports ill-formed or insufficient nolint directives + - nonamedreturns # reports all named returns + - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL + - perfsprint # checks that fmt.Sprintf can be replaced with a faster alternative + - predeclared # finds code that shadows one of Go's predeclared identifiers + - promlinter # checks Prometheus metrics naming via promlint + - protogetter # reports direct reads from proto message fields when getters should be used + - reassign # checks that package variables are not reassigned + - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint + - rowserrcheck # checks whether Err of rows is checked successfully + - sloglint # ensure consistent code style when using log/slog + - spancheck # checks for mistakes with OpenTelemetry/Census spans + - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed + - stylecheck # is a replacement for golint + - tenv # detects using os.Setenv instead of t.Setenv since Go1.17 + - testableexamples # checks if examples are testable (have an expected output) + - testifylint # checks usage of github.com/stretchr/testify + # - testpackage # makes you use a separate _test package + - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes + - unconvert # removes unnecessary type conversions + - unparam # reports unused function parameters + - usestdlibvars # detects the possibility to use variables/constants from the Go standard library + - wastedassign # finds wasted assignment statements + - whitespace # detects leading and trailing whitespace + + ## you may want to enable + #- decorder # checks declaration order and count of types, constants, variables and functions + #- exhaustruct # [highly recommend to enable] checks if all structure fields are initialized + #- gci # controls golang package import order and makes it always deterministic + #- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega + #- godox # detects FIXME, TODO and other comment keywords + #- goheader # checks is file header matches to pattern + #- inamedparam # [great idea, but too strict, need to ignore a lot of cases by default] reports interfaces with unnamed method parameters + #- interfacebloat # checks the number of methods inside an interface + #- ireturn # accept interfaces, return concrete types + #- prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated + #- tagalign # checks that struct tags are well aligned + #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope + #- wrapcheck # checks that errors returned from external packages are wrapped + #- zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event + + ## disabled + #- containedctx # detects struct contained context.Context field + #- contextcheck # [too many false positives] checks the function whether use a non-inherited context + #- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages + #- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + #- dupword # [useless without config] checks for duplicate words in the source code + #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted + #- forcetypeassert # [replaced by errcheck] finds forced type assertions + #- goerr113 # [too strict] checks the errors handling expressions + #- gofmt # [replaced by goimports] checks whether code was gofmt-ed + #- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed + #- gosmopolitan # reports certain i18n/l10n anti-patterns in your Go codebase + #- grouper # analyzes expression groups + #- importas # enforces consistent import aliases + #- maintidx # measures the maintainability index of each function + #- misspell # [useless] finds commonly misspelled English words in comments + #- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity + #- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test + #- tagliatelle # checks the struct tags + #- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers + #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines + + +issues: + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 50 + + exclude-rules: + - source: "(noinspection|TODO)" + linters: [ godot ] + - source: "//noinspection" + linters: [ gocritic ] + - path: "_test\\.go" + linters: + - bodyclose + - dupl + - funlen + - goconst + - gosec + - noctx + - wrapcheck diff --git a/README.md b/README.md index 5c4d07e..f70d9d8 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,5 @@ pre-commit run --all-files #### Go-licenses pre-commit hook -Windows users will not be able to run the `go-licenses` hook as yet - @momer will be sending through a -PR to that project to resolve the issue, which is to do with OS-agnostic filepath support! - - +Windows users: Ensure that you have `C:\Program Files\Git\usr\bin` added +to your `PATH`! \ No newline at end of file diff --git a/bonsai/bonsai.go b/bonsai/bonsai.go index 8726c5c..55ce06c 100644 --- a/bonsai/bonsai.go +++ b/bonsai/bonsai.go @@ -1,2 +1,6 @@ // Package bonsai wraps the Bonsai.io HTTP API to create a Go API Client. package bonsai + +const ( + Float64Epsilon = 1e-9 +) diff --git a/bonsai/bonsai_test.go b/bonsai/bonsai_test.go index e6c35be..c777f4c 100644 --- a/bonsai/bonsai_test.go +++ b/bonsai/bonsai_test.go @@ -2,10 +2,9 @@ package bonsai_test import ( "fmt" + "log" "log/slog" "os" - - _ "github.com/stretchr/testify/require" ) func init() { @@ -18,9 +17,12 @@ func initLogger() { logHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ AddSource: true, - ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr { + ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { if a.Key == slog.SourceKey { - src := a.Value.Any().(*slog.Source) + src, ok := a.Value.Any().(*slog.Source) + if !ok { + log.Fatalf("sourceKey attr is not a Source: %v", a.Value) + } // Ruby on Rails-ish formatting a.Value = slog.StringValue(fmt.Sprintf("%s:%d:in '%s'", src.File, src.Line, src.Function)) } diff --git a/bonsai/client.go b/bonsai/client.go index 744a7ca..547d85d 100644 --- a/bonsai/client.go +++ b/bonsai/client.go @@ -18,45 +18,53 @@ import ( "golang.org/x/time/rate" ) -// Client representation configuration +// Client representation configuration. const ( - // Version reflects this API Client's version + // Version reflects this API Client's version. Version = "1.0.0" - // BaseEndpoint is the target API URL base location + // BaseEndpoint is the target API URL base location. BaseEndpoint = "https://api.bonsai.io" // UserAgent is the internally used value for the User-Agent header - // in all outgoing HTTP requests + // in all outgoing HTTP requests. UserAgent = "bonsai-api-go/" + Version ) -// Client rate limiter configuration +// Client rate limiter configuration. const ( - // DefaultClientBurstAllowance is the default Bonsai API request burst allowance + // DefaultClientBurstAllowance is the default Bonsai API request burst + // allowance. DefaultClientBurstAllowance = 60 - // DefaultClientBurstDuration is the default interval for a token bucket of size DefaultClientBurstAllowance to be refilled. + // DefaultClientBurstDuration is the default interval for a token + // bucket of size DefaultClientBurstAllowance to be refilled. DefaultClientBurstDuration = 1 * time.Minute - // ProvisionClientBurstAllowance is the default Bonsai API request burst allowance + // ProvisionClientBurstAllowance is the default Bonsai API request burst allowance. ProvisionClientBurstAllowance = 5 - // ProvisionClientBurstDuration is the default interval for a token bucket of size ProvisionClientBurstAllowance to be refilled. + // ProvisionClientBurstDuration is the default interval for a token bucket + // of size ProvisionClientBurstAllowance to be refilled. ProvisionClientBurstDuration = 1 * time.Minute ) -// Common API Response headers +// Common API Response headers. const ( - // HeaderRetryAfter holds the number of seconds to delay before making the next request + // HeaderRetryAfter holds the number of seconds to delay before making the next request. // ref: https://bonsai.io/docs/api-error-429-too-many-requests HeaderRetryAfter = "Retry-After" ) -// HTTP Content Types and related Header +// HTTP Content Types and related Header. const ( HTTPHeaderContentType = "Content-Type" HTTPContentTypeJSON string = "application/json" ) -// HTTP Status Response Errors +// HTTP Status Response Errors. var ( - ErrorHTTPStatusNotFound = errors.New("not found") + ErrHTTPStatusNotFound = errors.New("not found") + ErrHTTPStatusForbidden = errors.New("forbidden") + ErrHTTPStatusPaymentRequired = errors.New("payment required") + ErrHTTPStatusUnprocessableEntity = errors.New("unprocessable entity") + ErrHTTPStatusUnauthorized = errors.New("unauthorized") + ErrHTTPStatusTooManyRequests = errors.New("too many requests") ) // ResponseError captures API response errors @@ -79,9 +87,20 @@ func (r ResponseError) Error() string { func (r ResponseError) Is(target error) bool { switch r.Status { + case http.StatusUnauthorized: + return target == ErrHTTPStatusUnauthorized case http.StatusNotFound: - return target == ErrorHTTPStatusNotFound + return target == ErrHTTPStatusNotFound + case http.StatusForbidden: + return target == ErrHTTPStatusForbidden + case http.StatusPaymentRequired: + return target == ErrHTTPStatusPaymentRequired + case http.StatusUnprocessableEntity: + return target == ErrHTTPStatusUnprocessableEntity + case http.StatusTooManyRequests: + return target == ErrHTTPStatusTooManyRequests } + return false } @@ -134,13 +153,13 @@ func (t Token) NotEmpty() bool { func NewToken(token string) (Token, error) { t := Token{token} - if ok := t.validHttpValue(); !ok { + if ok := t.validHTTPValue(); !ok { return Token{}, errors.New("invalid token") } return t, nil } -func (t Token) validHttpValue() bool { +func (t Token) validHTTPValue() bool { return httpguts.ValidHeaderFieldValue(t.string) } @@ -152,7 +171,6 @@ func WithEndpoint(endpoint string) ClientOption { return func(c *Client) { c.endpoint = strings.TrimRight(endpoint, "/") } - } // WithToken configures a Client to use the specified token for authentication. @@ -176,14 +194,14 @@ func WithApplication(app Application) ClientOption { } } -// WithDefaultRateLimit configures the default rate limit for client requests +// WithDefaultRateLimit configures the default rate limit for client requests. func WithDefaultRateLimit(l *rate.Limiter) ClientOption { return func(c *Client) { c.rateLimiter.limiter = l } } -// WithProvisionRateLimit configures the rate limit for client requests to the Provision API +// WithProvisionRateLimit configures the rate limit for client requests to the Provision API. func WithProvisionRateLimit(l *rate.Limiter) ClientOption { return func(c *Client) { c.rateLimiter.provisionLimiter = l @@ -198,41 +216,16 @@ type PaginatedResponse struct { type httpResponse = *http.Response type Response struct { - httpResponse + httpResponse `json:"-"` - Body io.ReadCloser + Body io.ReadCloser `json:"-"` PaginatedResponse `json:"pagination"` } func (r *Response) WithHTTPResponse(httpResp *http.Response) error { var err error - bodyBuf := new(bytes.Buffer) r.httpResponse = httpResp - if httpResp == nil { - return errors.New("received nil http.Response") - } - - _, err = bodyBuf.ReadFrom(httpResp.Body) - if err != nil { - return fmt.Errorf("error reading response body: %w", err) - } - - err = IoClose(httpResp.Body, err) - if err != nil { - return err - } - - r.Body = io.NopCloser(bodyBuf) - - switch httpResp.Header.Get("Content-Type") { - case HTTPContentTypeJSON: - err = json.Unmarshal(bodyBuf.Bytes(), r) - } - if err != nil { - return fmt.Errorf("error unmarshaling response body: %w", err) - } - return err } @@ -289,8 +282,8 @@ func (c *Client) UserAgent() string { // NewRequest creates an HTTP request against the API. The returned request // is assigned with ctx and has all necessary headers set (auth, user agent, etc.). func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) { - reqUrl := c.endpoint + path - req, err := http.NewRequest(method, reqUrl, body) + reqURL := c.endpoint + path + req, err := http.NewRequest(method, reqURL, body) if err != nil { return nil, err } @@ -328,60 +321,100 @@ func (c *Client) Do(ctx context.Context, req *http.Request) (*Response, error) { // We only retry in the scenario of http.StatusTooManyRequests (429). for { - respBuf := new(bytes.Buffer) - // Wrap the buffer in a no-op Closer, such that - // it satisfies the ReadCloser interface - if req.ContentLength > 0 { - req.Body = io.NopCloser(reqBuf) + respErr := &ResponseError{} + resp, err := c.doRequest(ctx, req, reqBuf) + switch { + case errors.As(err, respErr): + if reflect.ValueOf(respErr).IsZero() { + return resp, fmt.Errorf("unknown error occurred with response status %d", resp.StatusCode) + } else if errors.Is(err, ErrHTTPStatusTooManyRequests) { + // Block in this routine, if needed. + var delay int64 + if delay, err = extractRetryDelay(resp); err != nil { + time.Sleep(time.Duration(delay) * time.Second) + } + continue + } + return resp, err + default: + return resp, err } + } +} - // Context cancelled, timed-out, burst issue, or other rate limit issue; - // let the callers handle it. - if err := c.rateLimiter.Wait(ctx); err != nil { - return nil, fmt.Errorf("failed while awaiting execution per rate-limit: %w", err) - } +func (c *Client) doRequest(ctx context.Context, req *http.Request, reqBuf *bytes.Buffer) (*Response, error) { + // Wrap the buffer in a no-op Closer, such that + // it satisfies the ReadCloser interface + if req.ContentLength > 0 { + req.Body = io.NopCloser(reqBuf) + } - httpResp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("http request failed: %w", err) - } + // Context cancelled, timed-out, burst issue, or other rate limit issue; + // let the callers handle it. + if err := c.rateLimiter.Wait(ctx); err != nil { + return nil, fmt.Errorf("failed while awaiting execution per rate-limit: %w", err) + } - resp, err := NewResponse() - if err != nil { - return resp, fmt.Errorf("creating new Response") - } + httpResp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("http request failed: %w", err) + } + + if httpResp == nil { + return nil, errors.New("received nil http.Response") + } + + resp, err := NewResponse() + if err != nil { + return resp, errors.New("creating new Response") + } + defer func() { err = IoClose(httpResp.Body, err) }() + + // A place to store the response body bytes + bodyBuf := new(bytes.Buffer) + + _, err = bodyBuf.ReadFrom(httpResp.Body) + if err != nil { + return resp, fmt.Errorf("error reading response body: %w", err) + } + + err = resp.WithHTTPResponse(httpResp) + if err != nil { + return resp, fmt.Errorf("setting http response: %w", err) + } - err = resp.WithHTTPResponse(httpResp) + // Extract the pagination details + if httpResp.Header.Get("Content-Type") == HTTPContentTypeJSON { + err = json.Unmarshal(bodyBuf.Bytes(), &resp) if err != nil { - return resp, fmt.Errorf("setting http response: %w", err) + return resp, fmt.Errorf("error unmarshaling response body: %w", err) } + } - if resp.StatusCode >= 400 { - respErr := ResponseError{} - if err = json.Unmarshal(respBuf.Bytes(), &respErr); err != nil { - return resp, fmt.Errorf("error unmarshalling error response: %w", err) - } + if resp.StatusCode >= http.StatusBadRequest { + respErr := ResponseError{} + if err = json.Unmarshal(bodyBuf.Bytes(), &respErr); err != nil { + return resp, fmt.Errorf("error unmarshalling error response: %w", err) + } + return resp, respErr + } - if reflect.ValueOf(respErr).IsZero() { - return resp, fmt.Errorf("unknown error occurred with response status %d", resp.StatusCode) - } else if respErr.Status == http.StatusTooManyRequests { - // We're already blocking on this routine, so sleep inline per the header request. - if retryAfterStr := resp.Header.Get(HeaderRetryAfter); retryAfterStr != "" { - retryAfter, err := strconv.ParseInt(retryAfterStr, 10, 64) - if err != nil { - return resp, fmt.Errorf("error parsing retry-after response: %w", err) - } - time.Sleep(time.Duration(retryAfter) * time.Second) - } + return resp, err +} - continue - } else { - return resp, respErr - } +func extractRetryDelay(resp *Response) (int64, error) { + var ( + retryAfter int64 + err error + ) + // We're already blocking on this routine, so sleep inline per the header request. + if retryAfterStr := resp.Header.Get(HeaderRetryAfter); retryAfterStr != "" { + retryAfter, err = strconv.ParseInt(retryAfterStr, 10, 64) + if err != nil { + return retryAfter, fmt.Errorf("error parsing retry-after response: %w", err) } - - return resp, err } + return retryAfter, nil } // all loops through the next page pagination results until empty diff --git a/bonsai/client_impl_test.go b/bonsai/client_impl_test.go index b070494..f08c914 100644 --- a/bonsai/client_impl_test.go +++ b/bonsai/client_impl_test.go @@ -51,8 +51,8 @@ func (s *ClientImplTestSuite) SetupSuite() { func (s *ClientImplTestSuite) TestClientDefaultRateLimit() { c := NewClient() - s.Equal(c.rateLimiter.Burst(), DefaultClientBurstAllowance) - s.Equal(c.rateLimiter.Limit(), rate.Every(DefaultClientBurstDuration)) + s.Equal(DefaultClientBurstAllowance, c.rateLimiter.Burst()) + s.InEpsilon(float64(rate.Every(DefaultClientBurstDuration)), float64(c.rateLimiter.Limit()), Float64Epsilon) } func (s *ClientImplTestSuite) TestListOptsValues() { @@ -112,7 +112,7 @@ func (s *ClientImplTestSuite) TestClientAll() { } err := json.NewEncoder(w).Encode(respBody) - s.Nil(err, "encode response body") + s.NoError(err, "encode response body") }) // The caller must track results against expected count @@ -124,10 +124,10 @@ func (s *ClientImplTestSuite) TestClientAll() { path := fmt.Sprintf("/?page=%d&size=1", page) req, err := s.client.NewRequest(ctx, "GET", path, nil) - s.Nil(err, "new request for path") + s.NoError(err, "new request for path") resp, err := s.client.Do(context.Background(), req) - s.Nil(err, "do request") + s.NoError(err, "do request") expectedPage++ // A reference of how these funcs should handle this; @@ -143,9 +143,15 @@ func (s *ClientImplTestSuite) TestClientAll() { } return resp, err }) - s.Nil(err, "client.all call") - - s.Equalf(expectedPage, expectedPageCount, "expected page visit count (%d) matches actual visit count (%d)", expectedPageCount-1, expectedPage-1) + s.NoError(err, "client.all call") + + s.Equalf( + expectedPage, + expectedPageCount, + "expected page visit count (%d) matches actual visit count (%d)", + expectedPageCount-1, + expectedPage-1, + ) } func TestClientImplTestSuite(t *testing.T) { diff --git a/bonsai/client_test.go b/bonsai/client_test.go index 972efa3..c8ed385 100644 --- a/bonsai/client_test.go +++ b/bonsai/client_test.go @@ -3,7 +3,6 @@ package bonsai_test import ( "context" "encoding/json" - "errors" "fmt" "log" "net/http" @@ -11,22 +10,21 @@ import ( "testing" "github.com/stretchr/testify/require" - _ "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/omc/bonsai-api-go/v1/bonsai" ) const ( - ResponseErrorHttpStatusNotFound = `{ + ResponseErrorHTTPStatusNotFound = ` + { "errors": [ "Cluster doesnotexist-1234 not found.", "Please review the documentation available at https://docs.bonsai.io", "Undefined request." ], "status": 404 - } - ` + }` ) type ClientTestSuite struct { @@ -67,8 +65,16 @@ func (s *ClientTestSuite) TestResponseErrorUnmarshallJson() { expect bonsai.ResponseError }{ { - name: "error example from docs site", - received: "{\n \"errors\": [\n \"This request has failed authentication. Please read the docs or email us at support@bonsai.io.\"\n ],\n \"status\": 401\n}", + name: "error example from docs site", + received: ` + { + "errors": [ + "This request has failed authentication. ` + + `Please read the docs or email us at support@bonsai.io." + ], + "status": 401 + } + `, expect: bonsai.ResponseError{ Errors: []string{ "This request has failed authentication. Please read the docs or email us at support@bonsai.io.", @@ -82,7 +88,7 @@ func (s *ClientTestSuite) TestResponseErrorUnmarshallJson() { s.Run(tc.name, func() { respErr := bonsai.ResponseError{} err := json.Unmarshal([]byte(tc.received), &respErr) - s.Nil(err) + s.NoError(err) s.Equal(tc.expect, respErr) }) } @@ -92,33 +98,33 @@ func (s *ClientTestSuite) TestClientResponseError() { const p = "/clusters/doesnotexist-1234" // Configure Servemux to serve the error response at this path - s.serveMux.HandleFunc(p, func(w http.ResponseWriter, r *http.Request) { + s.serveMux.HandleFunc(p, func(w http.ResponseWriter, _ *http.Request) { var err error w.Header().Set("Content-Type", bonsai.HTTPContentTypeJSON) w.WriteHeader(http.StatusNotFound) respErr := &bonsai.ResponseError{} - err = json.Unmarshal([]byte(ResponseErrorHttpStatusNotFound), respErr) - s.Nil(err, "successfully unmarshals json into bonsaiResponseError") + err = json.Unmarshal([]byte(ResponseErrorHTTPStatusNotFound), respErr) + s.NoError(err, "successfully unmarshals json into bonsaiResponseError") err = json.NewEncoder(w).Encode(respErr) - s.Nil(err, "encodes http response into ResponseError") + s.NoError(err, "encodes http response into ResponseError") }) req, err := s.client.NewRequest(context.Background(), "GET", p, nil) - s.Nil(err, "request creation returns no error") + s.NoError(err, "request creation returns no error") resp, err := s.client.Do(context.Background(), req) - s.NotNil(err, "Client.Do returns an error") + s.Error(err, "Client.Do returns an error") - s.Equal(resp.StatusCode, http.StatusNotFound) - s.True(errors.As(err, &bonsai.ResponseError{}), "Client.Do error response type is of ResponseError") - s.True(errors.Is(err, bonsai.ErrorHTTPStatusNotFound), "ResponseError is comparable to bonsai.ErrorHttpResponseStatus") + s.Equal(http.StatusNotFound, resp.StatusCode) + s.ErrorAs(err, &bonsai.ResponseError{}, "Client.Do error response type is of ResponseError") + s.ErrorIs(err, bonsai.ErrHTTPStatusNotFound, "ResponseError is comparable to bonsai.ErrorHttpResponseStatus") } func (s *ClientTestSuite) TestClientResponseWithPagination() { - s.serveMux.HandleFunc("/clusters", func(w http.ResponseWriter, r *http.Request) { + s.serveMux.HandleFunc("/clusters", func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("RateLimit-Limit", "1000") w.Header().Set("RateLimit-Remaining", "999") @@ -134,18 +140,18 @@ func (s *ClientTestSuite) TestClientResponseWithPagination() { } } `) - s.Nil(err, "writes json response into response writer") + s.NoError(err, "writes json response into response writer") }) req, err := s.client.NewRequest(context.Background(), "GET", "/clusters", nil) - s.Nil(err, "request creation returns no error") + s.NoError(err, "request creation returns no error") resp, err := s.client.Do(context.Background(), req) - s.Nil(err, "Client.Do succeeds") + s.NoError(err, "Client.Do succeeds") - s.Equal(resp.PaginatedResponse.PageNumber, 1) - s.Equal(resp.PaginatedResponse.PageSize, 20) - s.Equal(resp.PaginatedResponse.TotalRecords, 255) + s.Equal(1, resp.PaginatedResponse.PageNumber) + s.Equal(20, resp.PaginatedResponse.PageSize) + s.Equal(255, resp.PaginatedResponse.TotalRecords) } func (s *ClientTestSuite) TestClient_WithApplication() { diff --git a/bonsai/internal/dep/dep.go b/bonsai/internal/dep/dep.go index b0af689..9635495 100644 --- a/bonsai/internal/dep/dep.go +++ b/bonsai/internal/dep/dep.go @@ -1,8 +1,9 @@ -// Package dep imports unused, but licensed, dependencies -// for capture by tooling like "go mod vendor" and -// github.com/google/go-licenses -package dep - -import ( - _ "github.com/hetznercloud/hcloud-go/v2/hcloud" -) +// Package dep imports unused, but licensed, dependencies +// for capture by tooling like "go mod vendor" and +// github.com/google/go-licenses +package dep + +import ( + // bonsai.Client is based on hcloud's implementation. + _ "github.com/hetznercloud/hcloud-go/v2/hcloud" +) diff --git a/doc/3rd-party-deps/github.com/beorn7/perks/quantile/LICENSE b/doc/3rd-party-deps/github.com/beorn7/perks/quantile/LICENSE index f1f6712..339177b 100644 --- a/doc/3rd-party-deps/github.com/beorn7/perks/quantile/LICENSE +++ b/doc/3rd-party-deps/github.com/beorn7/perks/quantile/LICENSE @@ -1,20 +1,20 @@ -Copyright (C) 2013 Blake Mizerany - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (C) 2013 Blake Mizerany + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/doc/3rd-party-deps/github.com/cespare/xxhash/v2/LICENSE.txt b/doc/3rd-party-deps/github.com/cespare/xxhash/v2/LICENSE.txt index 056aaef..24b5306 100644 --- a/doc/3rd-party-deps/github.com/cespare/xxhash/v2/LICENSE.txt +++ b/doc/3rd-party-deps/github.com/cespare/xxhash/v2/LICENSE.txt @@ -1,22 +1,22 @@ -Copyright (c) 2016 Caleb Spare - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2016 Caleb Spare + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/doc/3rd-party-deps/github.com/hetznercloud/hcloud-go/v2/hcloud/LICENSE b/doc/3rd-party-deps/github.com/hetznercloud/hcloud-go/v2/hcloud/LICENSE index 6bc5ff0..394ce10 100644 --- a/doc/3rd-party-deps/github.com/hetznercloud/hcloud-go/v2/hcloud/LICENSE +++ b/doc/3rd-party-deps/github.com/hetznercloud/hcloud-go/v2/hcloud/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2018-2020 Hetzner Cloud GmbH - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2018-2020 Hetzner Cloud GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/LICENSE b/doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/LICENSE index 29f81d8..261eeb9 100644 --- a/doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/LICENSE +++ b/doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/LICENSE @@ -1,201 +1,201 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/NOTICE b/doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/NOTICE index 63afd35..dd878a3 100644 --- a/doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/NOTICE +++ b/doc/3rd-party-deps/github.com/prometheus/client_golang/prometheus/NOTICE @@ -1,23 +1,23 @@ -Prometheus instrumentation library for Go applications -Copyright 2012-2015 The Prometheus Authors - -This product includes software developed at -SoundCloud Ltd. (http://soundcloud.com/). - - -The following components are included in this product: - -perks - a fork of https://github.com/bmizerany/perks -https://github.com/beorn7/perks -Copyright 2013-2015 Blake Mizerany, Björn Rabenstein -See https://github.com/beorn7/perks/blob/master/README.md for license details. - -Go support for Protocol Buffers - Google's data interchange format -http://github.com/golang/protobuf/ -Copyright 2010 The Go Authors -See source code for license details. - -Support for streaming Protocol Buffer messages for the Go language (golang). -https://github.com/matttproud/golang_protobuf_extensions -Copyright 2013 Matt T. Proud -Licensed under the Apache License, Version 2.0 +Prometheus instrumentation library for Go applications +Copyright 2012-2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). + + +The following components are included in this product: + +perks - a fork of https://github.com/bmizerany/perks +https://github.com/beorn7/perks +Copyright 2013-2015 Blake Mizerany, Björn Rabenstein +See https://github.com/beorn7/perks/blob/master/README.md for license details. + +Go support for Protocol Buffers - Google's data interchange format +http://github.com/golang/protobuf/ +Copyright 2010 The Go Authors +See source code for license details. + +Support for streaming Protocol Buffer messages for the Go language (golang). +https://github.com/matttproud/golang_protobuf_extensions +Copyright 2013 Matt T. Proud +Licensed under the Apache License, Version 2.0 diff --git a/doc/3rd-party-deps/github.com/prometheus/client_model/go/LICENSE b/doc/3rd-party-deps/github.com/prometheus/client_model/go/LICENSE index 29f81d8..261eeb9 100644 --- a/doc/3rd-party-deps/github.com/prometheus/client_model/go/LICENSE +++ b/doc/3rd-party-deps/github.com/prometheus/client_model/go/LICENSE @@ -1,201 +1,201 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/doc/3rd-party-deps/github.com/prometheus/client_model/go/NOTICE b/doc/3rd-party-deps/github.com/prometheus/client_model/go/NOTICE index adcd01c..20110e4 100644 --- a/doc/3rd-party-deps/github.com/prometheus/client_model/go/NOTICE +++ b/doc/3rd-party-deps/github.com/prometheus/client_model/go/NOTICE @@ -1,5 +1,5 @@ -Data model artifacts for Prometheus. -Copyright 2012-2015 The Prometheus Authors - -This product includes software developed at -SoundCloud Ltd. (http://soundcloud.com/). +Data model artifacts for Prometheus. +Copyright 2012-2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). diff --git a/doc/3rd-party-deps/github.com/prometheus/common/LICENSE b/doc/3rd-party-deps/github.com/prometheus/common/LICENSE index 29f81d8..261eeb9 100644 --- a/doc/3rd-party-deps/github.com/prometheus/common/LICENSE +++ b/doc/3rd-party-deps/github.com/prometheus/common/LICENSE @@ -1,201 +1,201 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/doc/3rd-party-deps/github.com/prometheus/common/NOTICE b/doc/3rd-party-deps/github.com/prometheus/common/NOTICE index 71dba95..636a2c1 100644 --- a/doc/3rd-party-deps/github.com/prometheus/common/NOTICE +++ b/doc/3rd-party-deps/github.com/prometheus/common/NOTICE @@ -1,5 +1,5 @@ -Common libraries shared by Prometheus Go components. -Copyright 2015 The Prometheus Authors - -This product includes software developed at -SoundCloud Ltd. (http://soundcloud.com/). +Common libraries shared by Prometheus Go components. +Copyright 2015 The Prometheus Authors + +This product includes software developed at +SoundCloud Ltd. (http://soundcloud.com/). diff --git a/doc/3rd-party-deps/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/README.txt b/doc/3rd-party-deps/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/README.txt index 89715da..7723656 100644 --- a/doc/3rd-party-deps/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/README.txt +++ b/doc/3rd-party-deps/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/README.txt @@ -1,67 +1,67 @@ -PACKAGE - -package goautoneg -import "bitbucket.org/ww/goautoneg" - -HTTP Content-Type Autonegotiation. - -The functions in this package implement the behaviour specified in -http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html - -Copyright (c) 2011, Open Knowledge Foundation Ltd. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - Neither the name of the Open Knowledge Foundation Ltd. nor the - names of its contributors may be used to endorse or promote - products derived from this software without specific prior written - permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -FUNCTIONS - -func Negotiate(header string, alternatives []string) (content_type string) -Negotiate the most appropriate content_type given the accept header -and a list of alternatives. - -func ParseAccept(header string) (accept []Accept) -Parse an Accept Header string returning a sorted list -of clauses - - -TYPES - -type Accept struct { - Type, SubType string - Q float32 - Params map[string]string -} -Structure to represent a clause in an HTTP Accept Header - - -SUBDIRECTORIES - - .hg +PACKAGE + +package goautoneg +import "bitbucket.org/ww/goautoneg" + +HTTP Content-Type Autonegotiation. + +The functions in this package implement the behaviour specified in +http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + +Copyright (c) 2011, Open Knowledge Foundation Ltd. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + Neither the name of the Open Knowledge Foundation Ltd. nor the + names of its contributors may be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +FUNCTIONS + +func Negotiate(header string, alternatives []string) (content_type string) +Negotiate the most appropriate content_type given the accept header +and a list of alternatives. + +func ParseAccept(header string) (accept []Accept) +Parse an Accept Header string returning a sorted list +of clauses + + +TYPES + +type Accept struct { + Type, SubType string + Q float32 + Params map[string]string +} +Structure to represent a clause in an HTTP Accept Header + + +SUBDIRECTORIES + + .hg diff --git a/doc/3rd-party-deps/github.com/prometheus/procfs/LICENSE b/doc/3rd-party-deps/github.com/prometheus/procfs/LICENSE deleted file mode 100644 index 29f81d8..0000000 --- a/doc/3rd-party-deps/github.com/prometheus/procfs/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/doc/3rd-party-deps/github.com/prometheus/procfs/NOTICE b/doc/3rd-party-deps/github.com/prometheus/procfs/NOTICE deleted file mode 100644 index 5ea05e6..0000000 --- a/doc/3rd-party-deps/github.com/prometheus/procfs/NOTICE +++ /dev/null @@ -1,7 +0,0 @@ -procfs provides functions to retrieve system, kernel and process -metrics from the pseudo-filesystem proc. - -Copyright 2014-2015 The Prometheus Authors - -This product includes software developed at -SoundCloud Ltd. (http://soundcloud.com/). diff --git a/doc/3rd-party-deps/golang.org/x/net/LICENSE b/doc/3rd-party-deps/golang.org/x/net/LICENSE index d71c2f1..6a66aea 100644 --- a/doc/3rd-party-deps/golang.org/x/net/LICENSE +++ b/doc/3rd-party-deps/golang.org/x/net/LICENSE @@ -1,27 +1,27 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/3rd-party-deps/golang.org/x/sys/unix/LICENSE b/doc/3rd-party-deps/golang.org/x/sys/windows/LICENSE similarity index 98% rename from doc/3rd-party-deps/golang.org/x/sys/unix/LICENSE rename to doc/3rd-party-deps/golang.org/x/sys/windows/LICENSE index d71c2f1..6a66aea 100644 --- a/doc/3rd-party-deps/golang.org/x/sys/unix/LICENSE +++ b/doc/3rd-party-deps/golang.org/x/sys/windows/LICENSE @@ -1,27 +1,27 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/3rd-party-deps/golang.org/x/text/LICENSE b/doc/3rd-party-deps/golang.org/x/text/LICENSE index d71c2f1..6a66aea 100644 --- a/doc/3rd-party-deps/golang.org/x/text/LICENSE +++ b/doc/3rd-party-deps/golang.org/x/text/LICENSE @@ -1,27 +1,27 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/3rd-party-deps/golang.org/x/time/rate/LICENSE b/doc/3rd-party-deps/golang.org/x/time/rate/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/doc/3rd-party-deps/golang.org/x/time/rate/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/3rd-party-deps/google.golang.org/protobuf/LICENSE b/doc/3rd-party-deps/google.golang.org/protobuf/LICENSE index ce0d8d3..49ea0f9 100644 --- a/doc/3rd-party-deps/google.golang.org/protobuf/LICENSE +++ b/doc/3rd-party-deps/google.golang.org/protobuf/LICENSE @@ -1,27 +1,27 @@ -Copyright (c) 2018 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Copyright (c) 2018 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 09f52c3d9944c43bd4ba77ec268b29d6f66b6fbc Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Thu, 2 May 2024 11:42:02 -0500 Subject: [PATCH 06/30] Lint resolutions --- bonsai/client.go | 81 ++++++++++++++++++++++++++------------------ bonsai/space.go | 80 +++++++++++++++++++++++++++++++++++++++++++ bonsai/space_test.go | 15 ++++++++ 3 files changed, 143 insertions(+), 33 deletions(-) create mode 100644 bonsai/space.go create mode 100644 bonsai/space_test.go diff --git a/bonsai/client.go b/bonsai/client.go index 547d85d..b14413f 100644 --- a/bonsai/client.go +++ b/bonsai/client.go @@ -57,6 +57,11 @@ const ( HTTPContentTypeJSON string = "application/json" ) +// Magic numbers used to limit allocations, etc. +const ( + defaultResponseCapacity = 8 +) + // HTTP Status Response Errors. var ( ErrHTTPStatusNotFound = errors.New("not found") @@ -216,16 +221,21 @@ type PaginatedResponse struct { type httpResponse = *http.Response type Response struct { - httpResponse `json:"-"` - - Body io.ReadCloser `json:"-"` + httpResponse `json:"-"` + BodyBuf *bytes.Buffer `json:"-"` PaginatedResponse `json:"pagination"` } +// WithHTTPResponse assigns an *http.Response to a *Response item +// and reads its response body into the *Response. func (r *Response) WithHTTPResponse(httpResp *http.Response) error { - var err error r.httpResponse = httpResp + err := r.readHTTPResponseBody() + if err != nil { + return fmt.Errorf("reading response body for error extraction: %w", err) + } + return err } @@ -233,6 +243,34 @@ func (r *Response) MarkPaginationComplete() { r.PaginatedResponse = PaginatedResponse{} } +func (r *Response) readHTTPResponseBody() error { + var ( + err error + ) + + _, err = r.BodyBuf.ReadFrom(r.Body) + if err != nil { + return fmt.Errorf("error reading response body: %w", err) + } + + return nil +} + +func extractRetryDelay(r *Response) (int64, error) { + var ( + retryAfter int64 + err error + ) + // We're already blocking on this routine, so sleep inline per the header request. + if retryAfterStr := r.Header.Get(HeaderRetryAfter); retryAfterStr != "" { + retryAfter, err = strconv.ParseInt(retryAfterStr, 10, 64) + if err != nil { + return retryAfter, fmt.Errorf("error parsing retry-after response: %w", err) + } + } + return retryAfter, nil +} + // NewResponse reserves this function signature, and is // the recommended way to instantiate a Response, as its behavior // may change. @@ -349,7 +387,7 @@ func (c *Client) doRequest(ctx context.Context, req *http.Request, reqBuf *bytes req.Body = io.NopCloser(reqBuf) } - // Context cancelled, timed-out, burst issue, or other rate limit issue; + // Context canceled, timed-out, burst issue, or other rate limit issue; // let the callers handle it. if err := c.rateLimiter.Wait(ctx); err != nil { return nil, fmt.Errorf("failed while awaiting execution per rate-limit: %w", err) @@ -359,6 +397,7 @@ func (c *Client) doRequest(ctx context.Context, req *http.Request, reqBuf *bytes if err != nil { return nil, fmt.Errorf("http request failed: %w", err) } + defer func() { err = IoClose(httpResp.Body, err) }() if httpResp == nil { return nil, errors.New("received nil http.Response") @@ -368,15 +407,6 @@ func (c *Client) doRequest(ctx context.Context, req *http.Request, reqBuf *bytes if err != nil { return resp, errors.New("creating new Response") } - defer func() { err = IoClose(httpResp.Body, err) }() - - // A place to store the response body bytes - bodyBuf := new(bytes.Buffer) - - _, err = bodyBuf.ReadFrom(httpResp.Body) - if err != nil { - return resp, fmt.Errorf("error reading response body: %w", err) - } err = resp.WithHTTPResponse(httpResp) if err != nil { @@ -385,16 +415,16 @@ func (c *Client) doRequest(ctx context.Context, req *http.Request, reqBuf *bytes // Extract the pagination details if httpResp.Header.Get("Content-Type") == HTTPContentTypeJSON { - err = json.Unmarshal(bodyBuf.Bytes(), &resp) + err = json.Unmarshal(resp.BodyBuf.Bytes(), &resp) if err != nil { - return resp, fmt.Errorf("error unmarshaling response body: %w", err) + return resp, fmt.Errorf("error unmarshaling response body for pagination: %w", err) } } if resp.StatusCode >= http.StatusBadRequest { respErr := ResponseError{} - if err = json.Unmarshal(bodyBuf.Bytes(), &respErr); err != nil { - return resp, fmt.Errorf("error unmarshalling error response: %w", err) + if err = json.Unmarshal(resp.BodyBuf.Bytes(), &respErr); err != nil { + return resp, fmt.Errorf("unmarshalling error response: %w", err) } return resp, respErr } @@ -402,21 +432,6 @@ func (c *Client) doRequest(ctx context.Context, req *http.Request, reqBuf *bytes return resp, err } -func extractRetryDelay(resp *Response) (int64, error) { - var ( - retryAfter int64 - err error - ) - // We're already blocking on this routine, so sleep inline per the header request. - if retryAfterStr := resp.Header.Get(HeaderRetryAfter); retryAfterStr != "" { - retryAfter, err = strconv.ParseInt(retryAfterStr, 10, 64) - if err != nil { - return retryAfter, fmt.Errorf("error parsing retry-after response: %w", err) - } - } - return retryAfter, nil -} - // all loops through the next page pagination results until empty // it allows the caller to pass a func (typically a closure) to collect // results. diff --git a/bonsai/space.go b/bonsai/space.go new file mode 100644 index 0000000..2629dce --- /dev/null +++ b/bonsai/space.go @@ -0,0 +1,80 @@ +package bonsai + +import ( + "context" + "encoding/json" + "fmt" + "net/http" +) + +const ( + spaceAPIBasePath = "/spaces" +) + +// CloudProvider contains details about the cloud provider and region +// attributes. +type CloudProvider struct { + Provider string `json:"provider"` + Region string `json:"region"` +} + +// Space represents the server groups and geographic regions available to their +// account, where clusters may be provisioned. +type Space struct { + Path string `json:"path"` + PrivateNetwork bool `json:"private_network"` + Cloud CloudProvider `json:"cloud"` +} + +// SpacesResultList is a wrapper around a slice of +// Spaces for json unmarshaling. +type SpacesResultList struct { + Spaces []Space `json:"spaces"` +} + +// SpaceClient is a client for the Spaces API. +type SpaceClient struct { + *Client +} + +// list returns a list of Spaces for the page specified, +// by performing a GET request against [spaceAPIBasePath]. +// +// Note: Pagination is not currently supported. +func (c *SpaceClient) list(ctx context.Context) ([]Space, *Response, error) { + var ( + req *http.Request + resp *Response + err error + ) + // Let's make some initial capacity to reduce allocations + results := SpacesResultList{ + Spaces: make([]Space, 0, defaultResponseCapacity), + } + + req, err = c.NewRequest(ctx, "GET", spaceAPIBasePath, nil) + if err != nil { + return nil, nil, err + } + + resp, err = c.Do(ctx, req) + if err != nil { + return nil, resp, fmt.Errorf("client.do failed: %w", err) + } + + if err = json.Unmarshal(resp.BodyBuf.Bytes(), &results); err != nil { + return nil, resp, fmt.Errorf("json.Unmarshal failed: %w", err) + } + + return results.Spaces, resp, nil +} + +// All lists all Spaces from the Spaces API. +func (c *SpaceClient) All(ctx context.Context) ([]Space, error) { + // No pagination support as yet + results, _, err := c.list(ctx) + if err != nil { + return results, fmt.Errorf("client.list failed: %w", err) + } + return results, err +} diff --git a/bonsai/space_test.go b/bonsai/space_test.go new file mode 100644 index 0000000..7d87f9d --- /dev/null +++ b/bonsai/space_test.go @@ -0,0 +1,15 @@ +package bonsai_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +type SpaceTestSuite struct { + *ClientTestSuite +} + +func TestClusterTestSuite(t *testing.T) { + suite.Run(t, new(SpaceTestSuite)) +} From 0fe0cf5cf1795d6827782fce418c32c6d5b6a3de Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Thu, 2 May 2024 12:09:50 -0500 Subject: [PATCH 07/30] List tests pass --- bonsai/client.go | 58 ++++++++++++++++++++++++++++------ bonsai/client_impl_test.go | 11 ++++--- bonsai/space.go | 65 +++++++++++++++++++++++++++++++------- bonsai/space_test.go | 56 ++++++++++++++++++++++++++++---- 4 files changed, 158 insertions(+), 32 deletions(-) diff --git a/bonsai/client.go b/bonsai/client.go index b14413f..60b08e6 100644 --- a/bonsai/client.go +++ b/bonsai/client.go @@ -60,6 +60,7 @@ const ( // Magic numbers used to limit allocations, etc. const ( defaultResponseCapacity = 8 + defaultListResultSize = 100 ) // HTTP Status Response Errors. @@ -116,7 +117,24 @@ type listOpts struct { Size int // Size of each page, with a max of 100 } -// Values returns the listOpts as URL values. +// newListOpts creates a new listOpts with default values per the API docs. +// +//nolint:unused // will be used for clusters endpoint +func newDefaultListOpts() listOpts { + return listOpts{ + Page: 1, + Size: defaultListResultSize, + } +} + +// newEmptyListOpts returns an empty list opts, +// to make it easy for readers to immediately see that there are no options +// being passed, rather than seeing a struct be initialized in-line. +func newEmptyListOpts() listOpts { + return listOpts{} +} + +// values returns the listOpts as URL values. func (l listOpts) values() url.Values { vals := url.Values{} if l.Page > 0 { @@ -128,6 +146,14 @@ func (l listOpts) values() url.Values { return vals } +func (l listOpts) IsZero() bool { + return l.Page == 0 && l.Size == 0 +} + +func (l listOpts) Valid() bool { + return !l.IsZero() +} + type Application struct { Name string Version string @@ -222,7 +248,7 @@ type PaginatedResponse struct { type httpResponse = *http.Response type Response struct { httpResponse `json:"-"` - BodyBuf *bytes.Buffer `json:"-"` + BodyBuf bytes.Buffer `json:"-"` PaginatedResponse `json:"pagination"` } @@ -294,6 +320,9 @@ type Client struct { endpoint string token Token userAgent string + + // Clients + Space SpaceClient } func NewClient(options ...ClientOption) *Client { @@ -310,6 +339,9 @@ func NewClient(options ...ClientOption) *Client { option(client) } + // Configure child clients + client.Space = SpaceClient{client} + return client } @@ -435,27 +467,33 @@ func (c *Client) doRequest(ctx context.Context, req *http.Request, reqBuf *bytes // all loops through the next page pagination results until empty // it allows the caller to pass a func (typically a closure) to collect // results. -func (c *Client) all(ctx context.Context, f func(int) (*Response, error)) error { - var ( - page = 1 - ) +func (c *Client) all(ctx context.Context, opt listOpts, f func(opts listOpts) (*Response, error)) error { for { select { case <-ctx.Done(): return ctx.Err() default: - resp, err := f(page) + resp, err := f(opt) if err != nil { return err } - // The caller is responsible for determining whether or not we've exhausted + // The caller is responsible for determining whether we've exhausted // retries. if reflect.ValueOf(resp.PaginatedResponse).IsZero() || resp.PageNumber <= 0 { return nil } - // We should be fine with a straight increment, but let's play it safe - page = resp.PageNumber + 1 + + // If the response contains a page number, provide the next call with an + // incremented page number, and the response page size. + // + // Again, the caller must determine whether the total number of results have been delivered. + if resp.PageNumber > 0 { + opt = listOpts{ + Page: resp.PageNumber + 1, + Size: resp.PageSize, + } + } } } } diff --git a/bonsai/client_impl_test.go b/bonsai/client_impl_test.go index f08c914..33edbe9 100644 --- a/bonsai/client_impl_test.go +++ b/bonsai/client_impl_test.go @@ -118,12 +118,15 @@ func (s *ClientImplTestSuite) TestClientAll() { // The caller must track results against expected count // A reminder to the reader: this is the caller. var resultCount = 0 - err := s.client.all(context.Background(), func(page int) (*Response, error) { - s.Equalf(expectedPage, page, "expected page number (%d) matches actual (%d)", expectedPage, page) + err := s.client.all(context.Background(), newEmptyListOpts(), func(opt listOpts) (*Response, error) { + reqPath := "/" - path := fmt.Sprintf("/?page=%d&size=1", page) + if opt.Valid() { + s.Equalf(expectedPage, opt.Page, "expected page number (%d) matches actual (%d)", expectedPage, opt.Page) + reqPath = fmt.Sprintf("%s?page=%d&size=1", reqPath, opt.Page) + } - req, err := s.client.NewRequest(ctx, "GET", path, nil) + req, err := s.client.NewRequest(ctx, "GET", reqPath, nil) s.NoError(err, "new request for path") resp, err := s.client.Do(context.Background(), req) diff --git a/bonsai/space.go b/bonsai/space.go index 2629dce..4a365a3 100644 --- a/bonsai/space.go +++ b/bonsai/space.go @@ -5,10 +5,12 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" + "reflect" ) const ( - spaceAPIBasePath = "/spaces" + SpaceAPIBasePath = "/spaces" ) // CloudProvider contains details about the cloud provider and region @@ -37,24 +39,43 @@ type SpaceClient struct { *Client } +type SpaceListOptions struct { + listOpts +} + +func (o SpaceListOptions) values() url.Values { + return o.listOpts.values() +} + // list returns a list of Spaces for the page specified, // by performing a GET request against [spaceAPIBasePath]. // // Note: Pagination is not currently supported. -func (c *SpaceClient) list(ctx context.Context) ([]Space, *Response, error) { +func (c *SpaceClient) list(ctx context.Context, opt SpaceListOptions) ([]Space, *Response, error) { var ( - req *http.Request - resp *Response - err error + req *http.Request + reqURL *url.URL + resp *Response + err error ) // Let's make some initial capacity to reduce allocations results := SpacesResultList{ Spaces: make([]Space, 0, defaultResponseCapacity), } - req, err = c.NewRequest(ctx, "GET", spaceAPIBasePath, nil) + reqURL, err = url.Parse(SpaceAPIBasePath) + if err != nil { + return nil, nil, fmt.Errorf("cannot parse relative url from basepath (%s): %w", SpaceAPIBasePath, err) + } + + // Conditionally set options if we received any + if !reflect.ValueOf(opt).IsZero() { + reqURL.RawQuery = opt.values().Encode() + } + + req, err = c.NewRequest(ctx, "GET", reqURL.String(), nil) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err) } resp, err = c.Do(ctx, req) @@ -71,10 +92,32 @@ func (c *SpaceClient) list(ctx context.Context) ([]Space, *Response, error) { // All lists all Spaces from the Spaces API. func (c *SpaceClient) All(ctx context.Context) ([]Space, error) { - // No pagination support as yet - results, _, err := c.list(ctx) + var ( + err error + resp *Response + ) + + allResults := make([]Space, 0, defaultListResultSize) + // No pagination support as yet, but support it for future use + + err = c.all(ctx, newEmptyListOpts(), func(opt listOpts) (*Response, error) { + var listResults []Space + + listResults, resp, err = c.list(ctx, SpaceListOptions{listOpts: opt}) + if err != nil { + return resp, fmt.Errorf("client.list failed: %w", err) + } + + allResults = append(allResults, listResults...) + if len(allResults) >= resp.PageSize { + resp.MarkPaginationComplete() + } + return resp, err + }) + if err != nil { - return results, fmt.Errorf("client.list failed: %w", err) + return allResults, fmt.Errorf("client.all failed: %w", err) } - return results, err + + return allResults, err } diff --git a/bonsai/space_test.go b/bonsai/space_test.go index 7d87f9d..7e6fafb 100644 --- a/bonsai/space_test.go +++ b/bonsai/space_test.go @@ -1,15 +1,57 @@ package bonsai_test import ( - "testing" + "context" + "encoding/json" + "net/http" - "github.com/stretchr/testify/suite" + "github.com/omc/bonsai-api-go/v1/bonsai" ) -type SpaceTestSuite struct { - *ClientTestSuite -} +func (s *ClientTestSuite) TestSpaceClient_All() { + s.serveMux.HandleFunc(bonsai.SpaceAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { + respStr := ` + { + "spaces": [ + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "aws", + "region": "aws-us-east-1" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "aws", + "region": "aws-eu-west-1" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "aws", + "region": "aws-ap-southeast-2" + } + } + ] + } + ` + + resp := &bonsai.SpacesResultList{Spaces: make([]bonsai.Space, 0, 1)} + err := json.Unmarshal([]byte(respStr), resp) + s.NoError(err, "successfully unmarshals json into bonsai.SpacesResultList") + + err = json.NewEncoder(w).Encode(resp) + s.NoError(err, "successfully encodes bonsai.SpacesResultList into json") + }) + + ctx := context.Background() -func TestClusterTestSuite(t *testing.T) { - suite.Run(t, new(SpaceTestSuite)) + spaces, err := s.client.Space.All(ctx) + s.NoError(err, "successfully get all spaces") + s.Len(spaces, 3) } From 1f2d7dccc93c37c0ea715749b4f272c8c290d3f7 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Thu, 2 May 2024 15:22:09 -0500 Subject: [PATCH 08/30] Spaces API complete --- bonsai/space.go | 42 ++++++++++++++++++++++++++++++++++++++---- bonsai/space_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/bonsai/space.go b/bonsai/space.go index 4a365a3..cb50353 100644 --- a/bonsai/space.go +++ b/bonsai/space.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/url" + "path" "reflect" ) @@ -65,7 +66,7 @@ func (c *SpaceClient) list(ctx context.Context, opt SpaceListOptions) ([]Space, reqURL, err = url.Parse(SpaceAPIBasePath) if err != nil { - return nil, nil, fmt.Errorf("cannot parse relative url from basepath (%s): %w", SpaceAPIBasePath, err) + return results.Spaces, nil, fmt.Errorf("cannot parse relative url from basepath (%s): %w", SpaceAPIBasePath, err) } // Conditionally set options if we received any @@ -75,16 +76,16 @@ func (c *SpaceClient) list(ctx context.Context, opt SpaceListOptions) ([]Space, req, err = c.NewRequest(ctx, "GET", reqURL.String(), nil) if err != nil { - return nil, nil, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err) + return results.Spaces, nil, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err) } resp, err = c.Do(ctx, req) if err != nil { - return nil, resp, fmt.Errorf("client.do failed: %w", err) + return results.Spaces, resp, fmt.Errorf("client.do failed: %w", err) } if err = json.Unmarshal(resp.BodyBuf.Bytes(), &results); err != nil { - return nil, resp, fmt.Errorf("json.Unmarshal failed: %w", err) + return results.Spaces, resp, fmt.Errorf("json.Unmarshal failed: %w", err) } return results.Spaces, resp, nil @@ -121,3 +122,36 @@ func (c *SpaceClient) All(ctx context.Context) ([]Space, error) { return allResults, err } + +func (c *SpaceClient) GetByPath(ctx context.Context, spacePath string) (Space, error) { + var ( + req *http.Request + reqURL *url.URL + resp *Response + err error + result Space + ) + + reqURL, err = url.Parse(SpaceAPIBasePath) + if err != nil { + return result, fmt.Errorf("cannot parse relative url from basepath (%s): %w", SpaceAPIBasePath, err) + } + + reqURL.Path = path.Join(reqURL.Path, spacePath) + + req, err = c.NewRequest(ctx, "GET", reqURL.String(), nil) + if err != nil { + return result, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err) + } + + resp, err = c.Do(ctx, req) + if err != nil { + return result, fmt.Errorf("client.do failed: %w", err) + } + + if err = json.Unmarshal(resp.BodyBuf.Bytes(), &result); err != nil { + return result, fmt.Errorf("json.Unmarshal failed: %w", err) + } + + return result, nil +} diff --git a/bonsai/space_test.go b/bonsai/space_test.go index 7e6fafb..e02468f 100644 --- a/bonsai/space_test.go +++ b/bonsai/space_test.go @@ -3,7 +3,9 @@ package bonsai_test import ( "context" "encoding/json" + "fmt" "net/http" + "net/url" "github.com/omc/bonsai-api-go/v1/bonsai" ) @@ -55,3 +57,40 @@ func (s *ClientTestSuite) TestSpaceClient_All() { s.NoError(err, "successfully get all spaces") s.Len(spaces, 3) } + +func (s *ClientTestSuite) TestSpaceClient_GetByPath() { + const targetSpacePath = "omc/bonsai/us-east-1/common" + + urlPath, err := url.JoinPath(bonsai.SpaceAPIBasePath, targetSpacePath) + s.NoError(err, "successfully create url path") + + s.serveMux.HandleFunc(urlPath, func(w http.ResponseWriter, _ *http.Request) { + respStr := fmt.Sprintf(` + { + "path": "%s", + "private_network": false, + "cloud": { + "provider": "aws", + "region": "aws-us-east-1" + } + } + `, targetSpacePath) + + resp := &bonsai.Space{} + err = json.Unmarshal([]byte(respStr), resp) + s.NoError(err, "successfully unmarshals json into bonsai.Space") + + err = json.NewEncoder(w).Encode(resp) + s.NoError(err, "successfully encodes bonsai.Space into json") + }) + + ctx := context.Background() + + space, err := s.client.Space.GetByPath(ctx, "omc/bonsai/us-east-1/common") + s.NoError(err, "successfully get space by path") + + s.Equal(space.Path, targetSpacePath) + s.Equal(space.PrivateNetwork, false) + s.Equal(space.Cloud.Provider, "aws") + s.Equal(space.Cloud.Region, "aws-us-east-1") +} From 08646728728ab394c0f81a0b1b83de9dfc875072 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Thu, 2 May 2024 15:26:27 -0500 Subject: [PATCH 09/30] Update pre-commit only run when go.mod changes --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7c1b5ca..2643062 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,3 +13,4 @@ repos: language: golang require_serial: true pass_filenames: false + files: ^go\.(mod|sum)$ From 7d538606132b73fbec2c1d2e03a0f05e1adc93ef Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Fri, 3 May 2024 07:44:25 -0500 Subject: [PATCH 10/30] Plan API client --- bonsai/client.go | 6 +- bonsai/plan.go | 271 +++++++++++++++++++++++++++++++++++++++ bonsai/plan_impl_test.go | 71 ++++++++++ bonsai/plan_test.go | 179 ++++++++++++++++++++++++++ bonsai/release.go | 10 ++ bonsai/space.go | 1 + 6 files changed, 537 insertions(+), 1 deletion(-) create mode 100644 bonsai/plan.go create mode 100644 bonsai/plan_impl_test.go create mode 100644 bonsai/plan_test.go create mode 100644 bonsai/release.go diff --git a/bonsai/client.go b/bonsai/client.go index 60b08e6..9dda073 100644 --- a/bonsai/client.go +++ b/bonsai/client.go @@ -323,6 +323,7 @@ type Client struct { // Clients Space SpaceClient + Plan PlanClient } func NewClient(options ...ClientOption) *Client { @@ -341,6 +342,7 @@ func NewClient(options ...ClientOption) *Client { // Configure child clients client.Space = SpaceClient{client} + client.Plan = PlanClient{client} return client } @@ -453,10 +455,12 @@ func (c *Client) doRequest(ctx context.Context, req *http.Request, reqBuf *bytes } } + // All error reposes should come with a JSON response per the Error handling + // section @ https://bonsai.io/docs/introduction-to-the-api. if resp.StatusCode >= http.StatusBadRequest { respErr := ResponseError{} if err = json.Unmarshal(resp.BodyBuf.Bytes(), &respErr); err != nil { - return resp, fmt.Errorf("unmarshalling error response: %w", err) + return resp, fmt.Errorf("unmarshaling error response: %w", err) } return resp, respErr } diff --git a/bonsai/plan.go b/bonsai/plan.go new file mode 100644 index 0000000..a1e08b3 --- /dev/null +++ b/bonsai/plan.go @@ -0,0 +1,271 @@ +package bonsai + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "path" + "reflect" +) + +const ( + PlanAPIBasePath = "/plans" +) + +// planAllResponse represents the JSON response object returned from the +// GET /plans endpoint. +// +// It differs from Plan namely in that the AvailableReleases returned is +// a list of string, not Release. +type planAllResponse struct { + // Represents a machine-readable name for the plan. + Slug string `json:"slug"` + // Represents the human-readable name of the plan. + Name string `json:"name"` + // Represents the plan price in cents. + PriceInCents int64 `json:"price_in_cents"` + // Represents the plan billing interval in months. + BillingIntervalInMonths int `json:"billing_interval_in_months"` + // Indicates whether the plan is single-tenant or not. A value of false + // indicates the Cluster will share hardware with other Clusters. Single + // tenant environments can be reached via the public Internet. + SingleTenant bool `json:"single_tenant"` + // Indicates whether the plan is on a publicly addressable network. + // Private plans provide environments that cannot be reached by the public + // Internet. A VPC connection will be needed to communicate with a private + // cluster. + PrivateNetwork bool `json:"private_network"` + // A collection of search release slugs available for the plan. Additional + // information about a release can be retrieved from the Releases API. + AvailableReleases []string `json:"available_releases"` + AvailableSpaces []string `json:"available_spaces"` +} + +type planAllResponseList struct { + Plans []planAllResponse `json:"plans"` +} + +type planAllResponseConverter struct{} + +// Convert copies a single planAllResponse into a Plan, +// transforming types as needed. +func (c *planAllResponseConverter) Convert(source planAllResponse) Plan { + plan := Plan{ + AvailableReleases: make([]Release, len(source.AvailableReleases)), + AvailableSpaces: make([]Space, len(source.AvailableSpaces)), + } + plan.Slug = source.Slug + plan.Name = source.Name + plan.PriceInCents = source.PriceInCents + plan.BillingIntervalInMonths = source.BillingIntervalInMonths + plan.SingleTenant = source.SingleTenant + plan.PrivateNetwork = source.PrivateNetwork + for i, release := range source.AvailableReleases { + plan.AvailableReleases[i] = Release{Slug: release} + } + for i, space := range source.AvailableSpaces { + plan.AvailableSpaces[i] = Space{Path: space} + } + + return plan +} + +// ConvertItems converts a slice of planAllResponse into a slice of Plan +// by way of the planAllResponseConverter.ConvertItems method. +func (c *planAllResponseConverter) ConvertItems(source []planAllResponse) []Plan { + var planList []Plan + if source != nil { + planList = make([]Plan, len(source)) + for i := range source { + planList[i] = c.Convert(source[i]) + } + } + return planList +} + +// Plan represents a subscription plan. +type Plan struct { + // Represents a machine-readable name for the plan. + Slug string `json:"slug"` + // Represents the human-readable name of the plan. + Name string `json:"name"` + // Represents the plan price in cents. + PriceInCents int64 `json:"price_in_cents"` + // Represents the plan billing interval in months. + BillingIntervalInMonths int `json:"billing_interval_months"` + // Indicates whether the plan is single-tenant or not. A value of false + // indicates the Cluster will share hardware with other Clusters. Single + // tenant environments can be reached via the public Internet. + SingleTenant bool `json:"single_tenant"` + // Indicates whether the plan is on a publicly addressable network. + // Private plans provide environments that cannot be reached by the public + // Internet. A VPC connection will be needed to communicate with a private + // cluster. + PrivateNetwork bool `json:"private_network"` + // A collection of search release slugs available for the plan. Additional + // information about a release can be retrieved from the Releases API. + AvailableReleases []Release `json:"available_releases"` + AvailableSpaces []Space `json:"available_spaces"` +} + +func (p *Plan) UnmarshalJSON(data []byte) error { + intermediary := planAllResponse{} + if err := json.Unmarshal(data, &intermediary); err != nil { + return fmt.Errorf("unmarshaling into intermediary type: %w", err) + } + + converter := planAllResponseConverter{} + converted := converter.Convert(intermediary) + *p = converted + + return nil +} + +// PlansResultList is a wrapper around a slice of +// Plans for json unmarshaling. +type PlansResultList struct { + Plans []Plan `json:"plans"` +} + +func (p *PlansResultList) UnmarshalJSON(data []byte) error { + planAllResponseList := make([]planAllResponse, 0) + + if err := json.Unmarshal(data, &planAllResponseList); err != nil { + return fmt.Errorf("unmarshaling into planAllResponseList type: %w", err) + } + + converter := planAllResponseConverter{} + p.Plans = converter.ConvertItems(planAllResponseList) + return nil +} + +// PlanClient is a client for the Plans API. +type PlanClient struct { + *Client +} + +type planListOptions struct { + listOpts +} + +func (o planListOptions) values() url.Values { + return o.listOpts.values() +} + +// list returns a list of Plans for the page specified, +// by performing a GET request against [spaceAPIBasePath]. +// +// Note: Pagination is not currently supported. +func (c *PlanClient) list(ctx context.Context, opt planListOptions) ([]Plan, *Response, error) { + var ( + req *http.Request + reqURL *url.URL + resp *Response + err error + + results []Plan + ) + // Let's make some initial capacity to reduce allocations + intermediaryResults := planAllResponseList{ + Plans: make([]planAllResponse, 0, defaultResponseCapacity), + } + + reqURL, err = url.Parse(PlanAPIBasePath) + if err != nil { + return results, nil, fmt.Errorf("cannot parse relative url from basepath (%s): %w", PlanAPIBasePath, err) + } + + // Conditionally set options if we received any + if !reflect.ValueOf(opt).IsZero() { + reqURL.RawQuery = opt.values().Encode() + } + + req, err = c.NewRequest(ctx, "GET", reqURL.String(), nil) + if err != nil { + return results, nil, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err) + } + + resp, err = c.Do(ctx, req) + if err != nil { + return results, resp, fmt.Errorf("client.do failed: %w", err) + } + + if err = json.Unmarshal(resp.BodyBuf.Bytes(), &intermediaryResults); err != nil { + return results, resp, fmt.Errorf("json.Unmarshal failed: %w", err) + } + + converter := planAllResponseConverter{} + results = converter.ConvertItems(intermediaryResults.Plans) + + return results, resp, nil +} + +// All lists all Plans from the Plans API. +func (c *PlanClient) All(ctx context.Context) ([]Plan, error) { + var ( + err error + resp *Response + ) + + allResults := make([]Plan, 0, defaultListResultSize) + // No pagination support as yet, but support it for future use + + err = c.all(ctx, newEmptyListOpts(), func(opt listOpts) (*Response, error) { + var listResults []Plan + + listResults, resp, err = c.list(ctx, planListOptions{listOpts: opt}) + if err != nil { + return resp, fmt.Errorf("client.list failed: %w", err) + } + + allResults = append(allResults, listResults...) + if len(allResults) >= resp.PageSize { + resp.MarkPaginationComplete() + } + return resp, err + }) + + if err != nil { + return allResults, fmt.Errorf("client.all failed: %w", err) + } + + return allResults, err +} + +// GetBySlug gets a Plan from the Plans API by its slug. +// +//nolint:dupl // Allow duplicated code blocks in code paths that may change +func (c *PlanClient) GetBySlug(ctx context.Context, slug string) (Plan, error) { + var ( + req *http.Request + reqURL *url.URL + resp *Response + err error + result Plan + ) + + reqURL, err = url.Parse(PlanAPIBasePath) + if err != nil { + return result, fmt.Errorf("cannot parse relative url from basepath (%s): %w", PlanAPIBasePath, err) + } + + reqURL.Path = path.Join(reqURL.Path, slug) + + req, err = c.NewRequest(ctx, "GET", reqURL.String(), nil) + if err != nil { + return result, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err) + } + + resp, err = c.Do(ctx, req) + if err != nil { + return result, fmt.Errorf("client.do failed: %w", err) + } + + if err = json.Unmarshal(resp.BodyBuf.Bytes(), &result); err != nil { + return result, fmt.Errorf("json.Unmarshal failed: %w", err) + } + + return result, nil +} diff --git a/bonsai/plan_impl_test.go b/bonsai/plan_impl_test.go new file mode 100644 index 0000000..f3dc0bc --- /dev/null +++ b/bonsai/plan_impl_test.go @@ -0,0 +1,71 @@ +package bonsai + +import ( + "encoding/json" + + "github.com/google/go-cmp/cmp" +) + +func (s *ClientImplTestSuite) TestPlanAllResponseJsonUnmarshal() { + testCases := []struct { + name string + received string + expect planAllResponse + }{ + { + name: "plan example response from docs site", + received: ` + { + "slug": "sandbox-aws-us-east-1", + "name": "Sandbox", + "price_in_cents": 0, + "billing_interval_in_months": 1, + "single_tenant": false, + "private_network": false, + "available_releases": [ + "elasticsearch-7.2.0" + ], + "available_spaces": [ + "omc/bonsai-gcp/us-east4/common", + "omc/bonsai/ap-northeast-1/common", + "omc/bonsai/ap-southeast-2/common", + "omc/bonsai/eu-central-1/common", + "omc/bonsai/eu-west-1/common", + "omc/bonsai/us-east-1/common", + "omc/bonsai/us-west-2/common" + ] + } + `, + expect: planAllResponse{ + Slug: "sandbox-aws-us-east-1", + Name: "Sandbox", + PriceInCents: 0, + BillingIntervalInMonths: 1, + SingleTenant: false, + PrivateNetwork: false, + AvailableReleases: []string{ + "elasticsearch-7.2.0", + }, + AvailableSpaces: []string{ + "omc/bonsai-gcp/us-east4/common", + "omc/bonsai/ap-northeast-1/common", + "omc/bonsai/ap-southeast-2/common", + "omc/bonsai/eu-central-1/common", + "omc/bonsai/eu-west-1/common", + "omc/bonsai/us-east-1/common", + "omc/bonsai/us-west-2/common", + }, + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + result := planAllResponse{} + err := json.Unmarshal([]byte(tc.received), &result) + s.NoError(err) + s.Equal(tc.expect, result) + s.Empty(cmp.Diff(result, tc.expect)) + }) + } +} diff --git a/bonsai/plan_test.go b/bonsai/plan_test.go new file mode 100644 index 0000000..07be289 --- /dev/null +++ b/bonsai/plan_test.go @@ -0,0 +1,179 @@ +package bonsai_test + +import ( + "context" + "fmt" + "net/http" + "net/url" + + "github.com/google/go-cmp/cmp" + "github.com/omc/bonsai-api-go/v1/bonsai" +) + +func (s *ClientTestSuite) TestPlanClient_All() { + s.serveMux.HandleFunc(bonsai.PlanAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { + respStr := ` + { + "plans": [ + { + "slug": "sandbox-aws-us-east-1", + "name": "Sandbox", + "price_in_cents": 0, + "billing_interval_in_months": 1, + "single_tenant": false, + "private_network": false, + "available_releases": [ + "7.2.0" + ], + "available_spaces": [ + "omc/bonsai-gcp/us-east4/common", + "omc/bonsai/ap-northeast-1/common", + "omc/bonsai/ap-southeast-2/common", + "omc/bonsai/eu-central-1/common", + "omc/bonsai/eu-west-1/common", + "omc/bonsai/us-east-1/common", + "omc/bonsai/us-west-2/common" + ] + }, + { + "slug": "standard-sm", + "name": "Standard Small", + "price_in_cents": 5000, + "billing_interval_in_months": 1, + "single_tenant": false, + "private_network": false, + "available_releases": [ + "elasticsearch-5.6.16", + "elasticsearch-6.8.3", + "elasticsearch-7.2.0" + ], + "available_spaces": [ + "omc/bonsai/ap-northeast-1/common", + "omc/bonsai/ap-southeast-2/common", + "omc/bonsai/eu-central-1/common", + "omc/bonsai/eu-west-1/common", + "omc/bonsai/us-east-1/common", + "omc/bonsai/us-west-2/common" + ] + } + ] + } + ` + _, err := w.Write([]byte(respStr)) + s.NoError(err, "write respStr to http.ResponseWriter") + }) + + expect := []bonsai.Plan{ + { + Slug: "sandbox-aws-us-east-1", + Name: "Sandbox", + PriceInCents: 0, + BillingIntervalInMonths: 1, + SingleTenant: false, + PrivateNetwork: false, + AvailableReleases: []bonsai.Release{ + // TODO: we'll see whether the response is actually a + // shortened version like this or a slug + // the documentation is conflicting at + // https://bonsai.io/docs/plans-api-introduction + {Slug: "7.2.0"}, + }, + AvailableSpaces: []bonsai.Space{ + {Path: "omc/bonsai-gcp/us-east4/common"}, + {Path: "omc/bonsai/ap-northeast-1/common"}, + {Path: "omc/bonsai/ap-southeast-2/common"}, + {Path: "omc/bonsai/eu-central-1/common"}, + {Path: "omc/bonsai/eu-west-1/common"}, + {Path: "omc/bonsai/us-east-1/common"}, + {Path: "omc/bonsai/us-west-2/common"}, + }, + }, + { + Slug: "standard-sm", + Name: "Standard Small", + PriceInCents: 5000, + BillingIntervalInMonths: 1, + SingleTenant: false, + PrivateNetwork: false, + AvailableReleases: []bonsai.Release{ + {Slug: "elasticsearch-5.6.16"}, + {Slug: "elasticsearch-6.8.3"}, + {Slug: "elasticsearch-7.2.0"}, + }, + AvailableSpaces: []bonsai.Space{ + {Path: "omc/bonsai/ap-northeast-1/common"}, + {Path: "omc/bonsai/ap-southeast-2/common"}, + {Path: "omc/bonsai/eu-central-1/common"}, + {Path: "omc/bonsai/eu-west-1/common"}, + {Path: "omc/bonsai/us-east-1/common"}, + {Path: "omc/bonsai/us-west-2/common"}, + }, + }, + } + spaces, err := s.client.Plan.All(context.Background()) + s.NoError(err, "successfully get all spaces") + s.Len(spaces, 2) + + s.Empty(cmp.Diff(expect, spaces), "diff between received All response and expected should be empty") +} + +func (s *ClientTestSuite) TestPlanClient_GetByPath() { + const targetPlanPath = "sandbox-aws-us-east-1" + + urlPath, err := url.JoinPath(bonsai.PlanAPIBasePath, "sandbox-aws-us-east-1") + s.NoError(err, "successfully resolved path") + + respStr := fmt.Sprintf(` + { + "slug": "%s", + "name": "Sandbox", + "price_in_cents": 0, + "billing_interval_in_months": 1, + "single_tenant": false, + "private_network": false, + "available_releases": [ + "elasticsearch-7.2.0" + ], + "available_spaces": [ + "omc/bonsai-gcp/us-east4/common", + "omc/bonsai/ap-northeast-1/common", + "omc/bonsai/ap-southeast-2/common", + "omc/bonsai/eu-central-1/common", + "omc/bonsai/eu-west-1/common", + "omc/bonsai/us-east-1/common", + "omc/bonsai/us-west-2/common" + ] + } + `, targetPlanPath) + + s.serveMux.HandleFunc(urlPath, func(w http.ResponseWriter, _ *http.Request) { + _, err = w.Write([]byte(respStr)) + s.NoError(err, "wrote response string to writer") + }) + + expect := bonsai.Plan{ + Slug: "sandbox-aws-us-east-1", + Name: "Sandbox", + PriceInCents: 0, + BillingIntervalInMonths: 1, + SingleTenant: false, + PrivateNetwork: false, + AvailableReleases: []bonsai.Release{ + {Slug: "elasticsearch-7.2.0"}, + }, + AvailableSpaces: []bonsai.Space{ + {Path: "omc/bonsai-gcp/us-east4/common"}, + {Path: "omc/bonsai/ap-northeast-1/common"}, + {Path: "omc/bonsai/ap-southeast-2/common"}, + {Path: "omc/bonsai/eu-central-1/common"}, + {Path: "omc/bonsai/eu-west-1/common"}, + {Path: "omc/bonsai/us-east-1/common"}, + {Path: "omc/bonsai/us-west-2/common"}, + }, + } + + resultResp, err := s.client.Plan.GetBySlug(context.Background(), "sandbox-aws-us-east-1") + s.NoError(err, "successfully get space by path") + + s.Empty(cmp.Diff(expect, resultResp), "diff between received plan response and expected should be empty") +} diff --git a/bonsai/release.go b/bonsai/release.go new file mode 100644 index 0000000..a776601 --- /dev/null +++ b/bonsai/release.go @@ -0,0 +1,10 @@ +package bonsai + +// Release is a placeholder for now. +type Release struct { + Name string `json:"name,omitempty"` + Slug string `json:"slug"` + ServiceType string `json:"service_type,omitempty"` + Version string `json:"version,omitempty"` + MultiTenant bool `json:"multi_tenant,omitempty"` +} diff --git a/bonsai/space.go b/bonsai/space.go index cb50353..8d2cf2c 100644 --- a/bonsai/space.go +++ b/bonsai/space.go @@ -123,6 +123,7 @@ func (c *SpaceClient) All(ctx context.Context) ([]Space, error) { return allResults, err } +//nolint:dupl // Allow duplicated code blocks in code paths that may change func (c *SpaceClient) GetByPath(ctx context.Context, spacePath string) (Space, error) { var ( req *http.Request From b6758db9b0a633a510e5236f3f704f8d1e0f4da6 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Fri, 3 May 2024 07:45:29 -0500 Subject: [PATCH 11/30] Go mod tidy --- go.mod | 1 + 1 file changed, 1 insertion(+) diff --git a/go.mod b/go.mod index bae3410..6095c75 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/omc/bonsai-api-go/v1 go 1.22 require ( + github.com/google/go-cmp v0.6.0 github.com/hetznercloud/hcloud-go/v2 v2.7.2 github.com/stretchr/testify v1.9.0 golang.org/x/net v0.24.0 From 07ee9106d78e53c02dfb7b6940b378eefc82cd52 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Fri, 3 May 2024 07:53:31 -0500 Subject: [PATCH 12/30] CRLF -> LF separators --- .github/workflows/golangci-lint.yml | 80 +-- .gitignore | 220 ++++---- .golangci.yml | 660 ++++++++++++------------ .pre-commit-config.yaml | 32 +- LICENSE | 750 ++++++++++++++-------------- README.md | 110 ++-- 6 files changed, 926 insertions(+), 926 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 9cf6e2f..84c1844 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -1,41 +1,41 @@ -name: golangci-lint -on: - push: - branches: - - '**' # Run on all branches - pull_request: - -permissions: - contents: read - # Optional: allow read access to pull request. Use with `only-new-issues` option. - # pull-requests: read - -jobs: - golangci: - name: lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - check-latest: true - go-version: '1.22' - - name: golangci-lint - uses: golangci/golangci-lint-action@v5 - with: - version: v1.57 - skip-cache: true - args: --timeout=5m - - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - check-latest: true - go-version: '1.22' - - name: Test all +name: golangci-lint +on: + push: + branches: + - '**' # Run on all branches + pull_request: + +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + # pull-requests: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + check-latest: true + go-version: '1.22' + - name: golangci-lint + uses: golangci/golangci-lint-action@v5 + with: + version: v1.57 + skip-cache: true + args: --timeout=5m + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + check-latest: true + go-version: '1.22' + - name: Test all run: go test ./... \ No newline at end of file diff --git a/.gitignore b/.gitignore index 02748af..e7e9976 100644 --- a/.gitignore +++ b/.gitignore @@ -1,111 +1,111 @@ -# via https://github.com/github/gitignore -# License/attribution not required, CC0 - -### -# -# Go -# -### - -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# doc/ - -# Go workspace file -go.work - -### -# -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 -# -### - -# We'll ignore the entire idea folder -.idea/ -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file +# via https://github.com/github/gitignore +# License/attribution not required, CC0 + +### +# +# Go +# +### + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# doc/ + +# Go workspace file +go.work + +### +# +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 +# +### + +# We'll ignore the entire idea folder +.idea/ +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index bd9cb1d..07a114a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,330 +1,330 @@ -# This code is licensed under the terms of the MIT license https://opensource.org/license/mit -# Copyright (c) 2021 Marat Reymers - -## Golden config for golangci-lint v1.57.2 -# -# This is the best config for golangci-lint based on my experience and opinion. -# It is very strict, but not extremely strict. -# Feel free to adapt and change it for your needs. - -run: - # Timeout for analysis, e.g. 30s, 5m. - # Default: 1m - timeout: 3m - - -# This file contains only configs which differ from defaults. -# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml -linters-settings: - cyclop: - # The maximal code complexity to report. - # Default: 10 - max-complexity: 40 - # The maximal average package complexity. - # If it's higher than 0.0 (float) the check is enabled - # Default: 0.0 - package-average: 10.0 - - errcheck: - # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. - # Such cases aren't reported by default. - # Default: false - check-type-assertions: true - - exhaustive: - # Program elements to check for exhaustiveness. - # Default: [ switch ] - check: - - switch - - map - - exhaustruct: - # List of regular expressions to exclude struct packages and their names from checks. - # Regular expressions must match complete canonical struct package/name/structname. - # Default: [] - exclude: - # std libs - - "^net/http.Client$" - - "^net/http.Cookie$" - - "^net/http.Request$" - - "^net/http.Response$" - - "^net/http.Server$" - - "^net/http.Transport$" - - "^net/url.URL$" - - "^os/exec.Cmd$" - - "^reflect.StructField$" - # public libs - - "^github.com/Shopify/sarama.Config$" - - "^github.com/Shopify/sarama.ProducerMessage$" - - "^github.com/mitchellh/mapstructure.DecoderConfig$" - - "^github.com/prometheus/client_golang/.+Opts$" - - "^github.com/spf13/cobra.Command$" - - "^github.com/spf13/cobra.CompletionOptions$" - - "^github.com/stretchr/testify/mock.Mock$" - - "^github.com/testcontainers/testcontainers-go.+Request$" - - "^github.com/testcontainers/testcontainers-go.FromDockerfile$" - - "^golang.org/x/tools/go/analysis.Analyzer$" - - "^google.golang.org/protobuf/.+Options$" - - "^gopkg.in/yaml.v3.Node$" - - funlen: - # Checks the number of lines in a function. - # If lower than 0, disable the check. - # Default: 60 - lines: 100 - # Checks the number of statements in a function. - # If lower than 0, disable the check. - # Default: 40 - statements: 50 - # Ignore comments when counting lines. - # Default false - ignore-comments: true - - gocognit: - # Minimal code complexity to report. - # Default: 30 (but we recommend 10-20) - min-complexity: 40 - - gocritic: - # Settings passed to gocritic. - # The settings key is the name of a supported gocritic checker. - # The list of supported checkers can be find in https://go-critic.github.io/overview. - settings: - captLocal: - # Whether to restrict checker to params only. - # Default: true - paramsOnly: false - underef: - # Whether to skip (*x).method() calls where x is a pointer receiver. - # Default: true - skipRecvDeref: false - - gomnd: - # List of function patterns to exclude from analysis. - # Values always ignored: `time.Date`, - # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`, - # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`. - # Default: [] - ignored-functions: - - flag.Arg - - flag.Duration.* - - flag.Float.* - - flag.Int.* - - flag.Uint.* - - os.Chmod - - os.Mkdir.* - - os.OpenFile - - os.WriteFile - - prometheus.ExponentialBuckets.* - - prometheus.LinearBuckets - - gomodguard: - blocked: - # List of blocked modules. - # Default: [] - modules: - - github.com/golang/protobuf: - recommendations: - - google.golang.org/protobuf - reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules" - - github.com/satori/go.uuid: - recommendations: - - github.com/google/uuid - reason: "satori's package is not maintained" - - github.com/gofrs/uuid: - recommendations: - - github.com/gofrs/uuid/v5 - reason: "gofrs' package was not go module before v5" - - govet: - # Enable all analyzers. - # Default: false - enable-all: true - # Disable analyzers by name. - # Run `go tool vet help` to see all analyzers. - # Default: [] - disable: - - fieldalignment # too strict - # Settings per analyzer. - settings: - shadow: - # Whether to be strict about shadowing; can be noisy. - # Default: false - strict: true - - inamedparam: - # Skips check for interface methods with only a single parameter. - # Default: false - skip-single-param: true - - nakedret: - # Make an issue if func has more lines of code than this setting, and it has naked returns. - # Default: 30 - max-func-lines: 0 - - nolintlint: - # Exclude following linters from requiring an explanation. - # Default: [] - allow-no-explanation: [ funlen, gocognit, lll ] - # Enable to require an explanation of nonzero length after each nolint directive. - # Default: false - require-explanation: true - # Enable to require nolint directives to mention the specific linter being suppressed. - # Default: false - require-specific: true - - perfsprint: - # Optimizes into strings concatenation. - # Default: true - strconcat: false - - rowserrcheck: - # database/sql is always checked - # Default: [] - packages: - - github.com/jmoiron/sqlx - - tenv: - # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. - # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. - # Default: false - all: true - - -linters: - disable-all: true - enable: - ## enabled by default - - errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases - - gosimple # specializes in simplifying a code - - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string - - ineffassign # detects when assignments to existing variables are not used - - staticcheck # is a go vet on steroids, applying a ton of static analysis checks - - typecheck # like the front-end of a Go compiler, parses and type-checks Go code - - unused # checks for unused constants, variables, functions and types - ## disabled by default - - asasalint # checks for pass []any as any in variadic func(...any) - - asciicheck # checks that your code does not contain non-ASCII identifiers - - bidichk # checks for dangerous unicode character sequences - - bodyclose # checks whether HTTP response body is closed successfully - - copyloopvar # detects places where loop variables are copied - - cyclop # checks function and package cyclomatic complexity - - dupl # tool for code clone detection - - durationcheck # checks for two durations multiplied together - - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error - - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13 - - execinquery # checks query string in Query function which reads your Go src files and warning it finds - - exhaustive # checks exhaustiveness of enum switch statements - - exportloopref # checks for pointers to enclosing loop variables - - forbidigo # forbids identifiers - - funlen # tool for detection of long functions - - gocheckcompilerdirectives # validates go compiler directive comments (//go:) - - gochecknoglobals # checks that no global variables exist -# - gochecknoinits # checks that no init functions are present in Go code - - gochecksumtype # checks exhaustiveness on Go "sum types" - - gocognit # computes and checks the cognitive complexity of functions - - goconst # finds repeated strings that could be replaced by a constant - - gocritic # provides diagnostics that check for bugs, performance and style issues - - gocyclo # computes and checks the cyclomatic complexity of functions - - godot # checks if comments end in a period - - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt - - gomnd # detects magic numbers - - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod - - gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations - - goprintffuncname # checks that printf-like functions are named with f at the end - - gosec # inspects source code for security problems - - intrange # finds places where for loops could make use of an integer range - - lll # reports long lines - - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) - - makezero # finds slice declarations with non-zero initial length - - mirror # reports wrong mirror patterns of bytes/strings usage - - musttag # enforces field tags in (un)marshaled structs - - nakedret # finds naked returns in functions greater than a specified function length - - nestif # reports deeply nested if statements - - nilerr # finds the code that returns nil even if it checks that the error is not nil - - nilnil # checks that there is no simultaneous return of nil error and an invalid value - - noctx # finds sending http request without context.Context - - nolintlint # reports ill-formed or insufficient nolint directives - - nonamedreturns # reports all named returns - - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL - - perfsprint # checks that fmt.Sprintf can be replaced with a faster alternative - - predeclared # finds code that shadows one of Go's predeclared identifiers - - promlinter # checks Prometheus metrics naming via promlint - - protogetter # reports direct reads from proto message fields when getters should be used - - reassign # checks that package variables are not reassigned - - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint - - rowserrcheck # checks whether Err of rows is checked successfully - - sloglint # ensure consistent code style when using log/slog - - spancheck # checks for mistakes with OpenTelemetry/Census spans - - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed - - stylecheck # is a replacement for golint - - tenv # detects using os.Setenv instead of t.Setenv since Go1.17 - - testableexamples # checks if examples are testable (have an expected output) - - testifylint # checks usage of github.com/stretchr/testify - # - testpackage # makes you use a separate _test package - - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes - - unconvert # removes unnecessary type conversions - - unparam # reports unused function parameters - - usestdlibvars # detects the possibility to use variables/constants from the Go standard library - - wastedassign # finds wasted assignment statements - - whitespace # detects leading and trailing whitespace - - ## you may want to enable - #- decorder # checks declaration order and count of types, constants, variables and functions - #- exhaustruct # [highly recommend to enable] checks if all structure fields are initialized - #- gci # controls golang package import order and makes it always deterministic - #- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega - #- godox # detects FIXME, TODO and other comment keywords - #- goheader # checks is file header matches to pattern - #- inamedparam # [great idea, but too strict, need to ignore a lot of cases by default] reports interfaces with unnamed method parameters - #- interfacebloat # checks the number of methods inside an interface - #- ireturn # accept interfaces, return concrete types - #- prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated - #- tagalign # checks that struct tags are well aligned - #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope - #- wrapcheck # checks that errors returned from external packages are wrapped - #- zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event - - ## disabled - #- containedctx # detects struct contained context.Context field - #- contextcheck # [too many false positives] checks the function whether use a non-inherited context - #- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages - #- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) - #- dupword # [useless without config] checks for duplicate words in the source code - #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted - #- forcetypeassert # [replaced by errcheck] finds forced type assertions - #- goerr113 # [too strict] checks the errors handling expressions - #- gofmt # [replaced by goimports] checks whether code was gofmt-ed - #- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed - #- gosmopolitan # reports certain i18n/l10n anti-patterns in your Go codebase - #- grouper # analyzes expression groups - #- importas # enforces consistent import aliases - #- maintidx # measures the maintainability index of each function - #- misspell # [useless] finds commonly misspelled English words in comments - #- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity - #- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test - #- tagliatelle # checks the struct tags - #- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers - #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines - - -issues: - # Maximum count of issues with the same text. - # Set to 0 to disable. - # Default: 3 - max-same-issues: 50 - - exclude-rules: - - source: "(noinspection|TODO)" - linters: [ godot ] - - source: "//noinspection" - linters: [ gocritic ] - - path: "_test\\.go" - linters: - - bodyclose - - dupl - - funlen - - goconst - - gosec - - noctx - - wrapcheck +# This code is licensed under the terms of the MIT license https://opensource.org/license/mit +# Copyright (c) 2021 Marat Reymers + +## Golden config for golangci-lint v1.57.2 +# +# This is the best config for golangci-lint based on my experience and opinion. +# It is very strict, but not extremely strict. +# Feel free to adapt and change it for your needs. + +run: + # Timeout for analysis, e.g. 30s, 5m. + # Default: 1m + timeout: 3m + + +# This file contains only configs which differ from defaults. +# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml +linters-settings: + cyclop: + # The maximal code complexity to report. + # Default: 10 + max-complexity: 40 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled + # Default: 0.0 + package-average: 10.0 + + errcheck: + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + # Such cases aren't reported by default. + # Default: false + check-type-assertions: true + + exhaustive: + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map + + exhaustruct: + # List of regular expressions to exclude struct packages and their names from checks. + # Regular expressions must match complete canonical struct package/name/structname. + # Default: [] + exclude: + # std libs + - "^net/http.Client$" + - "^net/http.Cookie$" + - "^net/http.Request$" + - "^net/http.Response$" + - "^net/http.Server$" + - "^net/http.Transport$" + - "^net/url.URL$" + - "^os/exec.Cmd$" + - "^reflect.StructField$" + # public libs + - "^github.com/Shopify/sarama.Config$" + - "^github.com/Shopify/sarama.ProducerMessage$" + - "^github.com/mitchellh/mapstructure.DecoderConfig$" + - "^github.com/prometheus/client_golang/.+Opts$" + - "^github.com/spf13/cobra.Command$" + - "^github.com/spf13/cobra.CompletionOptions$" + - "^github.com/stretchr/testify/mock.Mock$" + - "^github.com/testcontainers/testcontainers-go.+Request$" + - "^github.com/testcontainers/testcontainers-go.FromDockerfile$" + - "^golang.org/x/tools/go/analysis.Analyzer$" + - "^google.golang.org/protobuf/.+Options$" + - "^gopkg.in/yaml.v3.Node$" + + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + lines: 100 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 50 + # Ignore comments when counting lines. + # Default false + ignore-comments: true + + gocognit: + # Minimal code complexity to report. + # Default: 30 (but we recommend 10-20) + min-complexity: 40 + + gocritic: + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be find in https://go-critic.github.io/overview. + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + + gomnd: + # List of function patterns to exclude from analysis. + # Values always ignored: `time.Date`, + # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`, + # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`. + # Default: [] + ignored-functions: + - flag.Arg + - flag.Duration.* + - flag.Float.* + - flag.Int.* + - flag.Uint.* + - os.Chmod + - os.Mkdir.* + - os.OpenFile + - os.WriteFile + - prometheus.ExponentialBuckets.* + - prometheus.LinearBuckets + + gomodguard: + blocked: + # List of blocked modules. + # Default: [] + modules: + - github.com/golang/protobuf: + recommendations: + - google.golang.org/protobuf + reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules" + - github.com/satori/go.uuid: + recommendations: + - github.com/google/uuid + reason: "satori's package is not maintained" + - github.com/gofrs/uuid: + recommendations: + - github.com/gofrs/uuid/v5 + reason: "gofrs' package was not go module before v5" + + govet: + # Enable all analyzers. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `go tool vet help` to see all analyzers. + # Default: [] + disable: + - fieldalignment # too strict + # Settings per analyzer. + settings: + shadow: + # Whether to be strict about shadowing; can be noisy. + # Default: false + strict: true + + inamedparam: + # Skips check for interface methods with only a single parameter. + # Default: false + skip-single-param: true + + nakedret: + # Make an issue if func has more lines of code than this setting, and it has naked returns. + # Default: 30 + max-func-lines: 0 + + nolintlint: + # Exclude following linters from requiring an explanation. + # Default: [] + allow-no-explanation: [ funlen, gocognit, lll ] + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. + # Default: false + require-specific: true + + perfsprint: + # Optimizes into strings concatenation. + # Default: true + strconcat: false + + rowserrcheck: + # database/sql is always checked + # Default: [] + packages: + - github.com/jmoiron/sqlx + + tenv: + # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. + # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. + # Default: false + all: true + + +linters: + disable-all: true + enable: + ## enabled by default + - errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases + - gosimple # specializes in simplifying a code + - govet # reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # detects when assignments to existing variables are not used + - staticcheck # is a go vet on steroids, applying a ton of static analysis checks + - typecheck # like the front-end of a Go compiler, parses and type-checks Go code + - unused # checks for unused constants, variables, functions and types + ## disabled by default + - asasalint # checks for pass []any as any in variadic func(...any) + - asciicheck # checks that your code does not contain non-ASCII identifiers + - bidichk # checks for dangerous unicode character sequences + - bodyclose # checks whether HTTP response body is closed successfully + - copyloopvar # detects places where loop variables are copied + - cyclop # checks function and package cyclomatic complexity + - dupl # tool for code clone detection + - durationcheck # checks for two durations multiplied together + - errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error + - errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13 + - execinquery # checks query string in Query function which reads your Go src files and warning it finds + - exhaustive # checks exhaustiveness of enum switch statements + - exportloopref # checks for pointers to enclosing loop variables + - forbidigo # forbids identifiers + - funlen # tool for detection of long functions + - gocheckcompilerdirectives # validates go compiler directive comments (//go:) + - gochecknoglobals # checks that no global variables exist + # - gochecknoinits # checks that no init functions are present in Go code + - gochecksumtype # checks exhaustiveness on Go "sum types" + - gocognit # computes and checks the cognitive complexity of functions + - goconst # finds repeated strings that could be replaced by a constant + - gocritic # provides diagnostics that check for bugs, performance and style issues + - gocyclo # computes and checks the cyclomatic complexity of functions + - godot # checks if comments end in a period + - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt + - gomnd # detects magic numbers + - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod + - gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations + - goprintffuncname # checks that printf-like functions are named with f at the end + - gosec # inspects source code for security problems + - intrange # finds places where for loops could make use of an integer range + - lll # reports long lines + - loggercheck # checks key value pairs for common logger libraries (kitlog,klog,logr,zap) + - makezero # finds slice declarations with non-zero initial length + - mirror # reports wrong mirror patterns of bytes/strings usage + - musttag # enforces field tags in (un)marshaled structs + - nakedret # finds naked returns in functions greater than a specified function length + - nestif # reports deeply nested if statements + - nilerr # finds the code that returns nil even if it checks that the error is not nil + - nilnil # checks that there is no simultaneous return of nil error and an invalid value + - noctx # finds sending http request without context.Context + - nolintlint # reports ill-formed or insufficient nolint directives + - nonamedreturns # reports all named returns + - nosprintfhostport # checks for misuse of Sprintf to construct a host with port in a URL + - perfsprint # checks that fmt.Sprintf can be replaced with a faster alternative + - predeclared # finds code that shadows one of Go's predeclared identifiers + - promlinter # checks Prometheus metrics naming via promlint + - protogetter # reports direct reads from proto message fields when getters should be used + - reassign # checks that package variables are not reassigned + - revive # fast, configurable, extensible, flexible, and beautiful linter for Go, drop-in replacement of golint + - rowserrcheck # checks whether Err of rows is checked successfully + - sloglint # ensure consistent code style when using log/slog + - spancheck # checks for mistakes with OpenTelemetry/Census spans + - sqlclosecheck # checks that sql.Rows and sql.Stmt are closed + - stylecheck # is a replacement for golint + - tenv # detects using os.Setenv instead of t.Setenv since Go1.17 + - testableexamples # checks if examples are testable (have an expected output) + - testifylint # checks usage of github.com/stretchr/testify + # - testpackage # makes you use a separate _test package + - tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes + - unconvert # removes unnecessary type conversions + - unparam # reports unused function parameters + - usestdlibvars # detects the possibility to use variables/constants from the Go standard library + - wastedassign # finds wasted assignment statements + - whitespace # detects leading and trailing whitespace + + ## you may want to enable + #- decorder # checks declaration order and count of types, constants, variables and functions + #- exhaustruct # [highly recommend to enable] checks if all structure fields are initialized + #- gci # controls golang package import order and makes it always deterministic + #- ginkgolinter # [if you use ginkgo/gomega] enforces standards of using ginkgo and gomega + #- godox # detects FIXME, TODO and other comment keywords + #- goheader # checks is file header matches to pattern + #- inamedparam # [great idea, but too strict, need to ignore a lot of cases by default] reports interfaces with unnamed method parameters + #- interfacebloat # checks the number of methods inside an interface + #- ireturn # accept interfaces, return concrete types + #- prealloc # [premature optimization, but can be used in some cases] finds slice declarations that could potentially be preallocated + #- tagalign # checks that struct tags are well aligned + #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope + #- wrapcheck # checks that errors returned from external packages are wrapped + #- zerologlint # detects the wrong usage of zerolog that a user forgets to dispatch zerolog.Event + + ## disabled + #- containedctx # detects struct contained context.Context field + #- contextcheck # [too many false positives] checks the function whether use a non-inherited context + #- depguard # [replaced by gomodguard] checks if package imports are in a list of acceptable packages + #- dogsled # checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + #- dupword # [useless without config] checks for duplicate words in the source code + #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted + #- forcetypeassert # [replaced by errcheck] finds forced type assertions + #- goerr113 # [too strict] checks the errors handling expressions + #- gofmt # [replaced by goimports] checks whether code was gofmt-ed + #- gofumpt # [replaced by goimports, gofumports is not available yet] checks whether code was gofumpt-ed + #- gosmopolitan # reports certain i18n/l10n anti-patterns in your Go codebase + #- grouper # analyzes expression groups + #- importas # enforces consistent import aliases + #- maintidx # measures the maintainability index of each function + #- misspell # [useless] finds commonly misspelled English words in comments + #- nlreturn # [too strict and mostly code is not more readable] checks for a new line before return and branch statements to increase code clarity + #- paralleltest # [too many false positives] detects missing usage of t.Parallel() method in your Go test + #- tagliatelle # checks the struct tags + #- thelper # detects golang test helpers without t.Helper() call and checks the consistency of test helpers + #- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines + + +issues: + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 50 + + exclude-rules: + - source: "(noinspection|TODO)" + linters: [ godot ] + - source: "//noinspection" + linters: [ gocritic ] + - path: "_test\\.go" + linters: + - bodyclose + - dupl + - funlen + - goconst + - gosec + - noctx + - wrapcheck diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2643062..9b3d91a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,16 @@ -repos: - - repo: https://github.com/golangci/golangci-lint - rev: v1.57.2 - hooks: - - id: golangci-lint-full - - repo: local - hooks: - - id: go-licenses-save - name: go-licenses-save - description: Discover and save 3rd party dependency licenses - entry: go-licenses save ./... --save_path="doc/3rd-party-deps" --ignore github.com/omc --force - types: [go] - language: golang - require_serial: true - pass_filenames: false - files: ^go\.(mod|sum)$ +repos: + - repo: https://github.com/golangci/golangci-lint + rev: v1.57.2 + hooks: + - id: golangci-lint-full + - repo: local + hooks: + - id: go-licenses-save + name: go-licenses-save + description: Discover and save 3rd party dependency licenses + entry: go-licenses save ./... --save_path="doc/3rd-party-deps" --ignore github.com/omc --force + types: [ go ] + language: golang + require_serial: true + pass_filenames: false + files: ^go\.(mod|sum)$ diff --git a/LICENSE b/LICENSE index 67fdd16..15eba9d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,375 +1,375 @@ -Copyright (c) 2021 HashiCorp, Inc. - -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. +Copyright (c) 2021 HashiCorp, Inc. + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md index f70d9d8..a998d81 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,56 @@ -# bonsai-go: Bonsai Cloud Go API Client - -## Installation - -```shell -go get github.com/omc/bonsai-api-go/v1 -``` - -## Example - -```go -package main - -import ( - "context" - "fmt" - "log" - - "github.com/omc/bonsai-api-go/v1/bonsai" -) - -func main() { - token, err := bonsai.NewToken("TestToken") - if err != nil { - log.Fatal(fmt.Errorf("invalid token: %w", err)) - } - - client := bonsai.NewClient( - bonsai.WithToken(token), - ) - - clusters, _, err := client.Clusters.All(context.Background()) - if err != nil { - log.Fatalf("error listing clusters: %s\n", err) - } - log.Printf("Found %d clusters!\n", len(clusters)) -} -``` - -## Contributing - -### Pre-commit - -This project uses [pre-commit](https://pre-commit.com/) to lint and store 3rd-party dependency licenses. -Installation instructions are available on the [pre-commit](https://pre-commit.com/) website! - -To verify your installation, run this project's pre-commit hooks against all files: - -```shell -pre-commit run --all-files -``` - -#### Go-licenses pre-commit hook - -Windows users: Ensure that you have `C:\Program Files\Git\usr\bin` added +# bonsai-go: Bonsai Cloud Go API Client + +## Installation + +```shell +go get github.com/omc/bonsai-api-go/v1 +``` + +## Example + +```go +package main + +import ( + "context" + "fmt" + "log" + + "github.com/omc/bonsai-api-go/v1/bonsai" +) + +func main() { + token, err := bonsai.NewToken("TestToken") + if err != nil { + log.Fatal(fmt.Errorf("invalid token: %w", err)) + } + + client := bonsai.NewClient( + bonsai.WithToken(token), + ) + + clusters, _, err := client.Clusters.All(context.Background()) + if err != nil { + log.Fatalf("error listing clusters: %s\n", err) + } + log.Printf("Found %d clusters!\n", len(clusters)) +} +``` + +## Contributing + +### Pre-commit + +This project uses [pre-commit](https://pre-commit.com/) to lint and store 3rd-party dependency licenses. +Installation instructions are available on the [pre-commit](https://pre-commit.com/) website! + +To verify your installation, run this project's pre-commit hooks against all files: + +```shell +pre-commit run --all-files +``` + +#### Go-licenses pre-commit hook + +Windows users: Ensure that you have `C:\Program Files\Git\usr\bin` added to your `PATH`! \ No newline at end of file From b0c00b957afdc1f61e182a30c5a2812aa5f9273c Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Fri, 3 May 2024 10:14:39 -0500 Subject: [PATCH 13/30] Release Client; remove go.cmp --- bonsai/client.go | 6 +- bonsai/plan_impl_test.go | 5 +- bonsai/plan_test.go | 5 +- bonsai/release.go | 144 ++++++++++++++++++++++++++++++++++++++- bonsai/release_test.go | 118 ++++++++++++++++++++++++++++++++ go.mod | 1 - 6 files changed, 268 insertions(+), 11 deletions(-) create mode 100644 bonsai/release_test.go diff --git a/bonsai/client.go b/bonsai/client.go index 9dda073..69c7d5f 100644 --- a/bonsai/client.go +++ b/bonsai/client.go @@ -322,8 +322,9 @@ type Client struct { userAgent string // Clients - Space SpaceClient - Plan PlanClient + Space SpaceClient + Plan PlanClient + Release ReleaseClient } func NewClient(options ...ClientOption) *Client { @@ -343,6 +344,7 @@ func NewClient(options ...ClientOption) *Client { // Configure child clients client.Space = SpaceClient{client} client.Plan = PlanClient{client} + client.Release = ReleaseClient{client} return client } diff --git a/bonsai/plan_impl_test.go b/bonsai/plan_impl_test.go index f3dc0bc..ddb950e 100644 --- a/bonsai/plan_impl_test.go +++ b/bonsai/plan_impl_test.go @@ -2,8 +2,6 @@ package bonsai import ( "encoding/json" - - "github.com/google/go-cmp/cmp" ) func (s *ClientImplTestSuite) TestPlanAllResponseJsonUnmarshal() { @@ -64,8 +62,7 @@ func (s *ClientImplTestSuite) TestPlanAllResponseJsonUnmarshal() { result := planAllResponse{} err := json.Unmarshal([]byte(tc.received), &result) s.NoError(err) - s.Equal(tc.expect, result) - s.Empty(cmp.Diff(result, tc.expect)) + s.Equal(tc.expect, result, "expected struct matches unmarshaled result") }) } } diff --git a/bonsai/plan_test.go b/bonsai/plan_test.go index 07be289..8237989 100644 --- a/bonsai/plan_test.go +++ b/bonsai/plan_test.go @@ -6,7 +6,6 @@ import ( "net/http" "net/url" - "github.com/google/go-cmp/cmp" "github.com/omc/bonsai-api-go/v1/bonsai" ) @@ -114,7 +113,7 @@ func (s *ClientTestSuite) TestPlanClient_All() { s.NoError(err, "successfully get all spaces") s.Len(spaces, 2) - s.Empty(cmp.Diff(expect, spaces), "diff between received All response and expected should be empty") + s.ElementsMatch(expect, spaces, "elements expected match elements in received spaces") } func (s *ClientTestSuite) TestPlanClient_GetByPath() { @@ -175,5 +174,5 @@ func (s *ClientTestSuite) TestPlanClient_GetByPath() { resultResp, err := s.client.Plan.GetBySlug(context.Background(), "sandbox-aws-us-east-1") s.NoError(err, "successfully get space by path") - s.Empty(cmp.Diff(expect, resultResp), "diff between received plan response and expected should be empty") + s.Equal(expect, resultResp, "expected struct matches unmarshaled result") } diff --git a/bonsai/release.go b/bonsai/release.go index a776601..89d296a 100644 --- a/bonsai/release.go +++ b/bonsai/release.go @@ -1,10 +1,152 @@ package bonsai +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "path" + "reflect" +) + +const ReleaseAPIBasePath = "/releases" + // Release is a placeholder for now. type Release struct { Name string `json:"name,omitempty"` Slug string `json:"slug"` ServiceType string `json:"service_type,omitempty"` Version string `json:"version,omitempty"` - MultiTenant bool `json:"multi_tenant,omitempty"` + MultiTenant bool `json:"multitenant,omitempty"` +} + +// ReleasesResultList is a wrapper around a slice of +// Releases for json unmarshaling. +type ReleasesResultList struct { + Releases []Release `json:"releases"` +} + +// ReleaseClient is a client for the Releases API. +type ReleaseClient struct { + *Client +} + +type releaseListOptions struct { + listOpts +} + +func (o releaseListOptions) values() url.Values { + return o.listOpts.values() +} + +// list returns a list of Releases for the page specified, +// by performing a GET request against [spaceAPIBasePath]. +// +// Note: Pagination is not currently supported. +func (c *ReleaseClient) list(ctx context.Context, opt releaseListOptions) ([]Release, *Response, error) { + var ( + req *http.Request + reqURL *url.URL + resp *Response + err error + ) + // Let's make some initial capacity to reduce allocations + results := ReleasesResultList{ + Releases: make([]Release, 0, defaultResponseCapacity), + } + + reqURL, err = url.Parse(ReleaseAPIBasePath) + if err != nil { + return results.Releases, nil, fmt.Errorf("cannot parse relative url from basepath (%s): %w", ReleaseAPIBasePath, err) + } + + // Conditionally set options if we received any + if !reflect.ValueOf(opt).IsZero() { + reqURL.RawQuery = opt.values().Encode() + } + + req, err = c.NewRequest(ctx, "GET", reqURL.String(), nil) + if err != nil { + return results.Releases, nil, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err) + } + + resp, err = c.Do(ctx, req) + if err != nil { + return results.Releases, resp, fmt.Errorf("client.do failed: %w", err) + } + + if err = json.Unmarshal(resp.BodyBuf.Bytes(), &results); err != nil { + return results.Releases, resp, fmt.Errorf("json.Unmarshal failed: %w", err) + } + + return results.Releases, resp, nil +} + +// All lists all Releases from the Releases API. +func (c *ReleaseClient) All(ctx context.Context) ([]Release, error) { + var ( + err error + resp *Response + ) + + allResults := make([]Release, 0, defaultListResultSize) + // No pagination support as yet, but support it for future use + + err = c.all(ctx, newEmptyListOpts(), func(opt listOpts) (*Response, error) { + var listResults []Release + + listResults, resp, err = c.list(ctx, releaseListOptions{listOpts: opt}) + if err != nil { + return resp, fmt.Errorf("client.list failed: %w", err) + } + + allResults = append(allResults, listResults...) + if len(allResults) >= resp.PageSize { + resp.MarkPaginationComplete() + } + return resp, err + }) + + if err != nil { + return allResults, fmt.Errorf("client.all failed: %w", err) + } + + return allResults, err +} + +// GetBySlug gets a Release from the Releases API by its slug. +// +//nolint:dupl // Allow duplicated code blocks in code paths that may change +func (c *ReleaseClient) GetBySlug(ctx context.Context, slug string) (Release, error) { + var ( + req *http.Request + reqURL *url.URL + resp *Response + err error + result Release + ) + + reqURL, err = url.Parse(ReleaseAPIBasePath) + if err != nil { + return result, fmt.Errorf("cannot parse relative url from basepath (%s): %w", ReleaseAPIBasePath, err) + } + + reqURL.Path = path.Join(reqURL.Path, slug) + + req, err = c.NewRequest(ctx, "GET", reqURL.String(), nil) + if err != nil { + return result, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err) + } + + resp, err = c.Do(ctx, req) + if err != nil { + return result, fmt.Errorf("client.do failed: %w", err) + } + + if err = json.Unmarshal(resp.BodyBuf.Bytes(), &result); err != nil { + return result, fmt.Errorf("json.Unmarshal failed: %w", err) + } + + return result, nil } diff --git a/bonsai/release_test.go b/bonsai/release_test.go new file mode 100644 index 0000000..65b618d --- /dev/null +++ b/bonsai/release_test.go @@ -0,0 +1,118 @@ +package bonsai_test + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/omc/bonsai-api-go/v1/bonsai" +) + +func (s *ClientTestSuite) TestReleaseClient_All() { + s.serveMux.HandleFunc(bonsai.ReleaseAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { + respStr := ` + { + "releases": [ + { + "name": "Elasticsearch 5.6.16", + "slug": "elasticsearch-5.6.16", + "service_type": "elasticsearch", + "version": "5.6.16", + "multitenant": true + }, + { + "name": "Elasticsearch 6.5.4", + "slug": "elasticsearch-6.5.4", + "service_type": "elasticsearch", + "version": "6.5.4", + "multitenant": true + }, + { + "name": "Elasticsearch 7.2.0", + "slug": "elasticsearch-7.2.0", + "service_type": "elasticsearch", + "version": "7.2.0", + "multitenant": true + } + ] + } + ` + + resp := &bonsai.ReleasesResultList{Releases: make([]bonsai.Release, 0, 3)} + err := json.Unmarshal([]byte(respStr), resp) + s.NoError(err, "unmarshal json into bonsai.ReleasesResultList") + + err = json.NewEncoder(w).Encode(resp) + s.NoError(err, "encode bonsai.ReleasesResultList into json") + }) + + expect := []bonsai.Release{ + { + Name: "Elasticsearch 5.6.16", + Slug: "elasticsearch-5.6.16", + ServiceType: "elasticsearch", + Version: "5.6.16", + MultiTenant: true, + }, + { + Name: "Elasticsearch 6.5.4", + Slug: "elasticsearch-6.5.4", + ServiceType: "elasticsearch", + Version: "6.5.4", + MultiTenant: true, + }, + { + Name: "Elasticsearch 7.2.0", + Slug: "elasticsearch-7.2.0", + ServiceType: "elasticsearch", + Version: "7.2.0", + MultiTenant: true, + }, + } + releases, err := s.client.Release.All(context.Background()) + s.NoError(err, "successfully get all releases") + s.Len(releases, 3) + + s.ElementsMatch(expect, releases, "elements in expect match elements in received releases") +} + +func (s *ClientTestSuite) TestReleaseClient_GetBySlug() { + const targetReleaseSlug = "elasticsearch-7.2.0" + + urlPath, err := url.JoinPath(bonsai.ReleaseAPIBasePath, targetReleaseSlug) + s.NoError(err, "successfully resolved path") + + s.serveMux.HandleFunc(urlPath, func(w http.ResponseWriter, _ *http.Request) { + respStr := fmt.Sprintf(` + { + "name": "Elasticsearch 7.2.0", + "slug": "%s", + "service_type": "elasticsearch", + "version": "7.2.0", + "multitenant": true + } + `, targetReleaseSlug) + + resp := &bonsai.Release{} + err = json.Unmarshal([]byte(respStr), resp) + s.NoError(err, "unmarshals json into bonsai.Space") + + err = json.NewEncoder(w).Encode(resp) + s.NoError(err, "encodes bonsai.Space into json on the writer") + }) + + expect := bonsai.Release{ + Slug: "elasticsearch-7.2.0", + Name: "Elasticsearch 7.2.0", + ServiceType: "elasticsearch", + Version: "7.2.0", + MultiTenant: true, + } + + resultResp, err := s.client.Release.GetBySlug(context.Background(), targetReleaseSlug) + s.NoError(err, "successfully get release by path") + + s.Equal(expect, resultResp, "elements in expect match elements in received release response") +} diff --git a/go.mod b/go.mod index 6095c75..bae3410 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/omc/bonsai-api-go/v1 go 1.22 require ( - github.com/google/go-cmp v0.6.0 github.com/hetznercloud/hcloud-go/v2 v2.7.2 github.com/stretchr/testify v1.9.0 golang.org/x/net v0.24.0 From fdb0a9e048a1cd19526876d892c63f20b04a9837 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Fri, 3 May 2024 12:04:42 -0500 Subject: [PATCH 14/30] Cluster Client; switch to a different router to allow tests to keep encapsulation and share a singular multiplexer/router --- bonsai/client.go | 2 + bonsai/client_test.go | 5 +- bonsai/cluster.go | 461 ++++++++++++++++++++++++++++++++++++ bonsai/cluster_impl_test.go | 59 +++++ bonsai/cluster_test.go | 454 +++++++++++++++++++++++++++++++++++ bonsai/plan.go | 32 ++- bonsai/release.go | 9 +- bonsai/space.go | 9 +- go.mod | 2 + go.sum | 6 + 10 files changed, 1022 insertions(+), 17 deletions(-) create mode 100644 bonsai/cluster.go create mode 100644 bonsai/cluster_impl_test.go create mode 100644 bonsai/cluster_test.go diff --git a/bonsai/client.go b/bonsai/client.go index 69c7d5f..af56a33 100644 --- a/bonsai/client.go +++ b/bonsai/client.go @@ -325,6 +325,7 @@ type Client struct { Space SpaceClient Plan PlanClient Release ReleaseClient + Cluster ClusterClient } func NewClient(options ...ClientOption) *Client { @@ -345,6 +346,7 @@ func NewClient(options ...ClientOption) *Client { client.Space = SpaceClient{client} client.Plan = PlanClient{client} client.Release = ReleaseClient{client} + client.Cluster = ClusterClient{client} return client } diff --git a/bonsai/client_test.go b/bonsai/client_test.go index c8ed385..8b7260e 100644 --- a/bonsai/client_test.go +++ b/bonsai/client_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "github.com/go-chi/chi/v5" "github.com/omc/bonsai-api-go/v1/bonsai" ) @@ -34,7 +35,7 @@ type ClientTestSuite struct { suite.Suite // serveMux is the request multiplexer used for tests - serveMux *http.ServeMux + serveMux *chi.Mux // server is the testing server on some local port server *httptest.Server // client allows each test to have a reachable *bonsai.Client for testing @@ -43,7 +44,7 @@ type ClientTestSuite struct { func (s *ClientTestSuite) SetupSuite() { // Configure http client and other miscellany - s.serveMux = http.NewServeMux() + s.serveMux = chi.NewRouter() s.server = httptest.NewServer(s.serveMux) token, err := bonsai.NewToken("TestToken") if err != nil { diff --git a/bonsai/cluster.go b/bonsai/cluster.go new file mode 100644 index 0000000..2dc4e1e --- /dev/null +++ b/bonsai/cluster.go @@ -0,0 +1,461 @@ +package bonsai + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "path" + "reflect" + + "github.com/google/go-querystring/query" +) + +const ( + ClusterAPIBasePath = "/clusters" +) + +// ClusterStats holds *some* statistics about the cluster. +// +// This attribute should not be used for real-time monitoring! +// Stats are updated every 10-15 minutes. To monitor real-time metrics, monitor +// your cluster directly, via the Index Stats API. +type ClusterStats struct { + // Number of documents in the index. + Docs int64 `json:"docs,omitempty"` + // Number of shards the cluster is using. + ShardsUsed int64 `json:"shards_used,omitempty"` + // Number of bytes the cluster is using on-disk. + DataBytesUsed int64 `json:"data_bytes_used,omitempty"` +} + +// ClusterAccess holds information about connecting to the cluster. +type ClusterAccess struct { + // Host name of the cluster + Host string `json:"host"` + // HTTP Port the cluster is running on. + Port int `json:"port"` + // HTTP Scheme needed to access the cluster. Default: "https". + Scheme string `json:"scheme"` + + // User holds the username to access the cluster with. + // Only shown once, during cluster creation. + Username string `json:"user,omitempty"` + // Pass holds the password to access the cluster with. + // Only shown once, during cluster creation. + Password string `json:"pass,omitempty"` + // URL is the Cluster endpoint for access. + // Only shown once, during cluster creation. + URL string `json:"url,omitempty"` +} + +// ClusterState represents the current state of the cluster, indicating what +// the cluster is doing at any given moment. +type ClusterState string + +const ( + ClusterStateDeprovisioned ClusterState = "DEPROVISIONED" + ClusterStateDeprovisioning ClusterState = "DEPROVISIONING" + ClusterStateDisabled ClusterState = "DISABLED" + ClusterStateMaintenance ClusterState = "MAINTENANCE" + ClusterStateProvisioned ClusterState = "PROVISIONED" + ClusterStateProvisioning ClusterState = "PROVISIONING" + ClusterStateReadOnly ClusterState = "READONLY" + ClusterStateUpdatingPlan ClusterState = "UPDATING PLAN" +) + +// Cluster represents a subscription cluster. +type Cluster struct { + // Slug represents a unique, machine-readable name for the cluster. + // A cluster slug is based its name at creation, to which a random integer + // is concatenated. + Slug string `json:"slug"` + // Name is the human-readable name of the cluster. + Name string `json:"name"` + // URI is a machine-readable name for the cluster. + URI string `json:"uri"` + + // Plan holds some information about the cluster's current subscription plan. + Plan Plan `json:"plan"` + // Release holds some information about the cluster's current release. + Release Release `json:"release"` + + // Space holds some information about where the cluster is running. + Space Space `json:"space"` + + // Stats holds a collection of statistics about the cluster. + Stats ClusterStats `json:"stats"` + + // ClusterAccess holds information about connecting to the cluster. + Access ClusterAccess `json:"access"` + + // State represents the current state of the cluster. This indicates what + // the cluster is doing at any given moment. + State ClusterState `json:"state"` +} + +// ClustersResultList is a wrapper around a slice of +// Clusters for json unmarshaling. +type ClustersResultList struct { + Clusters []Cluster `json:"clusters"` +} + +// ClustersResultCreate is the result response for Create (POST) requests to the +// clusters endpoint. +type ClustersResultCreate struct { + // Message contains details about the cluster creation request. + Message string `json:"message"` + // Monitor holds a URI to the Cluster overview page. + Monitor string `json:"monitor"` + Access ClusterAccess `json:"access"` +} + +// ClustersResultUpdate is the result response for Update (PUT) requests to the +// clusters endpoint. +type ClustersResultUpdate struct { + // Message contains details about the cluster update request. + Message string `json:"message"` + // Monitor holds a URI to the Cluster overview page. + Monitor string `json:"monitor"` +} + +// ClustersResultDestroy is the result response for Destroy (DELETE) requests to the +// clusters endpoint. +type ClustersResultDestroy struct { + // Message contains details about the cluster destroy request. + Message string `json:"message"` + // Monitor holds a URI to the Cluster overview page. + Monitor string `json:"monitor"` +} + +// ClusterClient is a client for the Clusters API. +type ClusterClient struct { + *Client +} + +type ClusterAllOpts struct { + // Optional. A query string for filtering matching clusters. + // This currently works on name. + Query string `url:"q,omitempty"` + // Optional. A string which will constrain results to parent or child + // cluster. Valid values are: "parent", "child" + Tenancy string `url:"tenancy,omitempty"` + // Optional. A string representing the account, region, space, or cluster + // path where the cluster is located. You can get a list of available spaces + // with the [bonsai.SpaceClient] API. Space path prefixes work here, so you + // can find all clusters in a given region for a given cloud. + Location string `url:"location,omitempty"` +} + +type ClusterCreateOpts struct { + // Required. A String representing the name for the new cluster. + Name string `json:"name"` + // The slug of the Plan that the new cluster will be configured for. + // Use the [PlanClient.All] method to view a list of all Plans available. + Plan string `json:"plan,omitempty"` + // The slug of the Space where the new cluster should be deployed to. + // Use the [SpaceClient.All] method to view a list of all Spaces. + Space string `json:"space,omitempty"` + // The Search Service Release that the new cluster will use. + // Use the [ReleaseClient.All] method to view a list of all Spaces. + Release string `json:"release,omitempty"` +} + +func (o ClusterCreateOpts) Valid() error { + if o.Name == "" { + return errors.New("name can't be empty") + } + return nil +} + +type ClusterUpdateOpts struct { + // Required. A String representing the name for the new cluster. + Name string `json:"name"` + // The slug of the Plan that the new cluster will be configured for. + // Use the [PlanClient.All] method to view a list of all Plans available. + Plan string `json:"plan,omitempty"` +} + +func (o ClusterUpdateOpts) Valid() error { + if o.Name == "" { + return errors.New("name can't be empty") + } + return nil +} + +type clusterListOpts struct { + listOpts + ClusterAllOpts +} + +func (o clusterListOpts) values() (url.Values, error) { + queryValues := o.listOpts.values() + + clusterValues, err := query.Values(o.ClusterAllOpts) + if err != nil { + return nil, fmt.Errorf("error parsing cluster list options: %w", err) + } + + for k, v := range clusterValues { + queryValues[k] = v + } + + return queryValues, nil +} + +// list returns a list of Clusters for the page specified, +// by performing a GET request against [spaceAPIBasePath]. +// +// Note: Pagination is not currently supported. +func (c *ClusterClient) list(ctx context.Context, opt clusterListOpts) ( + []Cluster, + *Response, + error, +) { + var ( + req *http.Request + reqURL *url.URL + resp *Response + err error + ) + // Let's make some initial capacity to reduce allocations + results := ClustersResultList{ + Clusters: make([]Cluster, 0, defaultResponseCapacity), + } + + reqURL, err = url.Parse(ClusterAPIBasePath) + if err != nil { + return results.Clusters, nil, fmt.Errorf("cannot parse relative url from basepath (%s): %w", ClusterAPIBasePath, err) + } + + // Conditionally set options if we received any + if !reflect.ValueOf(opt).IsZero() { + var optVals url.Values + + optVals, err = opt.values() + if err != nil { + return results.Clusters, nil, fmt.Errorf("failed to get values from opt (%+v): %w", opt, err) + } + + reqURL.RawQuery = optVals.Encode() + } + + req, err = c.NewRequest(ctx, "GET", reqURL.String(), nil) + if err != nil { + return results.Clusters, nil, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err) + } + + resp, err = c.Do(ctx, req) + if err != nil { + return results.Clusters, resp, fmt.Errorf("client.do failed: %w", err) + } + + if err = json.Unmarshal(resp.BodyBuf.Bytes(), &results); err != nil { + return results.Clusters, resp, fmt.Errorf("json.Unmarshal failed: %w", err) + } + + return results.Clusters, resp, nil +} + +// All lists all Clusters from the Clusters API. +func (c *ClusterClient) All(ctx context.Context) ([]Cluster, error) { + var ( + err error + resp *Response + ) + + allResults := make([]Cluster, 0, defaultListResultSize) + // No pagination support as yet, but support it for future use + + err = c.all(ctx, newEmptyListOpts(), func(opt listOpts) (*Response, error) { + var listResults []Cluster + + listResults, resp, err = c.list(ctx, clusterListOpts{listOpts: opt}) + if err != nil { + return resp, fmt.Errorf("client.list failed: %w", err) + } + + allResults = append(allResults, listResults...) + if len(allResults) >= resp.PageSize { + resp.MarkPaginationComplete() + } + return resp, err + }) + + if err != nil { + return allResults, fmt.Errorf("client.all failed: %w", err) + } + + return allResults, err +} + +// GetBySlug gets a Cluster from the Clusters API by its slug. +// +//nolint:dupl // Allow duplicated code blocks in code paths that may change +func (c *ClusterClient) GetBySlug(ctx context.Context, slug string) (Cluster, error) { + var ( + req *http.Request + reqURL *url.URL + resp *Response + err error + result Cluster + ) + + reqURL, err = url.Parse(ClusterAPIBasePath) + if err != nil { + return result, fmt.Errorf("cannot parse relative url from basepath (%s): %w", ClusterAPIBasePath, err) + } + + reqURL.Path = path.Join(reqURL.Path, slug) + + req, err = c.NewRequest(ctx, "GET", reqURL.String(), nil) + if err != nil { + return result, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err) + } + + resp, err = c.Do(ctx, req) + if err != nil { + return result, fmt.Errorf("client.do failed: %w", err) + } + + if err = json.Unmarshal(resp.BodyBuf.Bytes(), &result); err != nil { + return result, fmt.Errorf("json.Unmarshal failed: %w", err) + } + + return result, nil +} + +// Create requests a new Cluster to be created. +// +//nolint:dupl // Allow duplicated code blocks in code paths that may change +func (c *ClusterClient) Create(ctx context.Context, opt ClusterCreateOpts) ( + ClustersResultCreate, + error, +) { + var ( + req *http.Request + reqURL *url.URL + reqBody []byte + resp *Response + err error + ) + // Let's make some initial capacity to reduce allocations + result := ClustersResultCreate{} + + reqURL, err = url.Parse(ClusterAPIBasePath) + if err != nil { + return result, fmt.Errorf("cannot parse relative url from basepath (%s): %w", ClusterAPIBasePath, err) + } + + if err = opt.Valid(); err != nil { + return result, fmt.Errorf("invalid create options (%v): %w", opt, err) + } + + reqBody, err = json.Marshal(opt) + if err != nil { + return result, fmt.Errorf("failed to marshal options (%v): %w", opt, err) + } + + req, err = c.NewRequest(ctx, "POST", reqURL.String(), bytes.NewReader(reqBody)) + if err != nil { + return result, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err) + } + + resp, err = c.Do(ctx, req) + if err != nil { + return result, fmt.Errorf("client.do failed: %w", err) + } + + if err = json.Unmarshal(resp.BodyBuf.Bytes(), &result); err != nil { + return result, fmt.Errorf("json.Unmarshal failed: %w", err) + } + + return result, nil +} + +// Update requests a new Cluster be updated. +// +//nolint:dupl // Allow duplicated code blocks in code paths that may change +func (c *ClusterClient) Update(ctx context.Context, opt ClusterUpdateOpts) ( + ClustersResultUpdate, + error, +) { + var ( + req *http.Request + reqURL *url.URL + reqBody []byte + resp *Response + err error + ) + // Let's make some initial capacity to reduce allocations + result := ClustersResultUpdate{} + + reqURL, err = url.Parse(ClusterAPIBasePath) + if err != nil { + return result, fmt.Errorf("cannot parse relative url from basepath (%s): %w", ClusterAPIBasePath, err) + } + + if err = opt.Valid(); err != nil { + return result, fmt.Errorf("invalid create options (%v): %w", opt, err) + } + + reqBody, err = json.Marshal(opt) + if err != nil { + return result, fmt.Errorf("failed to marshal options (%v): %w", opt, err) + } + + req, err = c.NewRequest(ctx, "PUT", reqURL.String(), bytes.NewReader(reqBody)) + if err != nil { + return result, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err) + } + + resp, err = c.Do(ctx, req) + if err != nil { + return result, fmt.Errorf("client.do failed: %w", err) + } + + if err = json.Unmarshal(resp.BodyBuf.Bytes(), &result); err != nil { + return result, fmt.Errorf("json.Unmarshal failed: %w", err) + } + + return result, nil +} + +// Destroy triggers the deprovisioning of the cluster associated with the slug. +// +//nolint:dupl // Allow duplicated code blocks in code paths that may change +func (c *ClusterClient) Destroy(ctx context.Context, slug string) (ClustersResultDestroy, error) { + var ( + req *http.Request + reqURL *url.URL + resp *Response + err error + result ClustersResultDestroy + ) + + reqURL, err = url.Parse(ClusterAPIBasePath) + if err != nil { + return result, fmt.Errorf("cannot parse relative url from basepath (%s): %w", ClusterAPIBasePath, err) + } + + reqURL.Path = path.Join(reqURL.Path, slug) + + req, err = c.NewRequest(ctx, "DELETE", reqURL.String(), nil) + if err != nil { + return result, fmt.Errorf("creating new http request for URL (%s): %w", reqURL.String(), err) + } + + resp, err = c.Do(ctx, req) + if err != nil { + return result, fmt.Errorf("client.do failed: %w", err) + } + + if err = json.Unmarshal(resp.BodyBuf.Bytes(), &result); err != nil { + return result, fmt.Errorf("json.Unmarshal failed: %w", err) + } + + return result, nil +} diff --git a/bonsai/cluster_impl_test.go b/bonsai/cluster_impl_test.go new file mode 100644 index 0000000..96e10c0 --- /dev/null +++ b/bonsai/cluster_impl_test.go @@ -0,0 +1,59 @@ +package bonsai + +import "net/url" + +func (s *ClientImplTestSuite) TestClusterListOptsValues() { + testCases := []struct { + name string + received clusterListOpts + expect url.Values + }{ + { + name: "with populated values", + received: clusterListOpts{ + listOpts: listOpts{ + Page: 3, + Size: 100, + }, + ClusterAllOpts: ClusterAllOpts{ + Query: "a query string", + Tenancy: "parent", + Location: "omc/bonsai/us-east-1/common", + }, + }, + expect: url.Values{ + "page": []string{"3"}, + "size": []string{"100"}, + "q": []string{"a query string"}, + "tenancy": []string{"parent"}, + "location": []string{"omc/bonsai/us-east-1/common"}, + }, + }, + { + name: "with pagination, but empty ClusterAllOpts values", + received: clusterListOpts{ + listOpts: listOpts{ + Page: 3, + Size: 100, + }, + }, + expect: url.Values{ + "page": []string{"3"}, + "size": []string{"100"}, + }, + }, + { + name: "with empty values", + received: clusterListOpts{}, + expect: url.Values{}, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + receivedVal, err := tc.received.values() + s.NoError(err, "received values values()") + s.Equal(receivedVal, tc.expect) + }) + } +} diff --git a/bonsai/cluster_test.go b/bonsai/cluster_test.go new file mode 100644 index 0000000..07f2202 --- /dev/null +++ b/bonsai/cluster_test.go @@ -0,0 +1,454 @@ +package bonsai_test + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/omc/bonsai-api-go/v1/bonsai" +) + +func (s *ClientTestSuite) TestClusterClient_All() { + s.serveMux.Get(bonsai.ClusterAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { + respStr := ` + { + "pagination": { + "page_number": 1, + "page_size": 10, + "total_records": 3 + }, + "clusters": [ + { + "slug": "first-testing-cluste-1234567890", + "name": "first_testing_cluster", + "uri": "https://api.bonsai.io/clusters/first-testing-cluste-1234567890", + "plan": { + "slug": "sandbox-aws-us-east-1", + "uri": "https://api.bonsai.io/plans/sandbox-aws-us-east-1" + }, + "release": { + "version": "7.2.0", + "slug": "elasticsearch-7.2.0", + "package_name": "7.2.0", + "service_type": "elasticsearch", + "uri": "https://api.bonsai.io/releases/elasticsearch-7.2.0" + }, + "space": { + "path": "omc/bonsai/us-east-1/common", + "region": "aws-us-east-1", + "uri": "https://api.bonsai.io/spaces/omc/bonsai/us-east-1/common" + }, + "stats": { + "docs": 0, + "shards_used": 0, + "data_bytes_used": 0 + }, + "access": { + "host": "first-testing-cluste-1234567890.us-east-1.bonsaisearch.net", + "port": 443, + "scheme": "https" + }, + "state": "PROVISIONED" + }, + { + "slug": "second-testing-clust-1234567890", + "name": "second_testing_cluster", + "uri": "https://api.bonsai.io/clusters/second-testing-clust-1234567890", + "plan": { + "slug": "sandbox-aws-us-east-1", + "uri": "https://api.bonsai.io/plans/sandbox-aws-us-east-1" + }, + "release": { + "version": "7.2.0", + "slug": "elasticsearch-7.2.0", + "package_name": "7.2.0", + "service_type": "elasticsearch", + "uri": "https://api.bonsai.io/releases/elasticsearch-7.2.0" + }, + "space": { + "path": "omc/bonsai/us-east-1/common", + "region": "aws-us-east-1", + "uri": "https://api.bonsai.io/spaces/omc/bonsai/us-east-1/common" + }, + "stats": { + "docs": 0, + "shards_used": 0, + "data_bytes_used": 0 + }, + "access": { + "host": "second-testing-clust-1234567890.us-east-1.bonsaisearch.net", + "port": 443, + "scheme": "https" + }, + "state": "PROVISIONED" + }, + { + "slug": "third-testing-clust-1234567890", + "name": "third_testing_cluster", + "uri": "https://api.bonsai.io/clusters/third-testing-clust-1234567890", + "plan": { + "slug": "sandbox-aws-us-east-1", + "uri": "https://api.bonsai.io/plans/sandbox-aws-us-east-1" + }, + "release": { + "version": "7.2.0", + "slug": "elasticsearch-7.2.0", + "package_name": "7.2.0", + "service_type": "elasticsearch", + "uri": "https://api.bonsai.io/releases/elasticsearch-7.2.0" + }, + "space": { + "path": "omc/bonsai/us-east-1/common", + "region": "aws-us-east-1", + "uri": "https://api.bonsai.io/spaces/omc/bonsai/us-east-1/common" + }, + "stats": { + "docs": 1500000, + "shards_used": 14, + "data_bytes_used": 93180912390 + }, + "access": { + "host": "third-testing-clust-1234567890.us-east-1.bonsaisearch.net", + "port": 443, + "scheme": "https" + }, + "state": "PROVISIONED" + } + ] + } + ` + + resp := &bonsai.ClustersResultList{Clusters: make([]bonsai.Cluster, 0, 2)} + err := json.Unmarshal([]byte(respStr), resp) + s.NoError(err, "unmarshal json into bonsai.ClustersResultList") + + err = json.NewEncoder(w).Encode(resp) + s.NoError(err, "encode bonsai.ClustersResultList into json") + }) + + expect := []bonsai.Cluster{ + { + Slug: "first-testing-cluste-1234567890", + Name: "first_testing_cluster", + URI: "https://api.bonsai.io/clusters/first-testing-cluste-1234567890", + Plan: bonsai.Plan{ + Slug: "sandbox-aws-us-east-1", + AvailableReleases: []bonsai.Release{}, + AvailableSpaces: []bonsai.Space{}, + URI: "https://api.bonsai.io/plans/sandbox-aws-us-east-1", + }, + Release: bonsai.Release{ + Version: "7.2.0", + Slug: "elasticsearch-7.2.0", + PackageName: "7.2.0", + ServiceType: "elasticsearch", + URI: "https://api.bonsai.io/releases/elasticsearch-7.2.0", + }, + Space: bonsai.Space{ + Path: "omc/bonsai/us-east-1/common", + Region: "aws-us-east-1", + URI: "https://api.bonsai.io/spaces/omc/bonsai/us-east-1/common", + }, + Stats: bonsai.ClusterStats{ + Docs: 0, + ShardsUsed: 0, + DataBytesUsed: 0, + }, + Access: bonsai.ClusterAccess{ + Host: "first-testing-cluste-1234567890.us-east-1.bonsaisearch.net", + Port: 443, + Scheme: "https", + }, + State: bonsai.ClusterStateProvisioned, + }, + { + Slug: "second-testing-clust-1234567890", + Name: "second_testing_cluster", + URI: "https://api.bonsai.io/clusters/second-testing-clust-1234567890", + Plan: bonsai.Plan{ + Slug: "sandbox-aws-us-east-1", + AvailableReleases: []bonsai.Release{}, + AvailableSpaces: []bonsai.Space{}, + URI: "https://api.bonsai.io/plans/sandbox-aws-us-east-1", + }, + Release: bonsai.Release{ + Version: "7.2.0", + Slug: "elasticsearch-7.2.0", + PackageName: "7.2.0", + ServiceType: "elasticsearch", + URI: "https://api.bonsai.io/releases/elasticsearch-7.2.0", + }, + Space: bonsai.Space{ + Path: "omc/bonsai/us-east-1/common", + Region: "aws-us-east-1", + URI: "https://api.bonsai.io/spaces/omc/bonsai/us-east-1/common", + }, + Stats: bonsai.ClusterStats{ + Docs: 0, + ShardsUsed: 0, + DataBytesUsed: 0, + }, + Access: bonsai.ClusterAccess{ + Host: "second-testing-clust-1234567890.us-east-1.bonsaisearch.net", + Port: 443, + Scheme: "https", + }, + State: bonsai.ClusterStateProvisioned, + }, + { + Slug: "third-testing-clust-1234567890", + Name: "third_testing_cluster", + URI: "https://api.bonsai.io/clusters/third-testing-clust-1234567890", + Plan: bonsai.Plan{ + Slug: "sandbox-aws-us-east-1", + AvailableReleases: []bonsai.Release{}, + AvailableSpaces: []bonsai.Space{}, + URI: "https://api.bonsai.io/plans/sandbox-aws-us-east-1", + }, + Release: bonsai.Release{ + Version: "7.2.0", + Slug: "elasticsearch-7.2.0", + PackageName: "7.2.0", + ServiceType: "elasticsearch", + URI: "https://api.bonsai.io/releases/elasticsearch-7.2.0", + }, + Space: bonsai.Space{ + Path: "omc/bonsai/us-east-1/common", + Region: "aws-us-east-1", + URI: "https://api.bonsai.io/spaces/omc/bonsai/us-east-1/common", + }, + Stats: bonsai.ClusterStats{ + Docs: 1500000, + ShardsUsed: 14, + DataBytesUsed: 93180912390, + }, + Access: bonsai.ClusterAccess{ + Host: "third-testing-clust-1234567890.us-east-1.bonsaisearch.net", + Port: 443, + Scheme: "https", + }, + State: bonsai.ClusterStateProvisioned, + }, + } + clusters, err := s.client.Cluster.All(context.Background()) + s.NoError(err, "successfully get all clusters") + s.Len(clusters, 3) + + s.ElementsMatch(expect, clusters, "elements in expect match elements in received clusters") + + // Comparisons on the individual struct level are much easier to debug + for i, cluster := range clusters { + s.Run(fmt.Sprintf("Cluster #%d", i), func() { + s.Equal(expect[i], cluster) + }) + } +} + +func (s *ClientTestSuite) TestClusterClient_GetBySlug() { + const targetClusterSlug = "second-testing-clust-1234567890" + + urlPath, err := url.JoinPath(bonsai.ClusterAPIBasePath, targetClusterSlug) + s.NoError(err, "successfully resolved path") + + s.serveMux.Get(urlPath, func(w http.ResponseWriter, _ *http.Request) { + respStr := fmt.Sprintf(` + { + "slug": "%s", + "name": "second_testing_cluster", + "uri": "https://api.bonsai.io/clusters/second-testing-clust-1234567890", + "plan": { + "slug": "sandbox-aws-us-east-1", + "uri": "https://api.bonsai.io/plans/sandbox-aws-us-east-1" + }, + "release": { + "version": "7.2.0", + "slug": "elasticsearch-7.2.0", + "package_name": "7.2.0", + "service_type": "elasticsearch", + "uri": "https://api.bonsai.io/releases/elasticsearch-7.2.0" + }, + "space": { + "path": "omc/bonsai/us-east-1/common", + "region": "aws-us-east-1", + "uri": "https://api.bonsai.io/spaces/omc/bonsai/us-east-1/common" + }, + "stats": { + "docs": 0, + "shards_used": 0, + "data_bytes_used": 0 + }, + "access": { + "host": "second-testing-clust-1234567890.us-east-1.bonsaisearch.net", + "port": 443, + "scheme": "https" + }, + "state": "PROVISIONED" + } + `, targetClusterSlug) + + resp := &bonsai.Cluster{} + err = json.Unmarshal([]byte(respStr), resp) + s.NoError(err, "unmarshals json into bonsai.Space") + + err = json.NewEncoder(w).Encode(resp) + s.NoError(err, "encodes bonsai.Space into json on the writer") + }) + + expect := bonsai.Cluster{ + Slug: "second-testing-clust-1234567890", + Name: "second_testing_cluster", + URI: "https://api.bonsai.io/clusters/second-testing-clust-1234567890", + Plan: bonsai.Plan{ + Slug: "sandbox-aws-us-east-1", + AvailableReleases: []bonsai.Release{}, + AvailableSpaces: []bonsai.Space{}, + URI: "https://api.bonsai.io/plans/sandbox-aws-us-east-1", + }, + Release: bonsai.Release{ + Version: "7.2.0", + Slug: "elasticsearch-7.2.0", + PackageName: "7.2.0", + ServiceType: "elasticsearch", + URI: "https://api.bonsai.io/releases/elasticsearch-7.2.0", + }, + Space: bonsai.Space{ + Path: "omc/bonsai/us-east-1/common", + Region: "aws-us-east-1", + URI: "https://api.bonsai.io/spaces/omc/bonsai/us-east-1/common", + }, + Stats: bonsai.ClusterStats{ + Docs: 0, + ShardsUsed: 0, + DataBytesUsed: 0, + }, + Access: bonsai.ClusterAccess{ + Host: "second-testing-clust-1234567890.us-east-1.bonsaisearch.net", + Port: 443, + Scheme: "https", + }, + State: bonsai.ClusterStateProvisioned, + } + + resultResp, err := s.client.Cluster.GetBySlug(context.Background(), targetClusterSlug) + s.NoError(err, "successfully get cluster by path") + + s.Equal(expect, resultResp, "elements in expect match elements in received cluster response") +} + +func (s *ClientTestSuite) TestClusterClient_Create() { + s.serveMux.Post(bonsai.ClusterAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { + respStr := ` + { + "message": "Your cluster is being provisioned.", + "monitor": "https://api.bonsai.io/clusters/test-5-x-3968320296", + "access": { + "user": "utji08pwu6", + "pass": "18v1fbey2y", + "host": "test-5-x-3968320296", + "port": 443, + "scheme": "https", + "url": "https://utji08pwu6:18v1fbey2y@test-5-x-3968320296.us-east-1.bonsaisearch.net:443" + }, + "status": 202 + } + ` + + resp := &bonsai.ClustersResultCreate{} + err := json.Unmarshal([]byte(respStr), resp) + s.NoError(err, "unmarshals json into bonsai.ClustersResultCreate") + + err = json.NewEncoder(w).Encode(resp) + s.NoError(err, "encodes bonsai.ClustersResultCreate into json on the writer") + }) + + expect := bonsai.ClustersResultCreate{ + Message: "Your cluster is being provisioned.", + Monitor: "https://api.bonsai.io/clusters/test-5-x-3968320296", + Access: bonsai.ClusterAccess{ + Host: "test-5-x-3968320296", + Port: 443, + Scheme: "https", + Username: "utji08pwu6", + Password: "18v1fbey2y", + URL: "https://utji08pwu6:18v1fbey2y@test-5-x-3968320296.us-east-1.bonsaisearch.net:443", + }, + } + + resultResp, err := s.client.Cluster.Create(context.Background(), bonsai.ClusterCreateOpts{ + Name: "test-5-x-3968320296", + Plan: "sandbox-aws-us-east-1", + Space: "omc/bonsai/us-east-1/common", + Release: "elasticsearch-7.2.0", + }) + s.NoError(err, "successfully execute create cluster request") + + s.Equal(expect, resultResp, "elements in expect match elements in received cluster create response") +} + +func (s *ClientTestSuite) TestClusterClient_Update() { + s.serveMux.Put(bonsai.ClusterAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { + respStr := ` + { + "message": "Your cluster is being updated.", + "monitor": "https://api.bonsai.io/clusters/test-5-x-3968320296", + "status": 202 + } + ` + + resp := &bonsai.ClustersResultUpdate{} + err := json.Unmarshal([]byte(respStr), resp) + s.NoError(err, "unmarshals json into bonsai.ClustersResultUpdate") + + err = json.NewEncoder(w).Encode(resp) + s.NoError(err, "encodes bonsai.ClustersResultUpdate into json on the writer") + }) + + expect := bonsai.ClustersResultUpdate{ + Message: "Your cluster is being updated.", + Monitor: "https://api.bonsai.io/clusters/test-5-x-3968320296", + } + + resultResp, err := s.client.Cluster.Update(context.Background(), bonsai.ClusterUpdateOpts{ + Name: "test-5-x-3968320296", + Plan: "sandbox-aws-us-east-2", + }) + s.NoError(err, "successfully execute create cluster request") + + s.Equal(expect, resultResp, "items in expect match items in received cluster update response") +} + +func (s *ClientTestSuite) TestClusterClient_Delete() { + const targetClusterSlug = "second-testing-clust-1234567890" + + reqPath, err := url.JoinPath(bonsai.ClusterAPIBasePath, targetClusterSlug) + s.NoError(err, "successfully resolved path") + + s.serveMux.Delete(reqPath, func(w http.ResponseWriter, _ *http.Request) { + respStr := fmt.Sprintf(` + { + "message": "Your cluster is being deprovisioned.", + "monitor": "%s", + "status": 202 + } + `, targetClusterSlug) + + resp := &bonsai.ClustersResultDestroy{} + err = json.Unmarshal([]byte(respStr), resp) + s.NoError(err, "unmarshals json into bonsai.ClustersResultDestroy") + + err = json.NewEncoder(w).Encode(resp) + s.NoError(err, "encodes bonsai.ClustersResultDestroy into json on the writer") + }) + + expect := bonsai.ClustersResultDestroy{ + Message: "Your cluster is being deprovisioned.", + Monitor: targetClusterSlug, + } + + resultResp, err := s.client.Cluster.Destroy(context.Background(), targetClusterSlug) + s.NoError(err, "successfully execute create cluster request") + + s.Equal(expect, resultResp, "items in expect match items in received cluster update response") +} diff --git a/bonsai/plan.go b/bonsai/plan.go index a1e08b3..2f2cba0 100644 --- a/bonsai/plan.go +++ b/bonsai/plan.go @@ -19,28 +19,34 @@ const ( // // It differs from Plan namely in that the AvailableReleases returned is // a list of string, not Release. +// +// Indeed, it exists to resolve differences between index list response and +// other response structures. type planAllResponse struct { // Represents a machine-readable name for the plan. - Slug string `json:"slug"` + Slug string `json:"slug,omitempty"` // Represents the human-readable name of the plan. - Name string `json:"name"` + Name string `json:"name,omitempty"` // Represents the plan price in cents. - PriceInCents int64 `json:"price_in_cents"` + PriceInCents int64 `json:"price_in_cents,omitempty"` // Represents the plan billing interval in months. - BillingIntervalInMonths int `json:"billing_interval_in_months"` + BillingIntervalInMonths int `json:"billing_interval_in_months,omitempty"` // Indicates whether the plan is single-tenant or not. A value of false // indicates the Cluster will share hardware with other Clusters. Single // tenant environments can be reached via the public Internet. - SingleTenant bool `json:"single_tenant"` + SingleTenant bool `json:"single_tenant,omitempty"` // Indicates whether the plan is on a publicly addressable network. // Private plans provide environments that cannot be reached by the public // Internet. A VPC connection will be needed to communicate with a private // cluster. - PrivateNetwork bool `json:"private_network"` + PrivateNetwork bool `json:"private_network,omitempty"` // A collection of search release slugs available for the plan. Additional // information about a release can be retrieved from the Releases API. AvailableReleases []string `json:"available_releases"` AvailableSpaces []string `json:"available_spaces"` + + // A URI to retrieve more information about this Plan. + URI string `json:"uri,omitempty"` } type planAllResponseList struct { @@ -68,6 +74,7 @@ func (c *planAllResponseConverter) Convert(source planAllResponse) Plan { for i, space := range source.AvailableSpaces { plan.AvailableSpaces[i] = Space{Path: space} } + plan.URI = source.URI return plan } @@ -90,24 +97,27 @@ type Plan struct { // Represents a machine-readable name for the plan. Slug string `json:"slug"` // Represents the human-readable name of the plan. - Name string `json:"name"` + Name string `json:"name,omitempty"` // Represents the plan price in cents. - PriceInCents int64 `json:"price_in_cents"` + PriceInCents int64 `json:"price_in_cents,omitempty"` // Represents the plan billing interval in months. - BillingIntervalInMonths int `json:"billing_interval_months"` + BillingIntervalInMonths int `json:"billing_interval_months,omitempty"` // Indicates whether the plan is single-tenant or not. A value of false // indicates the Cluster will share hardware with other Clusters. Single // tenant environments can be reached via the public Internet. - SingleTenant bool `json:"single_tenant"` + SingleTenant bool `json:"single_tenant,omitempty"` // Indicates whether the plan is on a publicly addressable network. // Private plans provide environments that cannot be reached by the public // Internet. A VPC connection will be needed to communicate with a private // cluster. - PrivateNetwork bool `json:"private_network"` + PrivateNetwork bool `json:"private_network,omitempty"` // A collection of search release slugs available for the plan. Additional // information about a release can be retrieved from the Releases API. AvailableReleases []Release `json:"available_releases"` AvailableSpaces []Space `json:"available_spaces"` + + // A URI to retrieve more information about this Plan. + URI string `json:"uri,omitempty"` } func (p *Plan) UnmarshalJSON(data []byte) error { diff --git a/bonsai/release.go b/bonsai/release.go index 89d296a..7089087 100644 --- a/bonsai/release.go +++ b/bonsai/release.go @@ -15,16 +15,21 @@ const ReleaseAPIBasePath = "/releases" // Release is a placeholder for now. type Release struct { Name string `json:"name,omitempty"` - Slug string `json:"slug"` + Slug string `json:"slug,omitempty"` ServiceType string `json:"service_type,omitempty"` Version string `json:"version,omitempty"` MultiTenant bool `json:"multitenant,omitempty"` + + // A URI to retrieve more information about this Release. + URI string `json:"uri,omitempty"` + // PackageName is the package name of the release. + PackageName string `json:"package_name,omitempty"` } // ReleasesResultList is a wrapper around a slice of // Releases for json unmarshaling. type ReleasesResultList struct { - Releases []Release `json:"releases"` + Releases []Release `json:"releases,omitempty"` } // ReleaseClient is a client for the Releases API. diff --git a/bonsai/space.go b/bonsai/space.go index 8d2cf2c..1f0e82f 100644 --- a/bonsai/space.go +++ b/bonsai/space.go @@ -26,13 +26,18 @@ type CloudProvider struct { type Space struct { Path string `json:"path"` PrivateNetwork bool `json:"private_network"` - Cloud CloudProvider `json:"cloud"` + Cloud CloudProvider `json:"cloud,omitempty"` + + // The geographic region in which the cluster is running. + Region string `json:"region,omitempty"` + // A URI to retrieve more information about this Release. + URI string `json:"uri,omitempty"` } // SpacesResultList is a wrapper around a slice of // Spaces for json unmarshaling. type SpacesResultList struct { - Spaces []Space `json:"spaces"` + Spaces []Space `json:"spaces,omitempty"` } // SpaceClient is a client for the Spaces API. diff --git a/go.mod b/go.mod index bae3410..901e977 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/omc/bonsai-api-go/v1 go 1.22 require ( + github.com/go-chi/chi/v5 v5.0.12 + github.com/google/go-querystring v1.1.0 github.com/hetznercloud/hcloud-go/v2 v2.7.2 github.com/stretchr/testify v1.9.0 golang.org/x/net v0.24.0 diff --git a/go.sum b/go.sum index 35f771d..56ffe21 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,13 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= +github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/hetznercloud/hcloud-go/v2 v2.7.2 h1:UlE7n1GQZacCfyjv9tDVUN7HZfOXErPIfM/M039u9A0= github.com/hetznercloud/hcloud-go/v2 v2.7.2/go.mod h1:49tIV+pXRJTUC7fbFZ03s45LKqSQdOPP5y91eOnJo/k= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -34,6 +39,7 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 0370eca38bb716480b3977b05a248f07d657060a Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Fri, 3 May 2024 14:33:00 -0500 Subject: [PATCH 15/30] Switch to chi for the router for all test suites --- bonsai/client_impl_test.go | 7 ++++--- bonsai/client_test.go | 4 ++-- bonsai/plan_test.go | 4 ++-- bonsai/release_test.go | 4 ++-- bonsai/space_test.go | 4 ++-- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/bonsai/client_impl_test.go b/bonsai/client_impl_test.go index 33edbe9..98f6c11 100644 --- a/bonsai/client_impl_test.go +++ b/bonsai/client_impl_test.go @@ -9,6 +9,7 @@ import ( "net/http/httptest" "testing" + "github.com/go-chi/chi/v5" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "golang.org/x/time/rate" @@ -25,7 +26,7 @@ type ClientImplTestSuite struct { suite.Suite // serveMux is the request multiplexer used for tests - serveMux *http.ServeMux + serveMux *chi.Mux // server is the testing server on some local port server *httptest.Server // client allows each test to have a reachable *Client for testing @@ -34,7 +35,7 @@ type ClientImplTestSuite struct { func (s *ClientImplTestSuite) SetupSuite() { // Configure http client and other miscellany - s.serveMux = http.NewServeMux() + s.serveMux = chi.NewRouter() s.server = httptest.NewServer(s.serveMux) token, err := NewToken("TestToken") if err != nil { @@ -90,7 +91,7 @@ func (s *ClientImplTestSuite) TestClientAll() { expectedPage = 1 ) - s.serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + s.serveMux.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set(HTTPHeaderContentType, HTTPContentTypeJSON) respBody, _ := NewResponse() diff --git a/bonsai/client_test.go b/bonsai/client_test.go index 8b7260e..d6274fc 100644 --- a/bonsai/client_test.go +++ b/bonsai/client_test.go @@ -99,7 +99,7 @@ func (s *ClientTestSuite) TestClientResponseError() { const p = "/clusters/doesnotexist-1234" // Configure Servemux to serve the error response at this path - s.serveMux.HandleFunc(p, func(w http.ResponseWriter, _ *http.Request) { + s.serveMux.Get(p, func(w http.ResponseWriter, _ *http.Request) { var err error w.Header().Set("Content-Type", bonsai.HTTPContentTypeJSON) @@ -125,7 +125,7 @@ func (s *ClientTestSuite) TestClientResponseError() { } func (s *ClientTestSuite) TestClientResponseWithPagination() { - s.serveMux.HandleFunc("/clusters", func(w http.ResponseWriter, _ *http.Request) { + s.serveMux.Get("/clusters", func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("RateLimit-Limit", "1000") w.Header().Set("RateLimit-Remaining", "999") diff --git a/bonsai/plan_test.go b/bonsai/plan_test.go index 8237989..e2d9feb 100644 --- a/bonsai/plan_test.go +++ b/bonsai/plan_test.go @@ -10,7 +10,7 @@ import ( ) func (s *ClientTestSuite) TestPlanClient_All() { - s.serveMux.HandleFunc(bonsai.PlanAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { + s.serveMux.Get(bonsai.PlanAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { respStr := ` { "plans": [ @@ -145,7 +145,7 @@ func (s *ClientTestSuite) TestPlanClient_GetByPath() { } `, targetPlanPath) - s.serveMux.HandleFunc(urlPath, func(w http.ResponseWriter, _ *http.Request) { + s.serveMux.Get(urlPath, func(w http.ResponseWriter, _ *http.Request) { _, err = w.Write([]byte(respStr)) s.NoError(err, "wrote response string to writer") }) diff --git a/bonsai/release_test.go b/bonsai/release_test.go index 65b618d..da471e9 100644 --- a/bonsai/release_test.go +++ b/bonsai/release_test.go @@ -11,7 +11,7 @@ import ( ) func (s *ClientTestSuite) TestReleaseClient_All() { - s.serveMux.HandleFunc(bonsai.ReleaseAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { + s.serveMux.Get(bonsai.ReleaseAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { respStr := ` { "releases": [ @@ -84,7 +84,7 @@ func (s *ClientTestSuite) TestReleaseClient_GetBySlug() { urlPath, err := url.JoinPath(bonsai.ReleaseAPIBasePath, targetReleaseSlug) s.NoError(err, "successfully resolved path") - s.serveMux.HandleFunc(urlPath, func(w http.ResponseWriter, _ *http.Request) { + s.serveMux.Get(urlPath, func(w http.ResponseWriter, _ *http.Request) { respStr := fmt.Sprintf(` { "name": "Elasticsearch 7.2.0", diff --git a/bonsai/space_test.go b/bonsai/space_test.go index e02468f..d3f59c1 100644 --- a/bonsai/space_test.go +++ b/bonsai/space_test.go @@ -11,7 +11,7 @@ import ( ) func (s *ClientTestSuite) TestSpaceClient_All() { - s.serveMux.HandleFunc(bonsai.SpaceAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { + s.serveMux.Get(bonsai.SpaceAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { respStr := ` { "spaces": [ @@ -64,7 +64,7 @@ func (s *ClientTestSuite) TestSpaceClient_GetByPath() { urlPath, err := url.JoinPath(bonsai.SpaceAPIBasePath, targetSpacePath) s.NoError(err, "successfully create url path") - s.serveMux.HandleFunc(urlPath, func(w http.ResponseWriter, _ *http.Request) { + s.serveMux.Get(urlPath, func(w http.ResponseWriter, _ *http.Request) { respStr := fmt.Sprintf(` { "path": "%s", From 81908a046d2e785e45af180d0c076eba5978a94d Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Fri, 3 May 2024 14:33:50 -0500 Subject: [PATCH 16/30] Remove old test vestige --- bonsai/client_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/bonsai/client_test.go b/bonsai/client_test.go index d6274fc..148c083 100644 --- a/bonsai/client_test.go +++ b/bonsai/client_test.go @@ -127,9 +127,6 @@ func (s *ClientTestSuite) TestClientResponseError() { func (s *ClientTestSuite) TestClientResponseWithPagination() { s.serveMux.Get("/clusters", func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") - w.Header().Set("RateLimit-Limit", "1000") - w.Header().Set("RateLimit-Remaining", "999") - w.Header().Set("RateLimit-Reset", "1511954577") w.WriteHeader(http.StatusOK) _, err := fmt.Fprint(w, ` { From db06a852081944fb937e6c43bc86d53563580206 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Fri, 3 May 2024 14:53:02 -0500 Subject: [PATCH 17/30] Suppress unmarshal errors in the unexpected event that API doesn't return an error descriptor in the body --- bonsai/client.go | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/bonsai/client.go b/bonsai/client.go index af56a33..d7aa24c 100644 --- a/bonsai/client.go +++ b/bonsai/client.go @@ -252,6 +252,10 @@ type Response struct { PaginatedResponse `json:"pagination"` } +func (r *Response) isJSON() bool { + return r.Header.Get("Content-Type") == HTTPContentTypeJSON +} + // WithHTTPResponse assigns an *http.Response to a *Response item // and reads its response body into the *Response. func (r *Response) WithHTTPResponse(httpResp *http.Response) error { @@ -451,24 +455,32 @@ func (c *Client) doRequest(ctx context.Context, req *http.Request, reqBuf *bytes return resp, fmt.Errorf("setting http response: %w", err) } - // Extract the pagination details - if httpResp.Header.Get("Content-Type") == HTTPContentTypeJSON { - err = json.Unmarshal(resp.BodyBuf.Bytes(), &resp) - if err != nil { - return resp, fmt.Errorf("error unmarshaling response body for pagination: %w", err) - } - } - // All error reposes should come with a JSON response per the Error handling // section @ https://bonsai.io/docs/introduction-to-the-api. + // + // That said, in the scenario that an error *isn't* returned as a JSON body + // response, it would be jarring to receive a message about an internal + // unmarshaling attempt, rather than to receive the HTTP Status Error if resp.StatusCode >= http.StatusBadRequest { - respErr := ResponseError{} - if err = json.Unmarshal(resp.BodyBuf.Bytes(), &respErr); err != nil { - return resp, fmt.Errorf("unmarshaling error response: %w", err) + respErr := ResponseError{Status: resp.StatusCode} + + if ok := resp.isJSON(); ok { + // Suppress unmarshaling errors in the event that the response didn't + // contain a message. + _ = json.Unmarshal(resp.BodyBuf.Bytes(), &respErr) } + return resp, respErr } + // Extract the pagination details + if resp.isJSON() { + err = json.Unmarshal(resp.BodyBuf.Bytes(), &resp) + if err != nil { + return resp, fmt.Errorf("error unmarshaling response body for pagination: %w", err) + } + } + return resp, err } From 0f70ce6545b1ec08077927a34b7669434e160d0e Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Fri, 3 May 2024 15:40:16 -0500 Subject: [PATCH 18/30] All provisioning calls are rate limited per config. Default is 5 requests/min per the docs --- bonsai/cluster.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/bonsai/cluster.go b/bonsai/cluster.go index 2dc4e1e..9df6161 100644 --- a/bonsai/cluster.go +++ b/bonsai/cluster.go @@ -136,6 +136,24 @@ type ClusterClient struct { *Client } +// Do performs an HTTP request against the API, with any required +// Cluster-specific configuration/limitations - for example, rate limiting. +func (c *ClusterClient) Do(ctx context.Context, req *http.Request) (*Response, error) { + // Allow non-provisioning Cluster endpoint requests to continue + if req.Method != http.MethodPost { + return c.Client.Do(ctx, req) + } + + // Limit provision requests + err := c.rateLimiter.provisionLimiter.Wait(ctx) + if err != nil { + // Context canceled, timed-out, burst issue, or other rate limit issue; + // let the callers handle it. + return nil, fmt.Errorf("failed while awaiting execution per rate-limit: %w", err) + } + return c.Client.Do(ctx, req) +} + type ClusterAllOpts struct { // Optional. A query string for filtering matching clusters. // This currently works on name. From e9da3edde630cd917f177dcc1e7288142fb8915a Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Fri, 3 May 2024 15:44:09 -0500 Subject: [PATCH 19/30] Update project title in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a998d81..6e7a7ed 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# bonsai-go: Bonsai Cloud Go API Client +# bonsai-api-go: Bonsai Cloud Go API Client ## Installation From c0aad80d9baaed284a10045673ed8294ad232e23 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Fri, 3 May 2024 15:50:18 -0500 Subject: [PATCH 20/30] Patch golang/protobuf; addressing CVE-2024-24786 --- go.mod | 10 +++++----- go.sum | 10 ++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 901e977..cca58af 100644 --- a/go.mod +++ b/go.mod @@ -13,15 +13,15 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.19.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.53.0 // indirect + github.com/prometheus/procfs v0.14.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/protobuf v1.32.0 // indirect + google.golang.org/protobuf v1.34.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 56ffe21..aa12828 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= @@ -23,10 +25,16 @@ github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7km github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= +github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= +github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= @@ -42,6 +50,8 @@ golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= +google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 966083cbb162e2cb352ab1713adeb14acf05f919 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Fri, 3 May 2024 16:23:20 -0500 Subject: [PATCH 21/30] Add link to documentation (lve when public; self-hostable) --- README.md | 21 +++++++++++++++++++++ doc/assets/bonsai.png | Bin 0 -> 2597 bytes 2 files changed, 21 insertions(+) create mode 100644 doc/assets/bonsai.png diff --git a/README.md b/README.md index 6e7a7ed..b98d65c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,26 @@ # bonsai-api-go: Bonsai Cloud Go API Client +![Bonsai | Fully Managed Elasticsearch & OpenSearch](doc/assets/bonsai.png) + +This is the Go API Client for [Bonsai Cloud](https://bonsai.io/) - The only +managed Elasticsearch, OpenSearch, and SolrCloud platform that provides the +support of a search engineering team, but at a fraction of the cost! + +## Documentation + +Full documentation is available on godoc at https://pkg.go.dev/github.com/omc/bonsai-api-go/v1/bonsai/ + +### Self-hosting the docs + +1. Install [godoc](https://pkg.go.dev/golang.org/x/tools/cmd/godoc) + ```shell + go install golang.org/x/tools/cmd/godoc@latest + ``` +2. Run `godoc` from the project's root directory: + ```shell + godoc -http:6060 + ``` + ## Installation ```shell diff --git a/doc/assets/bonsai.png b/doc/assets/bonsai.png new file mode 100644 index 0000000000000000000000000000000000000000..8cff9f54a3ca72e22b1f794f08cc5db1b74f1e70 GIT binary patch literal 2597 zcmV+=3flFFP)ZMo000T>Nkl z?Qz>U5QdS={Xbm=mMe%@ft3nqR}ia$*adJLj|z}of#eEeRbaUSDHWKhAnf37ka>|q z7)#v62LYM;%y=drvIqirfd#PGeW#RCMO99fQu^maDK%EV(2V=j#o%Eboe{e0fFOZR+iOeJV{X2=OVZQVV+tfgj={RqXExBG-)Za6u5_ zvml6E!3rnwo>Ans=^zNgk-!nUAP7f_2we~cfCya>27m}%5C(t z27m}%5b|S9EKyA@xd>elGGjYm-zlZE%lWlZO8}0U$ybgaIHz7lZ-ez3Yx}H~Iqn87|9@_XM-p;dnrw5k79` z>!y$DEvj;Ch2!~vd-eOuXj5Cq8P6#H4!fbrcqP2h8On0#of@nDzoGu4zlYT~CRRCL z3yXA!x^-UsjQ3f&^daW-+DAc8?)9(oD6fD#l>g^nLHr`V%eYAE>tpgoO z>R;mXe}%2F3i6p43F9?ZI13Ek;n0|uN~zMLeEZN1`rz8pxZqvPKsqIML<+hs@3-^y zV}vrBx<3#7-4=S#Er{hYeU%)M#ev%D7hZk3sLHRzy1T}idnYWR<7IA8h)@w{$c60! z1JD8@k4*!idbTnQx@(Uwwm@itKPY{GN zXv42Pid+$&l_J)m=@;3AU*iZprrC1|bI%Eg{3%gfJN@jfPw-6ryAU{aBoEC51G#JY zmpu-!HoffgI5G(_fQBNcUH64crQml`iQJ%{y>u-9l^!S(3Ebw&#WI1po%CAV|*&zuiq z26Y)6V)2*^ktjJ|i{)0IYjPWMmJ;S?W5DXyG008mBbb6zl9-GK7%`8Oa*LHe zVGdNRfgv#WjIZmu-`!YOLCw!CP9)6Vef9&+Q?J#19cGNv!1F7P4P(c%)H75t1(%N2 z;QPNH80k$OI+tU_5CSRAY4;f$+exII6L81LJEo6s zi>f@qyFJ@=#xdWQ^ahrW0YSnKSmL}=2mdfVZDQ%`HHH%jhoG{UT}ZLe3g}#ud2%W>)A85l%<*O8boRbKX64qw1M(=K2n< z?G}*R#Ltfc`UQ2FnRJcb=Vlx=;GH5KM{vXxqN6`BJ{!7GLtL1k4zw{$%$yxte4+M> zqnXit33j`ONfTmS$l3f!-Kd_#n0aDeZb?n*qdO8q5PHbxzU7&T3#VQeZ#BPuU)QaD z=}qXzQXw6r8mzJd?MbziM)3R2$+>AzNQ4=|2aee~!p#2SbAd^4Lxe8)K;B@r7~=xa zkD@9Up|1D}hY_I*&LBFC>qe)!r|^Qi+gCZ=-+pUIrWV1M8N+F0A0`H#p5dc~7_+fL zbd9Zec7}0l0BaCyud!kW9@UxVMHrk+_6e|0RS;4=1cGT!BLz^_c*qSp=5oLWC{^0TEeaxZ2(X2-S6o<;7q( z{zT|PFfg;vA~+U}A$Ioi00YEP%(^iVp$kF51)9@Hv-{FP?D~rC;1Qt>%ki2J9UPI(5CFqBm6tNf zGxRK}HmG^Fn>%7wp1ONY95EVSf*I_6+V!8`SXqol54rYB<#9fZXaN+9d#*9;t3=z7J6l~mmf#A8B zT@OV8rdkQfQ(^ak2wlhm2>v2LY(AG8ct?i_orfINLC2lp+f~mqdNQF0x)i}N69>_GBErd@;+rsI5QBH0LMNwBBJN~z#1tY5Dd#&x z=zeep<{`)NJE4BC7olTDrTQpSk2yGEy78IS@w=Rim=Z144{}`}I2NP!kEE%RJRw_| zea+|#&T&WJ&nYWT8C&ewTj1o5qbBGV{;X@A&T7fXMS<8OO=8 z`4fm_!8ChG5j*rqj+|)JQSBU!y26pl-f%!D_XNug?ro`~aG(f*4hn%VOzluhiV|+g zaw#?8oVazpcR)qt8_IW#&_{F14aczvpWs?-2DUM*6jQI-}B&&8R2d}BZR&U__e{sS_IZMRXMt=$aJPjDD&= zcV1NGrjrkY1tC2S z?|SqXFm-+E8{}a-U*CBIx}Z{H0=qz&ohJckOo|u zuX*e%ZxuF;ra04|c&sgJbhu}SBfvxDGx5_7?ecmrZPfpDanlj!tiiKL?}xKzyPu2# z>{vIgz7lP~M*W|89GCsd#fG!*O|0@VLvNWnyf=g6UZEd&;FuwpCbt=nS^m1O4Yqyd ziV>>F@dNg Date: Fri, 3 May 2024 16:23:20 -0500 Subject: [PATCH 22/30] Add link to documentation (lve when public; self-hostable) --- README.md | 21 +++++++++++++++++++++ doc/assets/bonsai.png | Bin 0 -> 2597 bytes 2 files changed, 21 insertions(+) create mode 100644 doc/assets/bonsai.png diff --git a/README.md b/README.md index 6e7a7ed..b98d65c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,26 @@ # bonsai-api-go: Bonsai Cloud Go API Client +![Bonsai | Fully Managed Elasticsearch & OpenSearch](doc/assets/bonsai.png) + +This is the Go API Client for [Bonsai Cloud](https://bonsai.io/) - The only +managed Elasticsearch, OpenSearch, and SolrCloud platform that provides the +support of a search engineering team, but at a fraction of the cost! + +## Documentation + +Full documentation is available on godoc at https://pkg.go.dev/github.com/omc/bonsai-api-go/v1/bonsai/ + +### Self-hosting the docs + +1. Install [godoc](https://pkg.go.dev/golang.org/x/tools/cmd/godoc) + ```shell + go install golang.org/x/tools/cmd/godoc@latest + ``` +2. Run `godoc` from the project's root directory: + ```shell + godoc -http:6060 + ``` + ## Installation ```shell diff --git a/doc/assets/bonsai.png b/doc/assets/bonsai.png new file mode 100644 index 0000000000000000000000000000000000000000..8cff9f54a3ca72e22b1f794f08cc5db1b74f1e70 GIT binary patch literal 2597 zcmV+=3flFFP)ZMo000T>Nkl z?Qz>U5QdS={Xbm=mMe%@ft3nqR}ia$*adJLj|z}of#eEeRbaUSDHWKhAnf37ka>|q z7)#v62LYM;%y=drvIqirfd#PGeW#RCMO99fQu^maDK%EV(2V=j#o%Eboe{e0fFOZR+iOeJV{X2=OVZQVV+tfgj={RqXExBG-)Za6u5_ zvml6E!3rnwo>Ans=^zNgk-!nUAP7f_2we~cfCya>27m}%5C(t z27m}%5b|S9EKyA@xd>elGGjYm-zlZE%lWlZO8}0U$ybgaIHz7lZ-ez3Yx}H~Iqn87|9@_XM-p;dnrw5k79` z>!y$DEvj;Ch2!~vd-eOuXj5Cq8P6#H4!fbrcqP2h8On0#of@nDzoGu4zlYT~CRRCL z3yXA!x^-UsjQ3f&^daW-+DAc8?)9(oD6fD#l>g^nLHr`V%eYAE>tpgoO z>R;mXe}%2F3i6p43F9?ZI13Ek;n0|uN~zMLeEZN1`rz8pxZqvPKsqIML<+hs@3-^y zV}vrBx<3#7-4=S#Er{hYeU%)M#ev%D7hZk3sLHRzy1T}idnYWR<7IA8h)@w{$c60! z1JD8@k4*!idbTnQx@(Uwwm@itKPY{GN zXv42Pid+$&l_J)m=@;3AU*iZprrC1|bI%Eg{3%gfJN@jfPw-6ryAU{aBoEC51G#JY zmpu-!HoffgI5G(_fQBNcUH64crQml`iQJ%{y>u-9l^!S(3Ebw&#WI1po%CAV|*&zuiq z26Y)6V)2*^ktjJ|i{)0IYjPWMmJ;S?W5DXyG008mBbb6zl9-GK7%`8Oa*LHe zVGdNRfgv#WjIZmu-`!YOLCw!CP9)6Vef9&+Q?J#19cGNv!1F7P4P(c%)H75t1(%N2 z;QPNH80k$OI+tU_5CSRAY4;f$+exII6L81LJEo6s zi>f@qyFJ@=#xdWQ^ahrW0YSnKSmL}=2mdfVZDQ%`HHH%jhoG{UT}ZLe3g}#ud2%W>)A85l%<*O8boRbKX64qw1M(=K2n< z?G}*R#Ltfc`UQ2FnRJcb=Vlx=;GH5KM{vXxqN6`BJ{!7GLtL1k4zw{$%$yxte4+M> zqnXit33j`ONfTmS$l3f!-Kd_#n0aDeZb?n*qdO8q5PHbxzU7&T3#VQeZ#BPuU)QaD z=}qXzQXw6r8mzJd?MbziM)3R2$+>AzNQ4=|2aee~!p#2SbAd^4Lxe8)K;B@r7~=xa zkD@9Up|1D}hY_I*&LBFC>qe)!r|^Qi+gCZ=-+pUIrWV1M8N+F0A0`H#p5dc~7_+fL zbd9Zec7}0l0BaCyud!kW9@UxVMHrk+_6e|0RS;4=1cGT!BLz^_c*qSp=5oLWC{^0TEeaxZ2(X2-S6o<;7q( z{zT|PFfg;vA~+U}A$Ioi00YEP%(^iVp$kF51)9@Hv-{FP?D~rC;1Qt>%ki2J9UPI(5CFqBm6tNf zGxRK}HmG^Fn>%7wp1ONY95EVSf*I_6+V!8`SXqol54rYB<#9fZXaN+9d#*9;t3=z7J6l~mmf#A8B zT@OV8rdkQfQ(^ak2wlhm2>v2LY(AG8ct?i_orfINLC2lp+f~mqdNQF0x)i}N69>_GBErd@;+rsI5QBH0LMNwBBJN~z#1tY5Dd#&x z=zeep<{`)NJE4BC7olTDrTQpSk2yGEy78IS@w=Rim=Z144{}`}I2NP!kEE%RJRw_| zea+|#&T&WJ&nYWT8C&ewTj1o5qbBGV{;X@A&T7fXMS<8OO=8 z`4fm_!8ChG5j*rqj+|)JQSBU!y26pl-f%!D_XNug?ro`~aG(f*4hn%VOzluhiV|+g zaw#?8oVazpcR)qt8_IW#&_{F14aczvpWs?-2DUM*6jQI-}B&&8R2d}BZR&U__e{sS_IZMRXMt=$aJPjDD&= zcV1NGrjrkY1tC2S z?|SqXFm-+E8{}a-U*CBIx}Z{H0=qz&ohJckOo|u zuX*e%ZxuF;ra04|c&sgJbhu}SBfvxDGx5_7?ecmrZPfpDanlj!tiiKL?}xKzyPu2# z>{vIgz7lP~M*W|89GCsd#fG!*O|0@VLvNWnyf=g6UZEd&;FuwpCbt=nS^m1O4Yqyd ziV>>F@dNg Date: Fri, 3 May 2024 20:00:38 -0500 Subject: [PATCH 23/30] Add integration tests, recordings, with VCR. Update Authentication, fix Clusters.Update. --- bonsai/bonsai_test.go | 13 + bonsai/client.go | 121 +- bonsai/client_impl_test.go | 18 +- bonsai/client_test.go | 187 +- bonsai/cluster.go | 11 +- bonsai/cluster_test.go | 70 +- ...stClientVCRTestSuite-TestClusterClient-All | 41 + ...lientVCRTestSuite-TestClusterClient-Create | 12 + ...lientVCRTestSuite-TestClusterClient-Delete | 4 + ...ntVCRTestSuite-TestClusterClient-GetBySlug | 26 + ...lientVCRTestSuite-TestClusterClient-Update | 4 + .../TestClientVCRTestSuite-TestPlanClient-All | 1905 +++++++++++++++++ ...lientVCRTestSuite-TestPlanClient-GetByPath | 94 + ...stClientVCRTestSuite-TestReleaseClient-All | 90 + ...ntVCRTestSuite-TestReleaseClient-GetByPath | 8 + ...TestClientVCRTestSuite-TestSpaceClient-All | 74 + ...ientVCRTestSuite-TestSpaceClient-GetByPath | 8 + .../golden/TestClusterClient-All.yaml | 3 + .../golden/TestClusterClient-Create.yaml | 3 + .../golden/TestClusterClient-Delete.yaml | 3 + .../golden/TestClusterClient-GetBySlug.yaml | 3 + .../golden/TestClusterClient-Update.yaml | 3 + .../fixtures/golden/TestPlanClient-All.yaml | 3 + .../golden/TestPlanClient-GetByPath.yaml | 3 + .../golden/TestReleaseClient-All.yaml | 3 + .../golden/TestReleaseClient-GetByPath.yaml | 3 + .../fixtures/golden/TestSpaceClient-All.yaml | 3 + .../golden/TestSpaceClient-GetByPath.yaml | 3 + bonsai/plan_test.go | 31 +- bonsai/release_test.go | 25 +- bonsai/space_test.go | 23 +- bonsai/vcr.go | 70 + go.mod | 2 + go.sum | 12 +- 34 files changed, 2793 insertions(+), 89 deletions(-) create mode 100644 bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-All create mode 100644 bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Create create mode 100644 bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Delete create mode 100644 bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-GetBySlug create mode 100644 bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Update create mode 100644 bonsai/fixtures/golden/TestClientVCRTestSuite-TestPlanClient-All create mode 100644 bonsai/fixtures/golden/TestClientVCRTestSuite-TestPlanClient-GetByPath create mode 100644 bonsai/fixtures/golden/TestClientVCRTestSuite-TestReleaseClient-All create mode 100644 bonsai/fixtures/golden/TestClientVCRTestSuite-TestReleaseClient-GetByPath create mode 100644 bonsai/fixtures/golden/TestClientVCRTestSuite-TestSpaceClient-All create mode 100644 bonsai/fixtures/golden/TestClientVCRTestSuite-TestSpaceClient-GetByPath create mode 100644 bonsai/fixtures/golden/TestClusterClient-All.yaml create mode 100644 bonsai/fixtures/golden/TestClusterClient-Create.yaml create mode 100644 bonsai/fixtures/golden/TestClusterClient-Delete.yaml create mode 100644 bonsai/fixtures/golden/TestClusterClient-GetBySlug.yaml create mode 100644 bonsai/fixtures/golden/TestClusterClient-Update.yaml create mode 100644 bonsai/fixtures/golden/TestPlanClient-All.yaml create mode 100644 bonsai/fixtures/golden/TestPlanClient-GetByPath.yaml create mode 100644 bonsai/fixtures/golden/TestReleaseClient-All.yaml create mode 100644 bonsai/fixtures/golden/TestReleaseClient-GetByPath.yaml create mode 100644 bonsai/fixtures/golden/TestSpaceClient-All.yaml create mode 100644 bonsai/fixtures/golden/TestSpaceClient-GetByPath.yaml create mode 100644 bonsai/vcr.go diff --git a/bonsai/bonsai_test.go b/bonsai/bonsai_test.go index c777f4c..0d2d6c5 100644 --- a/bonsai/bonsai_test.go +++ b/bonsai/bonsai_test.go @@ -5,6 +5,9 @@ import ( "log" "log/slog" "os" + "path/filepath" + + "github.com/omc/bonsai-api-go/v1/bonsai" ) func init() { @@ -33,3 +36,13 @@ func initLogger() { logger := slog.New(logHandler) slog.SetDefault(logger) } + +func assertGolden(s *ClientVCRTestSuite, expected any) { + s.T().Helper() + bonsai.AssertGolden( + s.T(), + filepath.Join("fixtures/golden/", s.normalize(s.T().Name())), + s.update(s.T().Name()), + expected, + ) +} diff --git a/bonsai/client.go b/bonsai/client.go index d7aa24c..008d63b 100644 --- a/bonsai/client.go +++ b/bonsai/client.go @@ -10,6 +10,7 @@ import ( "net/http" "net/url" "reflect" + "regexp" "strconv" "strings" "time" @@ -24,7 +25,7 @@ const ( Version = "1.0.0" // BaseEndpoint is the target API URL base location. BaseEndpoint = "https://api.bonsai.io" - // UserAgent is the internally used value for the User-Agent header + // UserAgent is the internally used value for the AccessKey-Agent header // in all outgoing HTTP requests. UserAgent = "bonsai-api-go/" + Version ) @@ -73,6 +74,8 @@ var ( ErrHTTPStatusTooManyRequests = errors.New("too many requests") ) +var contentTypeRegexp = regexp.MustCompile(fmt.Sprintf("^%s.*", HTTPContentTypeJSON)) + // ResponseError captures API response errors // returned as JSON in supported scenarios. // @@ -170,28 +173,54 @@ func (app Application) String() string { } } -type Token struct { - string +type Credential string + +type AccessKey Credential + +// NewAccessKey is a convenience method for verifying +// that access keys intended to be used with the API are valid HTTP header values. +func NewAccessKey(user string) (AccessKey, error) { + if ok := Credential(user).validHTTPValue(); !ok { + return AccessKey(""), errors.New("invalid user") + } + return AccessKey(user), nil } -func (t Token) Empty() bool { - return t.string == "" +type AccessToken Credential + +// NewAccessToken is a convenience method for verifying +// that access tokens intended to be used with the API are valid HTTP +// header values. +func NewAccessToken(password string) (AccessToken, error) { + if ok := Credential(password).validHTTPValue(); !ok { + return AccessToken(""), errors.New("invalid password") + } + return AccessToken(password), nil } -func (t Token) NotEmpty() bool { - return !t.Empty() +func (c Credential) Empty() bool { + return c == "" } -func NewToken(token string) (Token, error) { - t := Token{token} - if ok := t.validHTTPValue(); !ok { - return Token{}, errors.New("invalid token") - } - return t, nil +func (c Credential) NotEmpty() bool { + return !c.Empty() } -func (t Token) validHTTPValue() bool { - return httpguts.ValidHeaderFieldValue(t.string) +func (c Credential) validHTTPValue() bool { + return httpguts.ValidHeaderFieldValue(string(c)) +} + +type CredentialPair struct { + AccessKey + AccessToken +} + +func (c CredentialPair) Empty() bool { + return reflect.ValueOf(c).IsZero() +} + +func (c CredentialPair) NotEmpty() bool { + return !c.Empty() } // ClientOption is a functional option, used to configure Client. @@ -204,15 +233,16 @@ func WithEndpoint(endpoint string) ClientOption { } } -// WithToken configures a Client to use the specified token for authentication. -func WithToken(token Token) ClientOption { +// WithCredentialPair configures a Client to use +// the specified username for Basic authorization. +func WithCredentialPair(pair CredentialPair) ClientOption { return func(c *Client) { - c.token = token + c.credentialPair = pair } } // WithApplication configures the client to represent itself as -// a particular Application by modifying the User-Agent header +// a particular Application by modifying the AccessKey-Agent header // sent in all requests. func WithApplication(app Application) ClientOption { return func(c *Client) { @@ -239,6 +269,15 @@ func WithProvisionRateLimit(l *rate.Limiter) ClientOption { } } +// WithHTTPTransport configures the Client's HTTP Transport, such that +// "the mechanism by which individual HTTP requests are made" can be +// overridden. +func WithHTTPTransport(t http.RoundTripper) ClientOption { + return func(c *Client) { + c.httpClient.Transport = t + } +} + type PaginatedResponse struct { PageNumber int `json:"page_number"` PageSize int `json:"page_size"` @@ -253,7 +292,7 @@ type Response struct { } func (r *Response) isJSON() bool { - return r.Header.Get("Content-Type") == HTTPContentTypeJSON + return contentTypeRegexp.MatchString(r.Header.Get("Content-Type")) } // WithHTTPResponse assigns an *http.Response to a *Response item @@ -320,10 +359,10 @@ type ClientLimiter struct { type Client struct { httpClient *http.Client - rateLimiter *ClientLimiter - endpoint string - token Token - userAgent string + rateLimiter *ClientLimiter + endpoint string + credentialPair CredentialPair + userAgent string // Clients Space SpaceClient @@ -355,6 +394,16 @@ func NewClient(options ...ClientOption) *Client { return client } +// Transport returns the HTTP transport used by the Client to make requests. +func (c *Client) Transport() http.RoundTripper { + return c.httpClient.Transport +} + +// Transport returns the HTTP transport used by the Client to make requests. +func (c *Client) SetTransport(t http.RoundTripper) { + c.httpClient.Transport = t +} + func (c *Client) UserAgent() string { return c.userAgent } @@ -363,20 +412,26 @@ func (c *Client) UserAgent() string { // is assigned with ctx and has all necessary headers set (auth, user agent, etc.). func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) { reqURL := c.endpoint + path - req, err := http.NewRequest(method, reqURL, body) + req, err := http.NewRequestWithContext(ctx, method, reqURL, body) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to create new request: %w", err) } req.Header.Set("User-Agent", c.userAgent) - if c.token.NotEmpty() { - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token)) - } + if c.credentialPair.NotEmpty() { + req.SetBasicAuth( + string(c.credentialPair.AccessKey), + string(c.credentialPair.AccessToken), + ) - if body != nil { - req.Header.Set("Content-Type", "application/json") + if _, _, ok := req.BasicAuth(); !ok { + return nil, errors.New("invalid credentials") + } } + req.Header.Set("Content-Type", HTTPContentTypeJSON) + req.Header.Set("Accept", HTTPContentTypeJSON) + req = req.WithContext(ctx) return req, nil @@ -403,6 +458,7 @@ func (c *Client) Do(ctx context.Context, req *http.Request) (*Response, error) { for { respErr := &ResponseError{} resp, err := c.doRequest(ctx, req, reqBuf) + switch { case errors.As(err, respErr): if reflect.ValueOf(respErr).IsZero() { @@ -436,9 +492,6 @@ func (c *Client) doRequest(ctx context.Context, req *http.Request, reqBuf *bytes } httpResp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("http request failed: %w", err) - } defer func() { err = IoClose(httpResp.Body, err) }() if httpResp == nil { diff --git a/bonsai/client_impl_test.go b/bonsai/client_impl_test.go index 98f6c11..ba2518a 100644 --- a/bonsai/client_impl_test.go +++ b/bonsai/client_impl_test.go @@ -37,13 +37,25 @@ func (s *ClientImplTestSuite) SetupSuite() { // Configure http client and other miscellany s.serveMux = chi.NewRouter() s.server = httptest.NewServer(s.serveMux) - token, err := NewToken("TestToken") + + user, err := NewAccessKey("TestUser") + if err != nil { + log.Fatal(fmt.Errorf("invalid user received: %w", err)) + } + + password, err := NewAccessToken("TestToken") if err != nil { - log.Fatal(fmt.Errorf("invalid token received: %w", err)) + log.Fatal(fmt.Errorf("invalid token/password received: %w", err)) } + s.client = NewClient( WithEndpoint(s.server.URL), - WithToken(token), + WithCredentialPair( + CredentialPair{ + AccessKey: user, + AccessToken: password, + }, + ), ) // configure testify diff --git a/bonsai/client_test.go b/bonsai/client_test.go index 148c083..7031a0b 100644 --- a/bonsai/client_test.go +++ b/bonsai/client_test.go @@ -7,10 +7,16 @@ import ( "log" "net/http" "net/http/httptest" + "os" + "path/filepath" + "reflect" + "regexp" "testing" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "gopkg.in/dnaeon/go-vcr.v3/cassette" + "gopkg.in/dnaeon/go-vcr.v3/recorder" "github.com/go-chi/chi/v5" "github.com/omc/bonsai-api-go/v1/bonsai" @@ -42,24 +48,177 @@ type ClientTestSuite struct { client *bonsai.Client } -func (s *ClientTestSuite) SetupSuite() { +// ClientMockTestSuite is used for all requests against data +// either live from an API endpoint, or saved as a fixture from +// those same endpoints. +type ClientVCRTestSuite struct { + recordMode recorder.Mode + recorder *recorder.Recorder + recorderUpdateRegexp *regexp.Regexp + fileNormalizer *regexp.Regexp + ClientTestSuite +} + +func (s *ClientVCRTestSuite) ReadOnlyRun() bool { + return s.recordMode == recorder.ModePassthrough +} +func (s *ClientVCRTestSuite) WillRecord() bool { + return !s.ReadOnlyRun() +} + +func (s *ClientVCRTestSuite) SetRecorderMode(mode string) { + switch mode { + case "REC_ONCE": + s.recordMode = recorder.ModeRecordOnce + case "REC_ONLY": + s.recordMode = recorder.ModeRecordOnly + case "REPLAY_WITH_NEW": + s.recordMode = recorder.ModeReplayWithNewEpisodes + default: + s.recordMode = recorder.ModePassthrough + } +} + +func (s *ClientVCRTestSuite) SetupSuite() { + var err error + + s.fileNormalizer = regexp.MustCompile("[^A-Za-z0-9-]+") + + envVCRRecordMode := os.Getenv("BONSAI_REC_MODE") + // Passthrough if empty + s.SetRecorderMode(envVCRRecordMode) + + if recordUpdateRegexpStr, ok := os.LookupEnv("BONSAI_REC_UPDATE_MATCHING"); ok { + s.recorderUpdateRegexp = regexp.MustCompile(recordUpdateRegexpStr) + } + + envKey := os.Getenv("BONSAI_API_KEY") + envToken := os.Getenv("BONSAI_API_TOKEN") + + accessKey, err := bonsai.NewAccessKey(envKey) + if err != nil { + log.Fatal(fmt.Errorf("invalid user received: %w", err)) + } + + accessToken, err := bonsai.NewAccessToken(envToken) + if err != nil { + log.Fatal(fmt.Errorf("invalid token/password received: %w", err)) + } + + credentialPair := bonsai.CredentialPair{ + AccessKey: accessKey, + AccessToken: accessToken, + } + + // If we're not passing through, and we don't have a key, panic! + if s.WillRecord() && credentialPair.Empty() { + log.Panic("BONSAI_API_TOKEN environment variable not set for testing") + } + // Configure http client and other miscellany s.serveMux = chi.NewRouter() - s.server = httptest.NewServer(s.serveMux) - token, err := bonsai.NewToken("TestToken") - if err != nil { - log.Fatal(fmt.Errorf("invalid token received: %w", err)) + s.client = bonsai.NewClient( + bonsai.WithApplication( + bonsai.Application{ + Name: "bonsai-api-go", + Version: "v1", + }, + ), + bonsai.WithCredentialPair( + credentialPair, + ), + ) + + // configure testify + s.Assertions = require.New(s.T()) +} + +func (s *ClientVCRTestSuite) BeforeTest(_, testName string) { + var err error + + if s.WillRecord() { + s.recorder, err = recorder.NewWithOptions( + &recorder.Options{ + // filepath is os agnostic + CassetteName: filepath.Join("fixtures/golden/", s.normalize(testName)), + Mode: s.recordMode, + RealTransport: s.client.Transport(), + SkipRequestLatency: true, + }, + ) + if err != nil { + log.Fatalf("failed to create new recorder: %+v\n", err) + } + + // Add a hook which removes Authorization headers from all requests + hook := func(i *cassette.Interaction) error { + delete(i.Request.Headers, "Authorization") + return nil + } + s.recorder.AddHook(hook, recorder.AfterCaptureHook) } +} + +func (s *ClientVCRTestSuite) AfterTest(_, _ string) { + if s.recorder != nil && s.recorder.IsRecording() { + if err := s.recorder.Stop(); err != nil { + log.Fatalf("error stopping recorder: %v", err) + } + } +} + +func (s *ClientVCRTestSuite) TearDownSuite() { + +} + +func (s *ClientVCRTestSuite) update(name string) bool { + if s.ReadOnlyRun() { + return false + } + + if reflect.ValueOf(s.recorderUpdateRegexp).IsZero() { + return true + } + + return s.recorderUpdateRegexp.MatchString(name) +} + +func (s *ClientVCRTestSuite) normalize(path string) string { + return s.fileNormalizer.ReplaceAllLiteralString(path, "-") +} + +func TestClientVCRTestSuite(t *testing.T) { + suite.Run(t, new(ClientVCRTestSuite)) +} + +// ClientMockTestSuite is used for all mocked web requests. +type ClientMockTestSuite struct { + ClientTestSuite +} + +func (s *ClientMockTestSuite) SetupSuite() { + // Configure http client and other miscellany + s.serveMux = chi.NewRouter() + s.server = httptest.NewServer(s.serveMux) + s.client = bonsai.NewClient( bonsai.WithEndpoint(s.server.URL), - bonsai.WithToken(token), + bonsai.WithCredentialPair( + bonsai.CredentialPair{ + AccessKey: bonsai.AccessKey("TestKey"), + AccessToken: bonsai.AccessToken("TestToken"), + }, + ), ) - // configure testify s.Assertions = require.New(s.T()) } -func (s *ClientTestSuite) TestResponseErrorUnmarshallJson() { +func TestClientMockTestSuite(t *testing.T) { + suite.Run(t, new(ClientMockTestSuite)) +} + +func (s *ClientMockTestSuite) TestResponseErrorUnmarshallJson() { testCases := []struct { name string received string @@ -95,7 +254,7 @@ func (s *ClientTestSuite) TestResponseErrorUnmarshallJson() { } } -func (s *ClientTestSuite) TestClientResponseError() { +func (s *ClientMockTestSuite) TestClientResponseError() { const p = "/clusters/doesnotexist-1234" // Configure Servemux to serve the error response at this path @@ -124,9 +283,9 @@ func (s *ClientTestSuite) TestClientResponseError() { s.ErrorIs(err, bonsai.ErrHTTPStatusNotFound, "ResponseError is comparable to bonsai.ErrorHttpResponseStatus") } -func (s *ClientTestSuite) TestClientResponseWithPagination() { +func (s *ClientMockTestSuite) TestClientResponseWithPagination() { s.serveMux.Get("/clusters", func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "application/json") + w.Header().Set("Content-Type", bonsai.HTTPContentTypeJSON) w.WriteHeader(http.StatusOK) _, err := fmt.Fprint(w, ` { @@ -152,7 +311,7 @@ func (s *ClientTestSuite) TestClientResponseWithPagination() { s.Equal(255, resp.PaginatedResponse.TotalRecords) } -func (s *ClientTestSuite) TestClient_WithApplication() { +func (s *ClientMockTestSuite) TestClient_WithApplication() { testCases := []struct { name string received bonsai.Application @@ -190,7 +349,3 @@ func (s *ClientTestSuite) TestClient_WithApplication() { }) } } - -func TestClientTestSuite(t *testing.T) { - suite.Run(t, new(ClientTestSuite)) -} diff --git a/bonsai/cluster.go b/bonsai/cluster.go index 9df6161..b68c5d7 100644 --- a/bonsai/cluster.go +++ b/bonsai/cluster.go @@ -192,7 +192,7 @@ func (o ClusterCreateOpts) Valid() error { type ClusterUpdateOpts struct { // Required. A String representing the name for the new cluster. Name string `json:"name"` - // The slug of the Plan that the new cluster will be configured for. + // Required. The slug of the Plan that the new cluster will be configured for. // Use the [PlanClient.All] method to view a list of all Plans available. Plan string `json:"plan,omitempty"` } @@ -297,7 +297,7 @@ func (c *ClusterClient) All(ctx context.Context) ([]Cluster, error) { } allResults = append(allResults, listResults...) - if len(allResults) >= resp.PageSize { + if len(allResults) >= resp.TotalRecords { resp.MarkPaginationComplete() } return resp, err @@ -348,7 +348,7 @@ func (c *ClusterClient) GetBySlug(ctx context.Context, slug string) (Cluster, er // Create requests a new Cluster to be created. // -//nolint:dupl // Allow duplicated code blocks in code paths that may change + func (c *ClusterClient) Create(ctx context.Context, opt ClusterCreateOpts) ( ClustersResultCreate, error, @@ -396,8 +396,8 @@ func (c *ClusterClient) Create(ctx context.Context, opt ClusterCreateOpts) ( // Update requests a new Cluster be updated. // -//nolint:dupl // Allow duplicated code blocks in code paths that may change -func (c *ClusterClient) Update(ctx context.Context, opt ClusterUpdateOpts) ( + +func (c *ClusterClient) Update(ctx context.Context, slug string, opt ClusterUpdateOpts) ( ClustersResultUpdate, error, ) { @@ -415,6 +415,7 @@ func (c *ClusterClient) Update(ctx context.Context, opt ClusterUpdateOpts) ( if err != nil { return result, fmt.Errorf("cannot parse relative url from basepath (%s): %w", ClusterAPIBasePath, err) } + reqURL.Path = path.Join(reqURL.Path, slug) if err = opt.Valid(); err != nil { return result, fmt.Errorf("invalid create options (%v): %w", opt, err) diff --git a/bonsai/cluster_test.go b/bonsai/cluster_test.go index 07f2202..08d0d0d 100644 --- a/bonsai/cluster_test.go +++ b/bonsai/cluster_test.go @@ -10,7 +10,7 @@ import ( "github.com/omc/bonsai-api-go/v1/bonsai" ) -func (s *ClientTestSuite) TestClusterClient_All() { +func (s *ClientMockTestSuite) TestClusterClient_All() { s.serveMux.Get(bonsai.ClusterAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { respStr := ` { @@ -246,7 +246,7 @@ func (s *ClientTestSuite) TestClusterClient_All() { } } -func (s *ClientTestSuite) TestClusterClient_GetBySlug() { +func (s *ClientMockTestSuite) TestClusterClient_GetBySlug() { const targetClusterSlug = "second-testing-clust-1234567890" urlPath, err := url.JoinPath(bonsai.ClusterAPIBasePath, targetClusterSlug) @@ -337,7 +337,7 @@ func (s *ClientTestSuite) TestClusterClient_GetBySlug() { s.Equal(expect, resultResp, "elements in expect match elements in received cluster response") } -func (s *ClientTestSuite) TestClusterClient_Create() { +func (s *ClientMockTestSuite) TestClusterClient_Create() { s.serveMux.Post(bonsai.ClusterAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { respStr := ` { @@ -387,8 +387,13 @@ func (s *ClientTestSuite) TestClusterClient_Create() { s.Equal(expect, resultResp, "elements in expect match elements in received cluster create response") } -func (s *ClientTestSuite) TestClusterClient_Update() { - s.serveMux.Put(bonsai.ClusterAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { +func (s *ClientMockTestSuite) TestClusterClient_Update() { + const targetClusterSlug = "second-testing-clust-1234567890" + + urlPath, err := url.JoinPath(bonsai.ClusterAPIBasePath, targetClusterSlug) + s.NoError(err, "successfully resolved path") + + s.serveMux.Put(urlPath, func(w http.ResponseWriter, _ *http.Request) { respStr := ` { "message": "Your cluster is being updated.", @@ -398,7 +403,7 @@ func (s *ClientTestSuite) TestClusterClient_Update() { ` resp := &bonsai.ClustersResultUpdate{} - err := json.Unmarshal([]byte(respStr), resp) + err = json.Unmarshal([]byte(respStr), resp) s.NoError(err, "unmarshals json into bonsai.ClustersResultUpdate") err = json.NewEncoder(w).Encode(resp) @@ -410,7 +415,7 @@ func (s *ClientTestSuite) TestClusterClient_Update() { Monitor: "https://api.bonsai.io/clusters/test-5-x-3968320296", } - resultResp, err := s.client.Cluster.Update(context.Background(), bonsai.ClusterUpdateOpts{ + resultResp, err := s.client.Cluster.Update(context.Background(), targetClusterSlug, bonsai.ClusterUpdateOpts{ Name: "test-5-x-3968320296", Plan: "sandbox-aws-us-east-2", }) @@ -419,7 +424,7 @@ func (s *ClientTestSuite) TestClusterClient_Update() { s.Equal(expect, resultResp, "items in expect match items in received cluster update response") } -func (s *ClientTestSuite) TestClusterClient_Delete() { +func (s *ClientMockTestSuite) TestClusterClient_Delete() { const targetClusterSlug = "second-testing-clust-1234567890" reqPath, err := url.JoinPath(bonsai.ClusterAPIBasePath, targetClusterSlug) @@ -452,3 +457,52 @@ func (s *ClientTestSuite) TestClusterClient_Delete() { s.Equal(expect, resultResp, "items in expect match items in received cluster update response") } + +// VCR Tests. +func (s *ClientVCRTestSuite) TestClusterClient_All() { + ctx := context.Background() + + plans, err := s.client.Cluster.All(ctx) + s.NoError(err, "successfully get all clusters") + assertGolden(s, plans) +} + +func (s *ClientVCRTestSuite) TestClusterClient_GetBySlug() { + ctx := context.Background() + + plan, err := s.client.Cluster.GetBySlug(ctx, "dcek-group-llc-5240651189") + s.NoError(err, "successfully get cluster") + assertGolden(s, plan) +} + +func (s *ClientVCRTestSuite) TestClusterClient_Create() { + ctx := context.Background() + + plan, err := s.client.Cluster.Create(ctx, bonsai.ClusterCreateOpts{ + Name: "bonsai-api-go-test-cluster", + Plan: "standard-nano-comped", + Space: "omc/bonsai/us-east-1/common", + Release: "opensearch-2.6.0-mt", + }) + s.NoError(err, "successfully get cluster") + assertGolden(s, plan) +} + +func (s *ClientVCRTestSuite) TestClusterClient_Update() { + ctx := context.Background() + + plan, err := s.client.Cluster.Update(ctx, "bonsai-api-go-9994392953", bonsai.ClusterUpdateOpts{ + Name: "bonsai-api-go-test-cluster-updated", + Plan: "standard-nano-comped", + }) + s.NoError(err, "successfully get cluster") + assertGolden(s, plan) +} + +func (s *ClientVCRTestSuite) TestClusterClient_Delete() { + ctx := context.Background() + + plan, err := s.client.Cluster.Destroy(ctx, "bonsai-api-go-9994392953") + s.NoError(err, "successfully get cluster") + assertGolden(s, plan) +} diff --git a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-All b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-All new file mode 100644 index 0000000..4c4894a --- /dev/null +++ b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-All @@ -0,0 +1,41 @@ +[ + { + "slug": "dcek-group-llc-5240651189", + "name": "DCEK Group, LLC search", + "uri": "https://api.bonsai.io/clusters/dcek-group-llc-5240651189", + "plan": { + "slug": "sandbox-aws-us-east-1", + "available_releases": [], + "available_spaces": [], + "uri": "https://api.bonsai.io/plans/sandbox-aws-us-east-1" + }, + "release": { + "slug": "elasticsearch-7.10.2", + "service_type": "elasticsearch", + "version": "7.10.2", + "uri": "https://api.bonsai.io/releases/elasticsearch-7.10.2", + "package_name": "7.10.2" + }, + "space": { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + }, + "region": "aws-us-east-1", + "uri": "https://api.bonsai.io/spaces/omc/bonsai/us-east-1/common" + }, + "stats": { + "docs": 2, + "shards_used": 2, + "data_bytes_used": 17984 + }, + "access": { + "host": "dcek-group-llc-5240651189.us-east-1.bonsaisearch.net", + "port": 443, + "scheme": "https" + }, + "state": "PROVISIONED" + } + ] \ No newline at end of file diff --git a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Create b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Create new file mode 100644 index 0000000..674c3b6 --- /dev/null +++ b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Create @@ -0,0 +1,12 @@ +{ + "message": "Your cluster is being provisioned.", + "monitor": "https://api.bonsai.io/clusters/bonsai-api-go-9994392953", + "access": { + "host": "bonsai-api-go-9994392953", + "port": 443, + "scheme": "https", + "user": "REDACTED", + "pass": "REDACTED", + "url": "://REDACTED:REDACTED@" + } + } \ No newline at end of file diff --git a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Delete b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Delete new file mode 100644 index 0000000..fa75d39 --- /dev/null +++ b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Delete @@ -0,0 +1,4 @@ +{ + "message": "Your cluster is being deprovisioned.", + "monitor": "https://api.bonsai.io/clusters/bonsai-api-go-9994392953" + } \ No newline at end of file diff --git a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-GetBySlug b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-GetBySlug new file mode 100644 index 0000000..c2b3f69 --- /dev/null +++ b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-GetBySlug @@ -0,0 +1,26 @@ +{ + "slug": "", + "name": "", + "uri": "", + "plan": { + "slug": "", + "available_releases": null, + "available_spaces": null + }, + "release": {}, + "space": { + "path": "", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + "stats": {}, + "access": { + "host": "", + "port": 0, + "scheme": "" + }, + "state": "" + } \ No newline at end of file diff --git a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Update b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Update new file mode 100644 index 0000000..dd93530 --- /dev/null +++ b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Update @@ -0,0 +1,4 @@ +{ + "message": "Your cluster is being updated.", + "monitor": "https://api.bonsai.io/clusters/bonsai-api-go-9994392953" + } \ No newline at end of file diff --git a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestPlanClient-All b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestPlanClient-All new file mode 100644 index 0000000..482b473 --- /dev/null +++ b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestPlanClient-All @@ -0,0 +1,1905 @@ +[ + { + "slug": "standard-nano-comped", + "name": "Standard Nano", + "billing_interval_months": 1, + "available_releases": [ + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "standard-micro-aws-us-east-1", + "name": "Standard Micro", + "price_in_cents": 2000, + "billing_interval_months": 1, + "available_releases": [ + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "business-capacity-2x-aws-us-east-1", + "name": "Business Capacity 2X", + "price_in_cents": 200000, + "billing_interval_months": 1, + "single_tenant": true, + "available_releases": [ + { + "slug": "elasticsearch-2.4.0" + }, + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.3.2" + }, + { + "slug": "elasticsearch-6.4.2" + }, + { + "slug": "elasticsearch-6.5.4" + }, + { + "slug": "elasticsearch-6.8.17" + }, + { + "slug": "elasticsearch-6.8.19" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-1.2.4" + }, + { + "slug": "opensearch-2.3.0" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "business-performance-xl-aws-us-east-1", + "name": "Business Performance XL", + "price_in_cents": 95000, + "billing_interval_months": 1, + "single_tenant": true, + "available_releases": [ + { + "slug": "elasticsearch-2.4.0" + }, + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.3.2" + }, + { + "slug": "elasticsearch-6.4.2" + }, + { + "slug": "elasticsearch-6.5.4" + }, + { + "slug": "elasticsearch-6.8.17" + }, + { + "slug": "elasticsearch-6.8.19" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-1.2.4" + }, + { + "slug": "opensearch-2.3.0" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "business-performance-lg-aws-us-east-1", + "name": "Business Performance LG", + "price_in_cents": 70000, + "billing_interval_months": 1, + "single_tenant": true, + "available_releases": [ + { + "slug": "elasticsearch-2.4.0" + }, + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.3.2" + }, + { + "slug": "elasticsearch-6.4.2" + }, + { + "slug": "elasticsearch-6.5.4" + }, + { + "slug": "elasticsearch-6.8.17" + }, + { + "slug": "elasticsearch-6.8.19" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-1.2.4" + }, + { + "slug": "opensearch-2.3.0" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "standard-md-aws-us-east-1", + "name": "Standard MD", + "price_in_cents": 25000, + "billing_interval_months": 1, + "available_releases": [ + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "business-private-capacity-xl-aws-us-east-1", + "name": "Business Private Capacity XL", + "price_in_cents": 135000, + "billing_interval_months": 1, + "single_tenant": true, + "private_network": true, + "available_releases": [ + { + "slug": "elasticsearch-2.4.0" + }, + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.3.2" + }, + { + "slug": "elasticsearch-6.4.2" + }, + { + "slug": "elasticsearch-6.5.4" + }, + { + "slug": "elasticsearch-6.8.17" + }, + { + "slug": "elasticsearch-6.8.19" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-1.2.4" + }, + { + "slug": "opensearch-2.3.0" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "business-performance-2x-aws-us-east-1", + "name": "Business Performance 2X", + "price_in_cents": 140000, + "billing_interval_months": 1, + "single_tenant": true, + "available_releases": [ + { + "slug": "elasticsearch-2.4.0" + }, + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.3.2" + }, + { + "slug": "elasticsearch-6.4.2" + }, + { + "slug": "elasticsearch-6.5.4" + }, + { + "slug": "elasticsearch-6.8.17" + }, + { + "slug": "elasticsearch-6.8.19" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-1.2.4" + }, + { + "slug": "opensearch-2.3.0" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "business-private-capacity-2x-aws-us-east-1", + "name": "Business Private Capacity 2X", + "price_in_cents": 210000, + "billing_interval_months": 1, + "single_tenant": true, + "private_network": true, + "available_releases": [ + { + "slug": "elasticsearch-2.4.0" + }, + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.3.2" + }, + { + "slug": "elasticsearch-6.4.2" + }, + { + "slug": "elasticsearch-6.5.4" + }, + { + "slug": "elasticsearch-6.8.17" + }, + { + "slug": "elasticsearch-6.8.19" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-1.2.4" + }, + { + "slug": "opensearch-2.3.0" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "business-private-performance-lg-aws-us-east-1", + "name": "Business Private Performance LG", + "price_in_cents": 80000, + "billing_interval_months": 1, + "single_tenant": true, + "private_network": true, + "available_releases": [ + { + "slug": "elasticsearch-2.4.0" + }, + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.3.2" + }, + { + "slug": "elasticsearch-6.4.2" + }, + { + "slug": "elasticsearch-6.5.4" + }, + { + "slug": "elasticsearch-6.8.17" + }, + { + "slug": "elasticsearch-6.8.19" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-1.2.4" + }, + { + "slug": "opensearch-2.3.0" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "standard-nano-aws-us-east-1", + "name": "Standard Nano", + "price_in_cents": 5000, + "billing_interval_months": 1, + "available_releases": [ + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "business-private-capacity-lg-aws-us-east-1", + "name": "Business Private Capacity LG", + "price_in_cents": 95000, + "billing_interval_months": 1, + "single_tenant": true, + "private_network": true, + "available_releases": [ + { + "slug": "elasticsearch-2.4.0" + }, + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.3.2" + }, + { + "slug": "elasticsearch-6.4.2" + }, + { + "slug": "elasticsearch-6.5.4" + }, + { + "slug": "elasticsearch-6.8.17" + }, + { + "slug": "elasticsearch-6.8.19" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-1.2.4" + }, + { + "slug": "opensearch-2.3.0" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "business-capacity-xl-aws-us-east-1", + "name": "Business Capacity XL", + "price_in_cents": 125000, + "billing_interval_months": 1, + "single_tenant": true, + "available_releases": [ + { + "slug": "elasticsearch-2.4.0" + }, + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.3.2" + }, + { + "slug": "elasticsearch-6.4.2" + }, + { + "slug": "elasticsearch-6.5.4" + }, + { + "slug": "elasticsearch-6.8.17" + }, + { + "slug": "elasticsearch-6.8.19" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-1.2.4" + }, + { + "slug": "opensearch-2.3.0" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "business-private-performance-xl-aws-us-east-1", + "name": "Business Private Performance XL", + "price_in_cents": 105000, + "billing_interval_months": 1, + "single_tenant": true, + "private_network": true, + "available_releases": [ + { + "slug": "elasticsearch-2.4.0" + }, + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.3.2" + }, + { + "slug": "elasticsearch-6.4.2" + }, + { + "slug": "elasticsearch-6.5.4" + }, + { + "slug": "elasticsearch-6.8.17" + }, + { + "slug": "elasticsearch-6.8.19" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-1.2.4" + }, + { + "slug": "opensearch-2.3.0" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "standard-sm-aws-us-east-1", + "name": "Standard SM", + "price_in_cents": 15000, + "billing_interval_months": 1, + "available_releases": [ + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "business-capacity-lg-aws-us-east-1", + "name": "Business Capacity LG", + "price_in_cents": 85000, + "billing_interval_months": 1, + "single_tenant": true, + "available_releases": [ + { + "slug": "elasticsearch-2.4.0" + }, + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.3.2" + }, + { + "slug": "elasticsearch-6.4.2" + }, + { + "slug": "elasticsearch-6.5.4" + }, + { + "slug": "elasticsearch-6.8.17" + }, + { + "slug": "elasticsearch-6.8.19" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-1.2.4" + }, + { + "slug": "opensearch-2.3.0" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, + { + "slug": "business-private-performance-2x-aws-us-east-1", + "name": "Business Private Performance 2X", + "price_in_cents": 150000, + "billing_interval_months": 1, + "single_tenant": true, + "private_network": true, + "available_releases": [ + { + "slug": "elasticsearch-2.4.0" + }, + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.3.2" + }, + { + "slug": "elasticsearch-6.4.2" + }, + { + "slug": "elasticsearch-6.5.4" + }, + { + "slug": "elasticsearch-6.8.17" + }, + { + "slug": "elasticsearch-6.8.19" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-1.2.4" + }, + { + "slug": "opensearch-2.3.0" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + } + ] \ No newline at end of file diff --git a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestPlanClient-GetByPath b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestPlanClient-GetByPath new file mode 100644 index 0000000..4db0b7c --- /dev/null +++ b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestPlanClient-GetByPath @@ -0,0 +1,94 @@ +{ + "slug": "standard-micro-aws-us-east-1", + "name": "Standard Micro", + "price_in_cents": 2000, + "billing_interval_months": 1, + "available_releases": [ + { + "slug": "elasticsearch-5.6.16" + }, + { + "slug": "elasticsearch-6.8.21" + }, + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + } \ No newline at end of file diff --git a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestReleaseClient-All b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestReleaseClient-All new file mode 100644 index 0000000..7bf565a --- /dev/null +++ b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestReleaseClient-All @@ -0,0 +1,90 @@ +[ + { + "name": "Elasticsearch 2.4.0", + "slug": "elasticsearch-2.4.0", + "service_type": "elasticsearch", + "version": "2.4.0", + "package_name": "2.4.0" + }, + { + "name": "Elasticsearch 5.6.16", + "slug": "elasticsearch-5.6.16", + "service_type": "elasticsearch", + "version": "5.6.16", + "multitenant": true, + "package_name": "5.6.16" + }, + { + "name": "Elasticsearch 6.3.2", + "slug": "elasticsearch-6.3.2", + "service_type": "elasticsearch", + "version": "6.3.2", + "package_name": "6.3.2" + }, + { + "name": "Elasticsearch 6.4.2", + "slug": "elasticsearch-6.4.2", + "service_type": "elasticsearch", + "version": "6.4.2", + "package_name": "6.4.2" + }, + { + "name": "Elasticsearch 6.5.4", + "slug": "elasticsearch-6.5.4", + "service_type": "elasticsearch", + "version": "6.5.4", + "package_name": "6.5.4" + }, + { + "name": "Elasticsearch 6.8.17", + "slug": "elasticsearch-6.8.17", + "service_type": "elasticsearch", + "version": "6.8.17", + "package_name": "6.8.17" + }, + { + "name": "Elasticsearch 6.8.19", + "slug": "elasticsearch-6.8.19", + "service_type": "elasticsearch", + "version": "6.8.19", + "package_name": "6.8.19" + }, + { + "name": "Elasticsearch 6.8.21", + "slug": "elasticsearch-6.8.21", + "service_type": "elasticsearch", + "version": "6.8.21", + "multitenant": true, + "package_name": "6.8.21" + }, + { + "name": "Elasticsearch 7.10.2", + "slug": "elasticsearch-7.10.2", + "service_type": "elasticsearch", + "version": "7.10.2", + "multitenant": true, + "package_name": "7.10.2" + }, + { + "name": "OpenSearch 1.2.4", + "slug": "opensearch-1.2.4", + "service_type": "opensearch", + "version": "1.2.4", + "package_name": "1.2.4" + }, + { + "name": "OpenSearch 2.3.0", + "slug": "opensearch-2.3.0", + "service_type": "opensearch", + "version": "2.3.0", + "package_name": "2.3.0" + }, + { + "name": "OpenSearch 2.6.0", + "slug": "opensearch-2.6.0-mt", + "service_type": "opensearch", + "version": "2.6.0", + "multitenant": true, + "package_name": "2.6.0-mt" + } + ] \ No newline at end of file diff --git a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestReleaseClient-GetByPath b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestReleaseClient-GetByPath new file mode 100644 index 0000000..4768d65 --- /dev/null +++ b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestReleaseClient-GetByPath @@ -0,0 +1,8 @@ +{ + "name": "OpenSearch 2.6.0", + "slug": "opensearch-2.6.0-mt", + "service_type": "opensearch", + "version": "2.6.0", + "multitenant": true, + "package_name": "2.6.0-mt" + } \ No newline at end of file diff --git a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestSpaceClient-All b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestSpaceClient-All new file mode 100644 index 0000000..aeb1131 --- /dev/null +++ b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestSpaceClient-All @@ -0,0 +1,74 @@ +[ + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "aws", + "region": "aws-eu-west-1" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "aws", + "region": "aws-ap-southeast-2" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "aws", + "region": "aws-eu-central-1" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "aws", + "region": "aws-us-east-1" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "gcp", + "region": "gcp-us-west1" + } + }, + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "gcp", + "region": "gcp-us-east4" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "aws", + "region": "aws-us-west-2" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "aws", + "region": "aws-ap-northeast-1" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "aws", + "region": "aws-us-east-1" + } + } + ] \ No newline at end of file diff --git a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestSpaceClient-GetByPath b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestSpaceClient-GetByPath new file mode 100644 index 0000000..4e788eb --- /dev/null +++ b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestSpaceClient-GetByPath @@ -0,0 +1,8 @@ +{ + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "aws", + "region": "aws-eu-west-1" + } + } \ No newline at end of file diff --git a/bonsai/fixtures/golden/TestClusterClient-All.yaml b/bonsai/fixtures/golden/TestClusterClient-All.yaml new file mode 100644 index 0000000..2797c38 --- /dev/null +++ b/bonsai/fixtures/golden/TestClusterClient-All.yaml @@ -0,0 +1,3 @@ +--- +version: 2 +interactions: [] diff --git a/bonsai/fixtures/golden/TestClusterClient-Create.yaml b/bonsai/fixtures/golden/TestClusterClient-Create.yaml new file mode 100644 index 0000000..2797c38 --- /dev/null +++ b/bonsai/fixtures/golden/TestClusterClient-Create.yaml @@ -0,0 +1,3 @@ +--- +version: 2 +interactions: [] diff --git a/bonsai/fixtures/golden/TestClusterClient-Delete.yaml b/bonsai/fixtures/golden/TestClusterClient-Delete.yaml new file mode 100644 index 0000000..2797c38 --- /dev/null +++ b/bonsai/fixtures/golden/TestClusterClient-Delete.yaml @@ -0,0 +1,3 @@ +--- +version: 2 +interactions: [] diff --git a/bonsai/fixtures/golden/TestClusterClient-GetBySlug.yaml b/bonsai/fixtures/golden/TestClusterClient-GetBySlug.yaml new file mode 100644 index 0000000..2797c38 --- /dev/null +++ b/bonsai/fixtures/golden/TestClusterClient-GetBySlug.yaml @@ -0,0 +1,3 @@ +--- +version: 2 +interactions: [] diff --git a/bonsai/fixtures/golden/TestClusterClient-Update.yaml b/bonsai/fixtures/golden/TestClusterClient-Update.yaml new file mode 100644 index 0000000..2797c38 --- /dev/null +++ b/bonsai/fixtures/golden/TestClusterClient-Update.yaml @@ -0,0 +1,3 @@ +--- +version: 2 +interactions: [] diff --git a/bonsai/fixtures/golden/TestPlanClient-All.yaml b/bonsai/fixtures/golden/TestPlanClient-All.yaml new file mode 100644 index 0000000..2797c38 --- /dev/null +++ b/bonsai/fixtures/golden/TestPlanClient-All.yaml @@ -0,0 +1,3 @@ +--- +version: 2 +interactions: [] diff --git a/bonsai/fixtures/golden/TestPlanClient-GetByPath.yaml b/bonsai/fixtures/golden/TestPlanClient-GetByPath.yaml new file mode 100644 index 0000000..2797c38 --- /dev/null +++ b/bonsai/fixtures/golden/TestPlanClient-GetByPath.yaml @@ -0,0 +1,3 @@ +--- +version: 2 +interactions: [] diff --git a/bonsai/fixtures/golden/TestReleaseClient-All.yaml b/bonsai/fixtures/golden/TestReleaseClient-All.yaml new file mode 100644 index 0000000..2797c38 --- /dev/null +++ b/bonsai/fixtures/golden/TestReleaseClient-All.yaml @@ -0,0 +1,3 @@ +--- +version: 2 +interactions: [] diff --git a/bonsai/fixtures/golden/TestReleaseClient-GetByPath.yaml b/bonsai/fixtures/golden/TestReleaseClient-GetByPath.yaml new file mode 100644 index 0000000..2797c38 --- /dev/null +++ b/bonsai/fixtures/golden/TestReleaseClient-GetByPath.yaml @@ -0,0 +1,3 @@ +--- +version: 2 +interactions: [] diff --git a/bonsai/fixtures/golden/TestSpaceClient-All.yaml b/bonsai/fixtures/golden/TestSpaceClient-All.yaml new file mode 100644 index 0000000..2797c38 --- /dev/null +++ b/bonsai/fixtures/golden/TestSpaceClient-All.yaml @@ -0,0 +1,3 @@ +--- +version: 2 +interactions: [] diff --git a/bonsai/fixtures/golden/TestSpaceClient-GetByPath.yaml b/bonsai/fixtures/golden/TestSpaceClient-GetByPath.yaml new file mode 100644 index 0000000..2797c38 --- /dev/null +++ b/bonsai/fixtures/golden/TestSpaceClient-GetByPath.yaml @@ -0,0 +1,3 @@ +--- +version: 2 +interactions: [] diff --git a/bonsai/plan_test.go b/bonsai/plan_test.go index e2d9feb..d4eca04 100644 --- a/bonsai/plan_test.go +++ b/bonsai/plan_test.go @@ -9,7 +9,7 @@ import ( "github.com/omc/bonsai-api-go/v1/bonsai" ) -func (s *ClientTestSuite) TestPlanClient_All() { +func (s *ClientMockTestSuite) TestPlanClient_All() { s.serveMux.Get(bonsai.PlanAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { respStr := ` { @@ -109,14 +109,14 @@ func (s *ClientTestSuite) TestPlanClient_All() { }, }, } - spaces, err := s.client.Plan.All(context.Background()) - s.NoError(err, "successfully get all spaces") - s.Len(spaces, 2) + plans, err := s.client.Plan.All(context.Background()) + s.NoError(err, "successfully get all plans") + s.Len(plans, 2) - s.ElementsMatch(expect, spaces, "elements expected match elements in received spaces") + s.ElementsMatch(expect, plans, "elements expected match elements in received plans") } -func (s *ClientTestSuite) TestPlanClient_GetByPath() { +func (s *ClientMockTestSuite) TestPlanClient_GetByPath() { const targetPlanPath = "sandbox-aws-us-east-1" urlPath, err := url.JoinPath(bonsai.PlanAPIBasePath, "sandbox-aws-us-east-1") @@ -172,7 +172,24 @@ func (s *ClientTestSuite) TestPlanClient_GetByPath() { } resultResp, err := s.client.Plan.GetBySlug(context.Background(), "sandbox-aws-us-east-1") - s.NoError(err, "successfully get space by path") + s.NoError(err, "successfully get plan by path") s.Equal(expect, resultResp, "expected struct matches unmarshaled result") } + +// VCR Tests. +func (s *ClientVCRTestSuite) TestPlanClient_All() { + ctx := context.Background() + + plans, err := s.client.Plan.All(ctx) + s.NoError(err, "successfully get all plans") + assertGolden(s, plans) +} + +func (s *ClientVCRTestSuite) TestPlanClient_GetByPath() { + ctx := context.Background() + + plan, err := s.client.Plan.GetBySlug(ctx, "standard-micro-aws-us-east-1") + s.NoError(err, "successfully get plan") + assertGolden(s, plan) +} diff --git a/bonsai/release_test.go b/bonsai/release_test.go index da471e9..c2987da 100644 --- a/bonsai/release_test.go +++ b/bonsai/release_test.go @@ -10,7 +10,7 @@ import ( "github.com/omc/bonsai-api-go/v1/bonsai" ) -func (s *ClientTestSuite) TestReleaseClient_All() { +func (s *ClientMockTestSuite) TestReleaseClient_All() { s.serveMux.Get(bonsai.ReleaseAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { respStr := ` { @@ -78,7 +78,7 @@ func (s *ClientTestSuite) TestReleaseClient_All() { s.ElementsMatch(expect, releases, "elements in expect match elements in received releases") } -func (s *ClientTestSuite) TestReleaseClient_GetBySlug() { +func (s *ClientMockTestSuite) TestReleaseClient_GetBySlug() { const targetReleaseSlug = "elasticsearch-7.2.0" urlPath, err := url.JoinPath(bonsai.ReleaseAPIBasePath, targetReleaseSlug) @@ -97,10 +97,10 @@ func (s *ClientTestSuite) TestReleaseClient_GetBySlug() { resp := &bonsai.Release{} err = json.Unmarshal([]byte(respStr), resp) - s.NoError(err, "unmarshals json into bonsai.Space") + s.NoError(err, "unmarshals json into bonsai.Release") err = json.NewEncoder(w).Encode(resp) - s.NoError(err, "encodes bonsai.Space into json on the writer") + s.NoError(err, "encodes bonsai.Release into json on the writer") }) expect := bonsai.Release{ @@ -116,3 +116,20 @@ func (s *ClientTestSuite) TestReleaseClient_GetBySlug() { s.Equal(expect, resultResp, "elements in expect match elements in received release response") } + +// VCR Tests. +func (s *ClientVCRTestSuite) TestReleaseClient_All() { + ctx := context.Background() + + spaces, err := s.client.Release.All(ctx) + s.NoError(err, "successfully get all spaces") + assertGolden(s, spaces) +} + +func (s *ClientVCRTestSuite) TestReleaseClient_GetByPath() { + ctx := context.Background() + + space, err := s.client.Release.GetBySlug(ctx, "opensearch-2.6.0-mt") + s.NoError(err, "successfully get space") + assertGolden(s, space) +} diff --git a/bonsai/space_test.go b/bonsai/space_test.go index d3f59c1..5647c65 100644 --- a/bonsai/space_test.go +++ b/bonsai/space_test.go @@ -10,7 +10,9 @@ import ( "github.com/omc/bonsai-api-go/v1/bonsai" ) -func (s *ClientTestSuite) TestSpaceClient_All() { +// Mocked Tests + +func (s *ClientMockTestSuite) TestSpaceClient_All() { s.serveMux.Get(bonsai.SpaceAPIBasePath, func(w http.ResponseWriter, _ *http.Request) { respStr := ` { @@ -58,7 +60,7 @@ func (s *ClientTestSuite) TestSpaceClient_All() { s.Len(spaces, 3) } -func (s *ClientTestSuite) TestSpaceClient_GetByPath() { +func (s *ClientMockTestSuite) TestSpaceClient_GetByPath() { const targetSpacePath = "omc/bonsai/us-east-1/common" urlPath, err := url.JoinPath(bonsai.SpaceAPIBasePath, targetSpacePath) @@ -94,3 +96,20 @@ func (s *ClientTestSuite) TestSpaceClient_GetByPath() { s.Equal(space.Cloud.Provider, "aws") s.Equal(space.Cloud.Region, "aws-us-east-1") } + +// VCR Tests. +func (s *ClientVCRTestSuite) TestSpaceClient_All() { + ctx := context.Background() + + spaces, err := s.client.Space.All(ctx) + s.NoError(err, "successfully get all spaces") + assertGolden(s, spaces) +} + +func (s *ClientVCRTestSuite) TestSpaceClient_GetByPath() { + ctx := context.Background() + + space, err := s.client.Space.GetByPath(ctx, "omc/bonsai/eu-west-1/common") + s.NoError(err, "successfully get space") + assertGolden(s, space) +} diff --git a/bonsai/vcr.go b/bonsai/vcr.go new file mode 100644 index 0000000..a9d1fb2 --- /dev/null +++ b/bonsai/vcr.go @@ -0,0 +1,70 @@ +// All modifications to this file are licensed under the LICENSE found at +// the root of the repository. +// +// Some contents are licensed under Apache Version 2.0 per below: +// +// Copyright 2018 Sourcegraph, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bonsai + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func AssertGolden(t testing.TB, path string, update bool, want any) { + t.Helper() + + data := marshal(t, want) + + if update { + if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { + t.Fatalf("failed to update golden file %q: %s", path, err) + } + if err := os.WriteFile(path, data, 0o640); err != nil { //nolint:gosec // writing files for VCR reading + t.Fatalf("failed to update golden file %q: %s", path, err) + } + } + + golden, err := os.ReadFile(path) + if err != nil { + t.Fatalf("failed to read golden file %q: %s", path, err) + } + + if diff := cmp.Diff(string(golden), string(data)); diff != "" { + t.Errorf("(-want, +got):\n%s", diff) + } +} + +func marshal(t testing.TB, v any) []byte { + t.Helper() + + switch v2 := v.(type) { + case string: + return []byte(v2) + case []byte: + return v2 + default: + data, err := json.MarshalIndent(v, " ", " ") + if err != nil { + t.Fatal(err) + } + return data + } +} diff --git a/go.mod b/go.mod index cca58af..873435d 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,13 @@ go 1.22 require ( github.com/go-chi/chi/v5 v5.0.12 + github.com/google/go-cmp v0.6.0 github.com/google/go-querystring v1.1.0 github.com/hetznercloud/hcloud-go/v2 v2.7.2 github.com/stretchr/testify v1.9.0 golang.org/x/net v0.24.0 golang.org/x/time v0.5.0 + gopkg.in/dnaeon/go-vcr.v3 v3.2.0 ) require ( diff --git a/go.sum b/go.sum index aa12828..002748b 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -23,16 +21,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= @@ -48,12 +40,12 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/dnaeon/go-vcr.v3 v3.2.0 h1:Rltp0Vf+Aq0u4rQXgmXgtgoRDStTnFN83cWgSGSoRzM= +gopkg.in/dnaeon/go-vcr.v3 v3.2.0/go.mod h1:2IMOnnlx9I6u9x+YBsM3tAMx6AlOxnJ0pWxQAzZ79Ag= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 5b61eb4a4ea5ee1b991fd2239f449f4daba6a083 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Fri, 3 May 2024 23:30:26 -0500 Subject: [PATCH 24/30] Set integration tests behind an env var flag --- bonsai/client_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bonsai/client_test.go b/bonsai/client_test.go index 7031a0b..b738528 100644 --- a/bonsai/client_test.go +++ b/bonsai/client_test.go @@ -188,7 +188,9 @@ func (s *ClientVCRTestSuite) normalize(path string) string { } func TestClientVCRTestSuite(t *testing.T) { - suite.Run(t, new(ClientVCRTestSuite)) + if _, ok := os.LookupEnv("BONSAI_RUN_INTEGRATION_TESTS"); ok { + suite.Run(t, new(ClientVCRTestSuite)) + } } // ClientMockTestSuite is used for all mocked web requests. From 8c3637dc7727a08971984c44a243978b2b3e90ed Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Sat, 4 May 2024 00:02:01 -0500 Subject: [PATCH 25/30] This commit is essentially a no-op, as the impacted endpoints don't currently support pagination, but, the comparison that they referenced was incorrect --- bonsai/plan.go | 2 +- bonsai/release.go | 2 +- bonsai/space.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bonsai/plan.go b/bonsai/plan.go index 2f2cba0..796373e 100644 --- a/bonsai/plan.go +++ b/bonsai/plan.go @@ -231,7 +231,7 @@ func (c *PlanClient) All(ctx context.Context) ([]Plan, error) { } allResults = append(allResults, listResults...) - if len(allResults) >= resp.PageSize { + if len(allResults) >= resp.TotalRecords { resp.MarkPaginationComplete() } return resp, err diff --git a/bonsai/release.go b/bonsai/release.go index 7089087..edcbee1 100644 --- a/bonsai/release.go +++ b/bonsai/release.go @@ -107,7 +107,7 @@ func (c *ReleaseClient) All(ctx context.Context) ([]Release, error) { } allResults = append(allResults, listResults...) - if len(allResults) >= resp.PageSize { + if len(allResults) >= resp.TotalRecords { resp.MarkPaginationComplete() } return resp, err diff --git a/bonsai/space.go b/bonsai/space.go index 1f0e82f..97c1342 100644 --- a/bonsai/space.go +++ b/bonsai/space.go @@ -115,7 +115,7 @@ func (c *SpaceClient) All(ctx context.Context) ([]Space, error) { } allResults = append(allResults, listResults...) - if len(allResults) >= resp.PageSize { + if len(allResults) >= resp.TotalRecords { resp.MarkPaginationComplete() } return resp, err From 199c60c55844fcfadf739e9b468f3165cb0d9641 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Sat, 4 May 2024 06:48:47 -0500 Subject: [PATCH 26/30] Allow safe recording of fixture HTTP requests/responses (remove risky headers etc.). Run the VCR suite with REC_ONLY to update --- bonsai/bonsai_test.go | 4 +- bonsai/client_test.go | 46 +++--- bonsai/cluster_test.go | 2 +- ...lientVCRTestSuite-TestClusterClient-Create | 6 +- ...lientVCRTestSuite-TestClusterClient-Update | 2 +- .../TestClientVCRTestSuite-TestPlanClient-All | 87 +++++++++++ .../golden/TestClusterClient-All.yaml | 3 - .../golden/TestClusterClient-Create.yaml | 3 - .../golden/TestClusterClient-Delete.yaml | 3 - .../golden/TestClusterClient-GetBySlug.yaml | 3 - .../golden/TestClusterClient-Update.yaml | 3 - .../fixtures/golden/TestPlanClient-All.yaml | 3 - .../golden/TestPlanClient-GetByPath.yaml | 3 - .../golden/TestReleaseClient-All.yaml | 3 - .../golden/TestReleaseClient-GetByPath.yaml | 3 - .../fixtures/golden/TestSpaceClient-All.yaml | 3 - .../golden/TestSpaceClient-GetByPath.yaml | 3 - .../fixtures/vcr/TestClusterClient-All.yaml | 75 +++++++++ .../vcr/TestClusterClient-Create.yaml | 73 +++++++++ .../vcr/TestClusterClient-Delete.yaml | 73 +++++++++ .../vcr/TestClusterClient-GetBySlug.yaml | 75 +++++++++ .../vcr/TestClusterClient-Update.yaml | 143 ++++++++++++++++++ bonsai/fixtures/vcr/TestPlanClient-All.yaml | 75 +++++++++ .../vcr/TestPlanClient-GetByPath.yaml | 75 +++++++++ .../fixtures/vcr/TestReleaseClient-All.yaml | 75 +++++++++ .../vcr/TestReleaseClient-GetByPath.yaml | 75 +++++++++ bonsai/fixtures/vcr/TestSpaceClient-All.yaml | 75 +++++++++ .../vcr/TestSpaceClient-GetByPath.yaml | 75 +++++++++ bonsai/{vcr.go => vcr_test.go} | 51 ++++++- 29 files changed, 1059 insertions(+), 61 deletions(-) delete mode 100644 bonsai/fixtures/golden/TestClusterClient-All.yaml delete mode 100644 bonsai/fixtures/golden/TestClusterClient-Create.yaml delete mode 100644 bonsai/fixtures/golden/TestClusterClient-Delete.yaml delete mode 100644 bonsai/fixtures/golden/TestClusterClient-GetBySlug.yaml delete mode 100644 bonsai/fixtures/golden/TestClusterClient-Update.yaml delete mode 100644 bonsai/fixtures/golden/TestPlanClient-All.yaml delete mode 100644 bonsai/fixtures/golden/TestPlanClient-GetByPath.yaml delete mode 100644 bonsai/fixtures/golden/TestReleaseClient-All.yaml delete mode 100644 bonsai/fixtures/golden/TestReleaseClient-GetByPath.yaml delete mode 100644 bonsai/fixtures/golden/TestSpaceClient-All.yaml delete mode 100644 bonsai/fixtures/golden/TestSpaceClient-GetByPath.yaml create mode 100644 bonsai/fixtures/vcr/TestClusterClient-All.yaml create mode 100644 bonsai/fixtures/vcr/TestClusterClient-Create.yaml create mode 100644 bonsai/fixtures/vcr/TestClusterClient-Delete.yaml create mode 100644 bonsai/fixtures/vcr/TestClusterClient-GetBySlug.yaml create mode 100644 bonsai/fixtures/vcr/TestClusterClient-Update.yaml create mode 100644 bonsai/fixtures/vcr/TestPlanClient-All.yaml create mode 100644 bonsai/fixtures/vcr/TestPlanClient-GetByPath.yaml create mode 100644 bonsai/fixtures/vcr/TestReleaseClient-All.yaml create mode 100644 bonsai/fixtures/vcr/TestReleaseClient-GetByPath.yaml create mode 100644 bonsai/fixtures/vcr/TestSpaceClient-All.yaml create mode 100644 bonsai/fixtures/vcr/TestSpaceClient-GetByPath.yaml rename bonsai/{vcr.go => vcr_test.go} (51%) diff --git a/bonsai/bonsai_test.go b/bonsai/bonsai_test.go index 0d2d6c5..04be1e5 100644 --- a/bonsai/bonsai_test.go +++ b/bonsai/bonsai_test.go @@ -6,8 +6,6 @@ import ( "log/slog" "os" "path/filepath" - - "github.com/omc/bonsai-api-go/v1/bonsai" ) func init() { @@ -39,7 +37,7 @@ func initLogger() { func assertGolden(s *ClientVCRTestSuite, expected any) { s.T().Helper() - bonsai.AssertGolden( + AssertGolden( s.T(), filepath.Join("fixtures/golden/", s.normalize(s.T().Name())), s.update(s.T().Name()), diff --git a/bonsai/client_test.go b/bonsai/client_test.go index b738528..43f64cb 100644 --- a/bonsai/client_test.go +++ b/bonsai/client_test.go @@ -60,7 +60,7 @@ type ClientVCRTestSuite struct { } func (s *ClientVCRTestSuite) ReadOnlyRun() bool { - return s.recordMode == recorder.ModePassthrough + return s.recordMode == recorder.ModeReplayOnly } func (s *ClientVCRTestSuite) WillRecord() bool { return !s.ReadOnlyRun() @@ -72,10 +72,14 @@ func (s *ClientVCRTestSuite) SetRecorderMode(mode string) { s.recordMode = recorder.ModeRecordOnce case "REC_ONLY": s.recordMode = recorder.ModeRecordOnly + case "REPLAY_ONLY": + s.recordMode = recorder.ModeReplayOnly case "REPLAY_WITH_NEW": s.recordMode = recorder.ModeReplayWithNewEpisodes - default: + case "PASS_THROUGH": s.recordMode = recorder.ModePassthrough + default: + s.recordMode = recorder.ModeReplayOnly } } @@ -136,26 +140,34 @@ func (s *ClientVCRTestSuite) SetupSuite() { func (s *ClientVCRTestSuite) BeforeTest(_, testName string) { var err error - if s.WillRecord() { - s.recorder, err = recorder.NewWithOptions( - &recorder.Options{ - // filepath is os agnostic - CassetteName: filepath.Join("fixtures/golden/", s.normalize(testName)), - Mode: s.recordMode, - RealTransport: s.client.Transport(), - SkipRequestLatency: true, - }, - ) - if err != nil { - log.Fatalf("failed to create new recorder: %+v\n", err) - } + s.recorder, err = recorder.NewWithOptions( + &recorder.Options{ + // filepath is os agnostic + CassetteName: filepath.Join("fixtures/vcr/", s.normalize(testName)), + Mode: s.recordMode, + SkipRequestLatency: true, + }, + ) + if err != nil { + log.Fatalf("failed to create new recorder: %+v\n", err) + } + + if s.WillRecord() { // Add a hook which removes Authorization headers from all requests hook := func(i *cassette.Interaction) error { delete(i.Request.Headers, "Authorization") return nil } s.recorder.AddHook(hook, recorder.AfterCaptureHook) + + // Remove headers that might include secrets. + s.recorder.AddHook(riskyHeaderFilter, recorder.AfterCaptureHook) + + s.client.SetTransport(s.recorder) + } else { + s.recorder.SetReplayableInteractions(true) + s.client.SetTransport(s.recorder) } } @@ -188,9 +200,7 @@ func (s *ClientVCRTestSuite) normalize(path string) string { } func TestClientVCRTestSuite(t *testing.T) { - if _, ok := os.LookupEnv("BONSAI_RUN_INTEGRATION_TESTS"); ok { - suite.Run(t, new(ClientVCRTestSuite)) - } + suite.Run(t, new(ClientVCRTestSuite)) } // ClientMockTestSuite is used for all mocked web requests. diff --git a/bonsai/cluster_test.go b/bonsai/cluster_test.go index 08d0d0d..390d4df 100644 --- a/bonsai/cluster_test.go +++ b/bonsai/cluster_test.go @@ -491,7 +491,7 @@ func (s *ClientVCRTestSuite) TestClusterClient_Create() { func (s *ClientVCRTestSuite) TestClusterClient_Update() { ctx := context.Background() - plan, err := s.client.Cluster.Update(ctx, "bonsai-api-go-9994392953", bonsai.ClusterUpdateOpts{ + plan, err := s.client.Cluster.Update(ctx, "bonsai-api-go-2471463249", bonsai.ClusterUpdateOpts{ Name: "bonsai-api-go-test-cluster-updated", Plan: "standard-nano-comped", }) diff --git a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Create b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Create index 674c3b6..f221178 100644 --- a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Create +++ b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Create @@ -1,12 +1,12 @@ { "message": "Your cluster is being provisioned.", - "monitor": "https://api.bonsai.io/clusters/bonsai-api-go-9994392953", + "monitor": "https://api.bonsai.io/clusters/bonsai-api-go-2471463249", "access": { - "host": "bonsai-api-go-9994392953", + "host": "bonsai-api-go-2471463249", "port": 443, "scheme": "https", "user": "REDACTED", "pass": "REDACTED", - "url": "://REDACTED:REDACTED@" + "url": "://REDACTED:REDACTED" } } \ No newline at end of file diff --git a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Update b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Update index dd93530..b2195fa 100644 --- a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Update +++ b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestClusterClient-Update @@ -1,4 +1,4 @@ { "message": "Your cluster is being updated.", - "monitor": "https://api.bonsai.io/clusters/bonsai-api-go-9994392953" + "monitor": "https://api.bonsai.io/clusters/bonsai-api-go-2471463249" } \ No newline at end of file diff --git a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestPlanClient-All b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestPlanClient-All index 482b473..10ac2de 100644 --- a/bonsai/fixtures/golden/TestClientVCRTestSuite-TestPlanClient-All +++ b/bonsai/fixtures/golden/TestClientVCRTestSuite-TestPlanClient-All @@ -92,6 +92,93 @@ } ] }, + { + "slug": "sandbox-aws-us-east-1", + "name": "Sandbox", + "billing_interval_months": 1, + "available_releases": [ + { + "slug": "elasticsearch-7.10.2" + }, + { + "slug": "opensearch-2.6.0-mt" + } + ], + "available_spaces": [ + { + "path": "omc/bonsai-gcp/us-east4/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai-gcp/us-west1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-northeast-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/ap-southeast-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-central-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/eu-west-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/bonsai/us-west-2/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + }, + { + "path": "omc/websolr/us-east-1/common", + "private_network": false, + "cloud": { + "provider": "", + "region": "" + } + } + ] + }, { "slug": "standard-micro-aws-us-east-1", "name": "Standard Micro", diff --git a/bonsai/fixtures/golden/TestClusterClient-All.yaml b/bonsai/fixtures/golden/TestClusterClient-All.yaml deleted file mode 100644 index 2797c38..0000000 --- a/bonsai/fixtures/golden/TestClusterClient-All.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -version: 2 -interactions: [] diff --git a/bonsai/fixtures/golden/TestClusterClient-Create.yaml b/bonsai/fixtures/golden/TestClusterClient-Create.yaml deleted file mode 100644 index 2797c38..0000000 --- a/bonsai/fixtures/golden/TestClusterClient-Create.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -version: 2 -interactions: [] diff --git a/bonsai/fixtures/golden/TestClusterClient-Delete.yaml b/bonsai/fixtures/golden/TestClusterClient-Delete.yaml deleted file mode 100644 index 2797c38..0000000 --- a/bonsai/fixtures/golden/TestClusterClient-Delete.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -version: 2 -interactions: [] diff --git a/bonsai/fixtures/golden/TestClusterClient-GetBySlug.yaml b/bonsai/fixtures/golden/TestClusterClient-GetBySlug.yaml deleted file mode 100644 index 2797c38..0000000 --- a/bonsai/fixtures/golden/TestClusterClient-GetBySlug.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -version: 2 -interactions: [] diff --git a/bonsai/fixtures/golden/TestClusterClient-Update.yaml b/bonsai/fixtures/golden/TestClusterClient-Update.yaml deleted file mode 100644 index 2797c38..0000000 --- a/bonsai/fixtures/golden/TestClusterClient-Update.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -version: 2 -interactions: [] diff --git a/bonsai/fixtures/golden/TestPlanClient-All.yaml b/bonsai/fixtures/golden/TestPlanClient-All.yaml deleted file mode 100644 index 2797c38..0000000 --- a/bonsai/fixtures/golden/TestPlanClient-All.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -version: 2 -interactions: [] diff --git a/bonsai/fixtures/golden/TestPlanClient-GetByPath.yaml b/bonsai/fixtures/golden/TestPlanClient-GetByPath.yaml deleted file mode 100644 index 2797c38..0000000 --- a/bonsai/fixtures/golden/TestPlanClient-GetByPath.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -version: 2 -interactions: [] diff --git a/bonsai/fixtures/golden/TestReleaseClient-All.yaml b/bonsai/fixtures/golden/TestReleaseClient-All.yaml deleted file mode 100644 index 2797c38..0000000 --- a/bonsai/fixtures/golden/TestReleaseClient-All.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -version: 2 -interactions: [] diff --git a/bonsai/fixtures/golden/TestReleaseClient-GetByPath.yaml b/bonsai/fixtures/golden/TestReleaseClient-GetByPath.yaml deleted file mode 100644 index 2797c38..0000000 --- a/bonsai/fixtures/golden/TestReleaseClient-GetByPath.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -version: 2 -interactions: [] diff --git a/bonsai/fixtures/golden/TestSpaceClient-All.yaml b/bonsai/fixtures/golden/TestSpaceClient-All.yaml deleted file mode 100644 index 2797c38..0000000 --- a/bonsai/fixtures/golden/TestSpaceClient-All.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -version: 2 -interactions: [] diff --git a/bonsai/fixtures/golden/TestSpaceClient-GetByPath.yaml b/bonsai/fixtures/golden/TestSpaceClient-GetByPath.yaml deleted file mode 100644 index 2797c38..0000000 --- a/bonsai/fixtures/golden/TestSpaceClient-GetByPath.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -version: 2 -interactions: [] diff --git a/bonsai/fixtures/vcr/TestClusterClient-All.yaml b/bonsai/fixtures/vcr/TestClusterClient-All.yaml new file mode 100644 index 0000000..e42b384 --- /dev/null +++ b/bonsai/fixtures/vcr/TestClusterClient-All.yaml @@ -0,0 +1,75 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.bonsai.io + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - bonsai-api-go/v1 bonsai-api-go/1.0.0 + url: https://api.bonsai.io/clusters + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"pagination":{"page_number":1,"page_size":1000,"total_records":1},"clusters":[{"slug":"dcek-group-llc-5240651189","name":"DCEK Group, LLC search","uri":"https://api.bonsai.io/clusters/dcek-group-llc-5240651189","plan":{"slug":"sandbox-aws-us-east-1","uri":"https://api.bonsai.io/plans/sandbox-aws-us-east-1"},"release":{"service_type":"elasticsearch","package_name":"7.10.2","version":"7.10.2","slug":"elasticsearch-7.10.2","uri":"https://api.bonsai.io/releases/elasticsearch-7.10.2"},"space":{"path":"omc/bonsai/us-east-1/common","region":"aws-us-east-1","uri":"https://api.bonsai.io/spaces/omc/bonsai/us-east-1/common"},"stats":{"docs":2,"shards_used":2,"data_bytes_used":17984},"access":{"host":"dcek-group-llc-5240651189.us-east-1.bonsaisearch.net","port":443,"scheme":"https"},"state":"PROVISIONED"}]}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Security-Policy: + - 'default-src ''self'' https:; font-src ''self'' https: data:; img-src ''self'' https: data:; object-src ''none''; script-src ''self'' https: ''unsafe-inline''; style-src ''self'' https: ''unsafe-inline''; connect-src ''self'' http: wss://*.bonsaisearch.net' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 04 May 2024 11:42:50 GMT + Etag: + - W/"067d073e09ae505a2c52df89d915b0af" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - Cowboy + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Vary: + - Accept,Accept-Encoding + Via: + - 1.1 vegur + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 625ee3ed-ab85-4fde-8d5d-97d7d564680d + X-Runtime: + - "0.046763" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: 292.8763ms diff --git a/bonsai/fixtures/vcr/TestClusterClient-Create.yaml b/bonsai/fixtures/vcr/TestClusterClient-Create.yaml new file mode 100644 index 0000000..e9d8881 --- /dev/null +++ b/bonsai/fixtures/vcr/TestClusterClient-Create.yaml @@ -0,0 +1,73 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 137 + transfer_encoding: [] + trailer: {} + host: api.bonsai.io + remote_addr: "" + request_uri: "" + body: '{"name":"bonsai-api-go-test-cluster","plan":"standard-nano-comped","space":"omc/bonsai/us-east-1/common","release":"opensearch-2.6.0-mt"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - bonsai-api-go/v1 bonsai-api-go/1.0.0 + url: https://api.bonsai.io/clusters + method: POST + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"message":"Your cluster is being provisioned.","monitor":"https://api.bonsai.io/clusters/bonsai-api-go-2471463249","access":{"user":"REDACTED","pass":"REDACTED","host":"bonsai-api-go-2471463249","port":443,"scheme":"https","url":"://REDACTED:REDACTED"},"status":202}' + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Security-Policy: + - 'default-src ''self'' https:; font-src ''self'' https: data:; img-src ''self'' https: data:; object-src ''none''; script-src ''self'' https: ''unsafe-inline''; style-src ''self'' https: ''unsafe-inline''; connect-src ''self'' http: wss://*.bonsaisearch.net' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 04 May 2024 11:42:50 GMT + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - Cowboy + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Vary: + - Accept,Accept-Encoding + Via: + - 1.1 vegur + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 12370295-cde9-46cb-a6bc-f23903e5a4dd + X-Runtime: + - "0.098205" + X-Xss-Protection: + - 1; mode=block + status: 202 Accepted + code: 202 + duration: 130.6072ms diff --git a/bonsai/fixtures/vcr/TestClusterClient-Delete.yaml b/bonsai/fixtures/vcr/TestClusterClient-Delete.yaml new file mode 100644 index 0000000..aee25ba --- /dev/null +++ b/bonsai/fixtures/vcr/TestClusterClient-Delete.yaml @@ -0,0 +1,73 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.bonsai.io + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - bonsai-api-go/v1 bonsai-api-go/1.0.0 + url: https://api.bonsai.io/clusters/bonsai-api-go-9994392953 + method: DELETE + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"message":"Your cluster is being deprovisioned.","monitor":"https://api.bonsai.io/clusters/bonsai-api-go-9994392953","status":202}' + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Security-Policy: + - 'default-src ''self'' https:; font-src ''self'' https: data:; img-src ''self'' https: data:; object-src ''none''; script-src ''self'' https: ''unsafe-inline''; style-src ''self'' https: ''unsafe-inline''; connect-src ''self'' http: wss://*.bonsaisearch.net' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 04 May 2024 11:42:50 GMT + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - Cowboy + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Vary: + - Accept,Accept-Encoding + Via: + - 1.1 vegur + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - bc1f6723-f11b-4653-b1c5-84588880905e + X-Runtime: + - "0.035741" + X-Xss-Protection: + - 1; mode=block + status: 202 Accepted + code: 202 + duration: 71.2158ms diff --git a/bonsai/fixtures/vcr/TestClusterClient-GetBySlug.yaml b/bonsai/fixtures/vcr/TestClusterClient-GetBySlug.yaml new file mode 100644 index 0000000..c15b3e3 --- /dev/null +++ b/bonsai/fixtures/vcr/TestClusterClient-GetBySlug.yaml @@ -0,0 +1,75 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.bonsai.io + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - bonsai-api-go/v1 bonsai-api-go/1.0.0 + url: https://api.bonsai.io/clusters/dcek-group-llc-5240651189 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"cluster":{"slug":"dcek-group-llc-5240651189","name":"DCEK Group, LLC search","uri":"https://api.bonsai.io/clusters/dcek-group-llc-5240651189","plan":{"slug":"sandbox-aws-us-east-1","uri":"https://api.bonsai.io/plans/sandbox-aws-us-east-1"},"release":{"service_type":"elasticsearch","package_name":"7.10.2","version":"7.10.2","slug":"elasticsearch-7.10.2","uri":"https://api.bonsai.io/releases/elasticsearch-7.10.2"},"space":{"path":"omc/bonsai/us-east-1/common","region":"aws-us-east-1","uri":"https://api.bonsai.io/spaces/omc/bonsai/us-east-1/common"},"stats":{"docs":2,"shards_used":2,"data_bytes_used":17984},"access":{"host":"dcek-group-llc-5240651189.us-east-1.bonsaisearch.net","port":443,"scheme":"https"},"state":"PROVISIONED"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Security-Policy: + - 'default-src ''self'' https:; font-src ''self'' https: data:; img-src ''self'' https: data:; object-src ''none''; script-src ''self'' https: ''unsafe-inline''; style-src ''self'' https: ''unsafe-inline''; connect-src ''self'' http: wss://*.bonsaisearch.net' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 04 May 2024 11:42:50 GMT + Etag: + - W/"c289f3e152b59209b26bcc8f03b2a044" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - Cowboy + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Vary: + - Accept,Accept-Encoding + Via: + - 1.1 vegur + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 8f04bb3f-6396-4cdf-8041-04f0ded81abe + X-Runtime: + - "0.039003" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: 68.2324ms diff --git a/bonsai/fixtures/vcr/TestClusterClient-Update.yaml b/bonsai/fixtures/vcr/TestClusterClient-Update.yaml new file mode 100644 index 0000000..d47d509 --- /dev/null +++ b/bonsai/fixtures/vcr/TestClusterClient-Update.yaml @@ -0,0 +1,143 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 75 + transfer_encoding: [] + trailer: {} + host: api.bonsai.io + remote_addr: "" + request_uri: "" + body: '{"name":"bonsai-api-go-test-cluster-updated","plan":"standard-nano-comped"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - bonsai-api-go/v1 bonsai-api-go/1.0.0 + url: https://api.bonsai.io/clusters/bonsai-api-go-9994392953 + method: PUT + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"errors":["This cluster is no longer active and cannot be modified. Solution: Please select a different cluster.","Your request could not be processed. "],"status":422}' + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Security-Policy: + - 'default-src ''self'' https:; font-src ''self'' https: data:; img-src ''self'' https: data:; object-src ''none''; script-src ''self'' https: ''unsafe-inline''; style-src ''self'' https: ''unsafe-inline''; connect-src ''self'' http: wss://*.bonsaisearch.net' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 04 May 2024 11:42:50 GMT + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - Cowboy + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Vary: + - Accept,Accept-Encoding + Via: + - 1.1 vegur + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 67072137-6c4a-4c65-966e-2a8ea65d9668 + X-Runtime: + - "0.043148" + X-Xss-Protection: + - 1; mode=block + status: 422 Unprocessable Entity + code: 422 + duration: 73.4967ms + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 75 + transfer_encoding: [] + trailer: {} + host: api.bonsai.io + remote_addr: "" + request_uri: "" + body: '{"name":"bonsai-api-go-test-cluster-updated","plan":"standard-nano-comped"}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - bonsai-api-go/v1 bonsai-api-go/1.0.0 + url: https://api.bonsai.io/clusters/bonsai-api-go-2471463249 + method: PUT + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"message":"Your cluster is being updated.","monitor":"https://api.bonsai.io/clusters/bonsai-api-go-2471463249","status":202}' + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Security-Policy: + - 'default-src ''self'' https:; font-src ''self'' https: data:; img-src ''self'' https: data:; object-src ''none''; script-src ''self'' https: ''unsafe-inline''; style-src ''self'' https: ''unsafe-inline''; connect-src ''self'' http: wss://*.bonsaisearch.net' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 04 May 2024 11:43:53 GMT + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - Cowboy + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Vary: + - Accept,Accept-Encoding + Via: + - 1.1 vegur + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - c95dcfbe-00c0-4dcc-a600-f366362c4073 + X-Runtime: + - "0.126076" + X-Xss-Protection: + - 1; mode=block + status: 202 Accepted + code: 202 + duration: 310.9072ms diff --git a/bonsai/fixtures/vcr/TestPlanClient-All.yaml b/bonsai/fixtures/vcr/TestPlanClient-All.yaml new file mode 100644 index 0000000..b9b2c6f --- /dev/null +++ b/bonsai/fixtures/vcr/TestPlanClient-All.yaml @@ -0,0 +1,75 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.bonsai.io + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - bonsai-api-go/v1 bonsai-api-go/1.0.0 + url: https://api.bonsai.io/plans + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"plans":[{"slug":"standard-nano-comped","name":"Standard Nano","price_in_cents":0,"billing_interval_in_months":1,"single_tenant":false,"private_network":false,"available_releases":["elasticsearch-5.6.16","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"sandbox-aws-us-east-1","name":"Sandbox","price_in_cents":0,"billing_interval_in_months":1,"single_tenant":false,"private_network":false,"available_releases":["elasticsearch-7.10.2","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"standard-micro-aws-us-east-1","name":"Standard Micro","price_in_cents":2000,"billing_interval_in_months":1,"single_tenant":false,"private_network":false,"available_releases":["elasticsearch-5.6.16","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"business-capacity-2x-aws-us-east-1","name":"Business Capacity 2X","price_in_cents":200000,"billing_interval_in_months":1,"single_tenant":true,"private_network":false,"available_releases":["elasticsearch-2.4.0","elasticsearch-5.6.16","elasticsearch-6.3.2","elasticsearch-6.4.2","elasticsearch-6.5.4","elasticsearch-6.8.17","elasticsearch-6.8.19","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-1.2.4","opensearch-2.3.0","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"business-performance-xl-aws-us-east-1","name":"Business Performance XL","price_in_cents":95000,"billing_interval_in_months":1,"single_tenant":true,"private_network":false,"available_releases":["elasticsearch-2.4.0","elasticsearch-5.6.16","elasticsearch-6.3.2","elasticsearch-6.4.2","elasticsearch-6.5.4","elasticsearch-6.8.17","elasticsearch-6.8.19","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-1.2.4","opensearch-2.3.0","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"business-performance-lg-aws-us-east-1","name":"Business Performance LG","price_in_cents":70000,"billing_interval_in_months":1,"single_tenant":true,"private_network":false,"available_releases":["elasticsearch-2.4.0","elasticsearch-5.6.16","elasticsearch-6.3.2","elasticsearch-6.4.2","elasticsearch-6.5.4","elasticsearch-6.8.17","elasticsearch-6.8.19","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-1.2.4","opensearch-2.3.0","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"standard-md-aws-us-east-1","name":"Standard MD","price_in_cents":25000,"billing_interval_in_months":1,"single_tenant":false,"private_network":false,"available_releases":["elasticsearch-5.6.16","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"business-private-capacity-xl-aws-us-east-1","name":"Business Private Capacity XL","price_in_cents":135000,"billing_interval_in_months":1,"single_tenant":true,"private_network":true,"available_releases":["elasticsearch-2.4.0","elasticsearch-5.6.16","elasticsearch-6.3.2","elasticsearch-6.4.2","elasticsearch-6.5.4","elasticsearch-6.8.17","elasticsearch-6.8.19","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-1.2.4","opensearch-2.3.0","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"business-performance-2x-aws-us-east-1","name":"Business Performance 2X","price_in_cents":140000,"billing_interval_in_months":1,"single_tenant":true,"private_network":false,"available_releases":["elasticsearch-2.4.0","elasticsearch-5.6.16","elasticsearch-6.3.2","elasticsearch-6.4.2","elasticsearch-6.5.4","elasticsearch-6.8.17","elasticsearch-6.8.19","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-1.2.4","opensearch-2.3.0","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"business-private-capacity-2x-aws-us-east-1","name":"Business Private Capacity 2X","price_in_cents":210000,"billing_interval_in_months":1,"single_tenant":true,"private_network":true,"available_releases":["elasticsearch-2.4.0","elasticsearch-5.6.16","elasticsearch-6.3.2","elasticsearch-6.4.2","elasticsearch-6.5.4","elasticsearch-6.8.17","elasticsearch-6.8.19","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-1.2.4","opensearch-2.3.0","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"business-private-performance-lg-aws-us-east-1","name":"Business Private Performance LG","price_in_cents":80000,"billing_interval_in_months":1,"single_tenant":true,"private_network":true,"available_releases":["elasticsearch-2.4.0","elasticsearch-5.6.16","elasticsearch-6.3.2","elasticsearch-6.4.2","elasticsearch-6.5.4","elasticsearch-6.8.17","elasticsearch-6.8.19","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-1.2.4","opensearch-2.3.0","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"standard-nano-aws-us-east-1","name":"Standard Nano","price_in_cents":5000,"billing_interval_in_months":1,"single_tenant":false,"private_network":false,"available_releases":["elasticsearch-5.6.16","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"business-private-capacity-lg-aws-us-east-1","name":"Business Private Capacity LG","price_in_cents":95000,"billing_interval_in_months":1,"single_tenant":true,"private_network":true,"available_releases":["elasticsearch-2.4.0","elasticsearch-5.6.16","elasticsearch-6.3.2","elasticsearch-6.4.2","elasticsearch-6.5.4","elasticsearch-6.8.17","elasticsearch-6.8.19","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-1.2.4","opensearch-2.3.0","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"business-capacity-xl-aws-us-east-1","name":"Business Capacity XL","price_in_cents":125000,"billing_interval_in_months":1,"single_tenant":true,"private_network":false,"available_releases":["elasticsearch-2.4.0","elasticsearch-5.6.16","elasticsearch-6.3.2","elasticsearch-6.4.2","elasticsearch-6.5.4","elasticsearch-6.8.17","elasticsearch-6.8.19","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-1.2.4","opensearch-2.3.0","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"business-private-performance-xl-aws-us-east-1","name":"Business Private Performance XL","price_in_cents":105000,"billing_interval_in_months":1,"single_tenant":true,"private_network":true,"available_releases":["elasticsearch-2.4.0","elasticsearch-5.6.16","elasticsearch-6.3.2","elasticsearch-6.4.2","elasticsearch-6.5.4","elasticsearch-6.8.17","elasticsearch-6.8.19","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-1.2.4","opensearch-2.3.0","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"standard-sm-aws-us-east-1","name":"Standard SM","price_in_cents":15000,"billing_interval_in_months":1,"single_tenant":false,"private_network":false,"available_releases":["elasticsearch-5.6.16","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"business-capacity-lg-aws-us-east-1","name":"Business Capacity LG","price_in_cents":85000,"billing_interval_in_months":1,"single_tenant":true,"private_network":false,"available_releases":["elasticsearch-2.4.0","elasticsearch-5.6.16","elasticsearch-6.3.2","elasticsearch-6.4.2","elasticsearch-6.5.4","elasticsearch-6.8.17","elasticsearch-6.8.19","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-1.2.4","opensearch-2.3.0","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]},{"slug":"business-private-performance-2x-aws-us-east-1","name":"Business Private Performance 2X","price_in_cents":150000,"billing_interval_in_months":1,"single_tenant":true,"private_network":true,"available_releases":["elasticsearch-2.4.0","elasticsearch-5.6.16","elasticsearch-6.3.2","elasticsearch-6.4.2","elasticsearch-6.5.4","elasticsearch-6.8.17","elasticsearch-6.8.19","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-1.2.4","opensearch-2.3.0","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]}]}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Security-Policy: + - 'default-src ''self'' https:; font-src ''self'' https: data:; img-src ''self'' https: data:; object-src ''none''; script-src ''self'' https: ''unsafe-inline''; style-src ''self'' https: ''unsafe-inline''; connect-src ''self'' http: wss://*.bonsaisearch.net' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 04 May 2024 11:45:13 GMT + Etag: + - W/"eb7ff9161ac36190f5335eeb3f123fa3" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - Cowboy + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Vary: + - Accept,Accept-Encoding + Via: + - 1.1 vegur + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 0b81e0d8-fe54-4ee4-ae00-22e37a0c08fe + X-Runtime: + - "0.137879" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: 391.1983ms diff --git a/bonsai/fixtures/vcr/TestPlanClient-GetByPath.yaml b/bonsai/fixtures/vcr/TestPlanClient-GetByPath.yaml new file mode 100644 index 0000000..3306de3 --- /dev/null +++ b/bonsai/fixtures/vcr/TestPlanClient-GetByPath.yaml @@ -0,0 +1,75 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.bonsai.io + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - bonsai-api-go/v1 bonsai-api-go/1.0.0 + url: https://api.bonsai.io/plans/standard-micro-aws-us-east-1 + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"slug":"standard-micro-aws-us-east-1","name":"Standard Micro","price_in_cents":2000,"billing_interval_in_months":1,"single_tenant":false,"private_network":false,"available_releases":["elasticsearch-5.6.16","elasticsearch-6.8.21","elasticsearch-7.10.2","opensearch-2.6.0-mt"],"available_spaces":["omc/bonsai-gcp/us-east4/common","omc/bonsai-gcp/us-west1/common","omc/bonsai/ap-northeast-1/common","omc/bonsai/ap-southeast-2/common","omc/bonsai/eu-central-1/common","omc/bonsai/eu-west-1/common","omc/bonsai/us-east-1/common","omc/bonsai/us-west-2/common","omc/websolr/us-east-1/common"]}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Security-Policy: + - 'default-src ''self'' https:; font-src ''self'' https: data:; img-src ''self'' https: data:; object-src ''none''; script-src ''self'' https: ''unsafe-inline''; style-src ''self'' https: ''unsafe-inline''; connect-src ''self'' http: wss://*.bonsaisearch.net' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 04 May 2024 11:45:18 GMT + Etag: + - W/"b4359d6cf7e2ba32b5676a1136f6c033" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - Cowboy + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Vary: + - Accept,Accept-Encoding + Via: + - 1.1 vegur + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 2ddf841d-7b51-4dce-934b-3e67818e497a + X-Runtime: + - "0.034832" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: 285.9613ms diff --git a/bonsai/fixtures/vcr/TestReleaseClient-All.yaml b/bonsai/fixtures/vcr/TestReleaseClient-All.yaml new file mode 100644 index 0000000..9d6f37f --- /dev/null +++ b/bonsai/fixtures/vcr/TestReleaseClient-All.yaml @@ -0,0 +1,75 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.bonsai.io + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - bonsai-api-go/v1 bonsai-api-go/1.0.0 + url: https://api.bonsai.io/releases + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"releases":[{"name":"Elasticsearch 2.4.0","slug":"elasticsearch-2.4.0","service_type":"elasticsearch","version":"2.4.0","package_name":"2.4.0","multitenant":false},{"name":"Elasticsearch 5.6.16","slug":"elasticsearch-5.6.16","service_type":"elasticsearch","version":"5.6.16","package_name":"5.6.16","multitenant":true},{"name":"Elasticsearch 6.3.2","slug":"elasticsearch-6.3.2","service_type":"elasticsearch","version":"6.3.2","package_name":"6.3.2","multitenant":false},{"name":"Elasticsearch 6.4.2","slug":"elasticsearch-6.4.2","service_type":"elasticsearch","version":"6.4.2","package_name":"6.4.2","multitenant":false},{"name":"Elasticsearch 6.5.4","slug":"elasticsearch-6.5.4","service_type":"elasticsearch","version":"6.5.4","package_name":"6.5.4","multitenant":false},{"name":"Elasticsearch 6.8.17","slug":"elasticsearch-6.8.17","service_type":"elasticsearch","version":"6.8.17","package_name":"6.8.17","multitenant":false},{"name":"Elasticsearch 6.8.19","slug":"elasticsearch-6.8.19","service_type":"elasticsearch","version":"6.8.19","package_name":"6.8.19","multitenant":false},{"name":"Elasticsearch 6.8.21","slug":"elasticsearch-6.8.21","service_type":"elasticsearch","version":"6.8.21","package_name":"6.8.21","multitenant":true},{"name":"Elasticsearch 7.10.2","slug":"elasticsearch-7.10.2","service_type":"elasticsearch","version":"7.10.2","package_name":"7.10.2","multitenant":true},{"name":"OpenSearch 1.2.4","slug":"opensearch-1.2.4","service_type":"opensearch","version":"1.2.4","package_name":"1.2.4","multitenant":false},{"name":"OpenSearch 2.3.0","slug":"opensearch-2.3.0","service_type":"opensearch","version":"2.3.0","package_name":"2.3.0","multitenant":false},{"name":"OpenSearch 2.6.0","slug":"opensearch-2.6.0-mt","service_type":"opensearch","version":"2.6.0","package_name":"2.6.0-mt","multitenant":true}]}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Security-Policy: + - 'default-src ''self'' https:; font-src ''self'' https: data:; img-src ''self'' https: data:; object-src ''none''; script-src ''self'' https: ''unsafe-inline''; style-src ''self'' https: ''unsafe-inline''; connect-src ''self'' http: wss://*.bonsaisearch.net' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 04 May 2024 11:45:08 GMT + Etag: + - W/"d99fed993c4b49c72477901154e07d7a" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - Cowboy + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Vary: + - Accept,Accept-Encoding + Via: + - 1.1 vegur + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 612abd70-820a-468f-8c96-95c4767d1456 + X-Runtime: + - "0.044478" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: 198.2537ms diff --git a/bonsai/fixtures/vcr/TestReleaseClient-GetByPath.yaml b/bonsai/fixtures/vcr/TestReleaseClient-GetByPath.yaml new file mode 100644 index 0000000..8605693 --- /dev/null +++ b/bonsai/fixtures/vcr/TestReleaseClient-GetByPath.yaml @@ -0,0 +1,75 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.bonsai.io + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - bonsai-api-go/v1 bonsai-api-go/1.0.0 + url: https://api.bonsai.io/releases/opensearch-2.6.0-mt + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"name":"OpenSearch 2.6.0","slug":"opensearch-2.6.0-mt","service_type":"opensearch","version":"2.6.0","package_name":"2.6.0-mt","multitenant":true}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Security-Policy: + - 'default-src ''self'' https:; font-src ''self'' https: data:; img-src ''self'' https: data:; object-src ''none''; script-src ''self'' https: ''unsafe-inline''; style-src ''self'' https: ''unsafe-inline''; connect-src ''self'' http: wss://*.bonsaisearch.net' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 04 May 2024 11:45:04 GMT + Etag: + - W/"6f986805763c0fe363ac7d2d71e16321" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - Cowboy + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Vary: + - Accept,Accept-Encoding + Via: + - 1.1 vegur + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - e0e6cf3e-fa65-4edf-88b4-fb21536654aa + X-Runtime: + - "0.037590" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: 218.2436ms diff --git a/bonsai/fixtures/vcr/TestSpaceClient-All.yaml b/bonsai/fixtures/vcr/TestSpaceClient-All.yaml new file mode 100644 index 0000000..2a1dd96 --- /dev/null +++ b/bonsai/fixtures/vcr/TestSpaceClient-All.yaml @@ -0,0 +1,75 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.bonsai.io + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - bonsai-api-go/v1 bonsai-api-go/1.0.0 + url: https://api.bonsai.io/spaces + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"spaces":[{"path":"omc/bonsai/eu-west-1/common","private_network":false,"cloud":{"provider":"aws","region":"aws-eu-west-1"}},{"path":"omc/bonsai/ap-southeast-2/common","private_network":false,"cloud":{"provider":"aws","region":"aws-ap-southeast-2"}},{"path":"omc/bonsai/eu-central-1/common","private_network":false,"cloud":{"provider":"aws","region":"aws-eu-central-1"}},{"path":"omc/bonsai/us-east-1/common","private_network":false,"cloud":{"provider":"aws","region":"aws-us-east-1"}},{"path":"omc/bonsai-gcp/us-west1/common","private_network":false,"cloud":{"provider":"gcp","region":"gcp-us-west1"}},{"path":"omc/bonsai-gcp/us-east4/common","private_network":false,"cloud":{"provider":"gcp","region":"gcp-us-east4"}},{"path":"omc/bonsai/us-west-2/common","private_network":false,"cloud":{"provider":"aws","region":"aws-us-west-2"}},{"path":"omc/bonsai/ap-northeast-1/common","private_network":false,"cloud":{"provider":"aws","region":"aws-ap-northeast-1"}},{"path":"omc/websolr/us-east-1/common","private_network":false,"cloud":{"provider":"aws","region":"aws-us-east-1"}}]}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Security-Policy: + - 'default-src ''self'' https:; font-src ''self'' https: data:; img-src ''self'' https: data:; object-src ''none''; script-src ''self'' https: ''unsafe-inline''; style-src ''self'' https: ''unsafe-inline''; connect-src ''self'' http: wss://*.bonsaisearch.net' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 04 May 2024 11:44:54 GMT + Etag: + - W/"9ba8925afc58e91e133d9a2003ae88ed" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - Cowboy + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Vary: + - Accept,Accept-Encoding + Via: + - 1.1 vegur + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - abf6205a-c77e-4784-8137-85dd7d034836 + X-Runtime: + - "0.182905" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: 434.5154ms diff --git a/bonsai/fixtures/vcr/TestSpaceClient-GetByPath.yaml b/bonsai/fixtures/vcr/TestSpaceClient-GetByPath.yaml new file mode 100644 index 0000000..d2dbcdf --- /dev/null +++ b/bonsai/fixtures/vcr/TestSpaceClient-GetByPath.yaml @@ -0,0 +1,75 @@ +--- +version: 2 +interactions: + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.bonsai.io + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - bonsai-api-go/v1 bonsai-api-go/1.0.0 + url: https://api.bonsai.io/spaces/omc/bonsai/eu-west-1/common + method: GET + response: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + transfer_encoding: + - chunked + trailer: {} + content_length: -1 + uncompressed: true + body: '{"path":"omc/bonsai/eu-west-1/common","private_network":false,"cloud":{"provider":"aws","region":"aws-eu-west-1"}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Security-Policy: + - 'default-src ''self'' https:; font-src ''self'' https: data:; img-src ''self'' https: data:; object-src ''none''; script-src ''self'' https: ''unsafe-inline''; style-src ''self'' https: ''unsafe-inline''; connect-src ''self'' http: wss://*.bonsaisearch.net' + Content-Type: + - application/json; charset=utf-8 + Date: + - Sat, 04 May 2024 11:41:36 GMT + Etag: + - W/"fd791cbb84387e9be5e89cb367a1fd95" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - Cowboy + Strict-Transport-Security: + - max-age=63072000; includeSubDomains + Vary: + - Accept,Accept-Encoding + Via: + - 1.1 vegur + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 93559cfc-0182-43b1-beb4-6c9f36075f3f + X-Runtime: + - "0.057102" + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: 459.6812ms diff --git a/bonsai/vcr.go b/bonsai/vcr_test.go similarity index 51% rename from bonsai/vcr.go rename to bonsai/vcr_test.go index a9d1fb2..8039904 100644 --- a/bonsai/vcr.go +++ b/bonsai/vcr_test.go @@ -17,15 +17,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -package bonsai +package bonsai_test import ( "encoding/json" + "net/http" "os" "path/filepath" + "strings" "testing" "github.com/google/go-cmp/cmp" + "gopkg.in/dnaeon/go-vcr.v3/cassette" ) func AssertGolden(t testing.TB, path string, update bool, want any) { @@ -37,7 +40,7 @@ func AssertGolden(t testing.TB, path string, update bool, want any) { if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { t.Fatalf("failed to update golden file %q: %s", path, err) } - if err := os.WriteFile(path, data, 0o640); err != nil { //nolint:gosec // writing files for VCR reading + if err := os.WriteFile(path, data, 0o640); err != nil { t.Fatalf("failed to update golden file %q: %s", path, err) } } @@ -68,3 +71,47 @@ func marshal(t testing.TB, v any) []byte { return data } } + +// riskyHeaderFilter deletes anything that looks risky in request and response +// headers. +func riskyHeaderFilter(i *cassette.Interaction) error { + for _, headers := range []http.Header{i.Request.Headers, i.Response.Headers} { + for name, values := range headers { + if IsRiskyHeader(name, values) { + delete(headers, name) + } + } + } + return nil +} + +// IsRiskyHeader returns true if the request or response header is likely to contain private data. +func IsRiskyHeader(name string, values []string) bool { + return isRiskyHeaderName(name) || containsRiskyHeaderValue(values) +} + +// isRiskyHeaderName returns true if the request or response header is likely to contain private data +// based on its name. +func isRiskyHeaderName(name string) bool { + riskyHeaderKeys := []string{"auth", "cookie", "token", "heroku"} + for _, riskyKey := range riskyHeaderKeys { + if strings.Contains(strings.ToLower(name), riskyKey) { + return true + } + } + return false +} + +// ContainsRiskyHeaderValue returns true if the values array of a request or response header +// looks like it's likely to contain private data. +func containsRiskyHeaderValue(values []string) bool { + riskyHeaderValues := []string{"bearer", "ghp_", "glpat-", "heroku"} + for _, value := range values { + for _, riskyValue := range riskyHeaderValues { + if strings.Contains(strings.ToLower(value), riskyValue) { + return true + } + } + } + return false +} From 83ae2a166eb427c4bd5bd9401122bc05acebc41a Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Sat, 4 May 2024 07:15:11 -0500 Subject: [PATCH 27/30] LICENSE was copy pasted as I knew Hashicorp had a markdown version of the license; this repo has no source/affiliation from any hashicorp repository or code. --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 15eba9d..4d33ccb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2021 HashiCorp, Inc. +Copyright (c) 2024 One More Cloud, Inc. Mozilla Public License Version 2.0 ================================== From 46f30928ebf736fd7aee23b82802bcc2e4746bcc Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Mon, 6 May 2024 15:20:47 -0500 Subject: [PATCH 28/30] Add clusters demo; update imports for v1 - will need to add back in versions for versions >= 2 --- README.md | 28 +++++++++++++++++----------- examples/clusters/main.go | 32 ++++++++++++++++++++++++++++++++ examples/go.mod | 14 ++++++++++++++ examples/go.sum | 10 ++++++++++ 4 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 examples/clusters/main.go create mode 100644 examples/go.mod create mode 100644 examples/go.sum diff --git a/README.md b/README.md index b98d65c..68ffeb2 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Full documentation is available on godoc at https://pkg.go.dev/github.com/omc/bo ## Installation ```shell -go get github.com/omc/bonsai-api-go/v1 +go get github.com/omc/bonsai-api-go/bonsai ``` ## Example @@ -34,27 +34,33 @@ package main import ( "context" - "fmt" "log" + "os" - "github.com/omc/bonsai-api-go/v1/bonsai" + "github.com/omc/bonsai-api-go/bonsai" ) func main() { - token, err := bonsai.NewToken("TestToken") - if err != nil { - log.Fatal(fmt.Errorf("invalid token: %w", err)) - } - + // Fetch API Key and Token from environment variables + apiKey := os.Getenv("BONSAI_API_KEY") + apiToken := os.Getenv("BONSAI_API_TOKEN") + + // Create a new Bonsai API Client client := bonsai.NewClient( - bonsai.WithToken(token), + bonsai.WithCredentialPair( + bonsai.CredentialPair{ + AccessKey: bonsai.AccessKey(apiKey), + AccessToken: bonsai.AccessToken(apiToken), + }, + ), ) - clusters, _, err := client.Clusters.All(context.Background()) + // Fetch all of our clusters + clusters, err := client.Cluster.All(context.Background()) if err != nil { log.Fatalf("error listing clusters: %s\n", err) } - log.Printf("Found %d clusters!\n", len(clusters)) + log.Printf("Found %d clusters! Details: %v\n", len(clusters), clusters) } ``` diff --git a/examples/clusters/main.go b/examples/clusters/main.go new file mode 100644 index 0000000..df6b9aa --- /dev/null +++ b/examples/clusters/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "context" + "encoding/json" + "log" + "os" + + "github.com/omc/bonsai-api-go/bonsai" +) + +func main() { + apiKey := os.Getenv("BONSAI_API_KEY") + apiToken := os.Getenv("BONSAI_API_TOKEN") + + client := bonsai.NewClient( + bonsai.WithCredentialPair( + bonsai.CredentialPair{ + AccessKey: bonsai.AccessKey(apiKey), + AccessToken: bonsai.AccessToken(apiToken), + }, + ), + ) + + clusters, err := client.Cluster.All(context.Background()) + if err != nil { + log.Fatalf("error listing clusters: %s\n", err) + } + + asJson, err := json.MarshalIndent(clusters, "", " ") + log.Printf("Found %d clusters! Details: %v\n", len(clusters), string(asJson)) +} diff --git a/examples/go.mod b/examples/go.mod new file mode 100644 index 0000000..1ae912b --- /dev/null +++ b/examples/go.mod @@ -0,0 +1,14 @@ +module main + +go 1.22.2 + +require github.com/omc/bonsai-api-go v0.0.0-20240503161100-502dbac72ff9 + +require ( + github.com/google/go-querystring v1.1.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect +) + +replace github.com/omc/bonsai-api-go => ../ diff --git a/examples/go.sum b/examples/go.sum new file mode 100644 index 0000000..b5320b9 --- /dev/null +++ b/examples/go.sum @@ -0,0 +1,10 @@ +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 422c2a97e356804bc8914b8d60dd95993011171a Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Mon, 6 May 2024 15:59:23 -0500 Subject: [PATCH 29/30] Add TESTING.md with details on running/adding to the test suite --- SECURITY.md | 3 +++ TESTING.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 SECURITY.md create mode 100644 TESTING.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..dd53d2e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,3 @@ +# Security Policy + +We support the most recent two Go releases (for example, Go 1.21.x and Go 1.22.x when Go 1.22.x is the latest stable release). \ No newline at end of file diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..7bc66f2 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,73 @@ +# Testing + +## Test Suites + +This project makes use of the `_test` idiom as mentioned in +[pkg.go.dev/testing](https://pkg.go.dev/testing@master). + +All external API testing of the Bonsai API Go Client is done via unit tests +and integration tests in the `bonsai_test` package. + +Certain internal API functions and data structures are tested within the +`bonsai` package. + +For all suites, we use the [testify toolkit](https://github.com/stretchr/testify). + +### Running tests + +Run all tests: + +```shell +go test ./... +``` + +#### Re-recording Integration test HTTP interactions + +By default, all integration tests will run in `REPLAY_ONLY` mode, and +don't require any API credentials in order to test against the existing +recorded Bonsai API HTTP responses and marshaled results in +[bonsai/fixtures](bonsai/fixtures). + +To re-record these interactions, set these environment variables: + +- `BONSAI_REC_MODE`: Determines the recording strategy. + - One of: + `REC_ONCE`, `REC_ONLY`, `REPLAY_ONLY` (default), `REPLAY_WITH_NEW`, or `PASS_THROUGH`. + - See [go-vcr's godoc](https://pkg.go.dev/gopkg.in/dnaeon/go-vcr.v3/recorder#Mode) + for more details on recording strategies. +- `BONSAI_API_KEY`: Must be present +- `BONSAI_API_TOKEN`: Must be present + +### Adding tests + +Ensure that new tests are associated with a Testify `suite`; there are existing +suites for both internal and external API testing which should fit most use cases. + +#### Internal API + +- Where possible, name test files with the `$filename_impl_test.go` convention. +- The `ClientImplTestSuite` should satisfy all internal testing needs. +- Example @ [bonsai/client_impl_test.go](bonsai/client_impl_test.go) + + +#### External API + +- External API testing should be the primary location for unit and integration tests. +- Name files with the standard `$filename_test.go` convention. + +##### Unit Tests + +- Unit tests shouldn't interact with any external API endpoints, +and are expected to mock all HTTP responses if needed. +- The `ClientMockTestSuite` should be preferred for all unit tests in +the external API. +- Example @ [bonsai/cluster_test.go](bonsai/cluster_test.go) + +##### Integration Tests + +- Integration tests rely on the [go-vcr](https://github.com/dnaeon/go-vcr) +package to enable recording and replaying of all HTTP interactions in the +test suite. +- The `ClientVCRTestSuite` should be preferred for all integration tests - +that is, any external HTTP interaction - in the external API. +- Example @ [bonsai/cluster_test.go](bonsai/cluster_test.go) From 6c8397bc729b1e909eba9c00277c361d9aef36b1 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Mon, 6 May 2024 16:15:52 -0500 Subject: [PATCH 30/30] Move README#Contributing to CONTRIBUTING.md --- CONTRIBUTING.md | 17 +++++++++++++++++ README.md | 18 ------------------ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e69de29..19a4449 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -0,0 +1,17 @@ +# Contributing + +## Pre-commit + +This project uses [pre-commit](https://pre-commit.com/) to lint and store 3rd-party dependency licenses. +Installation instructions are available on the [pre-commit](https://pre-commit.com/) website! + +To verify your installation, run this project's pre-commit hooks against all files: + +```shell +pre-commit run --all-files +``` + +### Go-licenses pre-commit hook + +Windows users: Ensure that you have `C:\Program Files\Git\usr\bin` added +to your `PATH`! \ No newline at end of file diff --git a/README.md b/README.md index 68ffeb2..b5e3fa4 100644 --- a/README.md +++ b/README.md @@ -63,21 +63,3 @@ func main() { log.Printf("Found %d clusters! Details: %v\n", len(clusters), clusters) } ``` - -## Contributing - -### Pre-commit - -This project uses [pre-commit](https://pre-commit.com/) to lint and store 3rd-party dependency licenses. -Installation instructions are available on the [pre-commit](https://pre-commit.com/) website! - -To verify your installation, run this project's pre-commit hooks against all files: - -```shell -pre-commit run --all-files -``` - -#### Go-licenses pre-commit hook - -Windows users: Ensure that you have `C:\Program Files\Git\usr\bin` added -to your `PATH`! \ No newline at end of file