Skip to content

Commit

Permalink
refactor: update tests and refactor code
Browse files Browse the repository at this point in the history
Signed-off-by: Gamunu Balagalla <gamunu@fastcode.io>
  • Loading branch information
gamunu committed Sep 16, 2024
1 parent 0c940c3 commit f0b2b34
Show file tree
Hide file tree
Showing 6 changed files with 509 additions and 652 deletions.
57 changes: 32 additions & 25 deletions internal/features/modules/hooks/module_version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,29 @@ import (
)

var moduleVersionsMockResponse = `{
"modules": [
{
"source": "terraform-aws-modules/vpc/aws",
"versions": [
{
"version": "0.0.1"
},
{
"version": "2.0.24"
},
{
"version": "1.33.7"
}
]
}
]
}`
"addr": {
"display": "azure/aks/azurerm",
"namespace": "azure",
"name": "aks",
"target": "azurerm"
},
"description": "Terraform Module for deploying an AKS cluster",
"versions": [
{
"id": "v9.1.0",
"published": "2024-07-04T07:12:29+01:00"
},
{
"id": "v9.0.0",
"published": "2024-06-07T02:31:28+01:00"
},
{
"id": "v8.0.0",
"published": "2024-03-05T07:33:07Z"
}
],
"is_blocked": false
}`

func TestHooks_RegistryModuleVersions(t *testing.T) {
ctx := context.Background()
Expand Down Expand Up @@ -67,7 +73,7 @@ func TestHooks_RegistryModuleVersions(t *testing.T) {

regClient := registry.NewClient()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.RequestURI == "/v1/modules/terraform-aws-modules/vpc/aws/versions" {
if r.RequestURI == "/modules/azure/aks/azurerm/index.json" {
w.Write([]byte(moduleVersionsMockResponse))
return
}
Expand All @@ -90,7 +96,7 @@ func TestHooks_RegistryModuleVersions(t *testing.T) {
ModuleCalls: map[string]tfmod.DeclaredModuleCall{
"vpc": {
LocalName: "vpc",
SourceAddr: tfaddr.MustParseModuleSource("registry.terraform.io/terraform-aws-modules/vpc/aws"),
SourceAddr: tfaddr.MustParseModuleSource("registry.opentofu.org/azure/aks/azurerm"),
RangePtr: &hcl.Range{
Filename: "main.tf",
Start: hcl.Pos{Line: 1, Column: 1, Byte: 1},
Expand All @@ -106,26 +112,27 @@ func TestHooks_RegistryModuleVersions(t *testing.T) {

expectedCandidates := []decoder.Candidate{
{
Label: `"2.0.24"`,
Label: `"9.1.0"`,
Kind: lang.StringCandidateKind,
RawInsertText: `"2.0.24"`,
RawInsertText: `"9.1.0"`,
SortText: " 0",
},
{
Label: `"1.33.7"`,
Label: `"9.0.0"`,
Kind: lang.StringCandidateKind,
RawInsertText: `"1.33.7"`,
RawInsertText: `"9.0.0"`,
SortText: " 1",
},
{
Label: `"0.0.1"`,
Label: `"8.0.0"`,
Kind: lang.StringCandidateKind,
RawInsertText: `"0.0.1"`,
RawInsertText: `"8.0.0"`,
SortText: " 2",
},
}

candidates, _ := h.RegistryModuleVersions(ctx, cty.StringVal(""))
fmt.Print(candidates)
if diff := cmp.Diff(expectedCandidates, candidates); diff != "" {
t.Fatalf("mismatched candidates: %s", diff)
}
Expand Down
57 changes: 20 additions & 37 deletions internal/features/modules/jobs/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,15 +250,9 @@ func GetModuleDataFromRegistry(ctx context.Context, regClient registry.Client, m
continue
}

inputs := make([]tfregistry.Input, len(metaData.Root.Inputs))
for i, input := range metaData.Root.Inputs {
isRequired := isRegistryModuleInputRequired(metaData.PublishedAt, input)
inputs[i] = tfregistry.Input{
Name: input.Name,
Description: lang.Markdown(input.Description),
Required: isRequired,
}

inputs := make([]tfregistry.Input, 0, len(metaData.Inputs))
for name, input := range metaData.Inputs {
isRequired := input.Required
inputType := cty.DynamicPseudoType
if input.Type != "" {
// Registry API unfortunately doesn't marshal types using
Expand All @@ -270,27 +264,34 @@ func GetModuleDataFromRegistry(ctx context.Context, regClient registry.Client, m
inputType = typ
}
}
inputs[i].Type = inputType
newInput := tfregistry.Input{
Name: name,
Description: lang.Markdown(input.Description),
Required: isRequired,
Type: inputType,
}

if input.Default != "" {
if input.Default != nil {
// Registry API unfortunately doesn't marshal values using
// cty marshalers, making it lossy, so we just try to decode
// on best-effort basis.
val, err := ctyjson.Unmarshal([]byte(input.Default), inputType)
val, err := ctyjson.Unmarshal([]byte(fmt.Sprintf("%v", input.Default)), inputType)
if err == nil {
inputs[i].Default = val
newInput.Default = val
}
}
inputs = append(inputs, newInput)
}
outputs := make([]tfregistry.Output, len(metaData.Root.Outputs))
for i, output := range metaData.Root.Outputs {
outputs[i] = tfregistry.Output{
Name: output.Name,

outputs := make([]tfregistry.Output, 0, len(metaData.Outputs))
for name, output := range metaData.Outputs {
outputs = append(outputs, tfregistry.Output{
Name: name,
Description: lang.Markdown(output.Description),
}
})
}

modVersion, err := version.NewVersion(metaData.Version)
modVersion, err := version.NewVersion(metaData.ID)
if err != nil {
errs = multierror.Append(errs, err)
continue
Expand All @@ -313,21 +314,3 @@ func GetModuleDataFromRegistry(ctx context.Context, regClient registry.Client, m

return errs.ErrorOrNil()
}

// isRegistryModuleInputRequired checks whether the module input is required.
// It reflects the fact that modules ingested into the Registry
// may have used `default = null` (implying optional variable) which
// the Registry wasn't able to recognise until ~ 19th August 2022.
func isRegistryModuleInputRequired(publishTime time.Time, input registry.Input) bool {
fixTime := time.Date(2022, time.August, 20, 0, 0, 0, 0, time.UTC)
// Modules published after the date have "nullable" inputs
// (default = null) ingested as Required=false and Default="null".
//
// The same inputs ingested prior to the date make it impossible
// to distinguish variable with `default = null` and missing default.
if input.Required && input.Default == "" && publishTime.Before(fixTime) {
// To avoid false diagnostics, we safely assume the input is optional
return false
}
return input.Required
}
68 changes: 37 additions & 31 deletions internal/registry/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,68 +45,74 @@ type ModuleVersionDescriptor struct {
Published time.Time `json:"published"`
}

type ModuleVersion struct {
ID string `json:"id"`
Published time.Time `json:"published"`
Description string `json:"description"`
Downloads int `json:"downloads"`
Version string `json:"version"`
}

type ModuleDetails struct {
BaseDetails
Dependencies []ModuleDependency `json:"dependencies"`
ID string `json:"id"`
Published time.Time `json:"published"`
Readme bool `json:"readme"`
Inputs map[string]Variable `json:"variables"`
Outputs map[string]Output `json:"outputs"`
Providers []ProviderDependency `json:"providers"`
Dependencies []ModuleDependency `json:"dependencies"`
Submodules map[string]Submodule `json:"submodules"`
Resources []Resource `json:"resources"`
}

type BaseDetails struct {
Readme bool `json:"readme"`
Variables map[string]Variable `json:"variables"`
Outputs map[string]Output `json:"outputs"`
SchemaError string `json:"schema_error"`
EditLink string `json:"edit_link"`
type Variable struct {
Type string `json:"type"`
Default interface{} `json:"default"`
Description string `json:"description"`
Required bool `json:"required"`
Sensitive bool `json:"sensitive"`
}

type ModuleDependency struct {
Name string `json:"name"`
VersionConstraint string `json:"version_constraint"`
Source string `json:"source"`
type Output struct {
Description string `json:"description"`
Sensitive bool `json:"sensitive"`
}

type ProviderDependency struct {
Alias string `json:"alias"`
Name string `json:"name"`
FullName string `json:"full_name"`
VersionConstraint string `json:"version_constraint"`
}

type ModuleDependency struct {
Name string `json:"name"`
VersionConstraint string `json:"version_constraint"`
Source string `json:"source"`
}

type Resource struct {
Address string `json:"address"`
Type string `json:"type"`
Name string `json:"name"`
}

type Variable struct {
Type string `json:"type"`
Default interface{} `json:"default"`
Description string `json:"description"`
Required bool `json:"required"`
Sensitive bool `json:"sensitive"`
type Submodule struct {
ModuleDetails
}

type Output struct {
Description string `json:"description"`
Sensitive bool `json:"sensitive"`
type Example struct {
ModuleDetails
}

type License struct {
SPDX string `json:"spdx"`
Confidence float64 `json:"confidence"`
IsCompatible bool `json:"is_compatible"`
File string `json:"file"`
Link string `json:"link"`
}

type LicenseList []License

func (c Client) GetModuleData(ctx context.Context, addr tfaddr.Module, cons version.Constraints) (*ModuleDetails, error) {
v, err := c.GetMatchingModuleVersion(ctx, addr, cons)
if err != nil {
return nil, err
}

url := fmt.Sprintf("%s/modules/%s/%s/%s/%s/index.json",
url := fmt.Sprintf("%s/modules/%s/%s/%s/v%s/index.json",
c.BaseURL,
addr.Package.Namespace,
addr.Package.Name,
Expand Down
Loading

0 comments on commit f0b2b34

Please sign in to comment.