OPA PrepareForEval time increases exponentially - open-policy-agent

I need my application to be able to load policies (rego) and then evaluate an input JSON based on defined policies.
I mistakenly used PrepareForEval in my evaluation API instead of load policy API. The result of this surprised me as the response time kept on increasing exponentially after each evaluation, while the policies were kept unchanged. Although, after this, I've realized and changed my logic to call PrepareForEval method at the time of loading policy and then store the prepared query instance in my struct instance. But I'm still concerned if prepare method gets executed multiple times while loading policies then it'd still become an expensive operation.
So, it'd be great to be pointed towards the correct way of using the prepare method.
Sample code:
// My provider
func init() {
cachedRego := rego.New(rego.Query("data.report"))
}
// My load policy method
func loadPolicy(ctx context.Context, filename, regoPolicy string) {
mod, err := ast.ParseModule(filename, regoPolicy)
rego.ParsedModule(mod)(cachedRego)
}
// My evaluate method
func eval(ctx context.Context, input interface{}) {
// after loading my policies, the following call took 1s, 2s, 5s, 10s,... respectively on eval calls
preparedQuery, _ := cachedRego.PrepareForEval(ctx) // <- I've moved this to my load policy method and cached preparedQuery
// this doesn’t take much time
rs, _ := preparedQuery.Eval(ctx, rego.EvalInput(input))
}
// My use case
func main() {
// load policies and evaluate inputs
for _, p := range policySet1 {
loadPolicy(context.Background(), p.filename, p.regoPolicy)
}
for _, inp := range inputSet1 {
eval(context.Background(), inp)
}
// load more policies to the earlier set and evaluate another input set
for _, p := range policySet2 {
loadPolicy(context.Background(), p.filename, p.regoPolicy)
}
for _, inp := range inputSet2 {
eval(context.Background(), inp)
}
}

TLDR; this is probably not the right place for this question. File an issue on GitHub if you are seeing behaviour you think is wrong.
To answer your question about how to prepare queries properly, your main function should (minimally) look like this:
func main() {
pq1 := loadPolicies(policySet1)
for _, inp := range inputSet1 {
eval(pq1, inp)
}
pq2 := loadPolicies(policySet2)
for _, inp := range inputSet2 {
eval(pq2, inp)
}
}
The easiest way to implement loadPolicies for the above example would be something like:
func loadPolicies(policySet) {
opts := []func(*rego.Rego){}
// setup prepared query. load all modules.
for _, p := range policySet {
opts = append(opts, rego.Module(p.filename, p.regoPolicy))
}
// create new prepared query
return rego.New(rego.Query("data.report"), opts...).PrepareForEval()
}
Then your eval function becomes:
func eval(pq, inp) {
rs, err := pq.Eval(rego.EvalInput(inp))
// handle err
// interpret rs
}

Related

Docker (Moby) golang image build logs are base64 encoded

