From 84326d16e781e196aa8281aa78070a35a3410dc1 Mon Sep 17 00:00:00 2001 From: Mark Phelps <209477+markphelps@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:15:09 -0500 Subject: [PATCH] chore: Phase source (#146) * chore: mod tidy Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com> * chore: make source a first class thing Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com> * chore: add fuzz test for parser Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com> * chore: run tests on all prs Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com> * chore: use build tags to fix embed error in CI Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com> --------- Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com> --- .github/workflows/ci.yml | 3 +- Makefile | 4 +-- example.yml | 3 +- internal/core/core.go | 9 +++++- internal/parser/parser.go | 13 ++++++-- internal/parser/parser_test.go | 29 +++++++++++++++++ internal/parser/testdata/example.yml | 48 ++++++++++++++++++++++++++++ ui/embed.go | 3 ++ ui/embed_stub.go | 18 +++++++++++ ui/src/components/phase-node.tsx | 18 +++++++++-- ui/src/types/descriptor.ts | 9 +++++- 11 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 internal/parser/parser_test.go create mode 100644 internal/parser/testdata/example.yml create mode 100644 ui/embed_stub.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 449d189..ba50a11 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,6 @@ on: push: branches: [main] pull_request: - branches: [main] jobs: test: @@ -22,4 +21,4 @@ jobs: run: go mod download - name: Run tests - run: go test -v ./... + run: go test -v -race ./... diff --git a/Makefile b/Makefile index e4995a7..113ed9b 100644 --- a/Makefile +++ b/Makefile @@ -29,10 +29,10 @@ help: build: cd $(UI_DIR) && $(NPM) run build - $(GOBUILD) -o bin/$(BINARY_NAME) ./cmd/glu + $(GOBUILD) -o bin/$(BINARY_NAME) -tags ui ./cmd/glu test: - $(GOTEST) -v ./... + $(GOTEST) -v -race ./... clean: rm -f bin/$(BINARY_NAME) diff --git a/example.yml b/example.yml index fbda810..ffa1b68 100644 --- a/example.yml +++ b/example.yml @@ -3,8 +3,9 @@ pipelines: sources: git: kind: ci - scm: github name: get-glu/gitops-example + config: + scm: github staging-k8s: kind: kubernetes diff --git a/internal/core/core.go b/internal/core/core.go index d8cf367..bcef1cb 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -29,9 +29,16 @@ func Name(name string) Metadata { // Descriptor is a type which describes a Phase type Descriptor struct { - Kind string `json:"kind"` Pipeline string `json:"pipeline"` Metadata Metadata `json:"metadata"` + Source Source `json:"source"` +} + +// Source is a type which describes a source of a phase +type Source struct { + Kind string `json:"kind"` + Name string `json:"name"` + Config map[string]any `json:"config"` } func (d Descriptor) String() string { diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 01f0d06..19cc77b 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -26,7 +26,6 @@ type PipelineConfig struct { type SourceConfig struct { Kind string `yaml:"kind"` Name string `yaml:"name"` - SCM string `yaml:"scm,omitempty"` Config map[string]interface{} `yaml:"config,omitempty"` } @@ -45,6 +44,11 @@ func Parse(ctx context.Context, file string) (*core.System, error) { return sys, nil } +// parse is a helper function for testing +func parse(ctx context.Context, decoder *decoder) (*core.System, error) { + return readFrom(ctx, decoder) +} + // parsePipeline converts a PipelineConfig into a glu.Pipeline func parsePipeline(_ context.Context, cfg PipelineConfig) (*core.Pipeline, error) { pipeline := core.NewPipeline(core.Name(cfg.Name)) @@ -70,8 +74,13 @@ func parsePipeline(_ context.Context, cfg PipelineConfig) (*core.Pipeline, error // Create phase descriptor desc := core.Descriptor{ - Kind: sourceConfig.Kind, + Pipeline: cfg.Name, Metadata: phaseMeta, + Source: core.Source{ + Kind: sourceConfig.Kind, + Name: sourceConfig.Name, + Config: sourceConfig.Config, + }, } // Create the phase diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go new file mode 100644 index 0000000..93d37f9 --- /dev/null +++ b/internal/parser/parser_test.go @@ -0,0 +1,29 @@ +package parser + +import ( + "bytes" + "context" + "os" + "testing" + + "gopkg.in/yaml.v3" +) + +func FuzzParse(f *testing.F) { + testdata := []string{ + "testdata/example.yaml", + } + + for _, data := range testdata { + b, _ := os.ReadFile(data) + f.Add(b) + } + + f.Fuzz(func(t *testing.T, data []byte) { + _, err := parse(context.Background(), &decoder{rd: bytes.NewReader(data), enc: yaml.Unmarshal}) + if err != nil { + // we only care about panics + t.Skip() + } + }) +} diff --git a/internal/parser/testdata/example.yml b/internal/parser/testdata/example.yml new file mode 100644 index 0000000..ffa1b68 --- /dev/null +++ b/internal/parser/testdata/example.yml @@ -0,0 +1,48 @@ +pipelines: + - name: gitops-example-app + sources: + git: + kind: ci + name: get-glu/gitops-example + config: + scm: github + + staging-k8s: + kind: kubernetes + name: staging + config: + namespace: staging + name: gitops-example-app + container: gitops-example-app + + production-k8s: + kind: kubernetes + name: production + config: + namespace: production + name: gitops-example-app + container: gitops-example-app + + oci: + kind: oci + name: app + + phases: + ci: + source: git + + oci: + source: oci + depends_on: ci + + staging: + source: staging-k8s + depends_on: oci + labels: + url: http://0.0.0.0:30081 + + production: + source: production-k8s + depends_on: staging + labels: + url: http://0.0.0.0:30082 \ No newline at end of file diff --git a/ui/embed.go b/ui/embed.go index 9eab74d..26df2b5 100644 --- a/ui/embed.go +++ b/ui/embed.go @@ -1,3 +1,6 @@ +//go:build ui +// +build ui + package ui import ( diff --git a/ui/embed_stub.go b/ui/embed_stub.go new file mode 100644 index 0000000..ce584ed --- /dev/null +++ b/ui/embed_stub.go @@ -0,0 +1,18 @@ +//go:build !ui +// +build !ui + +package ui + +import "io/fs" + +// FS returns an empty filesystem when UI files are not embedded +func FS() fs.FS { + return emptyFS{} +} + +// emptyFS implements fs.FS and always returns fs.ErrNotExist +type emptyFS struct{} + +func (emptyFS) Open(name string) (fs.File, error) { + return nil, fs.ErrNotExist +} diff --git a/ui/src/components/phase-node.tsx b/ui/src/components/phase-node.tsx index 317aa00..9f56fc0 100644 --- a/ui/src/components/phase-node.tsx +++ b/ui/src/components/phase-node.tsx @@ -1,16 +1,28 @@ import { Handle, NodeProps, Position } from '@xyflow/react'; -import { Package, GitBranch } from 'lucide-react'; +import { Package, GitBranch, Container, Hexagon, Github, Gitlab } from 'lucide-react'; import { PhaseNode as PhaseNodeType } from '@/types/flow'; import { ANNOTATION_OCI_IMAGE_URL } from '@/types/metadata'; import { Label } from './label'; const PhaseNode = ({ data: phase }: NodeProps) => { const getIcon = () => { - switch (phase.descriptor.kind ?? '') { + switch (phase.descriptor.source.kind ?? '') { + case 'kubernetes': + case 'k8s': + return ; case 'oci': return ; - default: + case 'ci': + if (phase.descriptor.source.config?.scm === 'github') { + return ; + } else if (phase.descriptor.source.config?.scm === 'gitlab') { + return ; + } + return ; + case 'git': return ; + default: + return ; } }; diff --git a/ui/src/types/descriptor.ts b/ui/src/types/descriptor.ts index 57af94c..57a98d6 100644 --- a/ui/src/types/descriptor.ts +++ b/ui/src/types/descriptor.ts @@ -1,7 +1,14 @@ import { Metadata } from './metadata'; export type Descriptor = { - kind: string; pipeline: string; metadata: Metadata; + source: Source; +}; + +export type Source = { + kind: string; + name: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + config: Record; };