Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/status cmd #260

Open
wants to merge 13 commits into
base: 2.x.x
Choose a base branch
from
118 changes: 118 additions & 0 deletions cmd/status/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package status

import (
"encoding/json"
"fmt"
"log"

"github.com/dream11/odin/internal/service"
"github.com/dream11/odin/pkg/constant"
"github.com/dream11/odin/pkg/table"
"github.com/dream11/odin/pkg/util"
environment "github.com/dream11/odin/proto/gen/go/dream11/od/environment/v1"
"github.com/spf13/cobra"
)

// setstatusCmd represents the env command
var setstatusCmd = &cobra.Command{
Use: "env",
Short: "Fetch deployment status of the environment",
Long: `Fetch deployment status of the environment`,
Run: func(cmd *cobra.Command, args []string) {
getStatus(cmd)
},
}
var environmentClient = service.Environment{}
var serviceName string
var envName string

func init() {
statusCmd.AddCommand(setstatusCmd)
setstatusCmd.Flags().String("name", "", "Name of the environment")
setstatusCmd.Flags().String("service", "", "Name of the service (optional)")
}

func getStatus(cmd *cobra.Command) {
ctx := cmd.Context()
envName, _ = cmd.Flags().GetString("name")
serviceName, _ = cmd.Flags().GetString("service")
if envName == "" {
log.Fatal("Error: --name is required")
}
response, err := environmentClient.EnvironmentStatus(&ctx, &environment.StatusEnvironmentRequest{
EnvName: envName,
ServiceName: serviceName,
})
if err != nil {
log.Fatal("Failed to get environment status: ", err)
}
outputFormat, err := cmd.Flags().GetString("output")
if err != nil {
log.Fatal(err)
}
writeOutput(response, outputFormat)
}

func writeOutput(response *environment.StatusEnvironmentResponse, format string) {

switch format {
case constant.TEXT:
writeAsTextEnvResponse(response)
case constant.JSON:
writeAsJSONEnvResponse(response)
default:
log.Fatal("Unknown output format: ", format)
}
}

func writeAsTextEnvResponse(response *environment.StatusEnvironmentResponse) {
fmt.Printf("Fetching status for environment: %s\n", response.GetEnvName())
fmt.Printf("Environment Status: %s\n", response.GetEnvStatus())
var tableHeaders = []string{"NAME",
"VERSION",
"STATUS",
"LAST DEPLOYED",
}
var tableData [][]interface{}

if serviceName == "" {
fmt.Println("\nServices:")
for _, svc := range response.GetServicesStatus() {
tableData = append(tableData, []interface{}{
svc.GetServiceName(),
svc.GetServiceVersion(),
svc.GetServiceStatus(),
util.FormatToHumanReadableDuration(svc.GetLastDeployed()),
})
}
} else {
tableHeaders = []string{"NAME",
"VERSION",
"STATUS"}
fmt.Printf("Fetching status for service: %s in environment: %s\n", serviceName, envName)
for _, svc := range response.GetServicesStatus() {
if svc.GetServiceName() == serviceName {
fmt.Printf("Service version: %s\n", svc.GetServiceVersion())
fmt.Printf("Service Status: %s\n", svc.GetServiceStatus())
fmt.Printf("Last deployed: %s\n", util.FormatToHumanReadableDuration(svc.GetLastDeployed()))
fmt.Println("Component details:")
for _, component := range svc.GetComponentsStatus() {
tableData = append(tableData, []interface{}{
component.GetComponentName(),
component.GetComponentVersion(),
component.GetComponentStatus(),
})
}
}
}
}
table.Write(tableHeaders, tableData)
}

func writeAsJSONEnvResponse(response *environment.StatusEnvironmentResponse) {
output, err := json.MarshalIndent(response, "", " ")
if err != nil {
log.Fatal("Error marshaling JSON:", err)
}
fmt.Print(string(output))
}
17 changes: 17 additions & 0 deletions cmd/status/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package status

import (
"github.com/dream11/odin/cmd"
"github.com/spf13/cobra"
)

// statusCmd represents the status command
var statusCmd = &cobra.Command{
Use: "status",
Short: "This command is accessed by using one of the subcommands below.",
Long: `This command is accessed by using one of the subcommands below.`,
}

func init() {
cmd.RootCmd.AddCommand(statusCmd)
}
1,493 changes: 4 additions & 1,489 deletions go.sum

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions internal/service/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,36 @@ func (e *Environment) DescribeEnvironment(ctx *context.Context, request *environ

return response, nil
}

