cmd/rootCmd.go (146 lines of code) (raw):

// MIT License // // Copyright (c) Microsoft Corporation. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE package main import ( "fmt" "os" "path/filepath" "strings" "github.com/Azure/mpf/pkg/domain" "github.com/Azure/mpf/pkg/infrastructure/mpfSharedUtils" "github.com/google/uuid" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" log "github.com/sirupsen/logrus" ) var ( defaultConfigFilename = "stingoftheviper" envPrefix = "MPF" replaceHyphenWithCamelCase = false flgSubscriptionID string flgTenantID string flgSPClientID string flgSPObjectID string flgSPClientSecret string flgShowDetailedOutput bool flgJSONOutput bool flgVerbose bool flgDebug bool // RootCmd *cobra.Command ) func NewRootCommand() *cobra.Command { rootCmd := &cobra.Command{ Use: "azmpf", Short: "Find minimum permissions required for Azure deployments", Long: `Find minimum permissions required for Azure deployments including ARM and Terraform based deployments. For example: This CLI allows you to find the minimum permissions required for Azure deployments including ARM and Terraform based deployments. A Service Principal is required to run this CLI. All permissions associated with the Service principal are initially wiped by this command:`, Example: `azmpf arm --subscriptionID <subscriptionID> --tenantID <tenantID> --spClientID <spClientID> --spObjectID <spObjectID> --spClientSecret <spClientSecret> az-mpm terraform --subscriptionID <subscriptionID> --tenantID <tenantID> --spClientID <spClientID> --spObjectID <spObjectID> --spClientSecret <spClientSecret> --executablePath <executablePath> --workingDir <workingDir> --varFilePath <varFilePath> `, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return initializeConfig(cmd) }, Run: func(cmd *cobra.Command, args []string) { }, } // Define cobra flags, the default value has the lowest (least significant) precedence rootCmd.PersistentFlags().StringVarP(&flgSubscriptionID, "subscriptionID", "s", "", "Azure Subscription ID") rootCmd.PersistentFlags().StringVarP(&flgTenantID, "tenantID", "", "", "Azure Tenant ID") rootCmd.PersistentFlags().StringVarP(&flgSPClientID, "spClientID", "", "", "Service Principal Client ID") rootCmd.PersistentFlags().StringVarP(&flgSPObjectID, "spObjectID", "", "", "Service Principal Object ID") rootCmd.PersistentFlags().StringVarP(&flgSPClientSecret, "spClientSecret", "", "", "Service Principal Client Secret") rootCmd.PersistentFlags().BoolVarP(&flgShowDetailedOutput, "showDetailedOutput", "", false, "Show detailed output") rootCmd.PersistentFlags().BoolVarP(&flgJSONOutput, "jsonOutput", "", false, "Output in JSON format") rootCmd.PersistentFlags().BoolVarP(&flgVerbose, "verbose", "v", false, "verbose output") rootCmd.PersistentFlags().BoolVarP(&flgDebug, "debug", "d", false, "debug output") err := rootCmd.MarkPersistentFlagRequired("subscriptionID") if err != nil { log.Errorf("Error marking flag required for subscription ID: %v\n", err) } err = rootCmd.MarkPersistentFlagRequired("tenantID") if err != nil { log.Errorf("Error marking flag required for tenant ID: %v\n", err) } err = rootCmd.MarkPersistentFlagRequired("spClientID") if err != nil { log.Errorf("Error marking flag required for SP client ID: %v\n", err) } err = rootCmd.MarkPersistentFlagRequired("spObjectID") if err != nil { log.Errorf("Error marking flag required for SP object ID: %v\n", err) } err = rootCmd.MarkPersistentFlagRequired("spClientSecret") if err != nil { log.Errorf("Error marking flag required for SP client secret: %v\n", err) } rootCmd.MarkFlagsMutuallyExclusive("showDetailedOutput", "jsonOutput") // Add subcommands rootCmd.AddCommand(NewARMCommand()) rootCmd.AddCommand(NewBicepCommand()) rootCmd.AddCommand(NewTerraformCommand()) return rootCmd } func initializeConfig(cmd *cobra.Command) error { v := viper.New() v.SetConfigName(defaultConfigFilename) v.AddConfigPath(".") if err := v.ReadInConfig(); err != nil { // It's okay if there isn't a config file if _, ok := err.(viper.ConfigFileNotFoundError); !ok { return err } } v.SetEnvPrefix(envPrefix) v.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) v.AutomaticEnv() bindFlags(cmd, v) return nil } // Bind each cobra flag to its associated viper configuration (config file and environment variable) func bindFlags(cmd *cobra.Command, v *viper.Viper) { cmd.Flags().VisitAll(func(f *pflag.Flag) { // Determine the naming convention of the flags when represented in the config file configName := f.Name // If using camelCase in the config file, replace hyphens with a camelCased string. // Since viper does case-insensitive comparisons, we don't need to bother fixing the case, and only need to remove the hyphens. if replaceHyphenWithCamelCase { configName = strings.ReplaceAll(f.Name, "-", "") } // Apply the viper config value to the flag when the flag is not set and viper has a value if !f.Changed && v.IsSet(configName) { val := v.Get(configName) err := cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)) if err != nil { log.Errorf("Error setting flag %s: %v\n", f.Name, err) } } }) } func setLogLevel() { if flgVerbose { log.SetLevel(log.InfoLevel) } if flgDebug { log.SetLevel(log.DebugLevel) } } func getRootMPFConfig() domain.MPFConfig { mpfRole := domain.Role{} roleDefUUID, _ := uuid.NewRandom() mpfRole.RoleDefinitionID = roleDefUUID.String() mpfRole.RoleDefinitionName = fmt.Sprintf("tmp-rol-%s", mpfSharedUtils.GenerateRandomString(7)) mpfRole.RoleDefinitionResourceID = fmt.Sprintf("/subscriptions/%s/providers/Microsoft.Authorization/roleDefinitions/%s", flgSubscriptionID, mpfRole.RoleDefinitionID) log.Infoln("roleDefinitionResourceID:", mpfRole.RoleDefinitionResourceID) return domain.MPFConfig{ SubscriptionID: flgSubscriptionID, TenantID: flgTenantID, SP: domain.ServicePrincipal{ SPClientID: flgSPClientID, SPObjectID: flgSPObjectID, SPClientSecret: flgSPClientSecret, }, Role: mpfRole, } } func getAbsolutePath(path string) (string, error) { absPath := path if !filepath.IsAbs(path) { absWorkingDir, err := os.Getwd() if err != nil { return "", err } absPath = absWorkingDir + "/" + absPath } return absPath, nil }