mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
fix: resolve local mirror detection with -chdir option (#3072)
Signed-off-by: Babur Ayanlar <babur.ayanlar@ableton.com>
This commit is contained in:
@@ -199,21 +199,12 @@ func realMain() int {
|
||||
|
||||
modulePkgFetcher := remoteModulePackageFetcher(ctx, config.OCICredentialsPolicy)
|
||||
|
||||
providerSrc, diags := providerSource(ctx, config.ProviderInstallation, services, config.OCICredentialsPolicy)
|
||||
if len(diags) > 0 {
|
||||
Ui.Error("There are some problems with the provider_installation configuration:")
|
||||
for _, diag := range diags {
|
||||
earlyColor := &colorstring.Colorize{
|
||||
Colors: colorstring.DefaultColors,
|
||||
Disable: true, // Disable color to be conservative until we know better
|
||||
Reset: true,
|
||||
}
|
||||
Ui.Error(format.Diagnostic(diag, nil, earlyColor, 78))
|
||||
}
|
||||
if diags.HasErrors() {
|
||||
Ui.Error("As a result of the above problems, OpenTofu's provider installer may not behave as intended.\n\n")
|
||||
// We continue to run anyway, because most commands don't do provider installation.
|
||||
}
|
||||
// Get the original working directory before any -chdir processing
|
||||
originalWd, err := os.Getwd()
|
||||
if err != nil {
|
||||
// It would be very strange to end up here
|
||||
Ui.Error(fmt.Sprintf("Failed to determine current working directory: %s", err))
|
||||
return 1
|
||||
}
|
||||
providerDevOverrides := providerDevOverrides(config.ProviderInstallation)
|
||||
|
||||
@@ -233,13 +224,6 @@ func realMain() int {
|
||||
binName := filepath.Base(os.Args[0])
|
||||
args := os.Args[1:]
|
||||
|
||||
originalWd, err := os.Getwd()
|
||||
if err != nil {
|
||||
// It would be very strange to end up here
|
||||
Ui.Error(fmt.Sprintf("Failed to determine current working directory: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// The arguments can begin with a -chdir option to ask OpenTofu to switch
|
||||
// to a different working directory for the rest of its work. If that
|
||||
// option is present then extractChdirOption returns a trimmed args with that option removed.
|
||||
@@ -256,6 +240,23 @@ func realMain() int {
|
||||
}
|
||||
}
|
||||
|
||||
providerSrc, diags := providerSource(ctx, config.ProviderInstallation, services, config.OCICredentialsPolicy, originalWd)
|
||||
if len(diags) > 0 {
|
||||
Ui.Error("There are some problems with the provider_installation configuration:")
|
||||
for _, diag := range diags {
|
||||
earlyColor := &colorstring.Colorize{
|
||||
Colors: colorstring.DefaultColors,
|
||||
Disable: true, // Disable color to be conservative until we know better
|
||||
Reset: true,
|
||||
}
|
||||
Ui.Error(format.Diagnostic(diag, nil, earlyColor, 78))
|
||||
}
|
||||
if diags.HasErrors() {
|
||||
Ui.Error("As a result of the above problems, OpenTofu's provider installer may not behave as intended.\n\n")
|
||||
// We continue to run anyway, because most commands don't do provider installation.
|
||||
}
|
||||
}
|
||||
|
||||
// In tests, Commands may already be set to provide mock commands
|
||||
if commands == nil {
|
||||
// Commands get to hold on to the original working directory here,
|
||||
|
||||
@@ -26,12 +26,12 @@ import (
|
||||
// CLI configuration and some default search locations. This will be the
|
||||
// provider source used for provider installation in the "tofu init"
|
||||
// command, unless overridden by the special -plugin-dir option.
|
||||
func providerSource(ctx context.Context, configs []*cliconfig.ProviderInstallation, services *disco.Disco, getOCICredsPolicy ociCredsPolicyBuilder) (getproviders.Source, tfdiags.Diagnostics) {
|
||||
func providerSource(ctx context.Context, configs []*cliconfig.ProviderInstallation, services *disco.Disco, getOCICredsPolicy ociCredsPolicyBuilder, originalWorkingDir string) (getproviders.Source, tfdiags.Diagnostics) {
|
||||
if len(configs) == 0 {
|
||||
// If there's no explicit installation configuration then we'll build
|
||||
// up an implicit one with direct registry installation along with
|
||||
// some automatically-selected local filesystem mirrors.
|
||||
return implicitProviderSource(ctx, services), nil
|
||||
return implicitProviderSource(ctx, services, originalWorkingDir), nil
|
||||
}
|
||||
|
||||
// There should only be zero or one configurations, which is checked by
|
||||
@@ -92,12 +92,14 @@ func explicitProviderSource(ctx context.Context, config *cliconfig.ProviderInsta
|
||||
// one version available in a local directory are implicitly excluded from
|
||||
// direct installation, as if the user had listed them explicitly in the
|
||||
// "exclude" argument in the direct provider source in the CLI config.
|
||||
func implicitProviderSource(ctx context.Context, services *disco.Disco) getproviders.Source {
|
||||
func implicitProviderSource(ctx context.Context, services *disco.Disco, originalWorkingDir string) getproviders.Source {
|
||||
// The local search directories we use for implicit configuration are:
|
||||
// - The "terraform.d/plugins" directory in the current working directory,
|
||||
// which we've historically documented as a place to put plugins as a
|
||||
// way to include them in bundles uploaded to Terraform Cloud, where
|
||||
// there has historically otherwise been no way to use custom providers.
|
||||
// When using -chdir, this directory is checked in the launch directory
|
||||
// (original working directory), not the directory specified with -chdir.
|
||||
// - The "plugins" subdirectory of the CLI config search directory.
|
||||
// (that's ~/.terraform.d/plugins or $XDG_DATA_HOME/opentofu/plugins
|
||||
// on Unix systems, equivalents elsewhere)
|
||||
@@ -147,7 +149,9 @@ func implicitProviderSource(ctx context.Context, services *disco.Disco) getprovi
|
||||
}
|
||||
}
|
||||
|
||||
addLocalDir("terraform.d/plugins") // our "vendor" directory
|
||||
// Check and add the "terraform.d/plugins" directory in the original working directory
|
||||
addLocalDir(filepath.Join(originalWorkingDir, "terraform.d/plugins"))
|
||||
|
||||
cliDataDirs, err := cliconfig.DataDirs()
|
||||
if err == nil {
|
||||
for _, cliDataDir := range cliDataDirs {
|
||||
|
||||
152
cmd/tofu/provider_source_test.go
Normal file
152
cmd/tofu/provider_source_test.go
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) The OpenTofu Authors
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright (c) 2023 HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
"github.com/opentofu/opentofu/internal/command/cliconfig"
|
||||
"github.com/opentofu/opentofu/internal/command/cliconfig/ociauthconfig"
|
||||
"github.com/opentofu/svchost/disco"
|
||||
)
|
||||
|
||||
func TestProviderSource(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupFunc func(t *testing.T) (string, string) // returns (originalDir, overrideDir)
|
||||
expectedProvider string
|
||||
}{
|
||||
{
|
||||
name: "without overrideWd - should use terraform.d/plugins in original working directory",
|
||||
setupFunc: func(t *testing.T) (string, string) {
|
||||
// Create a temporary directory to conduct the test in
|
||||
tempDir := t.TempDir()
|
||||
|
||||
// Create terraform.d/plugins in the testing directory
|
||||
pluginsDir := filepath.Join(tempDir, "terraform.d", "plugins")
|
||||
err := os.MkdirAll(pluginsDir, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create plugins directory: %v", err)
|
||||
}
|
||||
|
||||
// Create a mock provider in the plugins directory
|
||||
providerDir := filepath.Join(pluginsDir, "registry.opentofu.org", "hashicorp", "test-provider", "1.0.0", "linux_amd64")
|
||||
err = os.MkdirAll(providerDir, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create provider directory: %v", err)
|
||||
}
|
||||
|
||||
// Create a mock provider binary
|
||||
providerBinary := filepath.Join(providerDir, "terraform-provider-test-provider")
|
||||
err = os.WriteFile(providerBinary, []byte("mock provider binary"), 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create mock provider binary: %v", err)
|
||||
}
|
||||
|
||||
return tempDir, ""
|
||||
},
|
||||
expectedProvider: "hashicorp/test-provider",
|
||||
},
|
||||
{
|
||||
name: "with overrideWd - should still use terraform.d/plugins in original working directory",
|
||||
setupFunc: func(t *testing.T) (string, string) {
|
||||
// Create a temporary directory to conduct the test in
|
||||
tempDir := t.TempDir()
|
||||
|
||||
// Create terraform.d/plugins in the testing directory
|
||||
pluginsDir := filepath.Join(tempDir, "terraform.d", "plugins")
|
||||
err := os.MkdirAll(pluginsDir, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create plugins directory: %v", err)
|
||||
}
|
||||
|
||||
// Create a mock provider in the plugins directory
|
||||
providerDir := filepath.Join(pluginsDir, "registry.opentofu.org", "hashicorp", "test-provider", "1.0.0", "linux_amd64")
|
||||
err = os.MkdirAll(providerDir, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create provider directory: %v", err)
|
||||
}
|
||||
|
||||
// Create a mock provider binary
|
||||
providerBinary := filepath.Join(providerDir, "terraform-provider-test-provider")
|
||||
err = os.WriteFile(providerBinary, []byte("mock provider binary"), 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create mock provider binary: %v", err)
|
||||
}
|
||||
|
||||
// Create a temporary directory for the override working directory
|
||||
overrideDir := filepath.Join(tempDir, "override")
|
||||
err = os.MkdirAll(overrideDir, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create override directory: %v", err)
|
||||
}
|
||||
|
||||
return tempDir, overrideDir
|
||||
},
|
||||
expectedProvider: "hashicorp/test-provider",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Setup test environment
|
||||
originalWorkingDir, overrideWd := tt.setupFunc(t)
|
||||
|
||||
err := os.Chdir(originalWorkingDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to change to original working directory: %v", err)
|
||||
}
|
||||
|
||||
// If we have an override directory, change to it (simulating -chdir behavior)
|
||||
if overrideWd != "" {
|
||||
err := os.Chdir(overrideWd)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to change to override directory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a mock disco service
|
||||
services := disco.New()
|
||||
|
||||
// Create a mock OCI credentials policy builder
|
||||
ociCredsPolicy := func(ctx context.Context) (ociauthconfig.CredentialsConfigs, error) {
|
||||
return ociauthconfig.CredentialsConfigs{}, nil
|
||||
}
|
||||
|
||||
// Call the function under test
|
||||
source, diags := providerSource(context.Background(), []*cliconfig.ProviderInstallation{}, services, ociCredsPolicy, originalWorkingDir)
|
||||
|
||||
// Verify no diagnostics were returned
|
||||
if len(diags) > 0 {
|
||||
t.Fatalf("Expected no diagnostics, got: %v", diags)
|
||||
}
|
||||
|
||||
// Verify the source is not nil
|
||||
if source == nil {
|
||||
t.Fatal("Expected source to be non-nil")
|
||||
}
|
||||
|
||||
// Try to get available versions for a test provider
|
||||
// This will help verify that the local directories are properly configured
|
||||
provider := addrs.MustParseProviderSourceString(tt.expectedProvider)
|
||||
versions, _, err := source.AvailableVersions(context.Background(), provider)
|
||||
if err != nil {
|
||||
// If available provider versions could not be determined, something went wrong with mock provider setup
|
||||
t.Fatalf("AvailableVersions failed (expected for test): %v", err)
|
||||
}
|
||||
|
||||
t.Logf("Source created successfully for test case: %s", tt.name)
|
||||
t.Logf("Provider: %v", provider)
|
||||
if versions != nil {
|
||||
t.Logf("Available versions: %v", versions)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user