Could be used for any tool, but mgmt is an obvious possibility. I should check this code more, but it's roughly right and I'm sure it will get refactored more when I build opt-in provisioning and so on.
587 lines
20 KiB
Go
587 lines
20 KiB
Go
// Mgmt
|
|
// Copyright (C) James Shubin and the project contributors
|
|
// Written by James Shubin <james@shubin.ca> and the project contributors
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
//
|
|
// Additional permission under GNU GPL version 3 section 7
|
|
//
|
|
// If you modify this program, or any covered work, by linking or combining it
|
|
// with embedded mcl code and modules (and that the embedded mcl code and
|
|
// modules which link with this program, contain a copy of their source code in
|
|
// the authoritative form) containing parts covered by the terms of any other
|
|
// license, the licensors of this program grant you additional permission to
|
|
// convey the resulting work. Furthermore, the licensors of this program grant
|
|
// the original author, James Shubin, additional permission to update this
|
|
// additional permission if he deems it necessary to achieve the goals of this
|
|
// additional permission.
|
|
|
|
//go:build !noembedded_provisioner
|
|
|
|
package coreprovisioner
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"os/user"
|
|
"strings"
|
|
|
|
"github.com/purpleidea/mgmt/cli"
|
|
"github.com/purpleidea/mgmt/entry"
|
|
"github.com/purpleidea/mgmt/lang/embedded"
|
|
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
|
"github.com/purpleidea/mgmt/lang/types"
|
|
"github.com/purpleidea/mgmt/lang/unification/fastsolver"
|
|
"github.com/purpleidea/mgmt/util"
|
|
"github.com/purpleidea/mgmt/util/errwrap"
|
|
"github.com/purpleidea/mgmt/util/password"
|
|
)
|
|
|
|
const (
|
|
// ModuleName is the prefix given to all the functions in this module.
|
|
ModuleName = "provisioner"
|
|
|
|
// Version is the version number of this module.
|
|
Version = "v0.0.1"
|
|
|
|
// Frontend is the name of the GAPI to run.
|
|
Frontend = "lang"
|
|
)
|
|
|
|
// NOTE: Grouped like shown is better, but you _can_ do it separately...
|
|
// Remember to add more patterns for nested child folders!
|
|
//
|
|
//go:embed metadata.yaml main.mcl files/*
|
|
var fs embed.FS // grouped is better
|
|
|
|
// NOTE: Separate as it's part of a different API and has a different function.
|
|
//
|
|
//go:embed top.mcl
|
|
var top []byte
|
|
|
|
// localArgs is our struct which is used to modify the CLI parser.
|
|
type localArgs struct {
|
|
// Interface is the local ethernet interface to provision from. It will
|
|
// be determined automatically if not specified.
|
|
Interface *string `arg:"--interface" help:"local ethernet interface to provision from" func:"cli_interface"` // eg: enp0s31f6 or eth0
|
|
|
|
// Network is the ip network with cidr that we want to use for the
|
|
// provisioner.
|
|
Network *string `arg:"--network" help:"network with cidr to use" func:"cli_network"` // eg: 192.168.42.0/24
|
|
|
|
// Router is the ip for this machine with included cidr. It must exist
|
|
// in the chosen network.
|
|
Router *string `arg:"--router" help:"router ip for this machine with cidr" func:"cli_router"` // eg: 192.168.42.1/24
|
|
|
|
// DNS are the list of upstream DNS servers to use during this process.
|
|
DNS []string `arg:"--dns,separate" help:"upstream dns servers to use" func:"cli_dns"` // eg: ["8.8.8.8", "1.1.1.1"]
|
|
|
|
// Prefix is a directory to store some provisioner specific state such
|
|
// as cached distro packages. It can be safely deleted. If you don't
|
|
// specify this value, one will be chosen automatically.
|
|
Prefix *string `arg:"--prefix" help:"local XDG_CACHE_HOME path" func:"cli_prefix"` // eg: ~/.cache/mgmt/provisioner/
|
|
|
|
// Firewalld will automatically open the required ports for being a
|
|
// provisioner. By default this is enabled, but it can be disabled if
|
|
// you use a different firewall system.
|
|
Firewalld bool `arg:"--firewalld" default:"true" help:"should we open firewalld on our provisioner" func:"cli_firewalld"`
|
|
|
|
// repo
|
|
|
|
// Distro specifies the distribution to use. Currently only `fedora` is
|
|
// supported.
|
|
Distro string `arg:"--distro" default:"fedora" help:"distribution to use" func:"cli_distro"`
|
|
|
|
// Version is the distribution version. This is a string, not an int.
|
|
Version string `arg:"--dversion" help:"distribution version" func:"cli_version"` // eg: "38"
|
|
|
|
// Arch is the distro architecture to use. Only x86_64 and aarch64 are
|
|
// currently supported. Patches welcome.
|
|
Arch string `arg:"--arch" default:"x86_64" help:"architecture to use" func:"cli_arch"`
|
|
|
|
// Flavour describes a flavour of distribution to provision. The value
|
|
// and what it does is highly dependent on the distro you specified. The
|
|
// default is set automatically depending on your distro variable.
|
|
Flavour *string `arg:"--flavour" help:"flavour of distribution" func:"cli_flavour"` // eg: "Workstation" or "Server"
|
|
|
|
// Mirror is the mirror to provision from. Pick one that supports both
|
|
// rsync AND https if you want the most capable provisioner features. A
|
|
// list for fedora is at: https://admin.fedoraproject.org/mirrormanager/
|
|
// eg: https://mirror.csclub.uwaterloo.ca/fedora/ for example. This
|
|
// points to: https://download.fedoraproject.org/pub/fedora/linux/ by
|
|
// default if unspecified, because it will automatically translate to a
|
|
// local mirror near you.
|
|
// TODO: Do we need to do a special step of checking the signature of
|
|
// the initrd or vmlinuz or the install.img file we first load?
|
|
Mirror string `arg:"--mirror" help:"https mirror for proxy provisioning" func:"cli_mirror"`
|
|
|
|
// Rsync is the rsync to sync from. Pick one that supports both rsync
|
|
// AND https if you want the most capable provisioner features. A list
|
|
// for fedora is at: https://admin.fedoraproject.org/mirrormanager/ eg:
|
|
// rsync://mirror.csclub.uwaterloo.ca/fedora-enchilada/linux/releases/
|
|
// for examples. Be advised that this option will likely pull down over
|
|
// 100GiB per os/arch/version combination. Consider only using `mirror`.
|
|
Rsync string `arg:"--rsync" help:"rsync mirror for full synchronization" func:"cli_rsync"`
|
|
|
|
// host
|
|
|
|
// Mac is the mac address of the host that we'd like to provision. If
|
|
// you omit this, than we will attempt to provision any computer which
|
|
// asks.
|
|
Mac *net.HardwareAddr `arg:"--mac" help:"mac address to provision" func:"cli_mac"`
|
|
|
|
// IP is the address of the host to provision. It must include the /cidr
|
|
// and be contained in the above network that was specified.
|
|
IP *string `arg:"--ip" help:"ip address with cidr of the host to provision" func:"cli_ip"` // eg: "192.168.42.114/24"
|
|
|
|
// Bios should be set true if you want to provision legacy machines.
|
|
Bios bool `arg:"--bios" help:"should we use bios or uefi" func:"cli_bios"`
|
|
|
|
// Password is an `openssl passwd -6` salted password. If you don't
|
|
// specify this, you will be prompted to enter the actual unhashed
|
|
// password, and it will be salted and hashed for you.
|
|
Password *string `arg:"--password" help:"the 'openssl passwd -6' salted password" func:"-"` // skip auto func gen
|
|
|
|
// Part is the magic partitioning scheme to use. At the moment you can
|
|
// either specify `plain` or `btrfs`. The default empty string will
|
|
// use the `plain` scheme.
|
|
Part string `arg:"--part" help:"partitioning scheme, read manual for details" func:"cli_part"` // eg: empty string for plain
|
|
|
|
// Packages are a list of additional distro packages to install. It's up
|
|
// to the user to make sure they exist and don't conflict with each
|
|
// other or the base installation packages.
|
|
Packages []string `arg:"--packages,separate" help:"list of additional distro packages to install" func:"cli_packages"`
|
|
|
|
// HandoffExec specifies that we want to handoff to this machine by
|
|
// running a single exec on firstboot. Usually an `mgmt run` command.
|
|
HandoffExec string `arg:"--handoff-exec" help:"exec command to run on firstboot" func:"cli_handoff_exec"` // eg: mgmt run ...
|
|
|
|
// HandoffCode specifies that we want to handoff to this machine with a
|
|
// static code deploy bolus. This is useful for isolated, one-time runs.
|
|
HandoffCode string `arg:"--handoff-code" help:"code dir to handoff to host" func:"cli_handoff_code"` // eg: /etc/mgmt/
|
|
|
|
// HandoffModulePath is the code handoff module path dir that we use.
|
|
// It's an absolute path on this local machine. (The provisioner!)
|
|
HandoffModulePath string `arg:"--handoff-module-path" help:"module path dir to use for handoff" func:"cli_handoff_module_path"` // eg: /etc/mgmt/modules/
|
|
|
|
// HandoffHostname specifies that we want to handoff a hostname to set
|
|
// on this machine. This is useful to make initial code handoff easier.
|
|
HandoffHostname string `arg:"--handoff-hostname" help:"hostname to handoff to host" func:"cli_handoff_hostname"` // eg: server1
|
|
|
|
// BmcURI specifies the BMC connect string we want to use for this host.
|
|
// This is a giant driver://user:password@host:port/whatever URL...
|
|
BmcURI string `arg:"--bmc-uri" help:"bmc connect string to use for this host" func:"cli_bmc_uri"`
|
|
|
|
// OnlyUnify tells the compiler to stop after type unification. This is
|
|
// used for testing.
|
|
OnlyUnify bool `arg:"--only-unify" help:"stop after type unification"`
|
|
}
|
|
|
|
// provisioner is our cli parser translator and general frontend object.
|
|
type provisioner struct {
|
|
init *entry.Init
|
|
|
|
// localArgs is a stored reference to the localArgs config struct that
|
|
// is used in the API of the command line parsing library. After it
|
|
// adds our flags and executes it, the resultant parsed values will be
|
|
// made available here where we've stored a copy.
|
|
localArgs *localArgs
|
|
|
|
// salted password
|
|
password string
|
|
}
|
|
|
|
// Init implements the Initable interface which lets us collect some data and
|
|
// handles from our caller.
|
|
func (obj *provisioner) Init(init *entry.Init) error {
|
|
obj.init = init // store some data/handles including logf
|
|
|
|
return nil
|
|
}
|
|
|
|
// Customize implements the Customizable interface which lets us manipulate the
|
|
// CLI.
|
|
func (obj *provisioner) Customize(a interface{}) (*cli.RunArgs, error) {
|
|
//if obj.init.Debug {
|
|
// obj.init.Logf("got: %T: %+v\n", a, a) // parent Args
|
|
//}
|
|
ctx := context.TODO()
|
|
|
|
runArgs, ok := a.(*cli.RunArgs)
|
|
if !ok {
|
|
// programming error?
|
|
return nil, fmt.Errorf("received invalid struct of type: %T", a)
|
|
}
|
|
|
|
libConfig := runArgs.Config
|
|
//var name string
|
|
var args interface{}
|
|
if cmd := runArgs.RunLang; cmd != nil {
|
|
//name = cliUtil.LookupSubcommand(obj, cmd) // "lang" // reflect.Value.Interface: cannot return value obtained from unexported field or method
|
|
args = cmd
|
|
}
|
|
//if name == "" {
|
|
// return nil, fmt.Errorf("no frontend activated")
|
|
//}
|
|
if args == nil {
|
|
return nil, fmt.Errorf("no frontend activated")
|
|
}
|
|
//if obj.init.Debug {
|
|
// obj.init.Logf("got: %T: %+v\n", args, args) // parent Args
|
|
//}
|
|
|
|
if obj.localArgs == nil {
|
|
// programming error
|
|
return nil, fmt.Errorf("could not convert/access our struct")
|
|
}
|
|
//localArgs := *obj.localArgs // optional
|
|
|
|
// Add custom defaults, and improve some as well.
|
|
|
|
if s := obj.localArgs.Interface; s == nil {
|
|
devices, err := util.GetPhysicalEthernetDevices()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if i := len(devices); i == 0 || i > 1 {
|
|
return nil, fmt.Errorf("couldn't guess ethernet device, got %d", i)
|
|
}
|
|
dev := devices[0]
|
|
obj.localArgs.Interface = &dev
|
|
}
|
|
obj.init.Logf("interface: %+v", *obj.localArgs.Interface)
|
|
|
|
if s := obj.localArgs.Network; s == nil {
|
|
x := "192.168.42.0/24"
|
|
obj.localArgs.Network = &x
|
|
}
|
|
_, netIPnet, err := net.ParseCIDR(*obj.localArgs.Network)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if s := obj.localArgs.Router; s == nil {
|
|
x := "192.168.42.1/24"
|
|
obj.localArgs.Router = &x
|
|
}
|
|
routerIP, _, err := net.ParseCIDR(*obj.localArgs.Router)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !netIPnet.Contains(routerIP) {
|
|
return nil, fmt.Errorf("network %s does not contain %s", *obj.localArgs.Network, *obj.localArgs.Router)
|
|
}
|
|
|
|
if s := obj.localArgs.IP; s == nil {
|
|
x := "192.168.42.13/24"
|
|
obj.localArgs.IP = &x
|
|
}
|
|
hostIP, _, err := net.ParseCIDR(*obj.localArgs.Router)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !netIPnet.Contains(hostIP) {
|
|
return nil, fmt.Errorf("network %s does not contain %s", *obj.localArgs.Network, *obj.localArgs.IP)
|
|
}
|
|
|
|
// TODO: add more validation
|
|
|
|
if p := obj.localArgs.Prefix; p != nil {
|
|
if strings.HasPrefix(*p, "~") {
|
|
expanded, err := util.ExpandHome(*p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
obj.localArgs.Prefix = &expanded
|
|
}
|
|
}
|
|
if obj.localArgs.Prefix == nil { // pick a default
|
|
user, err := user.Current()
|
|
if err != nil {
|
|
return nil, errwrap.Wrapf(err, "can't get current user")
|
|
}
|
|
|
|
xdg := os.Getenv("XDG_CACHE_HOME")
|
|
// Ensure there is a / at the end of the directory path.
|
|
if xdg != "" && !strings.HasSuffix(xdg, "/") {
|
|
xdg = xdg + "/"
|
|
}
|
|
if xdg == "" && user.HomeDir != "" {
|
|
xdg = fmt.Sprintf("%s/.cache/%s/", user.HomeDir, obj.init.Data.Program)
|
|
}
|
|
|
|
xdg += fmt.Sprintf("%s/", ModuleName) // pick a dir for this tool
|
|
obj.localArgs.Prefix = &xdg
|
|
}
|
|
obj.init.Logf("cache prefix: %+v", *obj.localArgs.Prefix)
|
|
|
|
if obj.localArgs.Mac == nil {
|
|
mac := net.HardwareAddr([]byte{}) // will print empty string
|
|
obj.localArgs.Mac = &mac
|
|
}
|
|
|
|
if obj.localArgs.Distro == "" {
|
|
return nil, fmt.Errorf("distro was not specified")
|
|
}
|
|
if obj.localArgs.Distro != "fedora" { // TODO: add other distros!
|
|
return nil, fmt.Errorf("only fedora is currently supported")
|
|
}
|
|
|
|
if obj.localArgs.Distro == "fedora" && obj.localArgs.Version == "" {
|
|
version, err := util.LatestFedoraVersion(ctx, obj.localArgs.Arch) // get a default for fedora
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
obj.localArgs.Version = version
|
|
}
|
|
if obj.localArgs.Version == "" {
|
|
return nil, fmt.Errorf("distro version was not specified")
|
|
}
|
|
|
|
if obj.localArgs.Arch == "" {
|
|
obj.localArgs.Arch = "x86_64"
|
|
}
|
|
|
|
if obj.localArgs.Distro == "fedora" && obj.localArgs.Flavour == nil {
|
|
flavour := "Workstation" // set a default for fedora
|
|
obj.localArgs.Flavour = &flavour
|
|
}
|
|
flavour := *obj.localArgs.Flavour
|
|
|
|
if obj.localArgs.Distro == "fedora" && flavour != strings.Title(flavour) {
|
|
return nil, fmt.Errorf("distro flavour should be in Title case")
|
|
}
|
|
|
|
if obj.localArgs.Distro == "fedora" && obj.localArgs.Mirror == "" {
|
|
obj.localArgs.Mirror = "https://download.fedoraproject.org/pub/fedora/linux/" // default
|
|
// This will auto-resolve once we get going.
|
|
m, err := util.GetFedoraDownloadURL(ctx)
|
|
if err == nil {
|
|
obj.localArgs.Mirror = m
|
|
}
|
|
}
|
|
|
|
obj.init.Logf("distro uid: %s%s-%s", obj.localArgs.Distro, obj.localArgs.Version, obj.localArgs.Arch)
|
|
obj.init.Logf("flavour: %+v", flavour)
|
|
obj.init.Logf("mirror: %+v", obj.localArgs.Mirror)
|
|
if len(obj.localArgs.Packages) > 0 {
|
|
obj.init.Logf("packages: %+v", strings.Join(obj.localArgs.Packages, ","))
|
|
}
|
|
|
|
modulePathArgs := []string{}
|
|
if p := obj.localArgs.HandoffModulePath; p != "" {
|
|
if strings.HasPrefix(p, "~") {
|
|
expanded, err := util.ExpandHome(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
obj.localArgs.HandoffModulePath = expanded
|
|
}
|
|
|
|
// Make path absolute.
|
|
if !strings.HasPrefix(obj.localArgs.HandoffModulePath, "/") {
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dir = dir + "/" // dir's should have a trailing slash!
|
|
obj.localArgs.HandoffModulePath = dir + obj.localArgs.HandoffModulePath
|
|
}
|
|
|
|
// Does this path actually exist?
|
|
if _, err := os.Stat(obj.localArgs.HandoffModulePath); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s := fmt.Sprintf("--module-path=%s", obj.localArgs.HandoffModulePath)
|
|
modulePathArgs = append(modulePathArgs, s)
|
|
}
|
|
|
|
if p := obj.localArgs.HandoffCode; p != "" {
|
|
if strings.HasPrefix(p, "~") {
|
|
expanded, err := util.ExpandHome(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
obj.localArgs.HandoffCode = expanded
|
|
}
|
|
|
|
// Make path absolute.
|
|
if !strings.HasPrefix(obj.localArgs.HandoffCode, "/") {
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dir = dir + "/" // dir's should have a trailing slash!
|
|
obj.localArgs.HandoffCode = dir + obj.localArgs.HandoffCode
|
|
}
|
|
|
|
// Does this path actually exist?
|
|
if _, err := os.Stat(obj.localArgs.HandoffCode); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
binary, err := util.ExecutablePath() // path to mgmt binary
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Type check this path before we provision?
|
|
out := ""
|
|
cmdOpts := &util.SimpleCmdOpts{
|
|
Debug: true,
|
|
Logf: func(format string, v ...interface{}) {
|
|
// XXX: HACK to make output more beautiful!
|
|
errorText := "cli parse error: "
|
|
s := fmt.Sprintf(format+"\n", v...)
|
|
for _, x := range strings.Split(s, "\n") {
|
|
if !strings.HasPrefix(x, errorText) {
|
|
continue
|
|
}
|
|
out = strings.TrimPrefix(x, errorText)
|
|
}
|
|
},
|
|
}
|
|
// TODO: Add a --quiet flag instead of the above filter hack.
|
|
cmdArgs := []string{"run", "--tmp-prefix", "lang", "--only-unify"}
|
|
cmdArgs = append(cmdArgs, modulePathArgs...)
|
|
cmdArgs = append(cmdArgs, obj.localArgs.HandoffCode)
|
|
if err := util.SimpleCmd(ctx, binary, cmdArgs, cmdOpts); err != nil {
|
|
return nil, fmt.Errorf("handoff code didn't type check: %s", out)
|
|
}
|
|
|
|
obj.init.Logf("handoff: %s", obj.localArgs.HandoffCode)
|
|
if len(modulePathArgs) > 0 {
|
|
obj.init.Logf("handoff module path: %s", obj.localArgs.HandoffModulePath)
|
|
}
|
|
}
|
|
if h := obj.localArgs.HandoffHostname; h != "" {
|
|
obj.init.Logf("handoff hostname: %s", h)
|
|
}
|
|
|
|
// Do this last to let others fail early b/c this has user interaction.
|
|
if obj.localArgs.Password == nil {
|
|
b, err := password.ReadPasswordCtxPrompt(ctx, "["+ModuleName+"] password: ")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fmt.Printf("\n") // leave space after the prompt
|
|
// XXX: I have no idea if I am doing this correctly, and I have
|
|
// no idea if the library is doing this correctly. Please check!
|
|
// XXX: erase values: https://github.com/golang/go/issues/21865
|
|
hash, err := password.SaltedSHA512Password(b) // internally salted
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
obj.password = hash // store
|
|
} else if p := *obj.localArgs.Password; p == "-" {
|
|
// XXX: pull from a file or something else if we choose this
|
|
return nil, fmt.Errorf("not implemented")
|
|
} else if len(p) != 106 { // salted length should be 106 chars AIUI
|
|
return nil, fmt.Errorf("password must be salted with openssl passwd -6")
|
|
} else {
|
|
obj.password = p // salted
|
|
}
|
|
|
|
// Make any changes here that we want to...
|
|
runArgs.RunLang.SkipUnify = true // speed things up for known good code
|
|
if obj.localArgs.OnlyUnify {
|
|
obj.init.Logf("stopping after type unification...")
|
|
runArgs.RunLang.OnlyUnify = true
|
|
runArgs.RunLang.SkipUnify = false // can't skip if we only unify
|
|
}
|
|
|
|
name := fastsolver.Name
|
|
// TODO: Remove these optimizations when the solver is faster overall.
|
|
runArgs.RunLang.UnifySolver = &name
|
|
//runArgs.RunLang.UnifyOptimizations = []string{
|
|
// fastsolver.TODO,
|
|
//}
|
|
libConfig.TmpPrefix = true
|
|
libConfig.NoPgp = true
|
|
|
|
runArgs.Config = libConfig // store any changes we made
|
|
return runArgs, nil
|
|
}
|
|
|
|
// Register generates some functions that expose the output of our local CLI.
|
|
func (obj *provisioner) Register(moduleName string) error {
|
|
|
|
// Build all the functions...
|
|
if err := simple.StructRegister(moduleName, obj.localArgs); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Build a few separately...
|
|
simple.ModuleRegister(moduleName, "cli_password", &simple.Scaffold{
|
|
I: &simple.Info{
|
|
Pure: false,
|
|
Memo: false,
|
|
Fast: false,
|
|
Spec: false,
|
|
},
|
|
T: types.NewType("func() str"),
|
|
F: func(ctx context.Context, input []types.Value) (types.Value, error) {
|
|
if obj.localArgs == nil {
|
|
// programming error
|
|
return nil, fmt.Errorf("could not convert/access our struct")
|
|
}
|
|
|
|
// TODO: plumb through the password lookup here instead?
|
|
|
|
//localArgs := *obj.localArgs // optional
|
|
return &types.StrValue{
|
|
V: obj.password,
|
|
}, nil
|
|
},
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func init() {
|
|
fullModuleName := embedded.FullModuleName(ModuleName)
|
|
//fs := embedded.MergeFS(metadata, main, files) // To merge filesystems!
|
|
embedded.ModuleRegister(fullModuleName, fs)
|
|
|
|
var a interface{} = &localArgs{} // must use the pointer here
|
|
|
|
custom := &provisioner{
|
|
localArgs: a.(*localArgs), // force the correct type
|
|
}
|
|
|
|
entry.Register(ModuleName, &entry.Data{
|
|
Program: ModuleName,
|
|
Version: Version, // TODO: get from git?
|
|
|
|
Debug: false,
|
|
Logf: func(format string, v ...interface{}) {
|
|
log.Printf(format, v...)
|
|
},
|
|
|
|
Args: a,
|
|
Custom: custom,
|
|
|
|
Frontend: Frontend,
|
|
Top: top,
|
|
})
|
|
|
|
if err := custom.Register(fullModuleName); err != nil { // functions from cli
|
|
panic(err)
|
|
}
|
|
}
|