extend existing cloud plugin to accomodate new plugin

fixing-stacks-plugin-server-panics
UKEME BASSEY 1 year ago
parent e1ad6a6fd6
commit 91b1dd3cc3

@ -9,8 +9,8 @@ import (
"io"
"log"
"github.com/hashicorp/terraform/internal/cloudplugin"
"github.com/hashicorp/terraform/internal/cloudplugin/cloudproto1"
"github.com/hashicorp/terraform/internal/pluginshared"
)
// GRPCCloudClient is the client interface for interacting with terraform-cloudplugin
@ -20,7 +20,7 @@ type GRPCCloudClient struct {
}
// Proof that GRPCCloudClient fulfills the go-plugin interface
var _ cloudplugin.Cloud1 = GRPCCloudClient{}
var _ pluginshared.CustomPluginClient = GRPCCloudClient{}
// Execute sends the client Execute request and waits for the plugin to return
// an exit code response before returning

@ -9,8 +9,8 @@ import (
"net/rpc"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/terraform/internal/cloudplugin"
"github.com/hashicorp/terraform/internal/cloudplugin/cloudproto1"
"github.com/hashicorp/terraform/internal/pluginshared"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
@ -19,7 +19,7 @@ import (
// implementation exists in this package.
type GRPCCloudPlugin struct {
plugin.GRPCPlugin
Impl cloudplugin.Cloud1
Impl pluginshared.CustomPluginClient
// Any configuration metadata that the plugin executable needs in order to
// do something useful, which will be passed along via gRPC metadata headers.
Metadata metadata.MD

@ -18,9 +18,9 @@ import (
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/terraform/internal/cloud"
"github.com/hashicorp/terraform/internal/cloudplugin"
"github.com/hashicorp/terraform/internal/cloudplugin/cloudplugin1"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/pluginshared"
"github.com/hashicorp/terraform/internal/tfdiags"
)
@ -112,7 +112,7 @@ func (c *CloudCommand) realRun(args []string, stdout, stderr io.Writer) int {
// Proxy the request
// Note: future changes will need to determine the type of raw when
// multiple versions are possible.
cloud1, ok := raw.(cloudplugin.Cloud1)
cloud1, ok := raw.(pluginshared.CustomPluginClient)
if !ok {
c.Ui.Error("If more than one cloudplugin versions are available, they need to be added to the cloud command. This is a bug in Terraform.")
return ExitRPCError
@ -217,7 +217,7 @@ func (c *CloudCommand) initPlugin() tfdiags.Diagnostics {
overridePath := os.Getenv("TF_CLOUD_PLUGIN_DEV_OVERRIDE")
bm, err := cloudplugin.NewBinaryManager(ctx, packagesPath, overridePath, c.pluginService, runtime.GOOS, runtime.GOARCH)
bm, err := pluginshared.NewCloudBinaryManager(ctx, packagesPath, overridePath, c.pluginService, runtime.GOOS, runtime.GOARCH)
if err != nil {
return diags.Append(tfdiags.Sourceless(tfdiags.Error, errorSummary, err.Error()))
}

@ -1,7 +1,7 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package cloudplugin
package pluginshared
import (
"bytes"
@ -9,7 +9,6 @@ import (
"encoding/json"
"fmt"
"log"
"net/url"
"os"
"path"
"path/filepath"
@ -22,17 +21,18 @@ import (
)
// BinaryManager downloads, caches, and returns information about the
// terraform-cloudplugin binary downloaded from the specified backend.
// plugin binary downloaded from the specified backend.
type BinaryManager struct {
signingKey string
binaryName string
cloudPluginDataDir string
overridePath string
host svchost.Hostname
client *CloudPluginClient
goos string
arch string
ctx context.Context
signingKey string
binaryName string
pluginName string
pluginDataDir string
overridePath string
host svchost.Hostname
client *BasePluginClient
goos string
arch string
ctx context.Context
}
// Binary is a struct containing the path to an authenticated binary corresponding to
@ -49,29 +49,8 @@ const (
MB = 1000 * KB
)
// BinaryManager initializes a new BinaryManager to broker data between the
// specified directory location containing cloudplugin package data and a
// HCP Terraform backend URL.
func NewBinaryManager(ctx context.Context, cloudPluginDataDir, overridePath string, serviceURL *url.URL, goos, arch string) (*BinaryManager, error) {
client, err := NewCloudPluginClient(ctx, serviceURL)
if err != nil {
return nil, fmt.Errorf("could not initialize cloudplugin version manager: %w", err)
}
return &BinaryManager{
cloudPluginDataDir: cloudPluginDataDir,
overridePath: overridePath,
host: svchost.Hostname(serviceURL.Host),
client: client,
binaryName: "terraform-cloudplugin",
goos: goos,
arch: arch,
ctx: ctx,
}, nil
}
func (v BinaryManager) binaryLocation() string {
return path.Join(v.cloudPluginDataDir, "bin", fmt.Sprintf("%s_%s", v.goos, v.arch))
return path.Join(v.pluginDataDir, "bin", fmt.Sprintf("%s_%s", v.goos, v.arch))
}
func (v BinaryManager) cachedVersion(version string) *string {
@ -94,7 +73,7 @@ func (v BinaryManager) cachedVersion(version string) *string {
// and returns its location and version.
func (v BinaryManager) Resolve() (*Binary, error) {
if v.overridePath != "" {
log.Printf("[TRACE] Using dev override for cloudplugin binary")
log.Printf("[TRACE] Using dev override for %s binary", v.pluginName)
return v.resolveDev()
}
return v.resolveRelease()
@ -111,10 +90,10 @@ func (v BinaryManager) resolveDev() (*Binary, error) {
func (v BinaryManager) resolveRelease() (*Binary, error) {
manifest, err := v.latestManifest(v.ctx)
if err != nil {
return nil, fmt.Errorf("could not resolve cloudplugin version for host %q: %w", v.host.ForDisplay(), err)
return nil, fmt.Errorf("could not resolve %s version for host %q: %w", v.pluginName, v.host.ForDisplay(), err)
}
buildInfo, err := manifest.Select(v.goos, v.arch)
buildInfo, err := manifest.Select(v.pluginName, v.goos, v.arch)
if err != nil {
return nil, err
}
@ -129,7 +108,7 @@ func (v BinaryManager) resolveRelease() (*Binary, error) {
}
// Download the archive
t, err := os.CreateTemp(os.TempDir(), "terraform-cloudplugin")
t, err := os.CreateTemp(os.TempDir(), v.binaryName)
if err != nil {
return nil, fmt.Errorf("failed to create temp file for download: %w", err)
}
@ -142,9 +121,9 @@ func (v BinaryManager) resolveRelease() (*Binary, error) {
t.Close() // Close only returns an error if it's already been called
// Authenticate the archive
err = v.verifyCloudPlugin(manifest, buildInfo, t.Name())
err = v.verifyPlugin(manifest, buildInfo, t.Name())
if err != nil {
return nil, fmt.Errorf("could not resolve cloudplugin version %q: %w", manifest.Version, err)
return nil, fmt.Errorf("could not resolve %s version %q: %w", v.pluginName, manifest.Version, err)
}
// Unarchive
@ -157,7 +136,7 @@ func (v BinaryManager) resolveRelease() (*Binary, error) {
err = unzip.Decompress(targetPath, t.Name(), true, 0000)
if err != nil {
return nil, fmt.Errorf("failed to decompress cloud plugin: %w", err)
return nil, fmt.Errorf("failed to decompress %s: %w", v.pluginName, err)
}
err = os.WriteFile(path.Join(targetPath, ".version"), []byte(manifest.Version), 0644)
@ -183,20 +162,20 @@ func (v BinaryManager) downloadFileBuffer(pathOrURL string) ([]byte, error) {
return buffer.Bytes(), err
}
// verifyCloudPlugin authenticates the downloaded release archive
func (v BinaryManager) verifyCloudPlugin(archiveManifest *Release, info *BuildArtifact, archiveLocation string) error {
// verifyPlugin authenticates the downloaded release archive
func (v BinaryManager) verifyPlugin(archiveManifest *Release, info *BuildArtifact, archiveLocation string) error {
signature, err := v.downloadFileBuffer(archiveManifest.URLSHASumsSignatures[0])
if err != nil {
return fmt.Errorf("failed to download cloudplugin SHA256SUMS signature file: %w", err)
return fmt.Errorf("failed to download %s SHA256SUMS signature file: %w", v.pluginName, err)
}
sums, err := v.downloadFileBuffer(archiveManifest.URLSHASums)
if err != nil {
return fmt.Errorf("failed to download cloudplugin SHA256SUMS file: %w", err)
return fmt.Errorf("failed to download %s SHA256SUMS file: %w", v.pluginName, err)
}
checksums, err := releaseauth.ParseChecksums(sums)
if err != nil {
return fmt.Errorf("failed to parse cloudplugin SHA256SUMS file: %w", err)
return fmt.Errorf("failed to parse %s SHA256SUMS file: %w", v.pluginName, err)
}
filename := path.Base(info.URL)
@ -219,21 +198,21 @@ func (v BinaryManager) verifyCloudPlugin(archiveManifest *Release, info *BuildAr
}
func (v BinaryManager) latestManifest(ctx context.Context) (*Release, error) {
manifestCacheLocation := path.Join(v.cloudPluginDataDir, v.host.String(), "manifest.json")
manifestCacheLocation := path.Join(v.pluginDataDir, v.host.String(), "manifest.json")
// Find the manifest cache for the hostname.
data, err := os.ReadFile(manifestCacheLocation)
modTime := time.Time{}
var localManifest *Release
if err != nil {
log.Printf("[TRACE] no cloudplugin manifest cache found for host %q", v.host)
log.Printf("[TRACE] no %s manifest cache found for host %q", v.pluginName, v.host)
} else {
log.Printf("[TRACE] cloudplugin manifest cache found for host %q", v.host)
log.Printf("[TRACE] %s manifest cache found for host %q", v.pluginName, v.host)
localManifest, err = decodeManifest(bytes.NewBuffer(data))
modTime = localManifest.TimestampUpdated
if err != nil {
log.Printf("[WARN] failed to decode cloudplugin manifest cache %q: %s", manifestCacheLocation, err)
log.Printf("[WARN] failed to decode %s manifest cache %q: %s", v.pluginName, manifestCacheLocation, err)
}
}
@ -241,7 +220,7 @@ func (v BinaryManager) latestManifest(ctx context.Context) (*Release, error) {
result, err := v.client.FetchManifest(modTime)
// FetchManifest can return nil, nil (see below)
if err != nil {
return nil, fmt.Errorf("failed to fetch cloudplugin manifest: %w", err)
return nil, fmt.Errorf("failed to fetch %s manifest: %w", v.pluginName, err)
}
// No error and no remoteManifest means the existing manifest is not modified
@ -251,24 +230,24 @@ func (v BinaryManager) latestManifest(ctx context.Context) (*Release, error) {
} else {
data, err := json.Marshal(result)
if err != nil {
return nil, fmt.Errorf("failed to dump cloudplugin manifest to JSON: %w", err)
return nil, fmt.Errorf("failed to dump %s manifest to JSON: %w", v.pluginName, err)
}
// Ensure target directory exists
if err := os.MkdirAll(filepath.Dir(manifestCacheLocation), 0755); err != nil {
return nil, fmt.Errorf("failed to create cloudplugin manifest cache directory: %w", err)
return nil, fmt.Errorf("failed to create %s manifest cache directory: %w", v.pluginName, err)
}
output, err := os.Create(manifestCacheLocation)
if err != nil {
return nil, fmt.Errorf("failed to create cloudplugin manifest cache: %w", err)
return nil, fmt.Errorf("failed to create %s manifest cache: %w", v.pluginName, err)
}
_, err = output.Write(data)
if err != nil {
return nil, fmt.Errorf("failed to write cloudplugin manifest cache: %w", err)
return nil, fmt.Errorf("failed to write %s manifest cache: %w", v.pluginName, err)
}
log.Printf("[TRACE] wrote cloudplugin manifest cache to %q", manifestCacheLocation)
log.Printf("[TRACE] wrote %s manifest cache to %q", v.pluginName, manifestCacheLocation)
}
return result, nil

@ -1,7 +1,7 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package cloudplugin
package pluginshared
import (
"context"
@ -63,7 +63,7 @@ func TestBinaryManager_Resolve(t *testing.T) {
serviceURL := serverURL.JoinPath("/api/cloudplugin/v1")
tempDir := t.TempDir()
manager, err := NewBinaryManager(context.Background(), tempDir, "", serviceURL, "darwin", "amd64")
manager, err := NewCloudBinaryManager(context.Background(), tempDir, "", serviceURL, "darwin", "amd64")
if err != nil {
t.Fatalf("expected no err, got: %s", err)
}

@ -1,7 +1,7 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package cloudplugin
package pluginshared
import (
"context"
@ -16,8 +16,6 @@ import (
"time"
"github.com/hashicorp/go-retryablehttp"
"github.com/hashicorp/terraform/internal/httpclient"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/releaseauth"
)
@ -137,17 +135,18 @@ type Release struct {
Version string `json:"version"`
}
// CloudPluginClient fetches and verifies release distributions of the cloudplugin
// BasePluginClient fetches and verifies release distributions of the custom plugins
// that correspond to an upstream backend.
type CloudPluginClient struct {
type BasePluginClient struct {
serviceURL *url.URL
httpClient *retryablehttp.Client
ctx context.Context
pluginName string
}
func requestLogHook(logger retryablehttp.Logger, req *http.Request, i int) {
if i > 0 {
logger.Printf("[INFO] Previous request to the remote cloud manifest failed, attempting retry.")
logger.Printf("[INFO] Previous request to the remote plugin manifest failed, attempting retry.")
}
}
@ -163,33 +162,14 @@ func decodeManifest(data io.Reader) (*Release, error) {
return &man, nil
}
// NewCloudPluginClient creates a new client for downloading and verifying
// terraform-cloudplugin archives
func NewCloudPluginClient(ctx context.Context, serviceURL *url.URL) (*CloudPluginClient, error) {
httpClient := httpclient.New()
httpClient.Timeout = defaultRequestTimeout
retryableClient := retryablehttp.NewClient()
retryableClient.HTTPClient = httpClient
retryableClient.RetryMax = 3
retryableClient.RequestLogHook = requestLogHook
retryableClient.Logger = logging.HCLogger()
return &CloudPluginClient{
httpClient: retryableClient,
serviceURL: serviceURL,
ctx: ctx,
}, nil
}
// FetchManifest retrieves the cloudplugin manifest from HCP Terraform,
// FetchManifest retrieves the plugin manifest from HCP Terraform,
// but returns a nil manifest if a 304 response is received, depending
// on the lastModified time.
func (c CloudPluginClient) FetchManifest(lastModified time.Time) (*Release, error) {
req, _ := retryablehttp.NewRequestWithContext(c.ctx, "GET", c.serviceURL.JoinPath("manifest.json").String(), nil)
func (b BasePluginClient) FetchManifest(lastModified time.Time) (*Release, error) {
req, _ := retryablehttp.NewRequestWithContext(b.ctx, "GET", b.serviceURL.JoinPath("manifest.json").String(), nil)
req.Header.Set("If-Modified-Since", lastModified.Format(http.TimeFormat))
resp, err := c.httpClient.Do(req)
resp, err := b.httpClient.Do(req)
if err != nil {
if errors.Is(err, context.Canceled) {
return nil, ErrRequestCanceled
@ -210,7 +190,7 @@ func (c CloudPluginClient) FetchManifest(lastModified time.Time) (*Release, erro
case http.StatusNotModified:
return nil, nil
case http.StatusNotFound:
return nil, ErrCloudPluginNotSupported
return nil, ErrPluginNotSupported
default:
return nil, ErrQueryFailed{
inner: errors.New(resp.Status),
@ -220,16 +200,16 @@ func (c CloudPluginClient) FetchManifest(lastModified time.Time) (*Release, erro
// DownloadFile gets the URL at the specified path or URL and writes the
// contents to the specified Writer.
func (c CloudPluginClient) DownloadFile(pathOrURL string, writer io.Writer) error {
url, err := c.resolveManifestURL(pathOrURL)
func (b BasePluginClient) DownloadFile(pathOrURL string, writer io.Writer) error {
url, err := b.resolveManifestURL(pathOrURL)
if err != nil {
return err
}
req, err := retryablehttp.NewRequestWithContext(c.ctx, "GET", url.String(), nil)
req, err := retryablehttp.NewRequestWithContext(b.ctx, "GET", url.String(), nil)
if err != nil {
return fmt.Errorf("invalid URL %q was provided by the cloudplugin manifest: %w", url, err)
return fmt.Errorf("invalid URL %q was provided by the %s manifest: %w", url, b.pluginName, err)
}
resp, err := c.httpClient.Do(req)
resp, err := b.httpClient.Do(req)
if err != nil {
if errors.Is(err, context.Canceled) {
return ErrRequestCanceled
@ -242,7 +222,7 @@ func (c CloudPluginClient) DownloadFile(pathOrURL string, writer io.Writer) erro
case http.StatusOK:
// OK
case http.StatusNotFound:
return ErrCloudPluginNotFound
return ErrPluginNotFound
default:
return ErrQueryFailed{
inner: errors.New(resp.Status),
@ -257,22 +237,22 @@ func (c CloudPluginClient) DownloadFile(pathOrURL string, writer io.Writer) erro
return nil
}
func (c CloudPluginClient) resolveManifestURL(pathOrURL string) (*url.URL, error) {
func (b BasePluginClient) resolveManifestURL(pathOrURL string) (*url.URL, error) {
if strings.HasPrefix(pathOrURL, "/") {
copy := *c.serviceURL
copy := *b.serviceURL
copy.Path = ""
return copy.JoinPath(pathOrURL), nil
}
result, err := url.Parse(pathOrURL)
if err != nil {
return nil, fmt.Errorf("received malformed URL %q from cloudplugin manifest: %w", pathOrURL, err)
return nil, fmt.Errorf("received malformed URL %q from %s manifest: %w", pathOrURL, b.pluginName, err)
}
return result, nil
}
// Select gets the specific build data from the Manifest for the specified OS/Architecture
func (m Release) Select(goos, arch string) (*BuildArtifact, error) {
func (m Release) Select(pluginName, goos, arch string) (*BuildArtifact, error) {
var supported []string
var found *BuildArtifact
for _, build := range m.Builds {
@ -285,7 +265,7 @@ func (m Release) Select(goos, arch string) (*BuildArtifact, error) {
}
osArchKey := fmt.Sprintf("%s_%s", goos, arch)
log.Printf("[TRACE] checking for cloudplugin archive for %s. Supported architectures: %v", osArchKey, supported)
log.Printf("[TRACE] checking for %s archive for %s. Supported architectures: %v", pluginName, osArchKey, supported)
if found == nil {
return nil, ErrArchNotSupported

@ -1,7 +1,7 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package cloudplugin
package pluginshared
import (
"bytes"
@ -52,8 +52,8 @@ func TestCloudPluginClient_DownloadFile(t *testing.T) {
t.Run("404 response", func(t *testing.T) {
err := client.DownloadFile("/archives/nope.zip", io.Discard)
if !errors.Is(err, ErrCloudPluginNotFound) {
t.Fatalf("expected error %q, got %q", ErrCloudPluginNotFound, err)
if !errors.Is(err, ErrPluginNotFound) {
t.Fatalf("expected error %q, got %q", ErrPluginNotFound, err)
}
})
}
@ -116,7 +116,7 @@ func TestCloudPluginClient_NotSupportedByTerraformCloud(t *testing.T) {
}
_, err = client.FetchManifest(time.Time{})
if !errors.Is(err, ErrCloudPluginNotSupported) {
if !errors.Is(err, ErrPluginNotSupported) {
t.Errorf("Expected ErrCloudPluginNotSupported, got %v", err)
}
}

@ -0,0 +1,41 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pluginshared
import (
"context"
"fmt"
"net/url"
svchost "github.com/hashicorp/terraform-svchost"
)
// CloudBinaryManager downloads, caches, and returns information about the
// terraform-cloudplugin binary downloaded from the specified backend.
type CloudBinaryManager struct {
BinaryManager
}
// NewCloudBinaryManager initializes a new BinaryManager to broker data between the
// specified directory location containing cloudplugin package data and a
// HCP Terraform backend URL.
func NewCloudBinaryManager(ctx context.Context, cloudPluginDataDir, overridePath string, serviceURL *url.URL, goos, arch string) (*CloudBinaryManager, error) {
client, err := NewCloudPluginClient(ctx, serviceURL)
if err != nil {
return nil, fmt.Errorf("could not initialize cloudplugin version manager: %w", err)
}
return &CloudBinaryManager{
BinaryManager{
pluginDataDir: cloudPluginDataDir,
overridePath: overridePath,
host: svchost.Hostname(serviceURL.Host),
client: client,
binaryName: "terraform-cloudplugin",
pluginName: "cloudplugin",
goos: goos,
arch: arch,
ctx: ctx,
}}, nil
}

@ -0,0 +1,40 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package pluginshared
import (
"context"
"net/url"
"github.com/hashicorp/go-retryablehttp"
"github.com/hashicorp/terraform/internal/httpclient"
"github.com/hashicorp/terraform/internal/logging"
)
// type CloudPluginClient struct {
// BasePluginClient
// }
// NewCloudPluginClient creates a new client for downloading and verifying
// terraform-cloudplugin archives
func NewCloudPluginClient(ctx context.Context, serviceURL *url.URL) (*BasePluginClient, error) {
httpClient := httpclient.New()
httpClient.Timeout = defaultRequestTimeout
retryableClient := retryablehttp.NewClient()
retryableClient.HTTPClient = httpClient
retryableClient.RetryMax = 3
retryableClient.RequestLogHook = requestLogHook
retryableClient.Logger = logging.HCLogger()
client := BasePluginClient{
ctx: ctx,
serviceURL: serviceURL,
httpClient: retryableClient,
pluginName: "cloudplugin",
}
// return &CloudPluginClient{BasePluginClient: client}, nil
return &client, nil
}

@ -1,7 +1,7 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package cloudplugin
package pluginshared
import (
"errors"
@ -9,23 +9,23 @@ import (
)
var (
// ErrCloudPluginNotSupported is the error returned when the upstream HCP Terraform does not
// ErrPluginNotSupported is the error returned when the upstream HCP Terraform does not
// have a manifest.
ErrCloudPluginNotSupported = errors.New("cloud plugin is not supported by the remote version of Terraform Enterprise")
ErrPluginNotSupported = errors.New("plugin is not supported by the remote version of Terraform Enterprise")
// ErrRequestCanceled is the error returned when the context was cancelled.
ErrRequestCanceled = errors.New("request was canceled")
// ErrArchNotSupported is the error returned when the cloudplugin does not have a build for the
// ErrArchNotSupported is the error returned when the plugin does not have a build for the
// current OS/Architecture.
ErrArchNotSupported = errors.New("cloud plugin is not supported by your computer architecture/operating system")
ErrArchNotSupported = errors.New("plugin is not supported by your computer architecture/operating system")
// ErrCloudPluginNotFound is the error returned when the cloudplugin manifest points to a location
// ErrPluginNotFound is the error returned when the plugin manifest points to a location
// that was does not exist.
ErrCloudPluginNotFound = errors.New("cloud plugin download was not found in the location specified in the manifest")
ErrPluginNotFound = errors.New("plugin download was not found in the location specified in the manifest")
)
// ErrQueryFailed is the error returned when the cloudplugin http client request fails
// ErrQueryFailed is the error returned when the plugin http client request fails
type ErrQueryFailed struct {
inner error
}
@ -37,7 +37,7 @@ type ErrCloudPluginNotVerified struct {
// Error returns a string representation of ErrQueryFailed
func (e ErrQueryFailed) Error() string {
return fmt.Sprintf("failed to fetch cloud plugin from HCP Terraform: %s", e.inner)
return fmt.Sprintf("failed to fetch plugin from HCP Terraform: %s", e.inner)
}
// Unwrap returns the inner error of ErrQueryFailed
@ -48,7 +48,7 @@ func (e ErrQueryFailed) Unwrap() error {
// Error returns the string representation of ErrCloudPluginNotVerified
func (e ErrCloudPluginNotVerified) Error() string {
return fmt.Sprintf("failed to verify cloud plugin. Ensure that the referenced plugin is the official HashiCorp distribution: %s", e.inner)
return fmt.Sprintf("failed to verify plugin. Ensure that the referenced plugin is the official HashiCorp distribution: %s", e.inner)
}
// Unwrap returns the inner error of ErrCloudPluginNotVerified

@ -1,12 +1,12 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package cloudplugin
package pluginshared
import (
"io"
)
type Cloud1 interface {
type CustomPluginClient interface {
Execute(args []string, stdout, stderr io.Writer) int
}

@ -1,7 +1,7 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package cloudplugin
package pluginshared
import (
"fmt"
Loading…
Cancel
Save