mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
bddb808b25
Fixes all our Go file imports to match the preferred spec that we've _mostly_ been using. For example: ``` import ( "context" "time" "github.com/prometheus/client_golang/prometheus" "golang.org/x/xerrors" "gopkg.in/natefinch/lumberjack.v2" "cdr.dev/slog/v3" "github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/serpent" ) ``` 3 groups: standard library, 3rd partly libs, Coder libs. This PR makes the change across the codebase. The PR in the stack above modifies our formatting to maintain this state of affairs, and is a separate PR so it's possible to review that one in detail.
195 lines
4.7 KiB
Go
195 lines
4.7 KiB
Go
package provisionersdk
|
|
|
|
import (
|
|
"archive/tar"
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
"cdr.dev/slog/v3"
|
|
"github.com/coder/coder/v2/coderd/util/xio"
|
|
)
|
|
|
|
const (
|
|
// TemplateArchiveLimit represents the maximum size of a template in bytes.
|
|
TemplateArchiveLimit = 1 << 20
|
|
)
|
|
|
|
func dirHasExt(dir string, exts ...string) (bool, error) {
|
|
dirEnts, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
for _, fi := range dirEnts {
|
|
for _, ext := range exts {
|
|
if strings.HasSuffix(fi.Name(), ext) {
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func DirHasLockfile(dir string) (bool, error) {
|
|
return dirHasExt(dir, ".terraform.lock.hcl")
|
|
}
|
|
|
|
// Tar archives a Terraform directory.
|
|
func Tar(w io.Writer, logger slog.Logger, directory string, limit int64) error {
|
|
// The total bytes written must be under the limit, so use -1
|
|
w = xio.NewLimitWriter(w, limit-1)
|
|
tarWriter := tar.NewWriter(w)
|
|
|
|
tfExts := []string{".tf", ".tf.json"}
|
|
hasTf, err := dirHasExt(directory, tfExts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !hasTf {
|
|
absPath, err := filepath.Abs(directory)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Show absolute path to aid in debugging. E.g. showing "." is
|
|
// useless.
|
|
return xerrors.Errorf(
|
|
"%s is not a valid template since it has no %s files",
|
|
absPath, tfExts,
|
|
)
|
|
}
|
|
|
|
err = filepath.Walk(directory, func(file string, fileInfo os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var link string
|
|
if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
|
|
link, err = os.Readlink(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
header, err := tar.FileInfoHeader(fileInfo, link)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rel, err := filepath.Rel(directory, file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// We want to allow .terraform.lock.hcl files to be archived. This
|
|
// allows provider plugins to be cached.
|
|
if (strings.HasPrefix(rel, ".") || strings.HasPrefix(filepath.Base(rel), ".")) && filepath.Base(rel) != ".terraform.lock.hcl" {
|
|
if fileInfo.IsDir() && rel != "." {
|
|
// Don't archive hidden files!
|
|
return filepath.SkipDir
|
|
}
|
|
// Don't archive hidden files!
|
|
return nil
|
|
}
|
|
if strings.Contains(rel, ".tfstate") {
|
|
// Don't store tfstate!
|
|
logger.Debug(context.Background(), "skip state", slog.F("name", rel))
|
|
return nil
|
|
}
|
|
if rel == "terraform.tfvars" || rel == "terraform.tfvars.json" || strings.HasSuffix(rel, ".auto.tfvars") || strings.HasSuffix(rel, ".auto.tfvars.json") {
|
|
// Don't store .tfvars, as Coder uses their own variables file.
|
|
logger.Debug(context.Background(), "skip variable definitions", slog.F("name", rel))
|
|
return nil
|
|
}
|
|
// Use unix paths in the tar archive.
|
|
header.Name = filepath.ToSlash(rel)
|
|
// tar.FileInfoHeader() will do this, but filepath.Rel() calls filepath.Clean()
|
|
// which strips trailing path separators for directories.
|
|
if fileInfo.IsDir() {
|
|
header.Name += "/"
|
|
}
|
|
if err := tarWriter.WriteHeader(header); err != nil {
|
|
return err
|
|
}
|
|
if !fileInfo.Mode().IsRegular() {
|
|
return nil
|
|
}
|
|
|
|
data, err := os.Open(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer data.Close()
|
|
_, err = io.Copy(tarWriter, data)
|
|
if err != nil {
|
|
if xerrors.Is(err, xio.ErrLimitReached) {
|
|
return xerrors.Errorf("Archive too big. Must be <= %d bytes", limit)
|
|
}
|
|
return err
|
|
}
|
|
|
|
return data.Close()
|
|
})
|
|
if err != nil {
|
|
if xerrors.Is(err, xio.ErrLimitReached) {
|
|
return xerrors.Errorf("Archive too big. Must be <= %d bytes", limit)
|
|
}
|
|
return err
|
|
}
|
|
err = tarWriter.Flush()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Untar extracts the archive to a provided directory.
|
|
func Untar(directory string, r io.Reader) error {
|
|
tarReader := tar.NewReader(r)
|
|
for {
|
|
header, err := tarReader.Next()
|
|
if xerrors.Is(err, io.EOF) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if header.Name == "." || strings.Contains(header.Name, "..") {
|
|
continue
|
|
}
|
|
// #nosec
|
|
target := filepath.Join(directory, filepath.FromSlash(header.Name))
|
|
switch header.Typeflag {
|
|
case tar.TypeDir:
|
|
if _, err := os.Stat(target); err != nil {
|
|
if err := os.MkdirAll(target, 0o755); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case tar.TypeReg:
|
|
// #nosec G115 - Safe conversion as tar header mode fits within uint32
|
|
err := os.MkdirAll(filepath.Dir(target), os.FileMode(header.Mode)|os.ModeDir|100)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// #nosec G115 - Safe conversion as tar header mode fits within uint32
|
|
file, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.FileMode(header.Mode))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Max file size of 10MB.
|
|
_, err = io.CopyN(file, tarReader, (1<<20)*10)
|
|
if xerrors.Is(err, io.EOF) {
|
|
err = nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_ = file.Close()
|
|
}
|
|
}
|
|
}
|