From 1bf40ddb077fac60bc372af22e0effca178cd882 Mon Sep 17 00:00:00 2001 From: David Siaw Date: Wed, 17 Mar 2021 15:37:51 +0900 Subject: [PATCH] refactor endpoint operations --- cmd/endpoint.go | 301 ++++++++++----------------- operations/endpoint_operation.go | 171 +++++++++++++++ operations/operation.go | 5 +- operations/profile_operation_test.go | 14 +- 4 files changed, 290 insertions(+), 201 deletions(-) create mode 100644 operations/endpoint_operation.go diff --git a/cmd/endpoint.go b/cmd/endpoint.go index 5ce81d9..4e61e4f 100644 --- a/cmd/endpoint.go +++ b/cmd/endpoint.go @@ -1,235 +1,150 @@ package cmd import ( - "bytes" - "encoding/json" - "fmt" - "os" - "github.com/degica/barcelona-cli/api" + "github.com/degica/barcelona-cli/operations" "github.com/degica/barcelona-cli/utils" - "github.com/olekukonko/tablewriter" "github.com/urfave/cli" ) -var EndpointCommand = cli.Command{ - Name: "endpoint", - Usage: "Endpoint operations", - Subcommands: []cli.Command{ - { +func getEndpointSubcommands() []cli.Command { + districtFlag := cli.StringFlag{ + Name: "district, d", + Value: "default", + Usage: "AWS region", + } + + publicFlag := cli.BoolFlag{ + Name: "public", + Usage: "Public facing endpoint", + } + + certificateFlag := cli.StringFlag{ + Name: "certificate-arn", + Usage: "ACM Certificate ARN", + } + + sslPolicyFlag := cli.StringFlag{ + Name: "ssl-policy", + Usage: "HTTPS SSL Policy", + } + + noConfirmFlag := cli.BoolFlag{ + Name: "no-confirmation", + } + + endpointSubcommands := map[operations.OperationType]cli.Command{ + operations.Create: cli.Command{ Name: "create", Usage: "Create a new endpoint", ArgsUsage: "ENDPOINT_NAME", Flags: []cli.Flag{ - cli.StringFlag{ - Name: "district, d", - Value: "default", - Usage: "AWS region", - }, - cli.BoolFlag{ - Name: "public", - Usage: "Public facing endpoint", - }, - cli.StringFlag{ - Name: "certificate-arn", - Usage: "ACM Certificate ARN", - }, - cli.StringFlag{ - Name: "ssl-policy", - Usage: "HTTPS SSL Policy", - }, + districtFlag, + publicFlag, + certificateFlag, + sslPolicyFlag, }, - Action: func(c *cli.Context) error { - endpointName := c.Args().Get(0) - if len(endpointName) == 0 { - return cli.NewExitError("endpoint name is required", 1) - } - - if len(c.Args()) != 1 { - return cli.NewExitError("please place options and flags before the endpoint name.", 1) - } - - public := c.Bool("public") - request := api.Endpoint{ - Name: endpointName, - Public: &public, - CertificateID: c.String("certificate-arn"), - SslPolicy: c.String("ssl-policy"), - } - - b, err := json.Marshal(&request) - if err != nil { - return cli.NewExitError(err.Error(), 1) - } + }, - resp, err := api.DefaultClient.Request("POST", fmt.Sprintf("/districts/%s/endpoints", c.String("district")), bytes.NewBuffer(b)) - if err != nil { - return cli.NewExitError(err.Error(), 1) - } - var eResp api.EndpointResponse - err = json.Unmarshal(resp, &eResp) - if err != nil { - return cli.NewExitError(err.Error(), 1) - } - printEndpoint(eResp.Endpoint) + operations.Update: cli.Command{ + Name: "update", + Usage: "Update an endpoint", + ArgsUsage: "ENDPOINT_NAME", + Flags: []cli.Flag{ + districtFlag, + certificateFlag, + sslPolicyFlag, + }, + }, - return nil + operations.Delete: cli.Command{ + Name: "delete", + Usage: "Delete an endpoint", + ArgsUsage: "ENDPOINT_NAME", + Flags: []cli.Flag{ + districtFlag, + noConfirmFlag, }, }, - { + + operations.Show: cli.Command{ Name: "show", Usage: "Show endpoint information", ArgsUsage: "ENDPOINT_NAME", Flags: []cli.Flag{ - cli.StringFlag{ - Name: "district, d", - Value: "default", - Usage: "District name", - }, - }, - Action: func(c *cli.Context) error { - endpointName := c.Args().Get(0) - if len(endpointName) == 0 { - return cli.NewExitError("endpoint name is required", 1) - } - - resp, err := api.DefaultClient.Request("GET", fmt.Sprintf("/districts/%s/endpoints/%s", c.String("district"), endpointName), nil) - if err != nil { - return cli.NewExitError(err.Error(), 1) - } - var eResp api.EndpointResponse - err = json.Unmarshal(resp, &eResp) - if err != nil { - return cli.NewExitError(err.Error(), 1) - } - printEndpoint(eResp.Endpoint) - - return nil + districtFlag, }, }, - { + + operations.List: cli.Command{ Name: "list", Usage: "List endpoints", Flags: []cli.Flag{ - cli.StringFlag{ - Name: "district, d", - Value: "default", - Usage: "District name", - }, - }, - Action: func(c *cli.Context) error { - resp, err := api.DefaultClient.Request("GET", fmt.Sprintf("/districts/%s/endpoints", c.String("district")), nil) - if err != nil { - return cli.NewExitError(err.Error(), 1) - } - var eResp api.EndpointResponse - err = json.Unmarshal(resp, &eResp) - if err != nil { - return cli.NewExitError(err.Error(), 1) - } - printEndpoints(eResp.Endpoints) - - return nil + districtFlag, }, }, - { - Name: "update", - Usage: "Update an endpoint", - ArgsUsage: "ENDPOINT_NAME", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "district, d", - Value: "default", - Usage: "District name", - }, - cli.StringFlag{ - Name: "certificate-arn", - Usage: "ACM Certificate ARN", - }, - cli.StringFlag{ - - Usage: "HTTPS SSL Policy", - }, - }, - Action: func(c *cli.Context) error { - endpointName := c.Args().Get(0) - if len(endpointName) == 0 { - return cli.NewExitError("endpoint name is required", 1) - } + } - request := api.Endpoint{ - CertificateID: c.String("certificate-arn"), - SslPolicy: c.String("ssl-policy"), - } + var array []cli.Command - b, err := json.Marshal(&request) - if err != nil { - return cli.NewExitError(err.Error(), 1) - } + for opname, command := range endpointSubcommands { - resp, err := api.DefaultClient.Request("PATCH", fmt.Sprintf("/districts/%s/endpoints/%s", c.String("district"), endpointName), bytes.NewBuffer(b)) - if err != nil { - return cli.NewExitError(err.Error(), 1) - } - var eResp api.EndpointResponse - err = json.Unmarshal(resp, &eResp) - if err != nil { - return cli.NewExitError(err.Error(), 1) - } - printEndpoint(eResp.Endpoint) + captured_opname := opname + captured_command := command - return nil - }, - }, - { - Name: "delete", - Usage: "Delete an endpoint", - ArgsUsage: "ENDPOINT_NAME", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "district, d", - Value: "default", - Usage: "District name", - }, - cli.BoolFlag{ - Name: "no-confirmation", - }, - }, + subcommand := cli.Command{ + Name: command.Name, + Usage: command.Usage, + ArgsUsage: command.ArgsUsage, + Flags: command.Flags, Action: func(c *cli.Context) error { + endpointName := c.Args().Get(0) - if len(endpointName) == 0 { - return cli.NewExitError("endpoint name is required", 1) - } + if captured_command.ArgsUsage == "ENDPOINT_NAME" { + if len(endpointName) == 0 { + return cli.NewExitError("endpoint name is required", 1) + } - fmt.Printf("You are attempting to delete /%s/endpoints/%s\n", c.String("district"), endpointName) - if !c.Bool("no-confirmation") && !utils.AreYouSure("This operation cannot be undone. Are you sure?", utils.NewStdinInputReader()) { - return nil + if len(c.Args()) != 1 { + return cli.NewExitError("please place options and flags before the endpoint name.", 1) + } } - _, err := api.DefaultClient.Request("DELETE", fmt.Sprintf("/districts/%s/endpoints/%s", c.String("district"), endpointName), nil) - if err != nil { - return cli.NewExitError(err.Error(), 1) - } - return nil + public := c.Bool("public") + cert_arn := c.String("certificate-arn") + policy := c.String("ssl-policy") + districtName := c.String("district") + noconfirm := c.Bool("no-confirmation") + + client := struct { + *api.Client + utils.UserInputReader + }{ + api.DefaultClient, + utils.NewStdinInputReader(), + } + + oper := operations.NewEndpointOperation( + districtName, + endpointName, + public, + cert_arn, + policy, + noconfirm, + captured_opname, + client, + ) + return operations.Execute(oper) }, - }, - }, -} + } + array = append(array, subcommand) + } -func printEndpoint(e *api.Endpoint) { - fmt.Printf("Name: %s\n", e.Name) - fmt.Printf("Public: %t\n", *e.Public) - fmt.Printf("SSL Policy: %s\n", e.SslPolicy) - fmt.Printf("Certificate ARN: %s\n", e.CertificateID) - fmt.Printf("DNS Name: %s\n", e.DNSName) + return array } -func printEndpoints(es []*api.Endpoint) { - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"Name", "District", "Public", "SSL Policy", "Cert ID"}) - table.SetBorder(false) - for _, e := range es { - table.Append([]string{e.Name, e.District.Name, fmt.Sprintf("%t", *e.Public), e.SslPolicy, e.CertificateID}) - } - table.Render() +var EndpointCommand = cli.Command{ + Name: "endpoint", + Usage: "Endpoint operations", + Subcommands: getEndpointSubcommands(), } diff --git a/operations/endpoint_operation.go b/operations/endpoint_operation.go new file mode 100644 index 0000000..bb4c7c6 --- /dev/null +++ b/operations/endpoint_operation.go @@ -0,0 +1,171 @@ +package operations + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + + "github.com/degica/barcelona-cli/api" + "github.com/degica/barcelona-cli/utils" + "github.com/olekukonko/tablewriter" +) + +type EndpointOperationApiClient interface { + Request(method string, path string, body io.Reader) ([]byte, error) + Read(secret bool) (string, error) +} + +type EndpointOperation struct { + districtName string + endpointName string + public bool + cert_arn string + policy string + noConfirmation bool + op_type OperationType + client EndpointOperationApiClient +} + +func NewEndpointOperation(districtName string, endpointName string, public bool, cert_arn string, policy string, noConfirmation bool, op OperationType, client EndpointOperationApiClient) *EndpointOperation { + return &EndpointOperation{ + districtName: districtName, + endpointName: endpointName, + public: public, + cert_arn: cert_arn, + policy: policy, + noConfirmation: noConfirmation, + op_type: op, + client: client, + } +} + +func (oper EndpointOperation) run() *runResult { + operations := map[OperationType](func(oper EndpointOperation) *runResult){ + Create: endpoint_create, + Update: endpoint_update, + Delete: endpoint_delete, + Show: endpoint_show, + List: endpoint_list, + } + + if function, ok := operations[oper.op_type]; ok { + return function(oper) + } + + return error_result("unknown operation") +} + +func endpoint_create(oper EndpointOperation) *runResult { + request := api.Endpoint{ + Name: oper.endpointName, + Public: &oper.public, + CertificateID: oper.cert_arn, + SslPolicy: oper.policy, + } + + b, err := json.Marshal(&request) + if err != nil { + return error_result(err.Error()) + } + + resp, err := oper.client.Request("POST", fmt.Sprintf("/districts/%s/endpoints", oper.districtName), bytes.NewBuffer(b)) + if err != nil { + return error_result(err.Error()) + } + var eResp api.EndpointResponse + err = json.Unmarshal(resp, &eResp) + if err != nil { + return error_result(err.Error()) + } + printEndpoint(eResp.Endpoint) + + return ok_result() +} + +func endpoint_show(oper EndpointOperation) *runResult { + resp, err := oper.client.Request("GET", fmt.Sprintf("/districts/%s/endpoints/%s", oper.districtName, oper.endpointName), nil) + if err != nil { + return error_result(err.Error()) + } + var eResp api.EndpointResponse + err = json.Unmarshal(resp, &eResp) + if err != nil { + return error_result(err.Error()) + } + printEndpoint(eResp.Endpoint) + + return ok_result() +} + +func endpoint_list(oper EndpointOperation) *runResult { + resp, err := oper.client.Request("GET", fmt.Sprintf("/districts/%s/endpoints", oper.districtName), nil) + if err != nil { + return error_result(err.Error()) + } + var eResp api.EndpointResponse + err = json.Unmarshal(resp, &eResp) + if err != nil { + return error_result(err.Error()) + } + printEndpoints(eResp.Endpoints) + + return ok_result() +} + +func endpoint_update(oper EndpointOperation) *runResult { + request := api.Endpoint{ + CertificateID: oper.cert_arn, + SslPolicy: oper.policy, + } + + b, err := json.Marshal(&request) + if err != nil { + return error_result(err.Error()) + } + + resp, err := oper.client.Request("PATCH", fmt.Sprintf("/districts/%s/endpoints/%s", oper.districtName, oper.endpointName), bytes.NewBuffer(b)) + if err != nil { + return error_result(err.Error()) + } + var eResp api.EndpointResponse + err = json.Unmarshal(resp, &eResp) + if err != nil { + return error_result(err.Error()) + } + printEndpoint(eResp.Endpoint) + + return ok_result() +} + +func endpoint_delete(oper EndpointOperation) *runResult { + fmt.Printf("You are attempting to delete /%s/endpoints/%s\n", oper.districtName, oper.endpointName) + if !oper.noConfirmation && !utils.AreYouSure("This operation cannot be undone. Are you sure?", oper.client) { + return nil + } + + _, err := oper.client.Request("DELETE", fmt.Sprintf("/districts/%s/endpoints/%s", oper.districtName, oper.endpointName), nil) + if err != nil { + return error_result(err.Error()) + } + return ok_result() +} + +func printEndpoint(e *api.Endpoint) { + fmt.Printf("Name: %s\n", e.Name) + fmt.Printf("Public: %t\n", *e.Public) + fmt.Printf("SSL Policy: %s\n", e.SslPolicy) + fmt.Printf("Certificate ARN: %s\n", e.CertificateID) + fmt.Printf("DNS Name: %s\n", e.DNSName) +} + +func printEndpoints(es []*api.Endpoint) { + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Name", "District", "Public", "SSL Policy", "Cert ID"}) + table.SetBorder(false) + for _, e := range es { + table.Append([]string{e.Name, e.District.Name, fmt.Sprintf("%t", *e.Public), e.SslPolicy, e.CertificateID}) + } + table.Render() +} diff --git a/operations/operation.go b/operations/operation.go index dfe0066..f489cab 100644 --- a/operations/operation.go +++ b/operations/operation.go @@ -44,6 +44,9 @@ func ok_result() *runResult { type OperationType string const ( - Delete OperationType = "Delete" + Create OperationType = "Create" + Delete = "Delete" Show = "Show" + List = "List" + Update = "Update" ) diff --git a/operations/profile_operation_test.go b/operations/profile_operation_test.go index 5fa3293..d0fa59a 100644 --- a/operations/profile_operation_test.go +++ b/operations/profile_operation_test.go @@ -94,9 +94,9 @@ func (_ MockProfileManipulation) GetEndpoint() string { type MockProfileManipulationForCreateProfile struct { MockProfileManipulation - profiles map[string]*profileFile - profileName string - currentProfile *profileFile + profiles map[string]*profileFile + profileName string + currentProfile *profileFile getProfileError error } @@ -121,8 +121,8 @@ func (m *MockProfileManipulationForCreateProfile) setProfile(pfile profileFile) func TestCreateProfileWithNothing(t *testing.T) { // This simulates not having a .bcn directory oper := &MockProfileManipulationForCreateProfile{ - profiles: map[string]*profileFile{}, - profileName: "adefault", + profiles: map[string]*profileFile{}, + profileName: "adefault", getProfileError: profileError{}, } @@ -135,8 +135,8 @@ func TestCreateProfileWithNothing(t *testing.T) { func TestCreateProfileWithExistingProfile(t *testing.T) { oper := &MockProfileManipulationForCreateProfile{ - profiles: map[string]*profileFile{}, - profileName: "adefault", + profiles: map[string]*profileFile{}, + profileName: "adefault", currentProfile: &profileFile{}, }