Files
opentf/internal/command/modules_get_package.go
Martin Atkins 98c67c2c97 wip module package installer command
The module installer API is not designed well enough to support this right
now and I exceeded my timebox trying to make it so.
2025-07-29 15:28:25 -07:00

129 lines
4.7 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package command
import (
"fmt"
version "github.com/hashicorp/go-version"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/initwd"
)
// MetadataFunctionsCommand is a Command implementation that prints out information
// about the available functions in OpenTofu.
type ModulesGetPackageCommand struct {
Meta
}
// Run implements cli.Command.
func (c *ModulesGetPackageCommand) Run(args []string) int {
ctx := c.CommandContext()
// If we were to build something like this "for real", rather than just
// prototyping, we should probably use the arguments/views approach for
// consistency with other modern commands, and consider having options
// for choosing between "-raw" and "-json" modes where the latter could
// potentially return more detailed information, describe gradual progress,
// and return machine-readable error messages. It should also use proper
// diagnostics rather than the legacy c.Ui.Error API.
//
// For now this is just simple to show what it might look like to _just_
// install a single module package, without doing all of the other stuff
// that "tofu init" or "tofu get" would normally do.
var versionRaw string
args = c.Meta.process(args)
cmdFlags := c.Meta.extendedFlagSet("modules get-package")
cmdFlags.StringVar(&versionRaw, "version", "", "Version constraint for module registry address")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line options: %s\n", err.Error()))
return 1
}
args = cmdFlags.Args()
if len(args) != 2 {
c.Ui.Error(c.Help())
return 1
}
sourceAddrRaw := args[0]
targetDir := args[1]
sourceAddr, err := addrs.ParseModuleSource(sourceAddrRaw)
if err != nil {
c.Ui.Error(fmt.Sprintf("Invalid module source address: %s\n", err.Error()))
return 1
}
if _, isLocal := sourceAddr.(addrs.ModuleSourceLocal); isLocal {
c.Ui.Error(fmt.Sprintf("Invalid module source address: must be a remote or module registry source address\n"))
return 1
}
// This API was originally designed for installing full dependency trees
// rather than individual packages, and the inner functions we use for
// fetching packages are not exported any other way, so for now we're
// passing nil for the config loader and assuming that the methods
// we're using on this type will not use the config loader.
// FIXME: Rework the module installer design a little so that the
// individual-package-level operations are exposed separately from the
// recursive dependency chasing.
inst := initwd.NewModuleInstaller(c.modulesDir(), nil, c.registryClient(ctx), c.ModulePackageFetcher)
// If we were given a registry address then we need to first translate it
// into its underlying remote source address.
if registrySourceAddr, isRegistry := sourceAddr.(addrs.ModuleSourceRegistry); isRegistry {
versionConstraints, err := version.NewConstraint(versionRaw)
if err != nil {
c.Ui.Error(fmt.Sprintf("Invalid version constraint: %s\n", err.Error()))
return 1
}
remoteSourceAddr, _, diags := inst.ResolveRegistryModule(ctx, registrySourceAddr, versionConstraints)
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
sourceAddr = remoteSourceAddr
} else {
if versionRaw != "" {
c.Ui.Error(fmt.Sprintf("The -version option is valid only for module registry source addresses\n"))
return 1
}
}
// By the time we get here sourceAddr should definitely be a remote source
// address, but we'll check to make sure.
remoteSourceAddr, ok := sourceAddr.(addrs.ModuleSourceRemote)
if !ok {
c.Ui.Error(fmt.Sprintf("Invalid module source address: unsupported address type\n"))
return 1
}
}
// Help implements cli.Command.
func (m *ModulesGetPackageCommand) Help() string {
return `
Usage: tofu [global options] modules get-package -version=<version-constraint> <source-addr> <target-dir>
Fetches a single module package from the given source address and extracts
it into the given target directory.
If successful, returns with exit code zero after printing the local filepath
of the selected module to stdout. The returned filepath will not exactly match
the given target directory if the source address includes a "//subdir"
portion to select a module in a subdirectory of the package.
This is a plumbing command intended only for use by external tools that
need to use files from OpenTofu module packages.
`
}
// Synopsis implements cli.Command.
func (m *ModulesGetPackageCommand) Synopsis() string {
return "Plumbing command for fetching a single module package"
}