From 9540e19f6212881599b29393f00b991bbb87c32d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Metz?= Date: Tue, 1 Nov 2022 17:02:59 +0100 Subject: [PATCH] Add table github_dependabot_alert --- github/plugin.go | 1 + github/table_github_dependabot_alert.go | 234 ++++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 github/table_github_dependabot_alert.go diff --git a/github/plugin.go b/github/plugin.go index 65adcb5c..febc6b05 100644 --- a/github/plugin.go +++ b/github/plugin.go @@ -27,6 +27,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "github_commit": tableGitHubCommit(ctx), "github_community_profile": tableGitHubCommunityProfile(ctx), "github_code_owner": tableGitHubCodeOwner(), + "github_dependabot_alert": tableGitHubDependabotAlert(), "github_gist": tableGitHubGist(), "github_gitignore": tableGitHubGitignore(), "github_issue": tableGitHubIssue(), diff --git a/github/table_github_dependabot_alert.go b/github/table_github_dependabot_alert.go new file mode 100644 index 00000000..77229677 --- /dev/null +++ b/github/table_github_dependabot_alert.go @@ -0,0 +1,234 @@ +package github + +import ( + "context" + + "github.com/google/go-github/v45/github" + "github.com/turbot/steampipe-plugin-sdk/v4/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v4/plugin" + "github.com/turbot/steampipe-plugin-sdk/v4/plugin/transform" +) + +//// TABLE DEFINITION + +func tableGitHubDependabotAlert() *plugin.Table { + return &plugin.Table{ + Name: "github_dependabot_alert", + Description: "", + List: &plugin.ListConfig{ + KeyColumns: []*plugin.KeyColumn{ + { + Name: "repository_full_name", + Require: plugin.Required, + }, + { + Name: "state", + Require: plugin.Optional, + }, + }, + ShouldIgnoreError: isNotFoundError([]string{"404"}), + Hydrate: tableGitHubDependabotAlertList, + }, + Get: &plugin.GetConfig{ + KeyColumns: plugin.AllColumns([]string{"repository_full_name", "dependabot_number"}), + ShouldIgnoreError: isNotFoundError([]string{"404"}), + Hydrate: tableGitHubDependabotAlertGet, + }, + Columns: []*plugin.Column{ + { + Name: "repository_full_name", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("repository_full_name"), + Description: "The full name of the repository (login/repo-name).", + }, + { + Name: "dependabot_number", + Type: proto.ColumnType_INT, + Description: "The security alert number.", + Transform: transform.FromField("Number"), + }, + { + Name: "state", + Type: proto.ColumnType_STRING, + Description: "The state of the Dependabot alert.", + }, + { + Name: "dependency_package_ecosystem", + Type: proto.ColumnType_STRING, + Description: "The package's language or package management ecosystem.", + Transform: transform.FromField("Dependency.Package.Ecosystem"), + }, + { + Name: "dependency_package_name", + Type: proto.ColumnType_STRING, + Description: "The unique package name within its ecosystem.", + Transform: transform.FromField("Dependency.Package.Name"), + }, + { + Name: "dependency_manifest_path", + Type: proto.ColumnType_STRING, + Description: "The unique package name within its ecosystem.", + Transform: transform.FromField("Dependency.ManifestPath"), + }, + { + Name: "dependency_scope", + Type: proto.ColumnType_STRING, + Description: "The execution scope of the vulnerable dependency.", + Transform: transform.FromField("Dependency.Scope"), + }, + + { + Name: "url", + Type: proto.ColumnType_STRING, + Description: "The REST API URL of the alert resource.", + }, + { + Name: "html_url", + Type: proto.ColumnType_STRING, + Description: "The GitHub URL of the alert resource.", + }, + { + Name: "created_at", + Type: proto.ColumnType_TIMESTAMP, + Description: "The time that the alert was created.", + Transform: transform.FromField("CreatedAt").Transform(convertTimestamp), + }, + { + Name: "updated_at", + Type: proto.ColumnType_TIMESTAMP, + Description: "The time that the alert was last updated.", + Transform: transform.FromField("UpdatedAt").Transform(convertTimestamp), + }, + { + Name: "dismissed_at", + Type: proto.ColumnType_TIMESTAMP, + Description: "The time that the alert was dismissed.", + Transform: transform.FromField("DismissedAt").NullIfZero().Transform(convertTimestamp), + }, + { + Name: "dismissed_reason", + Type: proto.ColumnType_STRING, + Description: "The reason that the alert was dismissed.", + }, + { + Name: "dismissed_comment", + Type: proto.ColumnType_STRING, + Description: "An optional comment associated with the alert's dismissal.", + }, + { + Name: "fixed_at", + Type: proto.ColumnType_TIMESTAMP, + Description: "The time that the alert was no longer detected and was considered fixed.", + Transform: transform.FromField("FixedAt").NullIfZero().Transform(convertTimestamp), + }, + }, + } +} + +//// LIST FUNCTION + +func tableGitHubDependabotAlertList(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + quals := d.KeyColumnQuals + + fullName := quals["repository_full_name"].GetStringValue() + owner, repo := parseRepoFullName(fullName) + + opt := &github.ListAlertsOptions{ + ListCursorOptions: github.ListCursorOptions{First: 100}, + } + + if quals["state"] != nil { + state := quals["state"].GetStringValue() + opt.State = &state + } + + type ListPageResponse struct { + alerts []*github.DependabotAlert + resp *github.Response + } + + client := connect(ctx, d) + + limit := d.QueryContext.Limit + if limit != nil { + if *limit < int64(opt.ListCursorOptions.First) { + opt.ListCursorOptions.First = int(*limit) + } + } + + listPage := func(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + alerts, resp, err := client.Dependabot.ListRepoAlerts(ctx, owner, repo, opt) + return ListPageResponse{ + alerts: alerts, + resp: resp, + }, err + } + for { + listPageResponse, err := retryHydrate(ctx, d, h, listPage) + + if err != nil { + return nil, err + } + + listResponse := listPageResponse.(ListPageResponse) + alerts := listResponse.alerts + resp := listResponse.resp + + for _, i := range alerts { + d.StreamListItem(ctx, i) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.QueryStatus.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + + if resp.After == "" { + break + } + + opt.ListCursorOptions.After = resp.After + } + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func tableGitHubDependabotAlertGet(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + var owner, repo string + var alertNumber int + + logger := plugin.Logger(ctx) + quals := d.KeyColumnQuals + + alertNumber = int(d.KeyColumnQuals["dependabot_number"].GetInt64Value()) + fullName := quals["repository_full_name"].GetStringValue() + owner, repo = parseRepoFullName(fullName) + logger.Trace("tableGitHubDependabotAlertGet", "owner", owner, "repo", repo, "alertNumber", alertNumber) + + client := connect(ctx, d) + + type GetResponse struct { + alert *github.DependabotAlert + resp *github.Response + } + + getDetails := func(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + alert, resp, err := client.Dependabot.GetRepoAlert(ctx, owner, repo, alertNumber) + return GetResponse{ + alert: alert, + resp: resp, + }, err + } + + getResponse, err := retryHydrate(ctx, d, h, getDetails) + if err != nil { + return nil, err + } + + getResp := getResponse.(GetResponse) + alert := getResp.alert + + return alert, nil +}