diff --git a/cmd/describe/environment.go b/cmd/describe/environment.go index f8545055..d5f544c7 100644 --- a/cmd/describe/environment.go +++ b/cmd/describe/environment.go @@ -3,10 +3,12 @@ package describe import ( "encoding/json" "fmt" + "github.com/dream11/odin/pkg/util" + "strings" "github.com/dream11/odin/internal/service" "github.com/dream11/odin/pkg/constant" - "github.com/dream11/odin/pkg/table" + v1 "github.com/dream11/odin/proto/gen/go/dream11/od/dto/v1" environment "github.com/dream11/odin/proto/gen/go/dream11/od/environment/v1" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -64,7 +66,7 @@ func writeOutput(response *environment.DescribeEnvironmentResponse, format strin switch format { case constant.TEXT: - writeAsTextEnvResponse(response) + printEnvInfo(response) case constant.JSON: writeAsJSONEnvResponse(response) default: @@ -72,58 +74,109 @@ func writeOutput(response *environment.DescribeEnvironmentResponse, format strin } } -func writeAsTextEnvResponse(response *environment.DescribeEnvironmentResponse) { - - tableHeaders := []string{"Name", - "state", - "autoDeletionTime", - "cloudProviderAccounts", - "createdBy", - "updatedBy", - "createdAt", - "updatedAt", - "services"} - var tableData [][]interface{} +func printEnvInfo(response *environment.DescribeEnvironmentResponse) { env := response.Environment - var accountInfoList []string + + // Extracting necessary fields + name := env.GetName() + envType := env.GetProvisioningType() + state := env.GetStatus() + autoDeletionTime := env.AutoDeletionTime.AsTime().String() + cloudProviderAccounts := []string{} + providerAccountCluster := map[string][]string{} for _, accountInfo := range env.AccountInformation { - accountInfoList = append(accountInfoList, accountInfo.ProviderAccountName) - } - accountInfoListJSON, err := json.Marshal(accountInfoList) - if err != nil { - log.Fatal("Failed to marshal account info list: ", err) + cloudProviderAccounts = append(cloudProviderAccounts, accountInfo.ProviderAccountName) + providerAccountCluster[accountInfo.ProviderAccountName] = getClusterNames(accountInfo) } - - var servicesSummary []map[string]interface{} + createdBy := env.GetCreatedBy() + updatedBy := env.GetUpdatedBy() + createdAt := env.CreatedAt.AsTime().String() + updatedAt := env.UpdatedAt.AsTime().String() + services := []string{} for _, svc := range env.Services { - serviceMap := map[string]interface{}{ - "name": svc.Name, - "version": svc.Version, - "status": svc.Status, - } - if len(svc.Components) > 0 { - serviceMap["components"] = svc.Components + if serviceName == "" { + services = append(services, fmt.Sprintf(" - name: %s\n version: %s", *svc.Name, *svc.Version)) + } else { + if serviceName == *svc.Name { + var customServicesOp = []string{} + customServicesOp = append(customServicesOp, fmt.Sprintf(" - name: %s\n version: %s\n", *svc.Name, *svc.Version)) + customServicesOp = append(customServicesOp, " components: \n") + componentBytes, err := json.MarshalIndent(svc.Components, "", " ") + if err != nil { + log.Fatal("Failed to marshal services summary: ", err) + } + var formatedComponentData = string(componentBytes) + formatedComponentData, _ = util.ConvertJSONToYAML(formatedComponentData) + lines := strings.Split(formatedComponentData, "\n") + + for i, line := range lines { + lines[i] = "\t" + line + } + formatedComponentData = strings.Join(lines, "\n") + // Add two tabs before each line in the string + customServicesOp = append(customServicesOp, formatedComponentData) + + services = append(services, strings.Join(customServicesOp, "")) + } + } - servicesSummary = append(servicesSummary, serviceMap) } - servicesSummaryJSON, err := json.Marshal(servicesSummary) - if err != nil { - log.Fatal("Failed to marshal services summary: ", err) + + // Formatting and printing the information + fmt.Printf("Describing Env: %s\n\n", name) + fmt.Printf("name: %s\n", name) + fmt.Printf("envType: %s\n", envType) + fmt.Printf("state: %s\n", state) + fmt.Printf("autoDeletionTime: \"%s\"\n", autoDeletionTime) + fmt.Printf("cloudProviderAccounts:\n") + for _, account := range cloudProviderAccounts { + fmt.Printf(" - %s\n", account) } + fmt.Printf("cluster:\n") + for account, clusters := range providerAccountCluster { + fmt.Printf(" - %s\n", account) + for _, cluster := range clusters { + fmt.Printf(" - %s\n", cluster) + } - tableData = append(tableData, []interface{}{ - env.GetName(), - env.GetStatus(), - env.AutoDeletionTime.AsTime().String(), - string(accountInfoListJSON), - env.GetCreatedBy(), - env.GetUpdatedBy(), - env.CreatedAt.AsTime().String(), - env.UpdatedAt.AsTime().String(), - string(servicesSummaryJSON), - }) + } + fmt.Printf("createdBy: %s\n", createdBy) + fmt.Printf("updatedBy: %s\n", updatedBy) + fmt.Printf("createdAt: \"%s\"\n", createdAt) + fmt.Printf("updatedAt: \"%s\"\n", updatedAt) + fmt.Printf("services:\n%s\n", strings.Join(services, "\n")) +} - table.Write(tableHeaders, tableData) +func findValueByKey(val interface{}, key string) string { + switch v := val.(type) { + case map[string]interface{}: // If it's a map, check for the key + if value, ok := v[key]; ok { + return fmt.Sprintf("%v", value) + } + // Recurse through nested maps or slices + for _, subVal := range v { + return findValueByKey(subVal, key) + } + case []interface{}: // If it's a slice, recurse for each element + for _, item := range v { + return findValueByKey(item, key) + } + } + return "" // Key not found +} + +func getClusterNames(information *v1.AccountInformation) []string { + clusterNames := []string{} + for _, service := range information.ServiceAccountsSnapshot.Account.Services { + if service.Category == "KUBERNETES" { + for key, val := range service.GetData().AsMap() { + if key == "clusters" { + clusterNames = append(clusterNames, findValueByKey(val, "name")) + } + } + } + } + return clusterNames } func writeAsJSONEnvResponse(response *environment.DescribeEnvironmentResponse) { diff --git a/go.mod b/go.mod index 6af98dbb..be97b484 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/google/uuid v1.6.0 github.com/mitchellh/cli v1.1.5 github.com/olekukonko/tablewriter v0.0.5 + gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/internal/service/component.go b/internal/service/component.go index 0f093188..477ce301 100644 --- a/internal/service/component.go +++ b/internal/service/component.go @@ -89,7 +89,7 @@ func (e *Component) DescribeComponentType(ctx *context.Context, request *compone } // CompareOperationChanges compares the operation changes -func (c *Component) CompareOperationChanges(ctx *context.Context, request *serviceProto.OperateComponentDiffRequest) (*serviceProto.OperateComponentDiffResponse, error) { +func (e *Component) CompareOperationChanges(ctx *context.Context, request *serviceProto.OperateComponentDiffRequest) (*serviceProto.OperateComponentDiffResponse, error) { conn, requestCtx, err := grpcClient(ctx) if err != nil { diff --git a/internal/service/service.go b/internal/service/service.go index 683f316f..fd83f292 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -17,6 +17,8 @@ import ( // Service performs operation on service like deploy. undeploy type Service struct{} +var responseMap = make(map[string]string) + // DeployService deploys service func (e *Service) DeployService(ctx *context.Context, request *serviceProto.DeployServiceRequest) error { conn, requestCtx, err := grpcClient(ctx) @@ -50,6 +52,7 @@ func (e *Service) DeployService(ctx *context.Context, request *serviceProto.Depl if response != nil { message = util.GenerateResponseMessage(response.GetServiceResponse()) + logFailedComponentMessagesOnce(response.GetServiceResponse()) spinnerInstance.Prefix = fmt.Sprintf(" %s ", message) spinnerInstance.Start() } @@ -59,6 +62,19 @@ func (e *Service) DeployService(ctx *context.Context, request *serviceProto.Depl return err } +func logFailedComponentMessagesOnce(response *serviceProto.ServiceResponse) { + for _, compMessage := range response.ComponentsStatus { + componentActionKey := compMessage.GetComponentName() + compMessage.GetComponentAction() + compMessage.GetComponentStatus() + //code to not print the same message for component action again + if responseMap[componentActionKey] == "" { + if compMessage.GetComponentStatus() == "FAILED" { + log.Error(fmt.Sprintf("Component %s %s %s %s", compMessage.GetComponentName(), compMessage.GetComponentAction(), compMessage.GetComponentStatus(), compMessage.GetError())) + } + responseMap[componentActionKey] = componentActionKey + } + } +} + // DeployServiceSet deploys service-set func (e *Service) DeployServiceSet(ctx *context.Context, request *serviceProto.DeployServiceSetRequest) error { conn, requestCtx, err := grpcClient(ctx) @@ -93,6 +109,7 @@ func (e *Service) DeployServiceSet(ctx *context.Context, request *serviceProto.D if response != nil { message = "" for _, serviceResponse := range response.GetServices() { + logFailedComponentMessagesOnce(serviceResponse.GetServiceResponse()) message += util.GenerateResponseMessage(serviceResponse.GetServiceResponse()) } spinnerInstance.Prefix = fmt.Sprintf(" %s ", message) diff --git a/pkg/util/util.go b/pkg/util/util.go index 11497c86..cf2c22b9 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -1,6 +1,7 @@ package util import ( + "encoding/json" "fmt" "net" "os" @@ -10,6 +11,7 @@ import ( v1 "github.com/dream11/odin/proto/gen/go/dream11/od/service/v1" "github.com/google/uuid" + "gopkg.in/yaml.v2" ) // SplitProviderAccount splits string into list of cloud provider accounts @@ -30,7 +32,7 @@ func IsIPAddress(address string) bool { func GenerateResponseMessage(response *v1.ServiceResponse) string { message := fmt.Sprintf("\n Service %s %s", response.ServiceStatus.ServiceAction, response.ServiceStatus) for _, compMessage := range response.ComponentsStatus { - message += fmt.Sprintf("\n Component %s %s %s %s", compMessage.ComponentName, compMessage.ComponentAction, compMessage.ComponentStatus, compMessage.Error) + message += fmt.Sprintf("\n Component %s %s %s ", compMessage.ComponentName, compMessage.ComponentAction, compMessage.ComponentStatus) } return message } @@ -123,3 +125,22 @@ func GetEnvOrDefault(key, defaultValue string) string { } return defaultValue } + +// ConvertJSONToYAML takes a JSON string as input and returns a formatted YAML string +func ConvertJSONToYAML(jsonStr string) (string, error) { + // Unmarshal the JSON into a generic structure + var jsonData interface{} + err := json.Unmarshal([]byte(jsonStr), &jsonData) + if err != nil { + return "", fmt.Errorf("failed to parse JSON: %v", err) + } + + // Marshal the structure into YAML + yamlData, err := yaml.Marshal(jsonData) + if err != nil { + return "", fmt.Errorf("failed to convert to YAML: %v", err) + } + + // Return the YAML string + return string(yamlData), nil +}