From 777be0099b8bf775e7e8b37d940f1ef08d7d46f4 Mon Sep 17 00:00:00 2001 From: Phil Adams Date: Mon, 30 Mar 2020 12:16:22 -0500 Subject: [PATCH] feat(Global Search): add service and test code to project --- globalsearchv2/global_search_v2.go | 618 ++++++++++++++++++ .../global_search_v2_integration_test.go | 150 +++++ globalsearchv2/global_search_v2_suite_test.go | 28 + globalsearchv2/global_search_v2_test.go | 201 ++++++ go.mod | 10 +- go.sum | 67 ++ 6 files changed, 1073 insertions(+), 1 deletion(-) create mode 100644 globalsearchv2/global_search_v2.go create mode 100644 globalsearchv2/global_search_v2_integration_test.go create mode 100644 globalsearchv2/global_search_v2_suite_test.go create mode 100644 globalsearchv2/global_search_v2_test.go diff --git a/globalsearchv2/global_search_v2.go b/globalsearchv2/global_search_v2.go new file mode 100644 index 00000000..c010e49f --- /dev/null +++ b/globalsearchv2/global_search_v2.go @@ -0,0 +1,618 @@ +/** + * (C) Copyright IBM Corp. 2020. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Package globalsearchv2 : Operations and models for the GlobalSearchV2 service +package globalsearchv2 + +import ( + "encoding/json" + "fmt" + "github.com/IBM/go-sdk-core/v3/core" + common "github.com/IBM/platform-services-go-sdk/common" + "strings" +) + +// GlobalSearchV2 : Search for resources with the global and shared resource properties repository integrated in the IBM +// Cloud Platform. The search repository stores and searches cloud resources attributes, which categorize or classify +// resources. A resource is a physical or logical component that can be provisioned or reserved for an application or +// service instance and is owned by resource providers, such as Cloud Foundry, IBM containers, or Resource Controller, +// in the IBM Cloud platform. Resources are uniquely identified by a CRN (Cloud Resource Naming identifier) or by an IMS +// ID. The properties of a resource include tags and system properties. Both properties are defined in an IBM Cloud +// billing account, and span across many regions. +// +// Version: 2.0.1 +type GlobalSearchV2 struct { + Service *core.BaseService +} + +// DefaultServiceURL is the default URL to make service requests to. +const DefaultServiceURL = "https://api.global-search-tagging.cloud.ibm.com/" + +// DefaultServiceName is the default key used to find external configuration information. +const DefaultServiceName = "global_search" + +// GlobalSearchV2Options : Service options +type GlobalSearchV2Options struct { + ServiceName string + URL string + Authenticator core.Authenticator +} + +// NewGlobalSearchV2UsingExternalConfig : constructs an instance of GlobalSearchV2 with passed in options and external configuration. +func NewGlobalSearchV2UsingExternalConfig(options *GlobalSearchV2Options) (globalSearch *GlobalSearchV2, err error) { + if options.ServiceName == "" { + options.ServiceName = DefaultServiceName + } + + if options.Authenticator == nil { + options.Authenticator, err = core.GetAuthenticatorFromEnvironment(options.ServiceName) + if err != nil { + return + } + } + + globalSearch, err = NewGlobalSearchV2(options) + if err != nil { + return + } + + err = globalSearch.Service.ConfigureService(options.ServiceName) + if err != nil { + return + } + + if options.URL != "" { + err = globalSearch.Service.SetServiceURL(options.URL) + } + return +} + +// NewGlobalSearchV2 : constructs an instance of GlobalSearchV2 with passed in options. +func NewGlobalSearchV2(options *GlobalSearchV2Options) (service *GlobalSearchV2, err error) { + serviceOptions := &core.ServiceOptions{ + URL: DefaultServiceURL, + Authenticator: options.Authenticator, + } + + baseService, err := core.NewBaseService(serviceOptions) + if err != nil { + return + } + + if options.URL != "" { + err = baseService.SetServiceURL(options.URL) + if err != nil { + return + } + } + + service = &GlobalSearchV2{ + Service: baseService, + } + + return +} + +// SetServiceURL sets the service URL +func (globalSearch *GlobalSearchV2) SetServiceURL(url string) error { + return globalSearch.Service.SetServiceURL(url) +} + +// Search : Find instances of resources +// 'Find cloud foundry resources, resource controlled enabled resources, or storage and network resources running on +// classic infrastructure in a specific account ID. You can apply query strings if necessary. To filter results, you can +// insert a string using the Lucene syntax and the query string is parsed into a series of terms and operators. A term +// can be a single word or a phrase, in which case the search is performed for all the words, in the same order. To +// filter for a specific value regardless of the property that contains it, use an asterisk as the key name. Only +// resources that belong to the account ID and that are accessible by the client are returned. You must use this +// operation when you need to fetch more than `10000` resource items. The `/v2/resources/search` prohibits paginating +// through such a big number. On the first call, the operation returns a live cursor on the data that you must use on +// all the subsequent calls to get the next batch of results until you get the empty result set. By default, the fields +// returned for every resources are: "crn", "name", "family", "type", "account_id". You can specify the subset of the +// fields you want in your request.''. +func (globalSearch *GlobalSearchV2) Search(searchOptions *SearchOptions) (result *ScanResult, response *core.DetailedResponse, err error) { + err = core.ValidateNotNil(searchOptions, "searchOptions cannot be nil") + if err != nil { + return + } + err = core.ValidateStruct(searchOptions, "searchOptions") + if err != nil { + return + } + + pathSegments := []string{"v3/resources/search"} + pathParameters := []string{} + + builder := core.NewRequestBuilder(core.POST) + _, err = builder.ConstructHTTPURL(globalSearch.Service.Options.URL, pathSegments, pathParameters) + if err != nil { + return + } + + for headerName, headerValue := range searchOptions.Headers { + builder.AddHeader(headerName, headerValue) + } + + sdkHeaders := common.GetSdkHeaders("global_search", "V2", "Search") + for headerName, headerValue := range sdkHeaders { + builder.AddHeader(headerName, headerValue) + } + builder.AddHeader("Accept", "application/json") + builder.AddHeader("Content-Type", "application/json") + if searchOptions.TransactionID != nil { + builder.AddHeader("transaction-id", fmt.Sprint(*searchOptions.TransactionID)) + } + + if searchOptions.AccountID != nil { + builder.AddQuery("account_id", fmt.Sprint(*searchOptions.AccountID)) + } + if searchOptions.Limit != nil { + builder.AddQuery("limit", fmt.Sprint(*searchOptions.Limit)) + } + if searchOptions.Timeout != nil { + builder.AddQuery("timeout", fmt.Sprint(*searchOptions.Timeout)) + } + if searchOptions.Sort != nil { + builder.AddQuery("sort", strings.Join(searchOptions.Sort, ",")) + } + + body := make(map[string]interface{}) + if searchOptions.Query != nil { + body["query"] = searchOptions.Query + } + if searchOptions.Fields != nil { + body["fields"] = searchOptions.Fields + } + if searchOptions.SearchCursor != nil { + body["search_cursor"] = searchOptions.SearchCursor + } + _, err = builder.SetBodyContentJSON(body) + if err != nil { + return + } + + request, err := builder.Build() + if err != nil { + return + } + + response, err = globalSearch.Service.Request(request, make(map[string]interface{})) + if err == nil { + m, ok := response.Result.(map[string]interface{}) + if !ok { + err = fmt.Errorf("an error occurred while processing the operation response") + return + } + result, err = UnmarshalScanResult(m) + response.Result = result + } + + return +} + +// GetSupportedTypes : Get all supported resource types +// Retrieves a list of all the resource types supported by GhoST. +func (globalSearch *GlobalSearchV2) GetSupportedTypes(getSupportedTypesOptions *GetSupportedTypesOptions) (result *SupportedTypesList, response *core.DetailedResponse, err error) { + err = core.ValidateStruct(getSupportedTypesOptions, "getSupportedTypesOptions") + if err != nil { + return + } + + pathSegments := []string{"v2/resources/supported_types"} + pathParameters := []string{} + + builder := core.NewRequestBuilder(core.GET) + _, err = builder.ConstructHTTPURL(globalSearch.Service.Options.URL, pathSegments, pathParameters) + if err != nil { + return + } + + for headerName, headerValue := range getSupportedTypesOptions.Headers { + builder.AddHeader(headerName, headerValue) + } + + sdkHeaders := common.GetSdkHeaders("global_search", "V2", "GetSupportedTypes") + for headerName, headerValue := range sdkHeaders { + builder.AddHeader(headerName, headerValue) + } + builder.AddHeader("Accept", "application/json") + + request, err := builder.Build() + if err != nil { + return + } + + response, err = globalSearch.Service.Request(request, make(map[string]interface{})) + if err == nil { + m, ok := response.Result.(map[string]interface{}) + if !ok { + err = fmt.Errorf("an error occurred while processing the operation response") + return + } + result, err = UnmarshalSupportedTypesList(m) + response.Result = result + } + + return +} + +// GetSupportedTypesOptions : The GetSupportedTypes options. +type GetSupportedTypesOptions struct { + + // Allows users to set headers on API requests + Headers map[string]string +} + +// NewGetSupportedTypesOptions : Instantiate GetSupportedTypesOptions +func (*GlobalSearchV2) NewGetSupportedTypesOptions() *GetSupportedTypesOptions { + return &GetSupportedTypesOptions{} +} + +// SetHeaders : Allow user to set Headers +func (options *GetSupportedTypesOptions) SetHeaders(param map[string]string) *GetSupportedTypesOptions { + options.Headers = param + return options +} + +// ResultItem : A resource returned in a search result. +type ResultItem struct { + // Resource identifier in CRN format. + Crn *string `json:"crn,omitempty"` + + // Allows users to set arbitrary properties + additionalProperties map[string]interface{} +} + + +// SetProperty allows the user to set an arbitrary property on an instance of ResultItem +func (o *ResultItem) SetProperty(key string, value interface{}) { + if o.additionalProperties == nil { + o.additionalProperties = make(map[string]interface{}) + } + o.additionalProperties[key] = value +} + +// GetProperty allows the user to retrieve an arbitrary property from an instance of ResultItem +func (o *ResultItem) GetProperty(key string) interface{} { + return o.additionalProperties[key] +} + +// GetProperties allows the user to retrieve the map of arbitrary properties from an instance of ResultItem +func (o *ResultItem) GetProperties() map[string]interface{} { + return o.additionalProperties +} + +// MarshalJSON performs custom serialization for instances of ResultItem +func (o *ResultItem) MarshalJSON() (buffer []byte, err error) { + m := make(map[string]interface{}) + if len(o.additionalProperties) > 0 { + for k, v := range o.additionalProperties { + m[k] = v + } + } + if o.Crn != nil { + m["crn"] = o.Crn + } + buffer, err = json.Marshal(m) + return +} + +// UnmarshalResultItem constructs an instance of ResultItem from the specified map. +func UnmarshalResultItem(m map[string]interface{}) (result *ResultItem, err error) { + m = core.CopyMap(m) + obj := new(ResultItem) + obj.Crn, err = core.UnmarshalString(m, "crn") + if err != nil { + return + } + delete(m, "crn") + for k := range m { + v, e := core.UnmarshalAny(m, k) + if e != nil { + err = e + return + } + obj.SetProperty(k, v) + } + result = obj + return +} + +// UnmarshalResultItemSlice unmarshals a slice of ResultItem instances from the specified list of maps. +func UnmarshalResultItemSlice(s []interface{}) (slice []ResultItem, err error) { + for _, v := range s { + objMap, ok := v.(map[string]interface{}) + if !ok { + err = fmt.Errorf("slice element should be a map containing an instance of 'ResultItem'") + return + } + obj, e := UnmarshalResultItem(objMap) + if e != nil { + err = e + return + } + slice = append(slice, *obj) + } + return +} + +// UnmarshalResultItemAsProperty unmarshals an instance of ResultItem that is stored as a property +// within the specified map. +func UnmarshalResultItemAsProperty(m map[string]interface{}, propertyName string) (result *ResultItem, err error) { + v, foundIt := m[propertyName] + if foundIt { + objMap, ok := v.(map[string]interface{}) + if !ok { + err = fmt.Errorf("map property '%s' should be a map containing an instance of 'ResultItem'", propertyName) + return + } + result, err = UnmarshalResultItem(objMap) + } + return +} + +// UnmarshalResultItemSliceAsProperty unmarshals a slice of ResultItem instances that are stored as a property +// within the specified map. +func UnmarshalResultItemSliceAsProperty(m map[string]interface{}, propertyName string) (slice []ResultItem, err error) { + v, foundIt := m[propertyName] + if foundIt { + vSlice, ok := v.([]interface{}) + if !ok { + err = fmt.Errorf("map property '%s' should be a slice of maps, each containing an instance of 'ResultItem'", propertyName) + return + } + slice, err = UnmarshalResultItemSlice(vSlice) + } + return +} + +// ScanResult : The search scan response. +type ScanResult struct { + // The search cursor to use on all calls after the first one. + SearchCursor *string `json:"search_cursor" validate:"required"` + + // Value of the limit parameter specified by the user. + Limit *float64 `json:"limit,omitempty"` + + // The array of results. Each item represents a resource. An empty array signals the end of the result set, there are + // no more hits to fetch. + Items []ResultItem `json:"items" validate:"required"` +} + + +// UnmarshalScanResult constructs an instance of ScanResult from the specified map. +func UnmarshalScanResult(m map[string]interface{}) (result *ScanResult, err error) { + obj := new(ScanResult) + obj.SearchCursor, err = core.UnmarshalString(m, "search_cursor") + if err != nil { + return + } + obj.Limit, err = core.UnmarshalFloat64(m, "limit") + if err != nil { + return + } + obj.Items, err = UnmarshalResultItemSliceAsProperty(m, "items") + if err != nil { + return + } + result = obj + return +} + +// UnmarshalScanResultSlice unmarshals a slice of ScanResult instances from the specified list of maps. +func UnmarshalScanResultSlice(s []interface{}) (slice []ScanResult, err error) { + for _, v := range s { + objMap, ok := v.(map[string]interface{}) + if !ok { + err = fmt.Errorf("slice element should be a map containing an instance of 'ScanResult'") + return + } + obj, e := UnmarshalScanResult(objMap) + if e != nil { + err = e + return + } + slice = append(slice, *obj) + } + return +} + +// UnmarshalScanResultAsProperty unmarshals an instance of ScanResult that is stored as a property +// within the specified map. +func UnmarshalScanResultAsProperty(m map[string]interface{}, propertyName string) (result *ScanResult, err error) { + v, foundIt := m[propertyName] + if foundIt { + objMap, ok := v.(map[string]interface{}) + if !ok { + err = fmt.Errorf("map property '%s' should be a map containing an instance of 'ScanResult'", propertyName) + return + } + result, err = UnmarshalScanResult(objMap) + } + return +} + +// UnmarshalScanResultSliceAsProperty unmarshals a slice of ScanResult instances that are stored as a property +// within the specified map. +func UnmarshalScanResultSliceAsProperty(m map[string]interface{}, propertyName string) (slice []ScanResult, err error) { + v, foundIt := m[propertyName] + if foundIt { + vSlice, ok := v.([]interface{}) + if !ok { + err = fmt.Errorf("map property '%s' should be a slice of maps, each containing an instance of 'ScanResult'", propertyName) + return + } + slice, err = UnmarshalScanResultSlice(vSlice) + } + return +} + +// SearchOptions : The Search options. +type SearchOptions struct { + // The Lucene-formatted query string. Default to '*' if not set. + Query *string `json:"query,omitempty"` + + // The list of the fields returned by the search. Defaults to all. `crn` is always returned. + Fields []string `json:"fields,omitempty"` + + // An opaque search cursor that is returned on each operation call and that must be set on next call. + SearchCursor *string `json:"search_cursor,omitempty"` + + // An aplhanumeric string that can be used to trace a request across services. If not specified it will be + // automatically generated with the prefix "gst-". + TransactionID *string `json:"transaction-id,omitempty"` + + // The account ID to filter resources. + AccountID *string `json:"account_id,omitempty"` + + // The maximum number of hits to return. Defaults to 10. + Limit *int64 `json:"limit,omitempty"` + + // A search timeout, bounding the search request to be executed within the specified time value and bail with the hits + // accumulated up to that point when expired. Defaults to the system defined timeout. + Timeout *int64 `json:"timeout,omitempty"` + + // Comma separated properties names used for sorting. + Sort []string `json:"sort,omitempty"` + + // Allows users to set headers on API requests + Headers map[string]string +} + +// NewSearchOptions : Instantiate SearchOptions +func (*GlobalSearchV2) NewSearchOptions() *SearchOptions { + return &SearchOptions{} +} + +// SetQuery : Allow user to set Query +func (options *SearchOptions) SetQuery(query string) *SearchOptions { + options.Query = core.StringPtr(query) + return options +} + +// SetFields : Allow user to set Fields +func (options *SearchOptions) SetFields(fields []string) *SearchOptions { + options.Fields = fields + return options +} + +// SetSearchCursor : Allow user to set SearchCursor +func (options *SearchOptions) SetSearchCursor(searchCursor string) *SearchOptions { + options.SearchCursor = core.StringPtr(searchCursor) + return options +} + +// SetTransactionID : Allow user to set TransactionID +func (options *SearchOptions) SetTransactionID(transactionID string) *SearchOptions { + options.TransactionID = core.StringPtr(transactionID) + return options +} + +// SetAccountID : Allow user to set AccountID +func (options *SearchOptions) SetAccountID(accountID string) *SearchOptions { + options.AccountID = core.StringPtr(accountID) + return options +} + +// SetLimit : Allow user to set Limit +func (options *SearchOptions) SetLimit(limit int64) *SearchOptions { + options.Limit = core.Int64Ptr(limit) + return options +} + +// SetTimeout : Allow user to set Timeout +func (options *SearchOptions) SetTimeout(timeout int64) *SearchOptions { + options.Timeout = core.Int64Ptr(timeout) + return options +} + +// SetSort : Allow user to set Sort +func (options *SearchOptions) SetSort(sort []string) *SearchOptions { + options.Sort = sort + return options +} + +// SetHeaders : Allow user to set Headers +func (options *SearchOptions) SetHeaders(param map[string]string) *SearchOptions { + options.Headers = param + return options +} + +// SupportedTypesList : A list of resource types supported by GhoST. +type SupportedTypesList struct { + // A list of resource types supported by GhoST. + SupportedTypes []string `json:"supported_types,omitempty"` +} + + +// UnmarshalSupportedTypesList constructs an instance of SupportedTypesList from the specified map. +func UnmarshalSupportedTypesList(m map[string]interface{}) (result *SupportedTypesList, err error) { + obj := new(SupportedTypesList) + obj.SupportedTypes, err = core.UnmarshalStringSlice(m, "supported_types") + if err != nil { + return + } + result = obj + return +} + +// UnmarshalSupportedTypesListSlice unmarshals a slice of SupportedTypesList instances from the specified list of maps. +func UnmarshalSupportedTypesListSlice(s []interface{}) (slice []SupportedTypesList, err error) { + for _, v := range s { + objMap, ok := v.(map[string]interface{}) + if !ok { + err = fmt.Errorf("slice element should be a map containing an instance of 'SupportedTypesList'") + return + } + obj, e := UnmarshalSupportedTypesList(objMap) + if e != nil { + err = e + return + } + slice = append(slice, *obj) + } + return +} + +// UnmarshalSupportedTypesListAsProperty unmarshals an instance of SupportedTypesList that is stored as a property +// within the specified map. +func UnmarshalSupportedTypesListAsProperty(m map[string]interface{}, propertyName string) (result *SupportedTypesList, err error) { + v, foundIt := m[propertyName] + if foundIt { + objMap, ok := v.(map[string]interface{}) + if !ok { + err = fmt.Errorf("map property '%s' should be a map containing an instance of 'SupportedTypesList'", propertyName) + return + } + result, err = UnmarshalSupportedTypesList(objMap) + } + return +} + +// UnmarshalSupportedTypesListSliceAsProperty unmarshals a slice of SupportedTypesList instances that are stored as a property +// within the specified map. +func UnmarshalSupportedTypesListSliceAsProperty(m map[string]interface{}, propertyName string) (slice []SupportedTypesList, err error) { + v, foundIt := m[propertyName] + if foundIt { + vSlice, ok := v.([]interface{}) + if !ok { + err = fmt.Errorf("map property '%s' should be a slice of maps, each containing an instance of 'SupportedTypesList'", propertyName) + return + } + slice, err = UnmarshalSupportedTypesListSlice(vSlice) + } + return +} diff --git a/globalsearchv2/global_search_v2_integration_test.go b/globalsearchv2/global_search_v2_integration_test.go new file mode 100644 index 00000000..e9a7bbe7 --- /dev/null +++ b/globalsearchv2/global_search_v2_integration_test.go @@ -0,0 +1,150 @@ +// +build integration + +package globalsearchv2_test + +/** + * (C) Copyright IBM Corp. 2020. + * + * 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. + */ + +import ( + "github.com/IBM/go-sdk-core/v3/core" + "github.com/IBM/platform-services-go-sdk/globalsearchv2" + "github.com/joho/godotenv" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "os" +) + +var service *globalsearchv2.GlobalSearchV2 +var configLoaded = false +const externalConfigFile = "../.ghostenv" + +func shouldSkipTest() { + if !configLoaded { + Skip("External configuration is not available, skipping...") + } +} + +var _ = Describe("Global Search and Tagging - Search integration test", func() { + It("Successfully load the configuration", func() { + err := godotenv.Load(externalConfigFile) + if err == nil { + configLoaded = true + } else { + Skip("External configuration could not be loaded, skipping...") + } + }) + + It("Successfully construct service", func() { + shouldSkipTest() + + // Create the authenticator. + authenticator := &core.IamAuthenticator{ + ApiKey: os.Getenv("GST_IINTERNA_APIKEY"), + URL: os.Getenv("GST_IAM_URL"), + } + + options := &globalsearchv2.GlobalSearchV2Options{ + Authenticator: authenticator, + URL: os.Getenv("GST_API_URL"), + } + service, err := globalsearchv2.NewGlobalSearchV2(options) + Expect(err).To(BeNil()) + Expect(service).ToNot(BeNil()) + }) + + Describe("Call Search v3 api with query 'name:gst-sdk*' all fields", func() { + + // Construct an instance of the SearchOptions model + searchOptionsModel := service.NewSearchOptions() + searchOptionsModel.SetQuery(os.Getenv("GST_QUERY")) + searchOptionsModel.SetFields([]string{"*"}) + + It("Successfully list all resources", func() { + shouldSkipTest() + result, detailedResponse, err := service.Search(searchOptionsModel) + Expect(err).To(BeNil()) + Expect(detailedResponse.StatusCode).To(Equal(200)) + Expect(result.Items).To(HaveLen(2)) + for _, elem := range result.Items { + Expect(elem.GetProperty("doc")).NotTo(BeNil()) + Expect(elem.GetProperty("family")).NotTo(BeNil()) + Expect(elem.GetProperty("type")).NotTo(BeNil()) + Expect(*elem.Crn).NotTo(BeNil()) + } + }) + }) + + Describe("Call Search v3 api with query 'name:gst-sdk*' retrieving only the attributes crn and name", func() { + + // Construct an instance of the SearchOptions model + searchOptionsModel := service.NewSearchOptions() + searchOptionsModel.SetQuery(os.Getenv("GST_QUERY")) + searchOptionsModel.SetLimit(1) + searchOptionsModel.SetFields([]string{"crn", "name"}) + + It("Successfully list resource using cursor", func() { + shouldSkipTest() + result, detailedResponse, err := service.Search(searchOptionsModel) + Expect(err).To(BeNil()) + Expect(detailedResponse.StatusCode).To(Equal(200)) + Expect(result.Items).To(HaveLen(1)) + for _, elem := range result.Items { + Expect(elem.GetProperty("doc")).To(BeNil()) + Expect(elem.GetProperty("family")).To(BeNil()) + Expect(elem.GetProperty("name")).NotTo(BeNil()) + Expect(*elem.Crn).NotTo(BeNil()) + } + firstCrn := *result.Items[0].Crn + + search_cursor := *result.SearchCursor + searchOptionsModelCursor := service.NewSearchOptions() + searchOptionsModelCursor.SetQuery(os.Getenv("GST_QUERY")) + searchOptionsModelCursor.SetLimit(1) + searchOptionsModelCursor.SetFields([]string{"crn", "name"}) + searchOptionsModelCursor.SetSearchCursor(search_cursor) + + resultCursor, detailedResponseCursor, errCursor := service.Search(searchOptionsModelCursor) + Expect(errCursor).To(BeNil()) + Expect(detailedResponseCursor.StatusCode).To(Equal(200)) + Expect(resultCursor.Items).To(HaveLen(1)) + for _, elem := range resultCursor.Items { + Expect(elem.GetProperty("doc")).To(BeNil()) + Expect(elem.GetProperty("family")).To(BeNil()) + Expect(elem.GetProperty("name")).NotTo(BeNil()) + Expect(*elem.Crn).NotTo(BeNil()) + } + secondCrn := *resultCursor.Items[0].Crn + + Expect(firstCrn).NotTo(BeIdenticalTo(secondCrn)) + }) + }) + + Describe("Call GetSupportedTypes", func() { + + // Construct an instance of the SearchOptions model + supportedTypessModel := service.NewGetSupportedTypesOptions() + + It("Successfully list all resources", func() { + shouldSkipTest() + + result, detailedResponse, err := service.GetSupportedTypes(supportedTypessModel) + Expect(err).To(BeNil()) + Expect(detailedResponse.StatusCode).To(Equal(200)) + Expect(result.SupportedTypes).To(ContainElement("cf-space")) + Expect(result.SupportedTypes).NotTo(ContainElement("fake-resource!")) + }) + }) +}) diff --git a/globalsearchv2/global_search_v2_suite_test.go b/globalsearchv2/global_search_v2_suite_test.go new file mode 100644 index 00000000..95d26af6 --- /dev/null +++ b/globalsearchv2/global_search_v2_suite_test.go @@ -0,0 +1,28 @@ +/** + * (C) Copyright IBM Corp. 2020. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package globalsearchv2_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "testing" +) + +func TestGlobalSearchV2(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "GlobalSearchV2 Suite") +} diff --git a/globalsearchv2/global_search_v2_test.go b/globalsearchv2/global_search_v2_test.go new file mode 100644 index 00000000..d483b8df --- /dev/null +++ b/globalsearchv2/global_search_v2_test.go @@ -0,0 +1,201 @@ +/** + * (C) Copyright IBM Corp. 2020. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package globalsearchv2_test + +import ( + "bytes" + "fmt" + "github.com/IBM/go-sdk-core/v3/core" + "github.com/IBM/platform-services-go-sdk/globalsearchv2" + "github.com/go-openapi/strfmt" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "time" +) + +var _ = Describe(`GlobalSearchV2`, func() { + Describe(`Search(searchOptions *SearchOptions)`, func() { + bearerToken := "0ui9876453" + searchPath := "/v3/resources/search" + Context(`Using mock server endpoint`, func() { + testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + defer GinkgoRecover() + + // Verify the contents of the request + Expect(req.URL.Path).To(Equal(searchPath)) + Expect(req.Method).To(Equal("POST")) + Expect(req.Header["Authorization"]).ToNot(BeNil()) + Expect(req.Header["Authorization"][0]).To(Equal("Bearer " + bearerToken)) + Expect(req.Header["Transaction-Id"]).ToNot(BeNil()) + Expect(req.Header["Transaction-Id"][0]).To(Equal(fmt.Sprintf("%v", "testString"))) + Expect(req.URL.Query()["account_id"]).To(Equal([]string{"testString"})) + + Expect(req.URL.Query()["limit"]).To(Equal([]string{fmt.Sprint(int64(38))})) + + Expect(req.URL.Query()["timeout"]).To(Equal([]string{fmt.Sprint(int64(38))})) + + res.Header().Set("Content-type", "application/json") + res.WriteHeader(200) + fmt.Fprintf(res, `{"search_cursor": "SearchCursor", "limit": 5, "items": [{"crn": "Crn"}]}`) + })) + It(`Invoke Search successfully`, func() { + defer testServer.Close() + + testService, testServiceErr := globalsearchv2.NewGlobalSearchV2(&globalsearchv2.GlobalSearchV2Options{ + URL: testServer.URL, + Authenticator: &core.BearerTokenAuthenticator{ + BearerToken: bearerToken, + }, + }) + Expect(testServiceErr).To(BeNil()) + Expect(testService).ToNot(BeNil()) + + // Invoke operation with nil options model (negative test) + result, response, operationErr := testService.Search(nil) + Expect(operationErr).NotTo(BeNil()) + Expect(response).To(BeNil()) + Expect(result).To(BeNil()) + + // Construct an instance of the SearchOptions model + searchOptionsModel := new(globalsearchv2.SearchOptions) + searchOptionsModel.Query = core.StringPtr("testString") + searchOptionsModel.Fields = []string{"testString"} + searchOptionsModel.SearchCursor = core.StringPtr("testString") + searchOptionsModel.TransactionID = core.StringPtr("testString") + searchOptionsModel.AccountID = core.StringPtr("testString") + searchOptionsModel.Limit = core.Int64Ptr(int64(38)) + searchOptionsModel.Timeout = core.Int64Ptr(int64(38)) + searchOptionsModel.Sort = []string{"testString"} + + // Invoke operation with valid options model (positive test) + result, response, operationErr = testService.Search(searchOptionsModel) + Expect(operationErr).To(BeNil()) + Expect(response).ToNot(BeNil()) + Expect(result).ToNot(BeNil()) + }) + }) + }) + Describe(`GetSupportedTypes(getSupportedTypesOptions *GetSupportedTypesOptions)`, func() { + bearerToken := "0ui9876453" + getSupportedTypesPath := "/v2/resources/supported_types" + Context(`Using mock server endpoint`, func() { + testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + defer GinkgoRecover() + + // Verify the contents of the request + Expect(req.URL.Path).To(Equal(getSupportedTypesPath)) + Expect(req.Method).To(Equal("GET")) + Expect(req.Header["Authorization"]).ToNot(BeNil()) + Expect(req.Header["Authorization"][0]).To(Equal("Bearer " + bearerToken)) + res.Header().Set("Content-type", "application/json") + res.WriteHeader(200) + fmt.Fprintf(res, `{"supported_types": ["SupportedTypes"]}`) + })) + It(`Invoke GetSupportedTypes successfully`, func() { + defer testServer.Close() + + testService, testServiceErr := globalsearchv2.NewGlobalSearchV2(&globalsearchv2.GlobalSearchV2Options{ + URL: testServer.URL, + Authenticator: &core.BearerTokenAuthenticator{ + BearerToken: bearerToken, + }, + }) + Expect(testServiceErr).To(BeNil()) + Expect(testService).ToNot(BeNil()) + + // Invoke operation with nil options model (negative test) + result, response, operationErr := testService.GetSupportedTypes(nil) + Expect(operationErr).NotTo(BeNil()) + Expect(response).To(BeNil()) + Expect(result).To(BeNil()) + + // Construct an instance of the GetSupportedTypesOptions model + getSupportedTypesOptionsModel := new(globalsearchv2.GetSupportedTypesOptions) + + // Invoke operation with valid options model (positive test) + result, response, operationErr = testService.GetSupportedTypes(getSupportedTypesOptionsModel) + Expect(operationErr).To(BeNil()) + Expect(response).ToNot(BeNil()) + Expect(result).ToNot(BeNil()) + }) + }) + }) + Describe(`Utility function tests`, func() { + It(`Invoke CreateMockMap() successfully`, func() { + mockMap := CreateMockMap() + Expect(mockMap).ToNot(BeNil()) + }) + It(`Invoke CreateMockByteArray() successfully`, func() { + mockByteArray := CreateMockByteArray("This is a test") + Expect(mockByteArray).ToNot(BeNil()) + }) + It(`Invoke CreateMockUUID() successfully`, func() { + mockUUID := CreateMockUUID("9fab83da-98cb-4f18-a7ba-b6f0435c9673") + Expect(mockUUID).ToNot(BeNil()) + }) + It(`Invoke CreateMockReader() successfully`, func() { + mockReader := CreateMockReader("This is a test.") + Expect(mockReader).ToNot(BeNil()) + }) + It(`Invoke CreateMockDate() successfully`, func() { + mockDate := CreateMockDate() + Expect(mockDate).ToNot(BeNil()) + }) + It(`Invoke CreateMockDateTime() successfully`, func() { + mockDateTime := CreateMockDateTime() + Expect(mockDateTime).ToNot(BeNil()) + }) + }) +}) + +// +// Utility functions used by the generated test code +// + +func CreateMockMap() map[string]interface{} { + m := make(map[string]interface{}) + return m +} + +func CreateMockByteArray(mockData string) *[]byte { + ba := make([]byte, len(mockData)) + ba = append(ba, mockData...) + return &ba +} + +func CreateMockUUID(mockData string) *strfmt.UUID { + uuid := strfmt.UUID(mockData) + return &uuid +} + +func CreateMockReader(mockData string) io.ReadCloser { + return ioutil.NopCloser(bytes.NewReader([]byte(mockData))) +} + +func CreateMockDate() *strfmt.Date { + d := strfmt.Date(time.Now()) + return &d +} + +func CreateMockDateTime() *strfmt.DateTime { + d := strfmt.DateTime(time.Now()) + return &d +} diff --git a/go.mod b/go.mod index 6bedb2f7..53151186 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,12 @@ module github.com/IBM/platform-services-go-sdk go 1.12 -require github.com/stretchr/testify v1.5.1 +require ( + github.com/IBM/go-sdk-core/v3 v3.3.0 + github.com/go-openapi/strfmt v0.19.4 + github.com/joho/godotenv v1.3.0 + github.com/onsi/ginkgo v1.12.0 + github.com/onsi/gomega v1.9.0 + github.com/stretchr/testify v1.5.1 + github.ibm.com/ibmcloud/platform-services-go-sdk v0.4.0 +) diff --git a/go.sum b/go.sum index a80206ab..3786911a 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,77 @@ +github.com/IBM/go-sdk-core/v3 v3.2.4/go.mod h1:lk9eOzNbNltPf3CBpcg1Ewkhw4qC3u2QCCKDRsUA2M0= +github.com/IBM/go-sdk-core/v3 v3.3.0 h1:OM1ime6nSApZHp6WrCzKZ3gkjU3M5oOcoxRbQFl+nHI= +github.com/IBM/go-sdk-core/v3 v3.3.0/go.mod h1:lk9eOzNbNltPf3CBpcg1Ewkhw4qC3u2QCCKDRsUA2M0= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/strfmt v0.19.4 h1:eRvaqAhpL0IL6Trh5fDsGnGhiXndzHFuA05w6sXH6/g= +github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.ibm.com/ibmcloud/platform-services-go-sdk v0.4.0 h1:8FOTm4w5FH2FU8b0yP5ljgnjCuXb1CSjuHh7XUE+tc0= +github.ibm.com/ibmcloud/platform-services-go-sdk v0.4.0/go.mod h1:b18gHuStwJ5Cw/+4m07DHlsyF6wKsHI3LJbZJcmuBfM= +go.mongodb.org/mongo-driver v1.0.3 h1:GKoji1ld3tw2aC+GX1wbr/J2fX13yNacEYoJ8Nhr0yU= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M= +gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=