diff --git a/docs/tables/github_actions_repository_runner.md b/docs/tables/github_actions_repository_runner.md new file mode 100644 index 00000000..f90639ec --- /dev/null +++ b/docs/tables/github_actions_repository_runner.md @@ -0,0 +1,47 @@ +# Table: github_actions_repository_runner + +A runner is a server that runs your workflows when they're triggered. Each runner can run a single job at a time. Self-hosted runners offer more control of hardware, operating system, and software tools than GitHub-hosted runners provide. + +The `github_actions_repository_runner` table can be used to query information about any self-hosted runner, and **you must specify which repository** in the where or join clause using the `repository_full_name` column. + +## Examples + +### List runners + +```sql +select + * +from + github_actions_repository_runner +where + repository_full_name = 'turbot/steampipe'; +``` + +### List runners with mac operating system + +```sql +select + repository_full_name, + id, + name, + os +from + github_actions_repository_runner +where + repository_full_name = 'turbot/steampipe' and os = 'macos'; +``` + +### List runners which are in use currently + +```sql +select + repository_full_name, + id, + name, + os, + busy +from + github_actions_repository_runner +where + repository_full_name = 'turbot/steampipe' and busy; +``` \ No newline at end of file diff --git a/github/plugin.go b/github/plugin.go index 780134ba..342b03ec 100644 --- a/github/plugin.go +++ b/github/plugin.go @@ -17,40 +17,41 @@ func Plugin(ctx context.Context) *plugin.Plugin { }, DefaultTransform: transform.FromGo(), TableMap: map[string]*plugin.Table{ + "github_actions_repository_runner": tableGitHubActionsRepositoryRunner(ctx), "github_actions_repository_secret": tableGitHubActionsRepositorySecret(ctx), - "github_branch": tableGitHubBranch(ctx), - "github_branch_protection": tableGitHubBranchProtection(ctx), - "github_commit": tableGitHubCommit(ctx), - "github_community_profile": tableGitHubCommunityProfile(ctx), - "github_gist": tableGitHubGist(), - "github_gitignore": tableGitHubGitignore(), - "github_issue": tableGitHubIssue(), - "github_license": tableGitHubLicense(), - "github_my_gist": tableGitHubMyGist(), - "github_my_issue": tableGitHubMyIssue(), - "github_my_organization": tableGitHubMyOrganization(), - "github_my_repository": tableGitHubMyRepository(), - "github_my_star": tableGitHubMyStar(), - "github_my_team": tableGitHubMyTeam(), - "github_organization": tableGitHubOrganization(), - "github_pull_request": tableGitHubPullRequest(), - "github_rate_limit": tableGitHubRateLimit(ctx), - "github_release": tableGitHubRelease(ctx), - "github_repository": tableGitHubRepository(), - "github_search_code": tableGitHubSearchCode(ctx), - "github_search_commit": tableGitHubSearchCommit(ctx), - "github_search_issue": tableGitHubSearchIssue(ctx), - "github_search_label": tableGitHubSearchLable(ctx), - "github_search_pull_request": tableGitHubSearchPullRequest(ctx), - "github_search_repository": tableGitHubSearchRepository(ctx), - "github_search_topic": tableGitHubSearchTopic(ctx), - "github_search_user": tableGitHubSearchUser(ctx), - "github_stargazer": tableGitHubStargazer(ctx), - "github_tag": tableGitHubTag(ctx), - "github_traffic_view_daily": tableGitHubTrafficViewDaily(ctx), - "github_traffic_view_weekly": tableGitHubTrafficViewWeekly(ctx), - "github_user": tableGitHubUser(), - "github_workflow": tableGitHubWorkflow(ctx), + "github_branch": tableGitHubBranch(ctx), + "github_branch_protection": tableGitHubBranchProtection(ctx), + "github_commit": tableGitHubCommit(ctx), + "github_community_profile": tableGitHubCommunityProfile(ctx), + "github_gist": tableGitHubGist(), + "github_gitignore": tableGitHubGitignore(), + "github_issue": tableGitHubIssue(), + "github_license": tableGitHubLicense(), + "github_my_gist": tableGitHubMyGist(), + "github_my_issue": tableGitHubMyIssue(), + "github_my_organization": tableGitHubMyOrganization(), + "github_my_repository": tableGitHubMyRepository(), + "github_my_star": tableGitHubMyStar(), + "github_my_team": tableGitHubMyTeam(), + "github_organization": tableGitHubOrganization(), + "github_pull_request": tableGitHubPullRequest(), + "github_rate_limit": tableGitHubRateLimit(ctx), + "github_release": tableGitHubRelease(ctx), + "github_repository": tableGitHubRepository(), + "github_search_code": tableGitHubSearchCode(ctx), + "github_search_commit": tableGitHubSearchCommit(ctx), + "github_search_issue": tableGitHubSearchIssue(ctx), + "github_search_label": tableGitHubSearchLable(ctx), + "github_search_pull_request": tableGitHubSearchPullRequest(ctx), + "github_search_repository": tableGitHubSearchRepository(ctx), + "github_search_topic": tableGitHubSearchTopic(ctx), + "github_search_user": tableGitHubSearchUser(ctx), + "github_stargazer": tableGitHubStargazer(ctx), + "github_tag": tableGitHubTag(ctx), + "github_traffic_view_daily": tableGitHubTrafficViewDaily(ctx), + "github_traffic_view_weekly": tableGitHubTrafficViewWeekly(ctx), + "github_user": tableGitHubUser(), + "github_workflow": tableGitHubWorkflow(ctx), }, } return p diff --git a/github/table_github_actions_repository_runner.go b/github/table_github_actions_repository_runner.go new file mode 100644 index 00000000..1b2e5669 --- /dev/null +++ b/github/table_github_actions_repository_runner.go @@ -0,0 +1,140 @@ +package github + +import ( + "context" + + "github.com/google/go-github/v33/github" + + "github.com/turbot/steampipe-plugin-sdk/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/plugin" + "github.com/turbot/steampipe-plugin-sdk/plugin/transform" +) + +//// TABLE DEFINITION + +func tableGitHubActionsRepositoryRunner(ctx context.Context) *plugin.Table { + return &plugin.Table{ + Name: "github_actions_repository_runner", + Description: "The runner is the application that runs a job from a GitHub Actions workflow", + List: &plugin.ListConfig{ + KeyColumns: plugin.SingleColumn("repository_full_name"), + ShouldIgnoreError: isNotFoundError([]string{"404"}), + Hydrate: tableGitHubRunnerList, + }, + Get: &plugin.GetConfig{ + KeyColumns: plugin.AllColumns([]string{"repository_full_name", "id"}), + ShouldIgnoreError: isNotFoundError([]string{"404"}), + Hydrate: tableGitHubRunnerGet, + }, + Columns: []*plugin.Column{ + // Top columns + {Name: "repository_full_name", Type: proto.ColumnType_STRING, Transform: transform.FromQual("repository_full_name"), Description: "Full name of the repository that contains the runners."}, + {Name: "id", Type: proto.ColumnType_INT, Transform: transform.FromGo(), Description: "The unique identifier of the runner."}, + {Name: "name", Type: proto.ColumnType_STRING, Description: "The name of the runner."}, + {Name: "os", Type: proto.ColumnType_STRING, Transform: transform.FromField("OS"), Description: "The operating system of the runner."}, + {Name: "status", Type: proto.ColumnType_STRING, Description: "The status of the runner."}, + {Name: "busy", Type: proto.ColumnType_BOOL, Description: "Indicates wheather the runner is currently in use or not."}, + {Name: "labels", Type: proto.ColumnType_JSON, Description: "Labels represents a collection of labels attached to each runner."}, + }, + } +} + +//// LIST FUNCTION + +func tableGitHubRunnerList(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + client := connect(ctx, d) + + orgName := d.KeyColumnQuals["repository_full_name"].GetStringValue() + owner, repo := parseRepoFullName(orgName) + + type ListPageResponse struct { + runners *github.Runners + resp *github.Response + } + + opts := &github.ListOptions{PerPage: 100} + + limit := d.QueryContext.Limit + if limit != nil { + if *limit < int64(opts.PerPage) { + opts.PerPage = int(*limit) + } + } + + listPage := func(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + runners, resp, err := client.Actions.ListRunners(ctx, owner, repo, opts) + return ListPageResponse{ + runners: runners, + resp: resp, + }, err + } + + for { + listPageResponse, err := plugin.RetryHydrate(ctx, d, h, listPage, &plugin.RetryConfig{ShouldRetryError: shouldRetryError}) + if err != nil { + return nil, err + } + + listResponse := listPageResponse.(ListPageResponse) + runners := listResponse.runners + resp := listResponse.resp + + for _, i := range runners.Runners { + if i != nil { + 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.NextPage == 0 { + break + } + + opts.Page = resp.NextPage + } + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func tableGitHubRunnerGet(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + runnerId := d.KeyColumnQuals["id"].GetInt64Value() + orgName := d.KeyColumnQuals["repository_full_name"].GetStringValue() + + // Empty check for the parameter + if runnerId == 0 || orgName == "" { + return nil, nil + } + + owner, repo := parseRepoFullName(orgName) + plugin.Logger(ctx).Trace("tableGitHubRunnerGet", "owner", owner, "repo", repo, "runnerId", runnerId) + + client := connect(ctx, d) + + type GetResponse struct { + runner *github.Runner + resp *github.Response + } + + getDetails := func(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + detail, resp, err := client.Actions.GetRunner(ctx, owner, repo, runnerId) + return GetResponse{ + runner: detail, + resp: resp, + }, err + } + + getResponse, err := plugin.RetryHydrate(ctx, d, h, getDetails, &plugin.RetryConfig{ShouldRetryError: shouldRetryError}) + if err != nil { + return nil, err + } + + getResp := getResponse.(GetResponse) + + return getResp.runner, nil +}