Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add provider version to metadata generation #2496

Merged
merged 33 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ba7e76b
Add provider versions to the getBlueprintRequirements function
Aug 7, 2024
8b24e2b
Pass in versions config file from cmd.go
Aug 7, 2024
f3d6eac
defined required_providers block schema, init the parseProviderVersio…
Aug 8, 2024
3b3a89e
implement parseBlueprintProviderVersion function
Aug 8, 2024
ef4a28f
Buildable CFT cli
Aug 9, 2024
3a66b4c
Generate provider versions as expected
Aug 9, 2024
6b2ea23
Cleanup logging messages, handle not found data for provider versions
Aug 9, 2024
d85a4fc
Merge branch 'master' into 356844884-provider-version
qz267 Aug 9, 2024
b316c0d
Merge branch 'GoogleCloudPlatform:master' into 356844884-provider-ver…
qz267 Aug 9, 2024
8d679e7
Add provider versions to the getBlueprintRequirements function
Aug 7, 2024
517230b
Pass in versions config file from cmd.go
Aug 7, 2024
b22b070
defined required_providers block schema, init the parseProviderVersio…
Aug 8, 2024
82f6e54
Buildable CFT cli
Aug 9, 2024
6d74abb
Generate provider versions as expected
Aug 9, 2024
65efc17
Update validator schema to match providerVersion name
Aug 9, 2024
95bdbe8
Add TF test for provider versions
Aug 9, 2024
981d57e
Refactoring implementation using config inspect
Aug 9, 2024
3b982be
Add provider version block in the golden data set
Aug 9, 2024
85ce4a9
Update validator proto gotag from versions to providerVersions
Aug 10, 2024
5b75721
remove unused required provider schema
Aug 10, 2024
463ada6
Merge branch 'master' into 356844884-provider-version
apeabody Aug 12, 2024
f6fe263
revert codefmt changes
Sep 4, 2024
e715ca3
Merge branch 'master' into 356844884-provider-version
qz267 Sep 4, 2024
2dce285
Add empty and incomplete test cases for metadata generation
Sep 4, 2024
d75c172
using log.info to replace fmt.printf
Sep 4, 2024
af67641
Unit and integration tests passing
Sep 5, 2024
1756002
Update cli/bpmetadata/tfconfig.go
qz267 Sep 6, 2024
375c7a4
Update cli/bpmetadata/tfconfig.go
qz267 Sep 6, 2024
4b72db6
Simplify provider version data assigment logic
Sep 6, 2024
a64ad37
Check if versionConfigPath, auto gen provider versions if the config …
Sep 6, 2024
48c4c29
Using fileExists in cli/bpmetadata/path.go to check version config fi…
Sep 6, 2024
321fa49
Merge branch 'master' into 356844884-provider-version
apeabody Sep 9, 2024
61f42a1
Update golden metadata provider order to pass integration test
Sep 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/bpmetadata/bpmetadata.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion cli/bpmetadata/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,8 @@ func CreateBlueprintMetadata(bpPath string, bpMetadataObj *BlueprintMetadata) (*
// get blueprint requirements
rolesCfgPath := path.Join(repoDetails.Source.BlueprintRootPath, tfRolesFileName)
svcsCfgPath := path.Join(repoDetails.Source.BlueprintRootPath, tfServicesFileName)
requirements, err := getBlueprintRequirements(rolesCfgPath, svcsCfgPath)
versionsCfgPath := path.Join(repoDetails.Source.BlueprintRootPath, tfVersionsFileName)
requirements, err := getBlueprintRequirements(rolesCfgPath, svcsCfgPath, versionsCfgPath)
if err != nil {
Log.Info("skipping blueprint requirements since roles and/or services configurations were not found as per https://tinyurl.com/tf-iam and https://tinyurl.com/tf-services")
} else {
Expand Down
5 changes: 5 additions & 0 deletions cli/bpmetadata/int-test/goldens/golden-metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,8 @@ spec:
- cloudresourcemanager.googleapis.com
- compute.googleapis.com
- serviceusage.googleapis.com
providerVersions:
- source: hashicorp/random
version: ">= 2.1"
- source: hashicorp/google
version: ">= 4.42, < 5.0"
2 changes: 1 addition & 1 deletion cli/bpmetadata/proto/bpmetadata.proto
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ message BlueprintRequirements {

// Required provider versions.
// Gen: auto-generated from required providers block.
repeated ProviderVersion provider_versions = 3; // @gotags: json:"versions,omitempty" yaml:"providerVersions,omitempty"
repeated ProviderVersion provider_versions = 3; // @gotags: json:"providerVersions,omitempty" yaml:"providerVersions,omitempty"
}

// ProviderVersion defines the required version for a provider.
Expand Down
2 changes: 1 addition & 1 deletion cli/bpmetadata/schema/gcp-blueprint-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@
},
"type": "array"
},
"versions": {
"providerVersions": {
"items": {
"$ref": "#/$defs/ProviderVersion"
},
Expand Down
83 changes: 67 additions & 16 deletions cli/bpmetadata/tfconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path/filepath"
"regexp"
"sort"
"strings"

hcl "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
Expand Down Expand Up @@ -188,6 +189,36 @@ func parseBlueprintVersion(versionsFile *hcl.File, diags hcl.Diagnostics) (strin
return "", nil
}

// parseBlueprintProviderVersions gets the blueprint provider_versions from the provided config
// from the required_providers block.
func parseBlueprintProviderVersions(versionsFile *hcl.File) ([]*ProviderVersion, error) {
var v []*ProviderVersion
// parse out the required providers from the config
var hclModule tfconfig.Module
hclModule.RequiredProviders = make(map[string]*tfconfig.ProviderRequirement)
diags := tfconfig.LoadModuleFromFile(versionsFile, &hclModule)
err := hasHclErrors(diags)
if err != nil {
return nil, err
}

for _, providerData := range hclModule.RequiredProviders {
if providerData.Source == "" {
Log.Info("Not found source in provider settings\n")
continue
}
if len(providerData.VersionConstraints) == 0 {
Log.Info("Not found version in provider settings\n")
continue
}
v = append(v, &ProviderVersion{
Source: providerData.Source,
Version: strings.Join(providerData.VersionConstraints, ", "),
})
}
return v, nil
}

// getBlueprintInterfaces gets the variables and outputs associated
// with the blueprint
func getBlueprintInterfaces(configPath string) (*BlueprintInterface, error) {
Expand Down Expand Up @@ -253,7 +284,7 @@ func getBlueprintOutput(modOut *tfconfig.Output) *BlueprintOutput {

// getBlueprintRequirements gets the services and roles associated
// with the blueprint
func getBlueprintRequirements(rolesConfigPath, servicesConfigPath string) (*BlueprintRequirements, error) {
func getBlueprintRequirements(rolesConfigPath, servicesConfigPath, versionsConfigPath string) (*BlueprintRequirements, error) {
//parse blueprint roles
p := hclparse.NewParser()
rolesFile, diags := p.ParseHCLFile(rolesConfigPath)
Expand All @@ -279,10 +310,30 @@ func getBlueprintRequirements(rolesConfigPath, servicesConfigPath string) (*Blue
return nil, err
}

return &BlueprintRequirements{
Roles: r,
Services: s,
}, nil
if versionsConfigPath != "" {
qz267 marked this conversation as resolved.
Show resolved Hide resolved
//parse blueprint provider versions
versionsFile, diags := p.ParseHCLFile(versionsConfigPath)
err = hasHclErrors(diags)
if err != nil {
return nil, err
}

v, err := parseBlueprintProviderVersions(versionsFile)
if err != nil {
return nil, err
}

return &BlueprintRequirements{
Roles: r,
Services: s,
ProviderVersions: v,
}, nil
} else {
qz267 marked this conversation as resolved.
Show resolved Hide resolved
return &BlueprintRequirements{
Roles: r,
Services: s,
}, nil
}
}

// parseBlueprintRoles gets the roles required for the blueprint to be provisioned
Expand Down Expand Up @@ -399,15 +450,15 @@ func hasTfconfigErrors(diags tfconfig.Diagnostics) error {
// MergeExistingConnections merges existing connections from an old BlueprintInterface into a new one,
// preserving manually authored connections.
func mergeExistingConnections(newInterfaces, existingInterfaces *BlueprintInterface) {
if existingInterfaces == nil {
return // Nothing to merge if existingInterfaces is nil
}

for i, variable := range newInterfaces.Variables {
for _, existingVariable := range existingInterfaces.Variables {
if variable.Name == existingVariable.Name && existingVariable.Connections != nil {
newInterfaces.Variables[i].Connections = existingVariable.Connections
}
}
}
if existingInterfaces == nil {
return // Nothing to merge if existingInterfaces is nil
}

for i, variable := range newInterfaces.Variables {
for _, existingVariable := range existingInterfaces.Variables {
if variable.Name == existingVariable.Name && existingVariable.Connections != nil {
newInterfaces.Variables[i].Connections = existingVariable.Connections
}
}
}
}
79 changes: 69 additions & 10 deletions cli/bpmetadata/tfconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import (
)

const (
tfTestdataPath = "../testdata/bpmetadata/tf"
tfTestdataPath = "../testdata/bpmetadata/tf"
metadataTestdataPath = "../testdata/bpmetadata/metadata"
interfaces = "sample-module"
interfaces = "sample-module"
)

func TestTFInterfaces(t *testing.T) {
Expand Down Expand Up @@ -268,25 +268,58 @@ func TestTFRoles(t *testing.T) {
}
}

func TestTFProviderVersions(t *testing.T) {
tests := []struct {
name string
configName string
wantProviderVersions []*ProviderVersion
}{
{
qz267 marked this conversation as resolved.
Show resolved Hide resolved
name: "Simple list of provider versions",
configName: "versions-beta.tf",
wantProviderVersions: []*ProviderVersion{
{
Source: "hashicorp/google",
Version: ">= 4.4.0, < 7",
},
{
Source: "hashicorp/google-beta",
Version: ">= 4.4.0, < 7",
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := hclparse.NewParser()
content, _ := p.ParseHCLFile(path.Join(tfTestdataPath, tt.configName))
got, err := parseBlueprintProviderVersions(content)
require.NoError(t, err)
assert.Equal(t, got, tt.wantProviderVersions)
})
}
}

func TestMergeExistingConnections(t *testing.T) {
tests := []struct {
name string
newInterfacesFile string
name string
newInterfacesFile string
existingInterfacesFile string
}{
{
name: "No existing connections",
newInterfacesFile: "new_interfaces_no_connections_metadata.yaml",
name: "No existing connections",
newInterfacesFile: "new_interfaces_no_connections_metadata.yaml",
existingInterfacesFile: "existing_interfaces_without_connections_metadata.yaml",
},
{
name: "One existing connection is preserved",
newInterfacesFile: "new_interfaces_no_connections_metadata.yaml",
name: "One existing connection is preserved",
newInterfacesFile: "new_interfaces_no_connections_metadata.yaml",
existingInterfacesFile: "existing_interfaces_with_one_connection_metadata.yaml",
},
{
name: "Multiple existing connections are preserved",
newInterfacesFile: "new_interfaces_no_connections_metadata.yaml",
name: "Multiple existing connections are preserved",
newInterfacesFile: "new_interfaces_no_connections_metadata.yaml",
existingInterfacesFile: "existing_interfaces_with_some_connections_metadata.yaml",
},
}
Expand All @@ -309,3 +342,29 @@ func TestMergeExistingConnections(t *testing.T) {
})
}
}

func TestTFIncompleteProviderVersions(t *testing.T) {
tests := []struct {
name string
configName string
}{
{
name: "Empty list of provider versions",
configName: "provider-versions-empty.tf",
},
{
name: "Missing ProviderVersion field",
configName: "provider-versions-bad.tf",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := hclparse.NewParser()
content, _ := p.ParseHCLFile(path.Join(tfTestdataPath, tt.configName))
got, err := parseBlueprintProviderVersions(content)
require.NoError(t, err)
assert.Nil(t, got)
})
}
}
19 changes: 19 additions & 0 deletions cli/testdata/bpmetadata/tf/provider-versions-bad.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
terraform {
required_version = ">= 0.13.0"

required_providers {
google = {
version = ">= 4.4.0, < 6"
}
google-beta = {
source = "hashicorp/google-beta"
}
}

provider_meta "google" {
module_name = "blueprints/terraform/terraform-google-kubernetes-engine:hub/v23.1.0"
}
provider_meta "google-beta" {
module_name = "blueprints/terraform/terraform-google-kubernetes-engine:hub/v23.1.0"
}
}
13 changes: 13 additions & 0 deletions cli/testdata/bpmetadata/tf/provider-versions-empty.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
terraform {
required_version = ">= 0.13.0"

required_providers {
}

provider_meta "google" {
module_name = "blueprints/terraform/terraform-google-kubernetes-engine:hub/v23.1.0"
}
provider_meta "google-beta" {
module_name = "blueprints/terraform/terraform-google-kubernetes-engine:hub/v23.1.0"
}
}
Loading