Skip to content

Commit

Permalink
implemented git clonining on the frontend
Browse files Browse the repository at this point in the history
Signed-off-by: Gergely Brautigam <[email protected]>
  • Loading branch information
Skarlso committed Jan 3, 2025
1 parent 25474f5 commit c5ae6da
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 117 deletions.
10 changes: 5 additions & 5 deletions cmd/crd.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,11 @@ func constructHandler(args *rootArgs) (Handler, error) {
crdHandler = &ConfigHandler{configFileLocation: args.configFileLocation}
case args.gitAccess:
crdHandler = &GitHandler{
url: args.url,
username: args.username,
password: args.password,
token: args.token,
tag: args.tag,
URL: args.url,
Username: args.username,
Password: args.password,
Token: args.token,
Tag: args.tag,
caBundle: args.caBundle,
privSSHKey: args.privSSHKey,
useSSHAgent: args.useSSHAgent,
Expand Down
112 changes: 66 additions & 46 deletions cmd/git_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import (
)

type GitHandler struct {
url string
username string
password string
token string
tag string
URL string
Username string
Password string
Token string
Tag string

caBundle string
privSSHKey string
useSSHAgent bool
Expand All @@ -38,17 +39,17 @@ func (g *GitHandler) CRDs() ([]*pkg.SchemaType, error) {

r, err := git.Clone(memory.NewStorage(), nil, opts)
if err != nil {
return nil, err
return nil, fmt.Errorf("error cloning git repository: %w", err)
}

var ref *plumbing.Reference
if g.tag != "" {
ref, err = r.Tag(g.tag)
if g.Tag != "" {
ref, err = r.Tag(g.Tag)
} else {
ref, err = r.Head()
}
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to construct reference: %v", err)
}

crds, err := gatherSchemaTypesForRef(r, ref)
Expand All @@ -62,9 +63,15 @@ func (g *GitHandler) CRDs() ([]*pkg.SchemaType, error) {
}

func gatherSchemaTypesForRef(r *git.Repository, ref *plumbing.Reference) ([]*pkg.SchemaType, error) {
commit, err := r.CommitObject(ref.Hash())
// Need to resolve the ref first to the right hash otherwise it's not found.
hash, err := r.ResolveRevision(plumbing.Revision(ref.Hash().String()))
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to resolve revision: %w", err)
}

commit, err := r.CommitObject(*hash)
if err != nil {
return nil, fmt.Errorf("error getting commit object: %v", err)
}

commitTree, err := commit.Tree()
Expand All @@ -73,83 +80,96 @@ func gatherSchemaTypesForRef(r *git.Repository, ref *plumbing.Reference) ([]*pkg
}

var crds []*pkg.SchemaType

// Tried to make this concurrent, but there was very little gain. It just takes this long to
// clone a large repository. It's not the processing OR the rendering that takes long.
if err := commitTree.Files().ForEach(func(f *object.File) error {
if ext := filepath.Ext(f.Name); ext != ".yaml" {
return nil
}

content, err := f.Contents()
crd, err := processEntry(f)
if err != nil {
return err
}

sanitized, err := sanitize.Sanitize([]byte(content))
if err != nil {
return fmt.Errorf("failed to sanitize content: %w", err)
if crd != nil {
crds = append(crds, crd)
}

crd := &unstructured.Unstructured{}
if err := yaml.Unmarshal(sanitized, crd); err != nil {
_, _ = fmt.Fprintln(os.Stderr, "skipping none CRD file: "+f.Name)
return nil
}); err != nil {
return nil, err
}

return nil //nolint:nilerr // intentional
}
schemaType, err := pkg.ExtractSchemaType(crd)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "skipping none CRD file: "+crd.GetName())
return crds, nil
}

return nil //nolint:nilerr // intentional
func processEntry(f *object.File) (*pkg.SchemaType, error) {
for _, path := range strings.Split(f.Name, string(filepath.Separator)) {
if path == "test" {
return nil, nil
}
}

if schemaType != nil {
crds = append(crds, schemaType)
}
if ext := filepath.Ext(f.Name); ext != ".yaml" {
return nil, nil
}

return nil
}); err != nil {
content, err := f.Contents()
if err != nil {
return nil, err
}

return crds, nil
sanitized, err := sanitize.Sanitize([]byte(content))
if err != nil {
return nil, fmt.Errorf("failed to sanitize content: %w", err)
}

crd := &unstructured.Unstructured{}
if err := yaml.Unmarshal(sanitized, crd); err != nil {
return nil, nil //nolint:nilerr // intentional
}

schemaType, err := pkg.ExtractSchemaType(crd)
if err != nil {
return nil, nil //nolint:nilerr // intentional
}

return schemaType, nil
}

func (g *GitHandler) constructGitOptions() (*git.CloneOptions, error) {
opts := &git.CloneOptions{
URL: g.url,
URL: g.URL,
Depth: 1,
}

// trickle down. if ssh key is set, this will be overwritten.
if g.username != "" && g.password != "" {
if g.Username != "" && g.Password != "" {
opts.Auth = &http.BasicAuth{
Username: g.username,
Password: g.password,
Username: g.Username,
Password: g.Password,
}
}
if g.token != "" {
if g.Token != "" {
opts.Auth = &http.TokenAuth{
Token: g.token,
Token: g.Token,
}
}
if g.caBundle != "" {
opts.CABundle = []byte(g.caBundle)
}
if g.privSSHKey != "" {
if !strings.Contains(g.url, "@") {
return nil, fmt.Errorf("git URL does not contain an ssh address: %s", g.url)
if !strings.Contains(g.URL, "@") {
return nil, fmt.Errorf("git URL does not contain an ssh address: %s", g.URL)
}

keys, err := ssh.NewPublicKeysFromFile("git", g.privSSHKey, g.password)
keys, err := ssh.NewPublicKeysFromFile("git", g.privSSHKey, g.Password)
if err != nil {
return nil, err
}

opts.Auth = keys
}
if g.useSSHAgent {
if !strings.Contains(g.url, "@") {
return nil, fmt.Errorf("git URL does not contain an ssh address: %s", g.url)
if !strings.Contains(g.URL, "@") {
return nil, fmt.Errorf("git URL does not contain an ssh address: %s", g.URL)
}

authMethod, err := ssh.NewSSHAgentAuth("git")
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/maxence-charriere/go-app/v10 v10.0.9
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.10.0
golang.org/x/sync v0.10.0
k8s.io/apiextensions-apiserver v0.32.0
k8s.io/apimachinery v0.32.0
)
Expand Down
49 changes: 20 additions & 29 deletions wasm/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@ import (
"strings"

"github.com/maxence-charriere/go-app/v10/pkg/app"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"

"github.com/Skarlso/crd-to-sample-yaml/pkg"
"github.com/Skarlso/crd-to-sample-yaml/pkg/fetcher"
"github.com/Skarlso/crd-to-sample-yaml/pkg/sanitize"
"github.com/Skarlso/crd-to-sample-yaml/v1beta1"
)

Expand All @@ -25,9 +22,11 @@ type crdView struct {
app.Compo
preRenderErr error

content []byte
//content []byte

Check failure on line 25 in wasm/app.go

View workflow job for this annotation

GitHub Actions / lint

commentFormatting: put a space between `//` and comment text (gocritic)
crds []*pkg.SchemaType
comment bool
minimal bool
useGit bool

Check failure on line 29 in wasm/app.go

View workflow job for this annotation

GitHub Actions / lint

field `useGit` is unused (unused)

navigateBackOnClick func(ctx app.Context, _ app.Event)
}
Expand Down Expand Up @@ -175,14 +174,14 @@ func (h *crdView) OnNav(ctx app.Context) {
return
}

content, err = sanitize.Sanitize(content)
crd, err := renderCRDContent(content)
if err != nil {
h.preRenderErr = err

return
}

h.content = content
h.crds = append(h.crds, crd)
}

// The Render method is where the component appearance is defined.
Expand All @@ -191,34 +190,26 @@ func (h *crdView) Render() app.UI {
return h.buildError(h.preRenderErr)
}

crd := &unstructured.Unstructured{}
if err := yaml.Unmarshal(h.content, crd); err != nil {
return h.buildError(err)
}

schemaType, err := pkg.ExtractSchemaType(crd)
if err != nil {
return h.buildError(err)
}

versions := make([]Version, 0)
for _, version := range schemaType.Versions {
v, err := h.generate(schemaType, version.Schema, version.Name)
if err != nil {
return h.buildError(err)
for _, schemaType := range h.crds {
for _, version := range schemaType.Versions {
v, err := h.generate(schemaType, version.Schema, schemaType.Kind+"-"+version.Name)
if err != nil {
return h.buildError(err)
}

versions = append(versions, v)
}

versions = append(versions, v)
}
// Parse validation instead.
if len(schemaType.Versions) == 0 && schemaType.Validation != nil {
v, err := h.generate(schemaType, schemaType.Validation.Schema, schemaType.Kind+"-"+schemaType.Validation.Name)
if err != nil {
return h.buildError(err)
}

// Parse validation instead.
if len(schemaType.Versions) == 0 && schemaType.Validation != nil {
v, err := h.generate(schemaType, schemaType.Validation.Schema, schemaType.Validation.Name)
if err != nil {
return h.buildError(err)
versions = append(versions, v)
}

versions = append(versions, v)
}

wrapper := app.Div().Class("content-wrapper")
Expand Down
84 changes: 84 additions & 0 deletions wasm/cors_proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Why does this exist? You can't use git clone through libraries because
// it will be blocked by CORS. A tiny proxy service will take care of that.
package main

import (
"io"
"net/http"
"time"
)

type CorsProxy struct{}

func NewCorsProxy() *CorsProxy {
return &CorsProxy{}
}

func (p *CorsProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding")
w.Header().Set("Access-Control-Allow-Credentials", "true")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)

return
}

targetURL := r.URL.Query().Get("url")
if targetURL == "" {
http.Error(w, "Missing 'url' parameter", http.StatusBadRequest)
return
}

// create the request to server
req, err := http.NewRequest(r.Method, targetURL, r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))

Check failure on line 38 in wasm/cors_proxy.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `w.Write` is not checked (errcheck)

return
}

// add ALL headers to the connection
for n, h := range r.Header {
for _, h := range h {
req.Header.Add(n, h)
}
}

// create a basic client to send the request
client := http.Client{
Timeout: time.Second * 30,
}
resp, err := client.Do(req)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))

Check failure on line 57 in wasm/cors_proxy.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `w.Write` is not checked (errcheck)

return
}

for h, v := range resp.Header {
for _, v := range v {
w.Header().Add(h, v)
}
}
// copy the response from the server to the connected client request
w.WriteHeader(resp.StatusCode)

_, err = io.Copy(w, resp.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))

Check failure on line 73 in wasm/cors_proxy.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `w.Write` is not checked (errcheck)

return
}
}

func (p *CorsProxy) Serve() *http.Server {
return &http.Server{

Check failure on line 80 in wasm/cors_proxy.go

View workflow job for this annotation

GitHub Actions / lint

G112: Potential Slowloris Attack because ReadHeaderTimeout is not configured in the http.Server (gosec)
Addr: ":8999",
Handler: p,
}
}
Loading

0 comments on commit c5ae6da

Please sign in to comment.