Bump github.com/hashicorp/terraform-plugin-sdk/v2 from 2.24.1 to 2.26.0

Bumps [github.com/hashicorp/terraform-plugin-sdk/v2](https://github.com/hashicorp/terraform-plugin-sdk) from 2.24.1 to 2.26.0.
- [Release notes](https://github.com/hashicorp/terraform-plugin-sdk/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-sdk/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-sdk/compare/v2.24.1...v2.26.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-sdk/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2023-03-20 20:25:53 +00:00
committed by GitHub
parent 4f33464489
commit 84c9110a24
502 changed files with 39722 additions and 9679 deletions

View File

@ -8,7 +8,7 @@ import (
)
// setColorization will mutate the values of this logger
// to approperately configure colorization options. It provides
// to appropriately configure colorization options. It provides
// a wrapper to the output stream on Windows systems.
func (l *intLogger) setColorization(opts *LoggerOptions) {
switch opts.Color {

View File

@ -11,7 +11,7 @@ import (
)
// setColorization will mutate the values of this logger
// to approperately configure colorization options. It provides
// to appropriately configure colorization options. It provides
// a wrapper to the output stream on Windows systems.
func (l *intLogger) setColorization(opts *LoggerOptions) {
switch opts.Color {

View File

@ -20,13 +20,13 @@ var (
)
// Default returns a globally held logger. This can be a good starting
// place, and then you can use .With() and .Name() to create sub-loggers
// place, and then you can use .With() and .Named() to create sub-loggers
// to be used in more specific contexts.
// The value of the Default logger can be set via SetDefault() or by
// changing the options in DefaultOptions.
//
// This method is goroutine safe, returning a global from memory, but
// cause should be used if SetDefault() is called it random times
// care should be used if SetDefault() is called it random times
// in the program as that may result in race conditions and an unexpected
// Logger being returned.
func Default() Logger {

View File

@ -17,6 +17,8 @@ import (
"sync"
"sync/atomic"
"time"
"unicode"
"unicode/utf8"
"github.com/fatih/color"
)
@ -48,6 +50,12 @@ var (
Warn: color.New(color.FgHiYellow),
Error: color.New(color.FgHiRed),
}
faintBoldColor = color.New(color.Faint, color.Bold)
faintColor = color.New(color.Faint)
faintMultiLinePrefix = faintColor.Sprint(" | ")
faintFieldSeparator = faintColor.Sprint("=")
faintFieldSeparatorWithNewLine = faintColor.Sprint("=\n")
)
// Make sure that intLogger is a Logger
@ -70,6 +78,7 @@ type intLogger struct {
level *int32
headerColor ColorOption
fieldColor ColorOption
implied []interface{}
@ -115,14 +124,19 @@ func newLogger(opts *LoggerOptions) *intLogger {
mutex = new(sync.Mutex)
}
var primaryColor, headerColor ColorOption
if opts.ColorHeaderOnly {
primaryColor = ColorOff
var (
primaryColor ColorOption = ColorOff
headerColor ColorOption = ColorOff
fieldColor ColorOption = ColorOff
)
switch {
case opts.ColorHeaderOnly:
headerColor = opts.Color
} else {
case opts.ColorHeaderAndFields:
fieldColor = opts.Color
headerColor = opts.Color
default:
primaryColor = opts.Color
headerColor = ColorOff
}
l := &intLogger{
@ -137,6 +151,7 @@ func newLogger(opts *LoggerOptions) *intLogger {
exclude: opts.Exclude,
independentLevels: opts.IndependentLevels,
headerColor: headerColor,
fieldColor: fieldColor,
}
if opts.IncludeLocation {
l.callerOffset = offsetIntLogger + opts.AdditionalLocationOffset
@ -160,7 +175,7 @@ func newLogger(opts *LoggerOptions) *intLogger {
}
// offsetIntLogger is the stack frame offset in the call stack for the caller to
// one of the Warn,Info,Log,etc methods.
// one of the Warn, Info, Log, etc methods.
const offsetIntLogger = 3
// Log a message and a set of key/value pairs if the given level is at
@ -235,7 +250,17 @@ func needsQuoting(str string) bool {
return false
}
// Non-JSON logging format function
// logPlain is the non-JSON logging format function which writes directly
// to the underlying writer the logger was initialized with.
//
// If the logger was initialized with a color function, it also handles
// applying the color to the log message.
//
// Color Options
// 1. No color.
// 2. Color the whole log line, based on the level.
// 3. Color only the header (level) part of the log line.
// 4. Color both the header and fields of the log line.
func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string, args ...interface{}) {
if !l.disableTime {
@ -269,16 +294,19 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string,
if name != "" {
l.writer.WriteString(name)
l.writer.WriteString(": ")
if msg != "" {
l.writer.WriteString(": ")
l.writer.WriteString(msg)
}
} else if msg != "" {
l.writer.WriteString(msg)
}
l.writer.WriteString(msg)
args = append(l.implied, args...)
var stacktrace CapturedStacktrace
if args != nil && len(args) > 0 {
if len(args) > 0 {
if len(args)%2 != 0 {
cs, ok := args[len(args)-1].(CapturedStacktrace)
if ok {
@ -292,13 +320,16 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string,
l.writer.WriteByte(':')
// Handle the field arguments, which come in pairs (key=val).
FOR:
for i := 0; i < len(args); i = i + 2 {
var (
key string
val string
raw bool
)
// Convert the field value to a string.
switch st := args[i+1].(type) {
case string:
val = st
@ -350,8 +381,7 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string,
}
}
var key string
// Convert the field key to a string.
switch st := args[i].(type) {
case string:
key = st
@ -359,21 +389,49 @@ func (l *intLogger) logPlain(t time.Time, name string, level Level, msg string,
key = fmt.Sprintf("%s", st)
}
// Optionally apply the ANSI "faint" and "bold"
// SGR values to the key.
if l.fieldColor != ColorOff {
key = faintBoldColor.Sprint(key)
}
// Values may contain multiple lines, and that format
// is preserved, with each line prefixed with a " | "
// to show it's part of a collection of lines.
//
// Values may also need quoting, if not all the runes
// in the value string are "normal", like if they
// contain ANSI escape sequences.
if strings.Contains(val, "\n") {
l.writer.WriteString("\n ")
l.writer.WriteString(key)
l.writer.WriteString("=\n")
writeIndent(l.writer, val, " | ")
if l.fieldColor != ColorOff {
l.writer.WriteString(faintFieldSeparatorWithNewLine)
writeIndent(l.writer, val, faintMultiLinePrefix)
} else {
l.writer.WriteString("=\n")
writeIndent(l.writer, val, " | ")
}
l.writer.WriteString(" ")
} else if !raw && needsQuoting(val) {
l.writer.WriteByte(' ')
l.writer.WriteString(key)
l.writer.WriteByte('=')
l.writer.WriteString(strconv.Quote(val))
if l.fieldColor != ColorOff {
l.writer.WriteString(faintFieldSeparator)
} else {
l.writer.WriteByte('=')
}
l.writer.WriteByte('"')
writeEscapedForOutput(l.writer, val, true)
l.writer.WriteByte('"')
} else {
l.writer.WriteByte(' ')
l.writer.WriteString(key)
l.writer.WriteByte('=')
if l.fieldColor != ColorOff {
l.writer.WriteString(faintFieldSeparator)
} else {
l.writer.WriteByte('=')
}
l.writer.WriteString(val)
}
}
@ -393,19 +451,98 @@ func writeIndent(w *writer, str string, indent string) {
if nl == -1 {
if str != "" {
w.WriteString(indent)
w.WriteString(str)
writeEscapedForOutput(w, str, false)
w.WriteString("\n")
}
return
}
w.WriteString(indent)
w.WriteString(str[:nl])
writeEscapedForOutput(w, str[:nl], false)
w.WriteString("\n")
str = str[nl+1:]
}
}
func needsEscaping(str string) bool {
for _, b := range str {
if !unicode.IsPrint(b) || b == '"' {
return true
}
}
return false
}
const (
lowerhex = "0123456789abcdef"
)
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func writeEscapedForOutput(w io.Writer, str string, escapeQuotes bool) {
if !needsEscaping(str) {
w.Write([]byte(str))
return
}
bb := bufPool.Get().(*bytes.Buffer)
bb.Reset()
defer bufPool.Put(bb)
for _, r := range str {
if escapeQuotes && r == '"' {
bb.WriteString(`\"`)
} else if unicode.IsPrint(r) {
bb.WriteRune(r)
} else {
switch r {
case '\a':
bb.WriteString(`\a`)
case '\b':
bb.WriteString(`\b`)
case '\f':
bb.WriteString(`\f`)
case '\n':
bb.WriteString(`\n`)
case '\r':
bb.WriteString(`\r`)
case '\t':
bb.WriteString(`\t`)
case '\v':
bb.WriteString(`\v`)
default:
switch {
case r < ' ':
bb.WriteString(`\x`)
bb.WriteByte(lowerhex[byte(r)>>4])
bb.WriteByte(lowerhex[byte(r)&0xF])
case !utf8.ValidRune(r):
r = 0xFFFD
fallthrough
case r < 0x10000:
bb.WriteString(`\u`)
for s := 12; s >= 0; s -= 4 {
bb.WriteByte(lowerhex[r>>uint(s)&0xF])
}
default:
bb.WriteString(`\U`)
for s := 28; s >= 0; s -= 4 {
bb.WriteByte(lowerhex[r>>uint(s)&0xF])
}
}
}
}
}
w.Write(bb.Bytes())
}
func (l *intLogger) renderSlice(v reflect.Value) string {
var buf bytes.Buffer
@ -707,6 +844,11 @@ func (l *intLogger) SetLevel(level Level) {
atomic.StoreInt32(l.level, int32(level))
}
// Returns the current level
func (l *intLogger) GetLevel() Level {
return Level(atomic.LoadInt32(l.level))
}
// Create a *log.Logger that will send it's data through this Logger. This
// allows packages that expect to be using the standard library log to actually
// use this logger.

View File

@ -9,7 +9,7 @@ import (
)
var (
//DefaultOutput is used as the default log output.
// DefaultOutput is used as the default log output.
DefaultOutput io.Writer = os.Stderr
// DefaultLevel is used as the default log level.
@ -28,7 +28,7 @@ const (
// of actions in code, such as function enters/exits, etc.
Trace Level = 1
// Debug information for programmer lowlevel analysis.
// Debug information for programmer low-level analysis.
Debug Level = 2
// Info information about steady state operations.
@ -44,13 +44,13 @@ const (
Off Level = 6
)
// Format is a simple convience type for when formatting is required. When
// Format is a simple convenience type for when formatting is required. When
// processing a value of this type, the logger automatically treats the first
// argument as a Printf formatting string and passes the rest as the values
// to be formatted. For example: L.Info(Fmt{"%d beans/day", beans}).
type Format []interface{}
// Fmt returns a Format type. This is a convience function for creating a Format
// Fmt returns a Format type. This is a convenience function for creating a Format
// type.
func Fmt(str string, args ...interface{}) Format {
return append(Format{str}, args...)
@ -134,7 +134,7 @@ func (l Level) String() string {
}
}
// Logger describes the interface that must be implemeted by all loggers.
// Logger describes the interface that must be implemented by all loggers.
type Logger interface {
// Args are alternating key, val pairs
// keys must be strings
@ -198,6 +198,9 @@ type Logger interface {
// implementation cannot update the level on the fly, it should no-op.
SetLevel(level Level)
// Returns the current level
GetLevel() Level
// Return a value that conforms to the stdlib log.Logger interface
StandardLogger(opts *StandardLoggerOptions) *log.Logger
@ -236,7 +239,7 @@ type LoggerOptions struct {
// Name of the subsystem to prefix logs with
Name string
// The threshold for the logger. Anything less severe is supressed
// The threshold for the logger. Anything less severe is suppressed
Level Level
// Where to write the logs to. Defaults to os.Stderr if nil
@ -267,13 +270,17 @@ type LoggerOptions struct {
// because setting TimeFormat to empty assumes the default format.
DisableTime bool
// Color the output. On Windows, colored logs are only avaiable for io.Writers that
// Color the output. On Windows, colored logs are only available for io.Writers that
// are concretely instances of *os.File.
Color ColorOption
// Only color the header, not the body. This can help with readability of long messages.
ColorHeaderOnly bool
// Color the header and message body fields. This can help with readability
// of long messages with multiple fields.
ColorHeaderAndFields bool
// A function which is called with the log information and if it returns true the value
// should not be logged.
// This is useful when interacting with a system that you wish to suppress the log
@ -282,8 +289,8 @@ type LoggerOptions struct {
// IndependentLevels causes subloggers to be created with an independent
// copy of this logger's level. This means that using SetLevel on this
// logger will not effect any subloggers, and SetLevel on any subloggers
// will not effect the parent or sibling loggers.
// logger will not affect any subloggers, and SetLevel on any subloggers
// will not affect the parent or sibling loggers.
IndependentLevels bool
}

View File

@ -49,6 +49,8 @@ func (l *nullLogger) ResetNamed(name string) Logger { return l }
func (l *nullLogger) SetLevel(level Level) {}
func (l *nullLogger) GetLevel() Level { return NoLevel }
func (l *nullLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger {
return log.New(l.StandardWriter(opts), "", log.LstdFlags)
}

View File

@ -1,3 +1,15 @@
## v1.4.8
BUG FIXES:
* Fix windows build: [[GH-227](https://github.com/hashicorp/go-plugin/pull/227)]
## v1.4.7
ENHANCEMENTS:
* More detailed error message on plugin start failure: [[GH-223](https://github.com/hashicorp/go-plugin/pull/223)]
## v1.4.6
BUG FIXES:

View File

@ -26,6 +26,14 @@ import (
"google.golang.org/grpc"
)
const unrecognizedRemotePluginMessage = `Unrecognized remote plugin message: %s
This usually means
the plugin was not compiled for this architecture,
the plugin is missing dynamic-link libraries necessary to run,
the plugin is not executable by this process due to file permissions, or
the plugin failed to negotiate the initial go-plugin protocol handshake
%s`
// If this is 1, then we've called CleanupClients. This can be used
// by plugin RPC implementations to change error behavior since you
// can expected network connection errors at this point. This should be
@ -473,7 +481,17 @@ func (c *Client) Kill() {
c.l.Unlock()
}
// Starts the underlying subprocess, communicating with it to negotiate
// peTypes is a list of Portable Executable (PE) machine types from https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
// mapped to GOARCH types. It is not comprehensive, and only includes machine types that Go supports.
var peTypes = map[uint16]string{
0x14c: "386",
0x1c0: "arm",
0x6264: "loong64",
0x8664: "amd64",
0xaa64: "arm64",
}
// Start the underlying subprocess, communicating with it to negotiate
// a port for RPC connections, and returning the address to connect via RPC.
//
// This method is safe to call multiple times. Subsequent calls have no effect.
@ -697,10 +715,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
line = strings.TrimSpace(line)
parts := strings.SplitN(line, "|", 6)
if len(parts) < 4 {
err = fmt.Errorf(
"Unrecognized remote plugin message: %s\n\n"+
"This usually means that the plugin is either invalid or simply\n"+
"needs to be recompiled to support the latest protocol.", line)
err = fmt.Errorf(unrecognizedRemotePluginMessage, line, additionalNotesAboutCommand(cmd.Path))
return
}

64
vendor/github.com/hashicorp/go-plugin/notes_unix.go generated vendored Normal file
View File

@ -0,0 +1,64 @@
//go:build !windows
// +build !windows
package plugin
import (
"debug/elf"
"debug/macho"
"debug/pe"
"fmt"
"os"
"os/user"
"runtime"
"strconv"
"syscall"
)
// additionalNotesAboutCommand tries to get additional information about a command that might help diagnose
// why it won't run correctly. It runs as a best effort only.
func additionalNotesAboutCommand(path string) string {
notes := ""
stat, err := os.Stat(path)
if err != nil {
return notes
}
notes += "\nAdditional notes about plugin:\n"
notes += fmt.Sprintf(" Path: %s\n", path)
notes += fmt.Sprintf(" Mode: %s\n", stat.Mode())
statT, ok := stat.Sys().(*syscall.Stat_t)
if ok {
currentUsername := "?"
if u, err := user.LookupId(strconv.FormatUint(uint64(os.Getuid()), 10)); err == nil {
currentUsername = u.Username
}
currentGroup := "?"
if g, err := user.LookupGroupId(strconv.FormatUint(uint64(os.Getgid()), 10)); err == nil {
currentGroup = g.Name
}
username := "?"
if u, err := user.LookupId(strconv.FormatUint(uint64(statT.Uid), 10)); err == nil {
username = u.Username
}
group := "?"
if g, err := user.LookupGroupId(strconv.FormatUint(uint64(statT.Gid), 10)); err == nil {
group = g.Name
}
notes += fmt.Sprintf(" Owner: %d [%s] (current: %d [%s])\n", statT.Uid, username, os.Getuid(), currentUsername)
notes += fmt.Sprintf(" Group: %d [%s] (current: %d [%s])\n", statT.Gid, group, os.Getgid(), currentGroup)
}
if elfFile, err := elf.Open(path); err == nil {
notes += fmt.Sprintf(" ELF architecture: %s (current architecture: %s)\n", elfFile.Machine, runtime.GOARCH)
} else if machoFile, err := macho.Open(path); err == nil {
notes += fmt.Sprintf(" MachO architecture: %s (current architecture: %s)\n", machoFile.Cpu, runtime.GOARCH)
} else if peFile, err := pe.Open(path); err == nil {
machine, ok := peTypes[peFile.Machine]
if !ok {
machine = "unknown"
}
notes += fmt.Sprintf(" PE architecture: %s (current architecture: %s)\n", machine, runtime.GOARCH)
}
return notes
}

40
vendor/github.com/hashicorp/go-plugin/notes_windows.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
//go:build windows
// +build windows
package plugin
import (
"debug/elf"
"debug/macho"
"debug/pe"
"fmt"
"os"
"runtime"
)
// additionalNotesAboutCommand tries to get additional information about a command that might help diagnose
// why it won't run correctly. It runs as a best effort only.
func additionalNotesAboutCommand(path string) string {
notes := ""
stat, err := os.Stat(path)
if err != nil {
return notes
}
notes += "\nAdditional notes about plugin:\n"
notes += fmt.Sprintf(" Path: %s\n", path)
notes += fmt.Sprintf(" Mode: %s\n", stat.Mode())
if elfFile, err := elf.Open(path); err == nil {
notes += fmt.Sprintf(" ELF architecture: %s (current architecture: %s)\n", elfFile.Machine, runtime.GOARCH)
} else if machoFile, err := macho.Open(path); err == nil {
notes += fmt.Sprintf(" MachO architecture: %s (current architecture: %s)\n", machoFile.Cpu, runtime.GOARCH)
} else if peFile, err := pe.Open(path); err == nil {
machine, ok := peTypes[peFile.Machine]
if !ok {
machine = "unknown"
}
notes += fmt.Sprintf(" PE architecture: %s (current architecture: %s)\n", machine, runtime.GOARCH)
}
return notes
}

View File

@ -1 +1 @@
1.17.3
1.19.5

View File

@ -1,29 +0,0 @@
project_name: tfinstall
builds:
- env:
- CGO_ENABLED=0
main: ./cmd/hcinstall/main.go
mod_timestamp: '{{ .CommitTimestamp }}'
id: "tfinstall"
binary: tfinstall
flags:
- -trimpath
ldflags:
- '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}'
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm
- arm64
archives:
- files: []
format: zip
name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}'
checksum:
name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS'
algorithm: sha256
changelog:
skip: true

View File

@ -1,3 +1,5 @@
Copyright (c) 2020 HashiCorp, Inc.
Mozilla Public License Version 2.0
==================================

View File

@ -123,7 +123,10 @@ func (lv *LatestVersion) Install(ctx context.Context) (string, error) {
if lv.ArmoredPublicKey != "" {
d.ArmoredPublicKey = lv.ArmoredPublicKey
}
err = d.DownloadAndUnpack(ctx, pv, dstDir)
zipFilePath, err := d.DownloadAndUnpack(ctx, pv, dstDir)
if zipFilePath != "" {
lv.pathsToRemove = append(lv.pathsToRemove, zipFilePath)
}
if err != nil {
return "", err
}

View File

@ -11,6 +11,7 @@ import (
"path/filepath"
"github.com/hashicorp/go-version"
"golang.org/x/mod/modfile"
)
var discardLogger = log.New(ioutil.Discard, "", 0)
@ -37,23 +38,47 @@ func (gb *GoBuild) log() *log.Logger {
// Build runs "go build" within a given repo to produce binaryName in targetDir
func (gb *GoBuild) Build(ctx context.Context, repoDir, targetDir, binaryName string) (string, error) {
goCmd, cleanupFunc, err := gb.ensureRequiredGoVersion(ctx, repoDir)
reqGo, err := gb.ensureRequiredGoVersion(ctx, repoDir)
if err != nil {
return "", err
}
defer cleanupFunc(ctx)
defer reqGo.CleanupFunc(ctx)
goArgs := []string{"build", "-o", filepath.Join(targetDir, binaryName)}
if reqGo.Version == nil {
gb.logger.Println("building using default available Go")
} else {
gb.logger.Printf("building using Go %s", reqGo.Version)
}
// `go build` would download dependencies as a side effect, but we attempt
// to do it early in a separate step, such that we can easily distinguish
// network failures from build failures.
//
// Note, that `go mod download` was introduced in Go 1.11
// See https://github.com/golang/go/commit/9f4ea6c2
minGoVersion := version.Must(version.NewVersion("1.11"))
if reqGo.Version.GreaterThanOrEqual(minGoVersion) {
downloadArgs := []string{"mod", "download"}
gb.log().Printf("executing %s %q in %q", reqGo.Cmd, downloadArgs, repoDir)
cmd := exec.CommandContext(ctx, reqGo.Cmd, downloadArgs...)
cmd.Dir = repoDir
out, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("unable to download dependencies: %w\n%s", err, out)
}
}
buildArgs := []string{"build", "-o", filepath.Join(targetDir, binaryName)}
if gb.DetectVendoring {
vendorDir := filepath.Join(repoDir, "vendor")
if fi, err := os.Stat(vendorDir); err == nil && fi.IsDir() {
goArgs = append(goArgs, "-mod", "vendor")
buildArgs = append(buildArgs, "-mod", "vendor")
}
}
gb.log().Printf("executing %s %q in %q", goCmd, goArgs, repoDir)
cmd := exec.CommandContext(ctx, goCmd, goArgs...)
gb.log().Printf("executing %s %q in %q", reqGo.Cmd, buildArgs, repoDir)
cmd := exec.CommandContext(ctx, reqGo.Cmd, buildArgs...)
cmd.Dir = repoDir
out, err := cmd.CombinedOutput()
if err != nil {
@ -71,35 +96,59 @@ func (gb *GoBuild) Remove(ctx context.Context) error {
return os.RemoveAll(gb.pathToRemove)
}
func (gb *GoBuild) ensureRequiredGoVersion(ctx context.Context, repoDir string) (string, CleanupFunc, error) {
type Go struct {
Cmd string
CleanupFunc CleanupFunc
Version *version.Version
}
func (gb *GoBuild) ensureRequiredGoVersion(ctx context.Context, repoDir string) (Go, error) {
cmdName := "go"
noopCleanupFunc := func(context.Context) {}
var installedVersion *version.Version
if gb.Version != nil {
gb.logger.Printf("attempting to satisfy explicit requirement for Go %s", gb.Version)
goVersion, err := GetGoVersion(ctx)
if err != nil {
return cmdName, noopCleanupFunc, err
return Go{
Cmd: cmdName,
CleanupFunc: noopCleanupFunc,
}, err
}
if !goVersion.GreaterThanOrEqual(gb.Version) {
// found incompatible version, try downloading the desired one
return gb.installGoVersion(ctx, gb.Version)
}
installedVersion = goVersion
}
if requiredVersion, ok := guessRequiredGoVersion(repoDir); ok {
gb.logger.Printf("attempting to satisfy guessed Go requirement %s", requiredVersion)
goVersion, err := GetGoVersion(ctx)
if err != nil {
return cmdName, noopCleanupFunc, err
return Go{
Cmd: cmdName,
CleanupFunc: noopCleanupFunc,
}, err
}
if !goVersion.GreaterThanOrEqual(requiredVersion) {
// found incompatible version, try downloading the desired one
return gb.installGoVersion(ctx, requiredVersion)
}
installedVersion = goVersion
} else {
gb.logger.Println("unable to guess Go requirement")
}
return cmdName, noopCleanupFunc, nil
return Go{
Cmd: cmdName,
CleanupFunc: noopCleanupFunc,
Version: installedVersion,
}, nil
}
// CleanupFunc represents a function to be called once Go is no longer needed
@ -119,5 +168,26 @@ func guessRequiredGoVersion(repoDir string) (*version.Version, bool) {
}
return requiredVersion, true
}
goModFile := filepath.Join(repoDir, "go.mod")
if fi, err := os.Stat(goModFile); err == nil && !fi.IsDir() {
b, err := ioutil.ReadFile(goModFile)
if err != nil {
return nil, false
}
f, err := modfile.ParseLax(fi.Name(), b, nil)
if err != nil {
return nil, false
}
if f.Go == nil {
return nil, false
}
requiredVersion, err := version.NewVersion(f.Go.Version)
if err != nil {
return nil, false
}
return requiredVersion, true
}
return nil, false
}

View File

@ -12,28 +12,36 @@ import (
// installGoVersion installs given version of Go using Go
// according to https://golang.org/doc/manage-install
func (gb *GoBuild) installGoVersion(ctx context.Context, v *version.Version) (string, CleanupFunc, error) {
// trim 0 patch versions as that's how Go does it :shrug:
shortVersion := strings.TrimSuffix(v.String(), ".0")
func (gb *GoBuild) installGoVersion(ctx context.Context, v *version.Version) (Go, error) {
versionString := v.Core().String()
// trim 0 patch versions as that's how Go does it :shrug:
shortVersion := strings.TrimSuffix(versionString, ".0")
pkgURL := fmt.Sprintf("golang.org/dl/go%s", shortVersion)
gb.log().Printf("go getting %q", pkgURL)
cmd := exec.CommandContext(ctx, "go", "get", pkgURL)
out, err := cmd.CombinedOutput()
if err != nil {
return "", nil, fmt.Errorf("unable to install Go %s: %w\n%s", v, err, out)
return Go{}, fmt.Errorf("unable to get Go %s: %w\n%s", v, err, out)
}
gb.log().Printf("go installing %q", pkgURL)
cmd = exec.CommandContext(ctx, "go", "install", pkgURL)
out, err = cmd.CombinedOutput()
if err != nil {
return Go{}, fmt.Errorf("unable to install Go %s: %w\n%s", v, err, out)
}
cmdName := fmt.Sprintf("go%s", shortVersion)
gb.log().Printf("downloading go %q", shortVersion)
gb.log().Printf("downloading go %q", v)
cmd = exec.CommandContext(ctx, cmdName, "download")
out, err = cmd.CombinedOutput()
if err != nil {
return "", nil, fmt.Errorf("unable to download Go %s: %w\n%s", v, err, out)
return Go{}, fmt.Errorf("unable to download Go %s: %w\n%s", v, err, out)
}
gb.log().Printf("download of go %q finished", shortVersion)
gb.log().Printf("download of go %q finished", v)
cleanupFunc := func(ctx context.Context) {
cmd = exec.CommandContext(ctx, cmdName, "env", "GOROOT")
@ -49,5 +57,9 @@ func (gb *GoBuild) installGoVersion(ctx context.Context, v *version.Version) (st
}
}
return cmdName, cleanupFunc, nil
return Go{
Cmd: cmdName,
CleanupFunc: cleanupFunc,
Version: v,
}, nil
}

View File

@ -5,7 +5,7 @@ import (
"net/http"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/hc-install/internal/version"
"github.com/hashicorp/hc-install/version"
)
// NewHTTPClient provides a pre-configured http.Client
@ -13,7 +13,7 @@ import (
func NewHTTPClient() *http.Client {
client := cleanhttp.DefaultClient()
userAgent := fmt.Sprintf("hc-install/%s", version.ModuleVersion())
userAgent := fmt.Sprintf("hc-install/%s", version.Version())
cli := cleanhttp.DefaultPooledClient()
cli.Transport = &userAgentRoundTripper{

View File

@ -2,11 +2,13 @@ package releasesjson
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"
@ -42,7 +44,7 @@ func HashSumFromHexDigest(hexDigest string) (HashSum, error) {
return HashSum(sumBytes), nil
}
func (cd *ChecksumDownloader) DownloadAndVerifyChecksums() (ChecksumFileMap, error) {
func (cd *ChecksumDownloader) DownloadAndVerifyChecksums(ctx context.Context) (ChecksumFileMap, error) {
sigFilename, err := cd.findSigFilename(cd.ProductVersion)
if err != nil {
return nil, err
@ -54,7 +56,12 @@ func (cd *ChecksumDownloader) DownloadAndVerifyChecksums() (ChecksumFileMap, err
url.PathEscape(cd.ProductVersion.RawVersion),
url.PathEscape(sigFilename))
cd.Logger.Printf("downloading signature from %s", sigURL)
sigResp, err := client.Get(sigURL)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, sigURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request for %q: %w", sigURL, err)
}
sigResp, err := client.Do(req)
if err != nil {
return nil, err
}
@ -70,7 +77,12 @@ func (cd *ChecksumDownloader) DownloadAndVerifyChecksums() (ChecksumFileMap, err
url.PathEscape(cd.ProductVersion.RawVersion),
url.PathEscape(cd.ProductVersion.SHASUMS))
cd.Logger.Printf("downloading checksums from %s", shasumsURL)
sumsResp, err := client.Get(shasumsURL)
req, err = http.NewRequestWithContext(ctx, http.MethodGet, shasumsURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request for %q: %w", shasumsURL, err)
}
sumsResp, err := client.Do(req)
if err != nil {
return nil, err
}

View File

@ -8,10 +8,12 @@ import (
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/hashicorp/hc-install/internal/httpclient"
)
@ -23,14 +25,14 @@ type Downloader struct {
BaseURL string
}
func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion, dstDir string) error {
func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion, dstDir string) (zipFilePath string, err error) {
if len(pv.Builds) == 0 {
return fmt.Errorf("no builds found for %s %s", pv.Name, pv.Version)
return "", fmt.Errorf("no builds found for %s %s", pv.Name, pv.Version)
}
pb, ok := pv.Builds.FilterBuild(runtime.GOOS, runtime.GOARCH, "zip")
if !ok {
return fmt.Errorf("no ZIP archive found for %s %s %s/%s",
return "", fmt.Errorf("no ZIP archive found for %s %s %s/%s",
pv.Name, pv.Version, runtime.GOOS, runtime.GOARCH)
}
@ -42,14 +44,14 @@ func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion,
Logger: d.Logger,
ArmoredPublicKey: d.ArmoredPublicKey,
}
verifiedChecksums, err := v.DownloadAndVerifyChecksums()
verifiedChecksums, err := v.DownloadAndVerifyChecksums(ctx)
if err != nil {
return err
return "", err
}
var ok bool
verifiedChecksum, ok = verifiedChecksums[pb.Filename]
if !ok {
return fmt.Errorf("no checksum found for %q", pb.Filename)
return "", fmt.Errorf("no checksum found for %q", pb.Filename)
}
}
@ -61,12 +63,12 @@ func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion,
// are still pointing to the mock server if one is set
baseURL, err := url.Parse(d.BaseURL)
if err != nil {
return err
return "", err
}
u, err := url.Parse(archiveURL)
if err != nil {
return err
return "", err
}
u.Scheme = baseURL.Scheme
u.Host = baseURL.Host
@ -74,13 +76,18 @@ func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion,
}
d.Logger.Printf("downloading archive from %s", archiveURL)
resp, err := client.Get(archiveURL)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, archiveURL, nil)
if err != nil {
return err
return "", fmt.Errorf("failed to create request for %q: %w", archiveURL, err)
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
if resp.StatusCode != 200 {
return fmt.Errorf("failed to download ZIP archive from %q: %s", archiveURL, resp.Status)
return "", fmt.Errorf("failed to download ZIP archive from %q: %s", archiveURL, resp.Status)
}
defer resp.Body.Close()
@ -90,7 +97,7 @@ func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion,
contentType := resp.Header.Get("content-type")
if !contentTypeIsZip(contentType) {
return fmt.Errorf("unexpected content-type: %s (expected any of %q)",
return "", fmt.Errorf("unexpected content-type: %s (expected any of %q)",
contentType, zipMimeTypes)
}
@ -105,15 +112,16 @@ func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion,
err := compareChecksum(d.Logger, r, verifiedChecksum, pb.Filename, expectedSize)
if err != nil {
return err
return "", err
}
}
pkgFile, err := ioutil.TempFile("", pb.Filename)
if err != nil {
return err
return "", err
}
defer pkgFile.Close()
pkgFilePath, err := filepath.Abs(pkgFile.Name())
d.Logger.Printf("copying %q (%d bytes) to %s", pb.Filename, expectedSize, pkgFile.Name())
// Unless the bytes were already downloaded above for checksum verification
@ -122,43 +130,48 @@ func (d *Downloader) DownloadAndUnpack(ctx context.Context, pv *ProductVersion,
// on demand over the network.
bytesCopied, err := io.Copy(pkgFile, pkgReader)
if err != nil {
return err
return pkgFilePath, err
}
d.Logger.Printf("copied %d bytes to %s", bytesCopied, pkgFile.Name())
if expectedSize != 0 && bytesCopied != int64(expectedSize) {
return fmt.Errorf("unexpected size (downloaded: %d, expected: %d)",
return pkgFilePath, fmt.Errorf("unexpected size (downloaded: %d, expected: %d)",
bytesCopied, expectedSize)
}
r, err := zip.OpenReader(pkgFile.Name())
if err != nil {
return err
return pkgFilePath, err
}
defer r.Close()
for _, f := range r.File {
if strings.Contains(f.Name, "..") {
// While we generally trust the source ZIP file
// we still reject path traversal attempts as a precaution.
continue
}
srcFile, err := f.Open()
if err != nil {
return err
return pkgFilePath, err
}
d.Logger.Printf("unpacking %s to %s", f.Name, dstDir)
dstPath := filepath.Join(dstDir, f.Name)
dstFile, err := os.Create(dstPath)
if err != nil {
return err
return pkgFilePath, err
}
_, err = io.Copy(dstFile, srcFile)
if err != nil {
return err
return pkgFilePath, err
}
srcFile.Close()
dstFile.Close()
}
return nil
return pkgFilePath, nil
}
// The production release site uses consistent single mime type

View File

@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
@ -68,7 +69,11 @@ func (r *Releases) ListProductVersions(ctx context.Context, productName string)
url.PathEscape(productName))
r.logger.Printf("requesting versions from %s", productIndexURL)
resp, err := client.Get(productIndexURL)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, productIndexURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request for %q: %w", productIndexURL, err)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
@ -133,7 +138,11 @@ func (r *Releases) GetProductVersion(ctx context.Context, product string, versio
url.PathEscape(version.String()))
r.logger.Printf("requesting version from %s", indexURL)
resp, err := client.Get(indexURL)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, indexURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request for %q: %w", indexURL, err)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}

View File

@ -1,9 +0,0 @@
package version
const version = "0.1.0"
// ModuleVersion returns the current version of the github.com/hashicorp/hc-install Go module.
// This is a function to allow for future possible enhancement using debug.BuildInfo.
func ModuleVersion() string {
return version
}

View File

@ -50,6 +50,6 @@ var Consul = Product{
BuildInstructions: &BuildInstructions{
GitRepoURL: "https://github.com/hashicorp/consul.git",
PreCloneCheck: &build.GoIsInstalled{},
Build: &build.GoBuild{Version: v1_18},
Build: &build.GoBuild{},
},
}

View File

@ -14,7 +14,6 @@ import (
var (
vaultVersionOutputRe = regexp.MustCompile(`Vault ` + simpleVersionRe)
v1_17 = version.Must(version.NewVersion("1.17"))
)
var Vault = Product{
@ -49,6 +48,6 @@ var Vault = Product{
BuildInstructions: &BuildInstructions{
GitRepoURL: "https://github.com/hashicorp/vault.git",
PreCloneCheck: &build.GoIsInstalled{},
Build: &build.GoBuild{Version: v1_17},
Build: &build.GoBuild{},
},
}

View File

@ -115,7 +115,10 @@ func (ev *ExactVersion) Install(ctx context.Context) (string, error) {
d.BaseURL = ev.apiBaseURL
}
err = d.DownloadAndUnpack(ctx, pv, dstDir)
zipFilePath, err := d.DownloadAndUnpack(ctx, pv, dstDir)
if zipFilePath != "" {
ev.pathsToRemove = append(ev.pathsToRemove, zipFilePath)
}
if err != nil {
return "", err
}

View File

@ -119,7 +119,10 @@ func (lv *LatestVersion) Install(ctx context.Context) (string, error) {
if lv.apiBaseURL != "" {
d.BaseURL = lv.apiBaseURL
}
err = d.DownloadAndUnpack(ctx, versionToInstall, dstDir)
zipFilePath, err := d.DownloadAndUnpack(ctx, versionToInstall, dstDir)
if zipFilePath != "" {
lv.pathsToRemove = append(lv.pathsToRemove, zipFilePath)
}
if err != nil {
return "", err
}

View File

@ -0,0 +1 @@
0.5.0

View File

@ -0,0 +1,20 @@
package version
import (
_ "embed"
"github.com/hashicorp/go-version"
)
//go:embed VERSION
var rawVersion string
// Version returns the version of the library
//
// Note: This is only exposed as public function/package
// due to hard-coded constraints in the release tooling.
// In general downstream should not implement version-specific
// logic and rely on this function to be present in future releases.
func Version() *version.Version {
return version.Must(version.NewVersion(rawVersion))
}

16
vendor/github.com/hashicorp/hcl/v2/.copywrite.hcl generated vendored Normal file
View File

@ -0,0 +1,16 @@
schema_version = 1
project {
license = "MPL-2.0"
copyright_year = 2014
# (OPTIONAL) A list of globs that should not have copyright/license headers.
# Supports doublestar glob patterns for more flexibility in defining which
# files or folders should be ignored
header_ignore = [
"hclsyntax/fuzz/testdata/**",
"hclwrite/fuzz/testdata/**",
"json/fuzz/testdata/**",
"specsuite/tests/**",
]
}

View File

@ -1,5 +1,31 @@
# HCL Changelog
## v2.16.2 (March 9, 2023)
### Bugs Fixed
* ext/typeexpr: Verify type assumptions when applying default values, and ignore input values that do not match type assumptions. ([#594](https://github.com/hashicorp/hcl/pull/594))
## v2.16.1 (February 13, 2023)
### Bugs Fixed
* hclsyntax: Report correct `Range.End` for `FunctionCall` with incomplete argument ([#588](https://github.com/hashicorp/hcl/pull/588))
## v2.16.0 (January 30, 2023)
### Enhancements
* ext/typeexpr: Modify the `Defaults` functionality to implement additional flexibility. HCL will now upcast lists and sets into tuples, and maps into objects, when applying default values if the applied defaults cause the elements within a target collection to have differing types. Previously, this would have resulted in a panic, now HCL will return a modified overall type. ([#574](https://github.com/hashicorp/hcl/pull/574))
Users should return to the advice provided by v2.14.0, and apply the go-cty convert functionality *after* setting defaults on a given `cty.Value`, rather than before.
* hclfmt: Avoid rewriting unchanged files. ([#576](https://github.com/hashicorp/hcl/pull/576))
* hclsyntax: Simplify the AST for certain string expressions. ([#584](https://github.com/hashicorp/hcl/pull/584))
### Bugs Fixed
* hclwrite: Fix data race in `formatSpaces`. ([#511](https://github.com/hashicorp/hcl/pull/511))
## v2.15.0 (November 10, 2022)
### Bugs Fixed

View File

@ -1,3 +1,5 @@
Copyright (c) 2014 HashiCorp, Inc.
Mozilla Public License, version 2.0
1. Definitions

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build go1.18
// +build go1.18

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package hcl contains the main modelling types and general utility functions
// for HCL.
//

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
// ExprCall tests if the given expression is a function call and,

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
// ExprList tests if the given expression is a static list construct and,

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
// ExprMap tests if the given expression is a static map construct and,

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
type unwrapExpression interface {

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package customdecode contains a HCL extension that allows, in certain
// contexts, expression evaluation to be overridden by custom static analysis.
//

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package customdecode
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package hclsyntax contains the parser, AST, etc for HCL's native language,
// as opposed to the JSON variant.
//

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
// Generated by expression_vars_get.go. DO NOT EDIT.

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
//go:generate go run expression_vars_gen.go

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (
@ -1174,7 +1177,12 @@ Token:
// if there was a parse error in the argument then we've
// probably been left in a weird place in the token stream,
// so we'll bail out with a partial argument list.
p.recover(TokenCParen)
recoveredTok := p.recover(TokenCParen)
// record the recovered token, if one was found
if recoveredTok.Type == TokenCParen {
closeTok = recoveredTok
}
break Token
}

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (
@ -38,6 +41,7 @@ func (p *parser) parseTemplateInner(end TokenType, flushHeredoc bool) ([]Express
if flushHeredoc {
flushHeredocTemplateParts(parts) // Trim off leading spaces on lines per the flush heredoc spec
}
meldConsecutiveStringLiterals(parts)
tp := templateParser{
Tokens: parts.Tokens,
SrcRange: parts.SrcRange,
@ -751,6 +755,37 @@ func flushHeredocTemplateParts(parts *templateParts) {
}
}
// meldConsecutiveStringLiterals simplifies the AST output by combining a
// sequence of string literal tokens into a single string literal. This must be
// performed after any whitespace trimming operations.
func meldConsecutiveStringLiterals(parts *templateParts) {
if len(parts.Tokens) == 0 {
return
}
// Loop over all tokens starting at the second element, as we want to join
// pairs of consecutive string literals.
i := 1
for i < len(parts.Tokens) {
if prevLiteral, ok := parts.Tokens[i-1].(*templateLiteralToken); ok {
if literal, ok := parts.Tokens[i].(*templateLiteralToken); ok {
// The current and previous tokens are both literals: combine
prevLiteral.Val = prevLiteral.Val + literal.Val
prevLiteral.SrcRange.End = literal.SrcRange.End
// Remove the current token from the slice
parts.Tokens = append(parts.Tokens[:i], parts.Tokens[i+1:]...)
// Continue without moving forward in the slice
continue
}
}
// Try the next pair of tokens
i++
}
}
type templateParts struct {
Tokens []templateToken
SrcRange hcl.Range

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//line scan_string_lit.rl:1
package hclsyntax

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//line scan_tokens.rl:1
package hclsyntax

View File

@ -82,7 +82,7 @@ Comments serve as program documentation and come in two forms:
- _Inline comments_ start with the `/*` sequence and end with the `*/`
sequence, and may have any characters within except the ending sequence.
An inline comments is considered equivalent to a whitespace sequence.
An inline comment is considered equivalent to a whitespace sequence.
Comments and whitespace cannot begin within other comments, or within
template literals except inside an interpolation sequence or template directive.
@ -268,10 +268,10 @@ collection value.
```ebnf
CollectionValue = tuple | object;
tuple = "[" (
(Expression ("," Expression)* ","?)?
(Expression (("," | Newline) Expression)* ","?)?
) "]";
object = "{" (
(objectelem ("," objectelem)* ","?)?
(objectelem (( "," | Newline) objectelem)* ","?)?
) "}";
objectelem = (Identifier | Expression) ("=" | ":") Expression;
```
@ -635,7 +635,7 @@ binaryOp = ExprTerm binaryOperator ExprTerm;
binaryOperator = compareOperator | arithmeticOperator | logicOperator;
compareOperator = "==" | "!=" | "<" | ">" | "<=" | ">=";
arithmeticOperator = "+" | "-" | "*" | "/" | "%";
logicOperator = "&&" | "||" | "!";
logicOperator = "&&" | "||";
```
The unary operators have the highest precedence.

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,4 +1,7 @@
#!/usr/bin/env ruby
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: MPL-2.0
#
# This scripted has been updated to accept more command-line arguments:
#

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hclsyntax
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import "fmt"

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
// BlockHeaderSchema represents the shape of a block header, and is

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
// -----------------------------------------------------------------------------

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
import (

View File

@ -1,3 +1,6 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package hcl
// AbsTraversalForExpr attempts to interpret the given expression as
@ -13,7 +16,7 @@ package hcl
//
// In most cases the calling application is interested in the value
// that results from an expression, but in rarer cases the application
// needs to see the the name of the variable and subsequent
// needs to see the name of the variable and subsequent
// attributes/indexes itself, for example to allow users to give references
// to the variables themselves rather than to their values. An implementer
// of this function should at least support attribute and index steps.

View File

@ -1,3 +1,5 @@
Copyright (c) 2020 HashiCorp, Inc.
Mozilla Public License Version 2.0
==================================

View File

@ -1,6 +1,6 @@
package version
const version = "0.17.3"
const version = "0.18.1"
// ModuleVersion returns the current version of the github.com/hashicorp/terraform-exec Go module.
// This is a function to allow for future possible enhancement using debug.BuildInfo.

View File

@ -3,6 +3,7 @@ package tfexec
import (
"context"
"fmt"
"io"
"os/exec"
"strconv"
)
@ -99,6 +100,27 @@ func (tf *Terraform) Apply(ctx context.Context, opts ...ApplyOption) error {
return tf.runTerraformCmd(ctx, cmd)
}
// ApplyJSON represents the terraform apply subcommand with the `-json` flag.
// Using the `-json` flag will result in
// [machine-readable](https://developer.hashicorp.com/terraform/internals/machine-readable-ui)
// JSON being written to the supplied `io.Writer`. ApplyJSON is likely to be
// removed in a future major version in favour of Apply returning JSON by default.
func (tf *Terraform) ApplyJSON(ctx context.Context, w io.Writer, opts ...ApplyOption) error {
err := tf.compatible(ctx, tf0_15_3, nil)
if err != nil {
return fmt.Errorf("terraform apply -json was added in 0.15.3: %w", err)
}
tf.SetStdout(w)
cmd, err := tf.applyJSONCmd(ctx, opts...)
if err != nil {
return err
}
return tf.runTerraformCmd(ctx, cmd)
}
func (tf *Terraform) applyCmd(ctx context.Context, opts ...ApplyOption) (*exec.Cmd, error) {
c := defaultApplyOptions
@ -106,6 +128,32 @@ func (tf *Terraform) applyCmd(ctx context.Context, opts ...ApplyOption) (*exec.C
o.configureApply(&c)
}
args, err := tf.buildApplyArgs(ctx, c)
if err != nil {
return nil, err
}
return tf.buildApplyCmd(ctx, c, args)
}
func (tf *Terraform) applyJSONCmd(ctx context.Context, opts ...ApplyOption) (*exec.Cmd, error) {
c := defaultApplyOptions
for _, o := range opts {
o.configureApply(&c)
}
args, err := tf.buildApplyArgs(ctx, c)
if err != nil {
return nil, err
}
args = append(args, "-json")
return tf.buildApplyCmd(ctx, c, args)
}
func (tf *Terraform) buildApplyArgs(ctx context.Context, c applyConfig) ([]string, error) {
args := []string{"apply", "-no-color", "-auto-approve", "-input=false"}
// string opts: only pass if set
@ -151,6 +199,10 @@ func (tf *Terraform) applyCmd(ctx context.Context, opts ...ApplyOption) (*exec.C
}
}
return args, nil
}
func (tf *Terraform) buildApplyCmd(ctx context.Context, c applyConfig, args []string) (*exec.Cmd, error) {
// string argument: pass if set
if c.dirOrPlan != "" {
args = append(args, c.dirOrPlan)

View File

@ -5,6 +5,7 @@ package tfexec
import (
"context"
"fmt"
"os/exec"
"strings"
"sync"
@ -40,11 +41,14 @@ func (tf *Terraform) runTerraformCmd(ctx context.Context, cmd *exec.Cmd) error {
}
err = cmd.Start()
if err == nil && ctx.Err() != nil {
err = ctx.Err()
if ctx.Err() != nil {
return cmdErr{
err: err,
ctxErr: ctx.Err(),
}
}
if err != nil {
return tf.wrapExitError(ctx, err, "")
return err
}
var errStdout, errStderr error
@ -66,19 +70,22 @@ func (tf *Terraform) runTerraformCmd(ctx context.Context, cmd *exec.Cmd) error {
wg.Wait()
err = cmd.Wait()
if err == nil && ctx.Err() != nil {
err = ctx.Err()
if ctx.Err() != nil {
return cmdErr{
err: err,
ctxErr: ctx.Err(),
}
}
if err != nil {
return tf.wrapExitError(ctx, err, errBuf.String())
return fmt.Errorf("%w\n%s", err, errBuf.String())
}
// Return error if there was an issue reading the std out/err
if errStdout != nil && ctx.Err() != nil {
return tf.wrapExitError(ctx, errStdout, errBuf.String())
return fmt.Errorf("%w\n%s", errStdout, errBuf.String())
}
if errStderr != nil && ctx.Err() != nil {
return tf.wrapExitError(ctx, errStderr, errBuf.String())
return fmt.Errorf("%w\n%s", errStderr, errBuf.String())
}
return nil

View File

@ -2,6 +2,7 @@ package tfexec
import (
"context"
"fmt"
"os/exec"
"strings"
"sync"
@ -45,11 +46,14 @@ func (tf *Terraform) runTerraformCmd(ctx context.Context, cmd *exec.Cmd) error {
}
err = cmd.Start()
if err == nil && ctx.Err() != nil {
err = ctx.Err()
if ctx.Err() != nil {
return cmdErr{
err: err,
ctxErr: ctx.Err(),
}
}
if err != nil {
return tf.wrapExitError(ctx, err, "")
return err
}
var errStdout, errStderr error
@ -71,19 +75,22 @@ func (tf *Terraform) runTerraformCmd(ctx context.Context, cmd *exec.Cmd) error {
wg.Wait()
err = cmd.Wait()
if err == nil && ctx.Err() != nil {
err = ctx.Err()
if ctx.Err() != nil {
return cmdErr{
err: err,
ctxErr: ctx.Err(),
}
}
if err != nil {
return tf.wrapExitError(ctx, err, errBuf.String())
return fmt.Errorf("%w\n%s", err, errBuf.String())
}
// Return error if there was an issue reading the std out/err
if errStdout != nil && ctx.Err() != nil {
return tf.wrapExitError(ctx, errStdout, errBuf.String())
return fmt.Errorf("%w\n%s", errStdout, errBuf.String())
}
if errStderr != nil && ctx.Err() != nil {
return tf.wrapExitError(ctx, errStderr, errBuf.String())
return fmt.Errorf("%w\n%s", errStderr, errBuf.String())
}
return nil

View File

@ -3,6 +3,7 @@ package tfexec
import (
"context"
"fmt"
"io"
"os/exec"
"strconv"
)
@ -95,6 +96,27 @@ func (tf *Terraform) Destroy(ctx context.Context, opts ...DestroyOption) error {
return tf.runTerraformCmd(ctx, cmd)
}
// DestroyJSON represents the terraform destroy subcommand with the `-json` flag.
// Using the `-json` flag will result in
// [machine-readable](https://developer.hashicorp.com/terraform/internals/machine-readable-ui)
// JSON being written to the supplied `io.Writer`. DestroyJSON is likely to be
// removed in a future major version in favour of Destroy returning JSON by default.
func (tf *Terraform) DestroyJSON(ctx context.Context, w io.Writer, opts ...DestroyOption) error {
err := tf.compatible(ctx, tf0_15_3, nil)
if err != nil {
return fmt.Errorf("terraform destroy -json was added in 0.15.3: %w", err)
}
tf.SetStdout(w)
cmd, err := tf.destroyJSONCmd(ctx, opts...)
if err != nil {
return err
}
return tf.runTerraformCmd(ctx, cmd)
}
func (tf *Terraform) destroyCmd(ctx context.Context, opts ...DestroyOption) (*exec.Cmd, error) {
c := defaultDestroyOptions
@ -102,6 +124,25 @@ func (tf *Terraform) destroyCmd(ctx context.Context, opts ...DestroyOption) (*ex
o.configureDestroy(&c)
}
args := tf.buildDestroyArgs(c)
return tf.buildDestroyCmd(ctx, c, args)
}
func (tf *Terraform) destroyJSONCmd(ctx context.Context, opts ...DestroyOption) (*exec.Cmd, error) {
c := defaultDestroyOptions
for _, o := range opts {
o.configureDestroy(&c)
}
args := tf.buildDestroyArgs(c)
args = append(args, "-json")
return tf.buildDestroyCmd(ctx, c, args)
}
func (tf *Terraform) buildDestroyArgs(c destroyConfig) []string {
args := []string{"destroy", "-no-color", "-auto-approve", "-input=false"}
// string opts: only pass if set
@ -138,6 +179,10 @@ func (tf *Terraform) destroyCmd(ctx context.Context, opts ...DestroyOption) (*ex
}
}
return args
}
func (tf *Terraform) buildDestroyCmd(ctx context.Context, c destroyConfig, args []string) (*exec.Cmd, error) {
// optional positional argument
if c.dir != "" {
args = append(args, c.dir)

View File

@ -1,6 +1,9 @@
package tfexec
import "fmt"
import (
"context"
"fmt"
)
// this file contains non-parsed exported errors
@ -37,3 +40,25 @@ type ErrManualEnvVar struct {
func (err *ErrManualEnvVar) Error() string {
return fmt.Sprintf("manual setting of env var %q detected", err.Name)
}
// cmdErr is a custom error type to be returned when a cmd exits with a context
// error such as context.Canceled or context.DeadlineExceeded.
// The type is specifically designed to respond true to errors.Is for these two
// errors.
// See https://github.com/golang/go/issues/21880 for why this is necessary.
type cmdErr struct {
err error
ctxErr error
}
func (e cmdErr) Is(target error) bool {
switch target {
case context.DeadlineExceeded, context.Canceled:
return e.ctxErr == context.DeadlineExceeded || e.ctxErr == context.Canceled
}
return false
}
func (e cmdErr) Error() string {
return e.err.Error()
}

View File

@ -1,344 +0,0 @@
package tfexec
import (
"context"
"fmt"
"os/exec"
"regexp"
"strings"
"text/template"
)
// this file contains errors parsed from stderr
var (
// The "Required variable not set:" case is for 0.11
missingVarErrRegexp = regexp.MustCompile(`Error: No value for required variable|Error: Required variable not set:`)
missingVarNameRegexp = regexp.MustCompile(`The root module input variable\s"(.+)"\sis\snot\sset,\sand\shas\sno\sdefault|Error: Required variable not set: (.+)`)
usageRegexp = regexp.MustCompile(`Too many command line arguments|^Usage: .*Options:.*|Error: Invalid -\d+ option`)
noInitErrRegexp = regexp.MustCompile(
// UNINITIALISED PROVIDERS/MODULES
`Error: Could not satisfy plugin requirements|` +
`Error: Could not load plugin|` + // v0.13
`Please run \"terraform init\"|` + // v1.1.0 early alpha versions (ref 89b05050)
`run:\s+terraform init|` + // v1.1.0 (ref df578afd)
`Run\s+\"terraform init\"|` + // v1.2.0
// UNINITIALISED BACKENDS
`Error: Initialization required.|` + // v0.13
`Error: Backend initialization required, please run \"terraform init\"`, // v0.15
)
noConfigErrRegexp = regexp.MustCompile(`Error: No configuration files`)
workspaceDoesNotExistRegexp = regexp.MustCompile(`Workspace "(.+)" doesn't exist.`)
workspaceAlreadyExistsRegexp = regexp.MustCompile(`Workspace "(.+)" already exists`)
tfVersionMismatchErrRegexp = regexp.MustCompile(`Error: The currently running version of Terraform doesn't meet the|Error: Unsupported Terraform Core version`)
tfVersionMismatchConstraintRegexp = regexp.MustCompile(`required_version = "(.+)"|Required version: (.+)\b`)
configInvalidErrRegexp = regexp.MustCompile(`There are some problems with the configuration, described below.`)
stateLockErrRegexp = regexp.MustCompile(`Error acquiring the state lock`)
stateLockInfoRegexp = regexp.MustCompile(`Lock Info:\n\s*ID:\s*([^\n]+)\n\s*Path:\s*([^\n]+)\n\s*Operation:\s*([^\n]+)\n\s*Who:\s*([^\n]+)\n\s*Version:\s*([^\n]+)\n\s*Created:\s*([^\n]+)\n`)
statePlanReadErrRegexp = regexp.MustCompile(
`Terraform couldn't read the given file as a state or plan file.|` +
`Error: Failed to read the given file as a state or plan file`)
lockIdInvalidErrRegexp = regexp.MustCompile(`Failed to unlock state: `)
)
func (tf *Terraform) wrapExitError(ctx context.Context, err error, stderr string) error {
exitErr, ok := err.(*exec.ExitError)
if !ok {
// not an exit error, short circuit, nothing to wrap
return err
}
ctxErr := ctx.Err()
// nothing to parse, return early
errString := strings.TrimSpace(stderr)
if errString == "" {
return &unwrapper{exitErr, ctxErr}
}
switch {
case tfVersionMismatchErrRegexp.MatchString(stderr):
constraint := ""
constraints := tfVersionMismatchConstraintRegexp.FindStringSubmatch(stderr)
for i := 1; i < len(constraints); i++ {
constraint = strings.TrimSpace(constraints[i])
if constraint != "" {
break
}
}
if constraint == "" {
// hardcode a value here for weird cases (incl. 0.12)
constraint = "unknown"
}
// only set this if it happened to be cached already
ver := ""
if tf != nil && tf.execVersion != nil {
ver = tf.execVersion.String()
}
return &ErrTFVersionMismatch{
unwrapper: unwrapper{exitErr, ctxErr},
Constraint: constraint,
TFVersion: ver,
}
case missingVarErrRegexp.MatchString(stderr):
name := ""
names := missingVarNameRegexp.FindStringSubmatch(stderr)
for i := 1; i < len(names); i++ {
name = strings.TrimSpace(names[i])
if name != "" {
break
}
}
return &ErrMissingVar{
unwrapper: unwrapper{exitErr, ctxErr},
VariableName: name,
}
case usageRegexp.MatchString(stderr):
return &ErrCLIUsage{
unwrapper: unwrapper{exitErr, ctxErr},
stderr: stderr,
}
case noInitErrRegexp.MatchString(stderr):
return &ErrNoInit{
unwrapper: unwrapper{exitErr, ctxErr},
stderr: stderr,
}
case noConfigErrRegexp.MatchString(stderr):
return &ErrNoConfig{
unwrapper: unwrapper{exitErr, ctxErr},
stderr: stderr,
}
case workspaceDoesNotExistRegexp.MatchString(stderr):
submatches := workspaceDoesNotExistRegexp.FindStringSubmatch(stderr)
if len(submatches) == 2 {
return &ErrNoWorkspace{
unwrapper: unwrapper{exitErr, ctxErr},
Name: submatches[1],
}
}
case workspaceAlreadyExistsRegexp.MatchString(stderr):
submatches := workspaceAlreadyExistsRegexp.FindStringSubmatch(stderr)
if len(submatches) == 2 {
return &ErrWorkspaceExists{
unwrapper: unwrapper{exitErr, ctxErr},
Name: submatches[1],
}
}
case configInvalidErrRegexp.MatchString(stderr):
return &ErrConfigInvalid{stderr: stderr}
case stateLockErrRegexp.MatchString(stderr):
submatches := stateLockInfoRegexp.FindStringSubmatch(stderr)
if len(submatches) == 7 {
return &ErrStateLocked{
unwrapper: unwrapper{exitErr, ctxErr},
ID: submatches[1],
Path: submatches[2],
Operation: submatches[3],
Who: submatches[4],
Version: submatches[5],
Created: submatches[6],
}
}
case statePlanReadErrRegexp.MatchString(stderr):
return &ErrStatePlanRead{stderr: stderr}
case lockIdInvalidErrRegexp.MatchString(stderr):
return &ErrLockIdInvalid{stderr: stderr}
}
return fmt.Errorf("%w\n%s", &unwrapper{exitErr, ctxErr}, stderr)
}
type unwrapper struct {
err error
ctxErr error
}
func (u *unwrapper) Unwrap() error {
return u.err
}
func (u *unwrapper) Is(target error) bool {
switch target {
case context.DeadlineExceeded, context.Canceled:
return u.ctxErr == context.DeadlineExceeded ||
u.ctxErr == context.Canceled
}
return false
}
func (u *unwrapper) Error() string {
return u.err.Error()
}
type ErrConfigInvalid struct {
stderr string
}
func (e *ErrConfigInvalid) Error() string {
return "configuration is invalid"
}
type ErrMissingVar struct {
unwrapper
VariableName string
}
func (err *ErrMissingVar) Error() string {
return fmt.Sprintf("variable %q was required but not supplied", err.VariableName)
}
type ErrNoWorkspace struct {
unwrapper
Name string
}
func (err *ErrNoWorkspace) Error() string {
return fmt.Sprintf("workspace %q does not exist", err.Name)
}
// ErrWorkspaceExists is returned when creating a workspace that already exists
type ErrWorkspaceExists struct {
unwrapper
Name string
}
func (err *ErrWorkspaceExists) Error() string {
return fmt.Sprintf("workspace %q already exists", err.Name)
}
type ErrNoInit struct {
unwrapper
stderr string
}
func (e *ErrNoInit) Error() string {
return e.stderr
}
type ErrStatePlanRead struct {
unwrapper
stderr string
}
func (e *ErrStatePlanRead) Error() string {
return e.stderr
}
type ErrNoConfig struct {
unwrapper
stderr string
}
func (e *ErrNoConfig) Error() string {
return e.stderr
}
type ErrLockIdInvalid struct {
unwrapper
stderr string
}
func (e *ErrLockIdInvalid) Error() string {
return e.stderr
}
// ErrCLIUsage is returned when the combination of flags or arguments is incorrect.
//
// CLI indicates usage errors in three different ways: either
// 1. Exit 1, with a custom error message on stderr.
// 2. Exit 1, with command usage logged to stderr.
// 3. Exit 127, with command usage logged to stdout.
// Currently cases 1 and 2 are handled.
// TODO KEM: Handle exit 127 case. How does this work on non-Unix platforms?
type ErrCLIUsage struct {
unwrapper
stderr string
}
func (e *ErrCLIUsage) Error() string {
return e.stderr
}
// ErrTFVersionMismatch is returned when the running Terraform version is not compatible with the
// value specified for required_version in the terraform block.
type ErrTFVersionMismatch struct {
unwrapper
TFVersion string
// Constraint is not returned in the error messaging on 0.12
Constraint string
}
func (e *ErrTFVersionMismatch) Error() string {
version := "version"
if e.TFVersion != "" {
version = e.TFVersion
}
requirement := ""
if e.Constraint != "" {
requirement = fmt.Sprintf(" (%s required)", e.Constraint)
}
return fmt.Sprintf("terraform %s not supported by configuration%s",
version, requirement)
}
// ErrStateLocked is returned when the state lock is already held by another process.
type ErrStateLocked struct {
unwrapper
ID string
Path string
Operation string
Who string
Version string
Created string
}
func (e *ErrStateLocked) Error() string {
tmpl := `Lock Info:
ID: {{.ID}}
Path: {{.Path}}
Operation: {{.Operation}}
Who: {{.Who}}
Version: {{.Version}}
Created: {{.Created}}
`
t := template.Must(template.New("LockInfo").Parse(tmpl))
var out strings.Builder
if err := t.Execute(&out, e); err != nil {
return "error acquiring the state lock"
}
return fmt.Sprintf("error acquiring the state lock: %v", out.String())
}

View File

@ -0,0 +1,34 @@
package tfexec
import (
"context"
"fmt"
"os/exec"
tfjson "github.com/hashicorp/terraform-json"
)
// MetadataFunctions represents the terraform metadata functions -json subcommand.
func (tf *Terraform) MetadataFunctions(ctx context.Context) (*tfjson.MetadataFunctions, error) {
err := tf.compatible(ctx, tf1_4_0, nil)
if err != nil {
return nil, fmt.Errorf("terraform metadata functions was added in 1.4.0: %w", err)
}
functionsCmd := tf.metadataFunctionsCmd(ctx)
var ret tfjson.MetadataFunctions
err = tf.runTerraformCmdJSON(ctx, functionsCmd, &ret)
if err != nil {
return nil, err
}
return &ret, nil
}
func (tf *Terraform) metadataFunctionsCmd(ctx context.Context, args ...string) *exec.Cmd {
allArgs := []string{"metadata", "functions", "-json"}
allArgs = append(allArgs, args...)
return tf.buildTerraformCmd(ctx, nil, allArgs...)
}

View File

@ -3,6 +3,7 @@ package tfexec
import (
"context"
"fmt"
"io"
"os/exec"
"strconv"
)
@ -108,6 +109,42 @@ func (tf *Terraform) Plan(ctx context.Context, opts ...PlanOption) (bool, error)
return false, err
}
// PlanJSON executes `terraform plan` with the specified options as well as the
// `-json` flag and waits for it to complete.
//
// Using the `-json` flag will result in
// [machine-readable](https://developer.hashicorp.com/terraform/internals/machine-readable-ui)
// JSON being written to the supplied `io.Writer`.
//
// The returned boolean is false when the plan diff is empty (no changes) and
// true when the plan diff is non-empty (changes present).
//
// The returned error is nil if `terraform plan` has been executed and exits
// with either 0 or 2.
//
// PlanJSON is likely to be removed in a future major version in favour of
// Plan returning JSON by default.
func (tf *Terraform) PlanJSON(ctx context.Context, w io.Writer, opts ...PlanOption) (bool, error) {
err := tf.compatible(ctx, tf0_15_3, nil)
if err != nil {
return false, fmt.Errorf("terraform plan -json was added in 0.15.3: %w", err)
}
tf.SetStdout(w)
cmd, err := tf.planJSONCmd(ctx, opts...)
if err != nil {
return false, err
}
err = tf.runTerraformCmd(ctx, cmd)
if err != nil && cmd.ProcessState.ExitCode() == 2 {
return true, nil
}
return false, err
}
func (tf *Terraform) planCmd(ctx context.Context, opts ...PlanOption) (*exec.Cmd, error) {
c := defaultPlanOptions
@ -115,6 +152,32 @@ func (tf *Terraform) planCmd(ctx context.Context, opts ...PlanOption) (*exec.Cmd
o.configurePlan(&c)
}
args, err := tf.buildPlanArgs(ctx, c)
if err != nil {
return nil, err
}
return tf.buildPlanCmd(ctx, c, args)
}
func (tf *Terraform) planJSONCmd(ctx context.Context, opts ...PlanOption) (*exec.Cmd, error) {
c := defaultPlanOptions
for _, o := range opts {
o.configurePlan(&c)
}
args, err := tf.buildPlanArgs(ctx, c)
if err != nil {
return nil, err
}
args = append(args, "-json")
return tf.buildPlanCmd(ctx, c, args)
}
func (tf *Terraform) buildPlanArgs(ctx context.Context, c planConfig) ([]string, error) {
args := []string{"plan", "-no-color", "-input=false", "-detailed-exitcode"}
// string opts: only pass if set
@ -162,6 +225,10 @@ func (tf *Terraform) planCmd(ctx context.Context, opts ...PlanOption) (*exec.Cmd
}
}
return args, nil
}
func (tf *Terraform) buildPlanCmd(ctx context.Context, c planConfig, args []string) (*exec.Cmd, error) {
// optional positional argument
if c.dir != "" {
args = append(args, c.dir)

View File

@ -2,6 +2,8 @@ package tfexec
import (
"context"
"fmt"
"io"
"os/exec"
"strconv"
)
@ -78,6 +80,27 @@ func (tf *Terraform) Refresh(ctx context.Context, opts ...RefreshCmdOption) erro
return tf.runTerraformCmd(ctx, cmd)
}
// RefreshJSON represents the terraform refresh subcommand with the `-json` flag.
// Using the `-json` flag will result in
// [machine-readable](https://developer.hashicorp.com/terraform/internals/machine-readable-ui)
// JSON being written to the supplied `io.Writer`. RefreshJSON is likely to be
// removed in a future major version in favour of Refresh returning JSON by default.
func (tf *Terraform) RefreshJSON(ctx context.Context, w io.Writer, opts ...RefreshCmdOption) error {
err := tf.compatible(ctx, tf0_15_3, nil)
if err != nil {
return fmt.Errorf("terraform refresh -json was added in 0.15.3: %w", err)
}
tf.SetStdout(w)
cmd, err := tf.refreshJSONCmd(ctx, opts...)
if err != nil {
return err
}
return tf.runTerraformCmd(ctx, cmd)
}
func (tf *Terraform) refreshCmd(ctx context.Context, opts ...RefreshCmdOption) (*exec.Cmd, error) {
c := defaultRefreshOptions
@ -85,6 +108,26 @@ func (tf *Terraform) refreshCmd(ctx context.Context, opts ...RefreshCmdOption) (
o.configureRefresh(&c)
}
args := tf.buildRefreshArgs(c)
return tf.buildRefreshCmd(ctx, c, args)
}
func (tf *Terraform) refreshJSONCmd(ctx context.Context, opts ...RefreshCmdOption) (*exec.Cmd, error) {
c := defaultRefreshOptions
for _, o := range opts {
o.configureRefresh(&c)
}
args := tf.buildRefreshArgs(c)
args = append(args, "-json")
return tf.buildRefreshCmd(ctx, c, args)
}
func (tf *Terraform) buildRefreshArgs(c refreshConfig) []string {
args := []string{"refresh", "-no-color", "-input=false"}
// string opts: only pass if set
@ -119,6 +162,10 @@ func (tf *Terraform) refreshCmd(ctx context.Context, opts ...RefreshCmdOption) (
}
}
return args
}
func (tf *Terraform) buildRefreshCmd(ctx context.Context, c refreshConfig, args []string) (*exec.Cmd, error) {
// optional positional argument
if c.dir != "" {
args = append(args, c.dir)

View File

@ -32,14 +32,14 @@ type printfer interface {
// but it ignores certain environment variables that are managed within the code and prohibits
// setting them through SetEnv:
//
// - TF_APPEND_USER_AGENT
// - TF_IN_AUTOMATION
// - TF_INPUT
// - TF_LOG
// - TF_LOG_PATH
// - TF_REATTACH_PROVIDERS
// - TF_DISABLE_PLUGIN_TLS
// - TF_SKIP_PROVIDER_VERIFY
// - TF_APPEND_USER_AGENT
// - TF_IN_AUTOMATION
// - TF_INPUT
// - TF_LOG
// - TF_LOG_PATH
// - TF_REATTACH_PROVIDERS
// - TF_DISABLE_PLUGIN_TLS
// - TF_SKIP_PROVIDER_VERIFY
type Terraform struct {
execPath string
workingDir string

View File

@ -25,7 +25,9 @@ var (
tf0_14_0 = version.Must(version.NewVersion("0.14.0"))
tf0_15_0 = version.Must(version.NewVersion("0.15.0"))
tf0_15_2 = version.Must(version.NewVersion("0.15.2"))
tf0_15_3 = version.Must(version.NewVersion("0.15.3"))
tf1_1_0 = version.Must(version.NewVersion("1.1.0"))
tf1_4_0 = version.Must(version.NewVersion("1.4.0"))
)
// Version returns structured output from the terraform version command including both the Terraform CLI version

View File

@ -1 +1 @@
1.16
1.20

View File

@ -0,0 +1,2 @@
# This codebase has shared ownership and responsibility.
* @hashicorp/terraform-core @hashicorp/terraform-devex @hashicorp/tf-editor-experience-engineers

View File

@ -1,3 +1,5 @@
Copyright (c) 2019 HashiCorp, Inc.
Mozilla Public License Version 2.0
==================================

View File

@ -1,6 +1,5 @@
# terraform-json
[![CircleCI](https://circleci.com/gh/hashicorp/terraform-json/tree/main.svg?style=svg)](https://circleci.com/gh/hashicorp/terraform-json/tree/main)
[![GoDoc](https://godoc.org/github.com/hashicorp/terraform-json?status.svg)](https://godoc.org/github.com/hashicorp/terraform-json)
This repository houses data types designed to help parse the data produced by

104
vendor/github.com/hashicorp/terraform-json/metadata.go generated vendored Normal file
View File

@ -0,0 +1,104 @@
package tfjson
import (
"encoding/json"
"errors"
"fmt"
"github.com/hashicorp/go-version"
"github.com/zclconf/go-cty/cty"
)
// MetadataFunctionsFormatVersionConstraints defines the versions of the JSON
// metadata functions format that are supported by this package.
var MetadataFunctionsFormatVersionConstraints = "~> 1.0"
// MetadataFunctions is the top-level object returned when exporting function
// signatures
type MetadataFunctions struct {
// The version of the format. This should always match the
// MetadataFunctionsFormatVersionConstraints in this package, else
// unmarshaling will fail.
FormatVersion string `json:"format_version"`
// The signatures of the functions available in a Terraform version.
Signatures map[string]*FunctionSignature `json:"function_signatures,omitempty"`
}
// Validate checks to ensure that MetadataFunctions is present, and the
// version matches the version supported by this library.
func (f *MetadataFunctions) Validate() error {
if f == nil {
return errors.New("metadata functions data is nil")
}
if f.FormatVersion == "" {
return errors.New("unexpected metadata functions data, format version is missing")
}
constraint, err := version.NewConstraint(MetadataFunctionsFormatVersionConstraints)
if err != nil {
return fmt.Errorf("invalid version constraint: %w", err)
}
version, err := version.NewVersion(f.FormatVersion)
if err != nil {
return fmt.Errorf("invalid format version %q: %w", f.FormatVersion, err)
}
if !constraint.Check(version) {
return fmt.Errorf("unsupported metadata functions format version: %q does not satisfy %q",
version, constraint)
}
return nil
}
func (f *MetadataFunctions) UnmarshalJSON(b []byte) error {
type rawFunctions MetadataFunctions
var functions rawFunctions
err := json.Unmarshal(b, &functions)
if err != nil {
return err
}
*f = *(*MetadataFunctions)(&functions)
return f.Validate()
}
// FunctionSignature represents a function signature.
type FunctionSignature struct {
// Description is an optional human-readable description
// of the function
Description string `json:"description,omitempty"`
// ReturnType is the ctyjson representation of the function's
// return types based on supplying all parameters using
// dynamic types. Functions can have dynamic return types.
ReturnType cty.Type `json:"return_type"`
// Parameters describes the function's fixed positional parameters.
Parameters []*FunctionParameter `json:"parameters,omitempty"`
// VariadicParameter describes the function's variadic
// parameter if it is supported.
VariadicParameter *FunctionParameter `json:"variadic_parameter,omitempty"`
}
// FunctionParameter represents a parameter to a function.
type FunctionParameter struct {
// Name is an optional name for the argument.
Name string `json:"name,omitempty"`
// Description is an optional human-readable description
// of the argument
Description string `json:"description,omitempty"`
// IsNullable is true if null is acceptable value for the argument
IsNullable bool `json:"is_nullable,omitempty"`
// A type that any argument for this parameter must conform to.
Type cty.Type `json:"type"`
}

View File

@ -42,6 +42,10 @@ type Plan struct {
// this plan.
PlannedValues *StateValues `json:"planned_values,omitempty"`
// The change operations for resources and data sources within this plan
// resulting from resource drift.
ResourceDrift []*ResourceChange `json:"resource_drift,omitempty"`
// The change operations for resources and data sources within this
// plan.
ResourceChanges []*ResourceChange `json:"resource_changes,omitempty"`

View File

@ -39,7 +39,7 @@ func (p *ProviderSchemas) Validate() error {
return errors.New("unexpected provider schema data, format version is missing")
}
constraint, err := version.NewConstraint(PlanFormatVersionConstraints)
constraint, err := version.NewConstraint(ProviderSchemasFormatVersionConstraints)
if err != nil {
return fmt.Errorf("invalid version constraint: %w", err)
}

View File

@ -122,7 +122,7 @@ func protocolDataDynamicValue6(_ context.Context, value *tfprotov6.DynamicValue)
}
func writeProtocolFile(ctx context.Context, dataDir string, rpc string, message string, field string, fileExtension string, fileContents []byte) {
fileName := fmt.Sprintf("%d_%s_%s_%s", time.Now().Unix(), rpc, message, field)
fileName := fmt.Sprintf("%d_%s_%s_%s", time.Now().UnixMilli(), rpc, message, field)
if fileExtension != "" {
fileName += "." + fileExtension

View File

@ -1,6 +1,8 @@
package toproto
import (
"unicode/utf8"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5"
)
@ -8,8 +10,8 @@ import (
func Diagnostic(in *tfprotov5.Diagnostic) (*tfplugin5.Diagnostic, error) {
diag := &tfplugin5.Diagnostic{
Severity: Diagnostic_Severity(in.Severity),
Summary: in.Summary,
Detail: in.Detail,
Summary: forceValidUTF8(in.Summary),
Detail: forceValidUTF8(in.Detail),
}
if in.Attribute != nil {
attr, err := AttributePath(in.Attribute)
@ -41,6 +43,59 @@ func Diagnostics(in []*tfprotov5.Diagnostic) ([]*tfplugin5.Diagnostic, error) {
return diagnostics, nil
}
// forceValidUTF8 returns a string guaranteed to be valid UTF-8 even if the
// input isn't, by replacing any invalid bytes with a valid UTF-8 encoding of
// the Unicode Replacement Character (\uFFFD).
//
// The protobuf serialization library will reject invalid UTF-8 with an
// unhelpful error message:
//
// string field contains invalid UTF-8
//
// Passing a string result through this function makes invalid UTF-8 instead
// emerge as placeholder characters on the other side of the wire protocol,
// giving a better chance of still returning a partially-legible message
// instead of a generic character encoding error.
//
// This is intended for user-facing messages such as diagnostic summary and
// detail messages, where Terraform will just treat the value as opaque and
// it's ultimately up to the user and their terminal or web browser to
// interpret the result. Don't use this for strings that have machine-readable
// meaning.
func forceValidUTF8(s string) string {
// Most strings that pass through here will already be valid UTF-8 and
// utf8.ValidString has a fast path which will beat our rune-by-rune
// analysis below, so it's worth the cost of walking the string twice
// in the rarer invalid case.
if utf8.ValidString(s) {
return s
}
// If we get down here then we know there's at least one invalid UTF-8
// sequence in the string, so in this slow path we'll reconstruct the
// string one rune at a time, guaranteeing that we'll only write valid
// UTF-8 sequences into the resulting buffer.
//
// Any invalid string will grow at least a little larger as a result of
// this operation because we'll be replacing each invalid byte with
// the three-byte sequence \xEF\xBF\xBD, which is the UTF-8 encoding of
// the replacement character \uFFFD. 9 is a magic number giving room for
// three such expansions without any further allocation.
ret := make([]byte, 0, len(s)+9)
for {
// If the first byte in s is not the start of a valid UTF-8 sequence
// then the following will return utf8.RuneError, 1, where
// utf8.RuneError is the unicode replacement character.
r, advance := utf8.DecodeRuneInString(s)
if advance == 0 {
break
}
s = s[advance:]
ret = utf8.AppendRune(ret, r)
}
return string(ret)
}
// we have to say this next thing to get golint to stop yelling at us about the
// underscores in the function names. We want the function names to match
// actually-generated code, so it feels like fair play. It's just a shame we

Some files were not shown because too many files have changed in this diff Show More