woodpecker/pkg/yaml/lint.go

193 lines
4.5 KiB
Go

package parser
import (
"fmt"
"path/filepath"
"strings"
common "github.com/drone/drone/pkg/types"
)
// lintRule defines a function that runs lint
// checks against a Yaml Config file. If the rule
// fails it should return an error message.
type lintRule func(*common.Config) error
var lintRules = []lintRule{
expectBuild,
expectImage,
expectCommand,
expectTrustedSetup,
expectTrustedClone,
expectTrustedPublish,
expectTrustedDeploy,
expectTrustedNotify,
expectCloneInWorkspace,
expectCacheInWorkspace,
}
// Lint runs all lint rules against the Yaml Config.
func Lint(c *common.Config) error {
for _, rule := range lintRules {
err := rule(c)
if err != nil {
return err
}
}
return nil
}
// lint rule that fails when no build is defined
func expectBuild(c *common.Config) error {
if c.Build == nil {
return fmt.Errorf("Yaml must define a build section")
}
return nil
}
// lint rule that fails when no build image is defined
func expectImage(c *common.Config) error {
if len(c.Build.Image) == 0 {
return fmt.Errorf("Yaml must define a build image")
}
return nil
}
// lint rule that fails when no build commands are defined
func expectCommand(c *common.Config) error {
if c.Build.Config == nil || c.Build.Config["commands"] == nil {
return fmt.Errorf("Yaml must define build commands")
}
return nil
}
// lint rule that fails when a non-trusted clone plugin is used.
func expectTrustedClone(c *common.Config) error {
if c.Clone != nil && strings.Contains(c.Clone.Image, "/") {
return fmt.Errorf("Yaml must use trusted clone plugins")
}
return nil
}
// lint rule that fails when a non-trusted setup plugin is used.
func expectTrustedSetup(c *common.Config) error {
if c.Setup != nil && strings.Contains(c.Setup.Image, "/") {
return fmt.Errorf("Yaml must use trusted setup plugins")
}
return nil
}
// lint rule that fails when a non-trusted publish plugin is used.
func expectTrustedPublish(c *common.Config) error {
for _, step := range c.Publish {
if strings.Contains(step.Image, "/") {
return fmt.Errorf("Yaml must use trusted publish plugins")
}
}
return nil
}
// lint rule that fails when a non-trusted deploy plugin is used.
func expectTrustedDeploy(c *common.Config) error {
for _, step := range c.Deploy {
if strings.Contains(step.Image, "/") {
return fmt.Errorf("Yaml must use trusted deploy plugins")
}
}
return nil
}
// lint rule that fails when a non-trusted notify plugin is used.
func expectTrustedNotify(c *common.Config) error {
for _, step := range c.Notify {
if strings.Contains(step.Image, "/") {
return fmt.Errorf("Yaml must use trusted notify plugins")
}
}
return nil
}
// lint rule that fails if the clone directory is not contained
// in the root workspace.
func expectCloneInWorkspace(c *common.Config) error {
pathv, ok := c.Clone.Config["path"]
var path string
if ok {
path, _ = pathv.(string)
}
if len(path) == 0 {
// This should only happen if the transformer was not run
return fmt.Errorf("No workspace specified")
}
relative, relOk := filepath.Rel("/drone/src", path)
if relOk != nil {
return fmt.Errorf("Path is not relative to root")
}
cleaned := filepath.Clean(relative)
if strings.Index(cleaned, "../") != -1 {
return fmt.Errorf("Cannot clone above the root")
}
return nil
}
// lint rule that fails if the cache directories are not contained
// in the workspace.
func expectCacheInWorkspace(c *common.Config) error {
for _, step := range c.Build.Cache {
if strings.Index(step, ":") != -1 {
return fmt.Errorf("Cache cannot contain : in the path")
}
cleaned := filepath.Clean(step)
if strings.Index(cleaned, "../") != -1 {
return fmt.Errorf("Cache must point to a path in the workspace")
} else if cleaned == "." {
return fmt.Errorf("Cannot cache the workspace")
}
}
return nil
}
func LintPlugins(c *common.Config, opts *Opts) error {
if len(opts.Whitelist) == 0 {
return nil
}
var images []string
images = append(images, c.Setup.Image)
images = append(images, c.Clone.Image)
for _, step := range c.Publish {
images = append(images, step.Image)
}
for _, step := range c.Deploy {
images = append(images, step.Image)
}
for _, step := range c.Notify {
images = append(images, step.Image)
}
for _, image := range images {
match := false
for _, pattern := range opts.Whitelist {
if pattern == image {
match = true
break
}
ok, err := filepath.Match(pattern, image)
if ok && err == nil {
match = true
break
}
}
if !match {
return fmt.Errorf("Cannot use un-trusted image %s", image)
}
}
return nil
}