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: Add Delete to rpaasv2 plugin #97

Merged
merged 1 commit into from
Apr 1, 2021
Merged
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
53 changes: 51 additions & 2 deletions cmd/plugin/rpaasv2/cmd/certificates.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import (

func NewCmdCertificates() *cli.Command {
return &cli.Command{
Name: "certificates",
Usage: "Manages TLS certificates",
Name: "certificates",
Aliases: []string{"certificate"},
Usage: "Manages TLS certificates",
Subcommands: []*cli.Command{
NewCmdUpdateCertitifcate(),
NewCmdDeleteCertitifcate(),
},
}
}
Expand Down Expand Up @@ -97,6 +99,53 @@ func runUpdateCertificate(c *cli.Context) error {
return nil
}

func NewCmdDeleteCertitifcate() *cli.Command {
return &cli.Command{
Name: "delete",
Aliases: []string{"remove"},
Usage: "Deletes a certificate from a rpaas instance",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "service",
Aliases: []string{"tsuru-service", "s"},
Usage: "the Tsuru service name",
},
&cli.StringFlag{
Name: "instance",
Aliases: []string{"tsuru-service-instance", "i"},
Usage: "the reverse proxy instance name",
Required: true,
},
&cli.StringFlag{
Name: "name",
Usage: "an identifier for the current certificate and key",
Value: "default",
},
},
Before: setupClient,
Action: runDeleteCertificate,
}
}

func runDeleteCertificate(c *cli.Context) error {
client, err := getClient(c)
if err != nil {
return err
}

args := rpaasclient.DeleteCertificateArgs{
Instance: c.String("instance"),
Name: c.String("name"),
}
err = client.DeleteCertificate(c.Context, args)
if err != nil {
return err
}

fmt.Fprintf(c.App.Writer, "certificate %q successfully deleted on %s\n", args.Name, formatInstanceName(c))
return nil
}

func writeCertificatesInfoOnTableFormat(w io.Writer, certs []clientTypes.CertificateInfo) {
var data [][]string
for _, c := range certs {
Expand Down
58 changes: 58 additions & 0 deletions cmd/plugin/rpaasv2/cmd/certificates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,61 @@ EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==
})
}
}

func TestDeleteCertificate(t *testing.T) {
tests := []struct {
name string
args []string
expected string
expectedError string
client rpaasclient.Client
}{
{
name: "when Delete certificate returns an error",
args: []string{"./rpaasv2", "certificates", "delete", "-i", "my-instance", "--name", "my-instance.example.com"},
expectedError: "some error",
client: &fake.FakeClient{
FakeDeleteCertificate: func(args rpaasclient.DeleteCertificateArgs) error {
expected := rpaasclient.DeleteCertificateArgs{
Instance: "my-instance",
Name: "my-instance.example.com",
}
assert.Equal(t, expected, args)
return fmt.Errorf("some error")
},
},
},
{
name: "when DeleteCertificate returns no error",
args: []string{"./rpaasv2", "certificates", "delete", "-i", "my-instance", "--name", "my-instance.example.com"},
client: &fake.FakeClient{
FakeDeleteCertificate: func(args rpaasclient.DeleteCertificateArgs) error {
expected := rpaasclient.DeleteCertificateArgs{
Instance: "my-instance",
Name: "my-instance.example.com",
}
assert.Equal(t, expected, args)
return nil
},
},
expected: "certificate \"my-instance.example.com\" successfully deleted on my-instance\n",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
app := NewApp(stdout, stderr, tt.client)
err := app.Run(tt.args)
if tt.expectedError != "" {
assert.Error(t, err)
assert.EqualError(t, err, tt.expectedError)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expected, stdout.String())
assert.Empty(t, stderr.String())
})
}
}
6 changes: 5 additions & 1 deletion internal/web/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"crypto/tls"
"fmt"
"net/http"
"net/url"

"github.com/labstack/echo/v4"

Expand All @@ -23,7 +24,10 @@ func deleteCertificate(c echo.Context) error {
}

instance := c.Param("instance")
certName := c.Param("name")
certName, err := url.QueryUnescape(c.Param("name"))
if err != nil {
return err
}
err = manager.DeleteCertificate(ctx, instance, certName)
if err != nil {
return err
Expand Down
14 changes: 14 additions & 0 deletions internal/web/certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"fmt"
"mime/multipart"
"net/http"
"net/url"
"strings"
"testing"

Expand Down Expand Up @@ -202,6 +203,19 @@ func Test_deleteCertificate(t *testing.T) {
},
expectedCode: http.StatusOK,
},
{
name: "passing a certificate name and asserting it",
instance: "my-instance",
certName: url.QueryEscape("cert name with spaces"),
manager: &fake.RpaasManager{
FakeDeleteCertificate: func(instance, name string) error {
assert.Equal(t, "my-instance", instance)
assert.Equal(t, "cert name with spaces", name)
return nil
},
},
expectedCode: http.StatusOK,
},
}

