From 77bfdf24d68bfc6395fff9e64308cbfa5af9a8e6 Mon Sep 17 00:00:00 2001 From: "lloyd.jones" <4392567+Recurzion@users.noreply.github.com> Date: Tue, 14 Jul 2026 13:15:53 -0400 Subject: [PATCH 1/3] new custom props --- docs/tables/github_my_repository.md | 25 +++++++++++- docs/tables/github_repository.md | 25 +++++++++++- docs/tables/github_search_repository.md | 25 ++++++++++++ docs/tables/github_team_repository.md | 29 +++++++++++++- github/table_github_repository.go | 52 +++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 3 deletions(-) diff --git a/docs/tables/github_my_repository.md b/docs/tables/github_my_repository.md index 80575a3..5af7a26 100644 --- a/docs/tables/github_my_repository.md +++ b/docs/tables/github_my_repository.md @@ -220,4 +220,27 @@ where json_extract(hook.value, '$.config.insecure_ssl') = '1' or json_extract(hook.value, '$.config.secret') is null or json_extract(hook.value, '$.config.url') not like '%https:%'; -``` \ No newline at end of file +``` + +### List repositories with custom property values +Explore custom property key/value pairs configured on repositories you can access. + +```sql+postgres +select + name_with_owner, + custom_properties +from + github_my_repository +where + custom_properties is not null; +``` + +```sql+sqlite +select + name_with_owner, + custom_properties +from + github_my_repository +where + custom_properties is not null; +``` diff --git a/docs/tables/github_repository.md b/docs/tables/github_repository.md index 480b24a..d481e04 100644 --- a/docs/tables/github_repository.md +++ b/docs/tables/github_repository.md @@ -101,4 +101,27 @@ from github_repository where full_name = 'turbot/steampipe'; -``` \ No newline at end of file +``` + +### Get custom property values for a repository +Explore the custom property key/value pairs assigned to a repository. + +```sql+postgres +select + full_name, + custom_properties +from + github_repository +where + full_name = 'my-org/my-repo'; +``` + +```sql+sqlite +select + full_name, + custom_properties +from + github_repository +where + full_name = 'my-org/my-repo'; +``` diff --git a/docs/tables/github_search_repository.md b/docs/tables/github_search_repository.md index 258c111..b51a539 100644 --- a/docs/tables/github_search_repository.md +++ b/docs/tables/github_search_repository.md @@ -136,6 +136,31 @@ where query = 'tinyspotifyr in:name created:2021-01-01..2021-01-05 fork:only'; ``` +### Get custom property values from search results +Explore custom property key/value pairs for repositories returned by a search query. + +```sql+postgres +select + name_with_owner, + custom_properties +from + github_search_repository +where + query = 'org:my-org archived:false' + and custom_properties is not null; +``` + +```sql+sqlite +select + name_with_owner, + custom_properties +from + github_search_repository +where + query = 'org:my-org archived:false' + and custom_properties is not null; +``` + ```sql+sqlite select name, diff --git a/docs/tables/github_team_repository.md b/docs/tables/github_team_repository.md index 0282d49..c653802 100644 --- a/docs/tables/github_team_repository.md +++ b/docs/tables/github_team_repository.md @@ -65,7 +65,6 @@ where organization = 'my_org' and slug = 'my-team'; ``` - ### List visible teams and repositories they have admin permissions to Explore the teams and associated repositories within your organization that have administrative permissions. This is useful to ensure appropriate access rights and maintain security within your GitHub organization. @@ -105,4 +104,32 @@ where organization = 'my_org' and slug = 'my-team' and permission = 'ADMIN'; +``` + +### List custom property values for a team's repositories +Explore custom property key/value pairs for repositories visible to a team. + +```sql+postgres +select + organization, + slug, + name, + custom_properties +from + github_team_repository +where + organization = 'my_org' + and slug = 'my-team'; +``` +```sql+sqlite +select + organization, + slug, + name, + custom_properties +from + github_team_repository +where + organization = 'my_org' + and slug = 'my-team'; ``` \ No newline at end of file diff --git a/github/table_github_repository.go b/github/table_github_repository.go index b660dab..eeedf50 100644 --- a/github/table_github_repository.go +++ b/github/table_github_repository.go @@ -19,6 +19,16 @@ func gitHubRepositoryColumns() []*plugin.Column { return append(repoColumns, sharedRepositoryColumns()...) } +func repositoryCustomPropertiesColumn() *plugin.Column { + return &plugin.Column{ + Name: "custom_properties", + Type: proto.ColumnType_JSON, + Description: "A map of custom property names and values assigned to the repository.", + Hydrate: hydrateRepositoryCustomPropertiesFromV3, + Transform: transform.FromValue(), + } +} + func sharedRepositoryColumns() []*plugin.Column { return []*plugin.Column{ {Name: "id", Type: proto.ColumnType_INT, Description: "The numeric ID of the repository.", Transform: transform.FromField("Id", "Node.Id")}, @@ -94,6 +104,7 @@ func sharedRepositoryColumns() []*plugin.Column { {Name: "open_issues_total_count", Type: proto.ColumnType_INT, Hydrate: repoHydrateOpenIssuesCount, Transform: transform.FromValue(), Description: "Count of issues open on the repository."}, {Name: "watchers_total_count", Type: proto.ColumnType_INT, Hydrate: repoHydrateWatchersCount, Transform: transform.FromValue(), Description: "Count of watchers on the repository."}, // Columns from v3 api - hydrates + repositoryCustomPropertiesColumn(), {Name: "hooks", Type: proto.ColumnType_JSON, Description: "The API Hooks URL.", Hydrate: hydrateRepositoryHooksFromV3, Transform: transform.FromValue()}, {Name: "topics", Type: proto.ColumnType_JSON, Description: "The topics (similar to tags or labels) associated with the repository.", Hydrate: hydrateRepositoryDataFromV3}, {Name: "subscribers_count", Type: proto.ColumnType_INT, Description: "The number of users who have subscribed to the repository.", Hydrate: hydrateRepositoryDataFromV3}, @@ -146,6 +157,47 @@ func tableGitHubRepositoryList(ctx context.Context, d *plugin.QueryData, h *plug return nil, nil } +type repositoryCustomPropertyValue struct { + PropertyName string `json:"property_name"` + Value interface{} `json:"value"` +} + +func hydrateRepositoryCustomPropertiesFromV3(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + repo, err := extractRepoFromHydrateItem(h) + if err != nil { + return nil, err + } + + owner := repo.Owner.Login + repoName := repo.Name + + client := connect(ctx, d) + req, err := client.NewRequest("GET", "repos/"+owner+"/"+repoName+"/properties/values", nil) + if err != nil { + return nil, err + } + + var values []repositoryCustomPropertyValue + _, err = client.Do(ctx, req, &values) + if err != nil { + if strings.Contains(err.Error(), "404") { + return nil, nil + } + return nil, err + } + + if len(values) == 0 { + return nil, nil + } + + customProperties := map[string]interface{}{} + for _, v := range values { + customProperties[v.PropertyName] = v.Value + } + + return customProperties, nil +} + func hydrateRepositoryDataFromV3(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { repo, err := extractRepoFromHydrateItem(h) if err != nil { From 3df2d195f733bd982aadaec33ec4c83be701777f Mon Sep 17 00:00:00 2001 From: "lloyd.jones" <4392567+Recurzion@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:39:04 -0400 Subject: [PATCH 2/3] v3 to graphql --- github/models/repository.go | 64 ++++++++++++++++++------------- github/repo_utils.go | 12 ++++++ github/table_github_repository.go | 52 +++++-------------------- 3 files changed, 59 insertions(+), 69 deletions(-) diff --git a/github/models/repository.go b/github/models/repository.go index 0d21205..6675429 100644 --- a/github/models/repository.go +++ b/github/models/repository.go @@ -48,33 +48,34 @@ type Repository struct { Owner struct { Login string `json:"login"` } `json:"owner"` - PrimaryLanguage Language `graphql:"primaryLanguage @include(if:$includePrimaryLanguage)" json:"primary_language"` - ProjectsUrl string `graphql:"projectsUrl @include(if:$includeProjectsUrl)" json:"projects_url"` - PullRequestTemplates []PullRequestTemplate `graphql:"pullRequestTemplates @include(if:$includePullRequestTemplates)" json:"pull_request_templates"` - PushedAt NullableTime `graphql:"pushedAt @include(if:$includePushedAt)" json:"pushed_at"` - RebaseMergeAllowed bool `graphql:"rebaseMergeAllowed @include(if:$includeRebaseMergeAllowed)" json:"rebase_merge_allowed"` - SecurityPolicyUrl string `graphql:"securityPolicyUrl @include(if:$includeSecurityPolicyUrl)" json:"security_policy_url"` - SquashMergeAllowed bool `graphql:"squashMergeAllowed @include(if:$includeSquashMergeAllowed)" json:"squash_merge_allowed"` - SquashMergeCommitMessage githubv4.SquashMergeCommitMessage `graphql:"squashMergeCommitMessage @include(if:$includeSquashMergeCommitMessage)" json:"squash_merge_commit_message"` - SquashMergeCommitTitle githubv4.SquashMergeCommitTitle `graphql:"squashMergeCommitTitle @include(if:$includeSquashMergeCommitTitle)" json:"squash_merge_commit_title"` - SshUrl string `graphql:"sshUrl @include(if:$includeSshUrl)" json:"ssh_url"` - StargazerCount int `graphql:"stargazerCount @include(if:$includeStargazerCount)" json:"stargazer_count"` - UpdatedAt NullableTime `graphql:"updatedAt @include(if:$includeUpdatedAt)" json:"updated_at"` - Url string `graphql:"url @include(if:$includeUrl)" json:"url"` - UsesCustomOpenGraphImage bool `graphql:"usesCustomOpenGraphImage @include(if:$includeUsesCustomOpenGraphImage)" json:"uses_custom_open_graph_image"` - CanAdminister bool `graphql:"canAdminister: viewerCanAdminister @include(if:$includeCanAdminister)" json:"can_administer"` - CanCreateProjects bool `graphql:"canCreateProjects: viewerCanCreateProjects @include(if:$includeCanCreateProjects)" json:"can_create_projects"` - CanSubscribe bool `graphql:"canSubscribe: viewerCanSubscribe @include(if:$includeCanSubscribe)" json:"can_subscribe"` - CanUpdateTopics bool `graphql:"canUpdateTopics: viewerCanUpdateTopics @include(if:$includeCanUpdateTopics)" json:"can_update_topics"` - HasStarred bool `graphql:"hasStarred: viewerHasStarred @include(if:$includeHasStarred)" json:"has_starred"` - YourPermission githubv4.RepositoryPermission `graphql:"yourPermission: viewerPermission @include(if:$includeYourPermission)" json:"your_permission"` - PossibleCommitEmails []string `graphql:"possibleCommitEmails: viewerPossibleCommitEmails @include(if:$includePossibleCommitEmails)" json:"possible_commit_emails"` - Subscription githubv4.SubscriptionState `graphql:"subscription: viewerSubscription @include(if:$includeSubscription)" json:"subscription"` - Visibility githubv4.RepositoryVisibility `graphql:"visibility @include(if:$includeVisibility)" json:"visibility"` - WebCommitSignoffRequired bool `graphql:"webCommitSignoffRequired @include(if:$includeWebCommitSignoffRequired)" json:"web_commit_signoff_required"` - RepositoryTopics Count `graphql:"repositoryTopics @include(if:$includeRepositoryTopics)" json:"repository_topics"` - OpenIssues Count `graphql:"issues(states: OPEN) @include(if:$includeOpenIssues)" json:"open_issues"` - Watchers Count `graphql:"watchers @include(if:$includeWatchers)" json:"watchers"` + PrimaryLanguage Language `graphql:"primaryLanguage @include(if:$includePrimaryLanguage)" json:"primary_language"` + ProjectsUrl string `graphql:"projectsUrl @include(if:$includeProjectsUrl)" json:"projects_url"` + PullRequestTemplates []PullRequestTemplate `graphql:"pullRequestTemplates @include(if:$includePullRequestTemplates)" json:"pull_request_templates"` + PushedAt NullableTime `graphql:"pushedAt @include(if:$includePushedAt)" json:"pushed_at"` + RebaseMergeAllowed bool `graphql:"rebaseMergeAllowed @include(if:$includeRebaseMergeAllowed)" json:"rebase_merge_allowed"` + SecurityPolicyUrl string `graphql:"securityPolicyUrl @include(if:$includeSecurityPolicyUrl)" json:"security_policy_url"` + SquashMergeAllowed bool `graphql:"squashMergeAllowed @include(if:$includeSquashMergeAllowed)" json:"squash_merge_allowed"` + SquashMergeCommitMessage githubv4.SquashMergeCommitMessage `graphql:"squashMergeCommitMessage @include(if:$includeSquashMergeCommitMessage)" json:"squash_merge_commit_message"` + SquashMergeCommitTitle githubv4.SquashMergeCommitTitle `graphql:"squashMergeCommitTitle @include(if:$includeSquashMergeCommitTitle)" json:"squash_merge_commit_title"` + SshUrl string `graphql:"sshUrl @include(if:$includeSshUrl)" json:"ssh_url"` + StargazerCount int `graphql:"stargazerCount @include(if:$includeStargazerCount)" json:"stargazer_count"` + UpdatedAt NullableTime `graphql:"updatedAt @include(if:$includeUpdatedAt)" json:"updated_at"` + Url string `graphql:"url @include(if:$includeUrl)" json:"url"` + UsesCustomOpenGraphImage bool `graphql:"usesCustomOpenGraphImage @include(if:$includeUsesCustomOpenGraphImage)" json:"uses_custom_open_graph_image"` + CanAdminister bool `graphql:"canAdminister: viewerCanAdminister @include(if:$includeCanAdminister)" json:"can_administer"` + CanCreateProjects bool `graphql:"canCreateProjects: viewerCanCreateProjects @include(if:$includeCanCreateProjects)" json:"can_create_projects"` + CanSubscribe bool `graphql:"canSubscribe: viewerCanSubscribe @include(if:$includeCanSubscribe)" json:"can_subscribe"` + CanUpdateTopics bool `graphql:"canUpdateTopics: viewerCanUpdateTopics @include(if:$includeCanUpdateTopics)" json:"can_update_topics"` + HasStarred bool `graphql:"hasStarred: viewerHasStarred @include(if:$includeHasStarred)" json:"has_starred"` + YourPermission githubv4.RepositoryPermission `graphql:"yourPermission: viewerPermission @include(if:$includeYourPermission)" json:"your_permission"` + PossibleCommitEmails []string `graphql:"possibleCommitEmails: viewerPossibleCommitEmails @include(if:$includePossibleCommitEmails)" json:"possible_commit_emails"` + Subscription githubv4.SubscriptionState `graphql:"subscription: viewerSubscription @include(if:$includeSubscription)" json:"subscription"` + Visibility githubv4.RepositoryVisibility `graphql:"visibility @include(if:$includeVisibility)" json:"visibility"` + WebCommitSignoffRequired bool `graphql:"webCommitSignoffRequired @include(if:$includeWebCommitSignoffRequired)" json:"web_commit_signoff_required"` + RepositoryTopics Count `graphql:"repositoryTopics @include(if:$includeRepositoryTopics)" json:"repository_topics"` + RepositoryCustomPropertyValues RepositoryCustomPropertyValueConnection `graphql:"repositoryCustomPropertyValues(first: 100) @include(if:$includeRepositoryCustomPropertyValues)" json:"repository_custom_property_values"` + OpenIssues Count `graphql:"issues(states: OPEN) @include(if:$includeOpenIssues)" json:"open_issues"` + Watchers Count `graphql:"watchers @include(if:$includeWatchers)" json:"watchers"` // AssignableUsers [pageable] // BranchProtectionRules [pageable] // CodeOwners [search by refName] @@ -146,3 +147,12 @@ type RepositoryFundingLinks struct { Url string `json:"url"` Platform githubv4.FundingPlatform `json:"platform"` } + +type RepositoryCustomPropertyValue struct { + PropertyName string `json:"property_name"` + Value interface{} `json:"value"` +} + +type RepositoryCustomPropertyValueConnection struct { + Nodes []RepositoryCustomPropertyValue `json:"nodes"` +} diff --git a/github/repo_utils.go b/github/repo_utils.go index 1e0e5b6..f8e1535 100644 --- a/github/repo_utils.go +++ b/github/repo_utils.go @@ -417,6 +417,7 @@ func appendRepoColumnIncludes(m *map[string]interface{}, cols []string) { "can_update_topics": "includeCanUpdateTopics", "code_of_conduct": "includeCodeOfConduct", "contact_links": "includeContactLinks", + "custom_properties": "includeRepositoryCustomPropertyValues", "created_at": "includeCreatedAt", "default_branch_ref": "includeDefaultBranchRef", "delete_branch_on_merge": "includeDeleteBranchOnMerge", @@ -1197,6 +1198,17 @@ func repoHydrateRepositoryTopicsCount(_ context.Context, _ *plugin.QueryData, h return nil, nil } +func repoHydrateRepositoryCustomPropertyValues(_ context.Context, _ *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + if r, ok := h.Item.(models.Repository); ok { + return r.RepositoryCustomPropertyValues.Nodes, nil + } else if r, ok := h.Item.(models.SearchRepositoryResult); ok { + return r.Node.RepositoryCustomPropertyValues.Nodes, nil + } else if r, ok := h.Item.(models.TeamRepositoryWithPermission); ok { + return r.Node.RepositoryCustomPropertyValues.Nodes, nil + } + return nil, nil +} + func repoHydrateOpenIssuesCount(_ context.Context, _ *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { if r, ok := h.Item.(models.Repository); ok { return r.OpenIssues.TotalCount, nil diff --git a/github/table_github_repository.go b/github/table_github_repository.go index eeedf50..8b89958 100644 --- a/github/table_github_repository.go +++ b/github/table_github_repository.go @@ -19,16 +19,6 @@ func gitHubRepositoryColumns() []*plugin.Column { return append(repoColumns, sharedRepositoryColumns()...) } -func repositoryCustomPropertiesColumn() *plugin.Column { - return &plugin.Column{ - Name: "custom_properties", - Type: proto.ColumnType_JSON, - Description: "A map of custom property names and values assigned to the repository.", - Hydrate: hydrateRepositoryCustomPropertiesFromV3, - Transform: transform.FromValue(), - } -} - func sharedRepositoryColumns() []*plugin.Column { return []*plugin.Column{ {Name: "id", Type: proto.ColumnType_INT, Description: "The numeric ID of the repository.", Transform: transform.FromField("Id", "Node.Id")}, @@ -103,8 +93,8 @@ func sharedRepositoryColumns() []*plugin.Column { {Name: "repository_topics_total_count", Type: proto.ColumnType_INT, Hydrate: repoHydrateRepositoryTopicsCount, Transform: transform.FromValue(), Description: "Count of topics associated with the repository."}, {Name: "open_issues_total_count", Type: proto.ColumnType_INT, Hydrate: repoHydrateOpenIssuesCount, Transform: transform.FromValue(), Description: "Count of issues open on the repository."}, {Name: "watchers_total_count", Type: proto.ColumnType_INT, Hydrate: repoHydrateWatchersCount, Transform: transform.FromValue(), Description: "Count of watchers on the repository."}, + {Name: "custom_properties", Type: proto.ColumnType_JSON, Description: "A map of custom property names and values assigned to the repository.", Hydrate: repoHydrateRepositoryCustomPropertyValues, Transform: transform.FromValue().Transform(repositoryCustomPropertiesTransform)}, // Columns from v3 api - hydrates - repositoryCustomPropertiesColumn(), {Name: "hooks", Type: proto.ColumnType_JSON, Description: "The API Hooks URL.", Hydrate: hydrateRepositoryHooksFromV3, Transform: transform.FromValue()}, {Name: "topics", Type: proto.ColumnType_JSON, Description: "The topics (similar to tags or labels) associated with the repository.", Hydrate: hydrateRepositoryDataFromV3}, {Name: "subscribers_count", Type: proto.ColumnType_INT, Description: "The number of users who have subscribed to the repository.", Hydrate: hydrateRepositoryDataFromV3}, @@ -157,44 +147,21 @@ func tableGitHubRepositoryList(ctx context.Context, d *plugin.QueryData, h *plug return nil, nil } -type repositoryCustomPropertyValue struct { - PropertyName string `json:"property_name"` - Value interface{} `json:"value"` -} - -func hydrateRepositoryCustomPropertiesFromV3(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - repo, err := extractRepoFromHydrateItem(h) - if err != nil { - return nil, err - } - - owner := repo.Owner.Login - repoName := repo.Name - - client := connect(ctx, d) - req, err := client.NewRequest("GET", "repos/"+owner+"/"+repoName+"/properties/values", nil) - if err != nil { - return nil, err +func repositoryCustomPropertiesTransform(_ context.Context, input *transform.TransformData) (interface{}, error) { + values, ok := input.Value.([]models.RepositoryCustomPropertyValue) + if !ok || len(values) == 0 { + return nil, nil } - var values []repositoryCustomPropertyValue - _, err = client.Do(ctx, req, &values) - if err != nil { - if strings.Contains(err.Error(), "404") { - return nil, nil - } - return nil, err + customProperties := make(map[string]interface{}, len(values)) + for _, value := range values { + customProperties[value.PropertyName] = value.Value } - if len(values) == 0 { + if len(customProperties) == 0 { return nil, nil } - customProperties := map[string]interface{}{} - for _, v := range values { - customProperties[v.PropertyName] = v.Value - } - return customProperties, nil } @@ -203,6 +170,7 @@ func hydrateRepositoryDataFromV3(ctx context.Context, d *plugin.QueryData, h *pl if err != nil { return nil, err } + owner := repo.Owner.Login repoName := repo.Name From 8f303ea896c67268717ba14e33d2c24f0e99df47 Mon Sep 17 00:00:00 2001 From: "lloyd.jones" <4392567+Recurzion@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:44:19 -0400 Subject: [PATCH 3/3] fix docs spacing and split --- docs/tables/github_search_repository.md | 30 ++++++++++++------------- docs/tables/github_team_repository.md | 2 ++ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/tables/github_search_repository.md b/docs/tables/github_search_repository.md index b51a539..a9e1c38 100644 --- a/docs/tables/github_search_repository.md +++ b/docs/tables/github_search_repository.md @@ -136,6 +136,21 @@ where query = 'tinyspotifyr in:name created:2021-01-01..2021-01-05 fork:only'; ``` +```sql+sqlite +select + name, + owner_login, + primary_language, + fork_count, + stargazer_count, + subscribers_count, + watchers_total_count +from + github_search_repository +where + query = 'tinyspotifyr in:name created:2021-01-01..2021-01-05 fork:only'; +``` + ### Get custom property values from search results Explore custom property key/value pairs for repositories returned by a search query. @@ -159,19 +174,4 @@ from where query = 'org:my-org archived:false' and custom_properties is not null; -``` - -```sql+sqlite -select - name, - owner_login, - primary_language, - fork_count, - stargazer_count, - subscribers_count, - watchers_total_count -from - github_search_repository -where - query = 'tinyspotifyr in:name created:2021-01-01..2021-01-05 fork:only'; ``` \ No newline at end of file diff --git a/docs/tables/github_team_repository.md b/docs/tables/github_team_repository.md index c653802..214fddd 100644 --- a/docs/tables/github_team_repository.md +++ b/docs/tables/github_team_repository.md @@ -65,6 +65,7 @@ where organization = 'my_org' and slug = 'my-team'; ``` + ### List visible teams and repositories they have admin permissions to Explore the teams and associated repositories within your organization that have administrative permissions. This is useful to ensure appropriate access rights and maintain security within your GitHub organization. @@ -121,6 +122,7 @@ where organization = 'my_org' and slug = 'my-team'; ``` + ```sql+sqlite select organization,