Skip to content

Conversation

@vic1707
Copy link
Contributor

@vic1707 vic1707 commented Jul 23, 2025

Well, this PR was opened much earlier than intended due to my mistake; I accidentally opened it against the source repo instead of my fork 😓.

This PR proposes a solution to #111, as envsubst may not be sufficient for all users.

This PR makes butane use gomplate as a templating engine, each file butane wants to open is passed through gomplate first.
Gomplate is configurable by having a .gomplate.yaml file in the current directory, no flags are used to configure gomplate (for CLI simplicity).

Disclaimer: This is my first time working with Go. I'm learning as I go, so things might not be perfect or fully optimized. Please be kind 🙏

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces Gomplate integration to allow templating of local files. The implementation adds new functions and a command-line flag to control this feature. My review focuses on correctness and maintainability. I've identified a critical issue with unreachable code and a high-severity bug where the loaded Gomplate configuration is not being used. I've also included some medium-severity suggestions regarding the use of global variables and potential memory usage improvements for large files.

Comment on lines 62 to 65
// if > v0.7
return GomplateReadLocalFile(file)
// else
return io.ReadAll(file)

Choose a reason for hiding this comment

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

critical

This block of code contains an unreachable return statement. The call to GomplateReadLocalFile on line 63 will always execute, making the io.ReadAll call on line 65 unreachable.

The comments // if > v0.7 and // else suggest that some conditional logic was intended here. This should be cleaned up to either implement the intended logic or remove the dead code and comments for clarity.

Suggested change
// if > v0.7
return GomplateReadLocalFile(file)
// else
return io.ReadAll(file)
return GomplateReadLocalFile(file)

return err
}

gomplateRenderer = gomplate.NewRenderer(gomplate.RenderOptions{})

Choose a reason for hiding this comment

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

high

The gomplateConfig is loaded from .gomplate.yaml in InitGomplateConfig but it's never actually used when initializing the gomplateRenderer. The gomplate.NewRenderer is called with empty RenderOptions. To apply the configuration from the file, you should pass it in the RenderOptions.

Suggested change
gomplateRenderer = gomplate.NewRenderer(gomplate.RenderOptions{})
gomplateRenderer = gomplate.NewRenderer(gomplate.RenderOptions{Config: gomplateConfig})

Comment on lines 83 to 84
var gomplateConfig *gomplate.Config
var gomplateRenderer gomplate.Renderer

Choose a reason for hiding this comment

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

medium

Using global variables like gomplateConfig and gomplateRenderer can make the code harder to reason about and test, as it introduces global state. While this might be acceptable for a command-line tool where initialization happens once at startup, consider refactoring this to avoid global state for better maintainability and testability. For example, you could have an initializer that returns a struct containing the renderer, which is then passed around where needed.

}

dataIn, err := io.ReadAll(infile)
dataIn, err := baseutil.GomplateReadLocalFile(infile)

Choose a reason for hiding this comment

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

medium

The GomplateReadLocalFile function is now being called on os.Stdin. This function reads the entire file into memory before processing. While this is consistent with how other files are handled, it could be problematic if a very large file is piped to stdin.

For streaming processing with gomplate, you could pass the io.Reader (infile) directly to gomplateRenderer.Render. This would avoid reading the entire input into memory at once.

// In GomplateReadLocalFile
// ...
err = gomplateRenderer.Render(context.Background(), file.Name(), file, &buf)

@vic1707 vic1707 changed the title Gomplate integration [WIP] Gomplate integration Jul 23, 2025
@vic1707 vic1707 force-pushed the gomplate-integration branch 2 times, most recently from 5c848a6 to fee2023 Compare July 23, 2025 18:13
@vic1707 vic1707 force-pushed the gomplate-integration branch from 69cb34c to c1fab1c Compare August 2, 2025 23:14
@vic1707 vic1707 changed the title [WIP] Gomplate integration Gomplate integration Aug 3, 2025
@vic1707 vic1707 changed the title Gomplate integration [Ready] Gomplate integration Aug 3, 2025
@vic1707 vic1707 mentioned this pull request Aug 3, 2025
@travier
Copy link
Member

travier commented Aug 4, 2025

Just looking at the amount of lines changed here (all the vendoring), I don't think this is a realistic path forward.

@travier
Copy link
Member

travier commented Aug 4, 2025

To make it easier to review/consider, please put all the vendored code in a single commit, the rest of the changes in another one.

@vic1707 vic1707 force-pushed the gomplate-integration branch from db0444a to 4a1bbd7 Compare August 4, 2025 14:16
@vic1707
Copy link
Contributor Author

vic1707 commented Aug 4, 2025

Just looking at the amount of lines changed here (all the vendoring), I don't think this is a realistic path forward.

I believe you're talking about the whole concept of embeding gomplate, not the amount of commits I had 😓 ?

To make it easier to review/consider, please put all the vendored code in a single commit, the rest of the changes in another one.

Done, the PR has now 3 commits

  1. fix a small typo: an extra ) in the license comment
  2. installing and vendoring everything
  3. the actual implementation

Thanks for your time and the review!

Note: I didn't add the license on the newly created files for now, I'll add it once you're ok with what I did 😉

Edit:

  • Do you want me to ping you upon updates/messages?
  • I thing I found a way to get all the config settings applied to this implementation (currently missing experimental support amongst others), the core idea of the PR won't change, but some implementation details might

@vic1707 vic1707 changed the title [Ready] Gomplate integration Gomplate integration Aug 5, 2025
@vic1707 vic1707 force-pushed the gomplate-integration branch from 4a1bbd7 to 606dace Compare August 10, 2025 18:21
@vic1707
Copy link
Contributor Author

vic1707 commented Aug 10, 2025

Was able to add support for the Experimental mode (and simplify implementation),
Appart from the Input and Output class configurations which are incompatible with butane,
I'm still missing

Plugins
PostExec
PluginTimeout
ExecPipe

I don't think ExecPipe or PostExec should be used, so only Plugins are missing assuming they're wanted.
Will look into it asap, still code is reviewable now 😉

Edit: Plugins also done, was easier than I thought

- gomplate is opt-in via the `--enable-gomplate` flag
- the only way to configure it is by providing a `.gomplate.yaml` file in the used `files-dir`
@vic1707 vic1707 force-pushed the gomplate-integration branch from 606dace to 9f9c493 Compare August 10, 2025 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants