Skip to content

Commit a88c221

Browse files
authored
cmd: new runner resync <runner id>|--all command (#181)
This commit introduces a new ecctl runner resync command which has the option to resync a single runner with the given runner ID, or all runners asynchronously with an -all flag
1 parent 2fda990 commit a88c221

File tree

5 files changed

+358
-0
lines changed

5 files changed

+358
-0
lines changed

cmd/platform/runner/resync.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package cmdrunner
19+
20+
import (
21+
"fmt"
22+
23+
"github.com/elastic/cloud-sdk-go/pkg/util/cmdutil"
24+
"github.com/spf13/cobra"
25+
26+
"github.com/elastic/ecctl/pkg/ecctl"
27+
"github.com/elastic/ecctl/pkg/platform/runner"
28+
)
29+
30+
var resyncRunnerCmd = &cobra.Command{
31+
Use: "resync {<runner id> | --all}",
32+
Short: "Resynchronizes the search index and cache for the selected runner or all",
33+
PreRunE: cmdutil.CheckInputHas1ArgsOr0ArgAndAll,
34+
RunE: func(cmd *cobra.Command, args []string) error {
35+
all, _ := cmd.Flags().GetBool("all")
36+
37+
if all {
38+
fmt.Println("Resynchronizing all runners")
39+
res, err := runner.ResyncAll(runner.Params{
40+
API: ecctl.Get().API,
41+
})
42+
if err != nil {
43+
return err
44+
}
45+
46+
return ecctl.Get().Formatter.Format("", res)
47+
}
48+
49+
fmt.Printf("Resynchronizing runner: %s\n", args[0])
50+
return runner.Resync(runner.ResyncParams{
51+
Params: runner.Params{
52+
API: ecctl.Get().API,
53+
},
54+
ID: args[0],
55+
})
56+
},
57+
}
58+
59+
func init() {
60+
Command.AddCommand(resyncRunnerCmd)
61+
resyncRunnerCmd.Flags().Bool("all", false, "Resynchronizes the search index for all runners")
62+
}

docs/ecctl_platform_runner.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,6 @@ ecctl platform runner [flags]
4040

4141
* [ecctl platform](ecctl_platform.md) - Manages the platform
4242
* [ecctl platform runner list](ecctl_platform_runner_list.md) - Lists the existing platform runners
43+
* [ecctl platform runner resync](ecctl_platform_runner_resync.md) - Resynchronizes the search index and cache for the selected runner or all
4344
* [ecctl platform runner show](ecctl_platform_runner_show.md) - Shows information about the specified runner
4445

docs/ecctl_platform_runner_resync.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
## ecctl platform runner resync
2+
3+
Resynchronizes the search index and cache for the selected runner or all
4+
5+
### Synopsis
6+
7+
Resynchronizes the search index and cache for the selected runner or all
8+
9+
```
10+
ecctl platform runner resync {<runner id> | --all} [flags]
11+
```
12+
13+
### Options
14+
15+
```
16+
--all Resynchronizes the search index for all runners
17+
-h, --help help for resync
18+
```
19+
20+
### Options inherited from parent commands
21+
22+
```
23+
--apikey string API key to use to authenticate (If empty will look for EC_APIKEY environment variable)
24+
--config string Config name, used to have multiple configs in $HOME/.ecctl/<env> (default "config")
25+
--force Do not ask for confirmation
26+
--format string Formats the output using a Go template
27+
--host string Base URL to use
28+
--insecure Skips all TLS validation
29+
--message string A message to set on cluster operation
30+
--output string Output format [text|json] (default "text")
31+
--pass string Password to use to authenticate (If empty will look for EC_PASS environment variable)
32+
--pprof Enables pprofing and saves the profile to pprof-20060102150405
33+
-q, --quiet Suppresses the configuration file used for the run, if any
34+
--timeout duration Timeout to use on all HTTP calls (default 30s)
35+
--trace Enables tracing saves the trace to trace-20060102150405
36+
--user string Username to use to authenticate (If empty will look for EC_USER environment variable)
37+
--verbose Enable verbose mode
38+
```
39+
40+
### SEE ALSO
41+
42+
* [ecctl platform runner](ecctl_platform_runner.md) - Manages platform runners (Available for ECE only)
43+

pkg/platform/runner/resync.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package runner
19+
20+
import (
21+
"github.com/elastic/cloud-sdk-go/pkg/api"
22+
"github.com/elastic/cloud-sdk-go/pkg/client/platform_infrastructure"
23+
"github.com/elastic/cloud-sdk-go/pkg/models"
24+
"github.com/hashicorp/go-multierror"
25+
26+
"github.com/elastic/ecctl/pkg/util"
27+
)
28+
29+
// ResyncParams is consumed by Resync
30+
type ResyncParams struct {
31+
Params
32+
ID string
33+
}
34+
35+
// Validate ensures the parameters are usable by the consuming function.
36+
func (params ResyncParams) Validate() error {
37+
var merr = new(multierror.Error)
38+
39+
if params.ID == "" {
40+
merr = multierror.Append(merr, util.ErrIDCannotBeEmpty)
41+
}
42+
43+
merr = multierror.Append(merr, params.Params.Validate())
44+
45+
return merr.ErrorOrNil()
46+
}
47+
48+
// Resync forces indexer to immediately resynchronize the search index
49+
// and cache for a given runner.
50+
func Resync(params ResyncParams) error {
51+
if err := params.Validate(); err != nil {
52+
return err
53+
}
54+
55+
return util.ReturnErrOnly(
56+
params.API.V1API.PlatformInfrastructure.ResyncRunner(
57+
platform_infrastructure.NewResyncRunnerParams().
58+
WithRunnerID(params.ID),
59+
params.API.AuthWriter,
60+
),
61+
)
62+
}
63+
64+
// ResyncAll asynchronously resynchronizes the search index for all runners.
65+
func ResyncAll(params Params) (*models.ModelVersionIndexSynchronizationResults, error) {
66+
if err := params.Validate(); err != nil {
67+
return nil, err
68+
}
69+
70+
res, err := params.API.V1API.PlatformInfrastructure.ResyncRunners(
71+
platform_infrastructure.NewResyncRunnersParams(),
72+
params.API.AuthWriter,
73+
)
74+
if err != nil {
75+
return nil, api.UnwrapError(err)
76+
}
77+
78+
return res.Payload, nil
79+
}

pkg/platform/runner/resync_test.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package runner
19+
20+
import (
21+
"errors"
22+
"net/http"
23+
"net/url"
24+
"reflect"
25+
"testing"
26+
27+
"github.com/elastic/cloud-sdk-go/pkg/api"
28+
"github.com/elastic/cloud-sdk-go/pkg/api/mock"
29+
"github.com/elastic/cloud-sdk-go/pkg/models"
30+
multierror "github.com/hashicorp/go-multierror"
31+
)
32+
33+
func TestResync(t *testing.T) {
34+
type args struct {
35+
params ResyncParams
36+
}
37+
tests := []struct {
38+
name string
39+
args args
40+
wantErr error
41+
}{
42+
{
43+
name: "Fails due to parameter validation (Cluster ID)",
44+
args: args{},
45+
wantErr: &multierror.Error{Errors: []error{
46+
errors.New("id field cannot be empty"),
47+
errors.New("api reference is required for command"),
48+
}},
49+
},
50+
{
51+
name: "Fails due to parameter validation (API)",
52+
args: args{params: ResyncParams{
53+
ID: "d324608c97154bdba2dff97511d40368",
54+
}},
55+
wantErr: &multierror.Error{Errors: []error{
56+
errors.New("api reference is required for command"),
57+
}},
58+
},
59+
{
60+
name: "Fails due to unknown API response",
61+
args: args{params: ResyncParams{
62+
ID: "2c221bd86b7f48959a59ee3128d5c5e8",
63+
Params: Params{
64+
API: api.NewMock(mock.Response{Response: http.Response{
65+
StatusCode: http.StatusForbidden,
66+
Body: mock.NewStringBody(`{"error": "some forbidden error"}`),
67+
}}),
68+
},
69+
}},
70+
wantErr: errors.New(`{"error": "some forbidden error"}`),
71+
},
72+
{
73+
name: "Fails due to API error",
74+
args: args{params: ResyncParams{
75+
ID: "2c221bd86b7f48959a59ee3128d5c5e8",
76+
Params: Params{
77+
API: api.NewMock(mock.Response{
78+
Error: errors.New("error with API"),
79+
}),
80+
},
81+
}},
82+
wantErr: &url.Error{
83+
Op: "Post",
84+
URL: "https://mock-host/mock-path/platform/infrastructure/runners/2c221bd86b7f48959a59ee3128d5c5e8/_resync",
85+
Err: errors.New("error with API"),
86+
},
87+
},
88+
{
89+
name: "Succeeds to resynchronize Kibana instance without errors",
90+
args: args{params: ResyncParams{
91+
ID: "d324608c97154bdba2dff97511d40368",
92+
Params: Params{
93+
API: api.NewMock(mock.Response{Response: http.Response{
94+
StatusCode: http.StatusOK,
95+
Body: mock.NewStringBody(`{}`),
96+
}}),
97+
},
98+
}},
99+
},
100+
}
101+
102+
for _, tt := range tests {
103+
t.Run(tt.name, func(t *testing.T) {
104+
if err := Resync(tt.args.params); !reflect.DeepEqual(err, tt.wantErr) {
105+
t.Errorf("Resync() error = %v, wantErr %v", err, tt.wantErr)
106+
}
107+
})
108+
}
109+
}
110+
111+
func TestResyncAll(t *testing.T) {
112+
type args struct {
113+
params Params
114+
}
115+
tests := []struct {
116+
name string
117+
args args
118+
wantErr error
119+
want *models.ModelVersionIndexSynchronizationResults
120+
}{
121+
{
122+
name: "Fails due to parameter validation (API)",
123+
args: args{params: Params{}},
124+
wantErr: errors.New("api reference is required for command"),
125+
},
126+
{
127+
name: "Fails due to unknown API response",
128+
args: args{params: Params{
129+
API: api.NewMock(mock.Response{Response: http.Response{
130+
StatusCode: http.StatusForbidden,
131+
Body: mock.NewStringBody(`{"error": "some forbidden error"}`),
132+
}}),
133+
}},
134+
wantErr: errors.New(`{"error": "some forbidden error"}`),
135+
},
136+
{
137+
name: "Fails due to API error",
138+
args: args{params: Params{
139+
API: api.NewMock(mock.Response{
140+
Error: errors.New("error with API"),
141+
}),
142+
}},
143+
wantErr: &url.Error{
144+
Op: "Post",
145+
URL: "https://mock-host/mock-path/platform/infrastructure/runners/_resync?skip_matching_version=true",
146+
Err: errors.New("error with API"),
147+
},
148+
},
149+
{
150+
name: "Succeeds to re-synchronize all Kibana instances without errors",
151+
args: args{params: Params{
152+
API: api.NewMock(mock.Response{Response: http.Response{
153+
StatusCode: http.StatusAccepted,
154+
Body: mock.NewStringBody(`{}`),
155+
}}),
156+
}},
157+
want: &models.ModelVersionIndexSynchronizationResults{},
158+
},
159+
}
160+
161+
for _, tt := range tests {
162+
t.Run(tt.name, func(t *testing.T) {
163+
got, err := ResyncAll(tt.args.params)
164+
if !reflect.DeepEqual(tt.wantErr, err) {
165+
t.Errorf("ResyncAll() error = %v, wantErr %v", err, tt.wantErr)
166+
return
167+
}
168+
if !reflect.DeepEqual(got, tt.want) {
169+
t.Errorf("ResyncAll() = %v, want %v", got, tt.want)
170+
}
171+
})
172+
}
173+
}

0 commit comments

Comments
 (0)