mirror of
https://github.com/thegeeklab/drone-yaml.git
synced 2024-11-21 09:30:41 +00:00
refactor: add more linters and fix findings (#65)
This commit is contained in:
parent
dabaa3fd18
commit
4cb5a900e3
10
.drone.yml
10
.drone.yml
@ -8,7 +8,7 @@ platform:
|
||||
|
||||
steps:
|
||||
- name: deps
|
||||
image: golang:1.19
|
||||
image: golang:1.20
|
||||
commands:
|
||||
- make deps
|
||||
volumes:
|
||||
@ -16,7 +16,7 @@ steps:
|
||||
path: /go
|
||||
|
||||
- name: lint
|
||||
image: golang:1.19
|
||||
image: golang:1.20
|
||||
commands:
|
||||
- make lint
|
||||
volumes:
|
||||
@ -24,7 +24,7 @@ steps:
|
||||
path: /go
|
||||
|
||||
- name: test
|
||||
image: golang:1.19
|
||||
image: golang:1.20
|
||||
commands:
|
||||
- make test
|
||||
volumes:
|
||||
@ -51,7 +51,7 @@ platform:
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: techknowlogick/xgo:go-1.19.x
|
||||
image: techknowlogick/xgo:go-1.20.x
|
||||
commands:
|
||||
- ln -s /drone/src /source
|
||||
- make release
|
||||
@ -292,6 +292,6 @@ depends_on:
|
||||
|
||||
---
|
||||
kind: signature
|
||||
hmac: 236f7dcfcdbb18ed57935d336f25ec56c34074e872a94682c932422e59def005
|
||||
hmac: 383e1b462823db3d79fb155de2bf3b53a4f4380ab036778552011c84fec23344
|
||||
|
||||
...
|
||||
|
113
.golangci.yml
113
.golangci.yml
@ -1,25 +1,92 @@
|
||||
linters:
|
||||
enable:
|
||||
- gosimple
|
||||
- deadcode
|
||||
- typecheck
|
||||
- govet
|
||||
- errcheck
|
||||
- staticcheck
|
||||
- unused
|
||||
- structcheck
|
||||
- varcheck
|
||||
- dupl
|
||||
- gofmt
|
||||
- misspell
|
||||
- gocritic
|
||||
- bidichk
|
||||
- ineffassign
|
||||
- revive
|
||||
- gofumpt
|
||||
- depguard
|
||||
enable-all: false
|
||||
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
|
||||
|
||||
run:
|
||||
@ -28,4 +95,10 @@ run:
|
||||
linters-settings:
|
||||
gofumpt:
|
||||
extra-rules: true
|
||||
lang-version: "1.18"
|
||||
lang-version: "1.20"
|
||||
tagliatelle:
|
||||
case:
|
||||
use-field-name: true
|
||||
rules:
|
||||
json: snake
|
||||
yaml: snake
|
||||
|
2
Makefile
2
Makefile
@ -19,7 +19,7 @@ GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@$(G
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||
|
||||
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
|
||||
|
||||
TARGETOS ?= linux
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
format = kingpin.Command("fmt", "format the yaml file")
|
||||
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()
|
||||
)
|
||||
|
||||
const DefaultFilePerm = 0o640
|
||||
|
||||
func main() {
|
||||
switch kingpin.Parse() {
|
||||
case format.FullCommand():
|
||||
@ -36,6 +39,7 @@ func main() {
|
||||
|
||||
func runFormat() error {
|
||||
f := *formatFile
|
||||
|
||||
m, err := yaml.Parse(f)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -45,23 +49,28 @@ func runFormat() error {
|
||||
pretty.Print(b, m)
|
||||
|
||||
if *formatSave {
|
||||
return os.WriteFile(f.Name(), b.Bytes(), 0o644)
|
||||
return os.WriteFile(f.Name(), b.Bytes(), DefaultFilePerm)
|
||||
}
|
||||
|
||||
_, err = io.Copy(os.Stderr, b)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func runLint() error {
|
||||
f := *lintFile
|
||||
|
||||
m, err := yaml.Parse(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, r := range m.Resources {
|
||||
err := linter.Lint(r, *lintPriv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@ -1,6 +1,6 @@
|
||||
module github.com/drone/drone-yaml
|
||||
|
||||
go 1.19
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0
|
||||
|
@ -29,15 +29,18 @@ type (
|
||||
// UnmarshalYAML implements yaml unmarshalling.
|
||||
func (b *Build) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
d := new(build)
|
||||
|
||||
err := unmarshal(&d.Image)
|
||||
if err != nil {
|
||||
err = unmarshal(d)
|
||||
}
|
||||
|
||||
b.Args = d.Args
|
||||
b.CacheFrom = d.CacheFrom
|
||||
b.Context = d.Context
|
||||
b.Dockerfile = d.Dockerfile
|
||||
b.Labels = d.Labels
|
||||
b.Image = d.Image
|
||||
|
||||
return err
|
||||
}
|
||||
|
13
yaml/cond.go
13
yaml/cond.go
@ -32,12 +32,15 @@ func (c *Condition) Match(v string) bool {
|
||||
if c.Excludes(v) {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.Includes(v) {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(c.Include) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@ -49,6 +52,7 @@ func (c *Condition) Includes(v string) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@ -60,13 +64,17 @@ func (c *Condition) Excludes(v string) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yml unmarshalling.
|
||||
func (c *Condition) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var out1 string
|
||||
var out2 []string
|
||||
var (
|
||||
out1 string
|
||||
out2 []string
|
||||
)
|
||||
|
||||
out3 := struct {
|
||||
Include []string
|
||||
Exclude []string
|
||||
@ -75,6 +83,7 @@ func (c *Condition) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
err := unmarshal(&out1)
|
||||
if err == nil {
|
||||
c.Include = []string{out1}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
10
yaml/cron.go
10
yaml/cron.go
@ -5,6 +5,8 @@ package yaml
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrInvlaidCronBranch = errors.New("yaml: invalid cron branch")
|
||||
|
||||
type (
|
||||
// Cron is a resource that defines a cron job, used
|
||||
// to execute pipelines at scheduled intervals.
|
||||
@ -19,9 +21,9 @@ type (
|
||||
|
||||
// CronSpec defines the cron job.
|
||||
CronSpec struct {
|
||||
Schedule string `json:"schedule,omitempty"`
|
||||
Branch string `json:"branch,omitempty"`
|
||||
Deploy CronDeployment `json:"deployment,omitempty" yaml:"deployment"`
|
||||
Schedule string `json:"schedule,omitempty"`
|
||||
Branch string `json:"branch,omitempty"`
|
||||
Deployment CronDeployment `json:"deployment,omitempty" yaml:"deployment"`
|
||||
}
|
||||
|
||||
// CronDeployment defines a cron job deployment.
|
||||
@ -40,7 +42,7 @@ func (c *Cron) GetKind() string { return c.Kind }
|
||||
func (c Cron) Validate() error {
|
||||
switch {
|
||||
case c.Spec.Branch == "":
|
||||
return errors.New("yaml: invalid cron branch")
|
||||
return ErrInvlaidCronBranch
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
21
yaml/env.go
21
yaml/env.go
@ -8,39 +8,46 @@ type (
|
||||
// can be defined as a string literal or as a reference
|
||||
// to a secret.
|
||||
Variable struct {
|
||||
Value string `json:"value,omitempty"`
|
||||
Secret string `json:"from_secret,omitempty" yaml:"from_secret"`
|
||||
Value string `json:"value,omitempty"`
|
||||
FromSecret string `json:"from_secret,omitempty" yaml:"from_secret"`
|
||||
}
|
||||
|
||||
// variable is a tempoary type used to unmarshal
|
||||
// variables with references to secrets.
|
||||
variable struct {
|
||||
Value string
|
||||
Secret string `yaml:"from_secret"`
|
||||
Value string
|
||||
FromSecret string `yaml:"from_secret"`
|
||||
}
|
||||
)
|
||||
|
||||
// UnmarshalYAML implements yaml unmarshalling.
|
||||
func (v *Variable) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
d := new(variable)
|
||||
|
||||
err := unmarshal(&d.Value)
|
||||
if err != nil {
|
||||
err = unmarshal(d)
|
||||
}
|
||||
|
||||
v.Value = d.Value
|
||||
v.Secret = d.Secret
|
||||
v.FromSecret = d.FromSecret
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalYAML implements yaml marshalling.
|
||||
func (v *Variable) MarshalYAML() (interface{}, error) {
|
||||
if v.Secret != "" {
|
||||
if v.FromSecret != "" {
|
||||
m := map[string]interface{}{}
|
||||
m["from_secret"] = v.Secret
|
||||
m["from_secret"] = v.FromSecret
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
if v.Value != "" {
|
||||
return v.Value, nil
|
||||
}
|
||||
|
||||
//nolint:nilnil
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -28,11 +28,12 @@ var ErrPipelineSelfDependency = errors.New("linter: pipeline cannot have a depen
|
||||
|
||||
// Manifest performs lint operations for a manifest.
|
||||
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{}{}
|
||||
|
||||
for _, resource := range manifest.Resources {
|
||||
switch v := resource.(type) {
|
||||
case *yaml.Pipeline:
|
||||
@ -40,11 +41,14 @@ func checkPipelines(manifest *yaml.Manifest, trusted bool) error {
|
||||
if ok {
|
||||
return ErrDuplicatePipelineName
|
||||
}
|
||||
|
||||
names[v.Name] = struct{}{}
|
||||
|
||||
err := checkPipelineDeps(v, names)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if (v.Kind == "pipeline" || v.Kind == "") && (v.Type == "" || v.Type == "docker") {
|
||||
err = checkPlatform(v.Platform)
|
||||
if err != nil {
|
||||
@ -55,6 +59,7 @@ func checkPipelines(manifest *yaml.Manifest, trusted bool) error {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -64,9 +69,11 @@ func checkPipelineDeps(pipeline *yaml.Pipeline, deps map[string]struct{}) error
|
||||
if !ok {
|
||||
return ErrMissingPipelineDependency
|
||||
}
|
||||
|
||||
if pipeline.Name == dep {
|
||||
return ErrPipelineSelfDependency
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -10,29 +10,49 @@ import (
|
||||
"github.com/drone/drone-yaml/yaml"
|
||||
)
|
||||
|
||||
var os = map[string]struct{}{
|
||||
"linux": {},
|
||||
"windows": {},
|
||||
}
|
||||
//nolint:gochecknoglobals
|
||||
var (
|
||||
os = map[string]struct{}{
|
||||
"linux": {},
|
||||
"windows": {},
|
||||
}
|
||||
arch = map[string]struct{}{
|
||||
"arm": {},
|
||||
"arm64": {},
|
||||
"amd64": {},
|
||||
}
|
||||
)
|
||||
|
||||
var arch = map[string]struct{}{
|
||||
"arm": {},
|
||||
"arm64": {},
|
||||
"amd64": {},
|
||||
}
|
||||
var (
|
||||
// ErrDuplicateStepName is returned when two Pipeline steps
|
||||
// have the same name.
|
||||
ErrDuplicateStepName = errors.New("linter: duplicate step names")
|
||||
|
||||
// ErrDuplicateStepName is returned when two Pipeline steps
|
||||
// have the same name.
|
||||
var ErrDuplicateStepName = errors.New("linter: duplicate step names")
|
||||
// ErrMissingDependency is returned when a Pipeline step
|
||||
// defines dependencies that are invalid or unknown.
|
||||
ErrMissingDependency = errors.New("linter: invalid or unknown step dependency")
|
||||
|
||||
// ErrMissingDependency is returned when a Pipeline step
|
||||
// defines dependencies that are invalid or unknown.
|
||||
var ErrMissingDependency = errors.New("linter: invalid or unknown step dependency")
|
||||
// ErrCyclicalDependency is returned when a Pipeline step
|
||||
// defines a cyclical dependency, which would result in an
|
||||
// infinite execution loop.
|
||||
ErrCyclicalDependency = errors.New("linter: cyclical step dependency detected")
|
||||
|
||||
// ErrCyclicalDependency is returned when a Pipeline step
|
||||
// defines a cyclical dependency, which would result in an
|
||||
// infinite execution loop.
|
||||
var ErrCyclicalDependency = errors.New("linter: cyclical step dependency detected")
|
||||
ErrUnsupportedOS = errors.New("linter: unsupported os")
|
||||
ErrUnsupportedArch = errors.New("linter: unsupported architecture")
|
||||
ErrInvalidImage = errors.New("linter: invalid or missing image")
|
||||
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.
|
||||
func Lint(resource yaml.Resource, trusted bool) error {
|
||||
@ -57,19 +77,23 @@ func checkPipeline(pipeline *yaml.Pipeline, trusted bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = checkPlatform(pipeline.Platform)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
names := map[string]struct{}{}
|
||||
if !pipeline.Clone.Disable {
|
||||
names["clone"] = struct{}{}
|
||||
}
|
||||
|
||||
for _, container := range pipeline.Steps {
|
||||
_, ok := names[container.Name]
|
||||
if ok {
|
||||
return ErrDuplicateStepName
|
||||
}
|
||||
|
||||
names[container.Name] = struct{}{}
|
||||
|
||||
err := checkContainer(container, trusted)
|
||||
@ -82,11 +106,13 @@ func checkPipeline(pipeline *yaml.Pipeline, trusted bool) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, container := range pipeline.Services {
|
||||
_, ok := names[container.Name]
|
||||
if ok {
|
||||
return ErrDuplicateStepName
|
||||
}
|
||||
|
||||
names[container.Name] = struct{}{}
|
||||
|
||||
err := checkContainer(container, trusted)
|
||||
@ -94,6 +120,7 @@ func checkPipeline(pipeline *yaml.Pipeline, trusted bool) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -101,15 +128,17 @@ func checkPlatform(platform yaml.Platform) error {
|
||||
if v := platform.OS; v != "" {
|
||||
_, ok := os[v]
|
||||
if !ok {
|
||||
return fmt.Errorf("linter: unsupported os: %s", v)
|
||||
return fmt.Errorf("%w: %s", ErrUnsupportedOS, v)
|
||||
}
|
||||
}
|
||||
|
||||
if v := platform.Arch; v != "" {
|
||||
_, ok := arch[v]
|
||||
if !ok {
|
||||
return fmt.Errorf("linter: unsupported architecture: %s", v)
|
||||
return fmt.Errorf("%w: %s", ErrUnsupportedArch, v)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -118,39 +147,50 @@ func checkContainer(container *yaml.Container, trusted bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if container.Build == nil && container.Image == "" {
|
||||
return errors.New("linter: invalid or missing image")
|
||||
return ErrInvalidImage
|
||||
}
|
||||
|
||||
if container.Build != nil && container.Build.Image == "" {
|
||||
return errors.New("linter: invalid or missing build image")
|
||||
return ErrInvalidBuildImage
|
||||
}
|
||||
|
||||
if container.Name == "" {
|
||||
return errors.New("linter: invalid or missing name")
|
||||
return ErrInvalidName
|
||||
}
|
||||
|
||||
if trusted && container.Privileged {
|
||||
return errors.New("linter: untrusted repositories cannot enable privileged mode")
|
||||
return ErrPrivilegedNotAllowed
|
||||
}
|
||||
|
||||
if trusted && len(container.Devices) > 0 {
|
||||
return errors.New("linter: untrusted repositories cannot mount devices")
|
||||
return ErrMountNotAllowed
|
||||
}
|
||||
|
||||
if trusted && len(container.DNS) > 0 {
|
||||
return errors.New("linter: untrusted repositories cannot configure dns")
|
||||
return ErrDNSNotAllowed
|
||||
}
|
||||
|
||||
if trusted && len(container.DNSSearch) > 0 {
|
||||
return errors.New("linter: untrusted repositories cannot configure dns_search")
|
||||
return ErrDNSSearchNotAllowed
|
||||
}
|
||||
|
||||
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 {
|
||||
switch mount.Name {
|
||||
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
|
||||
}
|
||||
|
||||
@ -161,49 +201,56 @@ func checkPorts(ports []*yaml.Port, trusted bool) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPort(port *yaml.Port, trusted bool) error {
|
||||
if trusted && port.Host != 0 {
|
||||
return errors.New("linter: untrusted repositories cannot map to a host port")
|
||||
return ErrHostPortNotAllowed
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkVolumes(pipeline *yaml.Pipeline, trusted bool) error {
|
||||
for _, volume := range pipeline.Volumes {
|
||||
if volume.EmptyDir != nil {
|
||||
err := checkEmptyDirVolume(volume.EmptyDir, trusted)
|
||||
if volume.Temp != nil {
|
||||
err := checkEmptyDirVolume(volume.Temp, trusted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if volume.HostPath != nil {
|
||||
err := checkHostPathVolume(volume.HostPath, trusted)
|
||||
|
||||
if volume.Host != nil {
|
||||
err := checkHostPathVolume(trusted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch volume.Name {
|
||||
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
|
||||
}
|
||||
|
||||
func checkHostPathVolume(volume *yaml.VolumeHostPath, trusted bool) error {
|
||||
func checkHostPathVolume(trusted bool) error {
|
||||
if trusted {
|
||||
return errors.New("linter: untrusted repositories cannot mount host volumes")
|
||||
return ErrHostVolumeNotAllowed
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkEmptyDirVolume(volume *yaml.VolumeEmptyDir, trusted bool) error {
|
||||
if trusted && volume.Medium == "memory" {
|
||||
return errors.New("linter: untrusted repositories cannot mount in-memory volumes")
|
||||
return ErrTempVolumeNotAllowed
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -213,9 +260,11 @@ func checkDeps(container *yaml.Container, deps map[string]struct{}) error {
|
||||
if !ok {
|
||||
return ErrMissingDependency
|
||||
}
|
||||
|
||||
if container.Name == dep {
|
||||
return ErrCyclicalDependency
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ const (
|
||||
KindSignature = "signature"
|
||||
)
|
||||
|
||||
var ErrMarshalNotImplemented = errors.New("yaml: marshal not implemented")
|
||||
|
||||
type (
|
||||
// Manifest is a collection of Drone resources.
|
||||
Manifest struct {
|
||||
@ -44,6 +46,7 @@ type (
|
||||
Data []byte `yaml:"-"`
|
||||
}
|
||||
|
||||
//nolint:musttag
|
||||
resource struct {
|
||||
Version string
|
||||
Kind string `json:"kind"`
|
||||
@ -54,17 +57,22 @@ type (
|
||||
// UnmarshalJSON implement the json.Unmarshaler.
|
||||
func (m *Manifest) UnmarshalJSON(b []byte) error {
|
||||
messages := []json.RawMessage{}
|
||||
|
||||
err := json.Unmarshal(b, &messages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, message := range messages {
|
||||
res := new(resource)
|
||||
|
||||
err := json.Unmarshal(message, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var obj Resource
|
||||
|
||||
switch res.Kind {
|
||||
case "cron":
|
||||
obj = new(Cron)
|
||||
@ -77,12 +85,15 @@ func (m *Manifest) UnmarshalJSON(b []byte) error {
|
||||
default:
|
||||
obj = new(Pipeline)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(message, obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Resources = append(m.Resources, obj)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -96,17 +107,19 @@ func (m *Manifest) MarshalJSON() ([]byte, error) {
|
||||
// documents, and MarshalYAML would otherwise attempt to marshal
|
||||
// as a single Yaml document. Use the Encode method instead.
|
||||
func (m *Manifest) MarshalYAML() (interface{}, error) {
|
||||
return nil, errors.New("yaml: marshal not implemented")
|
||||
return nil, ErrMarshalNotImplemented
|
||||
}
|
||||
|
||||
// Encode encodes the manifest in Yaml format.
|
||||
func (m *Manifest) Encode() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := yaml.NewEncoder(buf)
|
||||
|
||||
for _, res := range m.Resources {
|
||||
if err := enc.Encode(res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
@ -8,14 +8,14 @@ type (
|
||||
// can be defined as a literal or as a reference
|
||||
// to a secret.
|
||||
Parameter struct {
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
Secret string `json:"from_secret,omitempty" yaml:"from_secret"`
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
FromSecret string `json:"from_secret,omitempty" yaml:"from_secret"`
|
||||
}
|
||||
|
||||
// parameter is a tempoary type used to unmarshal
|
||||
// parameters with references to secrets.
|
||||
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 {
|
||||
d := new(parameter)
|
||||
err := unmarshal(d)
|
||||
if err == nil && d.Secret != "" {
|
||||
p.Secret = d.Secret
|
||||
|
||||
if err == nil && d.FromSecret != "" {
|
||||
p.FromSecret = d.FromSecret
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var i interface{}
|
||||
err = unmarshal(&i)
|
||||
p.Value = i
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// MarshalYAML implements yaml marshalling.
|
||||
func (p *Parameter) MarshalYAML() (interface{}, error) {
|
||||
if p.Secret != "" {
|
||||
if p.FromSecret != "" {
|
||||
m := map[string]interface{}{}
|
||||
m["from_secret"] = p.Secret
|
||||
m["from_secret"] = p.FromSecret
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
if p.Value != "" {
|
||||
return p.Value, nil
|
||||
}
|
||||
|
||||
//nolint:nilnil
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
"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.
|
||||
func Parse(r io.Reader) (*Manifest, error) {
|
||||
@ -22,23 +22,29 @@ func Parse(r io.Reader) (*Manifest, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
manifest := new(Manifest)
|
||||
|
||||
for _, raw := range resources {
|
||||
if raw == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
resource, err := parseRaw(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resource.GetKind() == "" {
|
||||
return nil, errorMissingKind
|
||||
return nil, ErrMissingKind
|
||||
}
|
||||
|
||||
manifest.Resources = append(
|
||||
manifest.Resources,
|
||||
resource,
|
||||
)
|
||||
}
|
||||
|
||||
return manifest, nil
|
||||
}
|
||||
|
||||
@ -63,11 +69,13 @@ func ParseFile(p string) (*Manifest, error) {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return Parse(f)
|
||||
}
|
||||
|
||||
func parseRaw(r *RawResource) (Resource, error) {
|
||||
func parseRaw(r *RawResource) (Resource, error) { //nolint:ireturn
|
||||
var obj Resource
|
||||
|
||||
switch r.Kind {
|
||||
case "cron":
|
||||
obj = new(Cron)
|
||||
@ -80,7 +88,9 @@ func parseRaw(r *RawResource) (Resource, error) {
|
||||
default:
|
||||
obj = new(Pipeline)
|
||||
}
|
||||
|
||||
err := yaml.Unmarshal(r.Data, obj)
|
||||
|
||||
return obj, err
|
||||
}
|
||||
|
||||
@ -88,8 +98,11 @@ func parseRaw(r *RawResource) (Resource, error) {
|
||||
// io.Reader and returns a slice of raw resources.
|
||||
func ParseRaw(r io.Reader) ([]*RawResource, error) {
|
||||
const newline = '\n'
|
||||
var resources []*RawResource
|
||||
var resource *RawResource
|
||||
|
||||
var (
|
||||
resources []*RawResource
|
||||
resource *RawResource
|
||||
)
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
@ -97,34 +110,42 @@ func ParseRaw(r io.Reader) ([]*RawResource, error) {
|
||||
if isSeparator(line) {
|
||||
resource = nil
|
||||
}
|
||||
|
||||
if resource == nil {
|
||||
resource = &RawResource{}
|
||||
resources = append(resources, resource)
|
||||
}
|
||||
|
||||
if isSeparator(line) {
|
||||
continue
|
||||
}
|
||||
|
||||
if isTerminator(line) {
|
||||
break
|
||||
}
|
||||
if scanner.Err() == io.EOF {
|
||||
|
||||
if errors.Is(scanner.Err(), io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
resource.Data = append(
|
||||
resource.Data,
|
||||
line...,
|
||||
)
|
||||
|
||||
resource.Data = append(
|
||||
resource.Data,
|
||||
newline,
|
||||
)
|
||||
}
|
||||
|
||||
for _, resource := range resources {
|
||||
err := yaml.Unmarshal(resource.Data, resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
@ -152,6 +173,7 @@ func ParseRawFile(p string) ([]*RawResource, error) {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return ParseRaw(f)
|
||||
}
|
||||
|
||||
|
@ -11,17 +11,17 @@ type Pipeline struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
Clone Clone `json:"clone,omitempty"`
|
||||
Concurrency Concurrency `json:"concurrency,omitempty"`
|
||||
DependsOn []string `json:"depends_on,omitempty" yaml:"depends_on" `
|
||||
Node map[string]string `json:"node,omitempty" yaml:"node"`
|
||||
Platform Platform `json:"platform,omitempty"`
|
||||
PullSecrets []string `json:"image_pull_secrets,omitempty" yaml:"image_pull_secrets"`
|
||||
Services []*Container `json:"services,omitempty"`
|
||||
Steps []*Container `json:"steps,omitempty"`
|
||||
Trigger Conditions `json:"trigger,omitempty"`
|
||||
Volumes []*Volume `json:"volumes,omitempty"`
|
||||
Workspace Workspace `json:"workspace,omitempty"`
|
||||
Clone Clone `json:"clone,omitempty"`
|
||||
Concurrency Concurrency `json:"concurrency,omitempty"`
|
||||
DependsOn []string `json:"depends_on,omitempty" yaml:"depends_on" `
|
||||
Node map[string]string `json:"node,omitempty" yaml:"node"`
|
||||
Platform Platform `json:"platform,omitempty"`
|
||||
ImagePullSecrets []string `json:"image_pull_secrets,omitempty" yaml:"image_pull_secrets"`
|
||||
Services []*Container `json:"services,omitempty"`
|
||||
Steps []*Container `json:"steps,omitempty"`
|
||||
Trigger Conditions `json:"trigger,omitempty"`
|
||||
Volumes []*Volume `json:"volumes,omitempty"`
|
||||
Workspace Workspace `json:"workspace,omitempty"`
|
||||
}
|
||||
|
||||
// GetVersion returns the resource version.
|
||||
@ -58,7 +58,7 @@ type (
|
||||
ExtraHosts []string `json:"extra_hosts,omitempty" yaml:"extra_hosts"`
|
||||
Failure string `json:"failure,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"`
|
||||
Ports []*Port `json:"ports,omitempty"`
|
||||
Privileged bool `json:"privileged,omitempty"`
|
||||
@ -102,23 +102,23 @@ type (
|
||||
|
||||
// Volume that can be mounted by containers.
|
||||
Volume struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
EmptyDir *VolumeEmptyDir `json:"temp,omitempty" yaml:"temp"`
|
||||
HostPath *VolumeHostPath `json:"host,omitempty" yaml:"host"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Temp *VolumeEmptyDir `json:"temp,omitempty" yaml:"temp"`
|
||||
Host *VolumeHostPath `json:"host,omitempty" yaml:"host"`
|
||||
}
|
||||
|
||||
// VolumeDevice describes a mapping of a raw block
|
||||
// device within a container.
|
||||
VolumeDevice struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
DevicePath string `json:"path,omitempty" yaml:"path"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Path string `json:"path,omitempty" yaml:"path"`
|
||||
}
|
||||
|
||||
// VolumeMount describes a mounting of a Volume
|
||||
// within a container.
|
||||
VolumeMount struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
MountPath string `json:"path,omitempty" yaml:"path"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Path string `json:"path,omitempty" yaml:"path"`
|
||||
}
|
||||
|
||||
// VolumeEmptyDir mounts a temporary directory from the
|
||||
|
@ -22,12 +22,15 @@ type (
|
||||
// UnmarshalYAML implements yaml unmarshalling.
|
||||
func (p *Port) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
out := new(port)
|
||||
|
||||
err := unmarshal(&out.Port)
|
||||
if err != nil {
|
||||
err = unmarshal(&out)
|
||||
}
|
||||
|
||||
p.Port = out.Port
|
||||
p.Host = out.Host
|
||||
p.Protocol = out.Protocol
|
||||
|
||||
return err
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ func printContainer(w writer, v *yaml.Container) {
|
||||
if v.Build != nil {
|
||||
printBuild(w, v.Build)
|
||||
}
|
||||
|
||||
if v.Push != nil {
|
||||
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_search", v.DNSSearch)
|
||||
w.WriteTagValue("extra_hosts", v.ExtraHosts)
|
||||
w.WriteTagValue("network_mode", v.Network)
|
||||
w.WriteTagValue("network_mode", v.NetworkMode)
|
||||
|
||||
if len(v.Settings) > 0 {
|
||||
printSettings(w, v.Settings)
|
||||
@ -49,21 +50,27 @@ func printContainer(w writer, v *yaml.Container) {
|
||||
if len(v.Devices) > 0 {
|
||||
printDeviceMounts(w, v.Devices)
|
||||
}
|
||||
|
||||
if len(v.Ports) > 0 {
|
||||
printPorts(w, v.Ports)
|
||||
}
|
||||
|
||||
if v.Resources != nil {
|
||||
printResources(w, v.Resources)
|
||||
}
|
||||
|
||||
if len(v.Volumes) > 0 {
|
||||
printVolumeMounts(w, v.Volumes)
|
||||
}
|
||||
|
||||
if !isConditionsEmpty(v.When) {
|
||||
printConditions(w, "when", v.When)
|
||||
}
|
||||
|
||||
if len(v.DependsOn) > 0 {
|
||||
printDependsOn(w, v.DependsOn)
|
||||
}
|
||||
|
||||
_ = w.WriteByte('\n')
|
||||
w.IndentDecrease()
|
||||
}
|
||||
@ -93,43 +100,49 @@ func printDependsOn(w writer, v []string) {
|
||||
// helper function pretty prints the device sequence.
|
||||
func printDeviceMounts(w writer, v []*yaml.VolumeDevice) {
|
||||
w.WriteTag("devices")
|
||||
|
||||
for _, v := range v {
|
||||
s := new(indexWriter)
|
||||
s.writer = w
|
||||
s.IndentIncrease()
|
||||
s.WriteTagValue("name", v.Name)
|
||||
s.WriteTagValue("path", v.DevicePath)
|
||||
s.WriteTagValue("path", v.Path)
|
||||
s.IndentDecrease()
|
||||
}
|
||||
}
|
||||
|
||||
// helper function pretty prints the environment mapping.
|
||||
func printEnviron(w writer, v map[string]*yaml.Variable) {
|
||||
var keys []string
|
||||
keys := make([]string, 0)
|
||||
|
||||
for k := range v {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
w.WriteTag("environment")
|
||||
w.IndentIncrease()
|
||||
|
||||
for _, k := range keys {
|
||||
v := v[k]
|
||||
if v.Secret == "" {
|
||||
if v.FromSecret == "" {
|
||||
w.WriteTagValue(k, v.Value)
|
||||
} else {
|
||||
w.WriteTag(k)
|
||||
w.IndentIncrease()
|
||||
w.WriteTagValue("from_secret", v.Secret)
|
||||
w.WriteTagValue("from_secret", v.FromSecret)
|
||||
w.IndentDecrease()
|
||||
}
|
||||
}
|
||||
|
||||
w.IndentDecrease()
|
||||
}
|
||||
|
||||
// helper function pretty prints the port sequence.
|
||||
func printPorts(w writer, v []*yaml.Port) {
|
||||
w.WriteTag("ports")
|
||||
|
||||
for _, v := range v {
|
||||
if shortPort(v) {
|
||||
_ = w.WriteByte('\n')
|
||||
@ -137,6 +150,7 @@ func printPorts(w writer, v []*yaml.Port) {
|
||||
_ = w.WriteByte('-')
|
||||
_ = w.WriteByte(' ')
|
||||
writeInt(w, v.Port)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
@ -162,6 +176,7 @@ func printResources(w writer, v *yaml.Resources) {
|
||||
w.WriteTagValue("memory", v.Limits.Memory)
|
||||
w.IndentDecrease()
|
||||
}
|
||||
|
||||
if v.Requests != nil {
|
||||
w.WriteTag("requests")
|
||||
w.IndentIncrease()
|
||||
@ -169,38 +184,44 @@ func printResources(w writer, v *yaml.Resources) {
|
||||
w.WriteTagValue("memory", v.Requests.Memory)
|
||||
w.IndentDecrease()
|
||||
}
|
||||
|
||||
w.IndentDecrease()
|
||||
}
|
||||
|
||||
// helper function pretty prints the resoure mapping.
|
||||
func printSettings(w writer, v map[string]*yaml.Parameter) {
|
||||
var keys []string
|
||||
keys := make([]string, 0)
|
||||
|
||||
for k := range v {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
w.WriteTag("settings")
|
||||
w.IndentIncrease()
|
||||
|
||||
for _, k := range keys {
|
||||
v := v[k]
|
||||
if v.Secret == "" {
|
||||
if v.FromSecret == "" {
|
||||
w.IncludeZero()
|
||||
w.WriteTagValue(k, v.Value)
|
||||
w.ExcludeZero()
|
||||
} else {
|
||||
w.WriteTag(k)
|
||||
w.IndentIncrease()
|
||||
w.WriteTagValue("from_secret", v.Secret)
|
||||
w.WriteTagValue("from_secret", v.FromSecret)
|
||||
w.IndentDecrease()
|
||||
}
|
||||
}
|
||||
|
||||
w.IndentDecrease()
|
||||
}
|
||||
|
||||
// helper function pretty prints the volume sequence.
|
||||
func printVolumeMounts(w writer, v []*yaml.VolumeMount) {
|
||||
w.WriteTag("volumes")
|
||||
|
||||
for _, v := range v {
|
||||
s := new(indexWriter)
|
||||
s.writer = w
|
||||
@ -208,7 +229,7 @@ func printVolumeMounts(w writer, v []*yaml.VolumeMount) {
|
||||
s.IndentIncrease()
|
||||
|
||||
s.WriteTagValue("name", v.Name)
|
||||
s.WriteTagValue("path", v.MountPath)
|
||||
s.WriteTagValue("path", v.Path)
|
||||
|
||||
s.IndentDecrease()
|
||||
w.IndentDecrease()
|
||||
|
@ -23,9 +23,11 @@ func printSpec(w writer, v *yaml.Cron) {
|
||||
w.IndentIncrease()
|
||||
w.WriteTagValue("schedule", v.Spec.Schedule)
|
||||
w.WriteTagValue("branch", v.Spec.Branch)
|
||||
|
||||
if hasDeployment(v) {
|
||||
printDeploy(w, v)
|
||||
}
|
||||
|
||||
w.IndentDecrease()
|
||||
}
|
||||
|
||||
@ -33,12 +35,12 @@ func printSpec(w writer, v *yaml.Cron) {
|
||||
func printDeploy(w writer, v *yaml.Cron) {
|
||||
w.WriteTag("deployment")
|
||||
w.IndentIncrease()
|
||||
w.WriteTagValue("target", v.Spec.Deploy.Target)
|
||||
w.WriteTagValue("target", v.Spec.Deployment.Target)
|
||||
w.IndentDecrease()
|
||||
}
|
||||
|
||||
// helper function returns true if the deployment
|
||||
// object is empty.
|
||||
func hasDeployment(v *yaml.Cron) bool {
|
||||
return v.Spec.Deploy.Target != ""
|
||||
return v.Spec.Deployment.Target != ""
|
||||
}
|
||||
|
@ -21,22 +21,27 @@ func printPipeline(w writer, v *yaml.Pipeline) {
|
||||
} else {
|
||||
printPlatformDefault(w)
|
||||
}
|
||||
|
||||
if !isCloneEmpty(v.Clone) {
|
||||
printClone(w, v.Clone)
|
||||
}
|
||||
|
||||
if !isConcurrencyEmpty(v.Concurrency) {
|
||||
printConcurrency(w, v.Concurrency)
|
||||
}
|
||||
|
||||
if !isWorkspaceEmpty(v.Workspace) {
|
||||
printWorkspace(w, v.Workspace)
|
||||
}
|
||||
|
||||
if len(v.Steps) > 0 {
|
||||
w.WriteTag("steps")
|
||||
|
||||
for _, step := range v.Steps {
|
||||
if step == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
seq := new(indexWriter)
|
||||
seq.writer = w
|
||||
seq.IndentIncrease()
|
||||
@ -47,10 +52,12 @@ func printPipeline(w writer, v *yaml.Pipeline) {
|
||||
|
||||
if len(v.Services) > 0 {
|
||||
w.WriteTag("services")
|
||||
|
||||
for _, step := range v.Services {
|
||||
if step == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
seq := new(indexWriter)
|
||||
seq.writer = w
|
||||
seq.IndentIncrease()
|
||||
@ -64,8 +71,8 @@ func printPipeline(w writer, v *yaml.Pipeline) {
|
||||
_ = w.WriteByte('\n')
|
||||
}
|
||||
|
||||
if len(v.PullSecrets) > 0 {
|
||||
w.WriteTagValue("image_pull_secrets", v.PullSecrets)
|
||||
if len(v.ImagePullSecrets) > 0 {
|
||||
w.WriteTagValue("image_pull_secrets", v.ImagePullSecrets)
|
||||
_ = w.WriteByte('\n')
|
||||
}
|
||||
|
||||
@ -111,42 +118,54 @@ func printConcurrency(w writer, v yaml.Concurrency) {
|
||||
func printConditions(w writer, name string, v yaml.Conditions) {
|
||||
w.WriteTag(name)
|
||||
w.IndentIncrease()
|
||||
|
||||
if !isConditionEmpty(v.Action) {
|
||||
printCondition(w, "action", v.Action)
|
||||
}
|
||||
|
||||
if !isConditionEmpty(v.Branch) {
|
||||
printCondition(w, "branch", v.Branch)
|
||||
}
|
||||
|
||||
if !isConditionEmpty(v.Cron) {
|
||||
printCondition(w, "cron", v.Cron)
|
||||
}
|
||||
|
||||
if !isConditionEmpty(v.Event) {
|
||||
printCondition(w, "event", v.Event)
|
||||
}
|
||||
|
||||
if !isConditionEmpty(v.Instance) {
|
||||
printCondition(w, "instance", v.Instance)
|
||||
}
|
||||
|
||||
if !isConditionEmpty(v.Paths) {
|
||||
printCondition(w, "paths", v.Paths)
|
||||
}
|
||||
|
||||
if !isConditionEmpty(v.Ref) {
|
||||
printCondition(w, "ref", v.Ref)
|
||||
}
|
||||
|
||||
if !isConditionEmpty(v.Repo) {
|
||||
printCondition(w, "repo", v.Repo)
|
||||
}
|
||||
|
||||
if !isConditionEmpty(v.Status) {
|
||||
printCondition(w, "status", v.Status)
|
||||
}
|
||||
|
||||
if !isConditionEmpty(v.Target) {
|
||||
printCondition(w, "target", v.Target)
|
||||
}
|
||||
|
||||
w.IndentDecrease()
|
||||
}
|
||||
|
||||
// helper function pretty prints a condition mapping.
|
||||
func printCondition(w writer, k string, v yaml.Condition) {
|
||||
w.WriteTag(k)
|
||||
|
||||
if len(v.Include) != 0 && len(v.Exclude) == 0 {
|
||||
_ = w.WriteByte('\n')
|
||||
w.IndentIncrease()
|
||||
@ -154,11 +173,13 @@ func printCondition(w writer, k string, v yaml.Condition) {
|
||||
writeValue(w, v.Include)
|
||||
w.IndentDecrease()
|
||||
}
|
||||
|
||||
if len(v.Include) != 0 && len(v.Exclude) != 0 {
|
||||
w.IndentIncrease()
|
||||
w.WriteTagValue("include", v.Include)
|
||||
w.IndentDecrease()
|
||||
}
|
||||
|
||||
if len(v.Exclude) != 0 {
|
||||
w.IndentIncrease()
|
||||
w.WriteTagValue("exclude", v.Exclude)
|
||||
@ -197,6 +218,7 @@ func printPlatformDefault(w writer) {
|
||||
// helper function pretty prints the volume sequence.
|
||||
func printVolumes(w writer, v []*yaml.Volume) {
|
||||
w.WriteTag("volumes")
|
||||
|
||||
for _, v := range v {
|
||||
s := new(indexWriter)
|
||||
s.writer = w
|
||||
@ -204,8 +226,10 @@ func printVolumes(w writer, v []*yaml.Volume) {
|
||||
s.IndentIncrease()
|
||||
|
||||
s.WriteTagValue("name", v.Name)
|
||||
if v := v.EmptyDir; v != nil {
|
||||
|
||||
if v := v.Temp; v != nil {
|
||||
s.WriteTag("temp")
|
||||
|
||||
if isEmptyDirEmpty(v) {
|
||||
_ = 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.IndentIncrease()
|
||||
s.WriteTagValue("path", v.Path)
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
// Print pretty prints the manifest.
|
||||
func Print(w io.Writer, v *yaml.Manifest) {
|
||||
state := new(baseWriter)
|
||||
|
||||
for _, r := range v.Resources {
|
||||
switch t := r.(type) {
|
||||
case *yaml.Cron:
|
||||
@ -24,6 +25,7 @@ func Print(w io.Writer, v *yaml.Manifest) {
|
||||
printPipeline(state, t)
|
||||
}
|
||||
}
|
||||
|
||||
state.WriteString("...")
|
||||
state.WriteByte('\n')
|
||||
_, _ = w.Write(state.Bytes())
|
||||
|
@ -9,8 +9,6 @@ import (
|
||||
"github.com/drone/drone-yaml/yaml"
|
||||
)
|
||||
|
||||
// TODO consider "!!binary |" for secret value
|
||||
|
||||
// helper function to pretty prints the signature resource.
|
||||
func printSecret(w writer, v *yaml.Secret) {
|
||||
_, _ = w.WriteString("---")
|
||||
@ -22,11 +20,13 @@ func printSecret(w writer, v *yaml.Secret) {
|
||||
w.WriteTagValue("name", v.Name)
|
||||
printData(w, v.Data)
|
||||
}
|
||||
|
||||
if !isSecretGetEmpty(v.Get) {
|
||||
w.WriteTagValue("name", v.Name)
|
||||
_ = w.WriteByte('\n')
|
||||
printGet(w, v.Get)
|
||||
}
|
||||
|
||||
_ = w.WriteByte('\n')
|
||||
_ = w.WriteByte('\n')
|
||||
}
|
||||
@ -42,22 +42,24 @@ func printGet(w writer, v yaml.SecretGet) {
|
||||
}
|
||||
|
||||
func printData(w writer, d string) {
|
||||
spaceReplacer := strings.NewReplacer(" ", "", "\n", "")
|
||||
|
||||
w.WriteTag("data")
|
||||
_ = w.WriteByte(' ')
|
||||
_ = w.WriteByte('>')
|
||||
w.IndentIncrease()
|
||||
|
||||
d = spaceReplacer.Replace(d)
|
||||
//nolint:gomnd
|
||||
for _, s := range chunk(d, 60) {
|
||||
_ = w.WriteByte('\n')
|
||||
w.Indent()
|
||||
_, _ = w.WriteString(s)
|
||||
}
|
||||
|
||||
w.IndentDecrease()
|
||||
}
|
||||
|
||||
// replace spaces and newlines.
|
||||
var spaceReplacer = strings.NewReplacer(" ", "", "\n", "")
|
||||
|
||||
// helper function returns true if the secret get
|
||||
// object is empty.
|
||||
func isSecretGetEmpty(v yaml.SecretGet) bool {
|
||||
|
@ -72,6 +72,7 @@ func isQuoted(s string) bool {
|
||||
}
|
||||
|
||||
var r0, r1 byte
|
||||
|
||||
t := strings.TrimSpace(s)
|
||||
|
||||
// if the trimmed string does not match the string, it
|
||||
@ -84,6 +85,7 @@ func isQuoted(s string) bool {
|
||||
if len(t) > 0 {
|
||||
r0 = t[0]
|
||||
}
|
||||
|
||||
if len(t) > 1 {
|
||||
r1 = t[1]
|
||||
}
|
||||
@ -103,6 +105,7 @@ func isQuoted(s string) bool {
|
||||
}
|
||||
|
||||
var prev rune
|
||||
|
||||
for _, b := range s {
|
||||
switch {
|
||||
case isEscapeCode(b):
|
||||
@ -112,6 +115,7 @@ func isQuoted(s string) bool {
|
||||
case b == '#' && prev == ' ':
|
||||
return true
|
||||
}
|
||||
|
||||
prev = b
|
||||
}
|
||||
|
||||
@ -124,13 +128,17 @@ func chunk(s string, chunkSize int) []string {
|
||||
if len(s) == 0 {
|
||||
return []string{s}
|
||||
}
|
||||
|
||||
var chunks []string
|
||||
|
||||
for i := 0; i < len(s); i += chunkSize {
|
||||
nn := i + chunkSize
|
||||
if nn > len(s) {
|
||||
nn = len(s)
|
||||
}
|
||||
|
||||
chunks = append(chunks, s[i:nn])
|
||||
}
|
||||
|
||||
return chunks
|
||||
}
|
||||
|
@ -71,6 +71,7 @@ func TestQuoted(t *testing.T) {
|
||||
writeEncode(buf, test.before)
|
||||
a := test.after
|
||||
b := buf.String()
|
||||
|
||||
if b != a {
|
||||
t.Errorf("Want %q, got %q", a, b)
|
||||
}
|
||||
@ -78,16 +79,17 @@ func TestQuoted(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestChunk(t *testing.T) {
|
||||
testChunk := []string{
|
||||
"ZDllMjFjZDg3Zjk0ZWFjZDRhMjdhMTA1ZDQ1OTVkYTA1ODBjMTk0ZWVlZjQyNmU4",
|
||||
"N2RiNTIwZjg0NWQwYjcyYjE3MmFmZDIyYzg3NTQ1N2YyYzgxODhjYjJmNDhhOTFj",
|
||||
"ZjdhMzA0YjEzYWFlMmYxMTIwMmEyM2Q1YjQ5Yjg2ZmMK",
|
||||
}
|
||||
|
||||
s := strings.Join(testChunk, "")
|
||||
got, want := chunk(s, 64), testChunk
|
||||
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Errorf("Unexpected chunk value")
|
||||
t.Log(diff)
|
||||
}
|
||||
}
|
||||
|
||||
var testChunk = []string{
|
||||
"ZDllMjFjZDg3Zjk0ZWFjZDRhMjdhMTA1ZDQ1OTVkYTA1ODBjMTk0ZWVlZjQyNmU4",
|
||||
"N2RiNTIwZjg0NWQwYjcyYjE3MmFmZDIyYzg3NTQ1N2YyYzgxODhjYjJmNDhhOTFj",
|
||||
"ZjdhMzA0YjEzYWFlMmYxMTIwMmEyM2Q1YjQ5Yjg2ZmMK",
|
||||
}
|
||||
|
@ -12,9 +12,6 @@ import (
|
||||
"github.com/drone/drone-yaml/yaml"
|
||||
)
|
||||
|
||||
// TODO rename WriteTag to WriteKey
|
||||
// TODO rename WriteTagValue to WriteKeyValue
|
||||
|
||||
// ESCAPING:
|
||||
//
|
||||
// The string starts with a special character:
|
||||
@ -102,7 +99,9 @@ func (w *baseWriter) WriteTagValue(k, v interface{}) {
|
||||
if isZero(v) && !w.zero {
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteTag(k)
|
||||
|
||||
switch {
|
||||
case isPrimative(v):
|
||||
_ = w.WriteByte(' ')
|
||||
@ -142,6 +141,7 @@ func (w *indexWriter) ExcludeZero() {
|
||||
|
||||
func (w *indexWriter) WriteTag(v interface{}) {
|
||||
_ = w.WriteByte('\n')
|
||||
|
||||
if w.index == 0 {
|
||||
w.IndentDecrease()
|
||||
w.Indent()
|
||||
@ -151,6 +151,7 @@ func (w *indexWriter) WriteTag(v interface{}) {
|
||||
} else {
|
||||
w.Indent()
|
||||
}
|
||||
|
||||
writeValue(w, v)
|
||||
_ = w.WriteByte(':')
|
||||
w.index++
|
||||
@ -160,7 +161,9 @@ func (w *indexWriter) WriteTagValue(k, v interface{}) {
|
||||
if isZero(v) && !w.zero {
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteTag(k)
|
||||
|
||||
switch {
|
||||
case isPrimative(v):
|
||||
_ = w.WriteByte(' ')
|
||||
@ -212,8 +215,10 @@ func writeEncode(w writer, v string) {
|
||||
if len(v) == 0 {
|
||||
_ = w.WriteByte('"')
|
||||
_ = w.WriteByte('"')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if isQuoted(v) {
|
||||
fmt.Fprintf(w, "%q", v)
|
||||
} else {
|
||||
@ -224,8 +229,10 @@ func writeEncode(w writer, v string) {
|
||||
func writeValue(w writer, v interface{}) {
|
||||
if v == nil {
|
||||
_ = w.WriteByte('~')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
switch v := v.(type) {
|
||||
case bool, int, int64, float64, string:
|
||||
writeScalar(w, v)
|
||||
@ -261,13 +268,16 @@ func writeSequence(w writer, v []interface{}) {
|
||||
if len(v) == 0 {
|
||||
_ = w.WriteByte('[')
|
||||
_ = w.WriteByte(']')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for i, v := range v {
|
||||
if i != 0 {
|
||||
_ = w.WriteByte('\n')
|
||||
w.Indent()
|
||||
}
|
||||
|
||||
_ = w.WriteByte('-')
|
||||
_ = w.WriteByte(' ')
|
||||
w.IndentIncrease()
|
||||
@ -280,13 +290,16 @@ func writeSequenceStr(w writer, v []string) {
|
||||
if len(v) == 0 {
|
||||
_ = w.WriteByte('[')
|
||||
_ = w.WriteByte(']')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for i, v := range v {
|
||||
if i != 0 {
|
||||
_ = w.WriteByte('\n')
|
||||
w.Indent()
|
||||
}
|
||||
|
||||
_ = w.WriteByte('-')
|
||||
_ = w.WriteByte(' ')
|
||||
writeEncode(w, v)
|
||||
@ -297,22 +310,30 @@ func writeMapping(w writer, v map[interface{}]interface{}) {
|
||||
if len(v) == 0 {
|
||||
_ = w.WriteByte('{')
|
||||
_ = w.WriteByte('}')
|
||||
|
||||
return
|
||||
}
|
||||
var keys []string
|
||||
|
||||
keys := make([]string, 0)
|
||||
|
||||
for k := range v {
|
||||
s := fmt.Sprint(k)
|
||||
keys = append(keys, s)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
for i, k := range keys {
|
||||
v := v[k]
|
||||
|
||||
if i != 0 {
|
||||
_ = w.WriteByte('\n')
|
||||
w.Indent()
|
||||
}
|
||||
|
||||
writeEncode(w, k)
|
||||
_ = w.WriteByte(':')
|
||||
|
||||
if v == nil || isPrimative(v) || isZero(v) {
|
||||
_ = w.WriteByte(' ')
|
||||
writeValue(w, v)
|
||||
@ -335,19 +356,26 @@ func writeMappingStr(w writer, v map[string]string) {
|
||||
if len(v) == 0 {
|
||||
_ = w.WriteByte('{')
|
||||
_ = w.WriteByte('}')
|
||||
|
||||
return
|
||||
}
|
||||
var keys []string
|
||||
|
||||
keys := make([]string, 0)
|
||||
|
||||
for k := range v {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
for i, k := range keys {
|
||||
v := v[k]
|
||||
|
||||
if i != 0 {
|
||||
_ = w.WriteByte('\n')
|
||||
w.Indent()
|
||||
}
|
||||
|
||||
writeEncode(w, k)
|
||||
_ = w.WriteByte(':')
|
||||
_ = w.WriteByte(' ')
|
||||
|
@ -13,25 +13,7 @@ import (
|
||||
// this unit tests pretty prints a complex yaml structure
|
||||
// to ensure we have common use cases covered.
|
||||
func TestWriteComplexValue(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
var testComplexValue = `
|
||||
testComplexValue := `
|
||||
a: b
|
||||
c:
|
||||
- d
|
||||
@ -57,3 +39,24 @@ x: ~
|
||||
z: "#y"
|
||||
zz: "\nz\n"
|
||||
"{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)
|
||||
}
|
||||
}
|
||||
|
@ -19,10 +19,13 @@ type (
|
||||
// UnmarshalYAML implements yaml unmarshalling.
|
||||
func (p *Push) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
d := new(push)
|
||||
|
||||
err := unmarshal(&d.Image)
|
||||
if err != nil {
|
||||
err = unmarshal(d)
|
||||
}
|
||||
|
||||
p.Image = d.Image
|
||||
|
||||
return err
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
var ErrInvalidRegistry = errors.New("yaml: invalid registry resource")
|
||||
|
||||
// GetVersion returns the resource 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.
|
||||
func (r *Registry) Validate() error {
|
||||
if len(r.Data) == 0 {
|
||||
return errors.New("yaml: invalid registry resource")
|
||||
return ErrInvalidRegistry
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -5,9 +5,6 @@ package yaml
|
||||
|
||||
import "errors"
|
||||
|
||||
// TODO(bradrydzewski) deprecate Secret
|
||||
// TODO(bradrydzewski) deprecate ExternalData
|
||||
|
||||
type (
|
||||
// Secret is a resource that provides encrypted data
|
||||
// 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.
|
||||
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.
|
||||
func (s *Secret) Validate() error {
|
||||
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
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
var ErrInvalidSignature = errors.New("yaml: invalid signature due to missing hash")
|
||||
|
||||
// GetVersion returns the resource 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.
|
||||
func (s Signature) Validate() error {
|
||||
if s.Hmac == "" {
|
||||
return errors.New("yaml: invalid signature. missing hash")
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ func (b *BytesSize) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var intType int64
|
||||
if err := unmarshal(&intType); err == nil {
|
||||
*b = BytesSize(intType)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -29,6 +30,7 @@ func (b *BytesSize) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
if err == nil {
|
||||
*b = BytesSize(intType)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -34,14 +34,18 @@ func TestBytesSize(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
in := []byte(test.yaml)
|
||||
out := BytesSize(0)
|
||||
|
||||
err := yaml.Unmarshal(in, &out)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if got, want := int64(out), test.size; got != want {
|
||||
t.Errorf("Want byte size %d, got %d", want, got)
|
||||
}
|
||||
|
||||
if got, want := out.String(), test.text; got != want {
|
||||
t.Errorf("Want byte text %s, got %s", want, got)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user