diff --git a/cmd/policy/main.go b/cmd/policy/main.go index 4f5f8cd0045c0983653fe6366fae4fbfe957b3a8..89ac306184072b2b50784b073c038a37fc9bfac4 100644 --- a/cmd/policy/main.go +++ b/cmd/policy/main.go @@ -4,6 +4,7 @@ import ( "context" "errors" "log" + "net" "net/http" "time" @@ -25,6 +26,7 @@ import ( goapolicy "code.vereign.com/gaiax/tsa/policy/gen/policy" "code.vereign.com/gaiax/tsa/policy/internal/config" "code.vereign.com/gaiax/tsa/policy/internal/regocache" + "code.vereign.com/gaiax/tsa/policy/internal/regofunc" "code.vereign.com/gaiax/tsa/policy/internal/service" "code.vereign.com/gaiax/tsa/policy/internal/service/health" "code.vereign.com/gaiax/tsa/policy/internal/service/policy" @@ -68,6 +70,13 @@ func main() { // create rego query cache regocache := regocache.New() + // custom rego functions + regofuncs := regofunc.New( + cfg.Cache.Addr, + regofunc.WithHTTPClient(httpClient()), + regofunc.WithLogger(logger), + ) + // subscribe the cache for policy data changes storage.AddPolicyChangeSubscriber(regocache) @@ -77,7 +86,7 @@ func main() { healthSvc goahealth.Service ) { - policySvc = policy.New(storage, regocache, logger) + policySvc = policy.New(storage, regocache, regofuncs, logger) healthSvc = health.New() } @@ -177,3 +186,19 @@ func createLogger(logLevel string, opts ...zap.Option) (*zap.Logger, error) { func errFormatter(e error) goahttp.Statuser { return service.NewErrorResponse(e) } + +func httpClient() *http.Client { + return &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + }).DialContext, + MaxIdleConns: 100, + MaxIdleConnsPerHost: 100, + TLSHandshakeTimeout: 10 * time.Second, + IdleConnTimeout: 60 * time.Second, + }, + Timeout: 30 * time.Second, + } +} diff --git a/design/design.go b/design/design.go index 323e5b72211f49b8d224ec9f22c72ae876da974f..109dc62bdde2af20ef3b5ee3628027ed979823e5 100644 --- a/design/design.go +++ b/design/design.go @@ -21,7 +21,7 @@ var _ = Service("policy", func() { Method("Evaluate", func() { Description("Evaluate executes a policy with the given 'data' as input.") Payload(EvaluateRequest) - Result(EvaluateResult) + Result(Any) HTTP(func() { POST("/policy/{group}/{policyName}/{version}/evaluation") Body("input") diff --git a/gen/http/cli/policy/cli.go b/gen/http/cli/policy/cli.go index 895aaa1760a5f7231224704a7050339bf635f4bd..ca3c7841a6acdfe4e3a1c21494ad084dac3d3e6f 100644 --- a/gen/http/cli/policy/cli.go +++ b/gen/http/cli/policy/cli.go @@ -260,7 +260,7 @@ Lock a policy so that it cannot be evaluated. -version STRING: Policy version. Example: - %[1]s policy lock --group "In illum est et hic." --policy-name "Deleniti non nihil dolor aut sed." --version "Incidunt unde consequatur voluptas dolorem nisi temporibus." + %[1]s policy lock --group "Deleniti non nihil dolor aut sed." --policy-name "Incidunt unde consequatur voluptas dolorem nisi temporibus." --version "Omnis quasi aut consequuntur." `, os.Args[0]) } @@ -273,6 +273,6 @@ Unlock a policy so it can be evaluated again. -version STRING: Policy version. Example: - %[1]s policy unlock --group "Aliquam atque voluptatum ut dolorem." --policy-name "Aut facere veniam repudiandae id." --version "Aut minus alias." + %[1]s policy unlock --group "Aut facere veniam repudiandae id." --policy-name "Aut minus alias." --version "At eos facilis molestias in voluptas rem." `, os.Args[0]) } diff --git a/gen/http/openapi.json b/gen/http/openapi.json index b5984d3e4ea0da00dbc8096509d6e4125d42c9e9..e45394a13bd6d5c554dec72c55245d7e1f09fcae 100644 --- a/gen/http/openapi.json +++ b/gen/http/openapi.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"title":"Policy Service","description":"The policy service exposes HTTP API for executing policies.","version":""},"host":"localhost:8081","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/policy/{group}/{policyName}/{version}/evaluation":{"post":{"tags":["policy"],"summary":"Evaluate policy","description":"Evaluate executes a policy with the given 'data' as input.","operationId":"policy#Evaluate","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"type":"string"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"type":"string"},{"name":"version","in":"path","description":"Policy version.","required":true,"type":"string"},{"name":"any","in":"body","description":"Input data passed to the policy execution runtime.","required":true,"schema":{"type":"string","format":"binary"}}],"responses":{"200":{"description":"OK response.","schema":{"$ref":"#/definitions/PolicyEvaluateResponseBody","required":["result"]}}},"schemes":["http"]}},"/policy/{group}/{policyName}/{version}/lock":{"post":{"tags":["policy"],"summary":"Lock policy","description":"Lock a policy so that it cannot be evaluated.","operationId":"policy#Lock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"type":"string"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"type":"string"},{"name":"version","in":"path","description":"Policy version.","required":true,"type":"string"}],"responses":{"200":{"description":"OK response."}},"schemes":["http"]},"delete":{"tags":["policy"],"summary":"Unlock policy","description":"Unlock a policy so it can be evaluated again.","operationId":"policy#Unlock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"type":"string"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"type":"string"},{"name":"version","in":"path","description":"Policy version.","required":true,"type":"string"}],"responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}}},"definitions":{"PolicyEvaluateResponseBody":{"title":"PolicyEvaluateResponseBody","type":"object","properties":{"result":{"type":"string","description":"Arbitrary JSON response.","example":"At eos facilis molestias in voluptas rem.","format":"binary"}},"example":{"result":"Ab accusantium ut ut aliquid sint animi."},"required":["result"]}}} \ No newline at end of file +{"swagger":"2.0","info":{"title":"Policy Service","description":"The policy service exposes HTTP API for executing policies.","version":""},"host":"localhost:8081","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/policy/{group}/{policyName}/{version}/evaluation":{"post":{"tags":["policy"],"summary":"Evaluate policy","description":"Evaluate executes a policy with the given 'data' as input.","operationId":"policy#Evaluate","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"type":"string"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"type":"string"},{"name":"version","in":"path","description":"Policy version.","required":true,"type":"string"},{"name":"any","in":"body","description":"Input data passed to the policy execution runtime.","required":true,"schema":{"type":"string","format":"binary"}}],"responses":{"200":{"description":"OK response.","schema":{"type":"string","format":"binary"}}},"schemes":["http"]}},"/policy/{group}/{policyName}/{version}/lock":{"post":{"tags":["policy"],"summary":"Lock policy","description":"Lock a policy so that it cannot be evaluated.","operationId":"policy#Lock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"type":"string"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"type":"string"},{"name":"version","in":"path","description":"Policy version.","required":true,"type":"string"}],"responses":{"200":{"description":"OK response."}},"schemes":["http"]},"delete":{"tags":["policy"],"summary":"Unlock policy","description":"Unlock a policy so it can be evaluated again.","operationId":"policy#Unlock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"type":"string"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"type":"string"},{"name":"version","in":"path","description":"Policy version.","required":true,"type":"string"}],"responses":{"200":{"description":"OK response."}},"schemes":["http"]}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}},"schemes":["http"]}}}} \ No newline at end of file diff --git a/gen/http/openapi.yaml b/gen/http/openapi.yaml index 60990b84a7a9ebe2751a44abe450aa6e7b9f0bde..e8bcb485a33fadf93c1fd3cc62f9d1273a2a89d5 100644 --- a/gen/http/openapi.yaml +++ b/gen/http/openapi.yaml @@ -58,9 +58,8 @@ paths: "200": description: OK response. schema: - $ref: '#/definitions/PolicyEvaluateResponseBody' - required: - - result + type: string + format: binary schemes: - http /policy/{group}/{policyName}/{version}/lock: @@ -129,17 +128,3 @@ paths: description: OK response. schemes: - http -definitions: - PolicyEvaluateResponseBody: - title: PolicyEvaluateResponseBody - type: object - properties: - result: - type: string - description: Arbitrary JSON response. - example: At eos facilis molestias in voluptas rem. - format: binary - example: - result: Ab accusantium ut ut aliquid sint animi. - required: - - result diff --git a/gen/http/openapi3.json b/gen/http/openapi3.json index e2c4e9780257067b00d350858e29fa720157039b..157c1dd6d45193244c4688d15e7c1e0f5dfcda32 100644 --- a/gen/http/openapi3.json +++ b/gen/http/openapi3.json @@ -1 +1 @@ -{"openapi":"3.0.3","info":{"title":"Policy Service","description":"The policy service exposes HTTP API for executing policies.","version":"1.0"},"servers":[{"url":"http://localhost:8081","description":"Policy Server"}],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}}}},"/policy/{group}/{policyName}/{version}/evaluation":{"post":{"tags":["policy"],"summary":"Evaluate policy","description":"Evaluate executes a policy with the given 'data' as input.","operationId":"policy#Evaluate","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Ut commodi perspiciatis corporis."},"example":"Accusamus autem sequi."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"Et nulla."},"example":"In quis nesciunt autem et."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"Sunt in et quia cum."},"example":"Commodi nemo fugiat id praesentium accusantium expedita."}],"requestBody":{"description":"Input data passed to the policy execution runtime.","required":true,"content":{"application/json":{"schema":{"type":"string","description":"Input data passed to the policy execution runtime.","example":"Dolorem cumque laborum quis nesciunt.","format":"binary"},"example":"Non mollitia nesciunt impedit facere."}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluateResult"},"example":{"result":"Explicabo beatae quisquam officiis libero voluptatibus."}}}}}}},"/policy/{group}/{policyName}/{version}/lock":{"delete":{"tags":["policy"],"summary":"Unlock policy","description":"Unlock a policy so it can be evaluated again.","operationId":"policy#Unlock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Recusandae est rerum corrupti quia."},"example":"Quam dolores architecto itaque."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"Voluptas ad corporis adipisci inventore ipsum."},"example":"Recusandae dolorum nisi distinctio vitae ad."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"Perspiciatis voluptatem."},"example":"Corporis est rem."}],"responses":{"200":{"description":"OK response."}}},"post":{"tags":["policy"],"summary":"Lock policy","description":"Lock a policy so that it cannot be evaluated.","operationId":"policy#Lock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Qui non quia."},"example":"Error maxime quasi quia non voluptatibus error."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"Optio quia et laborum."},"example":"In libero perspiciatis voluptatum ut soluta."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"Ut amet."},"example":"Accusamus enim."}],"responses":{"200":{"description":"OK response."}}}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}}}}},"components":{"schemas":{"EvaluateResult":{"type":"object","properties":{"result":{"type":"string","description":"Arbitrary JSON response.","example":"Aut voluptas.","format":"binary"}},"example":{"result":"Sint nam voluptatem ea consequatur similique et."},"required":["result"]}}},"tags":[{"name":"health","description":"Health service provides health check endpoints."},{"name":"policy","description":"Policy Service provides evaluation of policies through Open Policy Agent."}]} \ No newline at end of file +{"openapi":"3.0.3","info":{"title":"Policy Service","description":"The policy service exposes HTTP API for executing policies.","version":"1.0"},"servers":[{"url":"http://localhost:8081","description":"Policy Server"}],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}}}},"/policy/{group}/{policyName}/{version}/evaluation":{"post":{"tags":["policy"],"summary":"Evaluate policy","description":"Evaluate executes a policy with the given 'data' as input.","operationId":"policy#Evaluate","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Sint nam voluptatem ea consequatur similique et."},"example":"Non mollitia nesciunt impedit facere."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"Ut commodi perspiciatis corporis."},"example":"Accusamus autem sequi."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"Et nulla."},"example":"In quis nesciunt autem et."}],"requestBody":{"description":"Input data passed to the policy execution runtime.","required":true,"content":{"application/json":{"schema":{"type":"string","description":"Input data passed to the policy execution runtime.","example":"Ab accusantium ut ut aliquid sint animi.","format":"binary"},"example":"Aut voluptas."}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Dolorem cumque laborum quis nesciunt.","format":"binary"},"example":"Sunt in et quia cum."}}}}}},"/policy/{group}/{policyName}/{version}/lock":{"delete":{"tags":["policy"],"summary":"Unlock policy","description":"Unlock a policy so it can be evaluated again.","operationId":"policy#Unlock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Accusamus enim."},"example":"Recusandae est rerum corrupti quia."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"Quam dolores architecto itaque."},"example":"Voluptas ad corporis adipisci inventore ipsum."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"Recusandae dolorum nisi distinctio vitae ad."},"example":"Perspiciatis voluptatem."}],"responses":{"200":{"description":"OK response."}}},"post":{"tags":["policy"],"summary":"Lock policy","description":"Lock a policy so that it cannot be evaluated.","operationId":"policy#Lock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Commodi nemo fugiat id praesentium accusantium expedita."},"example":"Qui non quia."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"Error maxime quasi quia non voluptatibus error."},"example":"Optio quia et laborum."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"In libero perspiciatis voluptatum ut soluta."},"example":"Ut amet."}],"responses":{"200":{"description":"OK response."}}}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}}}}},"components":{},"tags":[{"name":"health","description":"Health service provides health check endpoints."},{"name":"policy","description":"Policy Service provides evaluation of policies through Open Policy Agent."}]} \ No newline at end of file diff --git a/gen/http/openapi3.yaml b/gen/http/openapi3.yaml index 66d1ca8c53bb54867873fea7086b7e895af411f3..f594026729efd68d14d29221bec6581f88a586e7 100644 --- a/gen/http/openapi3.yaml +++ b/gen/http/openapi3.yaml @@ -31,8 +31,8 @@ paths: schema: type: string description: Policy group. - example: Ut commodi perspiciatis corporis. - example: Accusamus autem sequi. + example: Sint nam voluptatem ea consequatur similique et. + example: Non mollitia nesciunt impedit facere. - name: policyName in: path description: Policy name. @@ -40,8 +40,8 @@ paths: schema: type: string description: Policy name. - example: Et nulla. - example: In quis nesciunt autem et. + example: Ut commodi perspiciatis corporis. + example: Accusamus autem sequi. - name: version in: path description: Policy version. @@ -49,8 +49,8 @@ paths: schema: type: string description: Policy version. - example: Sunt in et quia cum. - example: Commodi nemo fugiat id praesentium accusantium expedita. + example: Et nulla. + example: In quis nesciunt autem et. requestBody: description: Input data passed to the policy execution runtime. required: true @@ -59,18 +59,19 @@ paths: schema: type: string description: Input data passed to the policy execution runtime. - example: Dolorem cumque laborum quis nesciunt. + example: Ab accusantium ut ut aliquid sint animi. format: binary - example: Non mollitia nesciunt impedit facere. + example: Aut voluptas. responses: "200": description: OK response. content: application/json: schema: - $ref: '#/components/schemas/EvaluateResult' - example: - result: Explicabo beatae quisquam officiis libero voluptatibus. + type: string + example: Dolorem cumque laborum quis nesciunt. + format: binary + example: Sunt in et quia cum. /policy/{group}/{policyName}/{version}/lock: delete: tags: @@ -86,8 +87,8 @@ paths: schema: type: string description: Policy group. - example: Recusandae est rerum corrupti quia. - example: Quam dolores architecto itaque. + example: Accusamus enim. + example: Recusandae est rerum corrupti quia. - name: policyName in: path description: Policy name. @@ -95,8 +96,8 @@ paths: schema: type: string description: Policy name. - example: Voluptas ad corporis adipisci inventore ipsum. - example: Recusandae dolorum nisi distinctio vitae ad. + example: Quam dolores architecto itaque. + example: Voluptas ad corporis adipisci inventore ipsum. - name: version in: path description: Policy version. @@ -104,8 +105,8 @@ paths: schema: type: string description: Policy version. - example: Perspiciatis voluptatem. - example: Corporis est rem. + example: Recusandae dolorum nisi distinctio vitae ad. + example: Perspiciatis voluptatem. responses: "200": description: OK response. @@ -123,8 +124,8 @@ paths: schema: type: string description: Policy group. - example: Qui non quia. - example: Error maxime quasi quia non voluptatibus error. + example: Commodi nemo fugiat id praesentium accusantium expedita. + example: Qui non quia. - name: policyName in: path description: Policy name. @@ -132,8 +133,8 @@ paths: schema: type: string description: Policy name. - example: Optio quia et laborum. - example: In libero perspiciatis voluptatum ut soluta. + example: Error maxime quasi quia non voluptatibus error. + example: Optio quia et laborum. - name: version in: path description: Policy version. @@ -141,8 +142,8 @@ paths: schema: type: string description: Policy version. - example: Ut amet. - example: Accusamus enim. + example: In libero perspiciatis voluptatum ut soluta. + example: Ut amet. responses: "200": description: OK response. @@ -155,20 +156,7 @@ paths: responses: "200": description: OK response. -components: - schemas: - EvaluateResult: - type: object - properties: - result: - type: string - description: Arbitrary JSON response. - example: Aut voluptas. - format: binary - example: - result: Sint nam voluptatem ea consequatur similique et. - required: - - result +components: {} tags: - name: health description: Health service provides health check endpoints. diff --git a/gen/http/policy/client/encode_decode.go b/gen/http/policy/client/encode_decode.go index cc62c4177f5b9f56c14adaca7dff08c6771e72a3..d4a2a5f74eef5f02bf3ef2314eb8caadb28fc280 100644 --- a/gen/http/policy/client/encode_decode.go +++ b/gen/http/policy/client/encode_decode.go @@ -83,19 +83,14 @@ func DecodeEvaluateResponse(decoder func(*http.Response) goahttp.Decoder, restor switch resp.StatusCode { case http.StatusOK: var ( - body EvaluateResponseBody + body interface{} err error ) err = decoder(resp).Decode(&body) if err != nil { return nil, goahttp.ErrDecodingError("policy", "Evaluate", err) } - err = ValidateEvaluateResponseBody(&body) - if err != nil { - return nil, goahttp.ErrValidationError("policy", "Evaluate", err) - } - res := NewEvaluateResultOK(&body) - return res, nil + return body, nil default: body, _ := ioutil.ReadAll(resp.Body) return nil, goahttp.ErrInvalidResponse("policy", "Evaluate", resp.StatusCode, string(body)) diff --git a/gen/http/policy/client/types.go b/gen/http/policy/client/types.go index 4bbf4babfe1d591928aa72fd43035cf89b3aeecd..770fa2c41568d70efaee163ca48d02f4cdfe910b 100644 --- a/gen/http/policy/client/types.go +++ b/gen/http/policy/client/types.go @@ -6,34 +6,3 @@ // $ goa gen code.vereign.com/gaiax/tsa/policy/design package client - -import ( - policy "code.vereign.com/gaiax/tsa/policy/gen/policy" - goa "goa.design/goa/v3/pkg" -) - -// EvaluateResponseBody is the type of the "policy" service "Evaluate" endpoint -// HTTP response body. -type EvaluateResponseBody struct { - // Arbitrary JSON response. - Result interface{} `form:"result,omitempty" json:"result,omitempty" xml:"result,omitempty"` -} - -// NewEvaluateResultOK builds a "policy" service "Evaluate" endpoint result -// from a HTTP "OK" response. -func NewEvaluateResultOK(body *EvaluateResponseBody) *policy.EvaluateResult { - v := &policy.EvaluateResult{ - Result: body.Result, - } - - return v -} - -// ValidateEvaluateResponseBody runs the validations defined on -// EvaluateResponseBody -func ValidateEvaluateResponseBody(body *EvaluateResponseBody) (err error) { - if body.Result == nil { - err = goa.MergeErrors(err, goa.MissingFieldError("result", "body")) - } - return -} diff --git a/gen/http/policy/server/encode_decode.go b/gen/http/policy/server/encode_decode.go index 702747a37d0596b6f22cb31d6bbc69c1543e5374..6d676cb40ded28d66368dfa9bdb89fbd11d7984c 100644 --- a/gen/http/policy/server/encode_decode.go +++ b/gen/http/policy/server/encode_decode.go @@ -12,7 +12,6 @@ import ( "io" "net/http" - policy "code.vereign.com/gaiax/tsa/policy/gen/policy" goahttp "goa.design/goa/v3/http" goa "goa.design/goa/v3/pkg" ) @@ -21,9 +20,9 @@ import ( // policy Evaluate endpoint. func EncodeEvaluateResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, interface{}) error { return func(ctx context.Context, w http.ResponseWriter, v interface{}) error { - res, _ := v.(*policy.EvaluateResult) + res, _ := v.(interface{}) enc := encoder(ctx, w) - body := NewEvaluateResponseBody(res) + body := res w.WriteHeader(http.StatusOK) return enc.Encode(body) } diff --git a/gen/http/policy/server/types.go b/gen/http/policy/server/types.go index 8f123712ae895818271de3f69460ece5d5d20577..e39296d9694ab5be167b84d4e5311b5c8b1036bc 100644 --- a/gen/http/policy/server/types.go +++ b/gen/http/policy/server/types.go @@ -11,22 +11,6 @@ import ( policy "code.vereign.com/gaiax/tsa/policy/gen/policy" ) -// EvaluateResponseBody is the type of the "policy" service "Evaluate" endpoint -// HTTP response body. -type EvaluateResponseBody struct { - // Arbitrary JSON response. - Result interface{} `form:"result" json:"result" xml:"result"` -} - -// NewEvaluateResponseBody builds the HTTP response body from the result of the -// "Evaluate" endpoint of the "policy" service. -func NewEvaluateResponseBody(res *policy.EvaluateResult) *EvaluateResponseBody { - body := &EvaluateResponseBody{ - Result: res.Result, - } - return body -} - // NewEvaluateRequest builds a policy service Evaluate endpoint payload. func NewEvaluateRequest(body interface{}, group string, policyName string, version string) *policy.EvaluateRequest { v := body diff --git a/gen/policy/client.go b/gen/policy/client.go index 554aa783b90015e0836f32342226c7507fc7d3d3..4543ff51214d584abfe8a3ecaee9d5284cc7b876 100644 --- a/gen/policy/client.go +++ b/gen/policy/client.go @@ -30,13 +30,13 @@ func NewClient(evaluate, lock, unlock goa.Endpoint) *Client { } // Evaluate calls the "Evaluate" endpoint of the "policy" service. -func (c *Client) Evaluate(ctx context.Context, p *EvaluateRequest) (res *EvaluateResult, err error) { +func (c *Client) Evaluate(ctx context.Context, p *EvaluateRequest) (res interface{}, err error) { var ires interface{} ires, err = c.EvaluateEndpoint(ctx, p) if err != nil { return } - return ires.(*EvaluateResult), nil + return ires.(interface{}), nil } // Lock calls the "Lock" endpoint of the "policy" service. diff --git a/gen/policy/service.go b/gen/policy/service.go index 25f54089f5b7c33d79059c1cb7196c6a8b8f21b3..3895194865fa885d9569cde5e97c02e2b7ae33ec 100644 --- a/gen/policy/service.go +++ b/gen/policy/service.go @@ -14,7 +14,7 @@ import ( // Policy Service provides evaluation of policies through Open Policy Agent. type Service interface { // Evaluate executes a policy with the given 'data' as input. - Evaluate(context.Context, *EvaluateRequest) (res *EvaluateResult, err error) + Evaluate(context.Context, *EvaluateRequest) (res interface{}, err error) // Lock a policy so that it cannot be evaluated. Lock(context.Context, *LockRequest) (err error) // Unlock a policy so it can be evaluated again. @@ -43,12 +43,6 @@ type EvaluateRequest struct { Input interface{} } -// EvaluateResult is the result type of the policy service Evaluate method. -type EvaluateResult struct { - // Arbitrary JSON response. - Result interface{} -} - // LockRequest is the payload type of the policy service Lock method. type LockRequest struct { // Policy group. diff --git a/internal/config/config.go b/internal/config/config.go index 008f2f483bd3d2d72bff4ebf3b633ec71e5c9db5..f9119f67420ba434f36949e3ddbb1380dae39595 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,8 +4,8 @@ import "time" type Config struct { HTTP httpConfig - Redis redisConfig Mongo mongoConfig + Cache cacheConfig LogLevel string `envconfig:"LOG_LEVEL" default:"INFO"` } @@ -18,12 +18,8 @@ type httpConfig struct { WriteTimeout time.Duration `envconfig:"HTTP_WRITE_TIMEOUT" default:"10s"` } -type redisConfig struct { - Addr string `envconfig:"REDIS_ADDR" required:"true"` - User string `envconfig:"REDIS_USER" required:"true"` - Pass string `envconfig:"REDIS_PASS" required:"true"` - DB int `envconfig:"REDIS_DB" default:"1"` - TTL time.Duration `envconfig:"REDIS_EXPIRATION"` // no default expiration, keys are set to live forever +type cacheConfig struct { + Addr string `envconfig:"CACHE_ADDR" required:"true"` } type mongoConfig struct { diff --git a/internal/regofunc/option.go b/internal/regofunc/option.go new file mode 100644 index 0000000000000000000000000000000000000000..0e1e41cd66cbc89cb5481abc37217a65b76cd3f5 --- /dev/null +++ b/internal/regofunc/option.go @@ -0,0 +1,21 @@ +package regofunc + +import ( + "net/http" + + "go.uber.org/zap" +) + +type Option func(*RegoFunc) + +func WithHTTPClient(client *http.Client) Option { + return func(r *RegoFunc) { + r.httpClient = client + } +} + +func WithLogger(logger *zap.Logger) Option { + return func(c *RegoFunc) { + c.logger = logger + } +} diff --git a/internal/regofunc/regofunc.go b/internal/regofunc/regofunc.go new file mode 100644 index 0000000000000000000000000000000000000000..c7977c4169b57aa8dd0e0aa1380d85ed6d13b6e0 --- /dev/null +++ b/internal/regofunc/regofunc.go @@ -0,0 +1,139 @@ +// Package regofunc provides functions that extend the Rego runtime +// with additional capabilities and built-in functions which can be +// used when writing and evaluating Rego polices. +package regofunc + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/rego" + "github.com/open-policy-agent/opa/types" + "go.uber.org/zap" +) + +type RegoFunc struct { + cacheAddr string + + httpClient *http.Client + logger *zap.Logger +} + +func New(cacheAddr string, opts ...Option) *RegoFunc { + rf := &RegoFunc{ + cacheAddr: cacheAddr, + httpClient: http.DefaultClient, + logger: zap.NewNop(), + } + + for _, opt := range opts { + opt(rf) + } + + return rf +} + +func (r *RegoFunc) CacheGetFunc() (*rego.Function, rego.Builtin3) { + return ®o.Function{ + Name: "cache.get", + Decl: types.NewFunction(types.Args(types.S, types.S, types.S), types.A), + Memoize: true, + }, + func(bctx rego.BuiltinContext, a, b, c *ast.Term) (*ast.Term, error) { + var key, namespace, scope string + + if err := ast.As(a.Value, &key); err != nil { + return nil, fmt.Errorf("invalid key: %s", err) + } else if err = ast.As(b.Value, &namespace); err != nil { + return nil, fmt.Errorf("invalid namespace: %s", err) + } else if err = ast.As(c.Value, &scope); err != nil { + return nil, fmt.Errorf("invalid scope: %s", err) + } + + req, err := http.NewRequest("GET", r.cacheAddr+"/v1/cache", nil) + req.Header = http.Header{ + "x-cache-key": []string{key}, + "x-cache-namespace": []string{namespace}, + "x-cache-scope": []string{scope}, + } + if err != nil { + return nil, err + } + + resp, err := r.httpClient.Do(req.WithContext(bctx.Context)) + if err != nil { + return nil, err + } + defer resp.Body.Close() // nolint:errcheck + + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNotFound { + return nil, fmt.Errorf("unexpected response: %d %s", resp.StatusCode, resp.Status) + } + + v, err := ast.ValueFromReader(resp.Body) + if err != nil { + return nil, err + } + + return ast.NewTerm(v), nil + } +} + +func (r *RegoFunc) CacheSetFunc() (*rego.Function, rego.Builtin4) { + return ®o.Function{ + Name: "cache.set", + Decl: types.NewFunction(types.Args(types.S, types.S, types.S, types.S), types.A), + Memoize: true, + }, + func(bctx rego.BuiltinContext, k, n, s, d *ast.Term) (*ast.Term, error) { + var key, namespace, scope string + var data map[string]interface{} + + if err := ast.As(k.Value, &key); err != nil { + return nil, fmt.Errorf("invalid key: %s", err) + } else if err = ast.As(n.Value, &namespace); err != nil { + return nil, fmt.Errorf("invalid namespace: %s", err) + } else if err = ast.As(s.Value, &scope); err != nil { + return nil, fmt.Errorf("invalid scope: %s", err) + } else if err = ast.As(d.Value, &data); err != nil { + return nil, fmt.Errorf("invalid data: %s", err) + } + + jsonData, err := json.Marshal(data) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", r.cacheAddr+"/v1/cache", bytes.NewReader(jsonData)) + if err != nil { + return nil, err + } + + req.Header = http.Header{ + "x-cache-key": []string{key}, + "x-cache-namespace": []string{namespace}, + "x-cache-scope": []string{scope}, + } + + resp, err := r.httpClient.Do(req.WithContext(bctx.Context)) + if err != nil { + return nil, err + } + defer resp.Body.Close() // nolint:errcheck + + if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected response code: %d", resp.StatusCode) + } + + var val ast.Value + val, err = ast.InterfaceToValue("success") + if err != nil { + return nil, err + } + + return ast.NewTerm(val), nil + } +} diff --git a/internal/regofunc/regofunc_test.go b/internal/regofunc/regofunc_test.go new file mode 100644 index 0000000000000000000000000000000000000000..86e48dc96c900a401bb449c573604ada9446f066 --- /dev/null +++ b/internal/regofunc/regofunc_test.go @@ -0,0 +1,94 @@ +package regofunc_test + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/open-policy-agent/opa/rego" + "github.com/stretchr/testify/assert" + + "code.vereign.com/gaiax/tsa/policy/internal/regofunc" +) + +func TestRegoFunc_CacheGetFunc(t *testing.T) { + expected := `{"taskID":"deadbeef"}` + cacheSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprint(w, expected) + })) + defer cacheSrv.Close() + + regofuncs := regofunc.New(cacheSrv.URL) + + r := rego.New( + rego.Query(`cache.get("open-policy-agent", "opa", "111")`), + rego.Function3(regofuncs.CacheGetFunc()), + ) + resultSet, err := r.Eval(context.Background()) + assert.NoError(t, err) + + resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value) + assert.NoError(t, err) + assert.Equal(t, expected, string(resultBytes)) +} + +func TestRegoFunc_CacheSetFuncSuccess(t *testing.T) { + expected := "success" + cacheSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + expectedRequestBody := `{"test":123}` + bodyBytes, err := io.ReadAll(r.Body) + assert.NoError(t, err) + + bodyString := string(bodyBytes) + if bodyString != expectedRequestBody { + assert.Equal(t, expectedRequestBody, bodyString) + } + + w.WriteHeader(http.StatusCreated) + })) + defer cacheSrv.Close() + + regofuncs := regofunc.New(cacheSrv.URL) + + input := map[string]interface{}{"test": 123} + query, err := rego.New( + rego.Query(`cache.set("open-policy-agent", "opa", "111", input)`), + rego.Function4(regofuncs.CacheSetFunc()), + ).PrepareForEval(context.Background()) + assert.NoError(t, err) + + resultSet, err := query.Eval(context.Background(), rego.EvalInput(input)) + assert.NoError(t, err) + assert.NotEmpty(t, resultSet) + assert.NotEmpty(t, resultSet[0].Expressions) + assert.Equal(t, expected, resultSet[0].Expressions[0].Value) +} + +func TestRegoFunc_CacheSetFuncError(t *testing.T) { + cacheSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + expectedRequestBody := "test" + bodyBytes, err := io.ReadAll(r.Body) + assert.NoError(t, err) + + bodyString := string(bodyBytes) + assert.Equal(t, expectedRequestBody, bodyString) + + w.WriteHeader(http.StatusNotFound) + })) + defer cacheSrv.Close() + + regofuncs := regofunc.New(cacheSrv.URL) + + r := rego.New( + rego.Query(`cache.set("open-policy-agent", "opa", "111", "test")`), + rego.Function4(regofuncs.CacheSetFunc()), + ) + + resultSet, err := r.Eval(context.Background()) + assert.NoError(t, err) + assert.Empty(t, resultSet) +} diff --git a/internal/service/policy/service.go b/internal/service/policy/service.go index aff12a10b1ba8603a7d799e0cdc28fc317f63cae..9a1bfc7c215798c05ee5753109e7da890c9b0d1a 100644 --- a/internal/service/policy/service.go +++ b/internal/service/policy/service.go @@ -9,6 +9,7 @@ import ( "code.vereign.com/gaiax/tsa/golib/errors" "code.vereign.com/gaiax/tsa/policy/gen/policy" + "code.vereign.com/gaiax/tsa/policy/internal/regofunc" "code.vereign.com/gaiax/tsa/policy/internal/storage" ) @@ -26,16 +27,18 @@ type RegoCache interface { } type Service struct { - storage Storage - cache RegoCache - logger *zap.Logger + storage Storage + cache RegoCache + regoFunc *regofunc.RegoFunc + logger *zap.Logger } -func New(storage Storage, cache RegoCache, logger *zap.Logger) *Service { +func New(storage Storage, cache RegoCache, regoFunc *regofunc.RegoFunc, logger *zap.Logger) *Service { return &Service{ - storage: storage, - cache: cache, - logger: logger, + storage: storage, + cache: cache, + regoFunc: regoFunc, + logger: logger, } } @@ -47,7 +50,7 @@ func New(storage Storage, cache RegoCache, logger *zap.Logger) *Service { // Evaluating the URL: `.../policies/mygroup/example/1.0/evaluation` will // return results correctly, only if the package declaration inside the policy is: // `package mygroup.example` -func (s *Service) Evaluate(ctx context.Context, req *policy.EvaluateRequest) (*policy.EvaluateResult, error) { +func (s *Service) Evaluate(ctx context.Context, req *policy.EvaluateRequest) (interface{}, error) { logger := s.logger.With( zap.String("name", req.PolicyName), zap.String("group", req.Group), @@ -67,16 +70,16 @@ func (s *Service) Evaluate(ctx context.Context, req *policy.EvaluateRequest) (*p } if len(resultSet) == 0 { - logger.Error("policy evaluation results are missing") - return nil, errors.New("policy evaluation results are missing") + logger.Error("policy evaluation results are empty") + return nil, errors.New("policy evaluation results are empty") } if len(resultSet[0].Expressions) == 0 { - logger.Error("policy evaluation result expressions are missing") - return nil, errors.New("policy evaluation result expressions are missing") + logger.Error("policy evaluation result expressions are empty") + return nil, errors.New("policy evaluation result expressions are empty") } - return &policy.EvaluateResult{Result: resultSet[0].Expressions[0].Value}, nil + return resultSet[0].Expressions[0].Value, nil } // Lock a policy so that it cannot be evaluated. @@ -172,6 +175,9 @@ func (s *Service) prepareQuery(ctx context.Context, policyName, group, version s newQuery, err := rego.New( rego.Module(pol.Filename, pol.Rego), rego.Query(regoQuery), + rego.Function3(s.regoFunc.CacheGetFunc()), + rego.Function4(s.regoFunc.CacheSetFunc()), + rego.StrictBuiltinErrors(true), ).PrepareForEval(ctx) if err != nil { return nil, errors.New("error preparing rego query", err) diff --git a/internal/service/policy/service_test.go b/internal/service/policy/service_test.go index a5eebd2425e15d1088ff4863ebd4130dac8190c4..95fead05f1e6d5a30dc3d678e960173c489ef3fa 100644 --- a/internal/service/policy/service_test.go +++ b/internal/service/policy/service_test.go @@ -11,6 +11,7 @@ import ( "code.vereign.com/gaiax/tsa/golib/errors" goapolicy "code.vereign.com/gaiax/tsa/policy/gen/policy" + "code.vereign.com/gaiax/tsa/policy/internal/regofunc" "code.vereign.com/gaiax/tsa/policy/internal/service/policy" "code.vereign.com/gaiax/tsa/policy/internal/service/policy/policyfakes" "code.vereign.com/gaiax/tsa/policy/internal/storage" @@ -19,7 +20,8 @@ import ( func TestNew(t *testing.T) { storage := &policyfakes.FakeStorage{} regocache := &policyfakes.FakeRegoCache{} - svc := policy.New(storage, regocache, zap.NewNop()) + regofuncs := regofunc.New("https://example.com") + svc := policy.New(storage, regocache, regofuncs, zap.NewNop()) assert.Implements(t, (*goapolicy.Service)(nil), svc) } @@ -52,7 +54,7 @@ func TestService_Evaluate(t *testing.T) { regocache policy.RegoCache // expected result - res *goapolicy.EvaluateResult + res interface{} errkind errors.Kind errtext string }{ @@ -65,7 +67,7 @@ func TestService_Evaluate(t *testing.T) { return &q, true }, }, - res: &goapolicy.EvaluateResult{Result: map[string]interface{}{"allow": true}}, + res: map[string]interface{}{"allow": true}, }, { name: "policy is not found", @@ -138,13 +140,14 @@ func TestService_Evaluate(t *testing.T) { }, nil }, }, - res: &goapolicy.EvaluateResult{Result: map[string]interface{}{"allow": true}}, + res: map[string]interface{}{"allow": true}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - svc := policy.New(test.storage, test.regocache, zap.NewNop()) + regofuncs := regofunc.New("https://example.com") + svc := policy.New(test.storage, test.regocache, regofuncs, zap.NewNop()) res, err := svc.Evaluate(context.Background(), test.req) if err == nil { assert.Empty(t, test.errtext) @@ -243,7 +246,8 @@ func TestService_Lock(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - svc := policy.New(test.storage, nil, zap.NewNop()) + regofuncs := regofunc.New("https://example.com") + svc := policy.New(test.storage, nil, regofuncs, zap.NewNop()) err := svc.Lock(context.Background(), test.req) if err == nil { assert.Empty(t, test.errtext) @@ -340,7 +344,8 @@ func TestService_Unlock(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - svc := policy.New(test.storage, nil, zap.NewNop()) + regofuncs := regofunc.New("https://example.com") + svc := policy.New(test.storage, nil, regofuncs, zap.NewNop()) err := svc.Unlock(context.Background(), test.req) if err == nil { assert.Empty(t, test.errtext) diff --git a/regoext/cache.go b/regoext/cache.go deleted file mode 100644 index 60ea23ce6486f3e788e21a9bacd7eaeeacd39cbc..0000000000000000000000000000000000000000 --- a/regoext/cache.go +++ /dev/null @@ -1,134 +0,0 @@ -package regoext - -import ( - "bytes" - "fmt" - "net/http" - - "github.com/open-policy-agent/opa/ast" - "github.com/open-policy-agent/opa/rego" - "github.com/open-policy-agent/opa/types" -) - -const ( - Success string = "success" -) - -type CacheParams struct { - Key string - Namespace string - Scope string -} - -type CacheExt struct { - path string -} - -func NewCacheExt(path string) *CacheExt { - return &CacheExt{path: path} -} - -func (ce *CacheExt) GetCacheFunc() (*rego.Function, rego.Builtin3) { - return ®o.Function{ - Name: "cache.get", - Decl: types.NewFunction(types.Args(types.S, types.S, types.S), types.A), - Memoize: true, - }, - func(bctx rego.BuiltinContext, a, b, c *ast.Term) (*ast.Term, error) { - - var key, namespace, scope string - - if err := ast.As(a.Value, &key); err != nil { - return nil, err - } else if err = ast.As(b.Value, &namespace); err != nil { - return nil, err - } else if err = ast.As(c.Value, &scope); err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", ce.path+"/v1/cache", nil) - req.Header = http.Header{ - "x-cache-key": []string{key}, - "x-cache-namespace": []string{namespace}, - "x-cache-scope": []string{scope}, - } - if err != nil { - return nil, err - } - - resp, err := http.DefaultClient.Do(req.WithContext(bctx.Context)) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf(resp.Status) - } - - v, err := ast.ValueFromReader(resp.Body) - if err != nil { - return nil, err - } - - return ast.NewTerm(v), nil - } -} - -func (ce *CacheExt) SetCacheFunc() (*rego.Function, rego.Builtin4) { - return ®o.Function{ - Name: "cache.set", - Decl: types.NewFunction(types.Args(types.S, types.S, types.S, types.S), types.A), - Memoize: true, - }, - func(bctx rego.BuiltinContext, k, n, s, d *ast.Term) (*ast.Term, error) { - var key, namespace, scope, data string - - if err := ast.As(k.Value, &key); err != nil { - return nil, err - } else if err = ast.As(n.Value, &namespace); err != nil { - return nil, err - } else if err = ast.As(s.Value, &scope); err != nil { - return nil, err - } else if err = ast.As(d.Value, &data); err != nil { - return nil, err - } - - type Response struct { - Result string `json:"result"` - } - r := &Response{Success} - - payloadBuf := bytes.NewBufferString(data) - - req, err := http.NewRequest("POST", ce.path+"/v1/cache", payloadBuf) - req.Header = http.Header{ - "x-cache-key": []string{key}, - "x-cache-namespace": []string{namespace}, - "x-cache-scope": []string{scope}, - } - if err != nil { - return nil, err - } - - resp, err := http.DefaultClient.Do(req.WithContext(bctx.Context)) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - if resp.StatusCode != http.StatusCreated { - return nil, err - } - - var val ast.Value - val, err = ast.InterfaceToValue(r) - if err != nil { - return nil, err - } - - return ast.NewTerm(val), nil - } -} diff --git a/regoext/cache_test.go b/regoext/cache_test.go deleted file mode 100644 index b929377a34243db19bb06dc27c1f21fbc5b2bc3d..0000000000000000000000000000000000000000 --- a/regoext/cache_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package regoext_test - -import ( - "context" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "net/http/httptest" - "regexp" - "testing" - - "code.vereign.com/gaiax/tsa/policy/regoext" - "github.com/open-policy-agent/opa/rego" -) - -func TestCacheExt_GetCacheFunc(t *testing.T) { - expected := "{}" - - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, expected) - })) - defer srv.Close() - - cache := regoext.NewCacheExt(srv.URL) - - r := rego.New( - rego.Query(`cache.get("open-policy-agent", "opa", "111")`), - rego.Function3(cache.GetCacheFunc()), - ) - - rs, err := r.Eval(context.Background()) - - if err != nil { - t.Errorf("unexpected error, %v", err) - return - } - - bs, err := json.MarshalIndent(rs[0].Expressions[0].Value, "", " ") - if err != nil { - t.Errorf("unexpected error, %v", err) - return - } - if string(bs) != expected { - t.Errorf("expected %s, got %s", expected, string(bs)) - } -} - -func TestCacheExt_SetCacheFuncSuccess(t *testing.T) { - expected := `{ "result": "success" }` - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - expectedRequestBody := "test" - w.WriteHeader(http.StatusCreated) - fmt.Fprint(w, "") - - bodyBytes, err := io.ReadAll(r.Body) - if err != nil { - log.Fatal(err) - } - bodyString := string(bodyBytes) - if bodyString != expectedRequestBody { - t.Errorf("unexpected body string, expected %s, got %s", expectedRequestBody, bodyString) - } - })) - defer srv.Close() - - cache := regoext.NewCacheExt(srv.URL) - - r := rego.New( - rego.Query(`cache.set("open-policy-agent", "opa", "111", "test")`), - rego.Function4(cache.SetCacheFunc()), - ) - - rs, err := r.Eval(context.Background()) - - if err != nil { - t.Errorf("unexpected error, %v", err) - return - } - - bs, err := json.MarshalIndent(rs[0].Expressions[0].Value, "", " ") - if err != nil { - t.Errorf("unexpected error, %v", err) - return - } - - re := regexp.MustCompile(`(\s+)|(\n)+`) - s := re.ReplaceAllString(string(bs), " ") - if s != expected { - t.Errorf("unexpected result, expected %s, got %s", expected, s) - } -} - -func TestCacheExt_SetCacheFuncError(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - expectedRequestBody := "test" - w.WriteHeader(http.StatusNotFound) - fmt.Fprint(w, "") - - bodyBytes, err := io.ReadAll(r.Body) - if err != nil { - log.Fatal(err) - } - bodyString := string(bodyBytes) - if bodyString != expectedRequestBody { - t.Errorf("unexpected body string, expected %s, got %s", expectedRequestBody, bodyString) - } - })) - defer srv.Close() - - cache := regoext.NewCacheExt(srv.URL) - - r := rego.New( - rego.Query(`cache.set("open-policy-agent", "opa", "111", "test")`), - rego.Function4(cache.SetCacheFunc()), - ) - - rs, err := r.Eval(context.Background()) - - if err != nil { - t.Errorf("unexpected error, %v", err) - return - } - - if len(rs) != 0 { - t.Errorf("result set should be empty, got %v", rs) - } -}