-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add github_code_owner based on CODEOWNERS files
- Loading branch information
1 parent
3e8e9f3
commit 790c7c2
Showing
3 changed files
with
215 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# Table: github_code_owner | ||
|
||
All of your code owners rules defined in your repository, in CODEOWNERS files. | ||
|
||
The `github_code_owner` table can be used to query information about **ANY** repository, and **you must specify which repository** in the where or join clause (`where repository_full_name=`, `join github_code_owner on repository_full_name=`). | ||
|
||
## Examples | ||
|
||
### Get All your CodeOwners rules about a specific repository | ||
|
||
```sql | ||
select | ||
line, | ||
repository_full_name, | ||
pattern, | ||
users, | ||
teams, | ||
pre_comments, | ||
line_comment | ||
from | ||
github.github_code_owner | ||
where | ||
repository_full_name = 'my-orga/code-owners' | ||
order by | ||
line asc | ||
``` | ||
|
||
**CODEOWNERS file content** | ||
|
||
``` | ||
# fooo Comment | ||
# here | ||
README @super-user | ||
.github @super-user @my-orga/my-team | ||
# another comment | ||
# hello | ||
.cloud @super-user @super-user # line comment here | ||
.cloud/terraform @super-user # line comment here 2 | ||
/apps/github @super-user | ||
``` | ||
|
||
**ResultSet** | ||
|
||
| line | repository\_full\_name | pattern | users | teams | pre\_comments | line\_comment | | ||
| ---- |------------------------| ---------------- | ------------------------------- | ---------------------- | --------------------------------- |---------------------| | ||
| 3 | my-orga/code-owners | README | \["@super-user"\] | <null> | \["# fooo Comment","# here"\] | | | ||
| 4 | my-orga/code-owners | .github | \["@super-user"\] | \["@my-orga/my-team"\] | \["# fooo Comment","# here"\] | | | ||
| 8 | my-orga/code-owners | .cloud | \["@super-user","@super-user"\] | <null> | \["# another comment","# hello"\] | line comment here | | ||
| 9 | my-orga/code-owners | .cloud/terraform | \["@super-user"\] | <null> | \["# another comment","# hello"\] | line comment here 2 | | ||
| 11 | my-orga/code-owners | /apps/github | \["@super-user"\] | <null> | \[\] | | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
package github | ||
|
||
import ( | ||
"context" | ||
"github.com/google/go-github/v45/github" | ||
"github.com/turbot/steampipe-plugin-sdk/v4/plugin/transform" | ||
"strings" | ||
|
||
"github.com/turbot/steampipe-plugin-sdk/v4/grpc/proto" | ||
"github.com/turbot/steampipe-plugin-sdk/v4/plugin" | ||
) | ||
|
||
//// TABLE DEFINITION | ||
|
||
func gitHubCodeOwnerColumns() []*plugin.Column { | ||
return []*plugin.Column{ | ||
// Top columns | ||
{Name: "repository_full_name", Type: proto.ColumnType_STRING, Description: "The full name of the repository, including the owner and repo name."}, | ||
// Other columns | ||
{Name: "line", Type: proto.ColumnType_INT, Description: "The rule's line number in the CODEOWNERS file.", Transform: transform.FromField("LineNumber")}, | ||
{Name: "pattern", Type: proto.ColumnType_STRING, Description: "The pattern used to identify what code a team, or an individual is responsible for"}, | ||
{Name: "users", Type: proto.ColumnType_JSON, Description: "Users responsible for code in the repo"}, | ||
{Name: "teams", Type: proto.ColumnType_JSON, Description: "Teams responsible for code in the repo"}, | ||
{Name: "pre_comments", Type: proto.ColumnType_JSON, Description: "Specifies the comments added above a key."}, | ||
{Name: "line_comment", Type: proto.ColumnType_STRING, Description: "Specifies the comment following the node and before empty lines."}, | ||
} | ||
} | ||
|
||
//// TABLE DEFINITION | ||
|
||
func tableGithubCodeOwner() *plugin.Table { | ||
return &plugin.Table{ | ||
Name: "github_code_owner", | ||
Description: "Individuals or teams that are responsible for code in a repository.", | ||
List: &plugin.ListConfig{ | ||
Hydrate: tableGitHubCodeOwnerList, | ||
ShouldIgnoreError: isNotFoundError([]string{"404"}), | ||
KeyColumns: plugin.SingleColumn("repository_full_name"), | ||
}, | ||
Columns: gitHubCodeOwnerColumns(), | ||
} | ||
} | ||
|
||
// // LIST FUNCTION | ||
type CodeOwnerRule struct { | ||
LineNumber int | ||
Pattern string | ||
Users []string | ||
Teams []string | ||
PreComments []string | ||
LineComment string | ||
} | ||
|
||
func tableGitHubCodeOwnerList(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { | ||
plugin.Logger(ctx).Trace("tableGitHubCodeOwnerList") | ||
repoFullName := d.KeyColumnQuals["repository_full_name"].GetStringValue() | ||
owner, repoName := parseRepoFullName(repoFullName) | ||
|
||
type CodeOwnerRuleResponse struct { | ||
RepositoryFullName string | ||
LineNumber int | ||
Pattern string | ||
Users []string | ||
Teams []string | ||
PreComments []string | ||
LineComment string | ||
} | ||
getCodeOwners := func(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { | ||
var fileContent *github.RepositoryContent | ||
var err error | ||
|
||
client := connect(ctx, d) | ||
opt := &github.RepositoryContentGetOptions{} | ||
// stop on the first found CODEOWNERS file | ||
var paths = []string{".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"} | ||
for _, path := range paths { | ||
fileContent, _, _, err = client.Repositories.GetContents(ctx, owner, repoName, path, opt) | ||
if err == nil { | ||
break | ||
} | ||
} | ||
|
||
// no CODEOWNERS file | ||
if err != nil { | ||
return []CodeOwnerRuleResponse{}, err | ||
} | ||
|
||
decodedContent, err := fileContent.GetContent() | ||
if err != nil { | ||
return []CodeOwnerRuleResponse{}, err | ||
} | ||
|
||
return decodeCodeOwnerFileContent(decodedContent), err | ||
} | ||
|
||
codeOwnersElements, err := retryHydrate(ctx, d, h, getCodeOwners) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
for _, codeOwner := range codeOwnersElements.([]*CodeOwnerRule) { | ||
if codeOwner != nil { | ||
d.StreamListItem(ctx, CodeOwnerRuleResponse{ | ||
RepositoryFullName: repoFullName, | ||
LineNumber: codeOwner.LineNumber, | ||
Pattern: codeOwner.Pattern, | ||
Users: codeOwner.Users, | ||
Teams: codeOwner.Teams, | ||
PreComments: codeOwner.PreComments, | ||
LineComment: codeOwner.LineComment, | ||
}) | ||
} | ||
} | ||
return nil, nil | ||
} | ||
|
||
func decodeCodeOwnerFileContent(content string) []*CodeOwnerRule { | ||
var codeOwnerRules []*CodeOwnerRule | ||
|
||
var comments []string | ||
for i, line := range strings.Split(content, "\n") { | ||
lineNumber := i + 1 | ||
if len(line) == 0 { | ||
comments = []string{} | ||
continue | ||
} | ||
// code block with comments | ||
if strings.HasPrefix(line, "#") { | ||
comments = append(comments, line) | ||
continue | ||
} | ||
// code owners rule | ||
rule := strings.SplitN(line, " ", 2) | ||
if len(rule) < 2 { | ||
comments = []string{} | ||
continue | ||
} | ||
|
||
var pattern, lineComment string | ||
pattern = rule[0] | ||
|
||
// line comment | ||
ownersAndComment := strings.SplitN(rule[1], "#", 2) | ||
if len(ownersAndComment) == 2 && len(ownersAndComment[1]) > 0 { | ||
lineComment = ownersAndComment[1] | ||
} else { | ||
ownersAndComment = []string{rule[1]} | ||
} | ||
|
||
// owners computing | ||
var users, teams []string | ||
for _, owner := range strings.Split(strings.TrimSpace(ownersAndComment[0]), " ") { | ||
if strings.Index(owner, "/") > 0 { | ||
teams = append(teams, owner) | ||
} else { | ||
users = append(users, owner) | ||
} | ||
} | ||
codeOwnerRules = append(codeOwnerRules, &CodeOwnerRule{LineNumber: lineNumber, Pattern: pattern, Users: users, Teams: teams, PreComments: comments, LineComment: lineComment}) | ||
} | ||
return codeOwnerRules | ||
} |