From 9ed871c422ff41bfe714b5fc382433182ab231ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vanzuita?= Date: Sat, 9 Apr 2022 23:46:57 +0200 Subject: [PATCH 1/5] improve repository deletion message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Vanzuita --- internal/commands/repo/rm.go | 9 ++++++--- pkg/hub/repositories.go | 18 ++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/internal/commands/repo/rm.go b/internal/commands/repo/rm.go index 8196af8..c25e32e 100644 --- a/internal/commands/repo/rm.go +++ b/internal/commands/repo/rm.go @@ -47,7 +47,7 @@ type rmOptions struct { func newRmCmd(streams command.Streams, hubClient *hub.Client, parent string) *cobra.Command { var opts rmOptions cmd := &cobra.Command{ - Use: rmName + " [OPTIONS] REPOSITORY", + Use: rmName + " [OPTIONS] USERNAME/REPOSITORY", Short: "Delete a repository", Args: cli.ExactArgs(1), DisableFlagsInUseLine: true, @@ -73,7 +73,7 @@ func runRm(ctx context.Context, streams command.Streams, hubClient *hub.Client, } namedRef, ok := ref.(reference.Named) if !ok { - return fmt.Errorf("invalid reference: repository not specified") + return errors.New("invalid reference: repository not specified") } if !opts.force { @@ -104,6 +104,9 @@ func runRm(ctx context.Context, streams command.Streams, hubClient *hub.Client, if err := hubClient.RemoveRepository(namedRef.Name()); err != nil { return err } - fmt.Fprintln(streams.Out(), "Deleted", repository) + _, err = fmt.Fprintln(streams.Out(), fmt.Sprintf("Repository %q was deleted", repository)) + if err != nil { + return err + } return nil } diff --git a/pkg/hub/repositories.go b/pkg/hub/repositories.go index 28273db..0d3c569 100644 --- a/pkg/hub/repositories.go +++ b/pkg/hub/repositories.go @@ -25,10 +25,8 @@ import ( ) const ( - // RepositoriesURL path to the Hub API listing the repositories - RepositoriesURL = "/v2/repositories/%s/" - // DeleteRepositoryURL path to the Hub API to remove a repository - DeleteRepositoryURL = "/v2/repositories/%s/" + // HubBaseURL is the Hub API base URL + HubBaseURL = "/v2/repositories/" ) //Repository represents a Docker Hub repository @@ -46,7 +44,9 @@ func (c *Client) GetRepositories(account string) ([]Repository, int, error) { if account == "" { account = c.account } - u, err := url.Parse(c.domain + fmt.Sprintf(RepositoriesURL, account)) + repositoriesURL := fmt.Sprintf("%s%s%s", c.domain, HubBaseURL, account) + fmt.Println(repositoriesURL) + u, err := url.Parse(repositoriesURL) if err != nil { return nil, 0, err } @@ -75,14 +75,16 @@ func (c *Client) GetRepositories(account string) ([]Repository, int, error) { return repos, total, nil } -//RemoveRepository removes a repository on Hub +//RemoveRepository receives repository parameter in the format ``username/repository``, and request repository deletion +//on Hub using the user token func (c *Client) RemoveRepository(repository string) error { - req, err := http.NewRequest("DELETE", c.domain+fmt.Sprintf(DeleteRepositoryURL, repository), nil) + repositoryUrl := fmt.Sprintf("%s%s%s", c.domain, HubBaseURL, repository) + req, err := http.NewRequest(http.MethodDelete, repositoryUrl, nil) if err != nil { return err } _, err = c.doRequest(req, withHubToken(c.token)) - return err + return nil } func (c *Client) getRepositoriesPage(url, account string) ([]Repository, int, string, error) { From a1d28efc1413ac9f84bae336ade8668be9f1587f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vanzuita?= Date: Sat, 9 Apr 2022 23:47:31 +0200 Subject: [PATCH 2/5] handle resource not found error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Vanzuita --- pkg/hub/client.go | 5 +++++ pkg/hub/error.go | 12 ++++++++++++ pkg/hub/error_test.go | 5 +++++ 3 files changed, 22 insertions(+) diff --git a/pkg/hub/client.go b/pkg/hub/client.go index 0f8f098..2c5c04f 100644 --- a/pkg/hub/client.go +++ b/pkg/hub/client.go @@ -309,6 +309,11 @@ func (c *Client) doRequest(req *http.Request, reqOps ...RequestOp) ([]byte, erro defer resp.Body.Close() //nolint:errcheck } log.Tracef("HTTP response: %+v", resp) + + if resp.StatusCode == http.StatusNotFound { + return nil, ¬FoundError{} + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { if resp.StatusCode == http.StatusForbidden { return nil, &forbiddenError{} diff --git a/pkg/hub/error.go b/pkg/hub/error.go index a989dd4..7fe17f5 100644 --- a/pkg/hub/error.go +++ b/pkg/hub/error.go @@ -56,3 +56,15 @@ func IsForbiddenError(err error) bool { _, ok := err.(*forbiddenError) return ok } + +type notFoundError struct{} + +func (n notFoundError) Error() string { + return "resource not found" +} + +// IsNotFoundError check if the error type is a not found error +func IsNotFoundError(err error) bool { + _, ok := err.(*notFoundError) + return ok +} diff --git a/pkg/hub/error_test.go b/pkg/hub/error_test.go index 42d175d..8c20e1e 100644 --- a/pkg/hub/error_test.go +++ b/pkg/hub/error_test.go @@ -37,3 +37,8 @@ func TestIsForbiddenError(t *testing.T) { assert.Assert(t, IsForbiddenError(&forbiddenError{})) assert.Assert(t, !IsForbiddenError(errors.New(""))) } + +func TestIsNotFoundError(t *testing.T) { + assert.Assert(t, IsNotFoundError(¬FoundError{})) + assert.Assert(t, !IsNotFoundError(errors.New(""))) +} From b436f2a74e7dd771c652a613b874c2735a96a97a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vanzuita?= Date: Sat, 9 Apr 2022 23:48:57 +0200 Subject: [PATCH 3/5] allow user input repository name only for deletion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Vanzuita --- internal/commands/repo/rm.go | 2 +- pkg/hub/repositories.go | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/commands/repo/rm.go b/internal/commands/repo/rm.go index c25e32e..4d20ade 100644 --- a/internal/commands/repo/rm.go +++ b/internal/commands/repo/rm.go @@ -47,7 +47,7 @@ type rmOptions struct { func newRmCmd(streams command.Streams, hubClient *hub.Client, parent string) *cobra.Command { var opts rmOptions cmd := &cobra.Command{ - Use: rmName + " [OPTIONS] USERNAME/REPOSITORY", + Use: rmName + " [OPTIONS] REPOSITORY", Short: "Delete a repository", Args: cli.ExactArgs(1), DisableFlagsInUseLine: true, diff --git a/pkg/hub/repositories.go b/pkg/hub/repositories.go index 0d3c569..23c4542 100644 --- a/pkg/hub/repositories.go +++ b/pkg/hub/repositories.go @@ -25,8 +25,8 @@ import ( ) const ( - // HubBaseURL is the Hub API base URL - HubBaseURL = "/v2/repositories/" + // RepositoriesURL is the Hub API base URL + RepositoriesURL = "/v2/repositories/" ) //Repository represents a Docker Hub repository @@ -44,8 +44,7 @@ func (c *Client) GetRepositories(account string) ([]Repository, int, error) { if account == "" { account = c.account } - repositoriesURL := fmt.Sprintf("%s%s%s", c.domain, HubBaseURL, account) - fmt.Println(repositoriesURL) + repositoriesURL := fmt.Sprintf("%s%s%s", c.domain, RepositoriesURL, account) u, err := url.Parse(repositoriesURL) if err != nil { return nil, 0, err @@ -75,15 +74,18 @@ func (c *Client) GetRepositories(account string) ([]Repository, int, error) { return repos, total, nil } -//RemoveRepository receives repository parameter in the format ``username/repository``, and request repository deletion -//on Hub using the user token +//RemoveRepository removes a repository on Hub func (c *Client) RemoveRepository(repository string) error { - repositoryUrl := fmt.Sprintf("%s%s%s", c.domain, HubBaseURL, repository) + repositoryUrl := fmt.Sprintf("%s%s%s/%s/", c.domain, RepositoriesURL, c.account, repository) req, err := http.NewRequest(http.MethodDelete, repositoryUrl, nil) if err != nil { return err } _, err = c.doRequest(req, withHubToken(c.token)) + if err != nil { + return err + } + return nil } From 04791d1b4169d219fd4d867137e507a2113334d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vanzuita?= Date: Tue, 12 Apr 2022 20:25:47 +0200 Subject: [PATCH 4/5] require username or organization name to delete repositories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Vanzuita --- internal/commands/repo/rm.go | 8 ++++++-- pkg/hub/repositories.go | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/commands/repo/rm.go b/internal/commands/repo/rm.go index 4d20ade..6fbf714 100644 --- a/internal/commands/repo/rm.go +++ b/internal/commands/repo/rm.go @@ -47,7 +47,7 @@ type rmOptions struct { func newRmCmd(streams command.Streams, hubClient *hub.Client, parent string) *cobra.Command { var opts rmOptions cmd := &cobra.Command{ - Use: rmName + " [OPTIONS] REPOSITORY", + Use: rmName + " [OPTIONS] USERNAME(OR)ORGANIZATION NAME/REPOSITORY", Short: "Delete a repository", Args: cli.ExactArgs(1), DisableFlagsInUseLine: true, @@ -76,6 +76,10 @@ func runRm(ctx context.Context, streams command.Streams, hubClient *hub.Client, return errors.New("invalid reference: repository not specified") } + if !strings.Contains(repository, "/") { + return fmt.Errorf("repository name must include username or organization name, example: hub-tool repo rm username/repository") + } + if !opts.force { _, count, err := hubClient.GetTags(namedRef.Name()) if err != nil { @@ -104,7 +108,7 @@ func runRm(ctx context.Context, streams command.Streams, hubClient *hub.Client, if err := hubClient.RemoveRepository(namedRef.Name()); err != nil { return err } - _, err = fmt.Fprintln(streams.Out(), fmt.Sprintf("Repository %q was deleted", repository)) + _, err = fmt.Fprintf(streams.Out(), "Repository %q was successfully deleted\n", repository) if err != nil { return err } diff --git a/pkg/hub/repositories.go b/pkg/hub/repositories.go index 23c4542..30ff298 100644 --- a/pkg/hub/repositories.go +++ b/pkg/hub/repositories.go @@ -76,8 +76,8 @@ func (c *Client) GetRepositories(account string) ([]Repository, int, error) { //RemoveRepository removes a repository on Hub func (c *Client) RemoveRepository(repository string) error { - repositoryUrl := fmt.Sprintf("%s%s%s/%s/", c.domain, RepositoriesURL, c.account, repository) - req, err := http.NewRequest(http.MethodDelete, repositoryUrl, nil) + repositoryURL := fmt.Sprintf("%s%s%s/", c.domain, RepositoriesURL, repository) + req, err := http.NewRequest(http.MethodDelete, repositoryURL, nil) if err != nil { return err } From 06107511874ade69c88d97d4df8334954674b77f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vanzuita?= Date: Wed, 13 Apr 2022 21:34:57 +0200 Subject: [PATCH 5/5] update wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Vanzuita --- internal/commands/repo/rm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/commands/repo/rm.go b/internal/commands/repo/rm.go index 6fbf714..7fe558e 100644 --- a/internal/commands/repo/rm.go +++ b/internal/commands/repo/rm.go @@ -47,7 +47,7 @@ type rmOptions struct { func newRmCmd(streams command.Streams, hubClient *hub.Client, parent string) *cobra.Command { var opts rmOptions cmd := &cobra.Command{ - Use: rmName + " [OPTIONS] USERNAME(OR)ORGANIZATION NAME/REPOSITORY", + Use: rmName + " [OPTIONS] NAMESPACE/REPOSITORY", Short: "Delete a repository", Args: cli.ExactArgs(1), DisableFlagsInUseLine: true,