Skip to content
This repository has been archived by the owner on Nov 30, 2023. It is now read-only.

Add warning message for deprecated "hashicorp/terraform" provider #111

Merged
merged 10 commits into from
Oct 11, 2023
21 changes: 21 additions & 0 deletions src/internal/warnings/warnings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Package warnings defines the warnings associated with the provider
package warnings

// ProviderWarnings return the list of warnings for a given provider identified by its namespace and type
//
// Example: registry.terraform.io/hashicorp/terraform
//
// warn := ProviderWarnings("hashicorp", "terraform")
// fmt.Println(warn)
// >> [This provider is archived and no longer needed. The terraform_remote_state data source is built into the latest OpenTofu release.]
func ProviderWarnings(providerNamespace, providerType string) []string {
switch providerNamespace { //nolint:gocritic // Switch is more appropriate than 'if' for the use case
case "hashicorp":
switch providerType { //nolint:gocritic // Switch is more appropriate than 'if' for the use case
case "terraform":
return []string{`This provider is archived and no longer needed. The terraform_remote_state data source is built into the latest OpenTofu release.`}
}
}

return nil
}
44 changes: 44 additions & 0 deletions src/internal/warnings/warnings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package warnings

import (
"reflect"
"testing"
)

func TestProviderWarnings(t *testing.T) {
type args struct {
providerNamespace string
providerType string
}
tests := []struct {
name string
args args
want []string
}{
{
name: "shall return no warnings",
args: args{
providerNamespace: "foo",
providerType: "bar",
},
want: nil,
},
{
name: "shall return warnings as in https://github.com/opentofu/registry/issues/108",
args: args{
providerNamespace: "hashicorp",
providerType: "terraform",
},
want: []string{`This provider is archived and no longer needed. The terraform_remote_state data source is built into the latest OpenTofu release.`},
},
}
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
if got := ProviderWarnings(tt.args.providerNamespace, tt.args.providerType); !reflect.DeepEqual(got, tt.want) {
t.Errorf("ProviderWarnings() = %v, want %v", got, tt.want)
}
},
)
}
}
68 changes: 39 additions & 29 deletions src/lambda/api/providerVersions.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/opentofu/registry/internal/github"
"github.com/opentofu/registry/internal/providers"
"github.com/opentofu/registry/internal/providers/types"
"github.com/opentofu/registry/internal/warnings"
"golang.org/x/exp/slog"
)

Expand All @@ -39,6 +40,7 @@ func getListProvidersPathParams(req events.APIGatewayProxyRequest) ListProviders

type ListProviderVersionsResponse struct {
Versions []types.Version `json:"versions"`
Warnings []string `json:"warnings,omitempty"`
}

