From 518248f66db8114b2e0ab37948765e7b59543e97 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Sat, 29 Oct 2022 17:48:33 +0330 Subject: [PATCH 01/29] Add periodic get migration progress --- go.mod | 1 + go.sum | 3 + pkg/paas/migration.go | 136 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 129 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 62a042a..5ab9b98 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/docker/docker v20.10.0+incompatible // indirect github.com/fsouza/go-dockerclient v1.6.6 // indirect github.com/gonum/graph v0.0.0-20190426092945-678096d81a4b // indirect + github.com/gosuri/uilive v0.0.4 github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/moby/buildkit v0.8.0 // indirect diff --git a/go.sum b/go.sum index 95e48c3..40b8032 100644 --- a/go.sum +++ b/go.sum @@ -665,6 +665,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY= +github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -834,6 +836,7 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index d8114c0..4b367df 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -8,14 +8,16 @@ import ( "io" "log" "net/http" + "net/url" "os" "strconv" "strings" - "net/url" + "time" "github.com/arvancloud/cli/pkg/api" "github.com/arvancloud/cli/pkg/config" "github.com/arvancloud/cli/pkg/utl" + "github.com/gosuri/uilive" "github.com/olekukonko/tablewriter" "k8s.io/client-go/rest" @@ -30,6 +32,16 @@ const ( yellowColor = "\033[33m" resetColor = "\033[0m" bamdad = "ba1" + interval = 2 +) + +type State string + +const ( + Queued State = "Queued" + Doing = "Doing" + Completed = "Completed" + Failed = "Failed" ) type Request struct { @@ -55,6 +67,29 @@ type ZoneInfo struct { Gateway string `json:"gateway"` } +type StepData struct { + Time time.Time + Message string + Percent int + Response Response +} + +type Step struct { + Order int + Step string + Title string + State string + Data StepData +} + +type ProgressResponse struct { + State State + Source string + Destination string + Namespace string + Steps []Step +} + type Response struct { Source ZoneInfo `json:"source"` Destination ZoneInfo `json:"destination"` @@ -216,18 +251,66 @@ func (v confirmationValidator) confirmationValidate(input string) (bool, error) // migrate sends migration request and displays response. func migrate(request Request) error { - response, err := httpPost(migrationEndpoint, request) - if err != nil { - failureOutput() + postResponse, err := httpPost(migrationEndpoint, request) + if err != nil || postResponse.StatusCode != http.StatusCreated { + failureOutput(fmt.Sprint(postResponse.StatusCode)) return err } - successOutput(response) - + + writer := uilive.New() + writer.Start() + defer writer.Stop() + + stopChannel := make(chan bool, 1) + doEvery(interval*time.Second, stopChannel, func() { + response, _ := httpGet(migrationEndpoint) + + sprintResponse(*response, writer) + + if response.State == Completed { + close(stopChannel) + + successOutput(&response.Steps[len(response.Steps)-1].Data.Response) + } + + if response.State == Failed { + failureOutput(response.Steps[len(response.Steps)-1].Data.Message) + } + }) + + return nil +} + +// doEvery runs given function in periods of 'd' and stops using stopChannel. +func doEvery(d time.Duration, stopChannel chan bool, f func()) { + ticker := time.NewTicker(d) + + for { + select { + case <-ticker.C: + f() + case <-stopChannel: + ticker.Stop() + return + } + } +} + +// sprintResponse displays steps of migration. +func sprintResponse(response ProgressResponse, w *uilive.Writer) error { + responseStr := fmt.Sprintf("Migrating namespace \"%s\" from \"%s\" to \"%s\" started\n", response.Namespace, response.Source, response.Destination) + for _, s := range response.Steps { + responseStr += fmt.Sprintf("\t%s... %s %s\n", s.Title, s.State, s.Data.Message) + } + + fmt.Fprintf(w, "%s", responseStr) + time.Sleep(time.Second * 1) + return nil } // httpPost sends POST request to inserted url. -func httpPost(endpoint string, payload interface{}) (*Response, error) { +func httpPost(endpoint string, payload interface{}) (*http.Response, error) { requestBody, err := json.Marshal(payload) if err != nil { return nil, err @@ -238,7 +321,38 @@ func httpPost(endpoint string, payload interface{}) (*Response, error) { return nil, fmt.Errorf("invalid config") } - httpReq, err := http.NewRequest(http.MethodPost, arvanURL.Scheme + "://" + arvanURL.Host+endpoint, bytes.NewBuffer(requestBody)) + httpReq, err := http.NewRequest(http.MethodPost, arvanURL.Scheme+"://"+arvanURL.Host+endpoint, bytes.NewBuffer(requestBody)) + if err != nil { + return nil, err + } + apikey := arvanConfig.GetApiKey() + if apikey != "" { + httpReq.Header.Add("Authorization", apikey) + } + + httpReq.Header.Add("accept", "application/json") + httpReq.Header.Add("User-Agent", rest.DefaultKubernetesUserAgent()) + httpResp, err := http.DefaultClient.Do(httpReq) + if err != nil { + return nil, err + } + + if httpResp.StatusCode != http.StatusOK { + return nil, errors.New("server error. try again later") + } + + return httpResp, nil +} + +// httpGet sends GET request to inserted url. +func httpGet(endpoint string) (*ProgressResponse, error) { + arvanConfig := config.GetConfigInfo() + arvanURL, err := url.Parse(arvanConfig.GetServer()) + if err != nil { + return nil, fmt.Errorf("invalid config") + } + + httpReq, err := http.NewRequest(http.MethodGet, arvanURL.Scheme+"://"+arvanURL.Host+endpoint, bytes.NewBuffer([]byte{})) if err != nil { return nil, err } @@ -266,7 +380,7 @@ func httpPost(endpoint string, payload interface{}) (*Response, error) { } // parse response - var response Response + var response ProgressResponse err = json.Unmarshal(responseBody, &response) if err != nil { return nil, err @@ -275,8 +389,8 @@ func httpPost(endpoint string, payload interface{}) (*Response, error) { } // failureOutput displays failure output. -func failureOutput() { - fmt.Println("failed to migrate") +func failureOutput(message string) { + fmt.Println("failed to migrate", message) } // successOutput displays success output. From a10aefcc38c50b549f31b183b2b46791c5783568 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Sun, 30 Oct 2022 15:13:43 +0330 Subject: [PATCH 02/29] Add tabwriter to display lines in column --- pkg/paas/migration.go | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 4b367df..f449c88 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -12,17 +12,18 @@ import ( "os" "strconv" "strings" + "text/tabwriter" "time" "github.com/arvancloud/cli/pkg/api" "github.com/arvancloud/cli/pkg/config" "github.com/arvancloud/cli/pkg/utl" + "github.com/gosuri/uilive" "github.com/olekukonko/tablewriter" - "k8s.io/client-go/rest" - "github.com/openshift/oc/pkg/helpers/term" "github.com/spf13/cobra" + "k8s.io/client-go/rest" ) const ( @@ -38,7 +39,7 @@ const ( type State string const ( - Queued State = "Queued" + Pending State = "Pending" Doing = "Doing" Completed = "Completed" Failed = "Failed" @@ -252,23 +253,34 @@ func (v confirmationValidator) confirmationValidate(input string) (bool, error) // migrate sends migration request and displays response. func migrate(request Request) error { postResponse, err := httpPost(migrationEndpoint, request) - if err != nil || postResponse.StatusCode != http.StatusCreated { - failureOutput(fmt.Sprint(postResponse.StatusCode)) + if err != nil { + failureOutput(err.Error()) return err } - writer := uilive.New() - writer.Start() - defer writer.Stop() + if postResponse.StatusCode != http.StatusCreated { + failureOutput(fmt.Sprint(postResponse.StatusCode)) + } + + // init writer to update lines + uiliveWriter := uilive.New() + uiliveWriter.Start() + + // init writer to display lines in column + tabWriter := new(tabwriter.Writer) + tabWriter.Init(uiliveWriter, 0, 8, 0, '\t', 0) stopChannel := make(chan bool, 1) + doEvery(interval*time.Second, stopChannel, func() { response, _ := httpGet(migrationEndpoint) - sprintResponse(*response, writer) + sprintResponse(*response, tabWriter) if response.State == Completed { close(stopChannel) + tabWriter.Flush() + uiliveWriter.Stop() successOutput(&response.Steps[len(response.Steps)-1].Data.Response) } @@ -297,14 +309,14 @@ func doEvery(d time.Duration, stopChannel chan bool, f func()) { } // sprintResponse displays steps of migration. -func sprintResponse(response ProgressResponse, w *uilive.Writer) error { - responseStr := fmt.Sprintf("Migrating namespace \"%s\" from \"%s\" to \"%s\" started\n", response.Namespace, response.Source, response.Destination) +func sprintResponse(response ProgressResponse, w io.Writer) error { + responseStr := fmt.Sprintln("") for _, s := range response.Steps { - responseStr += fmt.Sprintf("\t%s... %s %s\n", s.Title, s.State, s.Data.Message) + responseStr += fmt.Sprintf("\t%s...\t\t%s\t%s\n", s.Title, s.State, s.Data.Message) } fmt.Fprintf(w, "%s", responseStr) - time.Sleep(time.Second * 1) + time.Sleep(time.Millisecond * 100) return nil } From 8ec096477a0e91b8ae911d05fde26b263d69bae7 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Sun, 30 Oct 2022 15:28:25 +0330 Subject: [PATCH 03/29] Change api call interval --- pkg/paas/migration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index f449c88..3ac7ec2 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -33,7 +33,7 @@ const ( yellowColor = "\033[33m" resetColor = "\033[0m" bamdad = "ba1" - interval = 2 + interval = 10 ) type State string From ad77affb1dc0335c97b7e3862413fefe4c2ee81d Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Sun, 30 Oct 2022 15:32:21 +0330 Subject: [PATCH 04/29] Fix lint --- pkg/paas/migration.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 3ac7ec2..7d8f0fc 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -40,9 +40,9 @@ type State string const ( Pending State = "Pending" - Doing = "Doing" - Completed = "Completed" - Failed = "Failed" + Doing State = "Doing" + Completed State = "Completed" + Failed State = "Failed" ) type Request struct { From 130f19fcec41ed2d2e0b27779cc9eddaf14e2f57 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Sun, 30 Oct 2022 17:16:36 +0330 Subject: [PATCH 05/29] Fix error handling --- pkg/paas/migration.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 7d8f0fc..be11e8d 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -106,7 +106,11 @@ func NewCmdMigrate(in io.Reader, out, errout io.Writer) *cobra.Command { explainOut := term.NewResponsiveWriter(out) c.SetOutput(explainOut) - project, _ := getSelectedProject(in, explainOut) + project, err := getSelectedProject(in, explainOut) + if err != nil { + failureOutput(err.Error()) + return + } currentRegionName := getCurrentRegion() @@ -260,6 +264,7 @@ func migrate(request Request) error { if postResponse.StatusCode != http.StatusCreated { failureOutput(fmt.Sprint(postResponse.StatusCode)) + return errors.New(fmt.Sprint(postResponse.StatusCode)) } // init writer to update lines @@ -402,7 +407,7 @@ func httpGet(endpoint string) (*ProgressResponse, error) { // failureOutput displays failure output. func failureOutput(message string) { - fmt.Println("failed to migrate", message) + fmt.Println("failed:", message) } // successOutput displays success output. From f5e17017665419f527ff127d95cea89ae699a152 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Sun, 30 Oct 2022 19:32:56 +0330 Subject: [PATCH 06/29] Add ticker function run immidiately --- pkg/paas/migration.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index be11e8d..63fa52f 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -303,9 +303,10 @@ func doEvery(d time.Duration, stopChannel chan bool, f func()) { ticker := time.NewTicker(d) for { + f() select { case <-ticker.C: - f() + continue case <-stopChannel: ticker.Stop() return From 1e520238cd892157c0c0e2d4b6ab73bc4538ffb5 Mon Sep 17 00:00:00 2001 From: pejman fahimi Date: Mon, 31 Oct 2022 03:36:06 +0330 Subject: [PATCH 07/29] fix migrate response type --- pkg/api/api.go | 9 +++--- pkg/config/zone.go | 2 +- pkg/paas/migration.go | 74 +++++++++++++++++++++---------------------- pkg/utl/helpers.go | 5 +-- 4 files changed, 45 insertions(+), 45 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 38b7d40..b420b8d 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -21,7 +21,7 @@ const ( updateServer = "https://cli.arvan.run" ) -//GetUserInfo returns a dictionary of user info if authentication credentials is valid. +// GetUserInfo returns a dictionary of user info if authentication credentials is valid. func GetUserInfo(apikey string) (map[string]string, error) { arvanConfig := config.GetConfigInfo() arvanServer := arvanConfig.GetServer() @@ -60,8 +60,7 @@ func GetUserInfo(apikey string) (map[string]string, error) { return user, nil } - -//GetZones from PaaS API +// GetZones from PaaS API func GetZones() (config.Region, error) { var regions config.Region arvanConfig := config.GetConfigInfo() @@ -69,8 +68,8 @@ func GetZones() (config.Region, error) { if err != nil { return regions, fmt.Errorf("invalid config") } - - httpReq, err := http.NewRequest("GET", arvanURL.Scheme + "://" + arvanURL.Host+regionsEndpoint, nil) + + httpReq, err := http.NewRequest("GET", arvanURL.Scheme+"://"+arvanURL.Host+regionsEndpoint, nil) if err != nil { return regions, err } diff --git a/pkg/config/zone.go b/pkg/config/zone.go index b7c000b..316b564 100644 --- a/pkg/config/zone.go +++ b/pkg/config/zone.go @@ -19,4 +19,4 @@ type Zone struct { RegionCity string `json:"region_city"` RegionCountry string `json:"region_country"` CreatedAt time.Time `json:"created_at"` -} \ No newline at end of file +} diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 63fa52f..62fb834 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -27,22 +27,22 @@ import ( ) const ( - migrationEndpoint = "/paas/v1/migrate" + migrationEndpoint = "/paas/v1/%s/migrate" redColor = "\033[31m" greenColor = "\033[32m" yellowColor = "\033[33m" resetColor = "\033[0m" bamdad = "ba1" - interval = 10 + interval = 2 ) type State string const ( - Pending State = "Pending" - Doing State = "Doing" - Completed State = "Completed" - Failed State = "Failed" + Pending State = "pending" + Doing State = "doing" + Completed State = "completed" + Failed State = "failed" ) type Request struct { @@ -69,10 +69,11 @@ type ZoneInfo struct { } type StepData struct { - Time time.Time - Message string - Percent int - Response Response + Time time.Time `json:"time"` + Message string `json:"message"` + Percent int `json:"percent"` + Source ZoneInfo `json:"source"` + Destination ZoneInfo `json:"destination"` } type Step struct { @@ -84,16 +85,11 @@ type Step struct { } type ProgressResponse struct { - State State - Source string - Destination string - Namespace string - Steps []Step -} - -type Response struct { - Source ZoneInfo `json:"source"` - Destination ZoneInfo `json:"destination"` + State State `json:"state"` + Source string `json:"source"` + Destination string `json:"destination"` + Namespace string `json:"namespace"` + Steps []Step `json:"steps"` } // NewCmdMigrate returns new cobra commad enables user to migrate namespaces to another region on arvan servers. @@ -256,17 +252,16 @@ func (v confirmationValidator) confirmationValidate(input string) (bool, error) // migrate sends migration request and displays response. func migrate(request Request) error { - postResponse, err := httpPost(migrationEndpoint, request) + postResponse, err := httpPost(fmt.Sprintf(migrationEndpoint, request.Source), request) if err != nil { failureOutput(err.Error()) return err } - if postResponse.StatusCode != http.StatusCreated { + if postResponse.StatusCode != http.StatusOK && postResponse.StatusCode != http.StatusFound { failureOutput(fmt.Sprint(postResponse.StatusCode)) return errors.New(fmt.Sprint(postResponse.StatusCode)) } - // init writer to update lines uiliveWriter := uilive.New() uiliveWriter.Start() @@ -278,7 +273,12 @@ func migrate(request Request) error { stopChannel := make(chan bool, 1) doEvery(interval*time.Second, stopChannel, func() { - response, _ := httpGet(migrationEndpoint) + response, err := httpGet(fmt.Sprintf(migrationEndpoint, request.Source)) + if err != nil { + failureOutput(err.Error()) + close(stopChannel) + return + } sprintResponse(*response, tabWriter) @@ -287,7 +287,7 @@ func migrate(request Request) error { tabWriter.Flush() uiliveWriter.Stop() - successOutput(&response.Steps[len(response.Steps)-1].Data.Response) + successOutput(response.Steps[len(response.Steps)-1].Data) } if response.State == Failed { @@ -318,7 +318,7 @@ func doEvery(d time.Duration, stopChannel chan bool, f func()) { func sprintResponse(response ProgressResponse, w io.Writer) error { responseStr := fmt.Sprintln("") for _, s := range response.Steps { - responseStr += fmt.Sprintf("\t%s...\t\t%s\t%s\n", s.Title, s.State, s.Data.Message) + responseStr += fmt.Sprintf("\t%d-%s \t\t\t%s\t%s\n", s.Order, s.Title, strings.Title(s.State), s.Data.Message) } fmt.Fprintf(w, "%s", responseStr) @@ -355,7 +355,7 @@ func httpPost(endpoint string, payload interface{}) (*http.Response, error) { return nil, err } - if httpResp.StatusCode != http.StatusOK { + if httpResp.StatusCode != http.StatusOK && httpResp.StatusCode != http.StatusFound { return nil, errors.New("server error. try again later") } @@ -412,14 +412,14 @@ func failureOutput(message string) { } // successOutput displays success output. -func successOutput(response *Response) { +func successOutput(data StepData) { fmt.Println("\nYour IPs changed successfully") ipTable := tablewriter.NewWriter(os.Stdout) ipTable.SetHeader([]string{"Old IPs", "New IPs"}) - for i := 0; i < len(response.Source.Services); i++ { - ipTable.Append([]string{redColor + response.Source.Services[i].IP + resetColor, greenColor + response.Destination.Services[i].IP + resetColor}) + for i := 0; i < len(data.Source.Services); i++ { + ipTable.Append([]string{redColor + data.Source.Services[i].IP + resetColor, greenColor + data.Destination.Services[i].IP + resetColor}) } ipTable.Render() @@ -429,13 +429,13 @@ func successOutput(response *Response) { nonfreeSourceRoutes := make([]Route, 0) nonfreeDestinationRoutes := make([]Route, 0) - for i := 0; i < len(response.Source.Routes); i++ { - if response.Source.Routes[i].IsFree { - freeSourceRoutes = append(freeSourceRoutes, response.Source.Routes[i]) - freeDestinationRoutes = append(freeDestinationRoutes, response.Destination.Routes[i]) + for i := 0; i < len(data.Source.Routes); i++ { + if data.Source.Routes[i].IsFree { + freeSourceRoutes = append(freeSourceRoutes, data.Source.Routes[i]) + freeDestinationRoutes = append(freeDestinationRoutes, data.Destination.Routes[i]) } else { - nonfreeSourceRoutes = append(nonfreeSourceRoutes, response.Source.Routes[i]) - nonfreeDestinationRoutes = append(nonfreeDestinationRoutes, response.Destination.Routes[i]) + nonfreeSourceRoutes = append(nonfreeSourceRoutes, data.Source.Routes[i]) + nonfreeDestinationRoutes = append(nonfreeDestinationRoutes, data.Destination.Routes[i]) } } @@ -467,7 +467,7 @@ func successOutput(response *Response) { gatewayTable.SetHeader([]string{"old gateway", "new gateway"}) fmt.Println("For non-free domains above, please change your gateway in your DNS provider as bellow:") - gatewayTable.Append([]string{redColor + response.Source.Gateway + resetColor, greenColor + response.Destination.Gateway + resetColor}) + gatewayTable.Append([]string{redColor + data.Source.Gateway + resetColor, greenColor + data.Destination.Gateway + resetColor}) gatewayTable.Render() } diff --git a/pkg/utl/helpers.go b/pkg/utl/helpers.go index ad1cdf5..aa33e80 100644 --- a/pkg/utl/helpers.go +++ b/pkg/utl/helpers.go @@ -48,8 +48,9 @@ func checkErr(err error, handleErr func(string, int)) { } // ReadInput prints explain and repeat printing inputExplain to out and reads a string from in. -// If input is empty and defaultVal is set returns default value -// If defaultVal is not set, tries to validate input using validate +// +// If input is empty and defaultVal is set returns default value +// If defaultVal is not set, tries to validate input using validate func ReadInput(inputExplain, defaultVal string, out io.Writer, in io.Reader, validate func(string) (bool, error)) string { reader := bufio.NewReader(in) for { From 954a8806fb2dca8c19d472b36c81abaadfa28a72 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Mon, 31 Oct 2022 16:52:33 +0330 Subject: [PATCH 08/29] Add ip check before printing its changed --- pkg/paas/migration.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 62fb834..d910523 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -413,17 +413,17 @@ func failureOutput(message string) { // successOutput displays success output. func successOutput(data StepData) { - fmt.Println("\nYour IPs changed successfully") + if len(data.Source.Services) > 0 { + ipTable := tablewriter.NewWriter(os.Stdout) + ipTable.SetHeader([]string{"Old IPs", "New IPs"}) - ipTable := tablewriter.NewWriter(os.Stdout) - ipTable.SetHeader([]string{"Old IPs", "New IPs"}) + for i := 0; i < len(data.Source.Services); i++ { + ipTable.Append([]string{redColor + data.Source.Services[i].IP + resetColor, greenColor + data.Destination.Services[i].IP + resetColor}) + } - for i := 0; i < len(data.Source.Services); i++ { - ipTable.Append([]string{redColor + data.Source.Services[i].IP + resetColor, greenColor + data.Destination.Services[i].IP + resetColor}) + ipTable.Render() } - ipTable.Render() - freeSourceRoutes := make([]Route, 0) freeDestinationRoutes := make([]Route, 0) nonfreeSourceRoutes := make([]Route, 0) @@ -463,12 +463,14 @@ func successOutput(data StepData) { nonFreeRouteTable.Render() } - gatewayTable := tablewriter.NewWriter(os.Stdout) - gatewayTable.SetHeader([]string{"old gateway", "new gateway"}) + if len(freeSourceRoutes) > 0 { + gatewayTable := tablewriter.NewWriter(os.Stdout) + gatewayTable.SetHeader([]string{"old gateway", "new gateway"}) - fmt.Println("For non-free domains above, please change your gateway in your DNS provider as bellow:") - gatewayTable.Append([]string{redColor + data.Source.Gateway + resetColor, greenColor + data.Destination.Gateway + resetColor}) - gatewayTable.Render() + fmt.Println("For non-free domains above, please change your gateway in your DNS provider as bellow:") + gatewayTable.Append([]string{redColor + data.Source.Gateway + resetColor, greenColor + data.Destination.Gateway + resetColor}) + gatewayTable.Render() + } } // getZoneByName gets zone from list of active zones giving it's name. From 4ac46cc761edebc570a8166368f71f55bd05ecca Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Mon, 31 Oct 2022 16:53:48 +0330 Subject: [PATCH 09/29] Fix close of closed channel --- pkg/paas/migration.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index d910523..6265c1b 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -276,14 +276,14 @@ func migrate(request Request) error { response, err := httpGet(fmt.Sprintf(migrationEndpoint, request.Source)) if err != nil { failureOutput(err.Error()) - close(stopChannel) + stopChannel <- true return } sprintResponse(*response, tabWriter) if response.State == Completed { - close(stopChannel) + stopChannel <- true tabWriter.Flush() uiliveWriter.Stop() From c5f081b7e3e700ee0a950a032fe25202ccea7141 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Mon, 31 Oct 2022 17:49:36 +0330 Subject: [PATCH 10/29] Modify warning message --- pkg/paas/migration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 6265c1b..6665b57 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -228,7 +228,7 @@ func migrationConfirm(project, region string, in io.Reader, writer io.Writer) bo if err != nil { return false } - inputExplain := fmt.Sprintf(yellowColor+"\nWARNING: This will STOP your applications during migration process."+resetColor+"\n\nPlease enter project's name [%s] to proceed: ", project) + inputExplain := fmt.Sprintf(yellowColor+"\nWARNING: This will STOP your applications during migration process.\nYour data would still be safe and available in destination region."+resetColor+"\n\nPlease enter project's name [%s] to proceed: ", project) defaultVal := "" From 2c1e68590f0d08749ecabee86bfb8fd794bf61d5 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Mon, 31 Oct 2022 18:07:05 +0330 Subject: [PATCH 11/29] Change constant keys --- pkg/paas/migration.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 6665b57..88275a1 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -40,7 +40,7 @@ type State string const ( Pending State = "pending" - Doing State = "doing" + Running State = "running" Completed State = "completed" Failed State = "failed" ) @@ -69,11 +69,9 @@ type ZoneInfo struct { } type StepData struct { - Time time.Time `json:"time"` - Message string `json:"message"` - Percent int `json:"percent"` - Source ZoneInfo `json:"source"` - Destination ZoneInfo `json:"destination"` + Detail string `json:"detail"` + Source ZoneInfo `json:"source"` + Destination ZoneInfo `json:"destination"` } type Step struct { @@ -228,7 +226,7 @@ func migrationConfirm(project, region string, in io.Reader, writer io.Writer) bo if err != nil { return false } - inputExplain := fmt.Sprintf(yellowColor+"\nWARNING: This will STOP your applications during migration process.\nYour data would still be safe and available in destination region."+resetColor+"\n\nPlease enter project's name [%s] to proceed: ", project) + inputExplain := fmt.Sprintf(yellowColor+"\nWARNING: This will STOP your applications during migration process.\nYour data would still be safe and available in source region."+resetColor+"\n\nPlease enter project's name [%s] to proceed: ", project) defaultVal := "" @@ -291,7 +289,7 @@ func migrate(request Request) error { } if response.State == Failed { - failureOutput(response.Steps[len(response.Steps)-1].Data.Message) + failureOutput(response.Steps[len(response.Steps)-1].Data.Detail) } }) @@ -318,7 +316,7 @@ func doEvery(d time.Duration, stopChannel chan bool, f func()) { func sprintResponse(response ProgressResponse, w io.Writer) error { responseStr := fmt.Sprintln("") for _, s := range response.Steps { - responseStr += fmt.Sprintf("\t%d-%s \t\t\t%s\t%s\n", s.Order, s.Title, strings.Title(s.State), s.Data.Message) + responseStr += fmt.Sprintf("\t%d-%s \t\t\t%s\t%s\n", s.Order, s.Title, strings.Title(s.State), s.Data.Detail) } fmt.Fprintf(w, "%s", responseStr) From 74d1f9d02d3e5a0268319e86f2c35886ab4bb2fe Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Mon, 31 Oct 2022 19:00:46 +0330 Subject: [PATCH 12/29] Add error message from http request --- pkg/paas/migration.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 88275a1..5f2ad3d 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -90,6 +90,10 @@ type ProgressResponse struct { Steps []Step `json:"steps"` } +type httpMessage struct { + Message string `json:"message"` +} + // NewCmdMigrate returns new cobra commad enables user to migrate namespaces to another region on arvan servers. func NewCmdMigrate(in io.Reader, out, errout io.Writer) *cobra.Command { cmd := &cobra.Command{ @@ -250,6 +254,7 @@ func (v confirmationValidator) confirmationValidate(input string) (bool, error) // migrate sends migration request and displays response. func migrate(request Request) error { + fmt.Println("Migration is running in the background and may take a while.\nYou can optionally detach(Ctrl+C) for now and\ncontinue monitoring the process after using 'arvan paas migrate'.") postResponse, err := httpPost(fmt.Sprintf(migrationEndpoint, request.Source), request) if err != nil { failureOutput(err.Error()) @@ -353,8 +358,19 @@ func httpPost(endpoint string, payload interface{}) (*http.Response, error) { return nil, err } + b, err := io.ReadAll(httpResp.Body) + if err != nil { + log.Fatalln(err) + } + + var r httpMessage + err = json.Unmarshal(b, &r) + if err != nil { + return nil, err + } + if httpResp.StatusCode != http.StatusOK && httpResp.StatusCode != http.StatusFound { - return nil, errors.New("server error. try again later") + return nil, errors.New(r.Message) } return httpResp, nil From c6d85a6111b9be0911a876cd85c78a36e953b9bc Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Mon, 31 Oct 2022 19:09:24 +0330 Subject: [PATCH 13/29] Modify error messages --- pkg/paas/migration.go | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 5f2ad3d..54d5c2a 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -87,13 +87,10 @@ type ProgressResponse struct { Source string `json:"source"` Destination string `json:"destination"` Namespace string `json:"namespace"` + Message string `json:"message"` Steps []Step `json:"steps"` } -type httpMessage struct { - Message string `json:"message"` -} - // NewCmdMigrate returns new cobra commad enables user to migrate namespaces to another region on arvan servers. func NewCmdMigrate(in io.Reader, out, errout io.Writer) *cobra.Command { cmd := &cobra.Command{ @@ -255,16 +252,12 @@ func (v confirmationValidator) confirmationValidate(input string) (bool, error) // migrate sends migration request and displays response. func migrate(request Request) error { fmt.Println("Migration is running in the background and may take a while.\nYou can optionally detach(Ctrl+C) for now and\ncontinue monitoring the process after using 'arvan paas migrate'.") - postResponse, err := httpPost(fmt.Sprintf(migrationEndpoint, request.Source), request) + err := httpPost(fmt.Sprintf(migrationEndpoint, request.Source), request) if err != nil { failureOutput(err.Error()) return err } - if postResponse.StatusCode != http.StatusOK && postResponse.StatusCode != http.StatusFound { - failureOutput(fmt.Sprint(postResponse.StatusCode)) - return errors.New(fmt.Sprint(postResponse.StatusCode)) - } // init writer to update lines uiliveWriter := uilive.New() uiliveWriter.Start() @@ -331,20 +324,20 @@ func sprintResponse(response ProgressResponse, w io.Writer) error { } // httpPost sends POST request to inserted url. -func httpPost(endpoint string, payload interface{}) (*http.Response, error) { +func httpPost(endpoint string, payload interface{}) error { requestBody, err := json.Marshal(payload) if err != nil { - return nil, err + return err } arvanConfig := config.GetConfigInfo() arvanURL, err := url.Parse(arvanConfig.GetServer()) if err != nil { - return nil, fmt.Errorf("invalid config") + return fmt.Errorf("invalid config") } httpReq, err := http.NewRequest(http.MethodPost, arvanURL.Scheme+"://"+arvanURL.Host+endpoint, bytes.NewBuffer(requestBody)) if err != nil { - return nil, err + return err } apikey := arvanConfig.GetApiKey() if apikey != "" { @@ -355,25 +348,25 @@ func httpPost(endpoint string, payload interface{}) (*http.Response, error) { httpReq.Header.Add("User-Agent", rest.DefaultKubernetesUserAgent()) httpResp, err := http.DefaultClient.Do(httpReq) if err != nil { - return nil, err + return err } - b, err := io.ReadAll(httpResp.Body) + responseBody, err := io.ReadAll(httpResp.Body) if err != nil { log.Fatalln(err) } - var r httpMessage - err = json.Unmarshal(b, &r) + var response ProgressResponse + err = json.Unmarshal(responseBody, &response) if err != nil { - return nil, err + return err } if httpResp.StatusCode != http.StatusOK && httpResp.StatusCode != http.StatusFound { - return nil, errors.New(r.Message) + return errors.New(response.Message) } - return httpResp, nil + return nil } // httpGet sends GET request to inserted url. From 67ec3cb9f361c269fd635bf626e3bc4732f8f2b3 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Mon, 31 Oct 2022 19:13:41 +0330 Subject: [PATCH 14/29] Change routes to domains --- pkg/paas/migration.go | 52 +++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 54d5c2a..f9ceb21 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -56,7 +56,7 @@ type Service struct { IP string `json:"ip"` } -type Route struct { +type Domain struct { Name string `json:"name"` Host string `json:"host"` IsFree bool `json:"is_free"` @@ -64,7 +64,7 @@ type Route struct { type ZoneInfo struct { Services []Service `json:"services"` - Routes []Route `json:"routes"` + Domains []Domain `json:"domains"` Gateway string `json:"gateway"` } @@ -431,46 +431,46 @@ func successOutput(data StepData) { ipTable.Render() } - freeSourceRoutes := make([]Route, 0) - freeDestinationRoutes := make([]Route, 0) - nonfreeSourceRoutes := make([]Route, 0) - nonfreeDestinationRoutes := make([]Route, 0) + freeSourceDomains := make([]Domain, 0) + freeDestinationDomains := make([]Domain, 0) + nonfreeSourceDomains := make([]Domain, 0) + nonfreeDestinationDomains := make([]Domain, 0) - for i := 0; i < len(data.Source.Routes); i++ { - if data.Source.Routes[i].IsFree { - freeSourceRoutes = append(freeSourceRoutes, data.Source.Routes[i]) - freeDestinationRoutes = append(freeDestinationRoutes, data.Destination.Routes[i]) + for i := 0; i < len(data.Source.Domains); i++ { + if data.Source.Domains[i].IsFree { + freeSourceDomains = append(freeSourceDomains, data.Source.Domains[i]) + freeDestinationDomains = append(freeDestinationDomains, data.Destination.Domains[i]) } else { - nonfreeSourceRoutes = append(nonfreeSourceRoutes, data.Source.Routes[i]) - nonfreeDestinationRoutes = append(nonfreeDestinationRoutes, data.Destination.Routes[i]) + nonfreeSourceDomains = append(nonfreeSourceDomains, data.Source.Domains[i]) + nonfreeDestinationDomains = append(nonfreeDestinationDomains, data.Destination.Domains[i]) } } - if len(freeSourceRoutes) > 0 { - fmt.Println("Your free routes changed successfully:") + if len(freeSourceDomains) > 0 { + fmt.Println("Your free domains changed successfully:") - freeRouteTable := tablewriter.NewWriter(os.Stdout) - freeRouteTable.SetHeader([]string{"old free routes", "new free routes"}) + freeDomainTable := tablewriter.NewWriter(os.Stdout) + freeDomainTable.SetHeader([]string{"old free domains", "new free domains"}) - for i := 0; i < len(freeSourceRoutes); i++ { - freeRouteTable.Append([]string{redColor + freeSourceRoutes[i].Host + resetColor, greenColor + freeDestinationRoutes[i].Host + resetColor}) + for i := 0; i < len(freeSourceDomains); i++ { + freeDomainTable.Append([]string{redColor + freeSourceDomains[i].Host + resetColor, greenColor + freeDestinationDomains[i].Host + resetColor}) } - freeRouteTable.Render() + freeDomainTable.Render() } - if len(nonfreeSourceRoutes) > 0 { - nonFreeRouteTable := tablewriter.NewWriter(os.Stdout) - nonFreeRouteTable.SetHeader([]string{"non-free routes"}) + if len(nonfreeSourceDomains) > 0 { + nonFreeDomainTable := tablewriter.NewWriter(os.Stdout) + nonFreeDomainTable.SetHeader([]string{"non-free domains"}) - for i := 0; i < len(nonfreeSourceRoutes); i++ { - nonFreeRouteTable.Append([]string{yellowColor + nonfreeDestinationRoutes[i].Host + resetColor}) + for i := 0; i < len(nonfreeSourceDomains); i++ { + nonFreeDomainTable.Append([]string{yellowColor + nonfreeDestinationDomains[i].Host + resetColor}) } - nonFreeRouteTable.Render() + nonFreeDomainTable.Render() } - if len(freeSourceRoutes) > 0 { + if len(freeSourceDomains) > 0 { gatewayTable := tablewriter.NewWriter(os.Stdout) gatewayTable.SetHeader([]string{"old gateway", "new gateway"}) From cf703f945f33c5fd490617b6526b9cde7c1fe40e Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Mon, 31 Oct 2022 22:22:02 +0330 Subject: [PATCH 15/29] Add migration inquiry --- pkg/paas/migration.go | 214 ++++++++++++++++++++++++++---------------- 1 file changed, 134 insertions(+), 80 deletions(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 62fb834..c2cfdfc 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -56,7 +56,7 @@ type Service struct { IP string `json:"ip"` } -type Route struct { +type Domain struct { Name string `json:"name"` Host string `json:"host"` IsFree bool `json:"is_free"` @@ -64,7 +64,7 @@ type Route struct { type ZoneInfo struct { Services []Service `json:"services"` - Routes []Route `json:"routes"` + Domains []Domain `json:"domains"` Gateway string `json:"gateway"` } @@ -89,6 +89,8 @@ type ProgressResponse struct { Source string `json:"source"` Destination string `json:"destination"` Namespace string `json:"namespace"` + Message string `json:"message"` + StatusCode int `json:"StatusCode"` Steps []Step `json:"steps"` } @@ -102,39 +104,70 @@ func NewCmdMigrate(in io.Reader, out, errout io.Writer) *cobra.Command { explainOut := term.NewResponsiveWriter(out) c.SetOutput(explainOut) - project, err := getSelectedProject(in, explainOut) + currentRegionName := getCurrentRegion() + + request := Request{ + Source: currentRegionName, + } + + response, err := httpGet(fmt.Sprintf(migrationEndpoint, request.Source)) if err != nil { failureOutput(err.Error()) return } - currentRegionName := getCurrentRegion() + fmt.Printf(">>>>> %d _ %s _ %s\n", response.StatusCode, response.Message, response.State) + + if response.StatusCode == http.StatusBadRequest { + failureOutput(response.Message) - if currentRegionName == bamdad { - log.Printf("migration from region %s is not possible now\nplease first switch your region using command:\n\n \tarvan paas region\n\n", currentRegionName) return } - destinationRegion, err := getZoneByName(bamdad) - utl.CheckErr(err) - - if currentRegionName == getRegionFromEndpoint(destinationRegion.Endpoint) { - log.Printf("can not migrate to this region") - return + if response.StatusCode == http.StatusOK { + if response.State == Completed || response.State == Failed { + migrate(request) + reMigrationConfirmed := reMigrationConfirm(in, explainOut) + if !reMigrationConfirmed { + return + } + } } - confirmed := migrationConfirm(project, getRegionFromEndpoint(destinationRegion.Endpoint), in, explainOut) - if !confirmed { - return + if response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusOK { + project, err := getSelectedProject(in, explainOut) + if err != nil { + failureOutput(err.Error()) + + return + } + + destinationRegion, err := getZoneByName(bamdad) + utl.CheckErr(err) + + if currentRegionName == getRegionFromEndpoint(destinationRegion.Endpoint) { + log.Printf("can not migrate to this region") + return + } + + confirmed := migrationConfirm(project, getRegionFromEndpoint(destinationRegion.Endpoint), in, explainOut) + if !confirmed { + return + } + + request.Namespace = project + request.Destination = fmt.Sprintf("%s-%s", destinationRegion.RegionName, destinationRegion.Name) + } - requset := Request{ - Namespace: project, - Source: currentRegionName, - Destination: fmt.Sprintf("%s-%s", destinationRegion.RegionName, destinationRegion.Name), + if response.StatusCode != http.StatusFound { + err := httpPost(fmt.Sprintf(migrationEndpoint, request.Source), request) + if err != nil { + failureOutput(err.Error()) + } } - err = migrate(requset) + err = migrate(request) if err != nil { log.Println(err) } @@ -144,6 +177,22 @@ func NewCmdMigrate(in io.Reader, out, errout io.Writer) *cobra.Command { return cmd } +func reMigrationConfirm(in io.Reader, writer io.Writer) bool { + inputExplain := "Do you want to run a new migration?[y/N]: " + + defaultVal := "N" + + value := utl.ReadInput(inputExplain, defaultVal, writer, in, confirmationValidate) + return value == "y" +} + +func confirmationValidate(input string) (bool, error) { + if input != "y" && input != "N" { + return false, fmt.Errorf("enter a valid answer 'y' for \"yes\" or 'N' for \"no\"") + } + return true, nil +} + // getSelectedProject gets intending namespace to migrate. func getSelectedProject(in io.Reader, writer io.Writer) (string, error) { projects, err := projectList() @@ -228,7 +277,7 @@ func migrationConfirm(project, region string, in io.Reader, writer io.Writer) bo if err != nil { return false } - inputExplain := fmt.Sprintf(yellowColor+"\nWARNING: This will STOP your applications during migration process."+resetColor+"\n\nPlease enter project's name [%s] to proceed: ", project) + inputExplain := fmt.Sprintf(yellowColor+"\nWARNING: This will STOP your applications during migration process.\nYour data would be still safe and available migration process.\nMigration is running in the background and may take a while.\nYou can optionally detach(Ctrl+C) for now and\ncontinue monitoring the process after using 'arvan paas migrate'."+resetColor+"\n\nPlease enter project's name [%s] to proceed: ", project) defaultVal := "" @@ -252,16 +301,6 @@ func (v confirmationValidator) confirmationValidate(input string) (bool, error) // migrate sends migration request and displays response. func migrate(request Request) error { - postResponse, err := httpPost(fmt.Sprintf(migrationEndpoint, request.Source), request) - if err != nil { - failureOutput(err.Error()) - return err - } - - if postResponse.StatusCode != http.StatusOK && postResponse.StatusCode != http.StatusFound { - failureOutput(fmt.Sprint(postResponse.StatusCode)) - return errors.New(fmt.Sprint(postResponse.StatusCode)) - } // init writer to update lines uiliveWriter := uilive.New() uiliveWriter.Start() @@ -276,14 +315,14 @@ func migrate(request Request) error { response, err := httpGet(fmt.Sprintf(migrationEndpoint, request.Source)) if err != nil { failureOutput(err.Error()) - close(stopChannel) + stopChannel <- true return } sprintResponse(*response, tabWriter) if response.State == Completed { - close(stopChannel) + stopChannel <- true tabWriter.Flush() uiliveWriter.Stop() @@ -328,20 +367,20 @@ func sprintResponse(response ProgressResponse, w io.Writer) error { } // httpPost sends POST request to inserted url. -func httpPost(endpoint string, payload interface{}) (*http.Response, error) { +func httpPost(endpoint string, payload interface{}) error { requestBody, err := json.Marshal(payload) if err != nil { - return nil, err + return err } arvanConfig := config.GetConfigInfo() arvanURL, err := url.Parse(arvanConfig.GetServer()) if err != nil { - return nil, fmt.Errorf("invalid config") + return fmt.Errorf("invalid config") } httpReq, err := http.NewRequest(http.MethodPost, arvanURL.Scheme+"://"+arvanURL.Host+endpoint, bytes.NewBuffer(requestBody)) if err != nil { - return nil, err + return err } apikey := arvanConfig.GetApiKey() if apikey != "" { @@ -352,14 +391,28 @@ func httpPost(endpoint string, payload interface{}) (*http.Response, error) { httpReq.Header.Add("User-Agent", rest.DefaultKubernetesUserAgent()) httpResp, err := http.DefaultClient.Do(httpReq) if err != nil { - return nil, err + return err + } + + // read body + defer httpResp.Body.Close() + responseBody, err := io.ReadAll(httpResp.Body) + if err != nil { + return err + } + + // parse response + var response ProgressResponse + err = json.Unmarshal(responseBody, &response) + if err != nil { + return err } if httpResp.StatusCode != http.StatusOK && httpResp.StatusCode != http.StatusFound { - return nil, errors.New("server error. try again later") + return errors.New(response.Message) } - return httpResp, nil + return nil } // httpGet sends GET request to inserted url. @@ -386,10 +439,6 @@ func httpGet(endpoint string) (*ProgressResponse, error) { return nil, err } - if httpResp.StatusCode != http.StatusOK { - return nil, errors.New("server error. try again later") - } - // read body defer httpResp.Body.Close() responseBody, err := io.ReadAll(httpResp.Body) @@ -403,6 +452,9 @@ func httpGet(endpoint string) (*ProgressResponse, error) { if err != nil { return nil, err } + + response.StatusCode = httpResp.StatusCode + return &response, nil } @@ -413,62 +465,64 @@ func failureOutput(message string) { // successOutput displays success output. func successOutput(data StepData) { - fmt.Println("\nYour IPs changed successfully") + fmt.Println("\nYour namespaces successfully migrated!") - ipTable := tablewriter.NewWriter(os.Stdout) - ipTable.SetHeader([]string{"Old IPs", "New IPs"}) + if len(data.Source.Services) > 0 { + ipTable := tablewriter.NewWriter(os.Stdout) + ipTable.SetHeader([]string{"Old IPs", "New IPs"}) - for i := 0; i < len(data.Source.Services); i++ { - ipTable.Append([]string{redColor + data.Source.Services[i].IP + resetColor, greenColor + data.Destination.Services[i].IP + resetColor}) - } + for i := 0; i < len(data.Source.Services); i++ { + ipTable.Append([]string{redColor + data.Source.Services[i].IP + resetColor, greenColor + data.Destination.Services[i].IP + resetColor}) + } - ipTable.Render() + ipTable.Render() + } - freeSourceRoutes := make([]Route, 0) - freeDestinationRoutes := make([]Route, 0) - nonfreeSourceRoutes := make([]Route, 0) - nonfreeDestinationRoutes := make([]Route, 0) + freeSourceDomains := make([]Domain, 0) + freeDestinationDomains := make([]Domain, 0) + nonfreeSourceDomains := make([]Domain, 0) + nonfreeDestinationDomains := make([]Domain, 0) - for i := 0; i < len(data.Source.Routes); i++ { - if data.Source.Routes[i].IsFree { - freeSourceRoutes = append(freeSourceRoutes, data.Source.Routes[i]) - freeDestinationRoutes = append(freeDestinationRoutes, data.Destination.Routes[i]) + for i := 0; i < len(data.Source.Domains); i++ { + if data.Source.Domains[i].IsFree { + freeSourceDomains = append(freeSourceDomains, data.Source.Domains[i]) + freeDestinationDomains = append(freeDestinationDomains, data.Destination.Domains[i]) } else { - nonfreeSourceRoutes = append(nonfreeSourceRoutes, data.Source.Routes[i]) - nonfreeDestinationRoutes = append(nonfreeDestinationRoutes, data.Destination.Routes[i]) + nonfreeSourceDomains = append(nonfreeSourceDomains, data.Source.Domains[i]) + nonfreeDestinationDomains = append(nonfreeDestinationDomains, data.Destination.Domains[i]) } } - if len(freeSourceRoutes) > 0 { - fmt.Println("Your free routes changed successfully:") + if len(freeSourceDomains) > 0 { + fmt.Println("Your free domains changed successfully:") - freeRouteTable := tablewriter.NewWriter(os.Stdout) - freeRouteTable.SetHeader([]string{"old free routes", "new free routes"}) + freeDomainTable := tablewriter.NewWriter(os.Stdout) + freeDomainTable.SetHeader([]string{"old free domains", "new free domains"}) - for i := 0; i < len(freeSourceRoutes); i++ { - freeRouteTable.Append([]string{redColor + freeSourceRoutes[i].Host + resetColor, greenColor + freeDestinationRoutes[i].Host + resetColor}) + for i := 0; i < len(freeSourceDomains); i++ { + freeDomainTable.Append([]string{redColor + freeSourceDomains[i].Host + resetColor, greenColor + freeDestinationDomains[i].Host + resetColor}) } - freeRouteTable.Render() + freeDomainTable.Render() } - if len(nonfreeSourceRoutes) > 0 { - nonFreeRouteTable := tablewriter.NewWriter(os.Stdout) - nonFreeRouteTable.SetHeader([]string{"non-free routes"}) + if len(nonfreeSourceDomains) > 0 { + nonFreeDomainTable := tablewriter.NewWriter(os.Stdout) + nonFreeDomainTable.SetHeader([]string{"non-free domains"}) - for i := 0; i < len(nonfreeSourceRoutes); i++ { - nonFreeRouteTable.Append([]string{yellowColor + nonfreeDestinationRoutes[i].Host + resetColor}) + for i := 0; i < len(nonfreeSourceDomains); i++ { + nonFreeDomainTable.Append([]string{yellowColor + nonfreeDestinationDomains[i].Host + resetColor}) } - nonFreeRouteTable.Render() - } + nonFreeDomainTable.Render() - gatewayTable := tablewriter.NewWriter(os.Stdout) - gatewayTable.SetHeader([]string{"old gateway", "new gateway"}) + gatewayTable := tablewriter.NewWriter(os.Stdout) + gatewayTable.SetHeader([]string{"old gateway", "new gateway"}) - fmt.Println("For non-free domains above, please change your gateway in your DNS provider as bellow:") - gatewayTable.Append([]string{redColor + data.Source.Gateway + resetColor, greenColor + data.Destination.Gateway + resetColor}) - gatewayTable.Render() + fmt.Println("For non-free domains above, please change your gateway in your DNS provider as bellow:") + gatewayTable.Append([]string{redColor + data.Source.Gateway + resetColor, greenColor + data.Destination.Gateway + resetColor}) + gatewayTable.Render() + } } // getZoneByName gets zone from list of active zones giving it's name. From f408ef6ae35d570c07e2285c3205d26c96616dcc Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Mon, 31 Oct 2022 22:32:54 +0330 Subject: [PATCH 16/29] Remove http post --- pkg/paas/migration.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index f9a5bcb..c3acf97 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -297,12 +297,6 @@ func (v confirmationValidator) confirmationValidate(input string) (bool, error) // migrate sends migration request and displays response. func migrate(request Request) error { - err := httpPost(fmt.Sprintf(migrationEndpoint, request.Source), request) - if err != nil { - failureOutput(err.Error()) - return err - } - // init writer to update lines uiliveWriter := uilive.New() uiliveWriter.Start() From 3fc0e120df7e99d018e6982fda98c91a0e5103be Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Mon, 31 Oct 2022 22:35:09 +0330 Subject: [PATCH 17/29] Handle error message --- pkg/paas/migration.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index c3acf97..203053e 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -160,12 +160,13 @@ func NewCmdMigrate(in io.Reader, out, errout io.Writer) *cobra.Command { err := httpPost(fmt.Sprintf(migrationEndpoint, request.Source), request) if err != nil { failureOutput(err.Error()) + return } } err = migrate(request) if err != nil { - log.Println(err) + failureOutput(err.Error()) } }, } From 1c3ff7458ab2cf3a8d8e7091f54d35467b9a0c98 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Tue, 1 Nov 2022 12:33:43 +0330 Subject: [PATCH 18/29] Remove newline from warning message --- pkg/paas/migration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 203053e..0f7eacc 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -274,7 +274,7 @@ func migrationConfirm(project, region string, in io.Reader, writer io.Writer) bo if err != nil { return false } - inputExplain := fmt.Sprintf(yellowColor+"\nWARNING: This will STOP your applications during migration process.\nYour data would still be safe and available in source region.\nMigration is running in the background and may take a while.\nYou can optionally detach(Ctrl+C) for now and\ncontinue monitoring the process after using 'arvan paas migrate'."+resetColor+"\n\nPlease enter project's name [%s] to proceed: ", project) + inputExplain := fmt.Sprintf(yellowColor+"\nWARNING:\nThis will STOP your applications during migration process. Your data would still be safe and available in source region. Migration is running in the background and may take a while. You can optionally detach(Ctrl+C) for now and continue monitoring the process after using 'arvan paas migrate'."+resetColor+"\n\nPlease enter project's name [%s] to proceed: ", project) defaultVal := "" From 686e12b7354ac6dab47c9067f41b1ecf1b371b78 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Tue, 1 Nov 2022 12:48:39 +0330 Subject: [PATCH 19/29] Modify messages --- pkg/paas/migration.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 0f7eacc..417e5ea 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -122,6 +122,7 @@ func NewCmdMigrate(in io.Reader, out, errout io.Writer) *cobra.Command { if response.StatusCode == http.StatusOK { if response.State == Completed || response.State == Failed { + fmt.Println("\nLast migration report is as bellow:") migrate(request) reMigrationConfirmed := reMigrationConfirm(in, explainOut) if !reMigrationConfirmed { @@ -274,7 +275,7 @@ func migrationConfirm(project, region string, in io.Reader, writer io.Writer) bo if err != nil { return false } - inputExplain := fmt.Sprintf(yellowColor+"\nWARNING:\nThis will STOP your applications during migration process. Your data would still be safe and available in source region. Migration is running in the background and may take a while. You can optionally detach(Ctrl+C) for now and continue monitoring the process after using 'arvan paas migrate'."+resetColor+"\n\nPlease enter project's name [%s] to proceed: ", project) + inputExplain := fmt.Sprintf(yellowColor+"\nWARNING:\nThis will STOP applications during migration process. Your data would still be safe and available in source region. Migration is running in the background and may take a while. You can optionally detach(Ctrl+C) for now and continue monitoring the process after using 'arvan paas migrate'."+resetColor+"\n\nPlease enter project's name [%s] to proceed: ", project) defaultVal := "" @@ -462,7 +463,7 @@ func failureOutput(message string) { // successOutput displays success output. func successOutput(data StepData) { - fmt.Println("\nYour namespaces successfully migrated!") + fmt.Println("\nNamespaces successfully migrated!") if len(data.Source.Services) > 0 { ipTable := tablewriter.NewWriter(os.Stdout) @@ -491,7 +492,7 @@ func successOutput(data StepData) { } if len(freeSourceDomains) > 0 { - fmt.Println("Your free domains changed successfully:") + fmt.Println("Free domains changed successfully:") freeDomainTable := tablewriter.NewWriter(os.Stdout) freeDomainTable.SetHeader([]string{"old free domains", "new free domains"}) @@ -518,7 +519,7 @@ func successOutput(data StepData) { gatewayTable := tablewriter.NewWriter(os.Stdout) gatewayTable.SetHeader([]string{"old gateway", "new gateway"}) - fmt.Println("For non-free domains above, please change your gateway in your DNS provider as bellow:") + fmt.Println("For non-free domains above, please change gateway in DNS provider as bellow:") gatewayTable.Append([]string{redColor + data.Source.Gateway + resetColor, greenColor + data.Destination.Gateway + resetColor}) gatewayTable.Render() } From e4975285406fb3892a046f75c5a25ef4af3afa91 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Tue, 1 Nov 2022 14:19:38 +0330 Subject: [PATCH 20/29] Fix failure check to stop loop --- pkg/paas/migration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 417e5ea..a53ede2 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -319,7 +319,7 @@ func migrate(request Request) error { sprintResponse(*response, tabWriter) - if response.State == Completed { + if response.State == Completed || response.State == Failed { stopChannel <- true tabWriter.Flush() uiliveWriter.Stop() From 9f43637375a8d6ddab7af790395e0dc3ce9bec48 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Tue, 1 Nov 2022 14:20:24 +0330 Subject: [PATCH 21/29] Fix failure check to stop loop --- pkg/paas/migration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index a53ede2..417e5ea 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -319,7 +319,7 @@ func migrate(request Request) error { sprintResponse(*response, tabWriter) - if response.State == Completed || response.State == Failed { + if response.State == Completed { stopChannel <- true tabWriter.Flush() uiliveWriter.Stop() From 5f6c43f4fadd359a6c9fff8e13950342bea467ca Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Tue, 1 Nov 2022 14:21:10 +0330 Subject: [PATCH 22/29] Fix failure check to stop loop --- pkg/paas/migration.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 417e5ea..3dd1303 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -328,6 +328,10 @@ func migrate(request Request) error { } if response.State == Failed { + stopChannel <- true + tabWriter.Flush() + uiliveWriter.Stop() + failureOutput(response.Steps[len(response.Steps)-1].Data.Detail) } }) From 96609bfc8385870f2de1037339e685d24a8c0120 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Tue, 1 Nov 2022 15:46:32 +0330 Subject: [PATCH 23/29] Changed failure message color --- pkg/paas/migration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 3dd1303..bc04abd 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -462,7 +462,7 @@ func httpGet(endpoint string) (*ProgressResponse, error) { // failureOutput displays failure output. func failureOutput(message string) { - fmt.Println("failed:", message) + fmt.Println(redColor + "\nFAILED: " + message + resetColor) } // successOutput displays success output. From 19bddff6c3ef3709993d61d33b4df01eb83eebae Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Tue, 1 Nov 2022 17:55:43 +0330 Subject: [PATCH 24/29] Remove detail on failure --- pkg/paas/migration.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index bc04abd..87e81a0 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -358,8 +358,14 @@ func doEvery(d time.Duration, stopChannel chan bool, f func()) { // sprintResponse displays steps of migration. func sprintResponse(response ProgressResponse, w io.Writer) error { responseStr := fmt.Sprintln("") + var detail string + for _, s := range response.Steps { - responseStr += fmt.Sprintf("\t%d-%s \t\t\t%s\t%s\n", s.Order, s.Title, strings.Title(s.State), s.Data.Detail) + if response.State != Failed { + detail = s.Data.Detail + } + + responseStr += fmt.Sprintf("\t%d-%s \t\t\t%s\t%s\n", s.Order, s.Title, strings.Title(s.State), detail) } fmt.Fprintf(w, "%s", responseStr) From 3b3bf12887999f6382ccb3dd34f78eb1e04d28a9 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Wed, 2 Nov 2022 11:01:40 +0330 Subject: [PATCH 25/29] Remove order from steps --- pkg/paas/migration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 87e81a0..e61fa4e 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -365,7 +365,7 @@ func sprintResponse(response ProgressResponse, w io.Writer) error { detail = s.Data.Detail } - responseStr += fmt.Sprintf("\t%d-%s \t\t\t%s\t%s\n", s.Order, s.Title, strings.Title(s.State), detail) + responseStr += fmt.Sprintf("\t%s \t\t\t%s\t%s\n", s.Title, strings.Title(s.State), detail) } fmt.Fprintf(w, "%s", responseStr) From 830ce86bedda4dc69c52e2cb88158f3efc482b1e Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Wed, 2 Nov 2022 12:23:39 +0330 Subject: [PATCH 26/29] Add namespace to report --- pkg/paas/migration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index e61fa4e..423e6d6 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -122,7 +122,7 @@ func NewCmdMigrate(in io.Reader, out, errout io.Writer) *cobra.Command { if response.StatusCode == http.StatusOK { if response.State == Completed || response.State == Failed { - fmt.Println("\nLast migration report is as bellow:") + fmt.Printf("\nLast migration report of projetct \"%s\" is as bellow:\n", response.Namespace) migrate(request) reMigrationConfirmed := reMigrationConfirm(in, explainOut) if !reMigrationConfirmed { From 1b8bfab447b3c53ed73f4c0bf55b1fb61f20c1e4 Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Wed, 2 Nov 2022 14:38:58 +0330 Subject: [PATCH 27/29] Add running migration display --- pkg/paas/migration.go | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 423e6d6..ddbdf6e 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -120,18 +120,16 @@ func NewCmdMigrate(in io.Reader, out, errout io.Writer) *cobra.Command { return } - if response.StatusCode == http.StatusOK { - if response.State == Completed || response.State == Failed { - fmt.Printf("\nLast migration report of projetct \"%s\" is as bellow:\n", response.Namespace) - migrate(request) - reMigrationConfirmed := reMigrationConfirm(in, explainOut) - if !reMigrationConfirmed { - return - } + if response.StatusCode == http.StatusOK && (response.State == Completed || response.State == Failed) { + fmt.Printf("\nLast migration report of projetct \"%s\" is as bellow:\n", response.Namespace) + migrate(request) + reMigrationConfirmed := reMigrationConfirm(in, explainOut) + if !reMigrationConfirmed { + return } } - if response.StatusCode == http.StatusNotFound || response.StatusCode == http.StatusOK { + if response.State == Completed || response.State == Failed { project, err := getSelectedProject(in, explainOut) if err != nil { failureOutput(err.Error()) @@ -155,10 +153,7 @@ func NewCmdMigrate(in io.Reader, out, errout io.Writer) *cobra.Command { request.Namespace = project request.Destination = fmt.Sprintf("%s-%s", destinationRegion.RegionName, destinationRegion.Name) - } - - if response.StatusCode != http.StatusFound { - err := httpPost(fmt.Sprintf(migrationEndpoint, request.Source), request) + err = httpPost(fmt.Sprintf(migrationEndpoint, request.Source), request) if err != nil { failureOutput(err.Error()) return From 7fe81c7433efc768c82e3ac2943cd0bcf0ae30da Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Wed, 2 Nov 2022 14:46:16 +0330 Subject: [PATCH 28/29] Add lost connection fail message --- pkg/paas/migration.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index ddbdf6e..1b07318 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -439,6 +439,7 @@ func httpGet(endpoint string) (*ProgressResponse, error) { httpReq.Header.Add("User-Agent", rest.DefaultKubernetesUserAgent()) httpResp, err := http.DefaultClient.Do(httpReq) if err != nil { + failureOutput("Migration is running in the background. You can continue monitoring the process using 'arvan paas migrate'.") return nil, err } From f06c5c3b765573bf078de2790e27f20122bd0c1c Mon Sep 17 00:00:00 2001 From: Payam Qorbanpour Date: Wed, 2 Nov 2022 15:02:40 +0330 Subject: [PATCH 29/29] Add function comments --- pkg/paas/migration.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/paas/migration.go b/pkg/paas/migration.go index 1b07318..6c8ec08 100644 --- a/pkg/paas/migration.go +++ b/pkg/paas/migration.go @@ -170,6 +170,7 @@ func NewCmdMigrate(in io.Reader, out, errout io.Writer) *cobra.Command { return cmd } +// reMigrationConfirm makes sure that user enters yes/no correctly. func reMigrationConfirm(in io.Reader, writer io.Writer) bool { inputExplain := "Do you want to run a new migration?[y/N]: " @@ -179,6 +180,7 @@ func reMigrationConfirm(in io.Reader, writer io.Writer) bool { return value == "y" } +// confirmationValidate checks yes/no entry func confirmationValidate(input string) (bool, error) { if input != "y" && input != "N" { return false, fmt.Errorf("enter a valid answer 'y' for \"yes\" or 'N' for \"no\"")