Skip to content

Commit

Permalink
cmd: add platform constructor resync command and --all flag (#131)
Browse files Browse the repository at this point in the history
Implements a new platform constructor resync command which resynchronises a given constructor or all.
  • Loading branch information
karencfv committed Jan 10, 2020
1 parent 6372103 commit 58138bd
Show file tree
Hide file tree
Showing 5 changed files with 352 additions and 0 deletions.
60 changes: 60 additions & 0 deletions 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 {<constructor id> | --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")
}
1 change: 1 addition & 0 deletions docs/ecctl_platform_constructor.md
Expand Up @@ -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

43 changes: 43 additions & 0 deletions 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 {<constructor id> | --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/<env> (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

81 changes: 81 additions & 0 deletions 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
}
167 changes: 167 additions & 0 deletions 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)
}
})
}
}

0 comments on commit 58138bd

Please sign in to comment.