pkg/cmd/root/root.go (146 lines of code) (raw):
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
package root
import (
// Blank import else compiler complains it's unused
_ "embed"
"errors"
"fmt"
"log"
"strings"
"github.com/fsnotify/fsnotify"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/aws/amazon-ec2-metadata-mock/pkg/cmd/asglifecycle"
"github.com/aws/amazon-ec2-metadata-mock/pkg/cmd/cmdutil"
"github.com/aws/amazon-ec2-metadata-mock/pkg/cmd/events"
gf "github.com/aws/amazon-ec2-metadata-mock/pkg/cmd/root/globalflags"
"github.com/aws/amazon-ec2-metadata-mock/pkg/cmd/spot"
cfg "github.com/aws/amazon-ec2-metadata-mock/pkg/config"
r "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/root"
"github.com/aws/amazon-ec2-metadata-mock/pkg/server"
)
var (
c cfg.Config
command *cobra.Command
//go:embed version.txt
version string
// defaults
cfgMdPrefix = cfg.GetCfgMdValPrefix()
cfgDnPrefix = cfg.GetCfgDnValPrefix()
defaultCfg = map[string]interface{}{
gf.ConfigFileFlag: cfg.GetDefaultCfgFileName(),
gf.MockDelayInSecFlag: 0,
gf.MockTriggerTimeFlag: "",
gf.MockIPCountFlag: 2,
gf.SaveConfigToFileFlag: false,
gf.WatchConfigFileFlag: false,
gf.Imdsv2Flag: false,
gf.RebalanceDelayInSecFlag: 0,
gf.RebalanceTriggerTimeFlag: "",
gf.ASGTerminationDelayInSecFlag: 0,
gf.ASGTerminationTriggerTimeFlag: "",
}
)
func init() {
cobra.OnInitialize(initializeConfig)
command = NewCmd()
}
func initializeConfig() {
cfg.LoadConfigForRoot(gf.ConfigFileFlag, defaultCfg)
}
// NewCmd returns a new root command after setting it up
func NewCmd() *cobra.Command {
var cmd = &cobra.Command{
Use: cmdutil.BinName + " <command> [arguments]",
SuggestFor: []string{"mock", "ec2-mock", "ec2-metadata-mock"},
Version: version,
Example: fmt.Sprintf(" %s --mock-delay-sec 10\tmocks all metadata paths\n %s spot --action terminate\tmocks spot ITN only", cmdutil.BinName, cmdutil.BinName),
PersistentPreRunE: setupAndSaveConfig, // persistentPreRun runs before PreRun
PreRunE: preRun,
Run: run,
Short: "Tool to mock Amazon EC2 instance metadata",
Long: cmdutil.BinName + " is a tool to mock Amazon EC2 instance metadata.",
}
cmd.SetVersionTemplate(`{{.Version}}`)
// global flags
cmd.PersistentFlags().StringP(gf.HostNameFlag, "n", "", "the HTTP hostname for the mock url (default: 0.0.0.0)")
cmd.PersistentFlags().StringP(gf.PortFlag, "p", "", "the HTTP port where the mock runs (default: 1338)")
cmd.PersistentFlags().StringP(gf.ConfigFileFlag, "c", "", "config file for cli input parameters in json format (default: "+cfg.GetDefaultCfgFileName()+")")
cmd.PersistentFlags().BoolP(gf.SaveConfigToFileFlag, "s", false, "whether to save processed config from all input sources in "+cfg.GetSavedCfgFileName()+" in $HOME or working dir, if homedir is not found (default: false)")
cmd.PersistentFlags().BoolP(gf.WatchConfigFileFlag, "w", false, "whether to watch the config file "+cfg.GetSavedCfgFileName()+" in $HOME or working dir, if homedir is not found (default: false)")
cmd.PersistentFlags().Int64P(gf.MockDelayInSecFlag, "d", 0, "spot itn delay in seconds, relative to the application start time (default: 0 seconds)")
cmd.PersistentFlags().String(gf.MockTriggerTimeFlag, "", "spot itn trigger time in RFC3339 format. This takes priority over "+gf.MockDelayInSecFlag+" (default: none)")
cmd.PersistentFlags().Int64P(gf.MockIPCountFlag, "x", 2, "number of IPs in a cluster that can receive a Spot Interrupt Notice and/or Scheduled Event")
cmd.PersistentFlags().BoolP(gf.Imdsv2Flag, "I", false, "whether to enable IMDSv2 only, requiring a session token when submitting requests (default: false, meaning both IMDS v1 and v2 are enabled)")
cmd.PersistentFlags().Int64(gf.RebalanceDelayInSecFlag, 0, "rebalance rec delay in seconds, relative to the application start time (default: 0 seconds)")
cmd.PersistentFlags().String(gf.RebalanceTriggerTimeFlag, "", "rebalance rec trigger time in RFC3339 format. This takes priority over "+gf.RebalanceDelayInSecFlag+" (default: none)")
cmd.PersistentFlags().Int64P(gf.ASGTerminationDelayInSecFlag, "", 0, "asg termination delay in seconds, relative to the application start time (default: 0 seconds)")
cmd.PersistentFlags().Int64P(gf.ASGTerminationTriggerTimeFlag, "", 0, "asg termination trigger time in RFC3339 format. This takes priority over "+gf.ASGTerminationDelayInSecFlag+" (default: none)")
// add subcommands
cmd.AddCommand(spot.Command, events.Command, asglifecycle.Command)
// bind all non-metadata flags at top level
var topLevelGFlags []*pflag.Flag
for _, n := range gf.GetTopLevelFlags() {
topLevelGFlags = append(topLevelGFlags, cmd.PersistentFlags().Lookup(n))
}
cfg.BindTopLevelGFlags(topLevelGFlags)
// bind second level flags
cfg.BindServerCfg(cmd.PersistentFlags().Lookup(gf.HostNameFlag))
cfg.BindServerCfg(cmd.PersistentFlags().Lookup(gf.PortFlag))
return cmd
}
func setupAndSaveConfig(cmd *cobra.Command, args []string) error {
if err := injectViperConfig(); err != nil {
return err
}
saveConfigToFile()
if watchCfg := viper.GetBool(gf.WatchConfigFileFlag); watchCfg {
viper.OnConfigChange(func(_ fsnotify.Event) {
if err := injectViperConfig(); err != nil {
log.Printf("Failed to reset config on config change: %v\n", err)
return
}
saveConfigToFile()
server.Reset()
cmdutil.RegisterHandlers(cmd, c)
})
viper.WatchConfig()
}
return nil
}
// injectViperConfig after the cobra initializations and before prerun
func injectViperConfig() error {
var aemmConfig = cfg.Config{}
err := viper.Unmarshal(&aemmConfig)
if err != nil {
return fmt.Errorf("Fatal error while attempting to load viper config: %s", err)
}
setConfig(aemmConfig)
return nil
}
// saveConfigToFile saves the config used by the tool to a local file, errors are logged as warnings
func saveConfigToFile() {
if saveCfg := viper.GetBool(gf.SaveConfigToFileFlag); saveCfg {
cfg.WriteConfigToFile()
}
}
func setConfig(config cfg.Config) {
c = config
spot.SetConfig(config)
events.SetConfig(config)
asglifecycle.SetConfig(config)
}
func preRun(cmd *cobra.Command, args []string) error {
if cfgErrors := validateConfig(); cfgErrors != nil {
return errors.New(strings.Join(cfgErrors, ""))
}
return nil
}
func validateConfig() []string {
var errStrings []string
// validate subcommands' config
errStrings = append(errStrings, spot.ValidateLocalConfig()...)
errStrings = append(errStrings, events.ValidateLocalConfig()...)
errStrings = append(errStrings, asglifecycle.ValidateLocalConfig()...)
if c.MockTriggerTime != "" {
if err := cmdutil.ValidateRFC3339TimeFormat(gf.MockTriggerTimeFlag, c.MockTriggerTime); err != nil {
errStrings = append(errStrings, err.Error())
}
}
return errStrings
}
func run(cmd *cobra.Command, args []string) {
log.Printf("Initiating %s for all mocks on port %s\n", cmdutil.BinName, c.Server.Port)
cmdutil.PrintFlags(cmd.Flags())
cmdutil.RegisterHandlers(cmd, c)
r.Mock(c)
}