diff --git a/README.md b/README.md index fc22906..c36b21c 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ Available resource types: - `artifactapi_remote_puppet` - `artifactapi_remote_terraform` - `artifactapi_remote_goproxy` +- `artifactapi_remote_github_rpm` #### Common Attributes @@ -93,11 +94,21 @@ Available resource types: | `ban_tags_enabled` | `false` | Enable tag banning | | `ban_tags` | | List of tags to ban | -#### Terraform-specific Attributes +#### Terraform / github_rpm-specific Attributes | Attribute | Default | Description | |-------------------|---------|----------------------------------------------------------| -| `releases_remote` | `""` | Name of a generic remote for download URL rewriting | +| `releases_remote` | `""` | Name of a backend remote that serves the download bytes. Terraform uses it for download URL rewriting; `github_rpm` 302-redirects `.rpm` downloads to it. | + +#### github_rpm-specific notes + +`artifactapi_remote_github_rpm` exposes a GitHub repo's releases as a `dnf`/`yum` +repository without precaching packages. Set `base_url` to the releases API root +(`https://api.github.com/repos/{owner}/{repo}`) and `releases_remote` to a +generic `github.com` remote that streams the `.rpm` bytes. `patterns` filters +which release assets become packages (regex on asset filename). `password` may +hold a token for private repos or higher API rate limits. See +`examples/resources/artifactapi_remote_github_rpm/main.tf`. #### Example diff --git a/examples/resources/artifactapi_remote_github_rpm/main.tf b/examples/resources/artifactapi_remote_github_rpm/main.tf new file mode 100644 index 0000000..7aa6fb0 --- /dev/null +++ b/examples/resources/artifactapi_remote_github_rpm/main.tf @@ -0,0 +1,44 @@ +terraform { + required_providers { + artifactapi = { + source = "git.unkin.net/unkin/artifactapi" + version = "0.0.1" + } + } +} + +provider "artifactapi" { + endpoint = "https://artifactapi.example.com" +} + +# Backend generic remote that serves the actual .rpm bytes from github.com. +# Its patterns must allowlist the target repo's release-download assets. +resource "artifactapi_remote_generic" "github" { + name = "github" + base_url = "https://github.com" + + patterns = [ + "acme/tools/releases/download/.*\\.rpm$", + ] +} + +# Metadata-only remote: scans acme/tools releases for .rpm assets and serves a +# synthesized yum repo. Packages are never precached; downloads 302-redirect to +# the "github" remote above. +resource "artifactapi_remote_github_rpm" "acme_tools" { + name = "acme-tools" + base_url = "https://api.github.com/repos/acme/tools" + description = "acme/tools GitHub releases as a yum repo" + releases_remote = artifactapi_remote_generic.github.name + + mutable_ttl = 3600 + + # Optional: restrict which release assets become packages. + patterns = [ + ".*\\.x86_64\\.rpm$", + ".*\\.noarch\\.rpm$", + ] + + # Optional: token for private repos / higher GitHub API rate limits. + # password = var.github_token +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 6476f46..9fdcd98 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -67,6 +67,7 @@ func (p *ArtifactAPIProvider) Resources(_ context.Context) []func() resource.Res newRemoteResource("puppet"), newRemoteResource("terraform"), newRemoteResource("goproxy"), + newRemoteResource("github_rpm"), NewVirtualResource, NewLocalTerraformResource, NewLocalPyPIResource, diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index 70d2132..102d8a9 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -66,8 +66,8 @@ func TestProvider_Resources(t *testing.T) { p := &ArtifactAPIProvider{version: "1.0.0"} resources := p.Resources(context.Background()) - // 10 remote resource types + 1 virtual + local_terraform/pypi/rpm/docker/generic = 16 - expectedCount := 16 + // 11 remote resource types + 1 virtual + local_terraform/pypi/rpm/docker/generic = 17 + expectedCount := 17 if len(resources) != expectedCount { t.Fatalf("expected %d resources, got %d", expectedCount, len(resources)) } @@ -106,6 +106,7 @@ func TestProvider_Resources_ContainsExpectedTypes(t *testing.T) { "artifactapi_remote_puppet", "artifactapi_remote_terraform", "artifactapi_remote_goproxy", + "artifactapi_remote_github_rpm", "artifactapi_virtual", "artifactapi_local_terraform", "artifactapi_local_pypi", diff --git a/internal/provider/resource_remote.go b/internal/provider/resource_remote.go index 5e53544..af2a352 100644 --- a/internal/provider/resource_remote.go +++ b/internal/provider/resource_remote.go @@ -66,6 +66,7 @@ func NewRemoteAlpine() resource.Resource { return &remoteResource{packageType func NewRemotePuppet() resource.Resource { return &remoteResource{packageType: "puppet"} } func NewRemoteTerraform() resource.Resource { return &remoteResource{packageType: "terraform"} } func NewRemoteGoProxy() resource.Resource { return &remoteResource{packageType: "goproxy"} } +func NewRemoteGitHubRPM() resource.Resource { return &remoteResource{packageType: "github_rpm"} } func (r *remoteResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_remote_" + r.packageType @@ -145,7 +146,7 @@ func (r *remoteResource) Schema(_ context.Context, _ resource.SchemaRequest, res ElementType: types.StringType, }, "releases_remote": schema.StringAttribute{ - Description: "Name of the CDN remote for download URL rewriting (terraform only).", + Description: "Name of a backend remote that serves the actual download bytes. Used by the terraform remote (download URL rewriting) and the github_rpm remote (302 redirect of .rpm downloads to a generic github.com remote).", Optional: true, Computed: true, Default: stringdefault.StaticString(""), }, "upstream_dial_timeout": schema.Int64Attribute{ diff --git a/internal/provider/resource_remote_test.go b/internal/provider/resource_remote_test.go index fd73364..0f77836 100644 --- a/internal/provider/resource_remote_test.go +++ b/internal/provider/resource_remote_test.go @@ -409,6 +409,7 @@ func TestRemoteResource_Metadata(t *testing.T) { {"helm", "artifactapi_remote_helm"}, {"pypi", "artifactapi_remote_pypi"}, {"npm", "artifactapi_remote_npm"}, + {"github_rpm", "artifactapi_remote_github_rpm"}, } for _, tt := range tests { @@ -424,6 +425,34 @@ func TestRemoteResource_Metadata(t *testing.T) { } } +func TestModelToAPI_GitHubRPM(t *testing.T) { + ctx := context.Background() + r := &remoteResource{packageType: "github_rpm"} + + model := remoteResourceModel{ + Name: types.StringValue("acme-tools"), + BaseURL: types.StringValue("https://api.github.com/repos/acme/tools"), + ReleasesRemote: types.StringValue("github"), + MutableTTL: types.Int64Value(3600), + Patterns: stringsToList(ctx, []string{`.*\.x86_64\.rpm$`}), + } + + api := r.modelToAPI(ctx, model) + + if api.PackageType != "github_rpm" { + t.Errorf("PackageType: expected github_rpm, got %s", api.PackageType) + } + if api.BaseURL != "https://api.github.com/repos/acme/tools" { + t.Errorf("BaseURL: got %s", api.BaseURL) + } + if api.ReleasesRemote != "github" { + t.Errorf("ReleasesRemote: expected github, got %s", api.ReleasesRemote) + } + if len(api.Patterns) != 1 || api.Patterns[0] != `.*\.x86_64\.rpm$` { + t.Errorf("Patterns: got %v", api.Patterns) + } +} + func TestRemoteResource_Schema(t *testing.T) { r := &remoteResource{packageType: "docker"} req := resource.SchemaRequest{} @@ -463,6 +492,7 @@ func TestNewRemoteResource_Constructors(t *testing.T) { {"puppet", func() resource.Resource { return NewRemotePuppet() }, "puppet"}, {"terraform", func() resource.Resource { return NewRemoteTerraform() }, "terraform"}, {"goproxy", func() resource.Resource { return NewRemoteGoProxy() }, "goproxy"}, + {"github_rpm", func() resource.Resource { return NewRemoteGitHubRPM() }, "github_rpm"}, } for _, tt := range tests {