Skip to content

Commit 4d71411

Browse files
authored
refactor: add upload all command for resources (#713)
* refactor: add upload all command for resources * fix: fix lint issues
1 parent a6ff29b commit 4d71411

File tree

4 files changed

+233
-28
lines changed

4 files changed

+233
-28
lines changed

client/cmd/job/replace_all.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func NewReplaceAllCommand() *cobra.Command {
4545
Short: "Replace all current optimus project to server",
4646
Long: heredoc.Doc(`Apply local changes to destination server which includes creating/updating/deleting
4747
jobs`),
48-
Example: "optimus job replace-all [--ignore-resources|--ignore-jobs]",
48+
Example: "optimus job replace-all [--verbose]",
4949
Annotations: map[string]string{
5050
"group:core": "true",
5151
},
@@ -97,7 +97,7 @@ func (r *replaceAllCommand) replaceAll(selectedNamespaces []*config.Namespace) e
9797
}
9898

9999
func (r *replaceAllCommand) replaceAllJobs(conn *connectivity.Connectivity, selectedNamespaces []*config.Namespace) error {
100-
namespaceNames := []string{}
100+
var namespaceNames []string
101101
for _, namespace := range selectedNamespaces {
102102
namespaceNames = append(namespaceNames, namespace.Name)
103103
}

client/cmd/resource/create.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ type createCommand struct {
2020
clientConfig *config.ClientConfig
2121

2222
namespaceSurvey *survey.NamespaceSurvey
23+
configFilePath string
2324
}
2425

2526
// NewCreateCommand initializes resource create command
26-
func NewCreateCommand(clientConfig *config.ClientConfig) *cobra.Command {
27+
func NewCreateCommand() *cobra.Command {
2728
l := logger.NewClientLogger()
2829
create := &createCommand{
29-
clientConfig: clientConfig,
3030
logger: l,
3131
namespaceSurvey: survey.NewNamespaceSurvey(l),
3232
}
@@ -36,10 +36,21 @@ func NewCreateCommand(clientConfig *config.ClientConfig) *cobra.Command {
3636
Short: "Create a new resource",
3737
Example: "optimus resource create",
3838
RunE: create.RunE,
39+
PreRunE: create.PreRunE,
3940
}
41+
cmd.Flags().StringVarP(&create.configFilePath, "config", "c", create.configFilePath, "File path for client configuration")
4042
return cmd
4143
}
4244

45+
func (c *createCommand) PreRunE(_ *cobra.Command, _ []string) error {
46+
var err error
47+
c.clientConfig, err = config.LoadClientConfig(c.configFilePath)
48+
if err != nil {
49+
return err
50+
}
51+
return nil
52+
}
53+
4354
func (c createCommand) RunE(_ *cobra.Command, _ []string) error {
4455
selectedNamespace, err := c.namespaceSurvey.AskToSelectNamespace(c.clientConfig)
4556
if err != nil {

client/cmd/resource/resource.go

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,19 @@ package resource
22

33
import (
44
"github.com/spf13/cobra"
5-
6-
"github.com/odpf/optimus/config"
75
)
86

9-
type resourceCommand struct {
10-
configFilePath string
11-
clientConfig *config.ClientConfig
12-
}
13-
147
// NewResourceCommand initializes command for resource
158
func NewResourceCommand() *cobra.Command {
16-
resource := &resourceCommand{
17-
clientConfig: &config.ClientConfig{},
18-
}
19-
209
cmd := &cobra.Command{
2110
Use: "resource",
2211
Short: "Interact with data resource",
2312
Annotations: map[string]string{
2413
"group:core": "true",
2514
},
26-
PersistentPreRunE: resource.PersistentPreRunE,
2715
}
28-
cmd.PersistentFlags().StringVarP(&resource.configFilePath, "config", "c", resource.configFilePath, "File path for client configuration")
2916

30-
cmd.AddCommand(NewCreateCommand(resource.clientConfig))
17+
cmd.AddCommand(NewCreateCommand())
18+
cmd.AddCommand(NewUploadAllCommand())
3119
return cmd
3220
}
33-
34-
func (r *resourceCommand) PersistentPreRunE(_ *cobra.Command, _ []string) error {
35-
// TODO: find a way to load the config in one place
36-
c, err := config.LoadClientConfig(r.configFilePath)
37-
if err != nil {
38-
return err
39-
}
40-
*r.clientConfig = *c
41-
return nil
42-
}

client/cmd/resource/upload_all.go

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package resource
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"strings"
9+
"time"
10+
11+
"github.com/MakeNowJust/heredoc"
12+
"github.com/odpf/salt/log"
13+
"github.com/spf13/afero"
14+
"github.com/spf13/cobra"
15+
16+
"github.com/odpf/optimus/client/cmd/internal/connectivity"
17+
"github.com/odpf/optimus/client/cmd/internal/logger"
18+
"github.com/odpf/optimus/client/local/specio"
19+
"github.com/odpf/optimus/config"
20+
pb "github.com/odpf/optimus/protos/odpf/optimus/core/v1beta1"
21+
)
22+
23+
const (
24+
uploadAllTimeout = time.Minute * 30
25+
)
26+
27+
type uploadAllCommand struct {
28+
logger log.Logger
29+
clientConfig *config.ClientConfig
30+
31+
selectedNamespaceNames []string
32+
verbose bool
33+
configFilePath string
34+
}
35+
36+
// NewUploadAllCommand initializes command for uploading all resources
37+
func NewUploadAllCommand() *cobra.Command {
38+
uploadAll := &uploadAllCommand{
39+
logger: logger.NewClientLogger(),
40+
}
41+
42+
cmd := &cobra.Command{
43+
Use: "upload-all",
44+
Short: "Upload all current optimus resources to server",
45+
Long: heredoc.Doc(`Apply local changes to destination server which includes creating/updating resources`),
46+
Example: "optimus resource upload-all [--verbose]",
47+
Annotations: map[string]string{
48+
"group:core": "true",
49+
},
50+
RunE: uploadAll.RunE,
51+
PreRunE: uploadAll.PreRunE,
52+
}
53+
cmd.Flags().StringVarP(&uploadAll.configFilePath, "config", "c", uploadAll.configFilePath, "File path for client configuration")
54+
cmd.Flags().StringSliceVarP(&uploadAll.selectedNamespaceNames, "namespace-names", "N", nil, "Selected namespaces of optimus project")
55+
cmd.Flags().BoolVarP(&uploadAll.verbose, "verbose", "v", false, "Print details related to upload-all stages")
56+
return cmd
57+
}
58+
59+
func (u *uploadAllCommand) PreRunE(_ *cobra.Command, _ []string) error {
60+
var err error
61+
u.clientConfig, err = config.LoadClientConfig(u.configFilePath)
62+
if err != nil {
63+
return err
64+
}
65+
return nil
66+
}
67+
68+
func (u *uploadAllCommand) RunE(_ *cobra.Command, _ []string) error {
69+
u.logger.Info("> Validating namespaces")
70+
selectedNamespaces, err := u.clientConfig.GetSelectedNamespaces(u.selectedNamespaceNames...)
71+
if err != nil {
72+
return err
73+
}
74+
if len(selectedNamespaces) == 0 {
75+
selectedNamespaces = u.clientConfig.Namespaces
76+
}
77+
u.logger.Info("namespace validation finished!\n")
78+
79+
return u.uploadAll(selectedNamespaces)
80+
}
81+
82+
func (u *uploadAllCommand) uploadAll(selectedNamespaces []*config.Namespace) error {
83+
conn, err := connectivity.NewConnectivity(u.clientConfig.Host, uploadAllTimeout)
84+
if err != nil {
85+
return err
86+
}
87+
defer conn.Close()
88+
89+
if err := u.uploadAllResources(conn, selectedNamespaces); err != nil {
90+
return err
91+
}
92+
u.logger.Info("finished uploading resource specifications to server!\n")
93+
94+
return nil
95+
}
96+
97+
func (u *uploadAllCommand) uploadAllResources(conn *connectivity.Connectivity, selectedNamespaces []*config.Namespace) error {
98+
var namespaceNames []string
99+
for _, namespace := range selectedNamespaces {
100+
namespaceNames = append(namespaceNames, namespace.Name)
101+
}
102+
103+
u.logger.Info("> Uploading all resources for namespaces [%s]", strings.Join(namespaceNames, ","))
104+
105+
stream, err := u.getResourceStreamClient(conn)
106+
if err != nil {
107+
return err
108+
}
109+
110+
var totalSpecsCount int
111+
for _, namespace := range selectedNamespaces {
112+
progressFn := func(totalCount int) {
113+
totalSpecsCount += totalCount
114+
}
115+
if err := u.sendNamespaceResourceRequest(stream, namespace, progressFn); err != nil {
116+
return err
117+
}
118+
}
119+
120+
if err := stream.CloseSend(); err != nil {
121+
return err
122+
}
123+
124+
if totalSpecsCount == 0 {
125+
u.logger.Warn("no resource specs are found from all the namespaces")
126+
return nil
127+
}
128+
129+
return u.processResourceDeploymentResponse(stream)
130+
}
131+
132+
func (u *uploadAllCommand) sendNamespaceResourceRequest(stream pb.ResourceService_DeployResourceSpecificationClient,
133+
namespace *config.Namespace, progressFn func(totalCount int),
134+
) error {
135+
datastoreSpecFs := CreateDataStoreSpecFs(namespace)
136+
for storeName, repoFS := range datastoreSpecFs {
137+
u.logger.Info("> Deploying %s resources for namespace [%s]", storeName, namespace.Name)
138+
request, err := u.getResourceDeploymentRequest(namespace.Name, storeName, repoFS)
139+
if err != nil {
140+
return fmt.Errorf("error getting resource specs for namespace [%s]: %w", namespace.Name, err)
141+
}
142+
143+
if err = stream.Send(request); err != nil {
144+
return fmt.Errorf("resource upload for namespace [%s] failed: %w", namespace.Name, err)
145+
}
146+
progressFn(len(request.GetResources()))
147+
}
148+
return nil
149+
}
150+
151+
func (u *uploadAllCommand) getResourceDeploymentRequest(namespaceName, storeName string,
152+
repoFS afero.Fs) (*pb.DeployResourceSpecificationRequest, error) {
153+
resourceSpecReadWriter, err := specio.NewResourceSpecReadWriter(repoFS)
154+
if err != nil {
155+
return nil, err
156+
}
157+
158+
resourceSpecs, err := resourceSpecReadWriter.ReadAll(".")
159+
if err != nil {
160+
return nil, err
161+
}
162+
163+
resourceSpecsProto := make([]*pb.ResourceSpecification, len(resourceSpecs))
164+
for i, resourceSpec := range resourceSpecs {
165+
resourceSpecProto, err := resourceSpec.ToProto()
166+
if err != nil {
167+
return nil, err
168+
}
169+
resourceSpecsProto[i] = resourceSpecProto
170+
}
171+
172+
return &pb.DeployResourceSpecificationRequest{
173+
Resources: resourceSpecsProto,
174+
ProjectName: u.clientConfig.Project.Name,
175+
DatastoreName: storeName,
176+
NamespaceName: namespaceName,
177+
}, nil
178+
}
179+
180+
func (u *uploadAllCommand) getResourceStreamClient(conn *connectivity.Connectivity) (pb.ResourceService_DeployResourceSpecificationClient, error) {
181+
client := pb.NewResourceServiceClient(conn.GetConnection())
182+
// TODO: create a new api for upload-all and remove deploy
183+
stream, err := client.DeployResourceSpecification(conn.GetContext())
184+
if err != nil {
185+
if errors.Is(err, context.DeadlineExceeded) {
186+
u.logger.Error("Deployment of resources took too long, timing out")
187+
}
188+
return nil, fmt.Errorf("deployement failed: %w", err)
189+
}
190+
return stream, nil
191+
}
192+
193+
func (u *uploadAllCommand) processResourceDeploymentResponse(stream pb.ResourceService_DeployResourceSpecificationClient) error {
194+
u.logger.Info("> Receiving responses:")
195+
196+
for {
197+
resp, err := stream.Recv()
198+
if err != nil {
199+
if errors.Is(err, io.EOF) {
200+
break
201+
}
202+
return err
203+
}
204+
205+
if logStatus := resp.GetLogStatus(); logStatus != nil {
206+
if u.verbose {
207+
logger.PrintLogStatusVerbose(u.logger, logStatus)
208+
} else {
209+
logger.PrintLogStatus(u.logger, logStatus)
210+
}
211+
continue
212+
}
213+
}
214+
215+
return nil
216+
}

0 commit comments

Comments
 (0)