diff --git a/cmd/platform/constructor/resync.go b/cmd/platform/constructor/resync.go new file mode 100644 index 00000000..64c01db7 --- /dev/null +++ b/cmd/platform/constructor/resync.go @@ -0,0 +1,60 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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 cmdconstructor + +import ( + "fmt" + + "github.com/spf13/cobra" + + cmdutil "github.com/elastic/ecctl/cmd/util" + "github.com/elastic/ecctl/pkg/ecctl" + "github.com/elastic/ecctl/pkg/platform/constructor" +) + +var resyncConstructorCmd = &cobra.Command{ + Use: "resync { | --all}", + Short: "Resynchronizes the search index and cache for the selected constructor or all", + PreRunE: cmdutil.CheckInputHas1ArgsOr0ArgAndAll, + RunE: func(cmd *cobra.Command, args []string) error { + all, _ := cmd.Flags().GetBool("all") + + if all { + fmt.Println("Resynchronizing all constructors") + res, err := constructor.ResyncAll(constructor.Params{ + API: ecctl.Get().API, + }) + if err != nil { + return err + } + + return ecctl.Get().Formatter.Format("", res) + } + + fmt.Printf("Resynchronizing constructor: %s\n", args[0]) + return constructor.Resync(constructor.ResyncParams{ + API: ecctl.Get().API, + ID: args[0], + }) + }, +} + +func init() { + Command.AddCommand(resyncConstructorCmd) + resyncConstructorCmd.Flags().Bool("all", false, "Resynchronizes the search index for all constructors") +} diff --git a/docs/ecctl_platform_constructor.md b/docs/ecctl_platform_constructor.md index f9ef1f4c..1c4dbe69 100644 --- a/docs/ecctl_platform_constructor.md +++ b/docs/ecctl_platform_constructor.md @@ -41,5 +41,6 @@ ecctl platform constructor [flags] * [ecctl platform](ecctl_platform.md) - Manages the platform * [ecctl platform constructor list](ecctl_platform_constructor_list.md) - Returns all of the constructors in the platform * [ecctl platform constructor maintenance](ecctl_platform_constructor_maintenance.md) - Sets/un-sets a constructor's maintenance mode +* [ecctl platform constructor resync](ecctl_platform_constructor_resync.md) - Resynchronizes the search index and cache for the selected constructor or all * [ecctl platform constructor show](ecctl_platform_constructor_show.md) - Returns information about the constructor with given ID diff --git a/docs/ecctl_platform_constructor_resync.md b/docs/ecctl_platform_constructor_resync.md new file mode 100644 index 00000000..bf8ad1ba --- /dev/null +++ b/docs/ecctl_platform_constructor_resync.md @@ -0,0 +1,43 @@ +## ecctl platform constructor resync + +Resynchronizes the search index and cache for the selected constructor or all + +### Synopsis + +Resynchronizes the search index and cache for the selected constructor or all + +``` +ecctl platform constructor resync { | --all} [flags] +``` + +### Options + +``` + --all Resynchronizes the search index for all constructors + -h, --help help for resync +``` + +### Options inherited from parent commands + +``` + --apikey string API key to use to authenticate (If empty will look for EC_APIKEY environment variable) + --config string Config name, used to have multiple configs in $HOME/.ecctl/ (default "config") + --force Do not ask for confirmation + --format string Formats the output using a Go template + --host string Base URL to use + --insecure Skips all TLS validation + --message string A message to set on cluster operation + --output string Output format [text|json] (default "text") + --pass string Password to use to authenticate (If empty will look for EC_PASS environment variable) + --pprof Enables pprofing and saves the profile to pprof-20060102150405 + -q, --quiet Suppresses the configuration file used for the run, if any + --timeout duration Timeout to use on all HTTP calls (default 30s) + --trace Enables tracing saves the trace to trace-20060102150405 + --user string Username to use to authenticate (If empty will look for EC_USER environment variable) + --verbose Enable verbose mode +``` + +### SEE ALSO + +* [ecctl platform constructor](ecctl_platform_constructor.md) - Manages constructors + diff --git a/pkg/platform/constructor/resync.go b/pkg/platform/constructor/resync.go new file mode 100644 index 00000000..b06749ae --- /dev/null +++ b/pkg/platform/constructor/resync.go @@ -0,0 +1,81 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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 constructor + +import ( + "github.com/elastic/cloud-sdk-go/pkg/api" + "github.com/elastic/cloud-sdk-go/pkg/client/platform_infrastructure" + "github.com/elastic/cloud-sdk-go/pkg/models" + "github.com/hashicorp/go-multierror" + + "github.com/elastic/ecctl/pkg/util" +) + +// ResyncParams is consumed by Resync +type ResyncParams struct { + *api.API + ID string +} + +// Validate ensures the parameters are usable by the consuming function. +func (params ResyncParams) Validate() error { + var merr = new(multierror.Error) + + if params.API == nil { + merr = multierror.Append(merr, errAPICannotBeNil) + } + + if params.ID == "" { + merr = multierror.Append(merr, errIDCannotBeEmpty) + } + + return merr.ErrorOrNil() +} + +// Resync forces indexer to immediately resynchronize the search index +// and cache for a given constructor. +func Resync(params ResyncParams) error { + if err := params.Validate(); err != nil { + return err + } + + return util.ReturnErrOnly( + params.API.V1API.PlatformInfrastructure.ResyncConstructor( + platform_infrastructure.NewResyncConstructorParams(). + WithConstructorID(params.ID), + params.API.AuthWriter, + ), + ) +} + +// ResyncAll asynchronously resynchronizes the search index for all constructors. +func ResyncAll(params Params) (*models.ModelVersionIndexSynchronizationResults, error) { + if err := params.Validate(); err != nil { + return nil, err + } + + res, err := params.API.V1API.PlatformInfrastructure.ResyncConstructors( + platform_infrastructure.NewResyncConstructorsParams(), + params.API.AuthWriter, + ) + if err != nil { + return nil, api.UnwrapError(err) + } + + return res.Payload, nil +} diff --git a/pkg/platform/constructor/resync_test.go b/pkg/platform/constructor/resync_test.go new file mode 100644 index 00000000..be85b06f --- /dev/null +++ b/pkg/platform/constructor/resync_test.go @@ -0,0 +1,167 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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 constructor + +import ( + "errors" + "net/http" + "net/url" + "reflect" + "testing" + + "github.com/elastic/cloud-sdk-go/pkg/api" + "github.com/elastic/cloud-sdk-go/pkg/api/mock" + "github.com/elastic/cloud-sdk-go/pkg/models" + multierror "github.com/hashicorp/go-multierror" +) + +func TestResync(t *testing.T) { + type args struct { + params ResyncParams + } + tests := []struct { + name string + args args + wantErr error + }{ + { + name: "Fails due to parameter validation (Cluster ID)", + args: args{}, + wantErr: &multierror.Error{Errors: []error{ + errors.New("api field cannot be nil"), + errors.New("id field cannot be empty"), + }}, + }, + { + name: "Fails due to parameter validation (API)", + args: args{params: ResyncParams{ + ID: "d324608c97154bdba2dff97511d40368", + }}, + wantErr: &multierror.Error{Errors: []error{ + errors.New("api field cannot be nil"), + }}, + }, + { + name: "Fails due to unknown API response", + args: args{params: ResyncParams{ + ID: "2c221bd86b7f48959a59ee3128d5c5e8", + API: api.NewMock(mock.Response{Response: http.Response{ + StatusCode: http.StatusForbidden, + Body: mock.NewStringBody(`{"error": "some forbidden error"}`), + }}), + }}, + wantErr: errors.New(`{"error": "some forbidden error"}`), + }, + { + name: "Fails due to API error", + args: args{params: ResyncParams{ + ID: "2c221bd86b7f48959a59ee3128d5c5e8", + API: api.NewMock(mock.Response{ + Error: errors.New("error with API"), + }), + }}, + wantErr: &url.Error{ + Op: "Post", + URL: "https://mock-host/mock-path/platform/infrastructure/constructors/2c221bd86b7f48959a59ee3128d5c5e8/_resync", + Err: errors.New("error with API"), + }, + }, + { + name: "Succeeds to resynchronize Kibana instance without errors", + args: args{params: ResyncParams{ + ID: "d324608c97154bdba2dff97511d40368", + API: api.NewMock(mock.Response{Response: http.Response{ + StatusCode: http.StatusOK, + Body: mock.NewStringBody(`{}`), + }}), + }}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Resync(tt.args.params); !reflect.DeepEqual(err, tt.wantErr) { + t.Errorf("Resync() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestResyncAll(t *testing.T) { + type args struct { + params Params + } + tests := []struct { + name string + args args + wantErr error + want *models.ModelVersionIndexSynchronizationResults + }{ + { + name: "Fails due to parameter validation (API)", + args: args{params: Params{}}, + wantErr: errors.New("api field cannot be nil"), + }, + { + name: "Fails due to unknown API response", + args: args{params: Params{ + API: api.NewMock(mock.Response{Response: http.Response{ + StatusCode: http.StatusForbidden, + Body: mock.NewStringBody(`{"error": "some forbidden error"}`), + }}), + }}, + wantErr: errors.New(`{"error": "some forbidden error"}`), + }, + { + name: "Fails due to API error", + args: args{params: Params{ + API: api.NewMock(mock.Response{ + Error: errors.New("error with API"), + }), + }}, + wantErr: &url.Error{ + Op: "Post", + URL: "https://mock-host/mock-path/platform/infrastructure/constructors/_resync?skip_matching_version=true", + Err: errors.New("error with API"), + }, + }, + { + name: "Succeeds to re-synchronize all Kibana instances without errors", + args: args{params: Params{ + API: api.NewMock(mock.Response{Response: http.Response{ + StatusCode: http.StatusAccepted, + Body: mock.NewStringBody(`{}`), + }}), + }}, + want: &models.ModelVersionIndexSynchronizationResults{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ResyncAll(tt.args.params) + if !reflect.DeepEqual(tt.wantErr, err) { + t.Errorf("ResyncAll() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ResyncAll() = %v, want %v", got, tt.want) + } + }) + } +}