From e00a080812ec070e402ff330ebe2ae4769c1fd53 Mon Sep 17 00:00:00 2001 From: boppanasusanth <110461367+boppanasusanth@users.noreply.github.com> Date: Fri, 17 Feb 2023 19:30:36 +0530 Subject: [PATCH 1/3] parallel execution for getting status of services in an environment (#171) * parallel execution for getting status of services in an environment * added --all flag to odin list env help * error should be returned as the last argument * separated table config * minor change * minor change --- app/app.go | 2 +- internal/backend/env.go | 10 +++++- internal/command/commands/env.go | 52 +++++++++++++++++++++------- internal/command/commands/service.go | 18 +++++----- pkg/table/table.go | 50 ++++++++++++++++++++++---- pkg/utils/utils.go | 17 +++++++++ 6 files changed, 119 insertions(+), 30 deletions(-) diff --git a/app/app.go b/app/app.go index 9f7dbb4b..2c6267c6 100644 --- a/app/app.go +++ b/app/app.go @@ -8,5 +8,5 @@ type application struct { // App (Application) interface var App application = application{ Name: "odin", - Version: "1.2.3", + Version: "1.2.4", } diff --git a/internal/backend/env.go b/internal/backend/env.go index 90ed073d..8bdf90dc 100644 --- a/internal/backend/env.go +++ b/internal/backend/env.go @@ -2,9 +2,11 @@ package backend import ( "encoding/json" + "errors" "path" envResp "github.com/dream11/odin/api/environment" + "github.com/dream11/odin/pkg/request" ) // Env entity @@ -141,11 +143,17 @@ func (e *Env) EnvServiceStatus(env, serviceName string) (envResp.EnvServiceStatu client := newApiClient() response := client.actionWithRetry(path.Join(envEntity, env)+"/services/"+serviceName+"/status", "GET", nil) - response.Process(true) // process response and exit if error var envResponse envResp.EnvServiceStatusResponse err := json.Unmarshal(response.Body, &envResponse) + if response.Error != nil { + return envResponse.ServiceResponse, response.Error + } + if request.MatchStatusCode(response.StatusCode, 400) || request.MatchStatusCode(response.StatusCode, 500) { + return envResponse.ServiceResponse, errors.New(string(response.Body)) + } + return envResponse.ServiceResponse, err } diff --git a/internal/command/commands/env.go b/internal/command/commands/env.go index 1b60f206..adf79801 100644 --- a/internal/command/commands/env.go +++ b/internal/command/commands/env.go @@ -114,27 +114,41 @@ func (e *Env) Run(args []string) int { } else { e.Logger.Info(fmt.Sprintf("Fetching status for environment: %s", *name)) - envStatus, err := envClient.EnvStatus(*name) + envDetail, err := envClient.DescribeEnv(*name, *service, *component) if err != nil { e.Logger.Error(err.Error()) return 1 } - e.Logger.Output(fmt.Sprintf("Environment Status: %s\n", envStatus.Status)) + e.Logger.Output(fmt.Sprintf("Environment Status: %s\n", envDetail.State)) tableHeaders := []string{"Name", "Version", "Status", "Last deployed"} - var tableData [][]interface{} + var tableData []interface{} e.Logger.Output("Services:") - for _, serviceStatus := range envStatus.ServiceStatus { - relativeDeployedSinceTime := datetime.DateTimeFromNow(serviceStatus.LastDeployedAt) - tableData = append(tableData, []interface{}{ - serviceStatus.Name, - serviceStatus.Version, - serviceStatus.Status, - relativeDeployedSinceTime, - }) + colWidth := utils.GetColumnWidth(envDetail.Services) + table.PrintHeader(tableHeaders, colWidth) + + c := make(chan serviceStatus) + for _, service := range envDetail.Services { + serviceName := service.Name + go e.GetServiceStatus(*name, serviceName, c) } - table.Write(tableHeaders, tableData) + for range envDetail.Services { + result := <-c + if result.err != nil { + e.Logger.Error(fmt.Sprintf("Error fetching status of service %s: %s", result.serviceName, result.err.Error())) + } else { + relativeDeployedSinceTime := datetime.DateTimeFromNow(result.response.LastDeployedAt) + tableData = []interface{}{ + result.serviceName, + result.response.Version, + result.response.Status, + relativeDeployedSinceTime, + } + table.AppendRow(tableData, colWidth) + } + } + close(c) } return 0 @@ -327,7 +341,7 @@ func (e *Env) Run(args []string) int { var updationData map[string]interface{} if isFilePresent { - err, parsedConfig := parseFile(*filePath) + parsedConfig, err := parseFile(*filePath) if err != nil { e.Logger.Error("Error while parsing service file " + *filePath + " : " + err.Error()) return 1 @@ -397,6 +411,7 @@ func (e *Env) Help() string { {Flag: "--team", Description: "name of team"}, {Flag: "--env-type", Description: "env type of the environment"}, {Flag: "--account", Description: "cloud provider account name"}, + {Flag: "--all", Description: "list all environments"}, }) } @@ -479,3 +494,14 @@ func (e *Env) Synopsis() string { } return defaultHelper() } + +func (e *Env) GetServiceStatus(name string, serviceName string, c chan serviceStatus) { + envServiceStatus, err := envClient.EnvServiceStatus(name, serviceName) + c <- serviceStatus{serviceName, envServiceStatus, err} +} + +type serviceStatus struct { + serviceName string + response environment.EnvServiceStatus + err error +} diff --git a/internal/command/commands/service.go b/internal/command/commands/service.go index 6778c2c0..3262cbcb 100644 --- a/internal/command/commands/service.go +++ b/internal/command/commands/service.go @@ -261,7 +261,7 @@ func (s *Service) Run(args []string) int { emptyUnreleasedParameters := emptyParameters(map[string]string{"--env": *envName, "--file": *filePath}) if len(emptyUnreleasedParameters) == 0 { - err, parsedConfig := parseFile(*filePath) + parsedConfig, err := parseFile(*filePath) if err != nil { s.Logger.Error("Error while parsing service file " + *filePath + " : " + err.Error()) return 1 @@ -394,7 +394,7 @@ func (s *Service) validateDeployService(envName *string, serviceName string, ser var parsedProvisioningConfig interface{} if len(*provisioningConfigFile) > 0 { - err, parsedConfig := parseFile(*provisioningConfigFile) + parsedConfig, err := parseFile(*provisioningConfigFile) if err != nil { s.Logger.Error("Error while parsing provisioning file " + *provisioningConfigFile + " : " + err.Error()) return nil, 1, true @@ -430,31 +430,31 @@ func (s *Service) validateDeployService(envName *string, serviceName string, ser return parsedProvisioningConfig, 0, false } -func parseFile(filePath string) (error, interface{}) { +func parseFile(filePath string) (interface{}, error) { if len(filePath) != 0 { var parsedDefinition interface{} fileDefinition, err := file.Read(filePath) if err != nil { - return errors.New("file does not exist"), parsedDefinition + return parsedDefinition, errors.New("file does not exist") } if strings.Contains(filePath, ".yaml") || strings.Contains(filePath, ".yml") { err = yaml.Unmarshal(fileDefinition, &parsedDefinition) if err != nil { - return errors.New("Unable to parse YAML. " + err.Error()), parsedDefinition + return parsedDefinition, errors.New("Unable to parse YAML. " + err.Error()) } } else if strings.Contains(filePath, ".json") { err = json.Unmarshal(fileDefinition, &parsedDefinition) if err != nil { - return errors.New("Unable to parse JSON. " + err.Error()), parsedDefinition + return parsedDefinition, errors.New("Unable to parse JSON. " + err.Error()) } } else { - return errors.New("unrecognized file format"), parsedDefinition + return parsedDefinition, errors.New("unrecognized file format") } - return nil, parsedDefinition + return parsedDefinition, nil } - return nil, errors.New("filepath cannot be empty") + return errors.New("filepath cannot be empty"), nil } // Help : returns an explanatory string diff --git a/pkg/table/table.go b/pkg/table/table.go index be8b3a66..4caaee62 100644 --- a/pkg/table/table.go +++ b/pkg/table/table.go @@ -7,18 +7,22 @@ import ( "github.com/olekukonko/tablewriter" ) -// Write : write provided input as tabular format -func Write(headers []string, data [][]interface{}) { - - table := tablewriter.NewWriter(os.Stdout) - - // table properties +func setDefaultConfig(table *tablewriter.Table) { table.SetRowLine(false) table.SetColumnSeparator("|") table.SetAlignment(tablewriter.ALIGN_LEFT) table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) table.SetBorders(tablewriter.Border{Left: false, Top: false, Right: false, Bottom: false}) table.SetAutoWrapText(false) +} + +// Write : write provided input as tabular format +func Write(headers []string, data [][]interface{}) { + + table := tablewriter.NewWriter(os.Stdout) + + // table properties + setDefaultConfig(table) var allHeaderColors []tablewriter.Colors for i := 0; i < len(headers); i++ { allHeaderColors = append(allHeaderColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlueColor}) @@ -37,6 +41,40 @@ func Write(headers []string, data [][]interface{}) { table.Render() } +func AppendRow(row []interface{}, colWidth []int) { + table := tablewriter.NewWriter(os.Stdout) + + setDefaultConfig(table) + + for index, width := range colWidth { + table.SetColMinWidth(index, width) + } + + s := make([]string, len(row)) + for i, v := range row { + s[i] = fmt.Sprint(v) + } + table.Append(s) + table.Render() +} + +func PrintHeader(headers []string, colWidth []int) { + table := tablewriter.NewWriter(os.Stdout) + + setDefaultConfig(table) + var allHeaderColors []tablewriter.Colors + for i := 0; i < len(headers); i++ { + allHeaderColors = append(allHeaderColors, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlueColor}) + } + for index, width := range colWidth { + table.SetColMinWidth(index, width) + } + + table.SetHeader(headers) + table.SetHeaderColor(allHeaderColors...) + table.Render() +} + /* Usage - diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index fd4080be..c8ec13be 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -9,6 +9,7 @@ import ( "regexp" "strings" + "github.com/dream11/odin/api/service" "github.com/dream11/odin/app" "github.com/dream11/odin/internal/config" "github.com/dream11/odin/pkg/file" @@ -81,3 +82,19 @@ func FetchKey(keyName string) string { f := reflect.Indirect(r).FieldByName(keyName) return f.String() } + +func maxWidth(a, b int) int { + if a > b { + return a + } + return b +} + +func GetColumnWidth(services []service.Service) []int { + a := []int{4, 7, 15} + for _, service := range services { + a[0] = maxWidth(a[0], len(service.Name)) + a[1] = maxWidth(a[1], len(service.Version)) + } + return a +} From e0e2bb5a06b3e0b5344bc2a4bbebf32222b4534e Mon Sep 17 00:00:00 2001 From: boppanasusanth Date: Wed, 1 Mar 2023 11:49:15 +0530 Subject: [PATCH 2/3] remove match status code func --- internal/backend/env.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/backend/env.go b/internal/backend/env.go index 8bdf90dc..3a06fe48 100644 --- a/internal/backend/env.go +++ b/internal/backend/env.go @@ -6,7 +6,6 @@ import ( "path" envResp "github.com/dream11/odin/api/environment" - "github.com/dream11/odin/pkg/request" ) // Env entity @@ -150,7 +149,7 @@ func (e *Env) EnvServiceStatus(env, serviceName string) (envResp.EnvServiceStatu if response.Error != nil { return envResponse.ServiceResponse, response.Error } - if request.MatchStatusCode(response.StatusCode, 400) || request.MatchStatusCode(response.StatusCode, 500) { + if response.StatusCode >= 400 { return envResponse.ServiceResponse, errors.New(string(response.Body)) } From 5407de77faecf7bebedc2c25f301014ecd4e3de9 Mon Sep 17 00:00:00 2001 From: Yogesh Badke <35845927+yogesh-badke@users.noreply.github.com> Date: Fri, 10 Mar 2023 03:18:04 +0530 Subject: [PATCH 3/3] Operation (#177) * added list and describe operation * added list, describe operation and operate component commands * added list, describe operation and operate component commands * linting fixes * blocked service redploy * removed redeploy block from cli --------- Co-authored-by: rahulrayal --- api/component/component.go | 60 ++---------- api/componenttype/componenttype.go | 58 ++++++++++++ api/operation/operation.go | 11 +++ api/service/service.go | 26 +++--- app/app.go | 2 +- internal/backend/component.go | 16 ++++ internal/backend/componentType.go | 10 +- internal/backend/operation.go | 19 ++++ internal/command/command_catalog.go | 9 ++ internal/command/commands/command.go | 1 + internal/command/commands/component.go | 90 ++++++++++++++++++ internal/command/commands/operation.go | 122 +++++++++++++++++++++++++ 12 files changed, 354 insertions(+), 70 deletions(-) create mode 100644 api/componenttype/componenttype.go create mode 100644 api/operation/operation.go create mode 100644 internal/backend/component.go create mode 100644 internal/backend/operation.go create mode 100644 internal/command/commands/component.go create mode 100644 internal/command/commands/operation.go diff --git a/api/component/component.go b/api/component/component.go index 359ddc5b..1b635531 100644 --- a/api/component/component.go +++ b/api/component/component.go @@ -1,58 +1,16 @@ package component -// Component interface -type Component struct { - Name string `yaml:"name,omitempty" json:"name,omitempty"` - Type string `yaml:"type,omitempty" json:"type,omitempty"` - Version string `yaml:"version,omitempty" json:"version,omitempty"` - Config interface{} `yaml:"config,omitempty" json:"config,omitempty"` - Deployment interface{} `yaml:"deployment_config,omitempty" json:"deployment_config,omitempty"` - Scaling interface{} `yaml:"scaling_config,omitempty" json:"scaling_config,omitempty"` - Discovery interface{} `yaml:"discovery_config,omitempty" json:"discovery_config,omitempty"` - DeploymentPlatformMapping interface{} `yaml:"behaviour,omitempty" json:"behaviour,omitempty"` +type Operation struct { + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Values interface{} `yaml:"values,omitempty" json:"values,omitempty"` } -// Type interface -type Type struct { - Name string `yaml:"name,omitempty" json:"name,omitempty"` - Version string `yaml:"version,omitempty" json:"version,omitempty"` - TotalVersions int `yaml:"totalVersions,omitempty" json:"totalVersions,omitempty"` - DeploymentTypes []string `yaml:"deployment_types,omitempty" json:"deployment_types,omitempty"` - CreatedBy string `yaml:"createdBy,omitempty" json:"createdBy,omitempty"` - UpdatedBy string `yaml:"updatedBy,omitempty" json:"updatedBy,omitempty"` - CreatedAt string `yaml:"createdAt,omitempty" json:"createdAt,omitempty"` - UpdatedAt string `yaml:"updatedAt,omitempty" json:"updatedAt,omitempty"` - Config interface{} `yaml:"config,omitempty" json:"config,omitempty"` - Deployment interface{} `yaml:"deployment_config,omitempty" json:"deployment_config,omitempty"` - Scaling interface{} `yaml:"scaling_config,omitempty" json:"scaling_config,omitempty"` - Discovery interface{} `yaml:"discovery_config,omitempty" json:"discovery_config,omitempty"` - DeploymentPlatformMapping interface{} `yaml:"behaviour,omitempty" json:"behaviour,omitempty"` +type Data struct { + EnvName string `yaml:"env_name,omitempty" json:"env_name,omitempty"` + ServiceName string `yaml:"service_name,omitempty" json:"service_name,omitempty"` + Operations []Operation `yaml:"operations,omitempty" json:"operations,omitempty"` } -// Exposed Config interface -type ExposedConfig struct { - Config string `yaml:"config,omitempty" json:"config,omitempty"` - Mandatory bool `yaml:"mandatory,omitempty" json:"mandatory,omitempty"` - DataType string `yaml:"data_type,omitempty" json:"data_type,omitempty"` -} - -// ListTypeResponse interface -type ListTypeResponse struct { - Response []Type `yaml:"resp,omitempty" json:"resp,omitempty"` -} - -// DetailComponentTypeResponse interface -type ComponentDetailsResponse struct { - Response ComponentDetails `yaml:"resp,omitempty" json:"resp,omitempty"` -} - -// ComponentDetails interface -type ComponentDetails struct { - Details Type `yaml:"details,omitempty" json:"details,omitempty"` - ExposedConfigs []ExposedConfig `yaml:"exposed_config,omitempty" json:"exposed_config,omitempty"` -} - -// ListTypeResponse interface -type DetailComponentResponse struct { - Response Component `yaml:"resp,omitempty" json:"resp,omitempty"` +type OperateComponentRequest struct { + Data Data `yaml:"data,omitempty" json:"data,omitempty"` } diff --git a/api/componenttype/componenttype.go b/api/componenttype/componenttype.go new file mode 100644 index 00000000..f13506bd --- /dev/null +++ b/api/componenttype/componenttype.go @@ -0,0 +1,58 @@ +package componenttype + +// Component interface +type Component struct { + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Type string `yaml:"type,omitempty" json:"type,omitempty"` + Version string `yaml:"version,omitempty" json:"version,omitempty"` + Config interface{} `yaml:"config,omitempty" json:"config,omitempty"` + Deployment interface{} `yaml:"deployment_config,omitempty" json:"deployment_config,omitempty"` + Scaling interface{} `yaml:"scaling_config,omitempty" json:"scaling_config,omitempty"` + Discovery interface{} `yaml:"discovery_config,omitempty" json:"discovery_config,omitempty"` + DeploymentPlatformMapping interface{} `yaml:"behaviour,omitempty" json:"behaviour,omitempty"` +} + +// Type interface +type Type struct { + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Version string `yaml:"version,omitempty" json:"version,omitempty"` + TotalVersions int `yaml:"totalVersions,omitempty" json:"totalVersions,omitempty"` + DeploymentTypes []string `yaml:"deployment_types,omitempty" json:"deployment_types,omitempty"` + CreatedBy string `yaml:"createdBy,omitempty" json:"createdBy,omitempty"` + UpdatedBy string `yaml:"updatedBy,omitempty" json:"updatedBy,omitempty"` + CreatedAt string `yaml:"createdAt,omitempty" json:"createdAt,omitempty"` + UpdatedAt string `yaml:"updatedAt,omitempty" json:"updatedAt,omitempty"` + Config interface{} `yaml:"config,omitempty" json:"config,omitempty"` + Deployment interface{} `yaml:"deployment_config,omitempty" json:"deployment_config,omitempty"` + Scaling interface{} `yaml:"scaling_config,omitempty" json:"scaling_config,omitempty"` + Discovery interface{} `yaml:"discovery_config,omitempty" json:"discovery_config,omitempty"` + DeploymentPlatformMapping interface{} `yaml:"behaviour,omitempty" json:"behaviour,omitempty"` +} + +// Exposed Config interface +type ExposedConfig struct { + Config string `yaml:"config,omitempty" json:"config,omitempty"` + Mandatory bool `yaml:"mandatory,omitempty" json:"mandatory,omitempty"` + DataType string `yaml:"data_type,omitempty" json:"data_type,omitempty"` +} + +// ListTypeResponse interface +type ListTypeResponse struct { + Response []Type `yaml:"resp,omitempty" json:"resp,omitempty"` +} + +// DetailComponentTypeResponse interface +type ComponentDetailsResponse struct { + Response ComponentDetails `yaml:"resp,omitempty" json:"resp,omitempty"` +} + +// ComponentDetails interface +type ComponentDetails struct { + Details Type `yaml:"details,omitempty" json:"details,omitempty"` + ExposedConfigs []ExposedConfig `yaml:"exposed_config,omitempty" json:"exposed_config,omitempty"` +} + +// ListTypeResponse interface +type DetailComponentResponse struct { + Response Component `yaml:"resp,omitempty" json:"resp,omitempty"` +} diff --git a/api/operation/operation.go b/api/operation/operation.go new file mode 100644 index 00000000..ab444fc4 --- /dev/null +++ b/api/operation/operation.go @@ -0,0 +1,11 @@ +package operation + +type Operation struct { + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Description string `yaml:"description,omitempty" json:"description,omitempty"` + InputSchema interface{} `yaml:"inputSchema,omitempty" json:"inputSchema,omitempty"` +} + +type ListOperation struct { + Response []Operation `yaml:"resp,omitempty" json:"resp,omitempty"` +} diff --git a/api/service/service.go b/api/service/service.go index 2b5e1fd8..23011f23 100644 --- a/api/service/service.go +++ b/api/service/service.go @@ -1,23 +1,23 @@ package service import ( - "github.com/dream11/odin/api/component" + "github.com/dream11/odin/api/componenttype" "github.com/dream11/odin/api/label" ) type Service struct { - Name string `yaml:"name,omitempty" json:"name,omitempty"` - Version string `yaml:"version,omitempty" json:"version,omitempty"` - Team string `yaml:"team,omitempty" json:"team,omitempty"` - Description string `yaml:"description,omitempty" json:"description,omitempty"` - CreatedBy string `yaml:"createdBy,omitempty" json:"createdBy,omitempty"` - UpdatedBy string `yaml:"updatedBy,omitempty" json:"updatedBy,omitempty"` - CreatedAt string `yaml:"createdAt,omitempty" json:"createdAt,omitempty"` - UpdatedAt string `yaml:"updatedAt,omitempty" json:"updatedAt,omitempty"` - Active *bool `yaml:"isActive,omitempty" json:"isActive,omitempty"` - Tags interface{} `yaml:"tags,omitempty" json:"tags,omitempty"` - Labels []label.Label `yaml:"labels,omitempty" json:"labels,omitempty"` - Components []component.Component `yaml:"components,omitempty" json:"components,omitempty"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Version string `yaml:"version,omitempty" json:"version,omitempty"` + Team string `yaml:"team,omitempty" json:"team,omitempty"` + Description string `yaml:"description,omitempty" json:"description,omitempty"` + CreatedBy string `yaml:"createdBy,omitempty" json:"createdBy,omitempty"` + UpdatedBy string `yaml:"updatedBy,omitempty" json:"updatedBy,omitempty"` + CreatedAt string `yaml:"createdAt,omitempty" json:"createdAt,omitempty"` + UpdatedAt string `yaml:"updatedAt,omitempty" json:"updatedAt,omitempty"` + Active *bool `yaml:"isActive,omitempty" json:"isActive,omitempty"` + Tags interface{} `yaml:"tags,omitempty" json:"tags,omitempty"` + Labels []label.Label `yaml:"labels,omitempty" json:"labels,omitempty"` + Components []componenttype.Component `yaml:"components,omitempty" json:"components,omitempty"` } // ListResponse interface diff --git a/app/app.go b/app/app.go index 2c6267c6..be81dc44 100644 --- a/app/app.go +++ b/app/app.go @@ -8,5 +8,5 @@ type application struct { // App (Application) interface var App application = application{ Name: "odin", - Version: "1.2.4", + Version: "1.3.0-beta", } diff --git a/internal/backend/component.go b/internal/backend/component.go new file mode 100644 index 00000000..43906508 --- /dev/null +++ b/internal/backend/component.go @@ -0,0 +1,16 @@ +package backend + +import ( + "path" + + "github.com/dream11/odin/api/component" +) + +type Component struct{} + +func (c *Component) OperateComponent(componentName string, data component.OperateComponentRequest) { + client := newStreamingApiClient() + client.Headers["Command-Verb"] = "operate" + response := client.streamWithRetry(path.Join("component", componentName, "operate"), "PUT", data) + response.Process(true) +} diff --git a/internal/backend/componentType.go b/internal/backend/componentType.go index eb09a4c8..f7e14cb8 100644 --- a/internal/backend/componentType.go +++ b/internal/backend/componentType.go @@ -4,14 +4,14 @@ import ( "encoding/json" "path" - "github.com/dream11/odin/api/component" + "github.com/dream11/odin/api/componenttype" ) // ComponentType entity type ComponentType struct{} // ListComponentTypes : list all available component types -func (c *ComponentType) ListComponentTypes(componentTypeName, version string) ([]component.Type, error) { +func (c *ComponentType) ListComponentTypes(componentTypeName, version string) ([]componenttype.Type, error) { client := newApiClient() client.QueryParams["version"] = version client.QueryParams["name"] = componentTypeName @@ -19,20 +19,20 @@ func (c *ComponentType) ListComponentTypes(componentTypeName, version string) ([ response.Process(true) // process response and exit if error - var componentTypeResponse component.ListTypeResponse + var componentTypeResponse componenttype.ListTypeResponse err := json.Unmarshal(response.Body, &componentTypeResponse) return componentTypeResponse.Response, err } // DescribeComponentTypes : describe a component type -func (c *ComponentType) DescribeComponentType(componentTypeName, version string) (component.ComponentDetails, error) { +func (c *ComponentType) DescribeComponentType(componentTypeName, version string) (componenttype.ComponentDetails, error) { client := newApiClient() client.QueryParams["version"] = version response := client.actionWithRetry(path.Join("componenttypes", componentTypeName), "GET", nil) response.Process(true) // process response and exit if error - var componentDetailsResponse component.ComponentDetailsResponse + var componentDetailsResponse componenttype.ComponentDetailsResponse err := json.Unmarshal(response.Body, &componentDetailsResponse) return componentDetailsResponse.Response, err diff --git a/internal/backend/operation.go b/internal/backend/operation.go new file mode 100644 index 00000000..1729de1e --- /dev/null +++ b/internal/backend/operation.go @@ -0,0 +1,19 @@ +package backend + +import ( + "encoding/json" + "path" + + operationapi "github.com/dream11/odin/api/operation" +) + +type Operation struct{} + +func (o *Operation) ListOperations(componentTypeName string) ([]operationapi.Operation, error) { + client := newApiClient() + response := client.actionWithRetry(path.Join("component", componentTypeName, "operate"), "GET", nil) + response.Process(true) // process response and exit if error + var listResponse operationapi.ListOperation + err := json.Unmarshal(response.Body, &listResponse) + return listResponse.Response, err +} diff --git a/internal/command/command_catalog.go b/internal/command/command_catalog.go index fbd644cf..7bdb2922 100644 --- a/internal/command/command_catalog.go +++ b/internal/command/command_catalog.go @@ -138,6 +138,15 @@ func CommandsCatalog() map[string]cli.CommandFactory { "undeploy service-set": func() (cli.Command, error) { return &commands.ServiceSet{Undeploy: true}, nil }, + "list operation": func() (cli.Command, error) { + return &commands.Operation{List: true}, nil + }, + "describe operation": func() (cli.Command, error) { + return &commands.Operation{Describe: true}, nil + }, + "operate component": func() (cli.Command, error) { + return &commands.Component{Operate: true}, nil + }, // Verb for application-template "generate application-template": func() (cli.Command, error) { diff --git a/internal/command/commands/command.go b/internal/command/commands/command.go index d1666dc3..80df4829 100644 --- a/internal/command/commands/command.go +++ b/internal/command/commands/command.go @@ -32,6 +32,7 @@ type command struct { Release bool // Release a resource record Set bool // Set a default env Update bool // Update a env + Operate bool // Operate on a component Logger ui.Logger // Use this to log messages Input ui.Input // Use this to take inputs diff --git a/internal/command/commands/component.go b/internal/command/commands/component.go new file mode 100644 index 00000000..e17d55f8 --- /dev/null +++ b/internal/command/commands/component.go @@ -0,0 +1,90 @@ +package commands + +import ( + "encoding/json" + "flag" + "fmt" + + "github.com/dream11/odin/api/component" + "github.com/dream11/odin/internal/backend" + "github.com/dream11/odin/pkg/utils" +) + +var componentClient backend.Component + +type Component command + +func (c *Component) Run(args []string) int { + // Define flag set + flagSet := flag.NewFlagSet("flagSet", flag.ContinueOnError) + name := flagSet.String("name", "", "name of the component") + serviceName := flagSet.String("service", "", "name of the service in which the component is deployed") + envName := flagSet.String("env", "", "name of the environment in which the service is deployed") + operation := flagSet.String("operation", "", "name of the operation to performed on the component") + options := flagSet.String("options", "", "options of the operation in JSON format") + + err := flagSet.Parse(args) + if err != nil { + c.Logger.Error("Unable to parse flags! " + err.Error()) + return 1 + } + + if c.Operate { + if *envName == "" { + *envName = utils.FetchKey(ENV_NAME_KEY) + } + emptyParameters := emptyParameters(map[string]string{"--name": *name, "--service": *serviceName, "--env": *envName, "--operation": *operation, "--options": *options}) + if len(emptyParameters) == 0 { + var optionsJson interface{} + err = json.Unmarshal([]byte(*options), &optionsJson) + if err != nil { + c.Logger.Error("Unable to parse options JSON " + err.Error()) + return 1 + } + + data := component.OperateComponentRequest{ + Data: component.Data{ + EnvName: *envName, + ServiceName: *serviceName, + Operations: []component.Operation{ + { + Name: *operation, + Values: optionsJson, + }, + }, + }, + } + + componentClient.OperateComponent(*name, data) + return 0 + } + + c.Logger.Error(fmt.Sprintf("%s cannot be blank", emptyParameters)) + return 1 + } + + c.Logger.Error("Not a valid command") + return 127 +} + +// Help : returns an explanatory string +func (c *Component) Help() string { + if c.Operate { + return commandHelper("operate", "component", "", []Options{ + {Flag: "--name", Description: "name of the component"}, + {Flag: "--service", Description: "name of the service in which the component is deployed"}, + {Flag: "--env", Description: "name of the environment in which the service is deployed"}, + {Flag: "--operation", Description: "name of the operation to performed on the component"}, + {Flag: "--options", Description: "options of the operation in JSON format"}, + }) + } + return defaultHelper() +} + +// Synopsis : returns a brief helper text for the command's verbs +func (c *Component) Synopsis() string { + if c.Operate { + return "Operate on a component" + } + return defaultHelper() +} diff --git a/internal/command/commands/operation.go b/internal/command/commands/operation.go new file mode 100644 index 00000000..bbc66935 --- /dev/null +++ b/internal/command/commands/operation.go @@ -0,0 +1,122 @@ +package commands + +import ( + "encoding/json" + "flag" + "fmt" + + "github.com/dream11/odin/internal/backend" + "github.com/dream11/odin/pkg/table" +) + +var operationClient backend.Operation + +type Operation command + +func (o *Operation) Run(args []string) int { + // Define flag set + flagSet := flag.NewFlagSet("flagSet", flag.ContinueOnError) + name := flagSet.String("name", "", "name of the operation") + componentType := flagSet.String("component-type", "", "component-type on which operations will be performed") + + err := flagSet.Parse(args) + if err != nil { + o.Logger.Error("Unable to parse flags! " + err.Error()) + return 1 + } + + if o.List { + emptyParameters := emptyParameters(map[string]string{"--component-type": *componentType}) + if len(emptyParameters) == 0 { + operationList, err := operationClient.ListOperations(*componentType) + if err != nil { + o.Logger.Error(err.Error()) + return 1 + } + + o.Logger.Info("Listing all operation(s)") + tableHeaders := []string{"Name", "Descrption"} + var tableData [][]interface{} + + for _, operation := range operationList { + tableData = append(tableData, []interface{}{ + operation.Name, + operation.Description, + }) + } + table.Write(tableHeaders, tableData) + + return 0 + } + + o.Logger.Error(fmt.Sprintf("%s cannot be blank", emptyParameters)) + return 1 + } + + if o.Describe { + emptyParameters := emptyParameters(map[string]string{"--name": *name, "--component-type": *componentType}) + if len(emptyParameters) == 0 { + operationList, err := operationClient.ListOperations(*componentType) + if err != nil { + o.Logger.Error(err.Error()) + return 1 + } + + o.Logger.Info("Describing operation: " + *name + " on component " + *componentType) + var operationKeys interface{} + + for i := range operationList { + if operationList[i].Name == *name { + operationKeys = operationList[i].InputSchema + break + } + } + + if operationKeys == nil { + o.Logger.Error(fmt.Sprintf("operation: %s does not exist for the component: %s", *name, *componentType)) + return 1 + } + + operationKeysJson, err := json.MarshalIndent(operationKeys, "", " ") + if err != nil { + o.Logger.Error(err.Error()) + return 1 + } + + o.Logger.Output(fmt.Sprintf("\n%s", operationKeysJson)) + return 0 + } + + o.Logger.Error(fmt.Sprintf("%s cannot be blank", emptyParameters)) + return 1 + } + o.Logger.Error("Not a valid command") + return 127 +} + +// Help : returns an explanatory string +func (o *Operation) Help() string { + if o.List { + return commandHelper("list", "operation", "", []Options{ + {Flag: "--component-type", Description: "component-type on which operations will be performed"}, + }) + } + if o.Describe { + return commandHelper("describe", "operation", "", []Options{ + {Flag: "--name", Description: "name of the operation"}, + {Flag: "--component-type", Description: "component-type on which operations will be performed"}, + }) + } + return defaultHelper() +} + +// Synopsis : returns a brief helper text for the command's verbs +func (o *Operation) Synopsis() string { + if o.List { + return "list all operations on a component-type" + } + if o.Describe { + return "describe operation on a component-type" + } + return defaultHelper() +}