diff --git a/docs/tables/github_package.md b/docs/tables/github_package.md new file mode 100644 index 0000000..ce55a46 --- /dev/null +++ b/docs/tables/github_package.md @@ -0,0 +1,181 @@ +--- +title: "Steampipe Table: github_package - Query GitHub Packages using SQL" +description: "Allows users to query GitHub Packages, including package metadata, versions, owner details, and associated repositories, providing insights into the package management within GitHub repositories." +--- + +# Table: github_package - Query GitHub Packages using SQL + +GitHub Packages allow you to store and manage container images and other packages directly within your GitHub repositories. With this table, you can query details about GitHub packages within an organization, including information about the package itself, its owner, associated repositories, and visibility. + +## Table Usage Guide + +The `github_package` table provides detailed insights into packages hosted in GitHub's container registry or other package managers like npm. You can retrieve information about the package owner, repository, creation time, versioning details, and more. This is particularly useful for monitoring and managing package versions and repository associations in an organization. + +**Important Notes** +- You must specify the `organization` column in the `where` clause to query the table. +- OAuth app tokens and personal access tokens (classic) need the `read:packages` scope to use this endpoint. If the package_type belongs to a GitHub Packages registry that only supports repository-scoped permissions, the `repo` scope is also required. Please refer [Permissions for repository-scoped packages](https://docs.github.com/en/packages/learn-github-packages/about-permissions-for-github-packages#permissions-for-repository-scoped-packages) for more information. + +## Examples + +### List all packages for a specific organization +Retrieve the details of all packages available in a specified GitHub organization, including information about the owner, repository, and package type. + +```sql+postgres +select + id, + name, + package_type, + repository_full_name, + visibility, + created_at, + updated_at, + owner_login, + url +from + github_package +where + organization = 'turbot'; +``` + +```sql+sqlite +select + id, + name, + package_type, + repository_full_name, + visibility, + created_at, + updated_at, + owner_login, + url +from + github_package +where + organization = 'turbot'; +``` + +### List all public packages for an organization +Filter and list only the packages that are publicly visible in a specific GitHub organization. This is helpful for managing public packages in your organization. + +```sql+postgres +select + id, + name, + package_type, + repository_full_name, + visibility, + html_url, + owner_login +from + github_package +where + organization = 'turbot' + and visibility = 'public'; +``` + +```sql+sqlite +select + id, + name, + package_type, + repository_full_name, + visibility, + html_url, + owner_login +from + github_package +where + organization = 'turbot' + and visibility = 'public'; +``` + +### Find packages associated with private repositories +Identify packages that are tied to private repositories within a GitHub organization, allowing you to manage internal packages. + +```sql+postgres +select + name, + repository_full_name, + repository_private, + owner_login +from + github_package +where + organization = 'turbot' + and repository_private = true; +``` + +```sql+sqlite +select + name, + repository_full_name, + repository_private, + owner_login +from + github_package +where + organization = 'turbot' + and repository_private = 1; +``` + +### Get the details of a specific package by name +Retrieve comprehensive details about a specific package by filtering based on the package name. It is useful for analyzing a single package's metadata, owner, and repository association. + +```sql+postgres +select + id, + name, + package_type, + repository_full_name, + owner_login, + created_at, + updated_at, + url +from + github_package +where + organization = 'turbot' + and name = 'steampipe/plugin/turbot/aws'; +``` + +```sql+sqlite +select + id, + name, + package_type, + repository_full_name, + owner_login, + created_at, + updated_at, + url +from + github_package +where + organization = 'turbot' + and name = 'steampipe/plugin/turbot/aws'; +``` + +### List all versions of a package for an organization +Explore the versioning details for a specific package, showing the available versions and metadata for each version. + +```sql+postgres +select + name, + jsonb_array_elements_text(package_version->'versions') as version +from + github_package +where + organization = 'turbot' + and name = 'steampipe/plugin/turbot/aws'; +``` + +```sql+sqlite +select + name, + json_extract(package_version, '$.versions[0]') as version +from + github_package +where + organization = 'turbot' + and name = 'steampipe/plugin/turbot/aws'; +``` \ No newline at end of file diff --git a/docs/tables/github_package_version.md b/docs/tables/github_package_version.md new file mode 100644 index 0000000..9fafa09 --- /dev/null +++ b/docs/tables/github_package_version.md @@ -0,0 +1,175 @@ +--- +title: "Steampipe Table: github_package_version - Query GitHub Package Versions using SQL" +description: "Allows users to query GitHub Package Versions, providing insights into the metadata, release information, visibility, and creation details of each version." +--- + +# Table: github_package_version - Query GitHub Package Versions using SQL + +GitHub Package Versions represent different versions of packages stored in GitHub, such as container images, npm packages, and more. These versions hold critical details about the state of a package at a specific point in time, including release information, tags, metadata, and visibility. + +## Table Usage Guide + +The `github_package_version` table allows you to query detailed information about different versions of packages in GitHub's package registry. This includes data such as the package author, digest, release information, and the visibility of the version (whether public or private). + +**Important Notes** +- You must specify the `organization` column in the `where` clause to query the table. +- OAuth app tokens and personal access tokens (classic) need the `read:packages` scope to use this endpoint. If the package_type belongs to a GitHub Packages registry that only supports repository-scoped permissions, the `repo` scope is also required. Please refer [Permissions for repository-scoped packages](https://docs.github.com/en/packages/learn-github-packages/about-permissions-for-github-packages#permissions-for-repository-scoped-packages) for more information. + +## Examples + +### List all versions of a specific package +This query retrieves all the versions for a specific package in an organization. It includes details such as the version's digest, creation date, and visibility status. + +```sql+postgres +select + id, + package_name, + digest, + prerelease, + created_at, + visibility +from + github_package_version +where + organization = 'turbot' + and package_name = 'steampipe/plugin/turbot/aws'; +``` + +```sql+sqlite +select + id, + package_name, + digest, + prerelease, + created_at, + visibility +from + github_package_version +where + organization = 'turbot' + and package_name = 'steampipe/plugin/turbot/aws'; +``` + +### List all public package versions for an organization +This query lists all publicly visible versions of packages in a GitHub organization, including their package type and associated metadata. + +```sql+postgres +select + package_name, + package_type, + digest, + visibility, + created_at, + updated_at +from + github_package_version +where + organization = 'turbot' + and visibility = 'public'; +``` + +```sql+sqlite +select + package_name, + package_type, + digest, + visibility, + created_at, + updated_at +from + github_package_version +where + organization = 'turbot' + and visibility = 'public'; +``` + +### List pre-release package versions for a specific package +This query retrieves all the pre-release versions of a specific package in an organization. Pre-release versions are used for testing before a package is officially released. + +```sql+postgres +select + id, + package_name, + prerelease, + created_at, + html_url +from + github_package_version +where + organization = 'turbot' + and package_name = 'steampipe/plugin/turbot/aws' + and prerelease = true; +``` + +```sql+sqlite +select + id, + package_name, + prerelease, + created_at, + html_url +from + github_package_version +where + organization = 'turbot' + and package_name = 'steampipe/plugin/turbot/aws' + and prerelease = 1; +``` + +### Get metadata of a specific package version +This query retrieves metadata details for a specific package version. Metadata can include additional version-specific information, such as description, version number, and other properties. + +```sql+postgres +select + id, + package_name, + jsonb_pretty(metadata) as metadata +from + github_package_version +where + organization = 'turbot' + and package_name = 'steampipe/plugin/turbot/aws' + and id = 12345; +``` + +```sql+sqlite +select + id, + package_name, + json_extract(metadata, '$') as metadata +from + github_package_version +where + organization = 'turbot' + and package_name = 'steampipe/plugin/turbot/aws' + and id = 12345; +``` + +### List the tags associated with a package version +This query retrieves the tags associated with a specific package version. Tags help identify different aspects of a version and can assist with version management. + +```sql+postgres +select + id, + package_name, + jsonb_array_elements_text(tags) as tag +from + github_package_version +where + organization = 'turbot' + and package_name = 'steampipe/plugin/turbot/aws' + and id = 12345; +``` + +```sql+sqlite +select + id, + package_name, + json_extract(tags, '$[0]') as tag +from + github_package_version +where + organization = 'turbot' + and package_name = 'steampipe/plugin/turbot/aws' + and id = 12345; +``` \ No newline at end of file diff --git a/github/plugin.go b/github/plugin.go index f44e812..abfb83e 100644 --- a/github/plugin.go +++ b/github/plugin.go @@ -50,6 +50,8 @@ func Plugin(ctx context.Context) *plugin.Plugin { "github_organization_external_identity": tableGitHubOrganizationExternalIdentity(), "github_organization_member": tableGitHubOrganizationMember(), "github_organization_collaborator": tableGitHubOrganizationCollaborator(), + "github_package": tableGitHubPackage(), + "github_package_version": tableGitHubPackageVersion(), "github_pull_request": tableGitHubPullRequest(), "github_pull_request_comment": tableGitHubPullRequestComment(), "github_pull_request_review": tableGitHubPullRequestReview(), diff --git a/github/table_github_package.go b/github/table_github_package.go new file mode 100644 index 0000000..f3d63bc --- /dev/null +++ b/github/table_github_package.go @@ -0,0 +1,136 @@ +package github + +import ( + "context" + "net/url" + + "github.com/google/go-github/v55/github" + + "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 tableGitHubPackage() *plugin.Table { + return &plugin.Table{ + Name: "github_package", + Description: "GitHub Packages allow you to store and manage packages such as container images or other artifacts in your GitHub repositories.", + List: &plugin.ListConfig{ + KeyColumns: plugin.KeyColumnSlice{ + {Name: "organization", Require: plugin.Required}, + {Name: "package_type", Require: plugin.Optional}, + {Name: "visibility", Require: plugin.Optional}, + }, + ShouldIgnoreError: isNotFoundError([]string{"404"}), + Hydrate: tableGitHubPackageList, + }, + Get: &plugin.GetConfig{ + KeyColumns: plugin.AllColumns([]string{"organization", "name", "package_type"}), + ShouldIgnoreError: isNotFoundError([]string{"404"}), + Hydrate: tableGitHubPackageGet, + }, + Columns: commonColumns([]*plugin.Column{ + {Name: "id", Type: proto.ColumnType_INT, Description: "Unique ID of the package."}, + {Name: "name", Type: proto.ColumnType_STRING, Description: "Name of the package."}, + {Name: "package_type", Type: proto.ColumnType_STRING, Description: "Type of the package (e.g., container, npm, etc.)."}, + {Name: "organization", Type: proto.ColumnType_STRING, Description: "The name of the GitHub organization.", Transform: transform.FromQual("organization")}, + {Name: "visibility", Type: proto.ColumnType_STRING, Description: "Visibility of the package (public or private)."}, + {Name: "url", Type: proto.ColumnType_STRING, Description: "API URL of the package."}, + {Name: "html_url", Type: proto.ColumnType_STRING, Description: "HTML URL of the package."}, + {Name: "created_at", Type: proto.ColumnType_TIMESTAMP, Transform: transform.FromField("CreatedAt").Transform(convertTimestamp), Description: "Timestamp when the package was created."}, + {Name: "updated_at", Type: proto.ColumnType_TIMESTAMP, Transform: transform.FromField("UpdatedAt").Transform(convertTimestamp), Description: "Timestamp when the package was last updated."}, + + // Package owner details + {Name: "owner_login", Type: proto.ColumnType_STRING, Transform: transform.FromField("Owner.Login"), Description: "Login name of the package owner."}, + {Name: "owner_id", Type: proto.ColumnType_INT, Transform: transform.FromField("Owner.ID"), Description: "ID of the package owner."}, + {Name: "owner_url", Type: proto.ColumnType_STRING, Transform: transform.FromField("Owner.URL"), Description: "API URL of the package owner."}, + {Name: "owner_html_url", Type: proto.ColumnType_STRING, Transform: transform.FromField("Owner.HTMLURL"), Description: "HTML URL of the package owner."}, + + // Repository details + {Name: "repository_id", Type: proto.ColumnType_INT, Transform: transform.FromField("Repository.ID"), Description: "ID of the repository associated with the package."}, + {Name: "repository_full_name", Type: proto.ColumnType_STRING, Transform: transform.FromField("Repository.FullName"), Description: "Full name of the repository associated with the package."}, + {Name: "repository_private", Type: proto.ColumnType_BOOL, Transform: transform.FromField("Repository.Private"), Description: "Indicates if the repository is private."}, + {Name: "repository_html_url", Type: proto.ColumnType_STRING, Transform: transform.FromField("Repository.HTMLURL"), Description: "HTML URL of the repository."}, + + // JSON field + {Name: "repository", Type: proto.ColumnType_JSON, Description: "The information about the repository."}, + {Name: "owner", Type: proto.ColumnType_JSON, Description: "The information about the owner."}, + }), + } +} + +func tableGitHubPackageList(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + client := connect(ctx, d) + org, packageType := d.EqualsQuals["organization"].GetStringValue(), d.EqualsQuals["package_type"].GetStringValue() + visibility := d.EqualsQuals["visibility"].GetStringValue() + + if packageType == "" { + // Default package type + packageType = "container" + } + + // Return, if org is not specified + if org == "" { + return nil, nil + } + + opts := &github.PackageListOptions{ + PackageType: &packageType, + ListOptions: github.ListOptions{ + PerPage: 100, + }, + } + + if visibility != "" { + opts.Visibility = &visibility + } + + limit := d.QueryContext.Limit + if limit != nil { + if *limit < int64(opts.PerPage) { + opts.PerPage = int(*limit) + } + } + + for { + packages, resp, err := client.Organizations.ListPackages(ctx, org, opts) + if err != nil { + plugin.Logger(ctx).Error("github_package.tableGitHubPackageList", "api_error", err) + return nil, err + } + + for _, pkg := range packages { + d.StreamListItem(ctx, pkg) + + // Stop if we've hit the limit set in the query context + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + + return nil, nil +} + +func tableGitHubPackageGet(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + client := connect(ctx, d) + org := d.EqualsQuals["organization"].GetStringValue() + name := d.EqualsQuals["name"].GetStringValue() + packageType := d.EqualsQuals["package_type"].GetStringValue() + + name = url.QueryEscape(name) + + // Fetch the package + pkg, _, err := client.Organizations.GetPackage(ctx, org, packageType, name) + if err != nil { + plugin.Logger(ctx).Error("github_package.tableGitHubPackageGet", "api_error", err) + return nil, err + } + + return pkg, nil +} diff --git a/github/table_github_package_version.go b/github/table_github_package_version.go new file mode 100644 index 0000000..1dabca7 --- /dev/null +++ b/github/table_github_package_version.go @@ -0,0 +1,150 @@ +package github + +import ( + "context" + "net/url" + + "github.com/google/go-github/v55/github" + + "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 tableGitHubPackageVersion() *plugin.Table { + return &plugin.Table{ + Name: "github_package_version", + Description: "GitHub Packages allow you to store and manage packages such as container images or other artifacts in your GitHub repositories.", + List: &plugin.ListConfig{ + KeyColumns: plugin.KeyColumnSlice{ + {Name: "organization", Require: plugin.Required}, + {Name: "package_type", Require: plugin.Optional}, + {Name: "package_name", Require: plugin.Optional}, + {Name: "visibility", Require: plugin.Optional}, + }, + ShouldIgnoreError: isNotFoundError([]string{"404"}), + ParentHydrate: tableGitHubPackageList, + Hydrate: tableGitHubPackageVersionList, + }, + Get: &plugin.GetConfig{ + KeyColumns: plugin.AllColumns([]string{"organization", "package_name", "package_type", "id"}), + ShouldIgnoreError: isNotFoundError([]string{"404"}), + Hydrate: tableGitHubPackageVersionGet, + }, + Columns: commonColumns([]*plugin.Column{ + {Name: "package_name", Type: proto.ColumnType_STRING, Description: "Name of the package version."}, + {Name: "id", Type: proto.ColumnType_INT, Description: "Unique ID of the package version.", Transform: transform.FromField("PackageVersion.ID")}, + {Name: "digest", Type: proto.ColumnType_STRING, Description: "The digest (shasum) of the package version.", Transform: transform.FromField("PackageVersion.Name")}, + {Name: "html_url", Type: proto.ColumnType_STRING, Description: "HTML URL of the package version.", Transform: transform.FromField("PackageVersion.HTMLURL")}, + + {Name: "organization", Type: proto.ColumnType_STRING, Description: "The name of the GitHub organization.", Transform: transform.FromQual("organization")}, + {Name: "package_type", Type: proto.ColumnType_STRING, Description: "Type of the package (e.g., container, npm, etc.)."}, + {Name: "visibility", Type: proto.ColumnType_STRING, Description: "Visibility of the package (public or private)."}, + {Name: "prerelease", Type: proto.ColumnType_BOOL, Description: "Indicates if the package version is a pre-release.", Transform: transform.FromField("PackageVersion.Prerelease")}, + {Name: "url", Type: proto.ColumnType_STRING, Description: "The URL of the package version.", Transform: transform.FromField("PackageVersion.URL")}, + {Name: "created_at", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp when the package version was created.", Transform: transform.FromField("PackageVersion.CreatedAt").Transform(convertTimestamp)}, + {Name: "updated_at", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp when the package version was last updated.", Transform: transform.FromField("PackageVersion.UpdatedAt").Transform(convertTimestamp)}, + + // JSON fields + {Name: "tags", Type: proto.ColumnType_JSON, Description: "Tags associated with the package version.", Transform: transform.FromField("PackageVersion.Metadata.Container.Tags")}, + {Name: "metadata", Type: proto.ColumnType_JSON, Description: "Metadata of the package version.", Transform: transform.FromField("PackageVersion.Metadata")}, + {Name: "author", Type: proto.ColumnType_JSON, Description: "Author of the package version.", Transform: transform.FromField("PackageVersion.Author")}, + {Name: "release", Type: proto.ColumnType_JSON, Description: "Release information of the package version.", Transform: transform.FromField("PackageVersion.Release")}, + }), + } +} + +type PackageVersionInfo struct { + PackageName string + PackageType string + Visibility string + PackageVersion *github.PackageVersion +} + +func tableGitHubPackageVersionList(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + client := connect(ctx, d) + pkg := h.Item.(*github.Package) + org, packageType, packageName := d.EqualsQuals["organization"].GetStringValue(), d.EqualsQuals["package_type"].GetStringValue(), d.EqualsQuals["package_name"].GetStringValue() + visibility := d.EqualsQuals["visibility"].GetStringValue() + + if packageName != "" && packageName != *pkg.Name { + return nil, nil + } + if packageType != "" && packageType != *pkg.PackageType { + return nil, nil + } + if visibility != "" && visibility != *pkg.Visibility { + return nil, nil + } + + // Return, if org is not specified + if org == "" { + return nil, nil + } + + // Encode the package name, otherwise we will get 404 err if the package name looks like "steampipe/plugins/turbot/jira" + packageName = url.QueryEscape(*pkg.Name) + + opts := &github.PackageListOptions{ + ListOptions: github.ListOptions{ + PerPage: 100, + }, + } + + if visibility != "" { + opts.Visibility = &visibility + } + + limit := d.QueryContext.Limit + if limit != nil { + if *limit < int64(opts.PerPage) { + opts.PerPage = int(*limit) + } + } + + for { + packageVersions, resp, err := client.Organizations.PackageGetAllVersions(ctx, org, *pkg.PackageType, packageName, opts) + if err != nil { + plugin.Logger(ctx).Error("github_package.tableGitHubPackageVersionList", "api_error", err) + return nil, err + } + + for _, pkgVersion := range packageVersions { + d.StreamListItem(ctx, PackageVersionInfo{*pkg.Name, *pkg.PackageType, *pkg.Visibility, pkgVersion}) + + // Stop if we've hit the limit set in the query context + if d.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + + return nil, nil +} + +//// HYDRATE FUNCTION + +func tableGitHubPackageVersionGet(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + client := connect(ctx, d) + org := d.EqualsQuals["organization"].GetStringValue() + name := d.EqualsQuals["package_name"].GetStringValue() + packageType := d.EqualsQuals["package_type"].GetStringValue() + packageVersionID := d.EqualsQuals["id"].GetInt64Value() + + // Encode the package name, otherwise we will get 404 err if the package name looks like + name = url.QueryEscape(name) + + // Fetch the package + pkgVersion, _, err := client.Organizations.PackageGetVersion(ctx, org, packageType, name, packageVersionID) + if err != nil { + plugin.Logger(ctx).Error("github_package.tableGitHubPackageVersionGet", "api_error", err) + return nil, err + } + + return PackageVersionInfo{name, packageType, "", pkgVersion}, nil +}