Skip to content

Commit

Permalink
feat: add playground (#99)
Browse files Browse the repository at this point in the history
* feat: build wasm

Signed-off-by: Charles-Edouard Brétéché <[email protected]>

* feat: add playground

Signed-off-by: Charles-Edouard Brétéché <[email protected]>

---------

Signed-off-by: Charles-Edouard Brétéché <[email protected]>
  • Loading branch information
eddycharly authored Oct 12, 2023
1 parent bef0191 commit bc9ee31
Show file tree
Hide file tree
Showing 28 changed files with 1,597 additions and 1,949 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
.gopath
kyverno-json
website/site
playground/assets/main.wasm
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,17 @@ $(CLI_BIN): fmt vet
@echo Build cli binary... >&2
@CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) go build -o ./$(CLI_BIN) -ldflags=$(LD_FLAGS) .

.PHONY: build
build: $(CLI_BIN) ## Build

.PHONY: build-wasm
build-wasm: fmt vet ## Build the wasm binary
@GOOS=js GOARCH=wasm go build -o ./playground/assets/main.wasm -ldflags=$(LD_FLAGS) ./cmd/wasm/main.go

.PHONY: serve
serve: build-wasm ## Serve static files.
python3 -m http.server -d playground/ 8080

###########
# CODEGEN #
###########
Expand Down
54 changes: 54 additions & 0 deletions cmd/wasm/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2023 Undistro Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build js && wasm

package main

import (
"context"
"os/signal"
"syscall"
"time"

server "github.com/kyverno/kyverno-json/pkg/server/wasm"
)

func main() {
// initialise gin framework
// gin.SetMode(c.ginFlags.mode)
// tonic.SetBindHook(tonic.DefaultBindingHookMaxBodyBytes(int64(c.ginFlags.maxBodySize)))
// create server
server, err := server.New(true, true)
if err != nil {
panic(err)
}
// register playground routes
if err := server.AddPlaygroundRoutes(); err != nil {
panic(err)
}
// run server
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
shutdown := server.Run(ctx)
<-ctx.Done()
stop()
if shutdown != nil {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := shutdown(ctx); err != nil {
panic(err)
}
}
}
196 changes: 7 additions & 189 deletions go.mod

Large diffs are not rendered by default.

1,759 changes: 6 additions & 1,753 deletions go.sum

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pkg/commands/serve/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ func (c *options) Run(_ *cobra.Command, _ []string) error {
client: client,
},
}
// register API routes
if err := server.AddAPIRoutes(config); err != nil {
// register api routes
if err := server.AddApiRoutes(config); err != nil {
return err
}
// run server
Expand Down
2 changes: 1 addition & 1 deletion pkg/payload/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"path/filepath"

"github.com/kyverno/kyverno-json/pkg/utils/file"
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
yamlutils "github.com/kyverno/kyverno-json/pkg/utils/yaml"
"gopkg.in/yaml.v3"
)

Expand Down
2 changes: 1 addition & 1 deletion pkg/policy/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (
"github.com/kyverno/kyverno-json/pkg/apis/v1alpha1"
"github.com/kyverno/kyverno-json/pkg/data"
fileinfo "github.com/kyverno/kyverno-json/pkg/utils/file-info"
yamlutils "github.com/kyverno/kyverno-json/pkg/utils/yaml"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource/convert"
"github.com/kyverno/kyverno/cmd/cli/kubectl-kyverno/resource/loader"
yamlutils "github.com/kyverno/kyverno/pkg/utils/yaml"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/kubectl-validate/pkg/openapiclient"
)
Expand Down
13 changes: 13 additions & 0 deletions pkg/server/playground/routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package playground

import (
"github.com/gin-gonic/gin"
"github.com/kyverno/kyverno-json/pkg/server/playground/scan"
)

func AddRoutes(group *gin.RouterGroup) error {
if err := scan.AddRoutes(group); err != nil {
return err
}
return nil
}
65 changes: 65 additions & 0 deletions pkg/server/playground/scan/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package scan

import (
"context"
"errors"
"fmt"
"net/http"

"github.com/gin-gonic/gin"
"github.com/kyverno/kyverno-json/pkg/apis/v1alpha1"
"github.com/kyverno/kyverno-json/pkg/engine/template"
jsonengine "github.com/kyverno/kyverno-json/pkg/json-engine"
"github.com/loopfz/gadgeto/tonic"
"sigs.k8s.io/yaml"
)

func newHandler() (gin.HandlerFunc, error) {
return tonic.Handler(func(ctx *gin.Context, in *Request) (*Response, error) {
// check input
if in == nil {
return nil, errors.New("input is null")
}
if in.Payload == "" {
return nil, errors.New("input payload is null")
}
if in.Policy == "" {
return nil, errors.New("input policy is null")
}
var payload interface{}
err := yaml.Unmarshal([]byte(in.Payload), &payload)
if err != nil {
return nil, fmt.Errorf("failed to parse payload (%w)", err)
}
// apply pre processors
for _, preprocessor := range in.Preprocessors {
result, err := template.Execute(context.Background(), preprocessor, payload, nil)
if err != nil {
return nil, fmt.Errorf("failed to execute prepocessor (%s) - %w", preprocessor, err)
}
if result == nil {
return nil, fmt.Errorf("prepocessor resulted in `null` payload (%s)", preprocessor)
}
payload = result
}
// load resources
var resources []interface{}
if slice, ok := payload.([]interface{}); ok {
resources = slice
} else {
resources = append(resources, payload)
}
// load policy
var policy v1alpha1.Policy
if err := yaml.Unmarshal([]byte(in.Policy), &policy); err != nil {
return nil, fmt.Errorf("failed to parse policies (%w)", err)
}
// run engine
e := jsonengine.New()
results := e.Run(context.Background(), jsonengine.JsonEngineRequest{
Resources: resources,
Policies: []*v1alpha1.Policy{&policy},
})
return makeResponse(results...), nil
}, http.StatusOK), nil
}
7 changes: 7 additions & 0 deletions pkg/server/playground/scan/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package scan

