diff --git a/cmd/cost/get.go b/cmd/cost/get.go index cb4c6140..d2a4657d 100644 --- a/cmd/cost/get.go +++ b/cmd/cost/get.go @@ -2,12 +2,13 @@ package cost import ( "fmt" - awsprovider "github.com/openshift/osdctl/pkg/provider/aws" - "github.com/spf13/cobra" "log" "strconv" "time" + awsprovider "github.com/openshift/osdctl/pkg/provider/aws" + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericclioptions" cmdutil "k8s.io/kubectl/pkg/cmd/util" @@ -23,41 +24,41 @@ func newCmdGet(streams genericclioptions.IOStreams) *cobra.Command { Use: "get", Short: "Get total cost of a given OU", Run: func(cmd *cobra.Command, args []string) { - - awsClient, err := opsCost.initAWSClients() - cmdutil.CheckErr(err) - - //Get information regarding Organizational Unit - OU := getOU(awsClient, ops.ou) - - var cost float64 - var unit string - - if ops.recursive { //Get cost of given OU by aggregating costs of all (including immediate) accounts under OU - if err := getOUCostRecursive(&cost, &unit, OU, awsClient, &ops.time); err != nil { - log.Fatalln("Error getting cost of OU recursively:", err) - } - } else { //Get cost of given OU by aggregating costs of only immediate accounts under given OU - if err := getOUCost(&cost, &unit, OU, awsClient, &ops.time); err != nil { - log.Fatalln("Error getting cost of OU:", err) - } - } - printCostGet(cost, unit, ops, OU) + cmdutil.CheckErr(ops.checkArgs(cmd, args)) + cmdutil.CheckErr(ops.run()) }, } getCmd.Flags().StringVar(&ops.ou, "ou", "", "get OU ID") getCmd.Flags().BoolVarP(&ops.recursive, "recursive", "r", false, "recurse through OUs") getCmd.Flags().StringVarP(&ops.time, "time", "t", "", "set time. One of 'LM', 'MTD', 'TYD', '3M', '6M', '1Y'") + getCmd.Flags().StringVar(&ops.start, "start", "", "set start date range") + getCmd.Flags().StringVar(&ops.end, "end", "", "set end date range") getCmd.Flags().BoolVar(&ops.csv, "csv", false, "output result as csv") - if err := getCmd.MarkFlagRequired("ou"); err != nil { - log.Fatalln("OU flag:", err) + return getCmd +} + +func (o *getOptions) checkArgs(cmd *cobra.Command, _ []string) error { + + // If no date range or time is define error out + if o.start == "" && o.end == "" && o.time == "" { + return cmdutil.UsageErrorf(cmd, "Please provide a date range or a predefined time") } - if err := getCmd.MarkFlagRequired("time"); err != nil { - log.Fatalln("time flag:", err) + // If both date range and time are defined error out + if o.start != "" && o.end != "" && o.time != "" { + return cmdutil.UsageErrorf(cmd, "Please provide either a date range or a predefined time") } - - return getCmd + // If either start or end is missing error out + if o.start != "" && o.end == "" { + return cmdutil.UsageErrorf(cmd, "Please provide end of date range") + } + if o.start == "" && o.end != "" { + return cmdutil.UsageErrorf(cmd, "Please provide start of date range") + } + if o.ou == "" { + return cmdutil.UsageErrorf(cmd, "Please provide OU") + } + return nil } //Store flag options for get command @@ -65,6 +66,8 @@ type getOptions struct { ou string recursive bool time string + start string + end string csv bool genericclioptions.IOStreams @@ -76,6 +79,33 @@ func newGetOptions(streams genericclioptions.IOStreams) *getOptions { } } +func (o *getOptions) run() error { + + awsClient, err := opsCost.initAWSClients() + if err != nil { + return err + } + + //Get information regarding Organizational Unit + OU := getOU(awsClient, o.ou) + + var cost float64 + var unit string + + if o.recursive { //Get cost of given OU by aggregating costs of all (including immediate) accounts under OU + if err := o.getOUCostRecursive(&cost, &unit, OU, awsClient); err != nil { + log.Fatalln("Error getting cost of OU recursively:", err) + } + } else { //Get cost of given OU by aggregating costs of only immediate accounts under given OU + if err := o.getOUCost(&cost, &unit, OU, awsClient); err != nil { + log.Fatalln("Error getting cost of OU:", err) + } + } + + printCostGet(cost, unit, o, OU) + return nil +} + //Get account IDs of immediate accounts under given OU func getAccounts(OU *organizations.OrganizationalUnit, awsClient awsprovider.Client) ([]*string, error) { var accountSlice []*string @@ -179,10 +209,20 @@ func getOUsRecursive(OU *organizations.OrganizationalUnit, awsClient awsprovider } //Get cost of given account -func getAccountCost(accountID *string, unit *string, awsClient awsprovider.Client, timePtr *string, cost *float64) error { +func (o *getOptions) getAccountCost(accountID *string, unit *string, awsClient awsprovider.Client, cost *float64) error { + + var start, end, granularity string + if o.time != "" { + start, end = getTimePeriod(&o.time) + granularity = "MONTHLY" + } + + if o.start != "" && o.end != "" { + start = o.start + end = o.end + granularity = "DAILY" + } - start, end := getTimePeriod(timePtr) - granularity := "MONTHLY" metrics := []string{ "NetUnblendedCost", } @@ -224,7 +264,7 @@ func getAccountCost(accountID *string, unit *string, awsClient awsprovider.Clien } //Get cost of given OU by aggregating costs of only immediate accounts under given OU -func getOUCost(cost *float64, unit *string, OU *organizations.OrganizationalUnit, awsClient awsprovider.Client, timePtr *string) error { +func (o *getOptions) getOUCost(cost *float64, unit *string, OU *organizations.OrganizationalUnit, awsClient awsprovider.Client) error { //Populate accounts accounts, err := getAccounts(OU, awsClient) if err != nil { @@ -233,7 +273,7 @@ func getOUCost(cost *float64, unit *string, OU *organizations.OrganizationalUnit //Increment costs of accounts for _, account := range accounts { - if err := getAccountCost(account, unit, awsClient, timePtr, cost); err != nil { + if err := o.getAccountCost(account, unit, awsClient, cost); err != nil { return err } } @@ -242,7 +282,7 @@ func getOUCost(cost *float64, unit *string, OU *organizations.OrganizationalUnit } //Get cost of given OU by aggregating costs of all (including immediate) accounts under OU -func getOUCostRecursive(cost *float64, unit *string, OU *organizations.OrganizationalUnit, awsClient awsprovider.Client, timePtr *string) error { +func (o *getOptions) getOUCostRecursive(cost *float64, unit *string, OU *organizations.OrganizationalUnit, awsClient awsprovider.Client) error { //Populate OUs OUs, err := getOUs(OU, awsClient) if err != nil { @@ -251,13 +291,13 @@ func getOUCostRecursive(cost *float64, unit *string, OU *organizations.Organizat //Loop through all child OUs, get their costs, and store it to cost of current OU for _, childOU := range OUs { - if err := getOUCostRecursive(cost, unit, childOU, awsClient, timePtr); err != nil { + if err := o.getOUCostRecursive(cost, unit, childOU, awsClient); err != nil { return err } } //Return cost of child OUs + cost of immediate accounts under current OU - if err := getOUCost(cost, unit, OU, awsClient, timePtr); err != nil { + if err := o.getOUCost(cost, unit, OU, awsClient); err != nil { return err } @@ -266,6 +306,7 @@ func getOUCostRecursive(cost *float64, unit *string, OU *organizations.Organizat //Get time period based on time flag func getTimePeriod(timePtr *string) (string, string) { + t := time.Now() //Starting from the 1st of the current month last year i.e. if today is 2020-06-29, then start date is 2019-06-01 diff --git a/cmd/cost/list.go b/cmd/cost/list.go index 41acff00..c25e0e8a 100644 --- a/cmd/cost/list.go +++ b/cmd/cost/list.go @@ -19,12 +19,11 @@ func newCmdList(streams genericclioptions.IOStreams) *cobra.Command { Use: "list", Short: "List the cost of each OU under given OU", Run: func(cmd *cobra.Command, args []string) { - + cmdutil.CheckErr(ops.checkArgs(cmd, args)) awsClient, err := opsCost.initAWSClients() cmdutil.CheckErr(err) OU := getOU(awsClient, ops.ou) - if err := listCostsUnderOU(OU, awsClient, ops); err != nil { log.Fatalln("Error listing costs under OU:", err) } @@ -33,6 +32,8 @@ func newCmdList(streams genericclioptions.IOStreams) *cobra.Command { listCmd.Flags().StringVar(&ops.ou, "ou", "", "get OU ID") // list supported time args listCmd.Flags().StringVarP(&ops.time, "time", "t", "", "set time. One of 'LM', 'MTD', 'TYD', '3M', '6M', '1Y'") + listCmd.Flags().StringVar(&ops.start, "start", "", "set start date range") + listCmd.Flags().StringVar(&ops.end, "end", "", "set end date range") listCmd.Flags().BoolVar(&ops.csv, "csv", false, "output result as csv") if err := listCmd.MarkFlagRequired("ou"); err != nil { @@ -46,11 +47,33 @@ func newCmdList(streams genericclioptions.IOStreams) *cobra.Command { return listCmd } +func (o *listOptions) checkArgs(cmd *cobra.Command, _ []string) error { + // check that only time or start/end is provided + if o.start == "" && o.end == "" && o.time == "" { + return cmdutil.UsageErrorf(cmd, "Please provide a date range or a predefined time") + } + if o.start != "" && o.end != "" && o.time != "" { + return cmdutil.UsageErrorf(cmd, "Please provide either a date range or a predefined time") + } + if o.start != "" && o.end == "" { + return cmdutil.UsageErrorf(cmd, "Please provide end of date range") + } + if o.start == "" && o.end != "" { + return cmdutil.UsageErrorf(cmd, "Please provide start of date range") + } + if o.ou == "" { + return cmdutil.UsageErrorf(cmd, "Please provide OU") + } + return nil +} + //Store flag options for get command type listOptions struct { - ou string - time string - csv bool + ou string + time string + start string + end string + csv bool genericclioptions.IOStreams } @@ -72,7 +95,8 @@ func listCostsUnderOU(OU *organizations.OrganizationalUnit, awsClient awsprovide var unit string var isChildNode bool - if err := getOUCostRecursive(&cost, &unit, OU, awsClient, &ops.time); err != nil { + o := &getOptions{} + if err := o.getOUCostRecursive(&cost, &unit, OU, awsClient); err != nil { return err } @@ -84,7 +108,7 @@ func listCostsUnderOU(OU *organizations.OrganizationalUnit, awsClient awsprovide cost = 0 isChildNode = true - if err := getOUCostRecursive(&cost, &unit, childOU, awsClient, &ops.time); err != nil { + if err := o.getOUCostRecursive(&cost, &unit, childOU, awsClient); err != nil { return err } printCostList(cost, unit, childOU, ops, isChildNode)