refactor: add more linters and fix findings (#65)

This commit is contained in:
Robert Kaussow 2023-02-08 10:14:20 +01:00 committed by GitHub
parent dabaa3fd18
commit 4cb5a900e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 485 additions and 173 deletions

View File

@ -8,7 +8,7 @@ platform:
steps: steps:
- name: deps - name: deps
image: golang:1.19 image: golang:1.20
commands: commands:
- make deps - make deps
volumes: volumes:
@ -16,7 +16,7 @@ steps:
path: /go path: /go
- name: lint - name: lint
image: golang:1.19 image: golang:1.20
commands: commands:
- make lint - make lint
volumes: volumes:
@ -24,7 +24,7 @@ steps:
path: /go path: /go
- name: test - name: test
image: golang:1.19 image: golang:1.20
commands: commands:
- make test - make test
volumes: volumes:
@ -51,7 +51,7 @@ platform:
steps: steps:
- name: build - name: build
image: techknowlogick/xgo:go-1.19.x image: techknowlogick/xgo:go-1.20.x
commands: commands:
- ln -s /drone/src /source - ln -s /drone/src /source
- make release - make release
@ -292,6 +292,6 @@ depends_on:
--- ---
kind: signature kind: signature
hmac: 236f7dcfcdbb18ed57935d336f25ec56c34074e872a94682c932422e59def005 hmac: 383e1b462823db3d79fb155de2bf3b53a4f4380ab036778552011c84fec23344
... ...

View File

@ -1,25 +1,92 @@
linters: linters:
enable:
- gosimple
- deadcode
- typecheck
- govet
- errcheck
- staticcheck
- unused
- structcheck
- varcheck
- dupl
- gofmt
- misspell
- gocritic
- bidichk
- ineffassign
- revive
- gofumpt
- depguard
enable-all: false enable-all: false
disable-all: true disable-all: true
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- typecheck
- unused
- asasalint
- asciicheck
- bidichk
- bodyclose
- containedctx
- contextcheck
- decorder
- depguard
- dogsled
- dupl
- dupword
- durationcheck
- errchkjson
- errname
- errorlint
- execinquery
- exhaustive
- exportloopref
- forcetypeassert
- ginkgolinter
- gocheckcompilerdirectives
- gochecknoglobals
- gochecknoinits
- gocognit
- goconst
- gocritic
- gocyclo
- godot
- godox
- goerr113
- gofmt
- gofumpt
- goheader
- goimports
- gomnd
- gomoddirectives
- gomodguard
- goprintffuncname
- gosec
- grouper
- importas
- interfacebloat
- ireturn
- lll
- loggercheck
- maintidx
- makezero
- misspell
- musttag
- nakedret
- nestif
- nilerr
- nilnil
- nlreturn
- noctx
- nolintlint
- nonamedreturns
- nosprintfhostport
- prealloc
- predeclared
- promlinter
- reassign
- revive
# - rowserrcheck
# - sqlclosecheck
# - structcheck
- stylecheck
- tagliatelle
- tenv
- testableexamples
- thelper
- tparallel
- unconvert
- unparam
- usestdlibvars
# - wastedassign
- whitespace
- wsl
fast: false fast: false
run: run:
@ -28,4 +95,10 @@ run:
linters-settings: linters-settings:
gofumpt: gofumpt:
extra-rules: true extra-rules: true
lang-version: "1.18" lang-version: "1.20"
tagliatelle:
case:
use-field-name: true
rules:
json: snake
yaml: snake

View File

@ -19,7 +19,7 @@ GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@$(G
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GENERATE ?= GENERATE ?=
XGO_VERSION := go-1.19.x XGO_VERSION := go-1.20.x
XGO_TARGETS ?= linux/amd64,linux/arm-6,linux/arm-7,linux/arm64 XGO_TARGETS ?= linux/amd64,linux/arm-6,linux/arm-7,linux/arm64
TARGETOS ?= linux TARGETOS ?= linux

View File

@ -15,6 +15,7 @@ import (
"gopkg.in/alecthomas/kingpin.v2" "gopkg.in/alecthomas/kingpin.v2"
) )
//nolint:gochecknoglobals
var ( var (
format = kingpin.Command("fmt", "format the yaml file") format = kingpin.Command("fmt", "format the yaml file")
formatSave = format.Flag("save", "save result to source").Short('s').Bool() formatSave = format.Flag("save", "save result to source").Short('s').Bool()
@ -25,6 +26,8 @@ var (
lintFile = lint.Arg("source", "source file location").Default(".drone.yml").File() lintFile = lint.Arg("source", "source file location").Default(".drone.yml").File()
) )
const DefaultFilePerm = 0o640
func main() { func main() {
switch kingpin.Parse() { switch kingpin.Parse() {
case format.FullCommand(): case format.FullCommand():
@ -36,6 +39,7 @@ func main() {
func runFormat() error { func runFormat() error {
f := *formatFile f := *formatFile
m, err := yaml.Parse(f) m, err := yaml.Parse(f)
if err != nil { if err != nil {
return err return err
@ -45,23 +49,28 @@ func runFormat() error {
pretty.Print(b, m) pretty.Print(b, m)
if *formatSave { if *formatSave {
return os.WriteFile(f.Name(), b.Bytes(), 0o644) return os.WriteFile(f.Name(), b.Bytes(), DefaultFilePerm)
} }
_, err = io.Copy(os.Stderr, b) _, err = io.Copy(os.Stderr, b)
return err return err
} }
func runLint() error { func runLint() error {
f := *lintFile f := *lintFile
m, err := yaml.Parse(f) m, err := yaml.Parse(f)
if err != nil { if err != nil {
return err return err
} }
for _, r := range m.Resources { for _, r := range m.Resources {
err := linter.Lint(r, *lintPriv) err := linter.Lint(r, *lintPriv)
if err != nil { if err != nil {
return err return err
} }
} }
return nil return nil
} }

2
go.mod
View File

@ -1,6 +1,6 @@
module github.com/drone/drone-yaml module github.com/drone/drone-yaml
go 1.19 go 1.20
require ( require (
github.com/bmatcuk/doublestar/v4 v4.6.0 github.com/bmatcuk/doublestar/v4 v4.6.0

View File

@ -29,15 +29,18 @@ type (
// UnmarshalYAML implements yaml unmarshalling. // UnmarshalYAML implements yaml unmarshalling.
func (b *Build) UnmarshalYAML(unmarshal func(interface{}) error) error { func (b *Build) UnmarshalYAML(unmarshal func(interface{}) error) error {
d := new(build) d := new(build)
err := unmarshal(&d.Image) err := unmarshal(&d.Image)
if err != nil { if err != nil {
err = unmarshal(d) err = unmarshal(d)
} }
b.Args = d.Args b.Args = d.Args
b.CacheFrom = d.CacheFrom b.CacheFrom = d.CacheFrom
b.Context = d.Context b.Context = d.Context
b.Dockerfile = d.Dockerfile b.Dockerfile = d.Dockerfile
b.Labels = d.Labels b.Labels = d.Labels
b.Image = d.Image b.Image = d.Image
return err return err
} }

View File

@ -32,12 +32,15 @@ func (c *Condition) Match(v string) bool {
if c.Excludes(v) { if c.Excludes(v) {
return false return false
} }
if c.Includes(v) { if c.Includes(v) {
return true return true
} }
if len(c.Include) == 0 { if len(c.Include) == 0 {
return true return true
} }
return false return false
} }
@ -49,6 +52,7 @@ func (c *Condition) Includes(v string) bool {
return true return true
} }
} }
return false return false
} }
@ -60,13 +64,17 @@ func (c *Condition) Excludes(v string) bool {
return true return true
} }
} }
return false return false
} }
// UnmarshalYAML implements yml unmarshalling. // UnmarshalYAML implements yml unmarshalling.
func (c *Condition) UnmarshalYAML(unmarshal func(interface{}) error) error { func (c *Condition) UnmarshalYAML(unmarshal func(interface{}) error) error {
var out1 string var (
var out2 []string out1 string
out2 []string
)
out3 := struct { out3 := struct {
Include []string Include []string
Exclude []string Exclude []string
@ -75,6 +83,7 @@ func (c *Condition) UnmarshalYAML(unmarshal func(interface{}) error) error {
err := unmarshal(&out1) err := unmarshal(&out1)
if err == nil { if err == nil {
c.Include = []string{out1} c.Include = []string{out1}
return nil return nil
} }

View File

@ -5,6 +5,8 @@ package yaml
import "errors" import "errors"
var ErrInvlaidCronBranch = errors.New("yaml: invalid cron branch")
type ( type (
// Cron is a resource that defines a cron job, used // Cron is a resource that defines a cron job, used
// to execute pipelines at scheduled intervals. // to execute pipelines at scheduled intervals.
@ -19,9 +21,9 @@ type (
// CronSpec defines the cron job. // CronSpec defines the cron job.
CronSpec struct { CronSpec struct {
Schedule string `json:"schedule,omitempty"` Schedule string `json:"schedule,omitempty"`
Branch string `json:"branch,omitempty"` Branch string `json:"branch,omitempty"`
Deploy CronDeployment `json:"deployment,omitempty" yaml:"deployment"` Deployment CronDeployment `json:"deployment,omitempty" yaml:"deployment"`
} }
// CronDeployment defines a cron job deployment. // CronDeployment defines a cron job deployment.
@ -40,7 +42,7 @@ func (c *Cron) GetKind() string { return c.Kind }
func (c Cron) Validate() error { func (c Cron) Validate() error {
switch { switch {
case c.Spec.Branch == "": case c.Spec.Branch == "":
return errors.New("yaml: invalid cron branch") return ErrInvlaidCronBranch
default: default:
return nil return nil
} }

View File

@ -8,39 +8,46 @@ type (
// can be defined as a string literal or as a reference // can be defined as a string literal or as a reference
// to a secret. // to a secret.
Variable struct { Variable struct {
Value string `json:"value,omitempty"` Value string `json:"value,omitempty"`
Secret string `json:"from_secret,omitempty" yaml:"from_secret"` FromSecret string `json:"from_secret,omitempty" yaml:"from_secret"`
} }
// variable is a tempoary type used to unmarshal // variable is a tempoary type used to unmarshal
// variables with references to secrets. // variables with references to secrets.
variable struct { variable struct {
Value string Value string
Secret string `yaml:"from_secret"` FromSecret string `yaml:"from_secret"`
} }
) )
// UnmarshalYAML implements yaml unmarshalling. // UnmarshalYAML implements yaml unmarshalling.
func (v *Variable) UnmarshalYAML(unmarshal func(interface{}) error) error { func (v *Variable) UnmarshalYAML(unmarshal func(interface{}) error) error {
d := new(variable) d := new(variable)
err := unmarshal(&d.Value) err := unmarshal(&d.Value)
if err != nil { if err != nil {
err = unmarshal(d) err = unmarshal(d)
} }
v.Value = d.Value v.Value = d.Value
v.Secret = d.Secret v.FromSecret = d.FromSecret
return err return err
} }
// MarshalYAML implements yaml marshalling. // MarshalYAML implements yaml marshalling.
func (v *Variable) MarshalYAML() (interface{}, error) { func (v *Variable) MarshalYAML() (interface{}, error) {
if v.Secret != "" { if v.FromSecret != "" {
m := map[string]interface{}{} m := map[string]interface{}{}
m["from_secret"] = v.Secret m["from_secret"] = v.FromSecret
return m, nil return m, nil
} }
if v.Value != "" { if v.Value != "" {
return v.Value, nil return v.Value, nil
} }
//nolint:nilnil
return nil, nil return nil, nil
} }

View File

@ -28,11 +28,12 @@ var ErrPipelineSelfDependency = errors.New("linter: pipeline cannot have a depen
// Manifest performs lint operations for a manifest. // Manifest performs lint operations for a manifest.
func Manifest(manifest *yaml.Manifest, trusted bool) error { func Manifest(manifest *yaml.Manifest, trusted bool) error {
return checkPipelines(manifest, trusted) return checkPipelines(manifest)
} }
func checkPipelines(manifest *yaml.Manifest, trusted bool) error { func checkPipelines(manifest *yaml.Manifest) error {
names := map[string]struct{}{} names := map[string]struct{}{}
for _, resource := range manifest.Resources { for _, resource := range manifest.Resources {
switch v := resource.(type) { switch v := resource.(type) {
case *yaml.Pipeline: case *yaml.Pipeline:
@ -40,11 +41,14 @@ func checkPipelines(manifest *yaml.Manifest, trusted bool) error {
if ok { if ok {
return ErrDuplicatePipelineName return ErrDuplicatePipelineName
} }
names[v.Name] = struct{}{} names[v.Name] = struct{}{}
err := checkPipelineDeps(v, names) err := checkPipelineDeps(v, names)
if err != nil { if err != nil {
return err return err
} }
if (v.Kind == "pipeline" || v.Kind == "") && (v.Type == "" || v.Type == "docker") { if (v.Kind == "pipeline" || v.Kind == "") && (v.Type == "" || v.Type == "docker") {
err = checkPlatform(v.Platform) err = checkPlatform(v.Platform)
if err != nil { if err != nil {
@ -55,6 +59,7 @@ func checkPipelines(manifest *yaml.Manifest, trusted bool) error {
continue continue
} }
} }
return nil return nil
} }
@ -64,9 +69,11 @@ func checkPipelineDeps(pipeline *yaml.Pipeline, deps map[string]struct{}) error
if !ok { if !ok {
return ErrMissingPipelineDependency return ErrMissingPipelineDependency
} }
if pipeline.Name == dep { if pipeline.Name == dep {
return ErrPipelineSelfDependency return ErrPipelineSelfDependency
} }
} }
return nil return nil
} }

View File

@ -10,29 +10,49 @@ import (
"github.com/drone/drone-yaml/yaml" "github.com/drone/drone-yaml/yaml"
) )
var os = map[string]struct{}{ //nolint:gochecknoglobals
"linux": {}, var (
"windows": {}, os = map[string]struct{}{
} "linux": {},
"windows": {},
}
arch = map[string]struct{}{
"arm": {},
"arm64": {},
"amd64": {},
}
)
var arch = map[string]struct{}{ var (
"arm": {}, // ErrDuplicateStepName is returned when two Pipeline steps
"arm64": {}, // have the same name.
"amd64": {}, ErrDuplicateStepName = errors.New("linter: duplicate step names")
}
// ErrDuplicateStepName is returned when two Pipeline steps // ErrMissingDependency is returned when a Pipeline step
// have the same name. // defines dependencies that are invalid or unknown.
var ErrDuplicateStepName = errors.New("linter: duplicate step names") ErrMissingDependency = errors.New("linter: invalid or unknown step dependency")
// ErrMissingDependency is returned when a Pipeline step // ErrCyclicalDependency is returned when a Pipeline step
// defines dependencies that are invalid or unknown. // defines a cyclical dependency, which would result in an
var ErrMissingDependency = errors.New("linter: invalid or unknown step dependency") // infinite execution loop.
ErrCyclicalDependency = errors.New("linter: cyclical step dependency detected")
// ErrCyclicalDependency is returned when a Pipeline step ErrUnsupportedOS = errors.New("linter: unsupported os")
// defines a cyclical dependency, which would result in an ErrUnsupportedArch = errors.New("linter: unsupported architecture")
// infinite execution loop. ErrInvalidImage = errors.New("linter: invalid or missing image")
var ErrCyclicalDependency = errors.New("linter: cyclical step dependency detected") ErrInvalidBuildImage = errors.New("linter: invalid or missing build image")
ErrInvalidName = errors.New("linter: invalid or missing name")
ErrPrivilegedNotAllowed = errors.New("linter: untrusted repositories cannot enable privileged mode")
ErrMountNotAllowed = errors.New("linter: untrusted repositories cannot mount devices")
ErrDNSNotAllowed = errors.New("linter: untrusted repositories cannot configure dns")
ErrDNSSearchNotAllowed = errors.New("linter: untrusted repositories cannot configure dns_search")
ErrExtraHostsNotAllowed = errors.New("linter: untrusted repositories cannot configure extra_hosts")
ErrNetworkModeNotAllowed = errors.New("linter: untrusted repositories cannot configure network_mode")
ErrInvalidVolumeName = errors.New("linter: invalid volume name")
ErrHostPortNotAllowed = errors.New("linter: untrusted repositories cannot map to a host port")
ErrHostVolumeNotAllowed = errors.New("linter: untrusted repositories cannot mount host volumes")
ErrTempVolumeNotAllowed = errors.New("linter: untrusted repositories cannot mount in-memory volumes")
)
// Lint performs lint operations for a resource. // Lint performs lint operations for a resource.
func Lint(resource yaml.Resource, trusted bool) error { func Lint(resource yaml.Resource, trusted bool) error {
@ -57,19 +77,23 @@ func checkPipeline(pipeline *yaml.Pipeline, trusted bool) error {
if err != nil { if err != nil {
return err return err
} }
err = checkPlatform(pipeline.Platform) err = checkPlatform(pipeline.Platform)
if err != nil { if err != nil {
return err return err
} }
names := map[string]struct{}{} names := map[string]struct{}{}
if !pipeline.Clone.Disable { if !pipeline.Clone.Disable {
names["clone"] = struct{}{} names["clone"] = struct{}{}
} }
for _, container := range pipeline.Steps { for _, container := range pipeline.Steps {
_, ok := names[container.Name] _, ok := names[container.Name]
if ok { if ok {
return ErrDuplicateStepName return ErrDuplicateStepName
} }
names[container.Name] = struct{}{} names[container.Name] = struct{}{}
err := checkContainer(container, trusted) err := checkContainer(container, trusted)
@ -82,11 +106,13 @@ func checkPipeline(pipeline *yaml.Pipeline, trusted bool) error {
return err return err
} }
} }
for _, container := range pipeline.Services { for _, container := range pipeline.Services {
_, ok := names[container.Name] _, ok := names[container.Name]
if ok { if ok {
return ErrDuplicateStepName return ErrDuplicateStepName
} }
names[container.Name] = struct{}{} names[container.Name] = struct{}{}
err := checkContainer(container, trusted) err := checkContainer(container, trusted)
@ -94,6 +120,7 @@ func checkPipeline(pipeline *yaml.Pipeline, trusted bool) error {
return err return err
} }
} }
return nil return nil
} }
@ -101,15 +128,17 @@ func checkPlatform(platform yaml.Platform) error {
if v := platform.OS; v != "" { if v := platform.OS; v != "" {
_, ok := os[v] _, ok := os[v]
if !ok { if !ok {
return fmt.Errorf("linter: unsupported os: %s", v) return fmt.Errorf("%w: %s", ErrUnsupportedOS, v)
} }
} }
if v := platform.Arch; v != "" { if v := platform.Arch; v != "" {
_, ok := arch[v] _, ok := arch[v]
if !ok { if !ok {
return fmt.Errorf("linter: unsupported architecture: %s", v) return fmt.Errorf("%w: %s", ErrUnsupportedArch, v)
} }
} }
return nil return nil
} }
@ -118,39 +147,50 @@ func checkContainer(container *yaml.Container, trusted bool) error {
if err != nil { if err != nil {
return err return err
} }
if container.Build == nil && container.Image == "" { if container.Build == nil && container.Image == "" {
return errors.New("linter: invalid or missing image") return ErrInvalidImage
} }
if container.Build != nil && container.Build.Image == "" { if container.Build != nil && container.Build.Image == "" {
return errors.New("linter: invalid or missing build image") return ErrInvalidBuildImage
} }
if container.Name == "" { if container.Name == "" {
return errors.New("linter: invalid or missing name") return ErrInvalidName
} }
if trusted && container.Privileged { if trusted && container.Privileged {
return errors.New("linter: untrusted repositories cannot enable privileged mode") return ErrPrivilegedNotAllowed
} }
if trusted && len(container.Devices) > 0 { if trusted && len(container.Devices) > 0 {
return errors.New("linter: untrusted repositories cannot mount devices") return ErrMountNotAllowed
} }
if trusted && len(container.DNS) > 0 { if trusted && len(container.DNS) > 0 {
return errors.New("linter: untrusted repositories cannot configure dns") return ErrDNSNotAllowed
} }
if trusted && len(container.DNSSearch) > 0 { if trusted && len(container.DNSSearch) > 0 {
return errors.New("linter: untrusted repositories cannot configure dns_search") return ErrDNSSearchNotAllowed
} }
if trusted && len(container.ExtraHosts) > 0 { if trusted && len(container.ExtraHosts) > 0 {
return errors.New("linter: untrusted repositories cannot configure extra_hosts") return ErrExtraHostsNotAllowed
} }
if trusted && len(container.Network) > 0 {
return errors.New("linter: untrusted repositories cannot configure network_mode") if trusted && len(container.NetworkMode) > 0 {
return ErrNetworkModeNotAllowed
} }
for _, mount := range container.Volumes { for _, mount := range container.Volumes {
switch mount.Name { switch mount.Name {
case "workspace", "_workspace", "_docker_socket": case "workspace", "_workspace", "_docker_socket":
return fmt.Errorf("linter: invalid volume name: %s", mount.Name) return fmt.Errorf("%w: %s", ErrInvalidVolumeName, mount.Name)
} }
} }
return nil return nil
} }
@ -161,49 +201,56 @@ func checkPorts(ports []*yaml.Port, trusted bool) error {
return err return err
} }
} }
return nil return nil
} }
func checkPort(port *yaml.Port, trusted bool) error { func checkPort(port *yaml.Port, trusted bool) error {
if trusted && port.Host != 0 { if trusted && port.Host != 0 {
return errors.New("linter: untrusted repositories cannot map to a host port") return ErrHostPortNotAllowed
} }
return nil return nil
} }
func checkVolumes(pipeline *yaml.Pipeline, trusted bool) error { func checkVolumes(pipeline *yaml.Pipeline, trusted bool) error {
for _, volume := range pipeline.Volumes { for _, volume := range pipeline.Volumes {
if volume.EmptyDir != nil { if volume.Temp != nil {
err := checkEmptyDirVolume(volume.EmptyDir, trusted) err := checkEmptyDirVolume(volume.Temp, trusted)
if err != nil { if err != nil {
return err return err
} }
} }
if volume.HostPath != nil {
err := checkHostPathVolume(volume.HostPath, trusted) if volume.Host != nil {
err := checkHostPathVolume(trusted)
if err != nil { if err != nil {
return err return err
} }
} }
switch volume.Name { switch volume.Name {
case "workspace", "_workspace", "_docker_socket": case "workspace", "_workspace", "_docker_socket":
return fmt.Errorf("linter: invalid volume name: %s", volume.Name) return fmt.Errorf("%w: %s", ErrInvalidVolumeName, volume.Name)
} }
} }
return nil return nil
} }
func checkHostPathVolume(volume *yaml.VolumeHostPath, trusted bool) error { func checkHostPathVolume(trusted bool) error {
if trusted { if trusted {
return errors.New("linter: untrusted repositories cannot mount host volumes") return ErrHostVolumeNotAllowed
} }
return nil return nil
} }
func checkEmptyDirVolume(volume *yaml.VolumeEmptyDir, trusted bool) error { func checkEmptyDirVolume(volume *yaml.VolumeEmptyDir, trusted bool) error {
if trusted && volume.Medium == "memory" { if trusted && volume.Medium == "memory" {
return errors.New("linter: untrusted repositories cannot mount in-memory volumes") return ErrTempVolumeNotAllowed
} }
return nil return nil
} }
@ -213,9 +260,11 @@ func checkDeps(container *yaml.Container, deps map[string]struct{}) error {
if !ok { if !ok {
return ErrMissingDependency return ErrMissingDependency
} }
if container.Name == dep { if container.Name == dep {
return ErrCyclicalDependency return ErrCyclicalDependency
} }
} }
return nil return nil
} }

View File

@ -20,6 +20,8 @@ const (
KindSignature = "signature" KindSignature = "signature"
) )
var ErrMarshalNotImplemented = errors.New("yaml: marshal not implemented")
type ( type (
// Manifest is a collection of Drone resources. // Manifest is a collection of Drone resources.
Manifest struct { Manifest struct {
@ -44,6 +46,7 @@ type (
Data []byte `yaml:"-"` Data []byte `yaml:"-"`
} }
//nolint:musttag
resource struct { resource struct {
Version string Version string
Kind string `json:"kind"` Kind string `json:"kind"`
@ -54,17 +57,22 @@ type (
// UnmarshalJSON implement the json.Unmarshaler. // UnmarshalJSON implement the json.Unmarshaler.
func (m *Manifest) UnmarshalJSON(b []byte) error { func (m *Manifest) UnmarshalJSON(b []byte) error {
messages := []json.RawMessage{} messages := []json.RawMessage{}
err := json.Unmarshal(b, &messages) err := json.Unmarshal(b, &messages)
if err != nil { if err != nil {
return err return err
} }
for _, message := range messages { for _, message := range messages {
res := new(resource) res := new(resource)
err := json.Unmarshal(message, res) err := json.Unmarshal(message, res)
if err != nil { if err != nil {
return err return err
} }
var obj Resource var obj Resource
switch res.Kind { switch res.Kind {
case "cron": case "cron":
obj = new(Cron) obj = new(Cron)
@ -77,12 +85,15 @@ func (m *Manifest) UnmarshalJSON(b []byte) error {
default: default:
obj = new(Pipeline) obj = new(Pipeline)
} }
err = json.Unmarshal(message, obj) err = json.Unmarshal(message, obj)
if err != nil { if err != nil {
return err return err
} }
m.Resources = append(m.Resources, obj) m.Resources = append(m.Resources, obj)
} }
return nil return nil
} }
@ -96,17 +107,19 @@ func (m *Manifest) MarshalJSON() ([]byte, error) {
// documents, and MarshalYAML would otherwise attempt to marshal // documents, and MarshalYAML would otherwise attempt to marshal
// as a single Yaml document. Use the Encode method instead. // as a single Yaml document. Use the Encode method instead.
func (m *Manifest) MarshalYAML() (interface{}, error) { func (m *Manifest) MarshalYAML() (interface{}, error) {
return nil, errors.New("yaml: marshal not implemented") return nil, ErrMarshalNotImplemented
} }
// Encode encodes the manifest in Yaml format. // Encode encodes the manifest in Yaml format.
func (m *Manifest) Encode() ([]byte, error) { func (m *Manifest) Encode() ([]byte, error) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
enc := yaml.NewEncoder(buf) enc := yaml.NewEncoder(buf)
for _, res := range m.Resources { for _, res := range m.Resources {
if err := enc.Encode(res); err != nil { if err := enc.Encode(res); err != nil {
return nil, err return nil, err
} }
} }
return buf.Bytes(), nil return buf.Bytes(), nil
} }

View File

@ -8,14 +8,14 @@ type (
// can be defined as a literal or as a reference // can be defined as a literal or as a reference
// to a secret. // to a secret.
Parameter struct { Parameter struct {
Value interface{} `json:"value,omitempty"` Value interface{} `json:"value,omitempty"`
Secret string `json:"from_secret,omitempty" yaml:"from_secret"` FromSecret string `json:"from_secret,omitempty" yaml:"from_secret"`
} }
// parameter is a tempoary type used to unmarshal // parameter is a tempoary type used to unmarshal
// parameters with references to secrets. // parameters with references to secrets.
parameter struct { parameter struct {
Secret string `yaml:"from_secret"` FromSecret string `yaml:"from_secret"`
} }
) )
@ -23,25 +23,33 @@ type (
func (p *Parameter) UnmarshalYAML(unmarshal func(interface{}) error) error { func (p *Parameter) UnmarshalYAML(unmarshal func(interface{}) error) error {
d := new(parameter) d := new(parameter)
err := unmarshal(d) err := unmarshal(d)
if err == nil && d.Secret != "" {
p.Secret = d.Secret if err == nil && d.FromSecret != "" {
p.FromSecret = d.FromSecret
return nil return nil
} }
var i interface{} var i interface{}
err = unmarshal(&i) err = unmarshal(&i)
p.Value = i p.Value = i
return err return err
} }
// MarshalYAML implements yaml marshalling. // MarshalYAML implements yaml marshalling.
func (p *Parameter) MarshalYAML() (interface{}, error) { func (p *Parameter) MarshalYAML() (interface{}, error) {
if p.Secret != "" { if p.FromSecret != "" {
m := map[string]interface{}{} m := map[string]interface{}{}
m["from_secret"] = p.Secret m["from_secret"] = p.FromSecret
return m, nil return m, nil
} }
if p.Value != "" { if p.Value != "" {
return p.Value, nil return p.Value, nil
} }
//nolint:nilnil
return nil, nil return nil, nil
} }

View File

@ -14,7 +14,7 @@ import (
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
var errorMissingKind = errors.New("yaml: missing kind attribute") var ErrMissingKind = errors.New("yaml: missing kind attribute")
// Parse parses the configuration from io.Reader r. // Parse parses the configuration from io.Reader r.
func Parse(r io.Reader) (*Manifest, error) { func Parse(r io.Reader) (*Manifest, error) {
@ -22,23 +22,29 @@ func Parse(r io.Reader) (*Manifest, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
manifest := new(Manifest) manifest := new(Manifest)
for _, raw := range resources { for _, raw := range resources {
if raw == nil { if raw == nil {
continue continue
} }
resource, err := parseRaw(raw) resource, err := parseRaw(raw)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if resource.GetKind() == "" { if resource.GetKind() == "" {
return nil, errorMissingKind return nil, ErrMissingKind
} }
manifest.Resources = append( manifest.Resources = append(
manifest.Resources, manifest.Resources,
resource, resource,
) )
} }
return manifest, nil return manifest, nil
} }
@ -63,11 +69,13 @@ func ParseFile(p string) (*Manifest, error) {
return nil, err return nil, err
} }
defer f.Close() defer f.Close()
return Parse(f) return Parse(f)
} }
func parseRaw(r *RawResource) (Resource, error) { func parseRaw(r *RawResource) (Resource, error) { //nolint:ireturn
var obj Resource var obj Resource
switch r.Kind { switch r.Kind {
case "cron": case "cron":
obj = new(Cron) obj = new(Cron)
@ -80,7 +88,9 @@ func parseRaw(r *RawResource) (Resource, error) {
default: default:
obj = new(Pipeline) obj = new(Pipeline)
} }
err := yaml.Unmarshal(r.Data, obj) err := yaml.Unmarshal(r.Data, obj)
return obj, err return obj, err
} }
@ -88,8 +98,11 @@ func parseRaw(r *RawResource) (Resource, error) {
// io.Reader and returns a slice of raw resources. // io.Reader and returns a slice of raw resources.
func ParseRaw(r io.Reader) ([]*RawResource, error) { func ParseRaw(r io.Reader) ([]*RawResource, error) {
const newline = '\n' const newline = '\n'
var resources []*RawResource
var resource *RawResource var (
resources []*RawResource
resource *RawResource
)
scanner := bufio.NewScanner(r) scanner := bufio.NewScanner(r)
for scanner.Scan() { for scanner.Scan() {
@ -97,34 +110,42 @@ func ParseRaw(r io.Reader) ([]*RawResource, error) {
if isSeparator(line) { if isSeparator(line) {
resource = nil resource = nil
} }
if resource == nil { if resource == nil {
resource = &RawResource{} resource = &RawResource{}
resources = append(resources, resource) resources = append(resources, resource)
} }
if isSeparator(line) { if isSeparator(line) {
continue continue
} }
if isTerminator(line) { if isTerminator(line) {
break break
} }
if scanner.Err() == io.EOF {
if errors.Is(scanner.Err(), io.EOF) {
break break
} }
resource.Data = append( resource.Data = append(
resource.Data, resource.Data,
line..., line...,
) )
resource.Data = append( resource.Data = append(
resource.Data, resource.Data,
newline, newline,
) )
} }
for _, resource := range resources { for _, resource := range resources {
err := yaml.Unmarshal(resource.Data, resource) err := yaml.Unmarshal(resource.Data, resource)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
return resources, nil return resources, nil
} }
@ -152,6 +173,7 @@ func ParseRawFile(p string) ([]*RawResource, error) {
return nil, err return nil, err
} }
defer f.Close() defer f.Close()
return ParseRaw(f) return ParseRaw(f)
} }

View File

@ -11,17 +11,17 @@ type Pipeline struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Clone Clone `json:"clone,omitempty"` Clone Clone `json:"clone,omitempty"`
Concurrency Concurrency `json:"concurrency,omitempty"` Concurrency Concurrency `json:"concurrency,omitempty"`
DependsOn []string `json:"depends_on,omitempty" yaml:"depends_on" ` DependsOn []string `json:"depends_on,omitempty" yaml:"depends_on" `
Node map[string]string `json:"node,omitempty" yaml:"node"` Node map[string]string `json:"node,omitempty" yaml:"node"`
Platform Platform `json:"platform,omitempty"` Platform Platform `json:"platform,omitempty"`
PullSecrets []string `json:"image_pull_secrets,omitempty" yaml:"image_pull_secrets"` ImagePullSecrets []string `json:"image_pull_secrets,omitempty" yaml:"image_pull_secrets"`
Services []*Container `json:"services,omitempty"` Services []*Container `json:"services,omitempty"`
Steps []*Container `json:"steps,omitempty"` Steps []*Container `json:"steps,omitempty"`
Trigger Conditions `json:"trigger,omitempty"` Trigger Conditions `json:"trigger,omitempty"`
Volumes []*Volume `json:"volumes,omitempty"` Volumes []*Volume `json:"volumes,omitempty"`
Workspace Workspace `json:"workspace,omitempty"` Workspace Workspace `json:"workspace,omitempty"`
} }
// GetVersion returns the resource version. // GetVersion returns the resource version.
@ -58,7 +58,7 @@ type (
ExtraHosts []string `json:"extra_hosts,omitempty" yaml:"extra_hosts"` ExtraHosts []string `json:"extra_hosts,omitempty" yaml:"extra_hosts"`
Failure string `json:"failure,omitempty"` Failure string `json:"failure,omitempty"`
Image string `json:"image,omitempty"` Image string `json:"image,omitempty"`
Network string `json:"network_mode,omitempty" yaml:"network_mode"` NetworkMode string `json:"network_mode,omitempty" yaml:"network_mode"`
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Ports []*Port `json:"ports,omitempty"` Ports []*Port `json:"ports,omitempty"`
Privileged bool `json:"privileged,omitempty"` Privileged bool `json:"privileged,omitempty"`
@ -102,23 +102,23 @@ type (
// Volume that can be mounted by containers. // Volume that can be mounted by containers.
Volume struct { Volume struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
EmptyDir *VolumeEmptyDir `json:"temp,omitempty" yaml:"temp"` Temp *VolumeEmptyDir `json:"temp,omitempty" yaml:"temp"`
HostPath *VolumeHostPath `json:"host,omitempty" yaml:"host"` Host *VolumeHostPath `json:"host,omitempty" yaml:"host"`
} }
// VolumeDevice describes a mapping of a raw block // VolumeDevice describes a mapping of a raw block
// device within a container. // device within a container.
VolumeDevice struct { VolumeDevice struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
DevicePath string `json:"path,omitempty" yaml:"path"` Path string `json:"path,omitempty" yaml:"path"`
} }
// VolumeMount describes a mounting of a Volume // VolumeMount describes a mounting of a Volume
// within a container. // within a container.
VolumeMount struct { VolumeMount struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
MountPath string `json:"path,omitempty" yaml:"path"` Path string `json:"path,omitempty" yaml:"path"`
} }
// VolumeEmptyDir mounts a temporary directory from the // VolumeEmptyDir mounts a temporary directory from the

View File

@ -22,12 +22,15 @@ type (
// UnmarshalYAML implements yaml unmarshalling. // UnmarshalYAML implements yaml unmarshalling.
func (p *Port) UnmarshalYAML(unmarshal func(interface{}) error) error { func (p *Port) UnmarshalYAML(unmarshal func(interface{}) error) error {
out := new(port) out := new(port)
err := unmarshal(&out.Port) err := unmarshal(&out.Port)
if err != nil { if err != nil {
err = unmarshal(&out) err = unmarshal(&out)
} }
p.Port = out.Port p.Port = out.Port
p.Host = out.Host p.Host = out.Host
p.Protocol = out.Protocol p.Protocol = out.Protocol
return err return err
} }

View File

@ -19,6 +19,7 @@ func printContainer(w writer, v *yaml.Container) {
if v.Build != nil { if v.Build != nil {
printBuild(w, v.Build) printBuild(w, v.Build)
} }
if v.Push != nil { if v.Push != nil {
w.WriteTagValue("push", v.Push.Image) w.WriteTagValue("push", v.Push.Image)
} }
@ -32,7 +33,7 @@ func printContainer(w writer, v *yaml.Container) {
w.WriteTagValue("dns", v.DNS) w.WriteTagValue("dns", v.DNS)
w.WriteTagValue("dns_search", v.DNSSearch) w.WriteTagValue("dns_search", v.DNSSearch)
w.WriteTagValue("extra_hosts", v.ExtraHosts) w.WriteTagValue("extra_hosts", v.ExtraHosts)
w.WriteTagValue("network_mode", v.Network) w.WriteTagValue("network_mode", v.NetworkMode)
if len(v.Settings) > 0 { if len(v.Settings) > 0 {
printSettings(w, v.Settings) printSettings(w, v.Settings)
@ -49,21 +50,27 @@ func printContainer(w writer, v *yaml.Container) {
if len(v.Devices) > 0 { if len(v.Devices) > 0 {
printDeviceMounts(w, v.Devices) printDeviceMounts(w, v.Devices)
} }
if len(v.Ports) > 0 { if len(v.Ports) > 0 {
printPorts(w, v.Ports) printPorts(w, v.Ports)
} }
if v.Resources != nil { if v.Resources != nil {
printResources(w, v.Resources) printResources(w, v.Resources)
} }
if len(v.Volumes) > 0 { if len(v.Volumes) > 0 {
printVolumeMounts(w, v.Volumes) printVolumeMounts(w, v.Volumes)
} }
if !isConditionsEmpty(v.When) { if !isConditionsEmpty(v.When) {
printConditions(w, "when", v.When) printConditions(w, "when", v.When)
} }
if len(v.DependsOn) > 0 { if len(v.DependsOn) > 0 {
printDependsOn(w, v.DependsOn) printDependsOn(w, v.DependsOn)
} }
_ = w.WriteByte('\n') _ = w.WriteByte('\n')
w.IndentDecrease() w.IndentDecrease()
} }
@ -93,43 +100,49 @@ func printDependsOn(w writer, v []string) {
// helper function pretty prints the device sequence. // helper function pretty prints the device sequence.
func printDeviceMounts(w writer, v []*yaml.VolumeDevice) { func printDeviceMounts(w writer, v []*yaml.VolumeDevice) {
w.WriteTag("devices") w.WriteTag("devices")
for _, v := range v { for _, v := range v {
s := new(indexWriter) s := new(indexWriter)
s.writer = w s.writer = w
s.IndentIncrease() s.IndentIncrease()
s.WriteTagValue("name", v.Name) s.WriteTagValue("name", v.Name)
s.WriteTagValue("path", v.DevicePath) s.WriteTagValue("path", v.Path)
s.IndentDecrease() s.IndentDecrease()
} }
} }
// helper function pretty prints the environment mapping. // helper function pretty prints the environment mapping.
func printEnviron(w writer, v map[string]*yaml.Variable) { func printEnviron(w writer, v map[string]*yaml.Variable) {
var keys []string keys := make([]string, 0)
for k := range v { for k := range v {
keys = append(keys, k) keys = append(keys, k)
} }
sort.Strings(keys) sort.Strings(keys)
w.WriteTag("environment") w.WriteTag("environment")
w.IndentIncrease() w.IndentIncrease()
for _, k := range keys { for _, k := range keys {
v := v[k] v := v[k]
if v.Secret == "" { if v.FromSecret == "" {
w.WriteTagValue(k, v.Value) w.WriteTagValue(k, v.Value)
} else { } else {
w.WriteTag(k) w.WriteTag(k)
w.IndentIncrease() w.IndentIncrease()
w.WriteTagValue("from_secret", v.Secret) w.WriteTagValue("from_secret", v.FromSecret)
w.IndentDecrease() w.IndentDecrease()
} }
} }
w.IndentDecrease() w.IndentDecrease()
} }
// helper function pretty prints the port sequence. // helper function pretty prints the port sequence.
func printPorts(w writer, v []*yaml.Port) { func printPorts(w writer, v []*yaml.Port) {
w.WriteTag("ports") w.WriteTag("ports")
for _, v := range v { for _, v := range v {
if shortPort(v) { if shortPort(v) {
_ = w.WriteByte('\n') _ = w.WriteByte('\n')
@ -137,6 +150,7 @@ func printPorts(w writer, v []*yaml.Port) {
_ = w.WriteByte('-') _ = w.WriteByte('-')
_ = w.WriteByte(' ') _ = w.WriteByte(' ')
writeInt(w, v.Port) writeInt(w, v.Port)
continue continue
} }
@ -162,6 +176,7 @@ func printResources(w writer, v *yaml.Resources) {
w.WriteTagValue("memory", v.Limits.Memory) w.WriteTagValue("memory", v.Limits.Memory)
w.IndentDecrease() w.IndentDecrease()
} }
if v.Requests != nil { if v.Requests != nil {
w.WriteTag("requests") w.WriteTag("requests")
w.IndentIncrease() w.IndentIncrease()
@ -169,38 +184,44 @@ func printResources(w writer, v *yaml.Resources) {
w.WriteTagValue("memory", v.Requests.Memory) w.WriteTagValue("memory", v.Requests.Memory)
w.IndentDecrease() w.IndentDecrease()
} }
w.IndentDecrease() w.IndentDecrease()
} }
// helper function pretty prints the resoure mapping. // helper function pretty prints the resoure mapping.
func printSettings(w writer, v map[string]*yaml.Parameter) { func printSettings(w writer, v map[string]*yaml.Parameter) {
var keys []string keys := make([]string, 0)
for k := range v { for k := range v {
keys = append(keys, k) keys = append(keys, k)
} }
sort.Strings(keys) sort.Strings(keys)
w.WriteTag("settings") w.WriteTag("settings")
w.IndentIncrease() w.IndentIncrease()
for _, k := range keys { for _, k := range keys {
v := v[k] v := v[k]
if v.Secret == "" { if v.FromSecret == "" {
w.IncludeZero() w.IncludeZero()
w.WriteTagValue(k, v.Value) w.WriteTagValue(k, v.Value)
w.ExcludeZero() w.ExcludeZero()
} else { } else {
w.WriteTag(k) w.WriteTag(k)
w.IndentIncrease() w.IndentIncrease()
w.WriteTagValue("from_secret", v.Secret) w.WriteTagValue("from_secret", v.FromSecret)
w.IndentDecrease() w.IndentDecrease()
} }
} }
w.IndentDecrease() w.IndentDecrease()
} }
// helper function pretty prints the volume sequence. // helper function pretty prints the volume sequence.
func printVolumeMounts(w writer, v []*yaml.VolumeMount) { func printVolumeMounts(w writer, v []*yaml.VolumeMount) {
w.WriteTag("volumes") w.WriteTag("volumes")
for _, v := range v { for _, v := range v {
s := new(indexWriter) s := new(indexWriter)
s.writer = w s.writer = w
@ -208,7 +229,7 @@ func printVolumeMounts(w writer, v []*yaml.VolumeMount) {
s.IndentIncrease() s.IndentIncrease()
s.WriteTagValue("name", v.Name) s.WriteTagValue("name", v.Name)
s.WriteTagValue("path", v.MountPath) s.WriteTagValue("path", v.Path)
s.IndentDecrease() s.IndentDecrease()
w.IndentDecrease() w.IndentDecrease()

View File

@ -23,9 +23,11 @@ func printSpec(w writer, v *yaml.Cron) {
w.IndentIncrease() w.IndentIncrease()
w.WriteTagValue("schedule", v.Spec.Schedule) w.WriteTagValue("schedule", v.Spec.Schedule)
w.WriteTagValue("branch", v.Spec.Branch) w.WriteTagValue("branch", v.Spec.Branch)
if hasDeployment(v) { if hasDeployment(v) {
printDeploy(w, v) printDeploy(w, v)
} }
w.IndentDecrease() w.IndentDecrease()
} }
@ -33,12 +35,12 @@ func printSpec(w writer, v *yaml.Cron) {
func printDeploy(w writer, v *yaml.Cron) { func printDeploy(w writer, v *yaml.Cron) {
w.WriteTag("deployment") w.WriteTag("deployment")
w.IndentIncrease() w.IndentIncrease()
w.WriteTagValue("target", v.Spec.Deploy.Target) w.WriteTagValue("target", v.Spec.Deployment.Target)
w.IndentDecrease() w.IndentDecrease()
} }
// helper function returns true if the deployment // helper function returns true if the deployment
// object is empty. // object is empty.
func hasDeployment(v *yaml.Cron) bool { func hasDeployment(v *yaml.Cron) bool {
return v.Spec.Deploy.Target != "" return v.Spec.Deployment.Target != ""
} }

View File

@ -21,22 +21,27 @@ func printPipeline(w writer, v *yaml.Pipeline) {
} else { } else {
printPlatformDefault(w) printPlatformDefault(w)
} }
if !isCloneEmpty(v.Clone) { if !isCloneEmpty(v.Clone) {
printClone(w, v.Clone) printClone(w, v.Clone)
} }
if !isConcurrencyEmpty(v.Concurrency) { if !isConcurrencyEmpty(v.Concurrency) {
printConcurrency(w, v.Concurrency) printConcurrency(w, v.Concurrency)
} }
if !isWorkspaceEmpty(v.Workspace) { if !isWorkspaceEmpty(v.Workspace) {
printWorkspace(w, v.Workspace) printWorkspace(w, v.Workspace)
} }
if len(v.Steps) > 0 { if len(v.Steps) > 0 {
w.WriteTag("steps") w.WriteTag("steps")
for _, step := range v.Steps { for _, step := range v.Steps {
if step == nil { if step == nil {
continue continue
} }
seq := new(indexWriter) seq := new(indexWriter)
seq.writer = w seq.writer = w
seq.IndentIncrease() seq.IndentIncrease()
@ -47,10 +52,12 @@ func printPipeline(w writer, v *yaml.Pipeline) {
if len(v.Services) > 0 { if len(v.Services) > 0 {
w.WriteTag("services") w.WriteTag("services")
for _, step := range v.Services { for _, step := range v.Services {
if step == nil { if step == nil {
continue continue
} }
seq := new(indexWriter) seq := new(indexWriter)
seq.writer = w seq.writer = w
seq.IndentIncrease() seq.IndentIncrease()
@ -64,8 +71,8 @@ func printPipeline(w writer, v *yaml.Pipeline) {
_ = w.WriteByte('\n') _ = w.WriteByte('\n')
} }
if len(v.PullSecrets) > 0 { if len(v.ImagePullSecrets) > 0 {
w.WriteTagValue("image_pull_secrets", v.PullSecrets) w.WriteTagValue("image_pull_secrets", v.ImagePullSecrets)
_ = w.WriteByte('\n') _ = w.WriteByte('\n')
} }
@ -111,42 +118,54 @@ func printConcurrency(w writer, v yaml.Concurrency) {
func printConditions(w writer, name string, v yaml.Conditions) { func printConditions(w writer, name string, v yaml.Conditions) {
w.WriteTag(name) w.WriteTag(name)
w.IndentIncrease() w.IndentIncrease()
if !isConditionEmpty(v.Action) { if !isConditionEmpty(v.Action) {
printCondition(w, "action", v.Action) printCondition(w, "action", v.Action)
} }
if !isConditionEmpty(v.Branch) { if !isConditionEmpty(v.Branch) {
printCondition(w, "branch", v.Branch) printCondition(w, "branch", v.Branch)
} }
if !isConditionEmpty(v.Cron) { if !isConditionEmpty(v.Cron) {
printCondition(w, "cron", v.Cron) printCondition(w, "cron", v.Cron)
} }
if !isConditionEmpty(v.Event) { if !isConditionEmpty(v.Event) {
printCondition(w, "event", v.Event) printCondition(w, "event", v.Event)
} }
if !isConditionEmpty(v.Instance) { if !isConditionEmpty(v.Instance) {
printCondition(w, "instance", v.Instance) printCondition(w, "instance", v.Instance)
} }
if !isConditionEmpty(v.Paths) { if !isConditionEmpty(v.Paths) {
printCondition(w, "paths", v.Paths) printCondition(w, "paths", v.Paths)
} }
if !isConditionEmpty(v.Ref) { if !isConditionEmpty(v.Ref) {
printCondition(w, "ref", v.Ref) printCondition(w, "ref", v.Ref)
} }
if !isConditionEmpty(v.Repo) { if !isConditionEmpty(v.Repo) {
printCondition(w, "repo", v.Repo) printCondition(w, "repo", v.Repo)
} }
if !isConditionEmpty(v.Status) { if !isConditionEmpty(v.Status) {
printCondition(w, "status", v.Status) printCondition(w, "status", v.Status)
} }
if !isConditionEmpty(v.Target) { if !isConditionEmpty(v.Target) {
printCondition(w, "target", v.Target) printCondition(w, "target", v.Target)
} }
w.IndentDecrease() w.IndentDecrease()
} }
// helper function pretty prints a condition mapping. // helper function pretty prints a condition mapping.
func printCondition(w writer, k string, v yaml.Condition) { func printCondition(w writer, k string, v yaml.Condition) {
w.WriteTag(k) w.WriteTag(k)
if len(v.Include) != 0 && len(v.Exclude) == 0 { if len(v.Include) != 0 && len(v.Exclude) == 0 {
_ = w.WriteByte('\n') _ = w.WriteByte('\n')
w.IndentIncrease() w.IndentIncrease()
@ -154,11 +173,13 @@ func printCondition(w writer, k string, v yaml.Condition) {
writeValue(w, v.Include) writeValue(w, v.Include)
w.IndentDecrease() w.IndentDecrease()
} }
if len(v.Include) != 0 && len(v.Exclude) != 0 { if len(v.Include) != 0 && len(v.Exclude) != 0 {
w.IndentIncrease() w.IndentIncrease()
w.WriteTagValue("include", v.Include) w.WriteTagValue("include", v.Include)
w.IndentDecrease() w.IndentDecrease()
} }
if len(v.Exclude) != 0 { if len(v.Exclude) != 0 {
w.IndentIncrease() w.IndentIncrease()
w.WriteTagValue("exclude", v.Exclude) w.WriteTagValue("exclude", v.Exclude)
@ -197,6 +218,7 @@ func printPlatformDefault(w writer) {
// helper function pretty prints the volume sequence. // helper function pretty prints the volume sequence.
func printVolumes(w writer, v []*yaml.Volume) { func printVolumes(w writer, v []*yaml.Volume) {
w.WriteTag("volumes") w.WriteTag("volumes")
for _, v := range v { for _, v := range v {
s := new(indexWriter) s := new(indexWriter)
s.writer = w s.writer = w
@ -204,8 +226,10 @@ func printVolumes(w writer, v []*yaml.Volume) {
s.IndentIncrease() s.IndentIncrease()
s.WriteTagValue("name", v.Name) s.WriteTagValue("name", v.Name)
if v := v.EmptyDir; v != nil {
if v := v.Temp; v != nil {
s.WriteTag("temp") s.WriteTag("temp")
if isEmptyDirEmpty(v) { if isEmptyDirEmpty(v) {
_ = w.WriteByte(' ') _ = w.WriteByte(' ')
_ = w.WriteByte('{') _ = w.WriteByte('{')
@ -218,7 +242,7 @@ func printVolumes(w writer, v []*yaml.Volume) {
} }
} }
if v := v.HostPath; v != nil { if v := v.Host; v != nil {
s.WriteTag("host") s.WriteTag("host")
s.IndentIncrease() s.IndentIncrease()
s.WriteTagValue("path", v.Path) s.WriteTagValue("path", v.Path)

View File

@ -12,6 +12,7 @@ import (
// Print pretty prints the manifest. // Print pretty prints the manifest.
func Print(w io.Writer, v *yaml.Manifest) { func Print(w io.Writer, v *yaml.Manifest) {
state := new(baseWriter) state := new(baseWriter)
for _, r := range v.Resources { for _, r := range v.Resources {
switch t := r.(type) { switch t := r.(type) {
case *yaml.Cron: case *yaml.Cron:
@ -24,6 +25,7 @@ func Print(w io.Writer, v *yaml.Manifest) {
printPipeline(state, t) printPipeline(state, t)
} }
} }
state.WriteString("...") state.WriteString("...")
state.WriteByte('\n') state.WriteByte('\n')
_, _ = w.Write(state.Bytes()) _, _ = w.Write(state.Bytes())

View File

@ -9,8 +9,6 @@ import (
"github.com/drone/drone-yaml/yaml" "github.com/drone/drone-yaml/yaml"
) )
// TODO consider "!!binary |" for secret value
// helper function to pretty prints the signature resource. // helper function to pretty prints the signature resource.
func printSecret(w writer, v *yaml.Secret) { func printSecret(w writer, v *yaml.Secret) {
_, _ = w.WriteString("---") _, _ = w.WriteString("---")
@ -22,11 +20,13 @@ func printSecret(w writer, v *yaml.Secret) {
w.WriteTagValue("name", v.Name) w.WriteTagValue("name", v.Name)
printData(w, v.Data) printData(w, v.Data)
} }
if !isSecretGetEmpty(v.Get) { if !isSecretGetEmpty(v.Get) {
w.WriteTagValue("name", v.Name) w.WriteTagValue("name", v.Name)
_ = w.WriteByte('\n') _ = w.WriteByte('\n')
printGet(w, v.Get) printGet(w, v.Get)
} }
_ = w.WriteByte('\n') _ = w.WriteByte('\n')
_ = w.WriteByte('\n') _ = w.WriteByte('\n')
} }
@ -42,22 +42,24 @@ func printGet(w writer, v yaml.SecretGet) {
} }
func printData(w writer, d string) { func printData(w writer, d string) {
spaceReplacer := strings.NewReplacer(" ", "", "\n", "")
w.WriteTag("data") w.WriteTag("data")
_ = w.WriteByte(' ') _ = w.WriteByte(' ')
_ = w.WriteByte('>') _ = w.WriteByte('>')
w.IndentIncrease() w.IndentIncrease()
d = spaceReplacer.Replace(d) d = spaceReplacer.Replace(d)
//nolint:gomnd
for _, s := range chunk(d, 60) { for _, s := range chunk(d, 60) {
_ = w.WriteByte('\n') _ = w.WriteByte('\n')
w.Indent() w.Indent()
_, _ = w.WriteString(s) _, _ = w.WriteString(s)
} }
w.IndentDecrease() w.IndentDecrease()
} }
// replace spaces and newlines.
var spaceReplacer = strings.NewReplacer(" ", "", "\n", "")
// helper function returns true if the secret get // helper function returns true if the secret get
// object is empty. // object is empty.
func isSecretGetEmpty(v yaml.SecretGet) bool { func isSecretGetEmpty(v yaml.SecretGet) bool {

View File

@ -72,6 +72,7 @@ func isQuoted(s string) bool {
} }
var r0, r1 byte var r0, r1 byte
t := strings.TrimSpace(s) t := strings.TrimSpace(s)
// if the trimmed string does not match the string, it // if the trimmed string does not match the string, it
@ -84,6 +85,7 @@ func isQuoted(s string) bool {
if len(t) > 0 { if len(t) > 0 {
r0 = t[0] r0 = t[0]
} }
if len(t) > 1 { if len(t) > 1 {
r1 = t[1] r1 = t[1]
} }
@ -103,6 +105,7 @@ func isQuoted(s string) bool {
} }
var prev rune var prev rune
for _, b := range s { for _, b := range s {
switch { switch {
case isEscapeCode(b): case isEscapeCode(b):
@ -112,6 +115,7 @@ func isQuoted(s string) bool {
case b == '#' && prev == ' ': case b == '#' && prev == ' ':
return true return true
} }
prev = b prev = b
} }
@ -124,13 +128,17 @@ func chunk(s string, chunkSize int) []string {
if len(s) == 0 { if len(s) == 0 {
return []string{s} return []string{s}
} }
var chunks []string var chunks []string
for i := 0; i < len(s); i += chunkSize { for i := 0; i < len(s); i += chunkSize {
nn := i + chunkSize nn := i + chunkSize
if nn > len(s) { if nn > len(s) {
nn = len(s) nn = len(s)
} }
chunks = append(chunks, s[i:nn]) chunks = append(chunks, s[i:nn])
} }
return chunks return chunks
} }

View File

@ -71,6 +71,7 @@ func TestQuoted(t *testing.T) {
writeEncode(buf, test.before) writeEncode(buf, test.before)
a := test.after a := test.after
b := buf.String() b := buf.String()
if b != a { if b != a {
t.Errorf("Want %q, got %q", a, b) t.Errorf("Want %q, got %q", a, b)
} }
@ -78,16 +79,17 @@ func TestQuoted(t *testing.T) {
} }
func TestChunk(t *testing.T) { func TestChunk(t *testing.T) {
testChunk := []string{
"ZDllMjFjZDg3Zjk0ZWFjZDRhMjdhMTA1ZDQ1OTVkYTA1ODBjMTk0ZWVlZjQyNmU4",
"N2RiNTIwZjg0NWQwYjcyYjE3MmFmZDIyYzg3NTQ1N2YyYzgxODhjYjJmNDhhOTFj",
"ZjdhMzA0YjEzYWFlMmYxMTIwMmEyM2Q1YjQ5Yjg2ZmMK",
}
s := strings.Join(testChunk, "") s := strings.Join(testChunk, "")
got, want := chunk(s, 64), testChunk got, want := chunk(s, 64), testChunk
if diff := cmp.Diff(got, want); diff != "" { if diff := cmp.Diff(got, want); diff != "" {
t.Errorf("Unexpected chunk value") t.Errorf("Unexpected chunk value")
t.Log(diff) t.Log(diff)
} }
} }
var testChunk = []string{
"ZDllMjFjZDg3Zjk0ZWFjZDRhMjdhMTA1ZDQ1OTVkYTA1ODBjMTk0ZWVlZjQyNmU4",
"N2RiNTIwZjg0NWQwYjcyYjE3MmFmZDIyYzg3NTQ1N2YyYzgxODhjYjJmNDhhOTFj",
"ZjdhMzA0YjEzYWFlMmYxMTIwMmEyM2Q1YjQ5Yjg2ZmMK",
}

View File

@ -12,9 +12,6 @@ import (
"github.com/drone/drone-yaml/yaml" "github.com/drone/drone-yaml/yaml"
) )
// TODO rename WriteTag to WriteKey
// TODO rename WriteTagValue to WriteKeyValue
// ESCAPING: // ESCAPING:
// //
// The string starts with a special character: // The string starts with a special character:
@ -102,7 +99,9 @@ func (w *baseWriter) WriteTagValue(k, v interface{}) {
if isZero(v) && !w.zero { if isZero(v) && !w.zero {
return return
} }
w.WriteTag(k) w.WriteTag(k)
switch { switch {
case isPrimative(v): case isPrimative(v):
_ = w.WriteByte(' ') _ = w.WriteByte(' ')
@ -142,6 +141,7 @@ func (w *indexWriter) ExcludeZero() {
func (w *indexWriter) WriteTag(v interface{}) { func (w *indexWriter) WriteTag(v interface{}) {
_ = w.WriteByte('\n') _ = w.WriteByte('\n')
if w.index == 0 { if w.index == 0 {
w.IndentDecrease() w.IndentDecrease()
w.Indent() w.Indent()
@ -151,6 +151,7 @@ func (w *indexWriter) WriteTag(v interface{}) {
} else { } else {
w.Indent() w.Indent()
} }
writeValue(w, v) writeValue(w, v)
_ = w.WriteByte(':') _ = w.WriteByte(':')
w.index++ w.index++
@ -160,7 +161,9 @@ func (w *indexWriter) WriteTagValue(k, v interface{}) {
if isZero(v) && !w.zero { if isZero(v) && !w.zero {
return return
} }
w.WriteTag(k) w.WriteTag(k)
switch { switch {
case isPrimative(v): case isPrimative(v):
_ = w.WriteByte(' ') _ = w.WriteByte(' ')
@ -212,8 +215,10 @@ func writeEncode(w writer, v string) {
if len(v) == 0 { if len(v) == 0 {
_ = w.WriteByte('"') _ = w.WriteByte('"')
_ = w.WriteByte('"') _ = w.WriteByte('"')
return return
} }
if isQuoted(v) { if isQuoted(v) {
fmt.Fprintf(w, "%q", v) fmt.Fprintf(w, "%q", v)
} else { } else {
@ -224,8 +229,10 @@ func writeEncode(w writer, v string) {
func writeValue(w writer, v interface{}) { func writeValue(w writer, v interface{}) {
if v == nil { if v == nil {
_ = w.WriteByte('~') _ = w.WriteByte('~')
return return
} }
switch v := v.(type) { switch v := v.(type) {
case bool, int, int64, float64, string: case bool, int, int64, float64, string:
writeScalar(w, v) writeScalar(w, v)
@ -261,13 +268,16 @@ func writeSequence(w writer, v []interface{}) {
if len(v) == 0 { if len(v) == 0 {
_ = w.WriteByte('[') _ = w.WriteByte('[')
_ = w.WriteByte(']') _ = w.WriteByte(']')
return return
} }
for i, v := range v { for i, v := range v {
if i != 0 { if i != 0 {
_ = w.WriteByte('\n') _ = w.WriteByte('\n')
w.Indent() w.Indent()
} }
_ = w.WriteByte('-') _ = w.WriteByte('-')
_ = w.WriteByte(' ') _ = w.WriteByte(' ')
w.IndentIncrease() w.IndentIncrease()
@ -280,13 +290,16 @@ func writeSequenceStr(w writer, v []string) {
if len(v) == 0 { if len(v) == 0 {
_ = w.WriteByte('[') _ = w.WriteByte('[')
_ = w.WriteByte(']') _ = w.WriteByte(']')
return return
} }
for i, v := range v { for i, v := range v {
if i != 0 { if i != 0 {
_ = w.WriteByte('\n') _ = w.WriteByte('\n')
w.Indent() w.Indent()
} }
_ = w.WriteByte('-') _ = w.WriteByte('-')
_ = w.WriteByte(' ') _ = w.WriteByte(' ')
writeEncode(w, v) writeEncode(w, v)
@ -297,22 +310,30 @@ func writeMapping(w writer, v map[interface{}]interface{}) {
if len(v) == 0 { if len(v) == 0 {
_ = w.WriteByte('{') _ = w.WriteByte('{')
_ = w.WriteByte('}') _ = w.WriteByte('}')
return return
} }
var keys []string
keys := make([]string, 0)
for k := range v { for k := range v {
s := fmt.Sprint(k) s := fmt.Sprint(k)
keys = append(keys, s) keys = append(keys, s)
} }
sort.Strings(keys) sort.Strings(keys)
for i, k := range keys { for i, k := range keys {
v := v[k] v := v[k]
if i != 0 { if i != 0 {
_ = w.WriteByte('\n') _ = w.WriteByte('\n')
w.Indent() w.Indent()
} }
writeEncode(w, k) writeEncode(w, k)
_ = w.WriteByte(':') _ = w.WriteByte(':')
if v == nil || isPrimative(v) || isZero(v) { if v == nil || isPrimative(v) || isZero(v) {
_ = w.WriteByte(' ') _ = w.WriteByte(' ')
writeValue(w, v) writeValue(w, v)
@ -335,19 +356,26 @@ func writeMappingStr(w writer, v map[string]string) {
if len(v) == 0 { if len(v) == 0 {
_ = w.WriteByte('{') _ = w.WriteByte('{')
_ = w.WriteByte('}') _ = w.WriteByte('}')
return return
} }
var keys []string
keys := make([]string, 0)
for k := range v { for k := range v {
keys = append(keys, k) keys = append(keys, k)
} }
sort.Strings(keys) sort.Strings(keys)
for i, k := range keys { for i, k := range keys {
v := v[k] v := v[k]
if i != 0 { if i != 0 {
_ = w.WriteByte('\n') _ = w.WriteByte('\n')
w.Indent() w.Indent()
} }
writeEncode(w, k) writeEncode(w, k)
_ = w.WriteByte(':') _ = w.WriteByte(':')
_ = w.WriteByte(' ') _ = w.WriteByte(' ')

View File

@ -13,25 +13,7 @@ import (
// this unit tests pretty prints a complex yaml structure // this unit tests pretty prints a complex yaml structure
// to ensure we have common use cases covered. // to ensure we have common use cases covered.
func TestWriteComplexValue(t *testing.T) { func TestWriteComplexValue(t *testing.T) {
block := map[interface{}]interface{}{} testComplexValue := `
err := yaml.Unmarshal([]byte(testComplexValue), &block)
if err != nil {
t.Error(err)
return
}
b := new(baseWriter)
writeValue(b, block)
got, want := b.String(), strings.TrimSpace(testComplexValue)
if got != want {
t.Errorf("Unexpected block format")
println(got)
println("---")
println(want)
}
}
var testComplexValue = `
a: b a: b
c: c:
- d - d
@ -57,3 +39,24 @@ x: ~
z: "#y" z: "#y"
zz: "\nz\n" zz: "\nz\n"
"{z}": z` "{z}": z`
block := map[interface{}]interface{}{}
err := yaml.Unmarshal([]byte(testComplexValue), &block)
if err != nil {
t.Error(err)
return
}
b := new(baseWriter)
writeValue(b, block)
got, want := b.String(), strings.TrimSpace(testComplexValue)
if got != want {
t.Errorf("Unexpected block format")
println(got)
println("---")
println(want)
}
}

View File

@ -19,10 +19,13 @@ type (
// UnmarshalYAML implements yaml unmarshalling. // UnmarshalYAML implements yaml unmarshalling.
func (p *Push) UnmarshalYAML(unmarshal func(interface{}) error) error { func (p *Push) UnmarshalYAML(unmarshal func(interface{}) error) error {
d := new(push) d := new(push)
err := unmarshal(&d.Image) err := unmarshal(&d.Image)
if err != nil { if err != nil {
err = unmarshal(d) err = unmarshal(d)
} }
p.Image = d.Image p.Image = d.Image
return err return err
} }

View File

@ -18,6 +18,8 @@ type (
} }
) )
var ErrInvalidRegistry = errors.New("yaml: invalid registry resource")
// GetVersion returns the resource version. // GetVersion returns the resource version.
func (r *Registry) GetVersion() string { return r.Version } func (r *Registry) GetVersion() string { return r.Version }
@ -27,7 +29,8 @@ func (r *Registry) GetKind() string { return r.Kind }
// Validate returns an error if the registry is invalid. // Validate returns an error if the registry is invalid.
func (r *Registry) Validate() error { func (r *Registry) Validate() error {
if len(r.Data) == 0 { if len(r.Data) == 0 {
return errors.New("yaml: invalid registry resource") return ErrInvalidRegistry
} }
return nil return nil
} }

View File

@ -5,9 +5,6 @@ package yaml
import "errors" import "errors"
// TODO(bradrydzewski) deprecate Secret
// TODO(bradrydzewski) deprecate ExternalData
type ( type (
// Secret is a resource that provides encrypted data // Secret is a resource that provides encrypted data
// and pointers to external data (i.e. from vault). // and pointers to external data (i.e. from vault).
@ -38,6 +35,8 @@ type (
} }
) )
var ErrInvalidSecret = errors.New("yaml: invalid secret resource")
// GetVersion returns the resource version. // GetVersion returns the resource version.
func (s *Secret) GetVersion() string { return s.Version } func (s *Secret) GetVersion() string { return s.Version }
@ -47,7 +46,8 @@ func (s *Secret) GetKind() string { return s.Kind }
// Validate returns an error if the secret is invalid. // Validate returns an error if the secret is invalid.
func (s *Secret) Validate() error { func (s *Secret) Validate() error {
if len(s.Data) == 0 && len(s.Get.Path) == 0 && len(s.Get.Name) == 0 { if len(s.Data) == 0 && len(s.Get.Path) == 0 && len(s.Get.Name) == 0 {
return errors.New("yaml: invalid secret resource") return ErrInvalidSecret
} }
return nil return nil
} }

View File

@ -17,6 +17,8 @@ type (
} }
) )
var ErrInvalidSignature = errors.New("yaml: invalid signature due to missing hash")
// GetVersion returns the resource version. // GetVersion returns the resource version.
func (s *Signature) GetVersion() string { return s.Version } func (s *Signature) GetVersion() string { return s.Version }
@ -26,7 +28,8 @@ func (s *Signature) GetKind() string { return s.Kind }
// Validate returns an error if the signature is invalid. // Validate returns an error if the signature is invalid.
func (s Signature) Validate() error { func (s Signature) Validate() error {
if s.Hmac == "" { if s.Hmac == "" {
return errors.New("yaml: invalid signature. missing hash") return ErrInvalidSignature
} }
return nil return nil
} }

View File

@ -17,6 +17,7 @@ func (b *BytesSize) UnmarshalYAML(unmarshal func(interface{}) error) error {
var intType int64 var intType int64
if err := unmarshal(&intType); err == nil { if err := unmarshal(&intType); err == nil {
*b = BytesSize(intType) *b = BytesSize(intType)
return nil return nil
} }
@ -29,6 +30,7 @@ func (b *BytesSize) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err == nil { if err == nil {
*b = BytesSize(intType) *b = BytesSize(intType)
} }
return err return err
} }

View File

@ -34,14 +34,18 @@ func TestBytesSize(t *testing.T) {
for _, test := range tests { for _, test := range tests {
in := []byte(test.yaml) in := []byte(test.yaml)
out := BytesSize(0) out := BytesSize(0)
err := yaml.Unmarshal(in, &out) err := yaml.Unmarshal(in, &out)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
if got, want := int64(out), test.size; got != want { if got, want := int64(out), test.size; got != want {
t.Errorf("Want byte size %d, got %d", want, got) t.Errorf("Want byte size %d, got %d", want, got)
} }
if got, want := out.String(), test.text; got != want { if got, want := out.String(), test.text; got != want {
t.Errorf("Want byte text %s, got %s", want, got) t.Errorf("Want byte text %s, got %s", want, got)
} }