I'm looking for help with extracting the image build logs from a dockerd (buildkit/moby) image build request sent by a Golang based client using the docker client libraries.
I can request the image build fine and receive the log stream of json messages then decode them as Jsonmessage instances. But the actual log lines from the builder appear to be base64 encoded in an aux field of each json message.
I can decode the base64 easily enough, but they seem to include odd terminal control characters and possibly mis-encoded data, which makes me wonder if they're actually a base64 encoding of some kind of struct I'm supposed to unpack.
What confuses me is that I can't find anything in the docker-ce or moby code that seems to base64-decode an 'aux' payload when processing logs when displaying build progress for docker buildx build.
As far as I can tell, the buildx code doesn't do anything special to the aux payload: https://github.com/docker/docker-ce/blob/523cf7e71252013fbb6a590be67a54b4a88c1dae/components/cli/cli/command/image/build_buildkit.go#L325
For example, trimmed-down build code like:
image := Image{Name: "test"}
contextreader, err := archive.TarWithOptions(buildConf.Build.Context, &archive.TarOptions{})
if err != nil {
return err
}
imageBuildResponse, err := b.client.ImageBuild(
ctx,
contextreader,
types.ImageBuildOptions{
Version: types.BuilderBuildKit,
Context: contextreader,
Dockerfile: dockerfile,
})
if err != nil {
return err
}
defer imageBuildResponse.Body.Close()
buf := bytes.NewBuffer(nil)
imageID := ""
writeAux := func(msg jsonmessage.JSONMessage) {
if msg.ID == "moby.image.id" {
var result types.BuildResult
if err := json.Unmarshal(*msg.Aux, &result); err != nil {
panic("don't do this in your real code")
}
imageID = result.ID
return
}
return err
}
err := jsonmessage.DisplayJSONMessagesStream(imageBuildResponse.Body, buf, os.Stderr.Fd(), false /* not terminal */, writeAux)
if err != nil {
if jerr, ok := err.(*jsonmessage.JSONError); ok {
// If no error code is set, default to 1
if jerr.Code == 0 {
jerr.Code = 1
}
return fmt.Errorf("error while building image: %s", jerr.Message)
}
}
will write json payloads to stderr like
{"id":"moby.buildkit.trace","aux":"Cn0KR3NoYTI1NjozZThhMzMxYmRkZGFjNWZkYmNjOGVhMDFmYWFhYmM3MjA0MDkwMmYwNjdmYzRhOGY0NDJmMmIzYWVlN2RkNGIyGiRbaW50ZXJuYWxdIGxvYWQgcmVtb3RlIGJ1aWxkIGNvbnRleHQqDAiYw8KaBhCykpCqAg=="}
{"id":"moby.buildkit.trace","aux":"CokBCkdzaGEyNTY6M2U4YTMzMWJkZGRhYzVmZGJjYzhlYTAxZmFhYWJjNzIwNDA5MDJmMDY3ZmM0YThmNDQyZjJiM2FlZTdkZDRiMhokW2ludGVybmFsXSBsb2FkIHJlbW90ZSBidWlsZCBjb250ZXh0KgwImMPCmgYQspKQqgIyCgiZw8KaBhD08F0="}
The base64 strings here don't decode as valid utf-8, and they don't make sense as ISO-8859-1 either. E.g. with a utf-8 console encoding:
$ base64 -d <<<'Cn0KR3NoYTI1NjozZThhMzMxYmRkZGFjNWZkYmNjOGVhMDFmYWFhYmM3MjA0MDkwMmYwNjdmYzRhOGY0NDJmMmIzYWVlN2RkNGIyGiRbaW50ZXJuYWxdIGxvYWQgcmVtb3RlIGJ1aWxkIGNvbnRleHQqDAiYw8KaBhCykpCqAg=='
}
Gsha256:3e8a331bdddac5fdbcc8ea01faaabc72040902f067fc4a8f442f2b3aee7dd4b2�$[internal] load remote build context*
������
It looks like it's probably a struct, but for the life of me I can't find what decodes and processes it.
So of course I find the answer while writing up the SO question...
The writeAux function in build_buildkit.go calls the write method of a tracer instance, and that does the real work. I must've been blind.
The messages are serialized instances of StatusResponse from the github.com/moby/buildkit/api/services/control package. They are unmarshalled from base64-decoded byte sequences and inspected. If you want logs and to skip everything else, just look for instances with non-empty Logs member arrays, e.g. something like this within the above writeAux function:
} else if msg.ID == "moby.buildkit.trace" {
// Process the message like
// https://github.com/docker/docker-ce/blob/523cf7e71252013fbb6a590be67a54b4a88c1dae/components/cli/cli/command/image/build_buildkit.go#L386
// the 'tracer.write' method in build_buildkit.go
var resp controlapi.StatusResponse
var dt []byte
// ignoring all messages that are not understood
if err := json.Unmarshal(*msg.Aux, &dt); err != nil {
return
}
if err := (&resp).Unmarshal(dt); err != nil {
return
}
for _, v := range resp.Vertexes {
fmt.Printf("layer: %+v", v)
}
for _, v := range resp.Statuses {
fmt.Printf("status: %+v", v)
}
for _, v := range resp.Logs {
fmt.Printf("log: msg.Msg)
}
}
The json.Unmarshal and controlapi.StatusResponse.Unmarshal do the base64 decoding and unpacking for you.

Response quits after X

package main
import (
"fmt"
"log"
"net/http"
"os"
"github.com/steven-ferrer/gonsole"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi there!\n")
file, err := os.Open("ItemLog.txt")
if err != nil {
log.Fatal(err)
}
reader := gonsole.NewReader(file)
counter := 0
for {
foo, _ := reader.NextWord()
if foo == "<Kept>" {
counter++
fmt.Fprintf(w, "%d"+": ", counter)
foo, _ = reader.NextWord()
for foo != "|" {
fmt.Fprintf(w, foo+" ")
foo, _ = reader.NextWord()
}
if foo == "|" { // need to reader.NewLine instead but this will work for now.
fmt.Fprintf(w, "\n")
}
}
}
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
My local CLI works but when I try to wrap it up in a server only so many lines are printed. It's like it times out or something. Help?
I need more text so: I'm parsing a text file.
Edit: here's a test file... https://pastebin.com/ZNbut51X
You are not helping yourself ignoring errors:
foo, _ := reader.NextWord()
This is very bad practice. Check the error and it will tell you what is going on.
Update:
You have infinite loop in your code.
for {
...
}
for{} works until you call continue or return inside that loop.
https://tour.golang.org/flowcontrol/4
In your case, it cannot run forever because go-routine that runs it is terminated by timeout.
Update2:
Infinite loop is not compatible with HTTP. With web service, you are getting request and should return response before go-routine is terminated by timeout.
You have two options:
send requests by timer every x seconds and return recent data to
caller in handler.
implement technology that supports bidirectional communications
between client and server -
https://godoc.org/golang.org/x/net/websocket
Both options unfortunately more complicated than console app :(

How to wrap exec.Command inside an io.Writer

I'm trying to compress a JPEG image in go using mozjpeg. Since it doesn't have official go binding, I think I'll just invoke its CLI to do the compression.
I try to model the usage after compress/gzip:
c := jpeg.NewCompresser(destFile)
_, err := io.Copy(c, srcFile)
Now the question is, how do I wrap the CLI inside Compresser so it can support this usage?
I tried something like this:
type Compresser struct {
cmd exec.Command
}
func NewCompressor(w io.Writer) *Compresser {
cmd := exec.Command("jpegtran", "-copy", "none")
cmd.Stdout = w
c := &Compresser{cmd}
return c
}
func (c *Compresser) Write(p []byte) (n int, err error) {
if c.cmd.Process == nil {
err = c.cmd.Start()
if err != nil {
return
}
}
// How do I write p into c.cmd.Stdin?
}
But couldn't finish it.
Also, a second question is, when do I shut down the command? How to shut down the command?
You should take a look at the Cmd.StdinPipe. There is an example in the documentation, which suits your case:
package main
import (
"fmt"
"io"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("cat")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
go func() {
defer stdin.Close()
io.WriteString(stdin, "values written to stdin are passed to cmd's standard input")
}()
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", out)
}
In this case, CombinedOutput() executes your command, and the execution is finished, when there are no more bytes to read from out.
As per Kiril's answer, use the cmd.StdInPipe to pass on the data you receive to Write.
However, in terms of closing, I'd be tempted to implement io.Closer. This would make *Compresser automatically implement the io.WriteCloser interface.
I would use Close() as the notification that there is no more data to be sent and that the command should be terminated. Any non-zero exit code returned from the command that indicates failure could be caught and returned as an error.
I would be wary of using CombinedOutput() inside Write() in case you have a slow input stream. The utility could finish processing the input stream and be waiting for more data. This would be incorrectly detected as command completion and would result in an invalid output.
Remember, the Write method can be called an indeterminate number of times during IO operations.

How to get all transaction history against a chaincode in Hyperledger fabric

I am able to do transactions in Hyperledger (fabric implementation). I want to see all the transactions and its payload details initiated by a user by passing the user's key.
for example:
A transfers 10 units to B
A transfers 5 units to C
D transfers 8 units to A
When I pass A's key then fabric must provide me all the transactions of A.
Is there any way? Or which fabric API function call should I use?
/chain/blocks/{Block} endpoint carries ordered list of transactions in a specified block.
Use /chain endpoint to get the height (number of blocks) of your chain, and then retrieve transactions from each block using /chain/blocks/{Block} REST endpoint.
You can develop the proper indexing and query function in your chaincode.
Meaning for each transaction you store its details in the internal key/value store (stub.PutState) with the user as key and return all the transactions associated to a user in your query (stub.GetState).
The best and simplest way is to use the shim package function
GetHistoryForKey(key string)
As the documentation says:
GetHistoryForKey function can be invoked by a chaincode to return a history of key values across time.
GetHistoryForKey is intended to be used for read-only queries.
IF anyone need Java SDk and go chaincode combination. There you go
answered here similar question
Java code
public List<HistoryDao> getUFOHistory(String key) throws Exception {
String[] args = { key };
Logger.getLogger(QueryChaincode.class.getName()).log(Level.INFO, "UFO communication history - " + args[0]);
Collection<ProposalResponse> responses1Query = ucc.getChannelClient().queryByChainCode("skynetchaincode", "getHistoryForUFO", args);
String stringResponse = null;
ArrayList<HistoryDao> newArrayList = new ArrayList<>();
for (ProposalResponse pres : responses1Query) {
stringResponse = new String(pres.getChaincodeActionResponsePayload());
Logger.getLogger(QueryChaincode.class.getName()).log(Level.INFO, stringResponse);
newArrayList = gson.fromJson(stringResponse, new TypeToken<ArrayList<HistoryDao>>() {
}.getType());
}
if (null == stringResponse)
stringResponse = "Not able to find any ufo communication history";
return newArrayList;
}
and you go chancode implemetation is as follows
Go code
func (t *SmartContract) getHistoryForUFO(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
if len(args) < 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
ufoId := args[0]
resultsIterator, err := APIstub.GetHistoryForKey(ufoId)
if err != nil {
return shim.Error(err.Error())
}
defer resultsIterator.Close()
var buffer bytes.Buffer
buffer.WriteString("[")
bArrayMemberAlreadyWritten := false
for resultsIterator.HasNext() {
response, err := resultsIterator.Next()
if err != nil {
return shim.Error(err.Error())
}
// Add a comma before array members, suppress it for the first array member
if bArrayMemberAlreadyWritten == true {
buffer.WriteString(",")
}
buffer.WriteString("{\"TxId\":")
buffer.WriteString("\"")
buffer.WriteString(response.TxId)
buffer.WriteString("\"")
buffer.WriteString(", \"Value\":")
// if it was a delete operation on given key, then we need to set the
//corresponding value null. Else, we will write the response.Value
//as-is (as the Value itself a JSON)
if response.IsDelete {
buffer.WriteString("null")
} else {
buffer.WriteString(string(response.Value))
}
buffer.WriteString(", \"Timestamp\":")
buffer.WriteString("\"")
buffer.WriteString(time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)).String())
buffer.WriteString("\"")
buffer.WriteString(", \"IsDelete\":")
buffer.WriteString("\"")
buffer.WriteString(strconv.FormatBool(response.IsDelete))
buffer.WriteString("\"")
buffer.WriteString("}")
bArrayMemberAlreadyWritten = true
}
buffer.WriteString("]")
fmt.Printf("- History returning:\n%s\n", buffer.String())
return shim.Success(buffer.Bytes())
}
Let me know if you question.
If you are using composer-client, you can simply use the Historian command.
var historian = await businessNetworkConnection.getHistorian();
historian.getAll().then(historianRecords => console.log(historianRecords));

Parsing a golang time object from an incomplete string

I have the following date string: 2017-09-04T04:00:00Z
I need to parse this string into a golang time in order to have uniform data across my application. Here is the code so far:
parsedTime := "2017-09-04T04:00:00Z"
test, err := time.Parse(time.RFC3339, parsedTime)
check(err)
fmt.Println(test)
I get the following error when I try to run the program:
": extra text: 0:00 +0000 UTC parsing time "2017-09-04T04:00:00Z
How can I either add the extra text that it is looking for or get the parser to stop looking after the Z?
I have also tried the following:
parsedTime := "2017-09-04T04:00:00Z"
test, err := time.Parse("2006-01-02T03:04:05Z", parsedTime)
check(err)
fmt.Println(test)
Which returns the following error:
": extra text: 017-09-04T04:00:00Z
Both formats you used work with the current version of go: https://play.golang.org/p/Typyq3Okrd
var formats = []string{
time.RFC3339,
"2006-01-02T03:04:05Z",
}
func main() {
parsedTime := "2017-09-04T04:00:00Z"
for _, format := range formats {
if test, err := time.Parse(format, parsedTime); err != nil {
fmt.Printf("ERROR: format %q resulted in error: %v\n", format, err)
} else {
fmt.Printf("format %q yielded %s\n", format, test)
}
}
}
Can you provide a working example that demonstrates your problem? You can use the go playground for shareable snippets.

Resources