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: Implemented deployment status check #88

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 207 additions & 0 deletions cmd/world/forge/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@ package forge

import (
"context"
"encoding/json"
"fmt"
"net/http"
"regexp"
"strings"
"time"

"github.com/rotisserie/eris"

"pkg.world.dev/world-cli/common/globalconfig"
)

var statusFailRegEx = regexp.MustCompile(`[^a-zA-Z0-9\. ]+`)

// Deploy a project
func deploy(ctx context.Context) error {
globalConfig, err := globalconfig.GetGlobalConfig()
Expand Down Expand Up @@ -128,3 +133,205 @@ func destroy(ctx context.Context) error {

return nil
}

//nolint:funlen, gocognit, gocyclo, cyclop // this is actually a straightforward function with a lot of error handling
func status(ctx context.Context) error {
globalConfig, err := globalconfig.GetGlobalConfig()
if err != nil {
return eris.Wrap(err, "Failed to get global config")
}
projectID := globalConfig.ProjectID
if projectID == "" {
printNoSelectedProject()
return nil
}
// Get project details
prj, err := getSelectedProject(ctx)
if err != nil {
return eris.Wrap(err, "Failed to get project details")
}

statusURL := fmt.Sprintf("%s/api/deployment/%s", baseURL, projectID)
result, err := sendRequest(ctx, http.MethodGet, statusURL, nil)
if err != nil {
return eris.Wrap(err, "Failed to get deployment status")
}
var response map[string]any
err = json.Unmarshal(result, &response)
if err != nil {
return eris.Wrap(err, "Failed to unmarshal deployment response")
}
var data map[string]any
if response["data"] != nil {
// data = null is returned when there are no deployments, so we have to check for that before we
// try to cast the response into a json object map, since this is not an error but the cast would
// fail
var ok bool
data, ok = response["data"].(map[string]any)
if !ok {
return eris.New("Failed to unmarshal deployment data")
}
}
fmt.Println("Deployment Status")
fmt.Println("-----------------")
fmt.Printf("Project: %s\n", prj.Name)
fmt.Printf("Project Slug: %s\n", prj.Slug)
fmt.Printf("Repository: %s\n", prj.RepoURL)
if data == nil {
fmt.Printf("\n** Project has not been deployed **\n")
return nil
}
if data["project_id"] != projectID {
return eris.Errorf("Deployment status does not match project id %s", projectID)
}
if data["type"] != "deploy" && data["type"] != "destroy" && data["type"] != "reset" {
return eris.Errorf("Unknown deployment type %s", data["type"])
}
executorID, ok := data["executor_id"].(string)
if !ok {
return eris.New("Failed to unmarshal deployment executor_id")
}
executionTimeStr, ok := data["execution_time"].(string)
if !ok {
return eris.New("Failed to unmarshal deployment execution_time")
}
dt, dte := time.Parse(time.RFC3339, executionTimeStr)
if dte != nil {
return eris.Wrapf(dte, "Failed to parse deployment execution_time %s", dt)
}
Comment on lines +198 to +201
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Enhance error handling for time parsing.

The error message for time parsing could be more informative by including the actual string that failed to parse.

-dt, dte := time.Parse(time.RFC3339, executionTimeStr)
-if dte != nil {
-    return eris.Wrapf(dte, "Failed to parse deployment execution_time %s", dt)
-}
+dt, dte := time.Parse(time.RFC3339, executionTimeStr)
+if dte != nil {
+    return eris.Wrapf(dte, "Failed to parse deployment execution_time: %q", executionTimeStr)
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
dt, dte := time.Parse(time.RFC3339, executionTimeStr)
if dte != nil {
return eris.Wrapf(dte, "Failed to parse deployment execution_time %s", dt)
}
dt, dte := time.Parse(time.RFC3339, executionTimeStr)
if dte != nil {
return eris.Wrapf(dte, "Failed to parse deployment execution_time: %q", executionTimeStr)
}

bnf, ok := data["build_number"].(float64)
if !ok {
return eris.New("Failed to unmarshal deployment build_number")
}
buildNumber := int(bnf)
buildTimeStr, ok := data["build_time"].(string)
if !ok {
return eris.New("Failed to unmarshal deployment build_time")
}
bt, bte := time.Parse(time.RFC3339, buildTimeStr)
if bte != nil {
return eris.Wrapf(bte, "Failed to parse deployment build_time %s", bt)
}
buildState, ok := data["build_state"].(string)
if !ok {
return eris.New("Failed to unmarshal deployment build_state")
}
if buildState != "finished" {
fmt.Printf("Build: #%d started %s by %s - %s\n", buildNumber, dt.Format(time.RFC822), executorID, buildState)
return nil
}
fmt.Printf("Build: #%d on %s by %s\n", buildNumber, dt.Format(time.RFC822), executorID)
fmt.Print("Health: ")

// fmt.Println()
// fmt.Println(string(result))

healthURL := fmt.Sprintf("%s/api/health/%s", baseURL, projectID)
result, err = sendRequest(ctx, http.MethodGet, healthURL, nil)
if err != nil {
return eris.Wrap(err, "Failed to get health")
}
err = json.Unmarshal(result, &response)
if err != nil {
return eris.Wrap(err, "Failed to unmarshal health response")
}
if response["data"] == nil {
return eris.New("Failed to unmarshal health data")
}
instances, ok := response["data"].([]any)
if !ok {
return eris.New("Failed to unmarshal deployment status")
}
if len(instances) == 0 {
fmt.Println("** No deployed instances found **")
return nil
}
fmt.Printf("(%d deployed instances)\n", len(instances))
currRegion := ""
for _, instance := range instances {
info, ok := instance.(map[string]any)
if !ok {
return eris.Errorf("Failed to unmarshal deployment instance %d info", instance)
}
region, ok := info["region"].(string)
if !ok {
return eris.Errorf("Failed to unmarshal deployment instance %d region", instance)
}
instancef, ok := info["instance"].(float64)
if !ok {
return eris.Errorf("Failed to unmarshal deployment instance %d instance number", instance)
}
instanceNum := int(instancef)
cardinalInfo, ok := info["cardinal"].(map[string]any)
if !ok {
return eris.Errorf("Failed to unmarshal deployment instance %d cardinal data", instance)
}
nakamaInfo, ok := info["nakama"].(map[string]any)
if !ok {
return eris.Errorf("Failed to unmarshal deployment instance %d nakama data", instance)
}
cardinalURL, ok := cardinalInfo["url"].(string)
if !ok {
return eris.Errorf("Failed to unmarshal deployment instance %d cardinal url", instance)
}
cardinalHost := strings.Split(cardinalURL, "/")[2]
cardinalOK, ok := cardinalInfo["ok"].(bool)
if !ok {
return eris.Errorf("Failed to unmarshal deployment instance %d cardinal ok flag", instance)
}
cardinalResultCodef, ok := cardinalInfo["result_code"].(float64)
if !ok {
return eris.Errorf("Failed to unmarshal deployment instance %d cardinal result_code", instance)
}
cardinalResultCode := int(cardinalResultCodef)
cardinalResultStr, ok := cardinalInfo["result_str"].(string)
if !ok {
return eris.Errorf("Failed to unmarshal deployment instance %d cardinal result_str", instance)
}
nakamaURL, ok := nakamaInfo["url"].(string)
if !ok {
return eris.Errorf("Failed to unmarshal deployment instance %d nakama url", instance)
}
nakamaHost := strings.Split(nakamaURL, "/")[2]
nakamaOK, ok := nakamaInfo["ok"].(bool)
if !ok {
return eris.Errorf("Failed to unmarshal deployment instance %d nakama ok", instance)
}
nakamaResultCodef, ok := nakamaInfo["result_code"].(float64)
if !ok {
return eris.Errorf("Failed to unmarshal deployment instance %d result_code", instance)
}
nakamaResultCode := int(nakamaResultCodef)
nakamaResultStr, ok := nakamaInfo["result_str"].(string)
if !ok {
return eris.Errorf("Failed to unmarshal deployment instance %d result_str", instance)
}
if region != currRegion {
currRegion = region
fmt.Printf("• %s\n", currRegion)
}
fmt.Printf(" %d)", instanceNum)
fmt.Printf("\tCardinal: %s - ", cardinalHost)
switch {
case cardinalOK:
fmt.Print("OK\n")
case cardinalResultCode == 0:
fmt.Printf("FAIL %s\n", statusFailRegEx.ReplaceAllString(cardinalResultStr, ""))
default:
fmt.Printf("FAIL %d %s\n", cardinalResultCode, statusFailRegEx.ReplaceAllString(cardinalResultStr, ""))
}
fmt.Printf("\tNakama: %s - ", nakamaHost)
switch {
case nakamaOK:
fmt.Print("OK\n")
case nakamaResultCode == 0:
fmt.Printf("FAIL %s\n", statusFailRegEx.ReplaceAllString(nakamaResultStr, ""))
default:
fmt.Printf("FAIL %d %s\n", nakamaResultCode, statusFailRegEx.ReplaceAllString(nakamaResultStr, ""))
}
}
// fmt.Println()
// fmt.Println(string(result))

return nil
}
14 changes: 13 additions & 1 deletion cmd/world/forge/forge.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

const (
// For local development
worldForgeBaseURLLocal = "http://localhost:8081"
worldForgeBaseURLLocal = "http://localhost:8001"
ezavada marked this conversation as resolved.
Show resolved Hide resolved

// For production
worldForgeBaseURLProd = "https://forge.world.dev"
Expand Down Expand Up @@ -173,6 +173,17 @@ var (
return destroy(cmd.Context())
},
}

statusCmd = &cobra.Command{
Use: "status",
Short: "Show status of a project",
RunE: func(cmd *cobra.Command, _ []string) error {
if !checkLogin() {
return nil
}
return status(cmd.Context())
},
}
)

func init() {
Expand Down Expand Up @@ -206,5 +217,6 @@ func init() {
// Add deployment commands
deploymentCmd.AddCommand(deployCmd)
deploymentCmd.AddCommand(destroyCmd)
deploymentCmd.AddCommand(statusCmd)
BaseCmd.AddCommand(deploymentCmd)
}
Loading
Loading