type Request struct {
Payload string `json:"payload"`
Preprocessors []string `json:"preprocessors"`
Policy string `json:"policy"`
}
26 changes: 26 additions & 0 deletions pkg/server/playground/scan/response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package scan

import (
"github.com/kyverno/kyverno-json/pkg/apis/v1alpha1"
jsonengine "github.com/kyverno/kyverno-json/pkg/json-engine"
)

type Response struct {
Results []Result `json:"results"`
}

type Result struct {
Policy *v1alpha1.Policy `json:"policy"`
Rule v1alpha1.Rule `json:"rule"`
Resource interface{} `json:"resource"`
Failure error `json:"failure"`
Error error `json:"error"`
}

func makeResponse(responses ...jsonengine.JsonEngineResponse) *Response {
var response Response
for _, result := range responses {
response.Results = append(response.Results, Result(result))
}
return &response
}
14 changes: 14 additions & 0 deletions pkg/server/playground/scan/routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package scan

import (
"github.com/gin-gonic/gin"
)

func AddRoutes(group *gin.RouterGroup) error {
handler, err := newHandler()
if err != nil {
return err
}
group.POST("/scan", handler)
return nil
}
15 changes: 12 additions & 3 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@ import (
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/kyverno/kyverno-json/pkg/server/api"
"github.com/kyverno/kyverno-json/pkg/server/playground"
)

const apiPrefix = "/api"
const (
apiPrefix = "/api"
playgroundPrefix = "/playground"
)

type Shutdown = func(context.Context) error

type Server interface {
AddAPIRoutes(api.Configuration) error
AddApiRoutes(api.Configuration) error
AddPlaygroundRoutes() error
Run(context.Context, string, int) Shutdown
}

Expand All @@ -41,10 +46,14 @@ func New(enableLogger bool, enableCors bool) (Server, error) {
return server{router}, nil
}

func (s server) AddAPIRoutes(config api.Configuration) error {
func (s server) AddApiRoutes(config api.Configuration) error {
return api.AddRoutes(s.Group(apiPrefix), config)
}

func (s server) AddPlaygroundRoutes() error {
return playground.AddRoutes(s.Group(playgroundPrefix))
}

func (s server) Run(_ context.Context, host string, port int) Shutdown {
address := fmt.Sprintf("%v:%v", host, port)
srv := &http.Server{
Expand Down
60 changes: 60 additions & 0 deletions pkg/server/wasm/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//go:build js && wasm

package wasm

import (
"context"

"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/kyverno/kyverno-json/pkg/server/api"
"github.com/kyverno/kyverno-json/pkg/server/playground"
wasmhttp "github.com/nlepage/go-wasm-http-server"
)

const (
apiPrefix = "/api"
playgroundPrefix = "/playground"
)

type Shutdown = func(context.Context) error

type Server interface {
AddApiRoutes(api.Configuration) error
AddPlaygroundRoutes() error
Run(context.Context) Shutdown
}

type server struct {
*gin.Engine
}

func New(enableLogger bool, enableCors bool) (Server, error) {
router := gin.New()
if enableLogger {
router.Use(gin.Logger())
}
router.Use(gin.Recovery())
if enableCors {
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowMethods: []string{"POST", "GET", "HEAD"},
AllowHeaders: []string{"Origin", "Content-Type"},
ExposeHeaders: []string{"Content-Length"},
}))
}
return server{router}, nil
}

func (s server) AddApiRoutes(config api.Configuration) error {
return api.AddRoutes(s.Group(apiPrefix), config)
}

func (s server) AddPlaygroundRoutes() error {
return playground.AddRoutes(s.Group(playgroundPrefix))
}

func (s server) Run(_ context.Context) Shutdown {
wasmhttp.Serve(s.Engine.Handler())
return nil
}
42 changes: 42 additions & 0 deletions pkg/utils/yaml/yaml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package yaml

import (
"bufio"
"bytes"
"fmt"
"io"
"strings"

"k8s.io/apimachinery/pkg/util/yaml"
)

// SplitDocuments reads the YAML bytes per-document, unmarshals the TypeMeta information from each document
// and returns a map between the GroupVersionKind of the document and the document bytes
func SplitDocuments(yamlBytes []byte) (documents [][]byte, error error) {
buf := bytes.NewBuffer(yamlBytes)
reader := yaml.NewYAMLReader(bufio.NewReader(buf))
for {
// Read one YAML document at a time, until io.EOF is returned
b, err := reader.Read()
if err == io.EOF || len(b) == 0 {
break
} else if err != nil {
return documents, fmt.Errorf("unable to read yaml")
}
if !IsEmptyYamlDocument(b) {
documents = append(documents, b)
}
}
return documents, nil
}

// IsEmptyYamlDocument checks if a yaml document is empty (contains only comments)
func IsEmptyYamlDocument(document []byte) bool {
for _, line := range strings.Split(string(document), "\n") {
line := strings.TrimSpace(line)
if line != "" && !strings.HasPrefix(line, "#") {
return false
}
}
return true
}
Loading

0 comments on commit bc9ee31

Please sign in to comment.