for _, tt := range tests {
Expand Down
6 changes: 6 additions & 0 deletions pkg/rpaas/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ type UpdateCertificateArgs struct {
boundary string
}

type DeleteCertificateArgs struct {
Instance string
Name string
}

type UpdateBlockArgs struct {
Instance string
Name string
Expand Down Expand Up @@ -98,6 +103,7 @@ type Client interface {
Scale(ctx context.Context, args ScaleArgs) error
Info(ctx context.Context, args InfoArgs) (*types.InstanceInfo, error)
UpdateCertificate(ctx context.Context, args UpdateCertificateArgs) error
DeleteCertificate(ctx context.Context, args DeleteCertificateArgs) error
UpdateBlock(ctx context.Context, args UpdateBlockArgs) error
DeleteBlock(ctx context.Context, args DeleteBlockArgs) error
ListBlocks(ctx context.Context, args ListBlocksArgs) ([]types.Block, error)
Expand Down
9 changes: 9 additions & 0 deletions pkg/rpaas/client/fake/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type FakeClient struct {
FakeGetFlavors func(instance string) ([]types.Flavor, error)
FakeScale func(args client.ScaleArgs) error
FakeUpdateCertificate func(args client.UpdateCertificateArgs) error
FakeDeleteCertificate func(args client.DeleteCertificateArgs) error
FakeUpdateBlock func(args client.UpdateBlockArgs) error
FakeDeleteBlock func(args client.DeleteBlockArgs) error
FakeListBlocks func(args client.ListBlocksArgs) ([]types.Block, error)
Expand Down Expand Up @@ -96,6 +97,14 @@ func (f *FakeClient) UpdateCertificate(ctx context.Context, args client.UpdateCe
return nil
}

func (f *FakeClient) DeleteCertificate(ctx context.Context, args client.DeleteCertificateArgs) error {
if f.FakeDeleteCertificate != nil {
return f.FakeDeleteCertificate(args)
}

return nil
}

func (f *FakeClient) UpdateBlock(ctx context.Context, args client.UpdateBlockArgs) error {
if f.FakeUpdateBlock != nil {
return f.FakeUpdateBlock(args)
Expand Down
36 changes: 36 additions & 0 deletions pkg/rpaas/client/internal_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,37 @@ func (c *client) UpdateCertificate(ctx context.Context, args UpdateCertificateAr
return nil
}

func (args *DeleteCertificateArgs) Validate() error {
if args.Instance == "" {
return ErrMissingInstance
}

return nil
}

func (c *client) DeleteCertificate(ctx context.Context, args DeleteCertificateArgs) error {
if err := args.Validate(); err != nil {
return err
}

args.Name = url.QueryEscape(args.Name)
request, err := c.buildRequest("DeleteCertificate", args)
if err != nil {
return err
}

response, err := c.do(ctx, request)
if err != nil {
return err
}

if response.StatusCode != http.StatusOK {
return newErrUnexpectedStatusCodeFromResponse(response)
}

return nil
}

func (args UpdateBlockArgs) Validate() error {
if args.Instance == "" {
return ErrMissingInstance
Expand Down Expand Up @@ -735,6 +766,11 @@ func (c *client) buildRequest(operation string, data interface{}) (req *http.Req
req, err = c.newRequest("POST", pathName, body, args.Instance)
req.Header.Set("Content-Type", fmt.Sprintf("multipart/form-data; boundary=%q", w.Boundary()))

case "DeleteCertificate":
args := data.(DeleteCertificateArgs)
pathName := fmt.Sprintf("/resources/%s/certificate/%s", args.Instance, args.Name)
req, err = c.newRequest("DELETE", pathName, nil, args.Instance)

case "UpdateBlock":
args := data.(UpdateBlockArgs)
values := url.Values{}
Expand Down
77 changes: 77 additions & 0 deletions pkg/rpaas/client/internal_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,83 @@ func TestClientThroughTsuru_UpdateCertificate(t *testing.T) {
}
}

func TestClientThroughTsuru_DeleteCertificate(t *testing.T) {
tests := []struct {
name string
args DeleteCertificateArgs
expectedError string
handler http.HandlerFunc
}{
{
name: "when instance is empty",
args: DeleteCertificateArgs{},
expectedError: "rpaasv2: instance cannot be empty",
},
{
name: "when certificate name is empty, should not return error since empty = default",
args: DeleteCertificateArgs{
Instance: "my-instance",
},
handler: func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "DELETE")
assert.Equal(t, fmt.Sprintf("/services/%s/proxy/%s?callback=%s", FakeTsuruService, "my-instance", "/resources/my-instance/certificate/"), r.URL.RequestURI())
assert.Equal(t, "Bearer f4k3t0k3n", r.Header.Get("Authorization"))
w.WriteHeader(http.StatusOK)
},
},
{
name: "when the server returns the expected response",
args: DeleteCertificateArgs{
Instance: "my-instance",
Name: "my-certificate",
},
handler: func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.Method, "DELETE")
assert.Equal(t, fmt.Sprintf("/services/%s/proxy/%s?callback=%s", FakeTsuruService, "my-instance", "/resources/my-instance/certificate/my-certificate"), r.URL.RequestURI())
assert.Equal(t, "Bearer f4k3t0k3n", r.Header.Get("Authorization"))
assert.Equal(t, "", getBody(t, r))
w.WriteHeader(http.StatusOK)
},
},
{
name: "when the server returns an error",
args: DeleteCertificateArgs{
Instance: "my-instance",
Name: "my-certificate",
},
expectedError: "rpaasv2: unexpected status code: 404 Not Found, detail: instance not found",
handler: func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "instance not found")
},
},
{
name: "when the certificate name has spaces should should query escape cert name",
args: DeleteCertificateArgs{
Instance: "my-instance",
Name: "my certificate",
},
handler: func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, fmt.Sprintf("/services/%s/proxy/%s?callback=%s", FakeTsuruService, "my-instance", "/resources/my-instance/certificate/my+certificate"), r.URL.RequestURI())
w.WriteHeader(http.StatusOK)
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, server := newClientThroughTsuru(t, tt.handler)
defer server.Close()
err := client.DeleteCertificate(context.TODO(), tt.args)
if tt.expectedError == "" {
require.NoError(t, err)
return
}
assert.EqualError(t, err, tt.expectedError)
})
}
}

func TestClientThroughTsuru_UpdateBlock(t *testing.T) {
tests := []struct {
name string
Expand Down