@@ -114,6 +114,11 @@ func main() {
|
||||
ArgsUsage: "<api-key>",
|
||||
Action: ArgCountWrapper(1, PublishMetrics),
|
||||
},
|
||||
{
|
||||
Name: "verify-drone",
|
||||
Usage: "Verify Drone configuration",
|
||||
Action: VerifyDrone,
|
||||
},
|
||||
{
|
||||
Name: "export-version",
|
||||
Usage: "Exports version in dist/grafana.version",
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/drone/drone-cli/drone/lint"
|
||||
"github.com/drone/drone-cli/drone/starlark"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/fsutil"
|
||||
cliv1 "github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func VerifyDrone(c *cli.Context) error {
|
||||
const yml = ".drone.yml"
|
||||
const backup = ".drone.yml.bak"
|
||||
|
||||
if err := fsutil.CopyFile(yml, backup); err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("failed to copy %s to %s: %s", yml, backup, err), 1)
|
||||
}
|
||||
defer func() {
|
||||
if err := os.Remove(yml); err != nil {
|
||||
log.Printf("Failed to rename %s to %s", backup, yml)
|
||||
}
|
||||
if err := os.Rename(backup, yml); err != nil {
|
||||
log.Printf("Failed to rename %s to %s", backup, yml)
|
||||
}
|
||||
}()
|
||||
|
||||
flags := &flag.FlagSet{}
|
||||
for _, flag := range starlark.Command.Flags {
|
||||
flag.Apply(flags)
|
||||
}
|
||||
cStarlark := cliv1.NewContext(cliv1.NewApp(), flags, nil)
|
||||
action := starlark.Command.Action.(func(*cliv1.Context))
|
||||
action(cStarlark)
|
||||
|
||||
if err := verifyYAML(yml, backup); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flags = &flag.FlagSet{}
|
||||
for _, flag := range lint.Command.Flags {
|
||||
flag.Apply(flags)
|
||||
}
|
||||
err := flags.Set("trusted", "true")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cLint := cliv1.NewContext(cliv1.NewApp(), flags, nil)
|
||||
actionE := lint.Command.Action.(func(*cliv1.Context) error)
|
||||
if err := actionE(cLint); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("%s is valid", yml)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readConfig(fpath string) ([]map[string]interface{}, error) {
|
||||
//nolint:gosec
|
||||
f, err := os.Open(fpath)
|
||||
if err != nil {
|
||||
return nil, cli.NewExitError(fmt.Sprintf("failed to read %s: %s", fpath, err), 1)
|
||||
}
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
log.Println("error closing file", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// The YAML stream may contain multiple pipeline configurations, read them all
|
||||
dec := yaml.NewDecoder(f)
|
||||
var c []map[string]interface{}
|
||||
for {
|
||||
var m map[string]interface{}
|
||||
if err := dec.Decode(&m); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
return nil, cli.NewExitError(fmt.Sprintf("Failed to decode %s: %s", fpath, err), 1)
|
||||
}
|
||||
|
||||
if m["kind"] == "signature" {
|
||||
log.Printf("Ignoring a signature")
|
||||
continue
|
||||
}
|
||||
|
||||
c = append(c, m)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func verifyYAML(yml, backup string) error {
|
||||
log.Printf("Comparing %s and %s", yml, backup)
|
||||
|
||||
c1, err := readConfig(yml)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c2, err := readConfig(backup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !cmp.Equal(c1, c2) {
|
||||
return cli.NewExitError(fmt.Sprintf("%s is out of sync with .drone.star - regenerate it with drone starlark convert",
|
||||
yml), 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package fsutil_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/fsutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCopyFile(t *testing.T) {
|
||||
src, err := os.CreateTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
if err := os.RemoveAll(src.Name()); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = os.WriteFile(src.Name(), []byte("Contents"), 0600)
|
||||
require.NoError(t, err)
|
||||
|
||||
dst, err := os.CreateTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
if err := os.RemoveAll(dst.Name()); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = fsutil.CopyFile(src.Name(), dst.Name())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCopyFile_Permissions(t *testing.T) {
|
||||
perms := os.FileMode(0700)
|
||||
if runtime.GOOS == "windows" {
|
||||
// Windows doesn't have file Unix style file permissions
|
||||
// It seems you have either 0444 for read-only or 0666 for read-write
|
||||
perms = os.FileMode(0666)
|
||||
}
|
||||
|
||||
src, err := os.CreateTemp("", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
if err := os.RemoveAll(src.Name()); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = os.WriteFile(src.Name(), []byte("Contents"), perms)
|
||||
require.NoError(t, err)
|
||||
err = os.Chmod(src.Name(), perms)
|
||||
require.NoError(t, err)
|
||||
|
||||
dst, err := os.CreateTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
if err := os.RemoveAll(dst.Name()); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = fsutil.CopyFile(src.Name(), dst.Name())
|
||||
require.NoError(t, err)
|
||||
|
||||
fi, err := os.Stat(dst.Name())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, perms, fi.Mode()&os.ModePerm)
|
||||
}
|
||||
|
||||
// Test case where destination directory doesn't exist.
|
||||
func TestCopyFile_NonExistentDestDir(t *testing.T) {
|
||||
src, err := os.CreateTemp("", "")
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
if err := os.RemoveAll(src.Name()); err != nil {
|
||||
t.Log(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = fsutil.CopyFile(src.Name(), "non-existent/dest")
|
||||
require.EqualError(t, err, "destination directory doesn't exist: \"non-existent\"")
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// CopyFile copies a file from src to dst.
|
||||
//
|
||||
// If src and dst files exist, and are the same, then return success. Otherwise, attempt to create a hard link
|
||||
// between the two files. If that fails, copy the file contents from src to dst.
|
||||
func CopyFile(src, dst string) (err error) {
|
||||
absSrc, err := filepath.Abs(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get absolute path of source file %q: %w", src, err)
|
||||
}
|
||||
sfi, err := os.Stat(src)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("couldn't stat source file %q: %w", absSrc, err)
|
||||
return
|
||||
}
|
||||
if !sfi.Mode().IsRegular() {
|
||||
// Cannot copy non-regular files (e.g., directories, symlinks, devices, etc.)
|
||||
return fmt.Errorf("non-regular source file %s (%q)", absSrc, sfi.Mode().String())
|
||||
}
|
||||
dpath := filepath.Dir(dst)
|
||||
exists, err := Exists(dpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
err = fmt.Errorf("destination directory doesn't exist: %q", dpath)
|
||||
return
|
||||
}
|
||||
|
||||
var dfi os.FileInfo
|
||||
dfi, err = os.Stat(dst)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if !(dfi.Mode().IsRegular()) {
|
||||
return fmt.Errorf("non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String())
|
||||
}
|
||||
if os.SameFile(sfi, dfi) {
|
||||
return copyPermissions(sfi.Name(), dfi.Name())
|
||||
}
|
||||
}
|
||||
|
||||
err = copyFileContents(src, dst)
|
||||
return err
|
||||
}
|
||||
|
||||
// copyFileContents copies the contents of the file named src to the file named
|
||||
// by dst. The file will be created if it does not already exist. If the
|
||||
// destination file exists, all it's contents will be replaced by the contents
|
||||
// of the source file.
|
||||
func copyFileContents(src, dst string) (err error) {
|
||||
//nolint:gosec
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := in.Close(); err != nil {
|
||||
log.Println("error closing file", err)
|
||||
}
|
||||
}()
|
||||
|
||||
//nolint:gosec
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if cerr := out.Close(); cerr != nil && err == nil {
|
||||
err = cerr
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err = io.Copy(out, in); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := out.Sync(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return copyPermissions(src, dst)
|
||||
}
|
||||
|
||||
func copyPermissions(src, dst string) error {
|
||||
sfi, err := os.Lstat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Chmod(dst, sfi.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package fsutil_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/build/fsutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestExists_NonExistent(t *testing.T) {
|
||||
exists, err := fsutil.Exists("non-existent")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.False(t, exists)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package fsutil
|
||||
|
||||
import "os"
|
||||
|
||||
// Exists determines whether a file/directory exists or not.
|
||||
func Exists(fpath string) (bool, error) {
|
||||
_, err := os.Stat(fpath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
Reference in New Issue
Block a user