From 16537a692d62d6faf8b0ca63d255bcbb21a5fd34 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Thu, 15 Aug 2024 22:33:33 +0530 Subject: [PATCH 1/6] feat: add create webhook cmd Signed-off-by: 35C4n0r --- cmd/harbor/root/cmd.go | 2 + cmd/harbor/root/webhook/cmd.go | 17 +++++ cmd/harbor/root/webhook/create.go | 62 +++++++++++++++++ pkg/api/webhook_handler.go | 63 +++++++++++++++++ pkg/utils/helper.go | 10 +++ pkg/views/webhook/create/view.go | 111 ++++++++++++++++++++++++++++++ 6 files changed, 265 insertions(+) create mode 100644 cmd/harbor/root/webhook/cmd.go create mode 100644 cmd/harbor/root/webhook/create.go create mode 100644 pkg/api/webhook_handler.go create mode 100644 pkg/views/webhook/create/view.go diff --git a/cmd/harbor/root/cmd.go b/cmd/harbor/root/cmd.go index 7a3fbf3d..f0bdbd96 100644 --- a/cmd/harbor/root/cmd.go +++ b/cmd/harbor/root/cmd.go @@ -10,6 +10,7 @@ import ( "github.com/goharbor/harbor-cli/cmd/harbor/root/registry" repositry "github.com/goharbor/harbor-cli/cmd/harbor/root/repository" "github.com/goharbor/harbor-cli/cmd/harbor/root/user" + "github.com/goharbor/harbor-cli/cmd/harbor/root/webhook" "github.com/goharbor/harbor-cli/pkg/utils" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -109,6 +110,7 @@ harbor help repositry.Repository(), user.User(), artifact.Artifact(), + webhook.Webhook(), ) return root diff --git a/cmd/harbor/root/webhook/cmd.go b/cmd/harbor/root/webhook/cmd.go new file mode 100644 index 00000000..d3668755 --- /dev/null +++ b/cmd/harbor/root/webhook/cmd.go @@ -0,0 +1,17 @@ +package webhook + +import "github.com/spf13/cobra" + +func Webhook() *cobra.Command { + cmd := &cobra.Command{ + Use: "webhook", + Short: "Manage webhooks", + Long: `Manage webhooks in Harbor Repository`, + Example: ` harbor webhook list`, + } + cmd.AddCommand( + CreateWebhookCmd(), + //ListWebhookCommand(), + ) + return cmd +} diff --git a/cmd/harbor/root/webhook/create.go b/cmd/harbor/root/webhook/create.go new file mode 100644 index 00000000..da9aafcb --- /dev/null +++ b/cmd/harbor/root/webhook/create.go @@ -0,0 +1,62 @@ +package webhook + +import ( + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/prompt" + "github.com/goharbor/harbor-cli/pkg/views/webhook/create" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +func CreateWebhookCmd() *cobra.Command { + var opts create.CreateView + + cmd := &cobra.Command{ + Use: "create", + Short: "webhook create", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + var err error + createView := &create.CreateView{ + ProjectName: opts.ProjectName, + Name: opts.Name, + Description: opts.Description, + NotifyType: opts.NotifyType, + PayloadFormat: opts.PayloadFormat, + EventType: opts.EventType, + EndpointURL: opts.EndpointURL, + AuthHeader: opts.AuthHeader, + VerifyRemoteCertificate: opts.VerifyRemoteCertificate, + } + + if opts.Name != "" && opts.PayloadFormat != "" { + err = api.CreateWebhook(&opts) + } else { + err = createWebhookView(createView) + } + + if err != nil { + log.Errorf("failed to create webhook: %v", err) + } + }, + } + flags := cmd.Flags() + + flags.StringVarP(&opts.ProjectName, "project", "", "", "Project Name") + flags.StringVarP(&opts.Name, "name", "", "", "Webhook Name") + flags.StringVarP(&opts.Description, "description", "", "", "Webhook Description") + flags.StringVarP(&opts.NotifyType, "notify-type", "", "", "Notify Type (http, slack)") + flags.StringArrayVarP(&opts.EventType, "event-type", "", []string{}, "Event Types (comma separated)") + flags.StringVarP(&opts.EndpointURL, "endpoint-url", "", "", "Webhook Endpoint URL") + flags.StringVarP(&opts.AuthHeader, "auth-header", "", "", "Authentication Header") + flags.BoolVarP(&opts.VerifyRemoteCertificate, "verify-remote-certificate", "", true, "Verify Remote Certificate") + + return cmd +} + +func createWebhookView(view *create.CreateView) error { + + view.ProjectName = prompt.GetProjectNameFromUser() + create.WebhookCreateView(view) + return api.CreateWebhook(view) +} diff --git a/pkg/api/webhook_handler.go b/pkg/api/webhook_handler.go new file mode 100644 index 00000000..f679437e --- /dev/null +++ b/pkg/api/webhook_handler.go @@ -0,0 +1,63 @@ +package api + +import ( + "github.com/goharbor/go-client/pkg/sdk/v2.0/client/webhook" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/webhook/create" + log "github.com/sirupsen/logrus" +) + +func ListWebhooks(projectName string) (webhook.ListWebhookPoliciesOfProjectOK, error) { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return webhook.ListWebhookPoliciesOfProjectOK{}, err + } + + response, err := client.Webhook.ListWebhookPoliciesOfProject(ctx, &webhook.ListWebhookPoliciesOfProjectParams{ + ProjectNameOrID: projectName, + }) + + if err != nil { + return webhook.ListWebhookPoliciesOfProjectOK{}, err + } + return *response, nil +} + +func CreateWebhook(opts *create.CreateView) error { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return err + } + + response, err := client.Webhook.CreateWebhookPolicyOfProject(ctx, &webhook.CreateWebhookPolicyOfProjectParams{ + ProjectNameOrID: opts.ProjectName, + Policy: &models.WebhookPolicy{ + Description: opts.Description, + Enabled: true, + EventTypes: opts.EventType, + Name: opts.Name, + Targets: []*models.WebhookTargetObject{ + { + Address: opts.EndpointURL, + AuthHeader: opts.AuthHeader, + PayloadFormat: models.PayloadFormatType(opts.PayloadFormat), + SkipCertVerify: !opts.VerifyRemoteCertificate, + Type: opts.NotifyType, + }, + }, + }, + }) + + if err != nil { + log.Errorf("%s", err) + return err + } + + if response != nil { + log.Infof("Webhook `%s` created successfully", opts.Name) + } + + return nil + +} diff --git a/pkg/utils/helper.go b/pkg/utils/helper.go index 8484ce7d..cba3c619 100644 --- a/pkg/utils/helper.go +++ b/pkg/utils/helper.go @@ -1,6 +1,7 @@ package utils import ( + "errors" "fmt" "strings" "time" @@ -35,3 +36,12 @@ func FormatUrl(url string) string { } return url } + +func EmptyStringValidator(variable string) func(string) error { + return func(str string) error { + if str == "" { + return errors.New(fmt.Sprintf("%s cannot be empty", variable)) + } + return nil + } +} diff --git a/pkg/views/webhook/create/view.go b/pkg/views/webhook/create/view.go new file mode 100644 index 00000000..0671f6c4 --- /dev/null +++ b/pkg/views/webhook/create/view.go @@ -0,0 +1,111 @@ +package create + +import ( + "errors" + "github.com/charmbracelet/huh" + "github.com/goharbor/harbor-cli/pkg/utils" + log "github.com/sirupsen/logrus" +) + +type CreateView struct { + ProjectName string + Name string + Description string + NotifyType string + PayloadFormat string + EventType []string + EndpointURL string + AuthHeader string + VerifyRemoteCertificate bool +} + +func WebhookCreateView(createView *CreateView) { + theme := huh.ThemeCharm() + err := huh.NewForm( + huh.NewGroup( + + huh.NewInput(). + Title("Name"). + Value(&createView.Name). + Validate(utils.EmptyStringValidator("Webhook Name")), + + huh.NewText(). + Title("Description"). + Value(&createView.Description), + + huh.NewSelect[string](). + Title("Notify Type"). + Options( + huh.NewOption("http", "http"), + huh.NewOption("slack", "slack"), + ). + Value(&createView.NotifyType), + ), + ).WithTheme(theme).Run() + + if err != nil { + log.Fatal(err) + } + + if createView.NotifyType == "http" { + err = huh.NewForm( + huh.NewGroup( + huh.NewSelect[string](). + Title("Payload Format"). + Options( + huh.NewOption("Default", "Default"), + huh.NewOption("CloudEvents", "CloudEvents"), + ). + Value(&createView.PayloadFormat), + ), + ).WithTheme(theme).Run() + + if err != nil { + log.Fatal(err) + } + } + + err = huh.NewForm( + huh.NewGroup( + + huh.NewInput().Title("Endpoint URL"). + Value(&createView.EndpointURL). + Validate(utils.EmptyStringValidator("Endpoint URL")), + + huh.NewInput().Title("Auth Header").Value(&createView.AuthHeader), + + huh.NewMultiSelect[string](). + Title("Select Event Types"). + Options( + huh.NewOption("Artifact deleted", "DELETE_ARTIFACT"), + huh.NewOption("Artifact pulled", "PULL_ARTIFACT"), + huh.NewOption("Artifact pushed", "PUSH_ARTIFACT"), + huh.NewOption("Quota exceed", "QUOTA_EXCEED"), + huh.NewOption("Quota near threshold", "QUOTA_WARNING"), + huh.NewOption("Replication status changed", "REPLICATION"), + huh.NewOption("Scanning failed", "SCANNING_FAILED"), + huh.NewOption("Scanning finished", "SCANNING_COMPLETED"), + huh.NewOption("Scanning stopped", "SCANNING_STOPPED"), + huh.NewOption("Tag retention finished", "TAG_RETENTION"), + ). + Value(&createView.EventType). + Validate(func(args []string) error { + if len(args) == 0 { + return errors.New("please select least one of event type(s)") + } + return nil + }), + + huh.NewConfirm().Title("Verify Remote Certificate"). + Description("Determine whether the webhook should verify the certificate of a remote url "+ + "Uncheck this box when the remote url uses a self-signed or untrusted certificate."). + Affirmative("Yes"). + Negative("No"). + Value(&createView.VerifyRemoteCertificate), + ), + ).WithTheme(theme).Run() + + if err != nil { + log.Fatal(err) + } +} From a8a71ab102fef7a5282f93df64dd6e2f10114a49 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Sat, 17 Aug 2024 11:32:17 +0530 Subject: [PATCH 2/6] feat: add list webhook cmd Signed-off-by: 35C4n0r --- cmd/harbor/root/webhook/cmd.go | 2 +- cmd/harbor/root/webhook/list.go | 45 +++++++++++++++++++++++++++ pkg/views/webhook/list/view.go | 54 +++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 cmd/harbor/root/webhook/list.go create mode 100644 pkg/views/webhook/list/view.go diff --git a/cmd/harbor/root/webhook/cmd.go b/cmd/harbor/root/webhook/cmd.go index d3668755..6d7bd9d0 100644 --- a/cmd/harbor/root/webhook/cmd.go +++ b/cmd/harbor/root/webhook/cmd.go @@ -11,7 +11,7 @@ func Webhook() *cobra.Command { } cmd.AddCommand( CreateWebhookCmd(), - //ListWebhookCommand(), + ListWebhookCommand(), ) return cmd } diff --git a/cmd/harbor/root/webhook/list.go b/cmd/harbor/root/webhook/list.go new file mode 100644 index 00000000..b0a33798 --- /dev/null +++ b/cmd/harbor/root/webhook/list.go @@ -0,0 +1,45 @@ +package webhook + +import ( + "github.com/goharbor/go-client/pkg/sdk/v2.0/client/webhook" + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/prompt" + "github.com/goharbor/harbor-cli/pkg/utils" + webhookViews "github.com/goharbor/harbor-cli/pkg/views/webhook/list" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func ListWebhookCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "list webhook", + Args: cobra.MaximumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + var err error + var resp webhook.ListWebhookPoliciesOfProjectOK + if len(args) > 0 { + projectName := args[0] + resp, err = api.ListWebhooks(projectName) + } else { + projectName := prompt.GetProjectNameFromUser() + resp, err = api.ListWebhooks(projectName) + } + + if err != nil { + log.Errorf("failed to list webhooks: %v", err) + } + + FormatFlag := viper.GetString("output-format") + if FormatFlag != "" { + utils.PrintPayloadInJSONFormat(resp) + return + } + + webhookViews.ListWebhooks(resp.Payload) + + }, + } + return cmd +} diff --git a/pkg/views/webhook/list/view.go b/pkg/views/webhook/list/view.go new file mode 100644 index 00000000..c5296183 --- /dev/null +++ b/pkg/views/webhook/list/view.go @@ -0,0 +1,54 @@ +package list + +import ( + "fmt" + "github.com/charmbracelet/bubbles/table" + tea "github.com/charmbracelet/bubbletea" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/base/tablelist" + "os" + "strconv" +) + +var columns = []table.Column{ + {Title: "ID", Width: 6}, + {Title: "Webhook Name", Width: 15}, + {Title: "Enabled", Width: 12}, + {Title: "Endpoint URL", Width: 40}, + {Title: "Notify Type", Width: 12}, + {Title: "Payload Format", Width: 15}, + {Title: "Creation Time", Width: 20}, +} + +func ListWebhooks(webhooks []*models.WebhookPolicy) { + var rows []table.Row + for _, webhook := range webhooks { + var webhookEnabled string + if webhook.Enabled { + webhookEnabled = "True" + } else { + webhookEnabled = "False" + } + payloadFormat := "--" + if len(webhook.Targets[0].PayloadFormat) != 0 { + payloadFormat = string(webhook.Targets[0].PayloadFormat) + } + creationTime, _ := utils.FormatCreatedTime(webhook.CreationTime.String()) + rows = append(rows, table.Row{ + strconv.FormatInt(webhook.ID, 10), + webhook.Name, + webhookEnabled, + webhook.Targets[0].Address, + webhook.Targets[0].Type, + payloadFormat, + creationTime, + }) + } + m := tablelist.NewModel(columns, rows, len(rows)) + + if _, err := tea.NewProgram(m).Run(); err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } +} From 70312059db8f0a97a91beb0e79587f04d7bfd6eb Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Sat, 17 Aug 2024 13:09:40 +0530 Subject: [PATCH 3/6] feat: add delete webhook cmd Signed-off-by: 35C4n0r --- cmd/harbor/root/webhook/cmd.go | 1 + cmd/harbor/root/webhook/create.go | 7 ++++- cmd/harbor/root/webhook/delete.go | 45 +++++++++++++++++++++++++++++++ pkg/api/webhook_handler.go | 19 +++++++++++++ pkg/prompt/prompt.go | 14 ++++++++++ pkg/views/webhook/select/view.go | 37 +++++++++++++++++++++++++ 6 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 cmd/harbor/root/webhook/delete.go create mode 100644 pkg/views/webhook/select/view.go diff --git a/cmd/harbor/root/webhook/cmd.go b/cmd/harbor/root/webhook/cmd.go index 6d7bd9d0..2a0954aa 100644 --- a/cmd/harbor/root/webhook/cmd.go +++ b/cmd/harbor/root/webhook/cmd.go @@ -12,6 +12,7 @@ func Webhook() *cobra.Command { cmd.AddCommand( CreateWebhookCmd(), ListWebhookCommand(), + DeleteWebhookCmd(), ) return cmd } diff --git a/cmd/harbor/root/webhook/create.go b/cmd/harbor/root/webhook/create.go index da9aafcb..75b96bf7 100644 --- a/cmd/harbor/root/webhook/create.go +++ b/cmd/harbor/root/webhook/create.go @@ -29,7 +29,11 @@ func CreateWebhookCmd() *cobra.Command { VerifyRemoteCertificate: opts.VerifyRemoteCertificate, } - if opts.Name != "" && opts.PayloadFormat != "" { + if opts.ProjectName != "" && + opts.Name != "" && + opts.NotifyType != "" && + len(opts.EventType) != 0 && + opts.EndpointURL != "" { err = api.CreateWebhook(&opts) } else { err = createWebhookView(createView) @@ -48,6 +52,7 @@ func CreateWebhookCmd() *cobra.Command { flags.StringVarP(&opts.NotifyType, "notify-type", "", "", "Notify Type (http, slack)") flags.StringArrayVarP(&opts.EventType, "event-type", "", []string{}, "Event Types (comma separated)") flags.StringVarP(&opts.EndpointURL, "endpoint-url", "", "", "Webhook Endpoint URL") + flags.StringVarP(&opts.PayloadFormat, "payload-format", "", "", "Payload Format (Default, CloudEvents)") flags.StringVarP(&opts.AuthHeader, "auth-header", "", "", "Authentication Header") flags.BoolVarP(&opts.VerifyRemoteCertificate, "verify-remote-certificate", "", true, "Verify Remote Certificate") diff --git a/cmd/harbor/root/webhook/delete.go b/cmd/harbor/root/webhook/delete.go new file mode 100644 index 00000000..10bf1969 --- /dev/null +++ b/cmd/harbor/root/webhook/delete.go @@ -0,0 +1,45 @@ +package webhook + +import ( + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/prompt" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "strconv" +) + +func DeleteWebhookCmd() *cobra.Command { + + var projectName string + var webhookId string + var webhookIdInt int64 + + cmd := &cobra.Command{ + Use: "delete", + Short: "webhook delete", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + var err error + + if projectName != "" && webhookId != "" { + webhookIdInt, err = strconv.ParseInt(webhookId, 10, 64) + err = api.DeleteWebhook(projectName, webhookIdInt) + } else { + projectName = prompt.GetProjectNameFromUser() + webhookIdInt = prompt.GetWebhookFromUser(projectName) + err = api.DeleteWebhook(projectName, webhookIdInt) + + } + if err != nil { + log.Errorf("failed to delete webhook: %v", err) + } + + }, + } + + flags := cmd.Flags() + flags.StringVarP(&projectName, "project", "", "", "Project Name") + flags.StringVarP(&webhookId, "webhook", "", "", "Webhook Id") + + return cmd +} diff --git a/pkg/api/webhook_handler.go b/pkg/api/webhook_handler.go index f679437e..8b71983a 100644 --- a/pkg/api/webhook_handler.go +++ b/pkg/api/webhook_handler.go @@ -59,5 +59,24 @@ func CreateWebhook(opts *create.CreateView) error { } return nil +} +func DeleteWebhook(projectName string, webhookId int64) error { + ctx, client, err := utils.ContextWithClient() + if err != nil { + log.Errorf("%s", err) + return err + } + response, err := client.Webhook.DeleteWebhookPolicyOfProject(ctx, &webhook.DeleteWebhookPolicyOfProjectParams{ + WebhookPolicyID: webhookId, + ProjectNameOrID: projectName, + }) + if err != nil { + log.Errorf("%s", err) + return err + } + if response != nil { + log.Infof("Webhook Id:`%s` deleted successfully", webhookId) + } + return nil } diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go index f67f9b94..116924ca 100644 --- a/pkg/prompt/prompt.go +++ b/pkg/prompt/prompt.go @@ -8,6 +8,7 @@ import ( rview "github.com/goharbor/harbor-cli/pkg/views/registry/select" repoView "github.com/goharbor/harbor-cli/pkg/views/repository/select" uview "github.com/goharbor/harbor-cli/pkg/views/user/select" + wview "github.com/goharbor/harbor-cli/pkg/views/webhook/select" log "github.com/sirupsen/logrus" ) @@ -89,3 +90,16 @@ func GetTagNameFromUser() string { return <-repoName } + +func GetWebhookFromUser(projectName string) int64 { + webhookName := make(chan int64) + + go func() { + res, err := api.ListWebhooks(projectName) + if err != nil { + log.Fatal(err) + } + wview.WebhookList(res.Payload, webhookName) + }() + return <-webhookName +} diff --git a/pkg/views/webhook/select/view.go b/pkg/views/webhook/select/view.go new file mode 100644 index 00000000..19d58416 --- /dev/null +++ b/pkg/views/webhook/select/view.go @@ -0,0 +1,37 @@ +package project + +import ( + "fmt" + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/views/base/selection" + "os" +) + +func WebhookList(webhooks []*models.WebhookPolicy, choice chan<- int64) { + itemsList := make([]list.Item, len(webhooks)) + + for i, webhook := range webhooks { + itemsList[i] = selection.Item(webhook.Name) + } + + m := selection.NewModel(itemsList, "Webhook") + + p, err := tea.NewProgram(m, tea.WithAltScreen()).Run() + + if err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } + + if p, ok := p.(selection.Model); ok { + for _, webhook := range webhooks { + if webhook.Name == p.Choice { + choice <- webhook.ID + break + } + } + } + +} From 2b5b44ecb7ac181e86661e692854ae294e4444b5 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Wed, 21 Aug 2024 16:59:20 +0530 Subject: [PATCH 4/6] feat: add edit webhook cmd Signed-off-by: 35C4n0r --- cmd/harbor/root/webhook/cmd.go | 1 + cmd/harbor/root/webhook/delete.go | 6 +- cmd/harbor/root/webhook/edit.go | 70 +++++++++++++++ pkg/api/webhook_handler.go | 38 ++++++++ pkg/prompt/prompt.go | 10 +-- pkg/views/webhook/edit/view.go | 138 ++++++++++++++++++++++++++++++ pkg/views/webhook/select/view.go | 8 +- 7 files changed, 260 insertions(+), 11 deletions(-) create mode 100644 cmd/harbor/root/webhook/edit.go create mode 100644 pkg/views/webhook/edit/view.go diff --git a/cmd/harbor/root/webhook/cmd.go b/cmd/harbor/root/webhook/cmd.go index 2a0954aa..c1b484b6 100644 --- a/cmd/harbor/root/webhook/cmd.go +++ b/cmd/harbor/root/webhook/cmd.go @@ -13,6 +13,7 @@ func Webhook() *cobra.Command { CreateWebhookCmd(), ListWebhookCommand(), DeleteWebhookCmd(), + EditWebhookCmd(), ) return cmd } diff --git a/cmd/harbor/root/webhook/delete.go b/cmd/harbor/root/webhook/delete.go index 10bf1969..1621967f 100644 --- a/cmd/harbor/root/webhook/delete.go +++ b/cmd/harbor/root/webhook/delete.go @@ -1,6 +1,7 @@ package webhook import ( + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" "github.com/goharbor/harbor-cli/pkg/api" "github.com/goharbor/harbor-cli/pkg/prompt" log "github.com/sirupsen/logrus" @@ -13,6 +14,7 @@ func DeleteWebhookCmd() *cobra.Command { var projectName string var webhookId string var webhookIdInt int64 + var selectedWebhook models.WebhookPolicy cmd := &cobra.Command{ Use: "delete", @@ -26,8 +28,8 @@ func DeleteWebhookCmd() *cobra.Command { err = api.DeleteWebhook(projectName, webhookIdInt) } else { projectName = prompt.GetProjectNameFromUser() - webhookIdInt = prompt.GetWebhookFromUser(projectName) - err = api.DeleteWebhook(projectName, webhookIdInt) + selectedWebhook = prompt.GetWebhookFromUser(projectName) + err = api.DeleteWebhook(projectName, selectedWebhook.ID) } if err != nil { diff --git a/cmd/harbor/root/webhook/edit.go b/cmd/harbor/root/webhook/edit.go new file mode 100644 index 00000000..100c58ec --- /dev/null +++ b/cmd/harbor/root/webhook/edit.go @@ -0,0 +1,70 @@ +package webhook + +import ( + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/prompt" + "github.com/goharbor/harbor-cli/pkg/views/webhook/edit" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +func EditWebhookCmd() *cobra.Command { + var opts edit.EditView + cmd := &cobra.Command{ + Use: "edit", + Short: "webhook edit", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + var err error + editView := &edit.EditView{ + WebhookId: opts.WebhookId, + ProjectName: opts.ProjectName, + Name: opts.Name, + Description: opts.Description, + NotifyType: opts.NotifyType, + PayloadFormat: opts.PayloadFormat, + EventType: opts.EventType, + EndpointURL: opts.EndpointURL, + AuthHeader: opts.AuthHeader, + VerifyRemoteCertificate: opts.VerifyRemoteCertificate, + } + if opts.ProjectName != "" && + opts.Name != "" && + opts.NotifyType != "" && + len(opts.EventType) != 0 && + opts.EndpointURL != "" { + err = api.UpdateWebhook(&opts) + } else { + err = editWebhookView(editView) + } + + if err != nil { + log.Errorf("failed to create webhook: %v", err) + } + + }, + } + return cmd +} + +func editWebhookView(view *edit.EditView) error { + var selectedWebhook models.WebhookPolicy + view.ProjectName = prompt.GetProjectNameFromUser() + selectedWebhook = prompt.GetWebhookFromUser(view.ProjectName) + + view.WebhookId = selectedWebhook.ID + view.Description = selectedWebhook.Description + view.Enabled = selectedWebhook.Enabled + view.EventType = selectedWebhook.EventTypes + view.Name = selectedWebhook.Name + + view.EndpointURL = selectedWebhook.Targets[0].Address + view.AuthHeader = selectedWebhook.Targets[0].AuthHeader + view.PayloadFormat = string(selectedWebhook.Targets[0].PayloadFormat) + view.VerifyRemoteCertificate = !selectedWebhook.Targets[0].SkipCertVerify + view.NotifyType = selectedWebhook.Targets[0].Type + + edit.WebhookEditView(view) + return api.UpdateWebhook(view) +} diff --git a/pkg/api/webhook_handler.go b/pkg/api/webhook_handler.go index 8b71983a..8a8b9363 100644 --- a/pkg/api/webhook_handler.go +++ b/pkg/api/webhook_handler.go @@ -5,6 +5,7 @@ import ( "github.com/goharbor/go-client/pkg/sdk/v2.0/models" "github.com/goharbor/harbor-cli/pkg/utils" "github.com/goharbor/harbor-cli/pkg/views/webhook/create" + "github.com/goharbor/harbor-cli/pkg/views/webhook/edit" log "github.com/sirupsen/logrus" ) @@ -80,3 +81,40 @@ func DeleteWebhook(projectName string, webhookId int64) error { } return nil } + +func UpdateWebhook(opts *edit.EditView) error { + ctx, client, err := utils.ContextWithClient() + if err != nil { + log.Errorf("%s", err) + return err + } + + response, err := client.Webhook.UpdateWebhookPolicyOfProject(ctx, &webhook.UpdateWebhookPolicyOfProjectParams{ + ProjectNameOrID: opts.ProjectName, + WebhookPolicyID: opts.WebhookId, + Policy: &models.WebhookPolicy{ + Description: opts.Description, + Enabled: true, + EventTypes: opts.EventType, + Name: opts.Name, + Targets: []*models.WebhookTargetObject{ + { + Address: opts.EndpointURL, + AuthHeader: opts.AuthHeader, + PayloadFormat: models.PayloadFormatType(opts.PayloadFormat), + SkipCertVerify: !opts.VerifyRemoteCertificate, + Type: opts.NotifyType, + }, + }, + }, + }) + if err != nil { + log.Errorf("%s", err) + return err + } + if response != nil { + log.Infof("Webhook Id:`%s` Updated successfully", opts.WebhookId) + } + return nil + +} diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go index 116924ca..d5737075 100644 --- a/pkg/prompt/prompt.go +++ b/pkg/prompt/prompt.go @@ -1,6 +1,7 @@ package prompt import ( + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" "github.com/goharbor/harbor-cli/pkg/api" aview "github.com/goharbor/harbor-cli/pkg/views/artifact/select" tview "github.com/goharbor/harbor-cli/pkg/views/artifact/tags/select" @@ -91,15 +92,14 @@ func GetTagNameFromUser() string { return <-repoName } -func GetWebhookFromUser(projectName string) int64 { - webhookName := make(chan int64) - +func GetWebhookFromUser(projectName string) models.WebhookPolicy { + selectedWebhook := make(chan models.WebhookPolicy) go func() { res, err := api.ListWebhooks(projectName) if err != nil { log.Fatal(err) } - wview.WebhookList(res.Payload, webhookName) + wview.WebhookList(res.Payload, selectedWebhook) }() - return <-webhookName + return <-selectedWebhook } diff --git a/pkg/views/webhook/edit/view.go b/pkg/views/webhook/edit/view.go new file mode 100644 index 00000000..5b67c111 --- /dev/null +++ b/pkg/views/webhook/edit/view.go @@ -0,0 +1,138 @@ +package edit + +import ( + "errors" + "github.com/charmbracelet/huh" + "github.com/goharbor/harbor-cli/pkg/utils" + log "github.com/sirupsen/logrus" +) + +type EditView struct { + WebhookId int64 + ProjectName string + Name string + Description string + NotifyType string + PayloadFormat string + EventType []string + EndpointURL string + AuthHeader string + VerifyRemoteCertificate bool + Enabled bool +} + +func isSelected(selected []string, option string) bool { + for _, item := range selected { + if item == option { + return true + } + } + return false +} + +func WebhookEditView(editView *EditView) { + theme := huh.ThemeCharm() + err := huh.NewForm( + huh.NewGroup( + + huh.NewInput(). + Title("Name"). + Value(&editView.Name). + Validate(utils.EmptyStringValidator("Webhook Name")), + + huh.NewText(). + Title("Description"). + Value(&editView.Description), + + huh.NewSelect[string](). + Title("Notify Type"). + Options( + huh.NewOption("http", "http").Selected(editView.NotifyType == "http"), + huh.NewOption("slack", "slack").Selected(editView.NotifyType == "slack"), + ). + Value(&editView.NotifyType), + ), + ).WithTheme(theme).Run() + + if err != nil { + log.Fatal(err) + } + + if editView.NotifyType == "http" { + err = huh.NewForm( + huh.NewGroup( + huh.NewSelect[string](). + Title("Payload Format"). + Options( + huh.NewOption("Default", "Default").Selected(editView.PayloadFormat == "Default"), + huh.NewOption("CloudEvents", "CloudEvents").Selected(editView.PayloadFormat == "CloudEvents"), + ). + Value(&editView.PayloadFormat), + ), + ).WithTheme(theme).Run() + + if err != nil { + log.Fatal(err) + } + } + + err = huh.NewForm( + huh.NewGroup( + + huh.NewInput().Title("Endpoint URL"). + Value(&editView.EndpointURL). + Validate(utils.EmptyStringValidator("Endpoint URL")), + + huh.NewInput(). + Title("Auth Header"). + Value(&editView.AuthHeader), + + huh.NewMultiSelect[string](). + Title("Select Event Types"). + Options( + huh.NewOption("Artifact deleted", "DELETE_ARTIFACT"). + Selected(isSelected(editView.EventType, "DELETE_ARTIFACT")), + huh.NewOption("Artifact pulled", "PULL_ARTIFACT"). + Selected(isSelected(editView.EventType, "PULL_ARTIFACT")), + huh.NewOption("Artifact pushed", "PUSH_ARTIFACT"). + Selected(isSelected(editView.EventType, "PUSH_ARTIFACT")), + huh.NewOption("Quota exceed", "QUOTA_EXCEED"). + Selected(isSelected(editView.EventType, "QUOTA_EXCEED")), + huh.NewOption("Quota near threshold", "QUOTA_WARNING"). + Selected(isSelected(editView.EventType, "QUOTA_WARNING")), + huh.NewOption("Replication status changed", "REPLICATION"). + Selected(isSelected(editView.EventType, "REPLICATION")), + huh.NewOption("Scanning failed", "SCANNING_FAILED"). + Selected(isSelected(editView.EventType, "SCANNING_FAILED")), + huh.NewOption("Scanning finished", "SCANNING_COMPLETED"). + Selected(isSelected(editView.EventType, "SCANNING_COMPLETED")), + huh.NewOption("Scanning stopped", "SCANNING_STOPPED"). + Selected(isSelected(editView.EventType, "SCANNING_STOPPED")), + huh.NewOption("Tag retention finished", "TAG_RETENTION"). + Selected(isSelected(editView.EventType, "TAG_RETENTION")), + ). + Value(&editView.EventType). + Validate(func(args []string) error { + if len(args) == 0 { + return errors.New("please select least one of event type(s)") + } + return nil + }), + + huh.NewConfirm().Title("Verify Remote Certificate"). + Description("Determine whether the webhook should verify the certificate of a remote url "+ + "Uncheck this box when the remote url uses a self-signed or untrusted certificate."). + Affirmative("Yes"). + Negative("No"). + Value(&editView.VerifyRemoteCertificate), + ), + ).WithTheme(theme).Run() + + if editView.NotifyType == "slack" { + editView.PayloadFormat = "" + } + + if err != nil { + log.Fatal(err) + } +} diff --git a/pkg/views/webhook/select/view.go b/pkg/views/webhook/select/view.go index 19d58416..e176fe08 100644 --- a/pkg/views/webhook/select/view.go +++ b/pkg/views/webhook/select/view.go @@ -9,11 +9,11 @@ import ( "os" ) -func WebhookList(webhooks []*models.WebhookPolicy, choice chan<- int64) { +func WebhookList(webhooks []*models.WebhookPolicy, choice chan<- models.WebhookPolicy) { itemsList := make([]list.Item, len(webhooks)) - for i, webhook := range webhooks { - itemsList[i] = selection.Item(webhook.Name) + for i, item := range webhooks { + itemsList[i] = selection.Item(item.Name) } m := selection.NewModel(itemsList, "Webhook") @@ -28,7 +28,7 @@ func WebhookList(webhooks []*models.WebhookPolicy, choice chan<- int64) { if p, ok := p.(selection.Model); ok { for _, webhook := range webhooks { if webhook.Name == p.Choice { - choice <- webhook.ID + choice <- *webhook break } } From 09c9a98a389fa0480331877419e3e1ee6470d19c Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Wed, 21 Aug 2024 17:01:18 +0530 Subject: [PATCH 5/6] chore: add flags for edit webhook cmd Signed-off-by: 35C4n0r --- cmd/harbor/root/webhook/edit.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cmd/harbor/root/webhook/edit.go b/cmd/harbor/root/webhook/edit.go index 100c58ec..afe31d71 100644 --- a/cmd/harbor/root/webhook/edit.go +++ b/cmd/harbor/root/webhook/edit.go @@ -45,6 +45,19 @@ func EditWebhookCmd() *cobra.Command { }, } + flags := cmd.Flags() + + flags.StringVarP(&opts.ProjectName, "project", "", "", "Project Name") + flags.StringVarP(&opts.ProjectName, "webhook-id", "", "", "Webhook ID") + flags.StringVarP(&opts.Name, "name", "", "", "Webhook Name") + flags.StringVarP(&opts.Description, "description", "", "", "Webhook Description") + flags.StringVarP(&opts.NotifyType, "notify-type", "", "", "Notify Type (http, slack)") + flags.StringArrayVarP(&opts.EventType, "event-type", "", []string{}, "Event Types (comma separated)") + flags.StringVarP(&opts.EndpointURL, "endpoint-url", "", "", "Webhook Endpoint URL") + flags.StringVarP(&opts.PayloadFormat, "payload-format", "", "", "Payload Format (Default, CloudEvents)") + flags.StringVarP(&opts.AuthHeader, "auth-header", "", "", "Authentication Header") + flags.BoolVarP(&opts.VerifyRemoteCertificate, "verify-remote-certificate", "", true, "Verify Remote Certificate") + return cmd } From ed1634a79c31c6f95aa4aa9930f30e4ca8246bab Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Wed, 21 Aug 2024 17:10:08 +0530 Subject: [PATCH 6/6] feat: feature to disable webhooks Signed-off-by: 35C4n0r --- cmd/harbor/root/webhook/edit.go | 9 ++++++++- pkg/api/webhook_handler.go | 2 +- pkg/views/webhook/edit/view.go | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/cmd/harbor/root/webhook/edit.go b/cmd/harbor/root/webhook/edit.go index afe31d71..8cbea4d5 100644 --- a/cmd/harbor/root/webhook/edit.go +++ b/cmd/harbor/root/webhook/edit.go @@ -7,10 +7,13 @@ import ( "github.com/goharbor/harbor-cli/pkg/views/webhook/edit" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "strconv" ) func EditWebhookCmd() *cobra.Command { var opts edit.EditView + var webhookId string + var webhookIdInt int64 cmd := &cobra.Command{ Use: "edit", Short: "webhook edit", @@ -30,10 +33,13 @@ func EditWebhookCmd() *cobra.Command { VerifyRemoteCertificate: opts.VerifyRemoteCertificate, } if opts.ProjectName != "" && + webhookId != "" && opts.Name != "" && opts.NotifyType != "" && len(opts.EventType) != 0 && opts.EndpointURL != "" { + webhookIdInt, err = strconv.ParseInt(webhookId, 10, 64) + opts.WebhookId = webhookIdInt err = api.UpdateWebhook(&opts) } else { err = editWebhookView(editView) @@ -48,7 +54,7 @@ func EditWebhookCmd() *cobra.Command { flags := cmd.Flags() flags.StringVarP(&opts.ProjectName, "project", "", "", "Project Name") - flags.StringVarP(&opts.ProjectName, "webhook-id", "", "", "Webhook ID") + flags.StringVarP(&webhookId, "webhook-id", "", "", "Webhook ID") flags.StringVarP(&opts.Name, "name", "", "", "Webhook Name") flags.StringVarP(&opts.Description, "description", "", "", "Webhook Description") flags.StringVarP(&opts.NotifyType, "notify-type", "", "", "Notify Type (http, slack)") @@ -57,6 +63,7 @@ func EditWebhookCmd() *cobra.Command { flags.StringVarP(&opts.PayloadFormat, "payload-format", "", "", "Payload Format (Default, CloudEvents)") flags.StringVarP(&opts.AuthHeader, "auth-header", "", "", "Authentication Header") flags.BoolVarP(&opts.VerifyRemoteCertificate, "verify-remote-certificate", "", true, "Verify Remote Certificate") + flags.BoolVarP(&opts.Enabled, "enabled", "", true, "Webhook Enabled") return cmd } diff --git a/pkg/api/webhook_handler.go b/pkg/api/webhook_handler.go index 8a8b9363..7e89c62d 100644 --- a/pkg/api/webhook_handler.go +++ b/pkg/api/webhook_handler.go @@ -94,7 +94,7 @@ func UpdateWebhook(opts *edit.EditView) error { WebhookPolicyID: opts.WebhookId, Policy: &models.WebhookPolicy{ Description: opts.Description, - Enabled: true, + Enabled: opts.Enabled, EventTypes: opts.EventType, Name: opts.Name, Targets: []*models.WebhookTargetObject{ diff --git a/pkg/views/webhook/edit/view.go b/pkg/views/webhook/edit/view.go index 5b67c111..d8b3df72 100644 --- a/pkg/views/webhook/edit/view.go +++ b/pkg/views/webhook/edit/view.go @@ -51,6 +51,13 @@ func WebhookEditView(editView *EditView) { huh.NewOption("slack", "slack").Selected(editView.NotifyType == "slack"), ). Value(&editView.NotifyType), + + huh.NewConfirm().Title("Webhook Enabled"). + Description("Determine whether the webhook should verify the certificate of a remote url "+ + "Uncheck this box when the remote url uses a self-signed or untrusted certificate."). + Affirmative("True"). + Negative("False"). + Value(&editView.Enabled), ), ).WithTheme(theme).Run()