Skip to content

Commit

Permalink
Merge pull request #136 from katherinelc321/addCostRange
Browse files Browse the repository at this point in the history
Add cost range option
  • Loading branch information
katherinelc321 authored Sep 9, 2021
2 parents ad5a1ec + 769304b commit 6c83d72
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 43 deletions.
113 changes: 77 additions & 36 deletions cmd/cost/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -23,48 +24,50 @@ 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
type getOptions struct {
ou string
recursive bool
time string
start string
end string
csv bool

genericclioptions.IOStreams
Expand All @@ -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
Expand Down Expand Up @@ -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",
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
}
Expand All @@ -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 {
Expand All @@ -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
}

Expand All @@ -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
Expand Down
38 changes: 31 additions & 7 deletions cmd/cost/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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 {
Expand All @@ -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
}
Expand All @@ -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
}

Expand All @@ -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)
Expand Down

0 comments on commit 6c83d72

Please sign in to comment.