Skip to content

Commit 6372103

Browse files
authored
cmd: Add deployment resync and --all flag (#130)
Implements a new deployment resync command which resynchronises a given deployment or all.
1 parent afbf5f3 commit 6372103

File tree

5 files changed

+359
-0
lines changed

5 files changed

+359
-0
lines changed

cmd/deployment/resync.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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 cmddeployment
19+
20+
import (
21+
"fmt"
22+
23+
"github.com/spf13/cobra"
24+
25+
cmdutil "github.com/elastic/ecctl/cmd/util"
26+
"github.com/elastic/ecctl/pkg/deployment"
27+
"github.com/elastic/ecctl/pkg/ecctl"
28+
)
29+
30+
var resyncDeploymentCmd = &cobra.Command{
31+
Use: "resync {<deployment id> | --all}",
32+
Short: "Resynchronizes the search index and cache for the selected deployment 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 deployments")
39+
res, err := deployment.ResyncAll(deployment.ResyncAllParams{
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 deployment: %s\n", args[0])
50+
return deployment.Resync(deployment.ResyncParams{
51+
API: ecctl.Get().API,
52+
ID: args[0],
53+
})
54+
},
55+
}
56+
57+
func init() {
58+
Command.AddCommand(resyncDeploymentCmd)
59+
resyncDeploymentCmd.Flags().Bool("all", false, "Resynchronizes the search index for all deployments")
60+
}

docs/ecctl_deployment.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ ecctl deployment [flags]
4949
* [ecctl deployment plan](ecctl_deployment_plan.md) - Manages deployment plans
5050
* [ecctl deployment resource](ecctl_deployment_resource.md) - Manages deployment resources
5151
* [ecctl deployment restore](ecctl_deployment_restore.md) - Restores a previously shut down deployment and all of its associated sub-resources
52+
* [ecctl deployment resync](ecctl_deployment_resync.md) - Resynchronizes the search index and cache for the selected deployment or all
5253
* [ecctl deployment search](ecctl_deployment_search.md) - Performs advanced deployment search using the Elasticsearch Query DSL
5354
* [ecctl deployment show](ecctl_deployment_show.md) - Shows the specified deployment resources
5455
* [ecctl deployment shutdown](ecctl_deployment_shutdown.md) - Shuts down a deployment and all of its associated sub-resources

docs/ecctl_deployment_resync.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
## ecctl deployment resync
2+
3+
Resynchronizes the search index and cache for the selected deployment or all
4+
5+
### Synopsis
6+
7+
Resynchronizes the search index and cache for the selected deployment or all
8+
9+
```
10+
ecctl deployment resync {<deployment id> | --all} [flags]
11+
```
12+
13+
### Options
14+
15+
```
16+
--all Resynchronizes the search index for all deployments
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 deployment](ecctl_deployment.md) - Manages deployments
43+

pkg/deployment/resync.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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 deployment
19+
20+
import (
21+
"github.com/elastic/cloud-sdk-go/pkg/api"
22+
"github.com/elastic/cloud-sdk-go/pkg/client/deployments"
23+
"github.com/elastic/cloud-sdk-go/pkg/models"
24+
multierror "github.com/hashicorp/go-multierror"
25+
26+
"github.com/elastic/ecctl/pkg/deployment/deputil"
27+
"github.com/elastic/ecctl/pkg/util"
28+
)
29+
30+
// ResyncParams is consumed by Resync
31+
type ResyncParams struct {
32+
*api.API
33+
ID string
34+
}
35+
36+
// Validate ensures the parameters are usable by the consuming function.
37+
func (params ResyncParams) Validate() error {
38+
var err = multierror.Append(new(multierror.Error),
39+
deputil.ValidateParams(&params),
40+
)
41+
return err.ErrorOrNil()
42+
}
43+
44+
// ResyncAllParams is consumed by ResyncAll
45+
type ResyncAllParams struct {
46+
*api.API
47+
}
48+
49+
// Validate ensures the parameters are usable by the consuming function.
50+
func (params ResyncAllParams) Validate() error {
51+
if params.API == nil {
52+
return util.ErrAPIReq
53+
}
54+
return nil
55+
}
56+
57+
// Resync forces indexer to immediately resynchronize the search index
58+
// and cache for a given deployment.
59+
func Resync(params ResyncParams) error {
60+
if err := params.Validate(); err != nil {
61+
return err
62+
}
63+
64+
return util.ReturnErrOnly(
65+
params.API.V1API.Deployments.ResyncDeployment(
66+
deployments.NewResyncDeploymentParams().
67+
WithDeploymentID(params.ID),
68+
params.API.AuthWriter,
69+
),
70+
)
71+
}
72+
73+
// ResyncAll asynchronously resynchronizes the search index for all deployments.
74+
func ResyncAll(params ResyncAllParams) (*models.IndexSynchronizationResults, error) {
75+
if err := params.Validate(); err != nil {
76+
return nil, err
77+
}
78+
79+
res, err := params.API.V1API.Deployments.ResyncDeployments(
80+
deployments.NewResyncDeploymentsParams(),
81+
params.API.AuthWriter,
82+
)
83+
if err != nil {
84+
return nil, api.UnwrapError(err)
85+
}
86+
87+
return res.Payload, nil
88+
}

pkg/deployment/resync_test.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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 deployment
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("api reference is required for command"),
47+
errors.New(`id "" is invalid`),
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+
API: api.NewMock(mock.Response{Response: http.Response{
64+
StatusCode: http.StatusForbidden,
65+
Body: mock.NewStringBody(`{"error": "some forbidden error"}`),
66+
}}),
67+
}},
68+
wantErr: errors.New(`{"error": "some forbidden error"}`),
69+
},
70+
{
71+
name: "Fails due to API error",
72+
args: args{params: ResyncParams{
73+
ID: "2c221bd86b7f48959a59ee3128d5c5e8",
74+
API: api.NewMock(mock.Response{
75+
Error: errors.New("error with API"),
76+
}),
77+
}},
78+
wantErr: &url.Error{
79+
Op: "Post",
80+
URL: "https://mock-host/mock-path/deployments/2c221bd86b7f48959a59ee3128d5c5e8/_resync",
81+
Err: errors.New("error with API"),
82+
},
83+
},
84+
{
85+
name: "Succeeds to resynchronize a deployment without errors",
86+
args: args{params: ResyncParams{
87+
ID: "d324608c97154bdba2dff97511d40368",
88+
API: api.NewMock(mock.Response{Response: http.Response{
89+
StatusCode: http.StatusOK,
90+
Body: mock.NewStringBody(`{}`),
91+
}}),
92+
}},
93+
},
94+
}
95+
96+
for _, tt := range tests {
97+
t.Run(tt.name, func(t *testing.T) {
98+
if err := Resync(tt.args.params); !reflect.DeepEqual(err, tt.wantErr) {
99+
t.Errorf("Resync() error = %v, wantErr %v", err, tt.wantErr)
100+
}
101+
})
102+
}
103+
}
104+
105+
func TestResyncAll(t *testing.T) {
106+
type args struct {
107+
params ResyncAllParams
108+
}
109+
tests := []struct {
110+
name string
111+
args args
112+
wantErr error
113+
want *models.IndexSynchronizationResults
114+
}{
115+
{
116+
name: "Fails due to parameter validation (API)",
117+
args: args{params: ResyncAllParams{}},
118+
wantErr: errors.New("api reference is required for command"),
119+
},
120+
{
121+
name: "Fails due to unknown API response",
122+
args: args{params: ResyncAllParams{
123+
API: api.NewMock(mock.Response{Response: http.Response{
124+
StatusCode: http.StatusForbidden,
125+
Body: mock.NewStringBody(`{"error": "some forbidden error"}`),
126+
}}),
127+
}},
128+
wantErr: errors.New(`{"error": "some forbidden error"}`),
129+
},
130+
{
131+
name: "Fails due to API error",
132+
args: args{params: ResyncAllParams{
133+
API: api.NewMock(mock.Response{
134+
Error: errors.New("error with API"),
135+
}),
136+
}},
137+
wantErr: &url.Error{
138+
Op: "Post",
139+
URL: "https://mock-host/mock-path/deployments/_resync",
140+
Err: errors.New("error with API"),
141+
},
142+
},
143+
{
144+
name: "Succeeds to resynchronize all deployments without errors",
145+
args: args{params: ResyncAllParams{
146+
API: api.NewMock(mock.Response{Response: http.Response{
147+
StatusCode: http.StatusOK,
148+
Body: mock.NewStringBody(`{}`),
149+
}}),
150+
}},
151+
want: &models.IndexSynchronizationResults{},
152+
},
153+
}
154+
155+
for _, tt := range tests {
156+
t.Run(tt.name, func(t *testing.T) {
157+
got, err := ResyncAll(tt.args.params)
158+
if !reflect.DeepEqual(tt.wantErr, err) {
159+
t.Errorf("ResyncAll() error = %v, wantErr %v", err, tt.wantErr)
160+
return
161+
}
162+
if !reflect.DeepEqual(got, tt.want) {
163+
t.Errorf("ResyncAll() = %v, want %v", got, tt.want)
164+
}
165+
})
166+
}
167+
}

0 commit comments

Comments
 (0)