// EnvironmentStatus shows environment status including services and components in it
func (e *Environment) EnvironmentStatus(ctx *context.Context, request *environment.StatusEnvironmentRequest) (*environment.StatusEnvironmentResponse, error) {
conn, requestCtx, err := grpcClient(ctx)
if err != nil {
return nil, err
}

client := environment.NewEnvironmentServiceClient(conn)
log.Info("Getting environment status...")
spinnerInstance := spinner.New(spinner.CharSets[constant.SpinnerType], constant.SpinnerDelay)
_ = spinnerInstance.Color(constant.SpinnerColor, constant.SpinnerStyle)
spinnerInstance.Start()
stream, err := client.StatusEnvironment(*requestCtx, request)
if err != nil {
return nil, err
}
var response *environment.StatusEnvironmentResponse
var prevResponse *environment.StatusEnvironmentResponse

for {
prevResponse = response
response, err = stream.Recv()
if err != nil {
if errors.Is(err, context.Canceled) || err == io.EOF {
break
}
return nil, err
}
}
spinnerInstance.Stop()
return prevResponse, nil
}
4 changes: 4 additions & 0 deletions internal/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"strings"

"github.com/briandowns/spinner"
"github.com/dream11/odin/pkg/constant"
Expand Down Expand Up @@ -286,6 +287,9 @@ func (e *Service) ReleaseService(ctx *context.Context, request *serviceProto.Rel
spinnerInstance.Start()
}
}
if strings.Contains(strings.ToLower(message), "fail") {
return errors.New(message)
}
log.Info("Service released successfully !")
return err
}
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
_ "github.com/dream11/odin/cmd/operate"
_ "github.com/dream11/odin/cmd/release"
_ "github.com/dream11/odin/cmd/set"
_ "github.com/dream11/odin/cmd/status"
_ "github.com/dream11/odin/cmd/undeploy"
_ "github.com/dream11/odin/cmd/update"
_ "github.com/dream11/odin/internal/ui"
Expand Down
32 changes: 32 additions & 0 deletions pkg/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"net"
"strings"
"time"

v1 "github.com/dream11/odin/proto/gen/go/dream11/od/service/v1"
)
Expand Down Expand Up @@ -31,6 +32,37 @@ func GenerateResponseMessage(response *v1.ServiceResponse) string {
return message
}

// FormatToHumanReadableDuration takes a date-time string representing the last deployment time, and returns a human-readable string representing the duration since the last deployment
func FormatToHumanReadableDuration(lastDeployed string) string {
// Parse the date-time string
layout := "02-01-2006 15:04:05:0000"
parsedTime, err := time.Parse(layout, lastDeployed)
if err != nil {
return fmt.Sprintf("Failed to parse last deployed time: %v", err)
}

// Calculate the duration
duration := time.Since(parsedTime)

// Handle negative durations
if duration < 0 {
duration = -duration
}

// Format the duration into a human-readable string
if duration.Hours() >= 24*180 {
months := int(duration.Hours() / (24 * 30))
return fmt.Sprintf("%d months ago", months)
} else if duration.Hours() >= 24 {
days := int(duration.Hours() / 24)
return fmt.Sprintf("%d days ago", days)
} else {
hours := int(duration.Hours())
minutes := int(duration.Minutes()) % 60
return fmt.Sprintf("%d hours %d minutes ago", hours, minutes)
}
}

// contains checks if a string is present in an array of strings
func contains(str string, arr []string) bool {
for _, item := range arr {
Expand Down
5 changes: 2 additions & 3 deletions proto/dream11/od/dto/v1/service_task.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ syntax = "proto3";
package dream11.od.dto.v1;

import "dream11/od/dto/v1/component_task.proto";
import "google/protobuf/timestamp.proto";

option go_package = "github.com/dream11/odin/proto/gen/go/dream11/od/dto/v1";

message ServiceTask {
optional int64 id = 1;
optional google.protobuf.Timestamp created_at = 2;
optional google.protobuf.Timestamp updated_at = 3;
optional string created_at = 2;
optional string updated_at = 3;
optional int64 created_by = 4;
optional int64 org_id = 5;
optional string name = 6;
Expand Down
26 changes: 26 additions & 0 deletions proto/dream11/od/environment/v1/environment.proto
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,32 @@ service EnvironmentService {
rpc UpdateEnvironment(UpdateEnvironmentRequest) returns (UpdateEnvironmentResponse) {}
rpc CreateEnvironment(CreateEnvironmentRequest) returns (stream CreateEnvironmentResponse) {}
rpc DeleteEnvironment(DeleteEnvironmentRequest) returns (stream DeleteEnvironmentResponse) {}
rpc StatusEnvironment(StatusEnvironmentRequest) returns (stream StatusEnvironmentResponse) {}
}

message StatusEnvironmentRequest {
string env_name = 1;
string service_name = 2;
}

message StatusEnvironmentResponse {
string env_name = 1;
string env_status = 2;
repeated ServiceComponentStatus services_status = 3;
}

message ServiceComponentStatus {
string service_name = 1;
string service_version = 2;
string service_status = 3;
string last_deployed = 4;
repeated ComponentStatusLive components_status = 5;
}

message ComponentStatusLive {
string component_name = 1;
string component_version = 2;
string component_status = 3;
}

message ListEnvironmentRequest {
Expand Down
Loading
Loading