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

🤖 auto-generate resources for Mondoo integrations #195

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/actions/spelling/excludes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,6 @@ ignore$
\.xpm$
\.xz$
\.zip$
\.tmpl$
^\.github/actions/spelling/
^\Q.github/workflows/spelling.yml\E$
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ bin/
dist/
modules-dev/
/pkg/
gen/generated
website/.vagrant
website/.bundle
website/build
Expand Down
154 changes: 154 additions & 0 deletions gen/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright (c) Mondoo, Inc.
// SPDX-License-Identifier: BUSL-1.1

package main

import (
"fmt"
"log"
"os"
"path/filepath"
"regexp"
"strings"
"text/template"

"github.com/fatih/structs"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not include unmaintained go modules.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find any other go module that does this. I we ok copying the code and NOT use the import?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would the alternative solution not to use mapstructure? github.com/go-viper/mapstructure/v2. In case we need to change the tags for mondoo-go, we need touch the templates here https://github.com/mondoohq/mondoo-go/blob/main/gen/gen.go#L260

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! 🌟

mondoov1 "go.mondoo.com/mondoo-go"
)

func main() {
// Generate resources for Mondoo integrations only
err := generateIntegrationResources()
if err != nil {
log.Fatalln(err)
}
}

type IntegrationResource struct {
ResourceClassName string
TerraformResourceName string
}

// generateIntegrationResources generates Terraform resources for Mondoo's integrations.
func generateIntegrationResources() error {
// Read the template file
resourceTemplateFile := filepath.Join("gen", "templates", "integration_resource.go.tmpl")
resourceTmpl, err := template.ParseFiles(resourceTemplateFile)
if err != nil {
return err
}

testTemplateFile := filepath.Join("gen", "templates", "integration_resource_test.go.tmpl")
testTmpl, err := template.ParseFiles(testTemplateFile)
if err != nil {
return err
}

resourceDotTFTemplateFile := filepath.Join("gen", "templates", "resource.tf.tmpl")
resourceTFTmpl, err := template.ParseFiles(resourceDotTFTemplateFile)
if err != nil {
return err
}

// Ensure the output directory exists
outputDirPath := filepath.Join("gen", "generated")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we create this directly in the directory the other integrations are in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not yet. still need some more work but we will ultimately write it in the right directory.

if err := os.MkdirAll(outputDirPath, 0755); err != nil {
return err
}

i := mondoov1.ClientIntegrationConfigurationInput{}
m := structs.Map(i)

for k := range m {
// TODO we know the type and the struct associated to the type, we need
// to look it (the struct) and use the same `structs.Map(v)` to list all
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something reads strange to me.
Perhaps:

Suggested change
// to look it (the struct) and use the same `structs.Map(v)` to list all
// to look at it (the struct) and use the same `structs.Map(v)` to list all

// fields per integration and auto generate the terraform schema and more
// details, for now, we only leave a comment where we need to add specific
// integration options

var (
className, _ = strings.CutSuffix(k, "ConfigurationOptions")
terraformResourceName = strings.ToLower(toSnakeCase(className))
resource = IntegrationResource{
ResourceClassName: className,
TerraformResourceName: terraformResourceName,
}
)

fmt.Printf("> Generating code for %s integration\n", className)

// Create the resource file
resourceOutputFilePath := filepath.Join(outputDirPath,
fmt.Sprintf("integration_%s_resource.go", terraformResourceName),
)
resourceFile, err := os.Create(resourceOutputFilePath)
if err != nil {
return err
}
defer resourceFile.Close()

if err := resourceTmpl.Execute(resourceFile, resource); err != nil {
return err
}

// Create test file
testOutputFilePath := filepath.Join(outputDirPath,
fmt.Sprintf("integration_%s_resource_test.go", terraformResourceName),
)
testFile, err := os.Create(testOutputFilePath)
if err != nil {
return err
}
defer testFile.Close()

if err := testTmpl.Execute(testFile, resource); err != nil {
return err
}

// Create examples/ files
examplesDirPath := filepath.Join(outputDirPath, "examples", fmt.Sprintf("mondoo_integration_%s", terraformResourceName))
// Ensure the output directory exists
if err := os.MkdirAll(examplesDirPath, 0755); err != nil {
return err
}
// Create example main.tf
err = os.WriteFile(filepath.Join(examplesDirPath, "main.tf"), mainDotTFTestFile(), 0644)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: Could we generate this directly in the final directory?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not yet. still need some more work but we will ultimately write it in the right directory.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something for a follow-up or do you want to include it in this PR?

if err != nil {
return err
}
// Create example resource.tf
resourceTFOutputFilePath := filepath.Join(examplesDirPath, "resource.tf")
resourceTFFile, err := os.Create(resourceTFOutputFilePath)
if err != nil {
return err
}
defer testFile.Close()

if err := resourceTFTmpl.Execute(resourceTFFile, resource); err != nil {
return err
}
}

return nil
}

var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this works as expected. Wouldn't the . also match an upper case letter?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, I'm missing something here.
Could you please add a comment or a test case to show how this works?

var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")

func toSnakeCase(str string) string {
snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}")
snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
return strings.ToLower(snake)
}

func mainDotTFTestFile() []byte {
return []byte(`terraform {
required_providers {
mondoo = {
source = "mondoohq/mondoo"
version = ">= 0.19"
}
}
}
`)
}
Loading
Loading