diff --git a/go.mod b/go.mod index dc22a78d..d6f5a0dc 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,11 @@ module github.com/splunk/terraform-provider-splunk -go 1.17 +go 1.18 require ( github.com/google/go-querystring v1.0.0 github.com/hashicorp/terraform-plugin-sdk v1.15.0 + github.com/splunk/go-splunk-client v0.0.1 ) require ( diff --git a/go.sum b/go.sum index 246aa41d..c71d92bb 100644 --- a/go.sum +++ b/go.sum @@ -179,6 +179,8 @@ github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/splunk/go-splunk-client v0.0.1 h1:f4VW1vKqpTUCd13fYQ5/vdohZliolHAEIKfcVOgajdw= +github.com/splunk/go-splunk-client v0.0.1/go.mod h1:PBd+U0yLaO2WQTV2uOkvSKRA9tIQA8f7gikMIPCXEDw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= @@ -248,10 +250,8 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/vendor/github.com/splunk/go-splunk-client/LICENSE b/vendor/github.com/splunk/go-splunk-client/LICENSE new file mode 100644 index 00000000..3152df83 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/LICENSE @@ -0,0 +1,202 @@ + + 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/vendor/github.com/splunk/go-splunk-client/NOTICE b/vendor/github.com/splunk/go-splunk-client/NOTICE new file mode 100644 index 00000000..d9060285 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/NOTICE @@ -0,0 +1,81 @@ +Go SDK for Splunk +Copyright 2022 Splunk, 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. + + +======================== Go Language Library ========================== +This product bundles the Go Language library, which is available under +a "3-clause BSD" license, hosted at https://cs.opensource.google/go/x/text. + +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. + + +======================== Go Networking Library ======================== + +This product bundles the Go Networking library, which is available under +a "3-clause BSD" license, hosted at https://cs.opensource.google/go/x/net. + +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/vendor/github.com/splunk/go-splunk-client/pkg/attributes/attributes.go b/vendor/github.com/splunk/go-splunk-client/pkg/attributes/attributes.go new file mode 100644 index 00000000..6495f65a --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/attributes/attributes.go @@ -0,0 +1,96 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package attributes provides types that can represent explicitly set values, +// including the zero values of their stored types, to enable HTTP requests to +// account for all intended parameter values. +package attributes + +import ( + "encoding/json" + "fmt" + "strconv" +) + +// Storable defines the types storable by ExplicitValue. +type Storable interface { + bool | int | string +} + +// Explicit is permits storing a value such that it can be explicitly empty/zero. +type Explicit[T Storable] struct { + set bool + value T +} + +// GetURLValue implements custom encoding of its url.Values value. +func (e Explicit[T]) GetURLValue() interface{} { + return e.value +} + +// Set explicitly sets the value. +func (e *Explicit[T]) Set(value T) { + e.set = true + e.value = value +} + +// NewExplicit returns a new Explicit with its value explicitly set. +func NewExplicit[T Storable](value T) (newExplicitValue Explicit[T]) { + newExplicitValue.Set(value) + + return +} + +// UnmarshalJSON implements custom JSON unmarshaling. The unmarshaled is explicitly set. +func (e *Explicit[T]) UnmarshalJSON(data []byte) error { + var newValue T + + if err := json.Unmarshal(data, &newValue); err != nil { + return err + } + + e.Set(newValue) + + return nil +} + +// Bool returns a value indicating the boolean representation of the stored value, and +// another boolean that will be true only if the value was explicitly set, and it can +// be parsed by strconv.ParseBool without error. +func (e Explicit[T]) Bool() (value bool, ok bool) { + if !e.set { + return + } + + strValue := fmt.Sprint(e.value) + value, err := strconv.ParseBool(strValue) + ok = err == nil + + return +} + +// Value returns the stored value. +func (e Explicit[T]) Value() T { + return e.value +} + +// ValueOk returns the stored value and a boolean indicating if it was explicitly set. +func (e Explicit[T]) ValueOk() (T, bool) { + return e.value, e.set +} + +// String returns the defualt string representation of the stored value. +func (e Explicit[T]) String() string { + return fmt.Sprint(e.value) +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/attributes/named_parameters.go b/vendor/github.com/splunk/go-splunk-client/pkg/attributes/named_parameters.go new file mode 100644 index 00000000..08556847 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/attributes/named_parameters.go @@ -0,0 +1,111 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attributes + +import ( + "encoding/json" + "fmt" + "reflect" +) + +// NamedParameters represent a set of Parameters that are associated with an overall Name. +type NamedParameters struct { + // Name is the overall name of this set of Parameters. It is likely the leftmost segment + // of a dotted parameter name, such as "actions" for "actions.email". + Name string `values:"-"` + + // Status is the string representation of a NamedParameters' status. This is typically + // true/false or 0/1, and is the value associated directly with the name segment, such as + // email=true. + Status Explicit[string] `values:",omitzero,anonymize"` + + Parameters Parameters `values:",omitzero,anonymize"` +} + +// GetURLKey implements custom key encoding for url.Values. +func (params NamedParameters) GetURLKey(parentKey string, childKey string) (string, error) { + if params.Name == "" { + return "", fmt.Errorf("attributes: unable to determine url.Values key for empty NamedParameters.Name") + } + + return fmt.Sprintf("%s.%s", parentKey, params.Name), nil +} + +// NamedParametersCollection is a collection of NamedParameters. +type NamedParametersCollection []NamedParameters + +// EnabledNames returns a list of Names of the member NamedParameters that have a true Status value. +func (collection NamedParametersCollection) EnabledNames() []string { + var enabled []string + + for _, params := range collection { + if isEnabled, ok := params.Status.Bool(); ok && isEnabled { + enabled = append(enabled, params.Name) + } + } + + return enabled +} + +// UnmarshalJSONForNamedParametersCollections unmarshals JSON data into the given dest interface. dest must be +// a pointer to a struct, and any struct fields with the "named_parameters_collection" tag must be of the +// NamedParametersCollection type. +// +// This method exists to enable unmarshaling of the same level of a JSON document to a struct and also +// to NamedParametersCollection fields of the same struct. +func UnmarshalJSONForNamedParametersCollections(data []byte, dest interface{}) error { + destVPtr := reflect.ValueOf(dest) + if destVPtr.Kind() != reflect.Ptr { + return fmt.Errorf("attempted UnmarshalJSONForNamedParametersCollection on non-pointer type: %T", dest) + } + + destV := destVPtr.Elem() + destT := destV.Type() + + if destT.Kind() != reflect.Struct { + return fmt.Errorf("attempted UnmarshalJSONForNamedParametersCollection on non-struct type: %T", dest) + } + + for i := 0; i < destT.NumField(); i++ { + fieldF := destT.Field(i) + if !fieldF.IsExported() { + continue + } + + fieldTag := fieldF.Tag.Get("named_parameters_collection") + if fieldTag == "" { + continue + } + + var collection NamedParametersCollection + if fieldF.Type != reflect.TypeOf(collection) { + return fmt.Errorf("attempted UnmarshalJSONForNamedParametersCollection on non-NamedParametersCollection type %T for field %s", destV.Field(i).Interface(), fieldF.Name) + } + + var allParams Parameters + if err := json.Unmarshal(data, &allParams); err != nil { + return err + } + + newParams := allParams.withDottedName(fieldTag) + + newCollection := newParams.namedParametersCollection() + newCollectionV := reflect.ValueOf(newCollection) + + destV.Field(i).Set(newCollectionV) + } + + return nil +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/attributes/parameters.go b/vendor/github.com/splunk/go-splunk-client/pkg/attributes/parameters.go new file mode 100644 index 00000000..13599ae2 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/attributes/parameters.go @@ -0,0 +1,210 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package attributes + +import ( + "encoding/json" + "fmt" + "net/url" + "reflect" + "sort" + "strings" +) + +// dottedParameterNameParts parses a dotted parameter name and returns the first segment as name, +// and the remaining segments as paramName. If there are not multiple segments, name will be empty. +// +// For example: +// +// dottedParameterNameParts("name") +// # ("name", "") +// +// dottedParameterNameParts("actions.email.to") +// # ("actions", "email.to") +func dottedParameterNameParts(fullFieldName string) (name string, paramName string) { + parts := strings.Split(fullFieldName, ".") + + return parts[0], strings.Join(parts[1:], ".") +} + +// Parameters is a map of parameter names to string values. +type Parameters map[string]string + +// withDottedName returns a new Parameters object containing the nested parameters +// for the given name. The new Parameters name field will have this name prefix removed. +// +// For example: +// +// Parameters{"action.email": "true", "action.email.to": "whoever@example.com"}.withDottedName("action") +// # Parameters{"email": "true", "email.to": "whoever@example.com"} +func (p Parameters) withDottedName(name string) Parameters { + var newParameters Parameters + + for key, value := range p { + fieldName, fieldParamName := dottedParameterNameParts(key) + + if fieldName == name && fieldParamName != "" { + if newParameters == nil { + newParameters = Parameters{} + } + + newParameters[fieldParamName] = value + } + } + + return newParameters +} + +// namedParametersWithDottedName returns a NamedParameters with the given name and Status and Parameter values +// as calculated from the input Parameters. +// +// Parameters{"email":"true","email.to":"whoever@example.com"}.namedParametersWithDottedName("email") +// # NamedParameters{Name: "email", Status: "true", Parameters{"to": "whoever@example.com"}} +func (p Parameters) namedParametersWithDottedName(name string) NamedParameters { + newParams := NamedParameters{ + Name: name, + Parameters: p.withDottedName(name), + } + + // Status only set explicitly if there was a key is present in Parameters + if statusValue, ok := p[name]; ok { + newParams.Status = NewExplicit(statusValue) + } + + return newParams +} + +// dottedNames returns the list of top-level names of fields in Parameters. +func (p Parameters) dottedNames() []string { + foundNamesMap := map[string]bool{} + var foundNames []string + + for key := range p { + fieldName, _ := dottedParameterNameParts(key) + + if _, ok := foundNamesMap[fieldName]; !ok { + foundNames = append(foundNames, fieldName) + } + foundNamesMap[fieldName] = true + } + + sort.Strings(foundNames) + + return foundNames +} + +// namedParametersCollection returns a NamedParametersCollection containing a NamedParameters object +// for each top-level name of Parameters. +func (p Parameters) namedParametersCollection() NamedParametersCollection { + names := p.dottedNames() + var newCollection NamedParametersCollection + + for _, name := range names { + newCollection = append(newCollection, p.namedParametersWithDottedName(name)) + } + + return newCollection +} + +// UnmarshalJSON implements custom JSON unmarshaling which assumes the content being unmarshaled is a simple map of strings +// to a single value (string, bool, float, int). It returns an error if a value other than these types is encountered. +func (p *Parameters) UnmarshalJSON(data []byte) error { + interfaceMap := map[string]interface{}{} + if err := json.Unmarshal(data, &interfaceMap); err != nil { + return err + } + + newP := Parameters{} + for key, value := range interfaceMap { + valueV := reflect.ValueOf(value) + if !valueV.IsValid() { + // invalid reflect.Value indicates a nil value, which we can safely ignore + continue + } + + switch valueV.Kind() { + default: + return fmt.Errorf("unable to unmarshal unhandled type %T into Parameters for key %s", value, key) + case reflect.String, reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + newP[key] = fmt.Sprintf("%v", value) + } + } + + if len(newP) == 0 { + return nil + } + + *p = newP + + return nil +} + +// SetURLValues implements custom encoding into url.Values. +func (p Parameters) SetURLValues(key string, v *url.Values) error { + for paramKey, paramValue := range p { + valueKey := strings.Join([]string{key, paramKey}, ".") + + v.Set(valueKey, paramValue) + } + + return nil +} + +// UnmarshalJSONForParameters unmarshals JSON data into the given dest interface. dest must be a pointer +// to a struct, and any struct fields with the "parameters" tag must be of the Parameters type. +// +// This method exists to enable unmarshaling of the same level of a JSON document to a struct and also +// to Parameters fields of the same struct. +func UnmarshalJSONForParameters(data []byte, dest interface{}) error { + destVPtr := reflect.ValueOf(dest) + if destVPtr.Kind() != reflect.Ptr { + return fmt.Errorf("attempted UnmarshalJSONForParameters on non-pointer type: %T", dest) + } + + destV := destVPtr.Elem() + destT := destV.Type() + + if destT.Kind() != reflect.Struct { + return fmt.Errorf("attempted UnmarshalJSONForParameters on non-struct type: %T", dest) + } + + for i := 0; i < destT.NumField(); i++ { + fieldF := destT.Field(i) + if !fieldF.IsExported() { + continue + } + + fieldTag := fieldF.Tag.Get("parameters") + if fieldTag == "" { + continue + } + + allParams := make(Parameters) + if fieldF.Type != reflect.TypeOf(allParams) { + return fmt.Errorf("attempted UnmarshalJSONForParameters on non-Parameters type %T for field %s", destV.Field(i).Interface(), fieldF.Name) + } + + if err := json.Unmarshal(data, &allParams); err != nil { + return err + } + + newParams := allParams.withDottedName(fieldTag) + newParamsV := reflect.ValueOf(newParams) + + destV.Field(i).Set(newParamsV) + } + + return nil +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/authenticator_test_cases.go b/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/authenticator_test_cases.go new file mode 100644 index 00000000..c19b6412 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/authenticator_test_cases.go @@ -0,0 +1,57 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package authenticators + +import ( + "net/http" + "testing" + + "github.com/splunk/go-splunk-client/pkg/client" + "github.com/splunk/go-splunk-client/pkg/internal/checks" +) + +// AuthenticatorTestCase defines a test against a specific Authenticator and Client. +type AuthenticatorTestCase struct { + name string + inputAuthenticator client.Authenticator + inputClient *client.Client + wantError bool + requestCheck checks.CheckRequestFunc +} + +// test performs a AuthenticatorTestCase's defined test. +func (test AuthenticatorTestCase) test(t *testing.T) { + r := &http.Request{} + err := test.inputAuthenticator.AuthenticateRequest(test.inputClient, r) + gotError := err != nil + + if gotError != test.wantError { + t.Errorf("%s AuthenticateRequest returned error? %v", test.name, gotError) + } + + if test.requestCheck != nil { + test.requestCheck(r, t) + } +} + +// AuthenticatorTestCases is a list of AuthenticatorTestCase instances. +type AuthenticatorTestCases []AuthenticatorTestCase + +// test performs the test for each of its AuthenticatorTestCase items. +func (tests AuthenticatorTestCases) test(t *testing.T) { + for _, test := range tests { + test.test(t) + } +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/authenticators.go b/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/authenticators.go new file mode 100644 index 00000000..cbe205cb --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/authenticators.go @@ -0,0 +1,17 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package authenticators provides types that authenticate to the Splunk REST API +// and satisfy the client.Authenticator interface. +package authenticators diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/password.go b/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/password.go new file mode 100644 index 00000000..a488c91e --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/password.go @@ -0,0 +1,94 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package authenticators + +import ( + "net/http" + "sync" + + "github.com/splunk/go-splunk-client/pkg/client" +) + +// Password defines password authentication to Splunk. +type Password struct { + Username string `values:"username"` + Password string `values:"password"` + + // UseBasicAuth can be set to true if Basic Authentication should always be used, + // which causes Username/Password to be passed with each authenticated request. + UseBasicAuth bool `values:"-"` + + // SessionKey holds the SessionKey after initial authentication occurs. Unless + // UseBasicAuth is set to true, this SessionKey will be used to authenticate requests. + SessionKey `url:"-"` + + // mu is used to enable locking to prevent race conditions when checking for and obtaining + // a SessionKey. + mu sync.Mutex + + // empty Namespace used to satisfy service.ServicePathGetter + _ client.Namespace `service:"auth/login"` +} + +// loginResponse represents the response returned from auth/login. +type loginResponse struct { + SessionKey +} + +// authenticate performs the authentication request and handles the response, storing the SessionKey +// if successful. +func (p *Password) authenticate(c *client.Client) error { + lR := loginResponse{} + + if err := c.RequestAndHandle( + client.ComposeRequestBuilder( + client.BuildRequestMethod(http.MethodPost), + client.BuildRequestServiceURL(c, p), + client.BuildRequestBodyValues(p), + ), + client.ComposeResponseHandler( + client.HandleResponseCode(http.StatusUnauthorized, client.HandleResponseXMLMessagesCustomError(client.ErrorUnauthorized)), + client.HandleResponseRequireCode(http.StatusOK, client.HandleResponseXMLMessagesError()), + client.HandleResponseXML(&lR), + ), + ); err != nil { + return err + } + + p.SessionKey = lR.SessionKey + + return nil +} + +// authenticateOnce calls authenticate only if currently unauthenticated. +func (p *Password) authenticateOnce(c *client.Client) error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.authenticated() { + return p.authenticate(c) + } + + return nil +} + +// AuthenticateRequest adds authentication to an http.Request. +func (p *Password) AuthenticateRequest(c *client.Client, r *http.Request) error { + if err := p.authenticateOnce(c); err != nil { + return err + } + + return p.SessionKey.AuthenticateRequest(c, r) +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/session_key.go b/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/session_key.go new file mode 100644 index 00000000..3f55b854 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/session_key.go @@ -0,0 +1,48 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package authenticators + +import ( + "fmt" + "net/http" + + "github.com/splunk/go-splunk-client/pkg/client" +) + +// SessionKey provides authentication to Splunk via a session key. +type SessionKey struct { + // SessionKey is the session key that will be used to authenticate to Splunk. + SessionKey string `xml:"sessionKey"` +} + +// authenticated returns true if SessionKey is not empty. +func (s SessionKey) authenticated() bool { + return s.SessionKey != "" +} + +// AuthenticateRequest adds the SessionKey to the http.Request's Header. +func (s SessionKey) AuthenticateRequest(c *client.Client, r *http.Request) error { + if !s.authenticated() { + return fmt.Errorf("attempted to authenticate request with empty SessionKey") + } + + if r.Header == nil { + r.Header = http.Header{} + } + + r.Header.Add("Authorization", fmt.Sprintf("Splunk %s", s.SessionKey)) + + return nil +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/token.go b/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/token.go new file mode 100644 index 00000000..f3ff7c90 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/authenticators/token.go @@ -0,0 +1,43 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package authenticators + +import ( + "fmt" + "net/http" + + "github.com/splunk/go-splunk-client/pkg/client" +) + +// Token provides authentication to Splunk via a bearer token. +type Token struct { + // Token is the token that will be used to authenticate to Splunk. + Token string +} + +// AuthenticateRequest adds the Token to the http.Request's Header. +func (t Token) AuthenticateRequest(c *client.Client, r *http.Request) error { + if t.Token == "" { + return fmt.Errorf("attempted to authenticate request with empty Token") + } + + if r.Header == nil { + r.Header = http.Header{} + } + + r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", t.Token)) + + return nil +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/client/acl.go b/vendor/github.com/splunk/go-splunk-client/pkg/client/acl.go new file mode 100644 index 00000000..4561cd12 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/client/acl.go @@ -0,0 +1,63 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "encoding/json" + + "github.com/splunk/go-splunk-client/pkg/attributes" +) + +// Sharing represents the level of sharing of a Splunk object. +type Sharing string + +const ( + SharingUndefined Sharing = "" + SharingGlobal Sharing = "global" + SharingUser Sharing = "user" + SharingApp Sharing = "app" +) + +// validate returns an error if Sharing is a value other than the predefined Sharing constants. +func (sharing Sharing) validate() error { + switch sharing { + default: + return wrapError(ErrorSharing, nil, "client: invalid Sharing value %q", sharing) + case SharingUndefined, SharingGlobal, SharingUser, SharingApp: + return nil + } +} + +// MarshalJSON implements custom marshaling. +func (sharing Sharing) MarshalJSON() ([]byte, error) { + if err := sharing.validate(); err != nil { + return nil, err + } + + return json.Marshal(string(sharing)) +} + +// Permissions represents the read/write permissions of a Splunk object. +type Permissions struct { + Read []string `json:"read" values:"read,omitzero,fillempty"` + Write []string `json:"write" values:"write,omitzero,fillempty"` +} + +// ACL represents the ACL of a Splunk object. +type ACL struct { + Permissions Permissions `json:"perms" values:"perms"` + Owner attributes.Explicit[string] `json:"owner" values:"owner,omitzero"` + Sharing Sharing `json:"sharing" values:"sharing,omitzero"` +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/client/authenticator.go b/vendor/github.com/splunk/go-splunk-client/pkg/client/authenticator.go new file mode 100644 index 00000000..d6e2e484 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/client/authenticator.go @@ -0,0 +1,22 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import "net/http" + +// Authenticators are capable of adding authentication to requests. +type Authenticator interface { + AuthenticateRequest(*Client, *http.Request) error +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/client/build_request.go b/vendor/github.com/splunk/go-splunk-client/pkg/client/build_request.go new file mode 100644 index 00000000..af63ee19 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/client/build_request.go @@ -0,0 +1,186 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "io" + "net/http" + "net/url" + "strings" + + "github.com/splunk/go-splunk-client/pkg/selective" + "github.com/splunk/go-splunk-client/pkg/service" + "github.com/splunk/go-splunk-client/pkg/values" +) + +// defaultStatusCodes set the expected StatusCodes for most CRUD operations. If +// a given type doesn't override them, responses will be checked against these +// codes. +var defaultStatusCodes = service.StatusCodes{ + Created: http.StatusCreated, + Read: http.StatusOK, + Updated: http.StatusOK, + Deleted: http.StatusOK, + NotFound: http.StatusNotFound, +} + +// RequestBuilder defines a function that performs an operation on an http.Request. +type RequestBuilder func(*http.Request) error + +// ComposeRequestBuilder creates a new RequestBuilder that performs each RequestBuilder +// provided as an argument, returning the first error encountered, if any. +func ComposeRequestBuilder(builders ...RequestBuilder) RequestBuilder { + return func(r *http.Request) error { + for _, builder := range builders { + if err := builder(r); err != nil { + return err + } + } + + return nil + } +} + +// buildRequest creates a new http.Request and applies the provided RequestBuilder. +func buildRequest(builder RequestBuilder) (*http.Request, error) { + r := &http.Request{} + + if err := builder(r); err != nil { + return nil, err + } + + return r, nil +} + +// BuildRequestMethod returns a RequestBuilder that sets the given method. +func BuildRequestMethod(method string) RequestBuilder { + return func(r *http.Request) error { + r.Method = method + + return nil + } +} + +// BuildRequestServiceURL returns a RequestBuilder that sets the URL to the ServiceURL +// for a given Service. +func BuildRequestServiceURL(c *Client, service interface{}) RequestBuilder { + return func(r *http.Request) error { + u, err := c.ServiceURL(service) + if err != nil { + return err + } + + r.URL = u + + return nil + } +} + +// BuildRequestBodyValues returns a RequestBuilder that sets the Body to the encoded url.Values for +// a given interface. +func BuildRequestBodyValues(i interface{}) RequestBuilder { + return func(r *http.Request) error { + v, err := values.Encode(i) + if err != nil { + return wrapError(ErrorValues, err, err.Error()) + } + + r.Body = io.NopCloser(strings.NewReader(v.Encode())) + + return nil + } +} + +// BuildRequestOutputModeJSON returns a RequestBuilder that sets the URL's RawQuery to output_mode=json. +// It checks that the URL is already set, so it must be applied after setting the URL. It overwrites +// any existing RawQuery Values. +func BuildRequestOutputModeJSON() RequestBuilder { + return func(r *http.Request) error { + if r.URL == nil { + return wrapError(ErrorNilValue, nil, "unable to set output mode on nil URL") + } + + if r.URL.RawQuery != "" { + return wrapError(ErrorOverwriteValue, nil, "attempted to set output_mode after RawQuery already set") + } + + r.URL.RawQuery = url.Values{ + "output_mode": []string{"json"}, + }.Encode() + + return nil + } +} + +// BuildRequestBodyValuesSelective returns a RequestBuilder that sets the Body to the encoded url.Values +// for a given interface and selective tag. +func BuildRequestBodyValuesSelective(c interface{}, tag string) RequestBuilder { + return func(r *http.Request) error { + selected, err := selective.Encode(c, tag) + if err != nil { + return err + } + + return BuildRequestBodyValues(selected)(r) + } +} + +// BuildRequestCollectionURL returns a RequestBuilder that sets the URL to the EntryURL +// for a given Entry. +func BuildRequestEntryURL(c *Client, entry interface{}) RequestBuilder { + return func(r *http.Request) error { + u, err := c.EntryURL(entry) + if err != nil { + return err + } + + r.URL = u + + return nil + } +} + +// BuildRequestEntryACLURL returns a RequestBuilder that sets the URL to the ACL URL +// for a given Entry. +func BuildRequestEntryACLURL(c *Client, entry interface{}, acl ACL) RequestBuilder { + return func(r *http.Request) error { + u, err := c.EntryACLURL(entry) + if err != nil { + return err + } + + r.URL = u + + return nil + } +} + +// BuildRequestAuthenticate returns a RequestBuilder that authenticates a request for a given Client. +func BuildRequestAuthenticate(c *Client) RequestBuilder { + return func(r *http.Request) error { + return c.Authenticator.AuthenticateRequest(c, r) + } +} + +// BuildRequestGetServiceStatusCodes updates codes for the given entry. It returns a RequestBuilder +// that returns the error (if any) returned by service.ServiceStatusCodes. +func BuildRequestGetServiceStatusCodes(entry interface{}, codes *service.StatusCodes) RequestBuilder { + newCodes, err := service.ServiceStatusCodes(entry, defaultStatusCodes) + *codes = newCodes + + return func(r *http.Request) error { + return err + } +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/client/client.go b/vendor/github.com/splunk/go-splunk-client/pkg/client/client.go new file mode 100644 index 00000000..b259eb41 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/client/client.go @@ -0,0 +1,332 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package client implements a client to the Splunk REST API. +package client + +import ( + "crypto/tls" + "net/http" + "net/http/cookiejar" + "net/url" + "reflect" + "sync" + "time" + + "github.com/splunk/go-splunk-client/pkg/deepset" + "github.com/splunk/go-splunk-client/pkg/internal/paths" + "github.com/splunk/go-splunk-client/pkg/service" + "golang.org/x/net/publicsuffix" +) + +const ( + defaultTimeout = time.Minute * 5 +) + +// Client defines connectivity and authentication to a Splunk REST API. +type Client struct { + // URL is the URL to the Splunk REST API. It should include the scheme and port number. + // + // Example: + // https://localhost:8089 + URL string + + // Authenticator defines which authentication method and credentials to use. + // + // Example: + // authenticators.Password{Username: "admin", Password: "changeme"} + Authenticator + + // Set TLSInsecureSkipVerify to true to skip TLS verification. + TLSInsecureSkipVerify bool + + // Timeout configures the timeout of requests. If unspecified, defaults to 5 minutes. + Timeout time.Duration + + httpClient *http.Client + mu sync.Mutex +} + +// urlForPath returns a url.URL for path, relative to Client's URL. +func (c *Client) urlForPath(path ...string) (*url.URL, error) { + if c.URL == "" { + return nil, wrapError(ErrorMissingURL, nil, "Client has empty URL") + } + + combinedPath := paths.Join(path...) + + u := paths.Join(c.URL, combinedPath) + + return url.Parse(u) +} + +// ServiceURL returns a url.URL for a Service, relative to the Client's URL. +func (c *Client) ServiceURL(s interface{}) (*url.URL, error) { + servicePath, err := service.ServicePath(s) + if err != nil { + return nil, err + } + + return c.urlForPath(servicePath) +} + +// EntryURL returns a url.URL for an Entry, relative to the Client's URL. +func (c *Client) EntryURL(e interface{}) (*url.URL, error) { + entryPath, err := service.EntryPath(e) + if err != nil { + return nil, err + } + + return c.urlForPath(entryPath) +} + +func (c *Client) EntryACLURL(e interface{}) (*url.URL, error) { + entryPath, err := service.EntryPath(e) + if err != nil { + return nil, err + } + + return c.urlForPath(entryPath, "acl") +} + +// httpClientPrep prepares the Client's http.Client. +func (c *Client) httpClientPrep() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.httpClient == nil { + jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) + if err != nil { + return wrapError(ErrorHTTPClient, err, "unable to create new cookiejar: %s", err) + } + + timeout := c.Timeout + if timeout == 0 { + timeout = defaultTimeout + } + + c.httpClient = &http.Client{ + Timeout: timeout, + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: c.TLSInsecureSkipVerify, + }, + }, + Jar: jar, + } + } + + return nil +} + +// do performs a given http.Request via the Client's http.Client. +func (c *Client) do(r *http.Request) (*http.Response, error) { + if err := c.httpClientPrep(); err != nil { + return nil, err + } + + resp, err := c.httpClient.Do(r) + if err != nil { + return nil, wrapError(ErrorHTTPClient, err, "error encountered performing request: %s", err) + } + + return resp, nil +} + +// RequestAndHandle creates a new http.Request from the given RequestBuilder, performs the +// request, and handles the http.Response with the given ResponseHandler. +func (c *Client) RequestAndHandle(builder RequestBuilder, handler ResponseHandler) error { + req, err := buildRequest(builder) + if err != nil { + return err + } + + resp, err := c.do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + return handler(resp) +} + +// Create performs a Create action for the given Entry. +func (client *Client) Create(entry interface{}) error { + var codes service.StatusCodes + + return client.RequestAndHandle( + ComposeRequestBuilder( + BuildRequestGetServiceStatusCodes(entry, &codes), + BuildRequestMethod(http.MethodPost), + BuildRequestServiceURL(client, entry), + BuildRequestOutputModeJSON(), + BuildRequestBodyValuesSelective(entry, "create"), + BuildRequestAuthenticate(client), + ), + ComposeResponseHandler( + HandleResponseRequireCode(codes.Created, HandleResponseJSONMessagesError()), + ), + ) +} + +// Read performs a Read action for the given Entry. It modifies entry in-place, +// so entry must be a pointer. +func (client *Client) Read(entry interface{}) error { + var codes service.StatusCodes + + return client.RequestAndHandle( + ComposeRequestBuilder( + BuildRequestGetServiceStatusCodes(entry, &codes), + BuildRequestMethod(http.MethodGet), + BuildRequestEntryURL(client, entry), + BuildRequestOutputModeJSON(), + BuildRequestAuthenticate(client), + ), + ComposeResponseHandler( + HandleResponseCode(codes.NotFound, HandleResponseJSONMessagesCustomError(ErrorNotFound)), + HandleResponseRequireCode(codes.Read, HandleResponseJSONMessagesError()), + HandleResponseEntry(entry), + ), + ) +} + +// Update performs an Update action for the given Entry. +func (client *Client) Update(entry interface{}) error { + var codes service.StatusCodes + + return client.RequestAndHandle( + ComposeRequestBuilder( + BuildRequestGetServiceStatusCodes(entry, &codes), + BuildRequestMethod(http.MethodPost), + BuildRequestEntryURL(client, entry), + BuildRequestOutputModeJSON(), + BuildRequestBodyValuesSelective(entry, "update"), + BuildRequestAuthenticate(client), + ), + ComposeResponseHandler( + HandleResponseRequireCode(codes.Updated, HandleResponseJSONMessagesError()), + ), + ) +} + +// Delete performs a Delete action for the given Entry. +func (client *Client) Delete(entry interface{}) error { + var codes service.StatusCodes + + return client.RequestAndHandle( + ComposeRequestBuilder( + BuildRequestGetServiceStatusCodes(entry, &codes), + BuildRequestMethod(http.MethodDelete), + BuildRequestEntryURL(client, entry), + BuildRequestOutputModeJSON(), + BuildRequestAuthenticate(client), + ), + ComposeResponseHandler( + HandleResponseRequireCode(codes.Deleted, HandleResponseJSONMessagesError()), + ), + ) +} + +func (client *Client) listModified(entries interface{}, modifier interface{}) error { + entriesPtrV := reflect.ValueOf(entries) + if entriesPtrV.Kind() != reflect.Ptr { + return wrapError(ErrorPtr, nil, "client: List attempted on on-pointer value") + } + + entriesV := reflect.Indirect(entriesPtrV) + if entriesV.Kind() != reflect.Slice { + return wrapError(ErrorSlice, nil, "client: List attempted on non-slice value") + } + entryT := entriesV.Type().Elem() + entryI := reflect.New(entryT).Interface() + + if modifier != nil { + if err := deepset.Set(entryI, modifier); err != nil { + return err + } + } + + return client.RequestAndHandle( + ComposeRequestBuilder( + BuildRequestMethod(http.MethodGet), + BuildRequestEntryURL(client, entryI), + BuildRequestOutputModeJSON(), + BuildRequestAuthenticate(client), + ), + ComposeResponseHandler( + HandleResponseRequireCode(http.StatusOK, HandleResponseJSONMessagesError()), + HandleResponseEntries(entries), + ), + ) +} + +// ListNamespace populates entries in place for a Namespace. +func (client *Client) ListNamespace(entries interface{}, ns Namespace) error { + return client.listModified(entries, ns) +} + +// ListNamespace populates entries in place for an ID. +func (client *Client) ListID(entries interface{}, id ID) error { + return client.listModified(entries, id) +} + +// ListNamespace populates entries in place without any ID or Namespace context. +func (client *Client) List(entries interface{}) error { + return client.listModified(entries, nil) +} + +// ReadACL performs a ReadACL action for the given Entry. It modifies acl in-place, +// so acl must be a pointer. +func (client *Client) ReadACL(entry interface{}, acl *ACL) error { + var aclResponse struct { + ACL ACL `json:"acl"` + } + + if err := client.RequestAndHandle( + ComposeRequestBuilder( + BuildRequestMethod(http.MethodGet), + BuildRequestEntryACLURL(client, entry, *acl), + BuildRequestOutputModeJSON(), + BuildRequestAuthenticate(client), + ), + ComposeResponseHandler( + HandleResponseCode(http.StatusNotFound, HandleResponseJSONMessagesCustomError(ErrorNotFound)), + HandleResponseRequireCode(http.StatusOK, HandleResponseJSONMessagesError()), + HandleResponseEntry(&aclResponse), + ), + ); err != nil { + return err + } + + *acl = aclResponse.ACL + + return nil +} + +// UpdateACL performs an UpdateACL action for the given Entry. +func (client *Client) UpdateACL(entry interface{}, acl ACL) error { + return client.RequestAndHandle( + ComposeRequestBuilder( + BuildRequestMethod(http.MethodPost), + BuildRequestEntryACLURL(client, entry, acl), + BuildRequestBodyValues(acl), + BuildRequestOutputModeJSON(), + BuildRequestAuthenticate(client), + ), + ComposeResponseHandler( + HandleResponseRequireCode(http.StatusOK, HandleResponseJSONMessagesError()), + ), + ) +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/client/conf_id.go b/vendor/github.com/splunk/go-splunk-client/pkg/client/conf_id.go new file mode 100644 index 00000000..abdebbdc --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/client/conf_id.go @@ -0,0 +1,128 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/splunk/go-splunk-client/pkg/internal/paths" +) + +// ConfID represents the ID of configs/conf- resources. +type ConfID struct { + Namespace Namespace + + File string + Stanza string +} + +// parseConfID returns a new ConfsID by parsing the ID URL string. +func parseConfID(idURL string) (ConfID, error) { + newNS, remnants, err := parseNamespace(idURL) + if err != nil { + return ConfID{}, err + } + + if len(remnants) < 2 { + return ConfID{}, wrapError(ErrorID, nil, "client: parseNamespace didn't return remnants for ConfID.File and ConfID.Stanza") + } + + fileRemnant := remnants[len(remnants)-2] + stanzaRemnant := remnants[len(remnants)-1] + + r := regexp.MustCompile("^conf-(.+)$") + // remnants[0] + foundFileStrings := r.FindStringSubmatch(fileRemnant) + if foundFileStrings == nil { + return ConfID{}, wrapError(ErrorID, nil, "client: unable to parse %q for ConfID.File", fileRemnant) + } + // [0] is the full match, [1] is the first capture group + foundFile := foundFileStrings[1] + + return ConfID{ + Namespace: newNS, + File: foundFile, + Stanza: stanzaRemnant, + }, nil +} + +// Parse sets the ID's value to match what is parsed from the given ID URL. +func (confID *ConfID) Parse(idURL string) error { + newConfID, err := parseConfID(idURL) + if err != nil { + return err + } + + *confID = newConfID + + return nil +} + +// GetServicePath implements custom GetServicePath encoding. +func (confID ConfID) GetServicePath(path string) (string, error) { + if confID.File == "" { + return "", wrapError(ErrorID, nil, "client: attempted ConfID.GetServicePath() with empty File") + } + + nsServicePath, err := confID.Namespace.GetServicePath(path) + if err != nil { + return "", err + } + + return paths.Join( + nsServicePath, + fmt.Sprintf("conf-%s", confID.File), + ), nil +} + +// GetEntryPath implements custom GetEntryPath encoding. +func (confID ConfID) GetEntryPath(path string) (string, error) { + servicePath, err := confID.GetServicePath(path) + if err != nil { + return "", err + } + + return paths.Join(servicePath, url.PathEscape(confID.Stanza)), nil +} + +// UnmarshalJSON implements custom JSON unmarshaling for ConfID. +func (confID *ConfID) UnmarshalJSON(data []byte) error { + idString := "" + if err := json.Unmarshal(data, &idString); err != nil { + return wrapError(ErrorID, err, "client: unable to unmarshal %q as string", data) + } + + if err := confID.Parse(idString); err != nil { + return err + } + + return nil +} + +// SetURLValues implements custom url.Query encoding of ConfID. It adds a field "name" for the ConfID's +// Stanza. If the Title value is empty, it returns an error, as there are no scenarios where a ConfID +// object is expected to be POSTed with an empty Stanza. +func (confID ConfID) SetURLValues(key string, v *url.Values) error { + if confID.Stanza == "" { + return wrapError(ErrorID, nil, "client: attempted SetURLValues on ConfID with empty Stanza") + } + + v.Add("name", confID.Stanza) + + return nil +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/client/error.go b/vendor/github.com/splunk/go-splunk-client/pkg/client/error.go new file mode 100644 index 00000000..668436a5 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/client/error.go @@ -0,0 +1,116 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "fmt" + "runtime/debug" +) + +// ErrorCode identifies the type of error that was encountered. +type ErrorCode int + +const ( + // ErrorUndefined is the zero-value, indicating that the error type + // has not been defined. + ErrorUndefined ErrorCode = iota + + // ErrorNamespace indicates an error with the Namespace. + ErrorNamespace + + // ErrorEndpoint indicates an error with the Endpoint configuration. + ErrorEndpoint + + // ErrorValues indicates an error was encountered while trying to encode + // to url.Values. + ErrorValues + + // ErrorNilValue indicates an attempt to perform an action against a nil value, + // such as attempting to set RawQuery on http.Request with a nil URL. + ErrorNilValue + + // ErrorOverwriteValue indicates an attempt to overwrite an existing value, + // such as attempting to set RawQuery multiple times on a URL. + ErrorOverwriteValue + + // ErrorMissingTitle indicates an operation that required a non-empty Title was + // attempted with an empty Title. + ErrorMissingTitle + + // ErrorMissingURL indicates the Client's URL value is missing. + ErrorMissingURL + + // ErrorHTTPClient indicates an error related to the http.Client was encountered. + ErrorHTTPClient + + // ErrorResponseBody indicates an error encountered while trying to parse the Body + // from an http.Response. + ErrorResponseBody + + // ErrorSplunkMessage indicates the Splunk REST API returned an error message. + ErrorSplunkMessage + + // ErrorUnauthorized indicates a request was unauthorized. + ErrorUnauthorized + + // ErrorNotFound indicates an attempt was made against an object that count not be found. + ErrorNotFound + + // ErrorPtr indicates an operation requiring a pointer was passed a non-pointer. + ErrorPtr + + // ErrorSlice indicates an operation requiring a slice was passed a non-slice, or a slice + // of the wrong type. + ErrorSlice + + // ErrorID indicates an error was encountered related to an object's ID. + ErrorID + + // ErrorSharing indicates an error was encountered related to a Sharing value. + ErrorSharing +) + +// Error represents an encountered error. It adheres to the "error" interface, +// so will be returned as a standard error. +// +// Returned errors can be handled as this Error type: +// +// if err := c.RequestAndHandle(...); err != nil { +// if clientErr, ok := err.(client.Error) { +// // check clientErr.Code to determine appropriate action +// } +// } +type Error struct { + Code ErrorCode + Message string + Wrapped error + StackTrace string +} + +// Wrap returns a new Error with the given code, error, and message. The error value +// may be nil if this is a new error. +func wrapError(code ErrorCode, err error, messagef string, messageArgs ...interface{}) Error { + return Error{ + Code: code, + Message: fmt.Sprintf(messagef, messageArgs...), + Wrapped: err, + StackTrace: string(debug.Stack()), + } +} + +// Error returns the Error's message. +func (err Error) Error() string { + return err.Message +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/client/handle_response.go b/vendor/github.com/splunk/go-splunk-client/pkg/client/handle_response.go new file mode 100644 index 00000000..a65c150f --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/client/handle_response.go @@ -0,0 +1,205 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "encoding/json" + "encoding/xml" + "net/http" + "reflect" + + "github.com/splunk/go-splunk-client/pkg/messages" +) + +// ResponseHandler defines a function that performs an action on an http.Response. +type ResponseHandler func(*http.Response) error + +// ComposeResponseHandler creates a new ResponseHandler that runs each ResponseHandler +// provided as an argument. +func ComposeResponseHandler(handlers ...ResponseHandler) ResponseHandler { + return func(r *http.Response) error { + for _, handler := range handlers { + if err := handler(r); err != nil { + return err + } + } + + return nil + } +} + +// HandleResponseXML returns a ResponseHandler that decodes an http.Response's Body +// as XML to the given interface. +func HandleResponseXML(i interface{}) ResponseHandler { + return func(r *http.Response) error { + if err := xml.NewDecoder(r.Body).Decode(i); err != nil { + return wrapError(ErrorResponseBody, err, "unable to decode response XML: %s", err) + } + + return nil + } +} + +// HandleResponseXMLMessagesCustomError returns a ResponseHandler that decode's an http.Response's +// Body as an XML document of Messages and returns the Messages as an error with the given ErrorCode. +func HandleResponseXMLMessagesCustomError(code ErrorCode) ResponseHandler { + return func(r *http.Response) error { + response := struct { + Messages messages.Messages + }{} + + if err := HandleResponseXML(&response)(r); err != nil { + return err + } + + return wrapError(code, nil, "response contained message: %s", response.Messages.String()) + } +} + +// HandleResponseXMLMessagesError returns a ResponseHandler that decodes an http.Response's Body +// as an XML document of Messages and returns the Messages as an error. +func HandleResponseXMLMessagesError() ResponseHandler { + return func(r *http.Response) error { + return HandleResponseXMLMessagesCustomError(ErrorSplunkMessage)(r) + } +} + +// HandleResponseJSON returns a ResponseHandler that decodes an http.Response's Body +// as JSON to the given interface. +func HandleResponseJSON(i interface{}) ResponseHandler { + return func(r *http.Response) error { + if err := json.NewDecoder(r.Body).Decode(i); err != nil { + return wrapError(ErrorResponseBody, err, "unable to decode response JSON: %s", err) + } + + return nil + } +} + +// HandleResponseJSONMessagesCustomError returns a ResponseHandler that decodes an http.Response's +// Body as JSON document of Messages and returns the Messages as an error with the given ErrorCode. +func HandleResponseJSONMessagesCustomError(code ErrorCode) ResponseHandler { + return func(r *http.Response) error { + msg := messages.Messages{} + if err := HandleResponseJSON(&msg)(r); err != nil { + return err + } + + return wrapError(code, nil, "response contained message: %s", msg.String()) + } +} + +// HandleResponseJSONMessagesError returns a ResponseHandler that decode's an http.Response's Body +// as a JSON document of Messages and returns the Messages as an error with the Code ErrorSplunkMessage. +func HandleResponseJSONMessagesError() ResponseHandler { + return func(r *http.Response) error { + return HandleResponseJSONMessagesCustomError(ErrorSplunkMessage)(r) + } +} + +// HandleResponseCode returns a ResponseHandler that calls errorResponseHandler if an http.Response's +// StatusCode is equal to the provided code. +func HandleResponseCode(code int, errorResponseHandler ResponseHandler) ResponseHandler { + return func(r *http.Response) error { + if r.StatusCode != code { + return nil + } + + return errorResponseHandler(r) + } +} + +// HandleResponseRequireCode returns a ResponseHandler that checks for a given StatusCode. If +// the http.Response has a different StatusCode, the provided ResponseHandler will be called +// to return the appopriate error message. +func HandleResponseRequireCode(code int, errorResponseHandler ResponseHandler) ResponseHandler { + return func(r *http.Response) error { + if r.StatusCode == code { + return nil + } + + return errorResponseHandler(r) + } +} + +// HandleResponseEntries returns a ResponseHandler that parses the http.Response Body +// into the list of Entry reference provided. +func HandleResponseEntries(entries interface{}) ResponseHandler { + return func(r *http.Response) error { + entriesPtrV := reflect.ValueOf(entries) + if entriesPtrV.Kind() != reflect.Ptr { + return wrapError(ErrorPtr, nil, "attempted to read entries to non-pointer") + } + + entriesV := reflect.Indirect(entriesPtrV) + if entriesV.Kind() != reflect.Slice { + return wrapError(ErrorSlice, nil, "attempted to read entries to non-slice") + } + + responseT := reflect.StructOf([]reflect.StructField{ + { + Name: "Entry", + Type: entriesV.Type(), + }, + }) + + entriesResponsePtrV := reflect.New(responseT) + entriesResponsePtrI := entriesResponsePtrV.Interface() + + d := json.NewDecoder(r.Body) + if err := d.Decode(entriesResponsePtrI); err != nil { + return wrapError(ErrorResponseBody, err, "unable to decode JSON: %s", err) + } + + entriesResponseV := reflect.Indirect(entriesResponsePtrV) + responseEntriesFieldV := entriesResponseV.FieldByName("Entry") + + entriesV.Set(responseEntriesFieldV) + + return nil + } +} + +// HandleResponseEntry returns a responseHaResponseHandlerndler that parses the http.Response Body +// into the given Entry. +func HandleResponseEntry(entry interface{}) ResponseHandler { + return func(r *http.Response) error { + entryPtrV := reflect.ValueOf(entry) + if entryPtrV.Kind() != reflect.Ptr { + return wrapError(ErrorPtr, nil, "attempted to read entry to non-pointer") + } + + entryV := reflect.Indirect(entryPtrV) + entryT := entryV.Type() + + entriesT := reflect.SliceOf(entryT) + entriesPtrV := reflect.New(entriesT) + entriesPtrI := entriesPtrV.Interface() + + if err := HandleResponseEntries(entriesPtrI)(r); err != nil { + return err + } + + entriesV := reflect.Indirect(entriesPtrV) + + if entriesV.Len() != 1 { + return wrapError(ErrorResponseBody, nil, "expected exactly 1 entry, got %d", entriesV.Len()) + } + + entryV.Set(entriesV.Index(0)) + + return nil + } +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/client/id.go b/vendor/github.com/splunk/go-splunk-client/pkg/client/id.go new file mode 100644 index 00000000..ba23a000 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/client/id.go @@ -0,0 +1,127 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "encoding/json" + "net/url" + + "github.com/splunk/go-splunk-client/pkg/internal/paths" +) + +// ID represents a Splunk object ID URL for a specific object. +type ID struct { + Namespace Namespace + + // Title is the ID's title component. It is the name of the Splunk object. + Title string + + // url is the ID in URL format. It is set by parseID (which is used by Parse and UnmarshalJSON) + // to give confidence that it is correct and reliable. + url string +} + +// ParseID returns a new ID by parsing the ID URL string. +func ParseID(idURL string) (ID, error) { + newNS, remnants, err := parseNamespace(idURL) + if err != nil { + return ID{}, err + } + + if len(remnants) < 1 { + return ID{}, wrapError(ErrorID, nil, "client: parseNamespace didn't return a remnant for ID.Title") + } + + return ID{ + Namespace: newNS, + Title: remnants[len(remnants)-1], + url: idURL, + }, nil +} + +// Parse sets the ID's value to match what is parsed from the given ID URL. +func (id *ID) Parse(idURL string) error { + newID, err := ParseID(idURL) + if err != nil { + return err + } + + *id = newID + + return nil +} + +// URL returns the URL for ID. An error is returned if URL() is run on ID that has no set URL, or if the +// stored URL doesn't match the ID's fields. +func (id ID) URL() (string, error) { + if id.url == "" { + return "", wrapError(ErrorID, nil, "client: ID has unset URL") + } + + idFromURL, err := ParseID(id.url) + if err != nil { + return "", wrapError(ErrorID, err, "client: unable to re-parse ID's stored URL: %s", err) + } + + if idFromURL != id { + return "", wrapError(ErrorID, nil, "client: ID doesn't match stored URL") + } + + return id.url, nil +} + +// GetServicePath implements custom GetServicePath encoding. It returns its Namespace's +// service path. +func (id ID) GetServicePath(path string) (string, error) { + return id.Namespace.GetServicePath(path) +} + +// GetEntryPath implements custom GetEntryPath encoding. It returns the url-encoded +// value of the ID's Title with the service path preceding it. +func (id ID) GetEntryPath(path string) (string, error) { + servicePath, err := id.GetServicePath(path) + if err != nil { + return "", err + } + + return paths.Join(servicePath, url.PathEscape(id.Title)), nil +} + +// UnmarshalJSON implements custom JSON unmarshaling for IDFields. +func (id *ID) UnmarshalJSON(data []byte) error { + idString := "" + if err := json.Unmarshal(data, &idString); err != nil { + return wrapError(ErrorID, err, "client: unable to unmarshal %q as string", data) + } + + if err := id.Parse(idString); err != nil { + return err + } + + return nil +} + +// SetURLValues implements custom url.Query encoding of ID. It adds a field "name" for the ID's +// Title. If the Title value is empty, it returns an error, as there are no scenarios where an ID +// object is expected to be POSTed with an empty Title. +func (id ID) SetURLValues(key string, v *url.Values) error { + if id.Title == "" { + return wrapError(ErrorID, nil, "client: attempted SetURLValues on ID with empty Title") + } + + v.Add("name", id.Title) + + return nil +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/client/namespace.go b/vendor/github.com/splunk/go-splunk-client/pkg/client/namespace.go new file mode 100644 index 00000000..5df42bad --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/client/namespace.go @@ -0,0 +1,111 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +import ( + "strings" + + "github.com/splunk/go-splunk-client/pkg/internal/paths" +) + +// Namespace is a Splunk object's namespace, consisting of a User and App. It is valid only if both User +// and App are set, or both are unset. +type Namespace struct { + User string + App string +} + +// namespacePath returns the namespace path. If the resulting path is invalid, it will be returned +// along with an error. +func (ns Namespace) namespacePath() (string, error) { + var path string + + // absence of both user/app indicates global context + if (ns.User == "") && (ns.App == "") { + path = "services" + } else { + path = paths.Join("servicesNS", ns.User, ns.App) + } + + return path, ns.validate() +} + +// GetServicePath implements custom GetServicePath encoding. It returns the given +// path back, which has the effect of using the ID field's struct tag as +// its GetServicePath. +func (ns Namespace) GetServicePath(path string) (string, error) { + nsPath, err := ns.namespacePath() + if err != nil { + return "", err + } + + return paths.Join(nsPath, path), nil +} + +// parseNamespace parses a string ID into a Namespace object. +func parseNamespace(id string) (Namespace, []string, error) { + // start with a clean IDFields + newNS := Namespace{} + + // an empty id is a "clean slate", with no namespace info (user/app), endpoint, or title + if id == "" { + return newNS, nil, nil + } + + reverseStrings := func(input []string) []string { + output := make([]string, 0, len(input)) + + for i := len(input) - 1; i >= 0; i-- { + output = append(output, input[i]) + } + + return output + } + + // we work backwards in the id path segments to find the namespace, so reverse idPartStrings + idPartStrings := strings.Split(id, "/") + idPartStrings = reverseStrings(idPartStrings) + + for i, idPartString := range idPartStrings { + // if we've made it to the "services" segment we have an empty Namespace + if idPartString == "services" { + return newNS, reverseStrings(idPartStrings[0:i]), nil + } + + if idPartString == "servicesNS" { + // if we've made it to the "servicesNS" segment we have a user/app namespace + + if i < 2 { + return Namespace{}, nil, wrapError(ErrorID, nil, "unable to parse ID, servicesNS found without user/app: %s", id) + } + + newNS.User = idPartStrings[i-1] + newNS.App = idPartStrings[i-2] + + return newNS, reverseStrings(idPartStrings[0 : i-2]), nil + } + } + + return Namespace{}, nil, wrapError(ErrorID, nil, "client: unable to parse Namespace, missing services or servicesNS: %s", id) +} + +// validate returns an error if the Namespace is invalid. +func (ns Namespace) validate() error { + if (ns.User == "") != (ns.App == "") { + return wrapError(ErrorNamespace, nil, "client: invalid Namespace, user and app must both be empty or non-empty") + } + + return nil +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/client/title.go b/vendor/github.com/splunk/go-splunk-client/pkg/client/title.go new file mode 100644 index 00000000..528f07fe --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/client/title.go @@ -0,0 +1,21 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package client + +// Titler defines methods that a type must implement to be a titled object. +type Titler interface { + // Title returns the string representation of the object's Title. + Title() string +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/deepset/deepset.go b/vendor/github.com/splunk/go-splunk-client/pkg/deepset/deepset.go new file mode 100644 index 00000000..5414bf73 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/deepset/deepset.go @@ -0,0 +1,17 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package deepset provides functionality to set a value of a given type on +// a value of any time that is or contains a value of the same type. +package deepset diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/deepset/set.go b/vendor/github.com/splunk/go-splunk-client/pkg/deepset/set.go new file mode 100644 index 00000000..01262861 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/deepset/set.go @@ -0,0 +1,117 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package deepset provides functionality to set the an input object's value directly, +// or set it on one of the object's struct fields, based on the type of the value being +// set. +package deepset + +import ( + "fmt" + "reflect" +) + +// Set attempts to set the given value on dest. +// +// If dest and value are of the same underlying type, dest will be set directly. +// +// If dest is a struct, and exactly one of its fields are of the same underlying type as value, that field will be set to value. +// +// Any dest field that is a struct will be followed recursively. +// +// If multiple struct fields at any given level match the value type, an error is returned. +func Set(dest interface{}, value interface{}) error { + destV := reflect.ValueOf(dest) + + for destV.Kind() == reflect.Ptr { + destV = destV.Elem() + } + + valueV := reflect.ValueOf(value) + + if !(destV.IsValid() && valueV.IsValid()) { + return fmt.Errorf("deepset: dest (%T) or input (%T) invalid", dest, value) + } + + if destV.Type() == valueV.Type() { + if !destV.CanSet() { + return fmt.Errorf("deepset: dest type (%T) is not settable", dest) + } + + destV.Set(valueV) + return nil + } + + if destV.Kind() == reflect.Struct { + setFieldV, err := structSetFieldValue(destV, valueV) + if err != nil { + return err + } + + if setFieldV.CanSet() { + setFieldV.Set(valueV) + return nil + } + } + + return fmt.Errorf("deepset: unable to set value (%T) on dest (%T)", value, dest) +} + +func structSetFieldValue(destV reflect.Value, valueV reflect.Value) (reflect.Value, error) { + var foundSetFieldV reflect.Value + + fieldsV := make([]reflect.Value, 0, destV.NumField()) + + for i := 0; i < destV.NumField(); i++ { + fieldV := destV.Field(i) + if !fieldV.CanSet() { + continue + } + + fieldsV = append(fieldsV, fieldV) + } + + for _, fieldV := range fieldsV { + if fieldV.Type() == valueV.Type() { + if foundSetFieldV.IsValid() { + return reflect.Value{}, fmt.Errorf("deepset: dest type (%T) has multiple fields of value type (%T)", destV, valueV) + } + + foundSetFieldV = fieldV + } + } + + if foundSetFieldV.IsValid() { + return foundSetFieldV, nil + } + + for _, fieldV := range fieldsV { + if fieldV.Kind() == reflect.Struct { + foundEmbeddedSetFieldV, err := structSetFieldValue(fieldV, valueV) + // error will be returned if the embedded struct is ambiguous + if err != nil { + return reflect.Value{}, err + } + + // check if *this* struct is ambiguous + if foundSetFieldV.IsValid() { + return reflect.Value{}, fmt.Errorf("deepset: dest type (%T) has multiple fields of value type (%T)", destV, valueV) + } + + foundSetFieldV = foundEmbeddedSetFieldV + } + } + + return foundSetFieldV, nil +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/entry/entry.go b/vendor/github.com/splunk/go-splunk-client/pkg/entry/entry.go new file mode 100644 index 00000000..65e1276d --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/entry/entry.go @@ -0,0 +1,17 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package entry provides types that manage remote content via the Splunk REST +// API. +package entry diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/entry/index.go b/vendor/github.com/splunk/go-splunk-client/pkg/entry/index.go new file mode 100644 index 00000000..4c091d2e --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/entry/index.go @@ -0,0 +1,68 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package entry + +import ( + "github.com/splunk/go-splunk-client/pkg/attributes" + "github.com/splunk/go-splunk-client/pkg/client" +) + +// IndexContent is the content for an Index. +type IndexContent struct { + BlockSignSize attributes.Explicit[int] `json:"blockSignSize" values:"blockSignSize,omitzero"` + BucketRebuildMemoryHint attributes.Explicit[int] `json:"bucketRebuildMemoryHint" values:"bucketRebuildMemoryHint,omitzero"` + ColdPath attributes.Explicit[string] `json:"coldPath" values:"coldPath,omitzero" selective:"create"` + ColdToFrozenDir attributes.Explicit[string] `json:"coldToFrozenDir" values:"coldToFrozenDir,omitzero"` + ColdToFrozenScript attributes.Explicit[string] `json:"coldToFrozenScript" values:"coldToFrozenScript,omitzero"` + CompressRawdata attributes.Explicit[bool] `json:"compressRawdata" values:"compressRawdata,omitzero"` + DataType attributes.Explicit[string] `json:"datatype" values:"datatype,omitzero" selective:"create"` + Disabled attributes.Explicit[bool] `json:"disabled" values:"disabled,omitzero" selective:"read"` + EnableOnlineBucketRepair attributes.Explicit[bool] `json:"enableOnlineBucketRepair" values:"enableOnlineBucketRepair,omitzero"` + FrozenTimePeriodInSecs attributes.Explicit[int] `json:"frozenTimePeriodInSecs" values:"frozenTimePeriodInSecs,omitzero"` + HomePath attributes.Explicit[string] `json:"homePath" values:"homePath,omitzero" selective:"create"` + MaxBloomBackfillBucketAge attributes.Explicit[string] `json:"maxBloomBackfillBucketAge" values:"maxBloomBackfillBucketAge,omitzero"` + MaxConcurrentOptimizes attributes.Explicit[int] `json:"maxConcurrentOptimizes" values:"maxConcurrentOptimizes,omitzero"` + MaxDataSize attributes.Explicit[string] `json:"maxDataSize" values:"maxDataSize,omitzero"` + MaxHotBuckets attributes.Explicit[string] `json:"maxHotBuckets" values:"maxHotBuckets,omitzero"` + MaxHotIdleSecs attributes.Explicit[int] `json:"maxHotIdleSecs" values:"maxHotIdleSecs,omitzero"` + MaxHotSpanSecs attributes.Explicit[int] `json:"maxHotSpanSecs" values:"maxHotSpanSecs,omitzero"` + MaxMemMB attributes.Explicit[int] `json:"maxMemMB" values:"maxMemMB,omitzero"` + MaxMetaEntries attributes.Explicit[int] `json:"maxMetaEntries" values:"maxMetaEntries,omitzero"` + MaxTimeUnreplicatedNoAcks attributes.Explicit[int] `json:"maxTimeUnreplicatedNoAcks" values:"maxTimeUnreplicatedNoAcks,omitzero"` + MaxTimeUnreplicatedWithAcks attributes.Explicit[int] `json:"maxTimeUnreplicatedWithAcks" values:"maxTimeUnreplicatedWithAcks,omitzero"` + MaxTotalDataSizeMB attributes.Explicit[int] `json:"maxTotalDataSizeMB" values:"maxTotalDataSizeMB,omitzero"` + MaxWarmDBCount attributes.Explicit[int] `json:"maxWarmDBCount" values:"maxWarmDBCount,omitzero"` + MinRawFileSyncSecs attributes.Explicit[string] `json:"minRawFileSyncSecs" values:"minRawFileSyncSecs,omitzero"` + MinStreamGroupQueueSize attributes.Explicit[int] `json:"minStreamGroupQueueSize" values:"minStreamGroupQueueSize,omitzero"` + PartialServiceMetaPeriod attributes.Explicit[int] `json:"partialServiceMetaPeriod" values:"partialServiceMetaPeriod,omitzero"` + ProcessTrackerServiceInterval attributes.Explicit[int] `json:"processTrackerServiceInterval" values:"processTrackerServiceInterval,omitzero"` + QuarantineFutureSecs attributes.Explicit[int] `json:"quarantineFutureSecs" values:"quarantineFutureSecs,omitzero"` + QuarantinePastSecs attributes.Explicit[int] `json:"quarantinePastSecs" values:"quarantinePastSecs,omitzero"` + RawChunkSizeBytes attributes.Explicit[int] `json:"rawChunkSizeBytes" values:"rawChunkSizeBytes,omitzero"` + RepFactor attributes.Explicit[int] `json:"repFactor" values:"repFactor,omitzero"` + RotatePeriodInSecs attributes.Explicit[int] `json:"rotatePeriodInSecs" values:"rotatePeriodInSecs,omitzero"` + ServiceMetaPeriod attributes.Explicit[int] `json:"serviceMetaPeriod" values:"serviceMetaPeriod,omitzero"` + SyncMeta attributes.Explicit[bool] `json:"syncMeta" values:"syncMeta,omitzero"` + ThawedPath attributes.Explicit[string] `json:"thawedPath" values:"thawedPath,omitzero" selective:"create"` + ThrottleCheckPeriod attributes.Explicit[int] `json:"throttleCheckPeriod" values:"throttleCheckPeriod,omitzero"` + TstatsHomePath attributes.Explicit[string] `json:"tstatsHomePath" values:"tstatsHomePath,omitzero" selective:"create"` + WarmToColdScript attributes.Explicit[string] `json:"warmToColdScript" values:"warmToColdScript,omitzero"` +} + +// Index is a Splunk Index. +type Index struct { + ID client.ID `selective:"create" service:"data/indexes"` + Content IndexContent `json:"content" values:",anonymize"` +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/entry/role.go b/vendor/github.com/splunk/go-splunk-client/pkg/entry/role.go new file mode 100644 index 00000000..64b1bdb9 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/entry/role.go @@ -0,0 +1,54 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package entry + +import ( + "github.com/splunk/go-splunk-client/pkg/attributes" + "github.com/splunk/go-splunk-client/pkg/client" +) + +// RoleContent defines the Content for a Role. +type RoleContent struct { + Capabilities []string `json:"capabilities" values:"capabilities,omitzero,fillempty"` + CumulativeRTSrchJobsQuota attributes.Explicit[int] `json:"cumulativeRTSrchJobsQuota" values:"cumulativeRTSrchJobsQuota,omitzero"` + CumulativeSrchJobsQuota attributes.Explicit[int] `json:"cumulativeSrchJobsQuota" values:"cumulativeSrchJobsQuota,omitzero"` + DefaultApp attributes.Explicit[string] `json:"defaultApp" values:"defaultApp,omitzero"` + RtSrchJobsQuota attributes.Explicit[int] `json:"rtSrchJobsQuota" values:"rtSrchJobsQuota,omitzero"` + SrchDiskQuota attributes.Explicit[int] `json:"srchDiskQuota" values:"srchDiskQuota,omitzero"` + SrchFilter attributes.Explicit[string] `json:"srchFilter" values:"srchFilter,omitzero"` + SrchIndexesAllowed []string `json:"srchIndexesAllowed" values:"srchIndexesAllowed,omitzero,fillempty"` + SrchIndexesDefault []string `json:"srchIndexesDefault" values:"srchIndexesDefault,omitzero,fillempty"` + SrchJobsQuota attributes.Explicit[int] `json:"srchJobsQuota" values:"srchJobsQuota,omitzero"` + SrchTimeWin attributes.Explicit[int] `json:"srchTimeWin" values:"srchTimeWin,omitzero"` + + // Read-only fields are populated by results returned by the Splunk API, but + // are not settable by Create or Update operations. + ImportedCapabilities []string `json:"imported_capabilities" values:"-"` + ImportedRoles []string `json:"imported_roles" values:"-"` + ImportedRtSrchJobsQuota attributes.Explicit[int] `json:"imported_rtSrchJobsQuota" values:"-"` + ImportedRtSrchJObsQuota attributes.Explicit[int] `json:"imported_rtSrchJObsQuota" values:"-"` + ImportedSrchDiskQuota attributes.Explicit[int] `json:"imported_srchDiskQuota" values:"-"` + ImportedSrchFilter attributes.Explicit[string] `json:"imported_srchFilter" values:"-"` + ImportedSrchIndexesAllowed []string `json:"imported_srchIndexesAllowed" values:"-"` + ImportedSrchIndexesDefault []string `json:"imported_srchIndexesDefault" values:"-"` + ImportedSrchJobsQuota attributes.Explicit[int] `json:"imported_srchJobsQuota" values:"-"` + ImportedSrchTimeWin attributes.Explicit[int] `json:"imported_srchTimeWin" values:"-"` +} + +// Role defines a Splunk role. +type Role struct { + ID client.ID `selective:"create" service:"authorization/roles"` + Content RoleContent `json:"content" values:",anonymize"` +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/entry/saml_group.go b/vendor/github.com/splunk/go-splunk-client/pkg/entry/saml_group.go new file mode 100644 index 00000000..24713978 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/entry/saml_group.go @@ -0,0 +1,33 @@ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package entry + +import ( + "github.com/splunk/go-splunk-client/pkg/client" + "github.com/splunk/go-splunk-client/pkg/service" +) + +// SAMLGroupContent defines the content for a SAMLGroup. +type SAMLGroupContent struct { + Roles []string `json:"roles" values:"roles,omitzero,fillempty"` +} + +// SAMLGroup defines a SAML group mapping. +type SAMLGroup struct { + ID client.ID `selective:"create" service:"admin/SAML-groups"` + Content SAMLGroupContent `json:"content" values:",anonymize"` + + // This endpoint returns a 400 if unable to find the given SAML Group. + _ service.StatusCodes `service:"NotFound=400"` +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/entry/savedsearch.go b/vendor/github.com/splunk/go-splunk-client/pkg/entry/savedsearch.go new file mode 100644 index 00000000..9ebf72d4 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/entry/savedsearch.go @@ -0,0 +1,120 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package entry + +import ( + "net/url" + + "github.com/splunk/go-splunk-client/pkg/attributes" + "github.com/splunk/go-splunk-client/pkg/client" +) + +// SavedSearchContent defines the content of a Savedsearch object. +type SavedSearchContent struct { + Actions attributes.NamedParametersCollection `json:"-" values:"action,omitzero" named_parameters_collection:"action"` + AlertDigestMode attributes.Explicit[bool] `json:"alert.digest_mode" values:"alert.digest_mode,omitzero"` + AlertExpires attributes.Explicit[string] `json:"alert.expires" values:"alert.expires,omitzero"` + AlertSeverity attributes.Explicit[string] `json:"alert.severity" values:"alert.severity,omitzero"` + AlertSuppress attributes.Explicit[bool] `json:"alert.suppress" values:"alert.suppress,omitzero"` + AlertSuppressFields attributes.Explicit[string] `json:"alert.suppress.fields" values:"alert.suppress.fields,omitzero"` + AlertSuppressGroupName attributes.Explicit[string] `json:"alert.suppress.group_name" values:"alert.suppress.group_name,omitzero"` + AlertSuppressPeriod attributes.Explicit[int] `json:"alert.suppress.period" values:"alert.suppress.period,omitzero"` + AlertTrack attributes.Explicit[string] `json:"alert.track" values:"alert.track,omitzero"` + AlertComparator attributes.Explicit[string] `json:"alert_comparator" values:"alert_comparator,omitzero"` + AlertCondition attributes.Explicit[string] `json:"alert_condition" values:"alert_condition,omitzero"` + AlertThreshold attributes.Explicit[int] `json:"alert_threshold" values:"alert_threshold,omitzero"` + AlertType attributes.Explicit[string] `json:"alert_type" values:"alert_type,omitzero"` + AllowSkew attributes.Explicit[string] `json:"allow_skew" values:"allow_skew,omitzero"` + AutoSummarize attributes.Explicit[bool] `json:"auto_summarize" values:"auto_summarize,omitzero"` + AutoSummarizeCommand attributes.Explicit[string] `json:"auto_summarize.command" values:"auto_summarize.command,omitzero"` + AutoSummarizeCronSchedule attributes.Explicit[string] `json:"auto_summarize.cron_schedule" values:"auto_summarize.cron_schedule,omitzero"` + AutoSummarizeDispatchEarliestTime attributes.Explicit[string] `json:"auto_summarize.dispatch.earliest_time" values:"auto_summarize.dispatch.earliest_time,omitzero"` + AutoSummarizeDispatchLatestTime attributes.Explicit[string] `json:"auto_summarize.dispatch.latest_time" values:"auto_summarize.dispatch.latest_time,omitzero"` + AutoSummarizeDispatchTimeFormat attributes.Explicit[string] `json:"auto_summarize.dispatch.time_format" values:"auto_summarize.dispatch.time_format,omitzero"` + AutoSummarizeDispatchTtl attributes.Explicit[string] `json:"auto_summarize.dispatch.ttl" values:"auto_summarize.dispatch.ttl,omitzero"` + AutoSummarizeMaxConcurrent attributes.Explicit[int] `json:"auto_summarize.max_concurrent" values:"auto_summarize.max_concurrent,omitzero"` + AutoSummarizeMaxDisabledBuckets attributes.Explicit[int] `json:"auto_summarize.max_disabled_buckets" values:"auto_summarize.max_disabled_buckets,omitzero"` + AutoSummarizeMaxSummaryRatio attributes.Explicit[int] `json:"auto_summarize.max_summary_ratio" values:"auto_summarize.max_summary_ratio,omitzero"` + AutoSummarizeMaxSummarySize attributes.Explicit[int] `json:"auto_summarize.max_summary_size" values:"auto_summarize.max_summary_size,omitzero"` + AutoSummarizeMaxTime attributes.Explicit[int] `json:"auto_summarize.max_time" values:"auto_summarize.max_time,omitzero"` + AutoSummarizeSuspendPeriod attributes.Explicit[string] `json:"auto_summarize.suspend_period" values:"auto_summarize.suspend_period,omitzero"` + AutoSummarizeTimespan attributes.Explicit[string] `json:"auto_summarize.timespan" values:"auto_summarize.timespan,omitzero"` + CronSchedule attributes.Explicit[string] `json:"cron_schedule" values:"cron_schedule,omitzero"` + Description attributes.Explicit[string] `json:"description" values:"description,omitzero"` + Disabled attributes.Explicit[bool] `json:"disabled" values:"disabled,omitzero"` + Dispatch attributes.NamedParametersCollection `json:"-" values:"dispatch,omitzero" named_parameters_collection:"dispatch" ` + DispatchAs attributes.Explicit[string] `json:"dispatchAs" values:"dispatchAs,omitzero"` + Displayview attributes.Explicit[string] `json:"displayview" values:"displayview,omitzero"` + DurableBackfillType attributes.Explicit[string] `json:"durable.backfill_type" values:"durable.backfill_type,omitzero"` + DurableLagTime attributes.Explicit[int] `json:"durable.lag_time" values:"durable.lag_time,omitzero"` + DurableMaxBackfillIntervals attributes.Explicit[int] `json:"durable.max_backfill_intervals" values:"durable.max_backfill_intervals,omitzero"` + DurableTrackTimeType attributes.Explicit[string] `json:"durable.track_time_type" values:"durable.track_time_type,omitzero"` + IsScheduled attributes.Explicit[bool] `json:"is_scheduled" values:"is_scheduled,omitzero"` + IsVisible attributes.Explicit[bool] `json:"is_visible" values:"is_visible,omitzero"` + MaxConcurrent attributes.Explicit[int] `json:"max_concurrent" values:"max_concurrent,omitzero"` + Name attributes.Explicit[string] `json:"name" values:"name,omitzero"` + NextScheduledTime attributes.Explicit[string] `json:"next_scheduled_time" values:"next_scheduled_time,omitzero"` + QualifiedSearch attributes.Explicit[string] `json:"qualifiedSearch" values:"qualifiedSearch,omitzero"` + RealtimeSchedule attributes.Explicit[bool] `json:"realtime_schedule" values:"realtime_schedule,omitzero"` + RequestUiDispatchApp attributes.Explicit[string] `json:"request.ui_dispatch_app" values:"request.ui_dispatch_app,omitzero"` + RequestUiDispatchView attributes.Explicit[string] `json:"request.ui_dispatch_view" values:"request.ui_dispatch_view,omitzero"` + RestartOnSearchpeerAdd attributes.Explicit[bool] `json:"restart_on_searchpeer_add" values:"restart_on_searchpeer_add,omitzero"` + RunNTimes attributes.Explicit[int] `json:"run_n_times" values:"run_n_times,omitzero"` + RunOnStartup attributes.Explicit[bool] `json:"run_on_startup" values:"run_on_startup,omitzero"` + SchedulePriority attributes.Explicit[string] `json:"schedule_priority" values:"schedule_priority,omitzero"` + ScheduleWindow attributes.Explicit[string] `json:"schedule_window" values:"schedule_window,omitzero"` + Search attributes.Explicit[string] `json:"search" values:"search,omitzero"` + Vsid attributes.Explicit[string] `json:"vsid" values:"vsid"` + WorkloadPool attributes.Explicit[string] `json:"workload_pool" values:"workload_pool"` +} + +// AddURLValues implements custom additional encoding to url.Values. +func (content SavedSearchContent) AddURLValues(key string, v *url.Values) error { + // The Splunk REST API returns savedsearch action status like "action.email=1", but doesn't honor that format + // for setting the action statuses. To set an action status, you must pass "actions=action1,action2" formatted + // values. Here we iterate through the enabled actions and add a url.Values entry for all enabled actions. + // + // If Actions is empty (not nil), we "clear" the enabled actions list by setting a single empty value for "actions". + + if content.Actions != nil && len(content.Actions) == 0 { + v.Add("actions", "") + } + + for _, enabledActionName := range content.Actions.EnabledNames() { + v.Add("actions", enabledActionName) + } + + return nil +} + +// UnmarshalJSON implements custom JSON unmarshaling. +func (content *SavedSearchContent) UnmarshalJSON(data []byte) error { + type contentAlias SavedSearchContent + var newAliasedContent contentAlias + + if err := attributes.UnmarshalJSONForNamedParametersCollections(data, &newAliasedContent); err != nil { + return err + } + + *content = SavedSearchContent(newAliasedContent) + + return nil +} + +// SavedSearch defines a Splunk savedsearch. +type SavedSearch struct { + ID client.ID `service:"saved/searches" selective:"create"` + Content SavedSearchContent `json:"content" values:",anonymize"` +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/entry/stanza.go b/vendor/github.com/splunk/go-splunk-client/pkg/entry/stanza.go new file mode 100644 index 00000000..f81b7bf5 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/entry/stanza.go @@ -0,0 +1,83 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package entry + +import ( + "encoding/json" + "fmt" + "regexp" + + "github.com/splunk/go-splunk-client/pkg/attributes" + "github.com/splunk/go-splunk-client/pkg/client" +) + +// StanzaContent defines the content for a Stanza. +type StanzaContent struct { + Disabled attributes.Explicit[bool] `json:"disabled" values:"disabled,omitzero"` + Values map[string]string `json:"-" values:",anonymize"` +} + +// Stanza is a Splunk configs/conf- stanza. +type Stanza struct { + ID client.ConfID `selective:"create" service:"configs"` + Content StanzaContent `json:"content" values:",anonymize"` +} + +// UnmarshalJSON implements custom JSON unmarshaling. +func (content *StanzaContent) UnmarshalJSON(data []byte) error { + type aliasType StanzaContent + var aliasValue aliasType + + if err := json.Unmarshal(data, &aliasValue); err != nil { + return err + } + + *content = StanzaContent(aliasValue) + + var allValues map[string]interface{} + if err := json.Unmarshal(data, &allValues); err != nil { + return err + } + + eaiR := regexp.MustCompile("^eai:") + + for key, value := range allValues { + if eaiR.MatchString(key) { + continue + } + + if key == "disabled" { + if valueBool, ok := value.(bool); ok { + content.Disabled = attributes.NewExplicit(valueBool) + continue + } else { + return fmt.Errorf("entry: unable to decode StanzaContent, disabled value %#v, expected bool", value) + } + } + + if valueString, ok := value.(string); ok { + if content.Values == nil { + content.Values = map[string]string{} + } + + content.Values[key] = valueString + continue + } else { + return fmt.Errorf("entry: unexpected non-string type in StanzaContent: %#v", value) + } + } + + return nil +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/entry/user.go b/vendor/github.com/splunk/go-splunk-client/pkg/entry/user.go new file mode 100644 index 00000000..93eb4d9b --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/entry/user.go @@ -0,0 +1,44 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package entry + +import ( + "github.com/splunk/go-splunk-client/pkg/attributes" + "github.com/splunk/go-splunk-client/pkg/client" +) + +// UserContent defines the content of a User object. +type UserContent struct { + // Read/Write + DefaultApp attributes.Explicit[string] `values:"defaultApp,omitzero"` + Email attributes.Explicit[string] `values:"email,omitzero"` + ForceChangePass attributes.Explicit[bool] `values:"force-change-pass,omitzero"` + Password attributes.Explicit[string] `values:"password,omitzero"` + RealName attributes.Explicit[string] `values:"realname,omitzero"` + RestartBackgroundJobs attributes.Explicit[bool] `values:"restart_background_jobs,omitzero"` + Roles []string `values:"roles,omitzero,fillempty"` + TZ attributes.Explicit[string] `values:"tz,omitzero"` + + // Read-only fields are populated by results returned by the Splunk API, but + // are not settable by Create or Update operations. + Capabilities []string `values:"-"` + Type attributes.Explicit[string] `values:"-"` +} + +// User defines a Splunk user. +type User struct { + ID client.ID `selective:"create" service:"authentication/users"` + Content UserContent `json:"content" values:",anonymize"` +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/internal/checks/requests.go b/vendor/github.com/splunk/go-splunk-client/pkg/internal/checks/requests.go new file mode 100644 index 00000000..7865497a --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/internal/checks/requests.go @@ -0,0 +1,116 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package checks + +import ( + "bytes" + "io" + "net/http" + "net/url" + "reflect" + "testing" +) + +// CheckRequestFunc functions perform a check against an http.Request. +type CheckRequestFunc func(*http.Request, *testing.T) + +// ComposeCheckRequestFunc returns a new CheckRequestFunc from an arbitrary number of other +// CheckRequestFunc functions. +func ComposeCheckRequestFunc(checks ...CheckRequestFunc) CheckRequestFunc { + return func(r *http.Request, t *testing.T) { + for _, check := range checks { + check(r, t) + } + } +} + +// CheckRequestHeaderKeyValue checks that an http.Request's header has a given value for +// a given key. +func CheckRequestHeaderKeyValue(key string, value ...string) CheckRequestFunc { + return func(r *http.Request, t *testing.T) { + if r.Header == nil { + t.Errorf("CheckRequestHeaderKeyValue: Header not set") + return + } + + got, ok := r.Header[key] + if !ok { + t.Errorf("CheckRequestHeaderKeyValue: Key %s not set", key) + return + } + + if !reflect.DeepEqual(got, value) { + t.Errorf("CheckRequestHeaderKeyValue: Key %s = %#v, want %#v", key, got, value) + return + } + } +} + +// CheckRequestMethod checks that a requests method matches the given method. +func CheckRequestMethod(method string) CheckRequestFunc { + return func(r *http.Request, t *testing.T) { + if r.Method != method { + t.Errorf("CheckRequestMethod: got %s, want %s", r.Method, method) + } + } +} + +// CheckRequestURL checks that a request's URL matches the given URL. +func CheckRequestURL(url string) CheckRequestFunc { + return func(r *http.Request, t *testing.T) { + gotURL := r.URL.String() + + if gotURL != url { + t.Errorf("CheckRequestURL: got\n%s, want\n%s", gotURL, url) + } + } +} + +// CheckRequestBodyValue checks that a requests URL-encoded body has the given +// key and value. +func CheckRequestBodyValue(key string, value ...string) CheckRequestFunc { + return func(r *http.Request, t *testing.T) { + if r.Body == nil { + t.Errorf("CheckRequestBodyValue: Body is nil") + } + + data, err := io.ReadAll(r.Body) + if err != nil { + t.Errorf("CheckRequestBodyValue: unable to read body: %s", err) + return + } + + // because reading is a one-time deal, we need to put back the data we + // read from r.Body + r.Body = io.NopCloser(bytes.NewReader(data)) + + v, err := url.ParseQuery(string(data)) + if err != nil { + t.Errorf("CheckRequestBodyValue: unable to parse query: %s", err) + return + } + + got, ok := v[key] + if !ok { + t.Errorf("CheckRequestBodyValue: key %s not present", key) + return + } + + if !reflect.DeepEqual(got, value) { + t.Errorf("CheckRequestBodyValue: key %s got %v, want %v", key, got, value) + return + } + } +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/internal/checks/unmarshal_json_test_cases.go b/vendor/github.com/splunk/go-splunk-client/pkg/internal/checks/unmarshal_json_test_cases.go new file mode 100644 index 00000000..823dddc3 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/internal/checks/unmarshal_json_test_cases.go @@ -0,0 +1,67 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package checks + +import ( + "encoding/json" + "reflect" + "testing" +) + +// JSONUnmarshalTestCase defines a test case for json.Unmarshal. +type JSONUnmarshalTestCase struct { + Name string + InputString string + Want interface{} + WantError bool +} + +// Test runs the Test case. +func (test JSONUnmarshalTestCase) Test(t *testing.T) { + // create a new pointer to a zero value of test.want + gotT := reflect.TypeOf(test.Want) + if gotT == nil { + t.Fatalf("%s attempted with nil want type", test.Name) + } + gotV := reflect.New(gotT) + gotP := gotV.Interface() + + // create a new pointer to a the same type as test.want, + // and set its data to match test.want + wantT := reflect.TypeOf(test.Want) + wantV := reflect.New(wantT) + wantV.Elem().Set(reflect.ValueOf(test.Want)) + wantP := wantV.Interface() + + err := json.Unmarshal([]byte(test.InputString), gotP) + gotError := err != nil + if gotError != test.WantError { + t.Fatalf("%s json.Unmarshal returned error? %v (%s)", test.Name, gotError, err) + } + + if !reflect.DeepEqual(gotP, wantP) { + t.Errorf("%s json.Unmarshal got\n%#v, want\n%#v", test.Name, gotP, wantP) + } +} + +// JSONUnmarshalTestCases is a collection of JSONUnmarshalTestCases tests. +type JSONUnmarshalTestCases []JSONUnmarshalTestCase + +// Test runs the Test defined for each item in the collection. +func (tests JSONUnmarshalTestCases) Test(t *testing.T) { + for _, test := range tests { + test.Test(t) + } +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/internal/checks/url_values_test_cases.go b/vendor/github.com/splunk/go-splunk-client/pkg/internal/checks/url_values_test_cases.go new file mode 100644 index 00000000..5a817752 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/internal/checks/url_values_test_cases.go @@ -0,0 +1,55 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package checks + +import ( + "net/url" + "reflect" + "testing" + + "github.com/splunk/go-splunk-client/pkg/values" +) + +// QueryValuesTestCase defines a test case for query.Values. +type QueryValuesTestCase struct { + Name string + Input interface{} + Want url.Values + WantError bool +} + +// Test runs the Test. +func (test QueryValuesTestCase) Test(t *testing.T) { + got, err := values.Encode(test.Input) + gotError := err != nil + + if gotError != test.WantError { + t.Errorf("%s values.Encode returned error? %v: %s", test.Name, gotError, err) + } + + if !reflect.DeepEqual(got, test.Want) { + t.Errorf("%s values.Encode got\n%#v, want\n%#v", test.Name, got, test.Want) + } +} + +// QueryValuesTestCases is a collection of queryValuesTestCase tests. +type QueryValuesTestCases []QueryValuesTestCase + +// Test runs the Test defined for each item in the collection. +func (tests QueryValuesTestCases) Test(t *testing.T) { + for _, test := range tests { + test.Test(t) + } +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/internal/paths/join.go b/vendor/github.com/splunk/go-splunk-client/pkg/internal/paths/join.go new file mode 100644 index 00000000..044998d0 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/internal/paths/join.go @@ -0,0 +1,31 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package paths + +import "strings" + +// Join returns a string crafted by trimming leading and trailing slashes +// from the input paths and joining them with slashes. Empty paths are retained, +// because each passed path is expected to be a component to be passed to the API, +// which shouldn't be removed just because they are empty. +func Join(path ...string) string { + parts := make([]string, len(path)) + + for i, part := range path { + parts[i] = strings.Trim(part, "/") + } + + return strings.Join(parts, "/") +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/messages/message.go b/vendor/github.com/splunk/go-splunk-client/pkg/messages/message.go new file mode 100644 index 00000000..1cd82949 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/messages/message.go @@ -0,0 +1,30 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package messages + +import "fmt" + +// Message represents the element of a entry. +type Message struct { + Value string `json:"text" xml:",chardata"` + Code string `json:"type" xml:"code,attr"` +} + +// String returns the string representation of a message. It will be in the form: +// +// Code: Value +func (m Message) String() string { + return fmt.Sprintf("%s: %s", m.Code, m.Value) +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/messages/messages.go b/vendor/github.com/splunk/go-splunk-client/pkg/messages/messages.go new file mode 100644 index 00000000..67e2fb17 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/messages/messages.go @@ -0,0 +1,38 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package messages provides the Messages and Message type to represent messages +// returned by the Splunk REST API. +package messages + +import ( + "strings" +) + +// Messages represents the element of a entry. +type Messages struct { + XMLName string `xml:"messages"` + Items []Message `json:"messages" xml:"msg"` +} + +// String returns the string representation of Messages. If multiple Message items are +// present, they will be comma-separated. +func (m Messages) String() string { + itemStrings := make([]string, len(m.Items)) + for i := 0; i < len(m.Items); i++ { + itemStrings[i] = m.Items[i].String() + } + + return strings.Join(itemStrings, ",") +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/messages/xml_unmarshaler_test_cases.go b/vendor/github.com/splunk/go-splunk-client/pkg/messages/xml_unmarshaler_test_cases.go new file mode 100644 index 00000000..a1ff43cd --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/messages/xml_unmarshaler_test_cases.go @@ -0,0 +1,53 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package messages + +import ( + "encoding/xml" + "reflect" + "testing" +) + +// xmlUnmarshalerTestCase represents an individual test case for unmarshaling from XML. +type xmlUnmarshalerTestCase struct { + input string + gotInterfacePtr interface{} + wantInterfacePtr interface{} + wantError bool +} + +// test performs the test for a xmlUnmarshalerTestCase definition. +func (test xmlUnmarshalerTestCase) test(t *testing.T) { + err := xml.Unmarshal([]byte(test.input), test.gotInterfacePtr) + gotError := err != nil + + if gotError != test.wantError { + t.Errorf("xml.Unmarshal(%q) returned error? %v (%s)", test.input, gotError, err) + } + + if !reflect.DeepEqual(test.gotInterfacePtr, test.wantInterfacePtr) { + t.Errorf("xml.Unmarshal(%q) got\n%#v, want\n%#v", test.input, test.gotInterfacePtr, test.wantInterfacePtr) + } +} + +// xmlUnmarshalerTestCases is a slice of xmlUnmarshalerTestCase objects. +type xmlUnmarshalerTestCases []xmlUnmarshalerTestCase + +// test performs each xmlUnmarshalerTestCase in xmlUnmarshalerTestCases. +func (tests xmlUnmarshalerTestCases) test(t *testing.T) { + for _, test := range tests { + test.test(t) + } +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/selective/selective.go b/vendor/github.com/splunk/go-splunk-client/pkg/selective/selective.go new file mode 100644 index 00000000..cb0a4d2a --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/selective/selective.go @@ -0,0 +1,117 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package selective provides functionality to translate one struct type into another +// by selectively including fields with matching field tags. +package selective + +import ( + "fmt" + "reflect" + "strings" +) + +// hasTagValue returns true if an individual tag value is present in the comma-separated +// tag value. An empty csvTag indicates the field should not be filtered, and wantTag +// should be assumed to be valid for the field, thus an empty csvTag always returns true. +func hasTagValue(csvTag string, wantTag string) bool { + if csvTag == "" { + return true + } + + parts := strings.Split(csvTag, ",") + for _, part := range parts { + if part == wantTag { + return true + } + } + + return false +} + +// Encode returns a value for an input struct value calculated by: +// +// • If no fields non-empty "selective" tag values, the input is returned directly, retaining +// unexported fields and methods. +// +// • Any field with a non-empty "selective" tag value is retained only if its tag contains +// the tag provided to Encode. +// +// • Struct field values are calculated by calling this function on the input field's value +// with the same tag. +// +// Returned values with changes (structs that contain at least one field with a non-empty +// "selective" tag) will lose unexported fields and methods. This is due to reflection being +// unable to retain them. +func Encode(i interface{}, tag string) (interface{}, error) { + iV := reflect.ValueOf(i) + + // dereference as many times as needed + for iV.Kind() == reflect.Ptr { + iV = iV.Elem() + } + + if iV.Kind() != reflect.Struct { + return nil, fmt.Errorf("selective: attempted Encode on non-struct type (%T)", i) + } + + var newStructFields []reflect.StructField + var newStructValues []reflect.Value + + iT := iV.Type() + hadTags := false + for i := 0; i < iT.NumField(); i++ { + field := iT.Field(i) + if !field.IsExported() { + continue + } + + foundTag := field.Tag.Get("selective") + if foundTag != "" { + hadTags = true + } + + if !hasTagValue(foundTag, tag) { + continue + } + + if field.Type.Kind() != reflect.Struct { + newStructFields = append(newStructFields, field) + newStructValues = append(newStructValues, iV.Field(i)) + } else { + embeddedStruct, err := Encode(iV.Field(i).Interface(), tag) + if err != nil { + return nil, err + } + + field.Type = reflect.TypeOf(embeddedStruct) + newStructFields = append(newStructFields, field) + newStructValues = append(newStructValues, reflect.ValueOf(embeddedStruct)) + } + } + + if !hadTags { + return i, nil + } + + newStructT := reflect.StructOf(newStructFields) + newStructVPtr := reflect.New(newStructT) + newStructV := newStructVPtr.Elem() + for i, newStructValue := range newStructValues { + newStructV.Field(i).Set(newStructValue) + } + newStructI := newStructV.Interface() + + return newStructI, nil +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/service/service.go b/vendor/github.com/splunk/go-splunk-client/pkg/service/service.go new file mode 100644 index 00000000..04a08a7a --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/service/service.go @@ -0,0 +1,165 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package service provides functionality to determine the service and +// entry paths for a struct. +package service + +import ( + "fmt" + "reflect" +) + +// ServicePathGetter is the interface for types that implement GetServicePath. +type ServicePathGetter interface { + // GetServicePath returns the object's service path based on the provided value. + GetServicePath(string) (string, error) +} + +// EntryPathGetter is the interfacae for types that implement GetEntryPath. +type EntryPathGetter interface { + // GetEntryPath returns the object's entry path based on the provided value. + GetEntryPath(string) (string, error) +} + +// ServicePath returns the relative service path for an object. +// +// The path will be the first of these that are present: +// +// • The value returned by the struct's GetServicePath method, if the struct is a service.ServicePathGetter. +// +// • The value returned by an exported field's GetServicePath method, if the struct is a service.ServicePathGetter. +// The field's "service" tag will be passed to its GetServicePath method.. +func ServicePath(i interface{}) (string, error) { + if path, err := structOrFieldPath(i, fieldServicePath); path != "" || err != nil { + return path, err + } + + return "", fmt.Errorf("service: unable to determine ServicePath for type %T", i) +} + +// EntryPath returns the relative entry path for an object. +// +// The path will be the first of these that are present: +// +// • The value returned by the struct's EntryPath method, if implemented. +// +// • The value returned by an exported service.EntryPathGetter member field's EntryPath +// method, which will be passed that field's "service" tag value. +func EntryPath(i interface{}) (string, error) { + if path, err := structOrFieldPath(i, fieldEntryPath); path != "" || err != nil { + return path, err + } + + return "", fmt.Errorf("service: unable to determine EntryPath") +} + +// structOrFieldPath returns the path for a given struct. The path is determined as the first of: +// +// • the result of calling pathFunc directly for the input struct (does the struct implement pathFunc's required interface?) and an empty tag value +// +// • the result of caling getPathFromStructFields for the input struct +// +// If no error is encountered, no path was determined, an empty path and nil error will be returned. +func structOrFieldPath(i interface{}, f pathFunc) (string, error) { + iV := reflect.ValueOf(i) + + if !iV.IsValid() { + return "", fmt.Errorf("service: attempted operation on invalid value") + } + + if path, err := f(iV, ""); path != "" || err != nil { + return path, err + } + + if path, err := getPathFromStructFields(iV, f); path != "" || err != nil { + return path, err + } + + return "", nil +} + +// getPathFromStructFields returns the path for a given struct's fields. The path is determined by iterating +// through each of the struct's exported fields and running the given pathFunc for them with the field's +// "service" struct tag. If multiple fields return a non-empty value for the pathFunc, an error will be returned, +// as this is an ambiguous configuration. +func getPathFromStructFields(v reflect.Value, f pathFunc) (string, error) { + derefV := derefValue(v) + + if derefV.Kind() != reflect.Struct { + return "", fmt.Errorf("service: attempted ServicePath of non-struct type %T", derefV.Type().Name()) + } + + var servicePathGetterPath string + + for i := 0; i < derefV.Type().NumField(); i++ { + field := derefV.Type().Field(i) + + var fieldV reflect.Value + + if !field.IsExported() { + // unexported fields are treated like their type's zero value + fieldV = reflect.New(field.Type).Elem() + } else { + fieldV = derefV.Field(i) + } + + fieldTag := field.Tag.Get("service") + + gotPath, err := f(fieldV, fieldTag) + if err != nil { + return "", err + } + + if gotPath != "" { + if servicePathGetterPath != "" { + return "", fmt.Errorf("service: multiple ServicePathGetter fields found in type %T", i) + } + + servicePathGetterPath = gotPath + } + } + + return servicePathGetterPath, nil +} + +// pathFunc describes a function that returns a path for a reflect.Value and tag value. +type pathFunc func(reflect.Value, string) (string, error) + +// fieldServicePath is a pathFunc that returns the path for a ServicePathGetter. +var fieldServicePath pathFunc = func(fieldV reflect.Value, fieldTag string) (string, error) { + if fieldPathGetter, ok := fieldV.Interface().(ServicePathGetter); ok { + return fieldPathGetter.GetServicePath(fieldTag) + } + + return "", nil +} + +// fieldEntryPath is a pathFunc that returns the path for an EntryPathGetter. +var fieldEntryPath pathFunc = func(fieldV reflect.Value, fieldTag string) (string, error) { + if entryPathGetter, ok := fieldV.Interface().(EntryPathGetter); ok { + return entryPathGetter.GetEntryPath(fieldTag) + } + + return "", nil +} + +// derefValue returns a new reflect.Value by dereferencing v. +func derefValue(v reflect.Value) reflect.Value { + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + + return v +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/service/service_status_codes.go b/vendor/github.com/splunk/go-splunk-client/pkg/service/service_status_codes.go new file mode 100644 index 00000000..ef71a994 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/service/service_status_codes.go @@ -0,0 +1,80 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package service + +import ( + "fmt" + "reflect" +) + +// TagDefaultsStatusCoder is the interface for types that return a StatusCodes object from a service +// tag configuration and default StatusCodes value. +type TagDefaultsStatusCoder interface { + WithTagDefaults(string, StatusCodes) (StatusCodes, error) +} + +// ServiceStatusCodes returns the StatusCodes configuration for the given input struct. +func ServiceStatusCodes(input interface{}, defaults StatusCodes) (StatusCodes, error) { + var defaultsCoder TagDefaultsStatusCoder + var serviceTag string + + if coder, ok := input.(TagDefaultsStatusCoder); ok { + defaultsCoder = coder + } + + if defaultsCoder == nil { + inputV := reflect.ValueOf(input) + + for inputV.Kind() == reflect.Ptr { + inputV = inputV.Elem() + } + + inputT := inputV.Type() + if inputV.Kind() != reflect.Struct { + return StatusCodes{}, fmt.Errorf("service: ServiceStatusCodes attempted on non-struct type %T", input) + } + + for i := 0; i < inputV.NumField(); i++ { + fieldV := inputV.Field(i) + field := inputT.Field(i) + fieldT := field.Type + if !field.IsExported() { + // unexported fields are treated like the zero values of their type + fieldV = reflect.New(fieldT).Elem() + } + + if coder, ok := fieldV.Interface().(TagDefaultsStatusCoder); ok { + if defaultsCoder != nil { + return StatusCodes{}, fmt.Errorf("service: ServiceStatusCodes attempted on struct type %T with multiple DefaultsStatusCoder fields", input) + } + + defaultsCoder = coder + serviceTag = inputV.Type().Field(i).Tag.Get("service") + } + } + } + + newCodes := defaults + if defaultsCoder != nil { + withTagDefaults, err := defaultsCoder.WithTagDefaults(serviceTag, defaults) + if err != nil { + return StatusCodes{}, err + } + + newCodes = withTagDefaults + } + + return newCodes, nil +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/service/status_codes.go b/vendor/github.com/splunk/go-splunk-client/pkg/service/status_codes.go new file mode 100644 index 00000000..093fe0fe --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/service/status_codes.go @@ -0,0 +1,107 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package service + +import ( + "fmt" + "reflect" + "strconv" + "strings" +) + +// StatusCodes define the expected status code returned for a given operation. +type StatusCodes struct { + Created int + Read int + Updated int + Deleted int + NotFound int +} + +// WithDefaults returns a new StatusCodes with values from defaults applied for any +// field with the zero value. +func (codes StatusCodes) WithDefaults(defaults StatusCodes) StatusCodes { + // for codesV to be settable, it needs to come from a pointer + // codes wasn't passed as a pointer, so this still only permits changing the local copy of codes + codesV := reflect.ValueOf(&codes).Elem() + + defaultsV := reflect.ValueOf(defaults) + + for i := 0; i < codesV.NumField(); i++ { + field := codesV.Field(i) + if !field.CanSet() || field.Kind() != reflect.Int { + continue + } + + if field.IsZero() { + field.Set(defaultsV.Field(i)) + } + } + + return codes +} + +// withTagDefaults returns a new StatusCodes with values from the tag's configuration +// applied for any field with the zero value. +func (codes StatusCodes) withTagDefaults(tag string) (StatusCodes, error) { + if tag == "" { + return codes, nil + } + + actions := strings.Split(tag, ",") + + tagDefaultCodes := StatusCodes{} + // for codesV to be settable, it needs to come from a pointer + codesV := reflect.ValueOf(&tagDefaultCodes).Elem() + + for _, action := range actions { + actionParts := strings.Split(action, "=") + if len(actionParts) != 2 { + return StatusCodes{}, fmt.Errorf("service: unable to parse action code: %q", action) + } + + actionName := actionParts[0] + actionCode, err := strconv.ParseInt(actionParts[1], 10, 0) + if err != nil { + return StatusCodes{}, fmt.Errorf("service: unable to parse status code for action: %q: %s", action, err) + } + + actionField := codesV.FieldByName(actionName) + if !actionField.IsValid() { + return StatusCodes{}, fmt.Errorf("service: unknown action name: %s", actionName) + } + + if actionField.Kind() != reflect.Int { + return StatusCodes{}, fmt.Errorf("service: field not int: %s", actionName) + } + + if !actionField.CanSet() { + return StatusCodes{}, fmt.Errorf("service: unable to set field: %s", actionName) + } + actionField.SetInt(actionCode) + } + + return codes.WithDefaults(tagDefaultCodes), nil +} + +// WithTagDefaults returns a new StatusCodes by applying the given tag and defaults. +func (codes StatusCodes) WithTagDefaults(tag string, defaults StatusCodes) (StatusCodes, error) { + codes, err := codes.withTagDefaults(tag) + if err != nil { + return StatusCodes{}, err + } + + return codes.WithDefaults(defaults), nil +} diff --git a/vendor/github.com/splunk/go-splunk-client/pkg/values/encode.go b/vendor/github.com/splunk/go-splunk-client/pkg/values/encode.go new file mode 100644 index 00000000..0d94e2f5 --- /dev/null +++ b/vendor/github.com/splunk/go-splunk-client/pkg/values/encode.go @@ -0,0 +1,230 @@ +// Copyright 2022 Splunk, 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 +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package values implements encoding objects into url.Values. +package values + +import ( + "fmt" + "net/url" + "reflect" +) + +// Encode returns url.Values for a given input interface. +// +// A struct field's key defaults to the name of the field. Nested values default +// to having their key as a dotted suffix to the parent object's key. An empty parent +// key results in the child key being used directly. The parent key for the original +// value passed to Encode is an empty string. +// +// Anonymous struct fields also start with an empty parent key. +// +// Struct tags can be used to customize the encoding behavior of specific fields. +// Tags are formatted as ",