pulumi-policy
pulumi-policy copied to clipboard
Support for writing policies in Go
I was linked here from the Crossguard page and I see this was opened in 2020. Is there any update or timeline for when Go will have support for writing policies?
Any due date? Thanks!!
Peeking in., would love this too.
👍
+1 from me.
Any update on when this might be available in Go?
+1 from me
Any update? thanks.
/assign
There is an old hackathon project to add a Go SDK: https://github.com/pulumi/pulumi-policy/compare/hackweek/go-policy
Thank you @justinvp!
-
Is the Python implementation equivalent to the TypeScript one? I noticed that it lacks the following definition: TypeScript Definition. I already have a raw Golang implementation that follows the Python version. When I looked at the go-policy branch, I found this definition again: Golang Definition. However, I couldn't track it in the Python implementation.
-
Are you open to a remake of the integration tests runner? I created a Docker environment to run it in isolation. Here’s what I found:
- It takes around 30 minutes.
- It produces too much log output, mainly due to prints from the testing.Environment run command and diagnostic prints. (probably ok)
- It has become bloated, and I assume it was initially designed for only one runtime (Node.js). Python was added later, and I think it will get worse if Go and, eventually, .NET are added in the same manner.
Additionally, it fails for me for some reason. I didn't find any differences compared to the GitHub Actions configuration. I will investigate it deeper.
I made some simple prototype to keep every runtime isolated during integration tests. I will push it later.
- Is the Python implementation equivalent to the TypeScript one?
Yes. The TypeScript implementation does have some more advanced capabilities for filtering to specific resource types, but they're largely equivalent. For the config schema, Python has this, but lacks the full type definitions that are present in TypeScript. I think we could/should extend Python to include type information for this, similar to TypeScript, and the go hackathon branch, using TypedDicts.
- Are you open to a remake of the integration tests runner?
Open to it. Ideally more would run in parallel. For example, matrix it out such that each test ran as its own separate GitHub Action job, each of which would be isolated and run in parallel.
- I will first try to ensure that Python matches with TypeScript.
- I have a version that runs in parallel—10 minutes compared to 30. I also added some flags to exclude non-target language policy runtimes. I still want to improve some workflows.
The implementation of Go and .NET will require a significant amount of testing. The current integration test system is becoming quite difficult to manage and to use.
Integration tests in Docker.
Ready to create a pull request
Pretty well described in README.md. I tested it extensively. list of commits
Just to note, we could reuse GitHub Actions with Act for this purpose, but the GitHub Actions configurations include release steps. Therefore, Docker (or Podman) is more widely applicable in this case.
Refactoring of the integration tests runner
Mostly done and tested.
I refactored many parts of code. Although there are many changes, This small code snippet captures the essence of all the changes. . I created a type to maintain state and refactored each step of the run script into separate methods.
- Separated logic for every language runtime.
- Added t.Parallel() (ensuring to skip it on one test case which requires environments).
- Enabled skipping of specific language runtimes (e.g., running tests only for Go).
- Introduced parallelization on the dependency level (due to the global lock in testing environments for most programs written in TypeScript and using Yarn).
I also investigated hackweek/go-policy.
I couldn't take much from it as it was written (I assume) in a hurry. Still, it was useful to see a solution from a different perspective.
Right now, I have implemented a GRPC server that decodes all protobuf objects into types defined in the Pulumi library (mostly from the plugin package). I try to reuse already defined types as much as possible.
I am considering what would be the best library interface.
1
My first version followed the Python approach. It was pretty straightforward:
type Policy struct {
apitype.Policy
ValidateStack []StackValidator
Validate []ResourceValidator
Remediate ResourceRemediator
}
The user would only need to fill in Validate, Validate & Remediate, Remediate, or ValidateStack. If the user mixes them, it will fail at runtime.
We could provide functions like NewStackValidationPolicy, which would create policies with the correct fields.
We could also make these fields private.
2
This version is almost the same, except that we have private config and use the Functional Options pattern.
So it would look like this:
p := apitype.Policy{} // or we also could send every fields separately
NewPolicy(p, WithRemediation(f), WithValidation([]f) .... )
If the user mixes WithValidation with WithStackValidation, it will fail at runtime. This approach also looks more Go-like.
3
I don't see much use for generics here. I tried playing around with them but couldn't find a simple way to implement an interface.
Violation handling.
I was also thinking about whether functions should include a ReportViolation function (more of a "JS style" approach) or be more Go-like and return errors.
In hackweek/go-policy the ReportViolation function clearly states that it needs two arguments (though in Python URN is optional), whereas returning an error would require a custom type, and the user would need to remember to fill in both fields. However, when it comes to URNs, we already know the URN (at least in most cases; I’m unsure about StackValidation calls).
Conclusion
While writing down these suggestions, I think I came up with an idea:
type Policy struct {
apitype.Policy
validateStack []StackValidator
validate []ResourceValidator
remediate ResourceRemediator
}
We would have functions like:
NewValidationPolicy(p, []f, WithRemediation(f))
At the same time, I want the PolicyPack to be customizable and allow the injection of custom implementations of pulumirpc.Analyzer and plugin.Analyzer. However, with private fields, users won't be able to inject plugin.Analyzer or reuse PolicyPack.
We could define an interface:
type PolicyI struct{
Validate()
Remediate()
ValidateStack()
}
The official implementation could know which method to call since it would have access to the private fields. However, this might actually be a bad idea because it would require the user to implement both Policy and PolicyPack if they want customize it.
@justinvp I am currently creating a plugin to run Go policies. I would like to know if I am heading in the right direction.
- I am planning to add another executable to ~/.pulumi/bin.
- I am using
sdk/go/pulumi-language-goas the base for the new plugin. - The plugin will compile the policy code in a temporary directory.
- Then it will (1) start a new policy executable, (2) perform the same logic with port writing/reading, and (3) proxy all gRPC calls to the policy executable.
I have already created a prototype and tested it.
Out of curiosity, I considered whether Go plugins might be a good option, but I decided that the compile/gRPC approach is more tested and standardized within the Pulumi ecosystem.
If this is the correct approach, should I move the compile logic from sdk/go/pulumi-language-go into a reusable library, or would it be better not to modify this critical code for now?
I've been looking into different parts of the code and had the idea of compiling pulumi-analyzer-policy-go using pulumi-language-go. Unfortunately, there's currently no way to compile a Go program without running it immediately. Otherwise, it would be a good approach to compile Go policies on the fly.
I will proceed with the solution I described earlier.