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: repository environment & deployment tables #282

Merged
merged 2 commits into from
Jun 14, 2023
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
57 changes: 57 additions & 0 deletions docs/tables/github_repository_deployment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Table: github_repository_deployment

The `github_repository_deployment` table can be used to query deployments for a specific repository.

**You must specify `repository_full_name` in the WHERE or JOIN clause.**

## Examples

### List deployments for a repository

```sql
select
id,
node_id,
sha,
created_at,
creator ->> 'login' as creator_login,
commit_sha,
description,
environment,
latest_status,
payload,
ref ->> 'prefix' as ref_prefix,
ref ->> 'name' as ref_name,
state,
task,
updated_at
from
github_repository_deployment
where
repository_full_name = 'turbot/steampipe';
```

### List deployments for all your repositories

```sql
select
id,
node_id,
sha,
created_at,
creator ->> 'login' as creator_login,
commit_sha,
description,
environment,
latest_status,
payload,
ref ->> 'prefix' as ref_prefix,
ref ->> 'name' as ref_name,
state,
task,
updated_at
from
github_repository_deployment
where
repository_full_name IN (select name_with_owner from github_my_repository);
```
33 changes: 33 additions & 0 deletions docs/tables/github_repository_environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Table: github_repository_environment

The `github_repository_environment` table can be used to query environments belonging to a repository.

**You must specify `repository_full_name` in the WHERE or JOIN clause.**

## Examples

### List environments for a repository

```sql
select
id,
node_id,
name
from
github_repository_environment
where
repository_full_name = 'turbot/steampipe';
```

### List environments all your repositories

```sql
select
id,
node_id,
name
from
github_repository_environment
where
repository_full_name IN (select name_with_owner from github_my_repository);
```
37 changes: 37 additions & 0 deletions github/models/deployment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package models

import "github.com/shurcooL/githubv4"

type Environment struct {
basicIdentifiers
// protectionRules [pageable]
}

type Deployment struct {
Id int `graphql:"id: databaseId" json:"id,omitempty"`
NodeId string `graphql:"nodeId: id" json:"node_id,omitempty"`
CommitSha string `graphql:"sha: commitOid" json:"sha"`
CreatedAt NullableTime `json:"created_at"`
Creator Actor `json:"creator"`
Description string `json:"description"`
Environment string `json:"environment"`
LatestEnvironment string `json:"latest_environment"`
LatestStatus DeploymentStatus `json:"latest_status"`
OriginalEnvironment string `json:"original_environment"`
Payload string `json:"payload"`
Ref BasicRef `json:"ref"`
State githubv4.DeploymentState `json:"state"`
Task string `json:"task"`
UpdatedAt NullableTime `json:"updated_at"`
}

type DeploymentStatus struct {
NodeId string `graphql:"nodeId: id" json:"node_id,omitempty"`
CreatedAt NullableTime `json:"created_at"`
Creator Actor `json:"creator"`
Description string `json:"description"`
EnvironmentUrl string `json:"environment_url"`
LogUrl string `json:"log_url"`
State githubv4.DeploymentStatusState `json:"state"`
UpdatedAt NullableTime `json:"updated_at"`
}
2 changes: 2 additions & 0 deletions github/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ func Plugin(ctx context.Context) *plugin.Plugin {
"github_repository": tableGitHubRepository(),
"github_repository_collaborator": tableGitHubRepositoryCollaborator(),
"github_repository_dependabot_alert": tableGitHubRepositoryDependabotAlert(),
"github_repository_deployment": tableGitHubRepositoryDeployment(),
"github_repository_environment": tableGitHubRepositoryEnvironment(),
"github_search_code": tableGitHubSearchCode(ctx),
"github_search_commit": tableGitHubSearchCommit(ctx),
"github_search_issue": tableGitHubSearchIssue(ctx),
Expand Down
106 changes: 106 additions & 0 deletions github/table_github_repository_deployment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package github

import (
"context"
"github.com/shurcooL/githubv4"
"github.com/turbot/steampipe-plugin-github/github/models"
"github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto"
"github.com/turbot/steampipe-plugin-sdk/v5/plugin"
"github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform"
)

func gitHubRepositoryDeploymentColumns() []*plugin.Column {
return []*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: "id", Type: proto.ColumnType_INT, Transform: transform.FromField("Id", "Node.Id"), Description: "The ID of the deployment."},
{Name: "node_id", Type: proto.ColumnType_STRING, Transform: transform.FromField("NodeId", "Node.NodeId"), Description: "The node ID of the deployment."},
{Name: "commit_sha", Type: proto.ColumnType_STRING, Transform: transform.FromField("CommitSha", "Node.CommitSha"), Description: "SHA of the commit the deployment is using."},
{Name: "created_at", Type: proto.ColumnType_TIMESTAMP, Transform: transform.FromField("CreatedAt", "Node.CreatedAt").NullIfZero().Transform(convertTimestamp), Description: "Timestamp when the deployment was created."},
{Name: "creator", Type: proto.ColumnType_JSON, Transform: transform.FromField("Creator", "Node.Creator").NullIfZero(), Description: "The deployment creator."},
{Name: "description", Type: proto.ColumnType_STRING, Transform: transform.FromField("Description", "Node.Description"), Description: "The description of the deployment."},
{Name: "environment", Type: proto.ColumnType_STRING, Transform: transform.FromField("Environment", "Node.Environment"), Description: "The name of the environment to which the deployment was made."},
{Name: "latest_environment", Type: proto.ColumnType_STRING, Transform: transform.FromField("LatestEnvironment", "Node.LatestEnvironment"), Description: "The name of the latest environment to which the deployment was made."},
{Name: "latest_status", Type: proto.ColumnType_JSON, Transform: transform.FromField("LatestStatus", "Node.LatestStatus").NullIfZero(), Description: "The latest status of the deployment."},
{Name: "original_environment", Type: proto.ColumnType_STRING, Transform: transform.FromField("OriginalEnvironment", "Node.OriginalEnvironment"), Description: "The original environment to which this deployment was made."},
{Name: "payload", Type: proto.ColumnType_STRING, Transform: transform.FromField("Payload", "Node.Payload"), Description: "Extra information that a deployment system might need."},
{Name: "ref", Type: proto.ColumnType_JSON, Transform: transform.FromField("Ref", "Node.Ref").NullIfZero(), Description: "Identifies the Ref of the deployment, if the deployment was created by ref."},
{Name: "state", Type: proto.ColumnType_STRING, Transform: transform.FromField("State", "Node.State"), Description: "The current state of the deployment."},
{Name: "task", Type: proto.ColumnType_STRING, Transform: transform.FromField("Task", "Node.Task"), Description: "The deployment task."},
{Name: "updated_at", Type: proto.ColumnType_TIMESTAMP, Transform: transform.FromField("UpdatedAt", "Node.UpdatedAt").NullIfZero().Transform(convertTimestamp), Description: "Timestamp when the deployment was last updated."},
}
}

func tableGitHubRepositoryDeployment() *plugin.Table {
return &plugin.Table{
Name: "github_repository_deployment",
Description: "GitHub Deployments are releases of the app/service/etc to an environment.",
List: &plugin.ListConfig{
KeyColumns: []*plugin.KeyColumn{
{
Name: "repository_full_name",
Require: plugin.Required,
},
},
ShouldIgnoreError: isNotFoundError([]string{"404"}),
Hydrate: tableGitHubRepositoryDeploymentList,
},
Columns: gitHubRepositoryDeploymentColumns(),
}
}

