Skip to content

Commit

Permalink
minor tweaks and testing
Browse files Browse the repository at this point in the history
  • Loading branch information
wass3r committed Oct 30, 2024
1 parent 68f27af commit d57b4eb
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 79 deletions.
46 changes: 26 additions & 20 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,29 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: clone
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0

- name: install go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
# use version from go.mod file
go-version-file: 'go.mod'
cache: true
check-latest: true

- name: test
run: |
make test
- name: coverage
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: coverage.out
- name: clone
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0

- name: install go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
# use version from go.mod file
go-version-file: "go.mod"
cache: true
check-latest: true

- name: test
run: |
make test
- name: test jsonschema
run: |
go install github.com/santhosh-tekuri/jsonschema/cmd/jv@v0.7.0
make test-jsonschema
- name: coverage
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: coverage.out

34 changes: 34 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,40 @@ jsonschema:
@echo "### Generating JSON schema"
@go run cmd/jsonschema-gen/main.go > schema.json

# The `test-jsonschema` target is intended to test
# the created jsonschema against a set of failing
# and passing example vela templates located in
# schema/testdata/pipeline.
#
# The test relies on the `jv` command line tool,
# which can be installed via:
#
# go install github.com/santhosh-tekuri/jsonschema/cmd/jv@latest
#
# Usage: `make test-jsonschema`
.PHONY: test-jsonschema
test-jsonschema: jsonschema
@echo
@echo "### Testing Pipelines against JSON Schema"
@echo
@echo "=== Expected Failing Tests"
@for file in schema/testdata/pipeline/fail/*.yml; do \
echo "› Test: $$file"; \
if jv schema.json $$file >/dev/null 2>&1; then \
echo "Unexpected success for $$file"; \
exit 1; \
fi; \
done
@echo
@echo "=== Expected Passing Tests"
@for file in schema/testdata/pipeline/pass/*.yml; do \
echo "› Test: $$file"; \
if ! jv schema.json $$file >/dev/null 2>&1; then \
echo "Unexpected failure for $$file"; \
exit 1; \
fi; \
done

# The `lint` target is intended to lint the
# Go source code with golangci-lint.
#
Expand Down
63 changes: 5 additions & 58 deletions cmd/jsonschema-gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,78 +2,25 @@

//go:build ignore

// This program will utilize the json/jsonschema tags
// on structs in compiler/types/yaml to generate the
// majority of the final jsonschema for a
// Vela pipeline.
//
// Some manual intervention is needed for custom types
// and/or custom marshalling that is in place. For reference
// we use the provided mechanisms, see:
// https://github.com/invopop/jsonschema?tab=readme-ov-file#custom-type-definitions
// for hooking into the schema generation process. Some
// types will have a JSONSchema or JSONSchemaExtend method
// attached to handle the overrides.

package main

import (
"encoding/json"
"fmt"

"github.com/invopop/jsonschema"
"github.com/sirupsen/logrus"

"github.com/go-vela/server/compiler/types/yaml"
"github.com/go-vela/server/schema"
)

func main() {
ref := jsonschema.Reflector{
ExpandedStruct: true,
}
d := ref.Reflect(yaml.Build{})

if d == nil {
logrus.Fatal("reflection failed")
}

d.Title = "Vela Pipeline Configuration"

// allows folks to have other top level arbitrary
// keys without validation errors
d.AdditionalProperties = nil

// rules can currently live at ruleset level or
// nested within 'if' (default) or 'unless'.
// without changes the struct would only allow
// the nested version.
//
// note: we have to do the modification here,
// because the custom type hooks can't provide
// access to the top level definitions, even if
// they were already processed, so we have to
// do it at this top level.
ruleSetWithRules := *d.Definitions["Rules"]
ruleSet := *d.Definitions["Ruleset"]

// copy every property from Ruleset, other than `if` and `unless`
for item := ruleSet.Properties.Newest(); item != nil; item = item.Prev() {
if item.Key != "if" && item.Key != "unless" {
ruleSetWithRules.Properties.Set(item.Key, item.Value)
}
}

// create a new definition for Ruleset
d.Definitions["Ruleset"].AnyOf = []*jsonschema.Schema{
&ruleSet,
&ruleSetWithRules,
js, err := schema.NewPipelineSchema()
if err != nil {
logrus.Fatal("schema generation failed:", err)
}
d.Definitions["Ruleset"].Properties = nil
d.Definitions["Ruleset"].Type = ""
d.Definitions["Ruleset"].AdditionalProperties = nil

// output json
j, err := json.MarshalIndent(d, "", " ")
j, err := json.MarshalIndent(js, "", " ")
if err != nil {
logrus.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/types/yaml/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type (
Name string `yaml:"name,omitempty" json:"name,omitempty" jsonschema:"required,minLength=1,description=Unique identifier for the template.\nReference: https://go-vela.github.io/docs/reference/yaml/templates/#the-name-key"`
Source string `yaml:"source,omitempty" json:"source,omitempty" jsonschema:"required,minLength=1,description=Path to template in remote system.\nReference: https://go-vela.github.io/docs/reference/yaml/templates/#the-source-key"`
Format string `yaml:"format,omitempty" json:"format,omitempty" jsonschema:"enum=starlark,enum=golang,enum=go,default=go,minLength=1,description=language used within the template file \nReference: https://go-vela.github.io/docs/reference/yaml/templates/#the-format-key"`
Type string `yaml:"type,omitempty" json:"type,omitempty" jsonschema:"minLength=1,example=github,description=Type of template provided from the remote system.\nReference: https://go-vela.github.io/docs/reference/yaml/templates/#the-type-key"`
Type string `yaml:"type,omitempty" json:"type,omitempty" jsonschema:"minLength=1,enum=github,enum=file,example=github,description=Type of template provided from the remote system.\nReference: https://go-vela.github.io/docs/reference/yaml/templates/#the-type-key"`
Variables map[string]interface{} `yaml:"vars,omitempty" json:"vars,omitempty" jsonschema:"description=Variables injected into the template.\nReference: https://go-vela.github.io/docs/reference/yaml/templates/#the-variables-key"`
}

Expand Down
91 changes: 91 additions & 0 deletions schema/pipeline.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: Apache-2.0

// This program utilizes the json/jsonschema tags on structs in compiler/types/yaml
// to generate the majority of the final jsonschema for a Vela pipeline.
//
// Some manual intervention is needed for custom types and/or custom unmarshaling
// that is in place. For reference, we use the mechanisms provided by the schema lib:
// https://github.com/invopop/jsonschema?tab=readme-ov-file#custom-type-definitions
// for hooking into the schema generation process. Some types will have a JSONSchema
// or JSONSchemaExtend method attached to handle these overrides.

package schema

import (
"fmt"

"github.com/invopop/jsonschema"

types "github.com/go-vela/server/compiler/types/yaml"
)

// NewPipelineSchema generates the JSON schema object for a Vela pipeline configuration.
//
// The returned value can be marshaled into actual JSON.
func NewPipelineSchema() (*jsonschema.Schema, error) {
ref := jsonschema.Reflector{
ExpandedStruct: true,
}
s := ref.Reflect(types.Build{})

// very unlikely scenario
if s == nil {
return nil, fmt.Errorf("schema generation failed")
}

s.Title = "Vela Pipeline Configuration"

// allows folks to have other top level arbitrary
// keys without validation errors
s.AdditionalProperties = nil

// apply Ruleset modification
//
// note: we have to do the modification here,
// because the custom type hooks can't provide
// access to the top level definitions, even if
// they were already processed, so we have to
// do it at this top level.
modRulesetSchema(s)

return s, nil
}

// modRulesetSchema applies modifications to the Ruleset definition.
//
// rules can currently live at ruleset level or nested within
// 'if' (default) or 'unless'. without changes the struct would
// only allow the nested version.
func modRulesetSchema(schema *jsonschema.Schema) {
if schema.Definitions == nil {
return
}

rules, hasRules := schema.Definitions["Rules"]
ruleSet, hasRuleset := schema.Definitions["Ruleset"]

// exit early if we don't have what we need
if !hasRules || !hasRuleset {
return
}

// create copies
_rulesWithRuleset := *rules
_ruleSet := *ruleSet

// copy every property from Ruleset, other than `if` and `unless`
for item := _ruleSet.Properties.Newest(); item != nil; item = item.Prev() {
if item.Key != "if" && item.Key != "unless" {
_rulesWithRuleset.Properties.Set(item.Key, item.Value)
}
}

// create a new definition for Ruleset
schema.Definitions["Ruleset"].AnyOf = []*jsonschema.Schema{
&_ruleSet,
&_rulesWithRuleset,
}
schema.Definitions["Ruleset"].Properties = nil
schema.Definitions["Ruleset"].Type = ""
schema.Definitions["Ruleset"].AdditionalProperties = nil
}
40 changes: 40 additions & 0 deletions schema/pipeline_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: Apache-2.0

package schema

import (
"testing"
)

func TestSchema_NewPipelineSchema(t *testing.T) {
tests := []struct {
name string
wantErr bool
}{
{
name: "basic schema generation",
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewPipelineSchema()
if (err != nil) != tt.wantErr {
t.Errorf("NewPipelineSchema() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && got == nil {
t.Error("NewPipelineSchema() returned nil schema without error")
}
if !tt.wantErr {
if got.Title != "Vela Pipeline Configuration" {
t.Errorf("NewPipelineSchema() title = %v, want %v", got.Title, "Vela Pipeline Configuration")
}
if got.AdditionalProperties != nil {
t.Error("NewPipelineSchema() AdditionalProperties should be nil")
}
}
})
}
}
15 changes: 15 additions & 0 deletions schema/testdata/pipeline/fail/stages_and_steps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: "1"

stages:
test:
steps:
- name: test
image: alpine:latest
commands:
- echo "hello world"

steps:
- name: test
image: alpine:latest
commands:
- echo "hello world"
7 changes: 7 additions & 0 deletions schema/testdata/pipeline/pass/basic.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: "1"

steps:
- name: test
image: alpine:latest
commands:
- echo "hello world"
Loading

0 comments on commit d57b4eb

Please sign in to comment.