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

Support for writing policies in Go #230

Open
justinvp opened this issue Apr 9, 2020 · 17 comments
Open

Support for writing policies in Go #230

justinvp opened this issue Apr 9, 2020 · 17 comments
Labels
kind/enhancement Improvements or new features language/go

Comments

@justinvp
Copy link
Member

justinvp commented Apr 9, 2020

No description provided.

@lukehoban lukehoban added language/go kind/enhancement Improvements or new features labels Jul 12, 2021
@mountainerd
Copy link

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?

@ghost
Copy link

ghost commented Feb 6, 2022

Any due date? Thanks!!

@hazcod
Copy link

hazcod commented Feb 13, 2022

Peeking in., would love this too.

@agpenton
Copy link

agpenton commented Jul 6, 2022

👍

@smebyp1
Copy link

smebyp1 commented Jul 19, 2022

+1 from me.

@Graham-Beer
Copy link

Any update on when this might be available in Go?

@jasbir84
Copy link

+1 from me

@root-ali
Copy link

Any update? thanks.

@im7mortal
Copy link

/assign

@justinvp
Copy link
Member Author

justinvp commented Aug 9, 2024

There is an old hackathon project to add a Go SDK: https://github.com/pulumi/pulumi-policy/compare/hackweek/go-policy

@im7mortal
Copy link

Thank you @justinvp!

  1. 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.

  2. 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.

@justinvp
Copy link
Member Author

  1. 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.

  1. 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.

@im7mortal
Copy link

  1. I will first try to ensure that Python matches with TypeScript.
  2. 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.

@im7mortal
Copy link

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.

  1. Separated logic for every language runtime.
  2. Added t.Parallel() (ensuring to skip it on one test case which requires environments).
  3. Enabled skipping of specific language runtimes (e.g., running tests only for Go).
  4. Introduced parallelization on the dependency level (due to the global lock in testing environments for most programs written in TypeScript and using Yarn).

@im7mortal
Copy link

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.

@im7mortal
Copy link

@justinvp I am currently creating a plugin to run Go policies. I would like to know if I am heading in the right direction.

  1. I am planning to add another executable to ~/.pulumi/bin.
  2. I am using sdk/go/pulumi-language-go as the base for the new plugin.
  3. The plugin will compile the policy code in a temporary directory.
  4. 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?

@im7mortal
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/enhancement Improvements or new features language/go
Projects
Status: 💡 Opportunity
Development

No branches or pull requests

10 participants