func tableGitHubRepositoryDeploymentList(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
quals := d.EqualsQuals
fullName := quals["repository_full_name"].GetStringValue()
owner, repoName := parseRepoFullName(fullName)

pageSize := adjustPageSize(100, d.QueryContext.Limit)

var query struct {
RateLimit models.RateLimit
Repository struct {
Deployments struct {
PageInfo models.PageInfo
TotalCount int
Nodes []models.Deployment
} `graphql:"deployments(first: $pageSize, after: $cursor)"`
} `graphql:"repository(owner: $owner, name: $name)"`
}

variables := map[string]interface{}{
"owner": githubv4.String(owner),
"name": githubv4.String(repoName),
"pageSize": githubv4.Int(pageSize),
"cursor": (*githubv4.String)(nil),
}

client := connectV4(ctx, d)

listPage := func(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
return nil, client.Query(ctx, &query, variables)
}

for {
_, err := plugin.RetryHydrate(ctx, d, h, listPage, retryConfig())
plugin.Logger(ctx).Debug(rateLimitLogString("github_repository_deployment", &query.RateLimit))
if err != nil {
plugin.Logger(ctx).Error("github_repository_deployment", "api_error", err)
return nil, err
}

for _, deployment := range query.Repository.Deployments.Nodes {
d.StreamListItem(ctx, deployment)

// Context can be cancelled due to manual cancellation or the limit has been hit
if d.RowsRemaining(ctx) == 0 {
return nil, nil
}
}

if !query.Repository.Deployments.PageInfo.HasNextPage {
break
}
variables["cursor"] = githubv4.NewString(query.Repository.Deployments.PageInfo.EndCursor)
}

return nil, nil
}
94 changes: 94 additions & 0 deletions github/table_github_repository_environment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package github

import (
"context"
"github.com/shurcooL/githubv4"
"github.com/turbot/steampipe-plugin-github/github/models"
"github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto"
"github.com/turbot/steampipe-plugin-sdk/v5/plugin"
"github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform"
)

func gitHubRepositoryEnvironmentColumns() []*plugin.Column {
return []*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: "id", Type: proto.ColumnType_INT, Transform: transform.FromField("Id", "Node.Id"), Description: "The ID of the environment."},
{Name: "node_id", Type: proto.ColumnType_STRING, Transform: transform.FromField("NodeId", "Node.NodeId"), Description: "The node ID of the environment."},
{Name: "name", Type: proto.ColumnType_STRING, Transform: transform.FromField("Name", "Node.Name"), Description: "The name of the environment."},
}
}

func tableGitHubRepositoryEnvironment() *plugin.Table {
return &plugin.Table{
Name: "github_repository_environment",
Description: "GitHub Environments are named deployment targets, usually isolated for usage such as test, prod, staging, etc.",
List: &plugin.ListConfig{
KeyColumns: []*plugin.KeyColumn{
{
Name: "repository_full_name",
Require: plugin.Required,
},
},
ShouldIgnoreError: isNotFoundError([]string{"404"}),
Hydrate: tableGitHubRepositoryEnvironmentList,
},
Columns: gitHubRepositoryEnvironmentColumns(),
}
}

func tableGitHubRepositoryEnvironmentList(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
quals := d.EqualsQuals
fullName := quals["repository_full_name"].GetStringValue()
owner, repoName := parseRepoFullName(fullName)

pageSize := adjustPageSize(100, d.QueryContext.Limit)

var query struct {
RateLimit models.RateLimit
Repository struct {
Environments struct {
PageInfo models.PageInfo
TotalCount int
Nodes []models.Environment
} `graphql:"environments(first: $pageSize, after: $cursor)"`
} `graphql:"repository(owner: $owner, name: $name)"`
}

variables := map[string]interface{}{
"owner": githubv4.String(owner),
"name": githubv4.String(repoName),
"pageSize": githubv4.Int(pageSize),
"cursor": (*githubv4.String)(nil),
}

client := connectV4(ctx, d)

listPage := func(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
return nil, client.Query(ctx, &query, variables)
}

for {
_, err := plugin.RetryHydrate(ctx, d, h, listPage, retryConfig())
plugin.Logger(ctx).Debug(rateLimitLogString("github_repository_environment", &query.RateLimit))
if err != nil {
plugin.Logger(ctx).Error("github_repository_environment", "api_error", err)
return nil, err
}

for _, environment := range query.Repository.Environments.Nodes {
d.StreamListItem(ctx, environment)

// Context can be cancelled due to manual cancellation or the limit has been hit
if d.RowsRemaining(ctx) == 0 {
return nil, nil
}
}

if !query.Repository.Environments.PageInfo.HasNextPage {
break
}
variables["cursor"] = githubv4.NewString(query.Repository.Environments.PageInfo.EndCursor)
}

return nil, nil
}