func listProviderVersions(config config.Config) LambdaFunc {
Expand All @@ -47,17 +49,18 @@ func listProviderVersions(config config.Config) LambdaFunc {
params.AnnotateLogger()

effectiveNamespace := config.EffectiveProviderNamespace(params.Namespace)
repoName := providers.GetRepoName(params.Type)

// For now, we will ignore errors from the cache and just fetch from GH instead if the document doesn't exist
document, _ := config.ProviderVersionCache.GetItem(ctx, fmt.Sprintf("%s/%s", effectiveNamespace, params.Type))
if document != nil {
return processDocumentForProviderListing(ctx, document, config, effectiveNamespace, params.Type)
// Warnings lookup: https://github.com/opentofu/registry/issues/108
warn := warnings.ProviderWarnings(params.Namespace, params.Type)

// For now, we will ignore errors from the cache and just fetch from GH instead
versionList, _ := listVersionsFromCache(ctx, config, effectiveNamespace, params.Type)
if len(versionList) > 0 {
return versionsResponse(versionList, warn)
}

// now that we know we don't have the document, we should check that the repo exists
// if we checked the repo exists before then we are making extra calls to GitHub that we don't need to make.
if exists, err := github.RepositoryExists(ctx, config.ManagedGithubClient, effectiveNamespace, repoName); !exists {
versionList, repoExists, err := listVersionsFromRepository(ctx, config, effectiveNamespace, params.Type)
if !repoExists {
if err != nil {
slog.Error("Error checking if repo exists", "error", err)
return events.APIGatewayProxyResponse{StatusCode: http.StatusInternalServerError}, err
Expand All @@ -66,43 +69,46 @@ func listProviderVersions(config config.Config) LambdaFunc {
// if the repo doesn't exist, there's no point in trying to fetch versions
return NotFoundResponse, nil
}
if err != nil {
slog.Error("Error fetching versions from github", "error", err)
return events.APIGatewayProxyResponse{StatusCode: http.StatusInternalServerError}, err
}

// if the document didn't exist in the cache, trigger the lambda to populate it and return the current results from GH
// if the document didn't exist in the cache, trigger the lambda to populate it
if err := triggerPopulateProviderVersions(ctx, config, effectiveNamespace, params.Type); err != nil {
slog.Error("Error triggering lambda", "error", err)
}

return fetchFromGithub(ctx, config, effectiveNamespace, repoName)
return versionsResponse(versionList, warn)
}
}

func processDocumentForProviderListing(ctx context.Context, document *types.CacheItem, config config.Config, namespace, providerType string) (events.APIGatewayProxyResponse, error) {
slog.Info("Found document in cache", "last_updated", document.LastUpdated, "versions", len(document.Versions))

if !document.IsStale() {
slog.Info("Document is not too old, returning cached versions", "last_updated", document.LastUpdated)
return versionsResponse(document.Versions.ToVersions())
func listVersionsFromCache(ctx context.Context, config config.Config, effectiveNamespace, providerType string) ([]types.Version, error) {
document, err := config.ProviderVersionCache.GetItem(ctx, fmt.Sprintf("%s/%s", effectiveNamespace, providerType))
if err != nil || document == nil {
return nil, err
}

slog.Info("Document is too old, triggering lambda to update dynamodb", "last_updated", document.LastUpdated)
if err := triggerPopulateProviderVersions(ctx, config, namespace, providerType); err != nil {
// if we can't trigger the lambda, we should still return the cached versions and just log the error
slog.Error("Error triggering lambda", "error", err)
slog.Info("Found document in cache", "last_updated", document.LastUpdated, "versions", len(document.Versions))

if document.IsStale() {
return nil, nil
}

return versionsResponse(document.Versions.ToVersions())
slog.Info("Document is not too old, returning cached versions", "last_updated", document.LastUpdated)
return document.Versions.ToVersions(), nil
}

func fetchFromGithub(ctx context.Context, config config.Config, namespace, repoName string) (events.APIGatewayProxyResponse, error) {
slog.Info("Fetching versions from github\n")

versionList, err := providers.GetVersions(ctx, config.RawGithubv4Client, namespace, repoName)
func listVersionsFromRepository(ctx context.Context, config config.Config, effectiveNamespace, providerType string) ([]types.Version, bool, error) {
repoName := providers.GetRepoName(providerType)
exists, err := github.RepositoryExists(ctx, config.ManagedGithubClient, effectiveNamespace, repoName)
if err != nil {
slog.Error("Error fetching versions from github", "error", err)
return events.APIGatewayProxyResponse{StatusCode: http.StatusInternalServerError}, err
return nil, exists, err
}

return versionsResponse(versionList.ToVersions())
slog.Info("Fetching versions from github\n")
versionList, err := providers.GetVersions(ctx, config.RawGithubv4Client, effectiveNamespace, repoName)
return versionList.ToVersions(), exists, err
}

func triggerPopulateProviderVersions(ctx context.Context, config config.Config, effectiveNamespace string, effectiveType string) error {
Expand All @@ -120,11 +126,15 @@ func triggerPopulateProviderVersions(ctx context.Context, config config.Config,
return nil
}

func versionsResponse(versions []types.Version) (events.APIGatewayProxyResponse, error) {
func versionsResponse(versions []types.Version, warnings []string) (events.APIGatewayProxyResponse, error) {
response := ListProviderVersionsResponse{
Versions: versions,
}

if len(warnings) > 0 {
response.Warnings = warnings
}

resBody, err := json.Marshal(response)
if err != nil {
return events.APIGatewayProxyResponse{StatusCode: http.StatusInternalServerError}, err
Expand Down
Loading