Skip to content

Commit e63b41c

Browse files
committed
initial add for checking aws billing
1 parent 9a32781 commit e63b41c

File tree

12 files changed

+1634
-0
lines changed

12 files changed

+1634
-0
lines changed

lw-billing/cmd/aws.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"lw-billing/cmd/lwaws"
6+
"lw-billing/helpers"
7+
)
8+
9+
var awsCmd = &cobra.Command{
10+
Use: "aws",
11+
Short: "AWS Billing",
12+
Long: `AWS Billing`,
13+
Run: func(cmd *cobra.Command, args []string) {
14+
billingCSV := lwaws.ParseBilling(cmd)
15+
//profiles := lwaws.ParseProfiles(cmd)
16+
debug := helpers.ParseDebug(cmd)
17+
lwaws.Run(billingCSV, debug)
18+
},
19+
}
20+
21+
func init() {
22+
rootCmd.AddCommand(awsCmd)
23+
awsCmd.Flags().StringP("billing", "b", "", "AWS billing csv")
24+
awsCmd.Flags().BoolP("debug", "d", false, "Show Debug Logs")
25+
}

lw-billing/cmd/azure.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"lw-billing/cmd/lwazure"
6+
"lw-billing/helpers"
7+
)
8+
9+
var azureCmd = &cobra.Command{
10+
Use: "azure",
11+
Short: "Azure Billing",
12+
Long: `Azure Billing`,
13+
Run: func(cmd *cobra.Command, args []string) {
14+
debug := helpers.ParseDebug(cmd)
15+
lwazure.Run(debug)
16+
},
17+
}
18+
19+
func init() {
20+
rootCmd.AddCommand(azureCmd)
21+
azureCmd.Flags().BoolP("debug", "d", false, "Show Debug Logs")
22+
}

lw-billing/cmd/gcp.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"lw-billing/cmd/lwgcp"
6+
"lw-billing/helpers"
7+
// compute "google.golang.org/api/compute/v1"
8+
)
9+
10+
var gcpCmd = &cobra.Command{
11+
Use: "gcp",
12+
Short: "GCP Billing",
13+
Long: `GCP Billing`,
14+
Run: func(cmd *cobra.Command, args []string) {
15+
debug := helpers.ParseDebug(cmd)
16+
lwgcp.Run(debug)
17+
},
18+
}
19+
20+
func init() {
21+
rootCmd.AddCommand(gcpCmd)
22+
gcpCmd.Flags().BoolP("debug", "d", false, "Show Debug Logs")
23+
}

lw-billing/cmd/lwaws/aws.go

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package lwaws
2+
3+
import (
4+
"encoding/csv"
5+
"fmt"
6+
log "github.com/sirupsen/logrus"
7+
"github.com/spf13/cobra"
8+
"io"
9+
"lw-billing/helpers"
10+
"os"
11+
"strconv"
12+
"strings"
13+
)
14+
15+
func Run(billingCSV string, debug bool) {
16+
if debug {
17+
log.SetLevel(log.DebugLevel)
18+
}
19+
20+
instanceTypes := loadAWSInstanceTypes()
21+
instances := getVMVCPU(billingCSV, instanceTypes)
22+
lambdas := getLambdaVCPU(billingCSV)
23+
getFargateVCPU(billingCSV)
24+
25+
vmvcpus := countUpVMs(instances)
26+
println("VM vCPUS:", vmvcpus)
27+
28+
lambdavcpus := countUpLambdas(lambdas)
29+
println("Lambda vCPUS:", lambdavcpus)
30+
31+
println("Total AWS vCPUS:", vmvcpus+lambdavcpus)
32+
}
33+
34+
const (
35+
hoursPerMonth = 720
36+
recordID = 4
37+
linkedAccountID = 2
38+
usageType = 15
39+
usageQty = 21
40+
productCode = 12
41+
payerAccountID = 1
42+
)
43+
44+
type InstanceType struct {
45+
Name string
46+
VCPU int
47+
}
48+
49+
type BillingInstance struct {
50+
Name string
51+
Hours float64
52+
AccountID string
53+
VCPU int
54+
}
55+
56+
func check(e error) {
57+
if e != nil {
58+
panic(e)
59+
}
60+
}
61+
62+
func countUpVMs(instances map[string][]BillingInstance) int {
63+
var totalVCPUs float64
64+
65+
for account, instance := range instances {
66+
var accountvCPUs float64
67+
for _, it := range instance {
68+
numInstances := it.Hours / hoursPerMonth
69+
vcpus := numInstances * float64(it.VCPU)
70+
accountvCPUs += vcpus
71+
totalVCPUs += vcpus
72+
}
73+
fmt.Printf("Account %s - VM vCPUs: %.2f\n", account, accountvCPUs)
74+
}
75+
76+
return int(totalVCPUs)
77+
}
78+
79+
func countUpLambdas(instances map[string]float64) int {
80+
var totalVCPUs float64
81+
82+
for account, vcpus := range instances {
83+
var accountvCPUs float64
84+
accountvCPUs += vcpus
85+
totalVCPUs += vcpus
86+
fmt.Printf("Account %s - Lambda vCPUs: %.2f\n", account, accountvCPUs)
87+
}
88+
return int(totalVCPUs)
89+
}
90+
91+
func getVMVCPU(filename string, instanceTypes []InstanceType) map[string][]BillingInstance {
92+
readFile, err := os.Open(filename)
93+
check(err)
94+
defer readFile.Close()
95+
96+
r := csv.NewReader(readFile)
97+
98+
instances := make(map[string][]BillingInstance)
99+
for {
100+
row, err := r.Read()
101+
if err == io.EOF {
102+
break
103+
}
104+
if err != nil {
105+
log.Fatal(err)
106+
}
107+
108+
for _, instanceType := range instanceTypes {
109+
if !strings.Contains(row[recordID], "AccountTotal") && row[linkedAccountID] != "" && strings.Contains(row[usageType], instanceType.Name) {
110+
hours, _ := strconv.ParseFloat(row[usageQty], 64)
111+
accountID := row[linkedAccountID]
112+
if accountID == "" {
113+
accountID = row[payerAccountID]
114+
}
115+
instances[accountID] = append(instances[accountID], BillingInstance{
116+
Name: instanceType.Name,
117+
AccountID: accountID,
118+
Hours: hours,
119+
VCPU: instanceType.VCPU,
120+
})
121+
}
122+
}
123+
}
124+
125+
return instances
126+
}
127+
128+
func getLambdaVCPU(filename string) map[string]float64 {
129+
readFile, err := os.Open(filename)
130+
check(err)
131+
defer readFile.Close()
132+
133+
r := csv.NewReader(readFile)
134+
135+
lambdas := make(map[string]float64)
136+
for {
137+
row, err := r.Read()
138+
if err == io.EOF {
139+
break
140+
}
141+
if err != nil {
142+
log.Fatal(err)
143+
}
144+
145+
if !strings.Contains(row[recordID], "AccountTotal") && row[linkedAccountID] != "" && row[productCode] == "AWSLambda" && strings.Contains(row[usageType], "Lambda-GB-Second") {
146+
accountID := row[linkedAccountID]
147+
if accountID == "" {
148+
accountID = row[payerAccountID]
149+
}
150+
seconds, _ := strconv.ParseFloat(row[usageQty], 64)
151+
152+
lambdas[accountID] += seconds / 3600 / 1024 / hoursPerMonth
153+
}
154+
}
155+
156+
return lambdas
157+
}
158+
159+
func getFargateVCPU(filename string) map[string]float64 {
160+
readFile, err := os.Open(filename)
161+
check(err)
162+
defer readFile.Close()
163+
164+
r := csv.NewReader(readFile)
165+
166+
lambdas := make(map[string]float64)
167+
for {
168+
row, err := r.Read()
169+
if err == io.EOF {
170+
break
171+
}
172+
if err != nil {
173+
log.Fatal(err)
174+
}
175+
176+
if !strings.Contains(row[recordID], "AccountTotal") && row[linkedAccountID] != "" && row[productCode] == "AmazonECS" && strings.Contains(row[usageType], "Fargate-vCPU-Hours:perCPU") {
177+
accountID := row[linkedAccountID]
178+
if accountID == "" {
179+
accountID = row[payerAccountID]
180+
}
181+
hours, _ := strconv.ParseFloat(row[usageQty], 64)
182+
fmt.Printf("Fargate %s, %.2f\n", accountID, hours)
183+
}
184+
}
185+
186+
return lambdas
187+
}
188+
189+
func loadAWSInstanceTypes() []InstanceType {
190+
var instanceTypes []InstanceType
191+
for instance, vcpu := range helpers.Aws_instances {
192+
instanceTypes = append(instanceTypes, InstanceType{
193+
Name: instance,
194+
VCPU: vcpu,
195+
})
196+
}
197+
198+
return instanceTypes
199+
}
200+
201+
func ParseBilling(cmd *cobra.Command) string {
202+
billingCSV := helpers.GetFlagEnvironmentString(cmd, "billing", "billing", "Missing Billing CSV", true)
203+
return billingCSV
204+
}

lw-billing/cmd/lwazure/azure.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package lwazure
2+
3+
import log "github.com/sirupsen/logrus"
4+
5+
func Run(debug bool) {
6+
if debug {
7+
log.SetLevel(log.DebugLevel)
8+
}
9+
10+
}

lw-billing/cmd/lwgcp/gcp.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package lwgcp
2+
3+
import log "github.com/sirupsen/logrus"
4+
5+
func Run(debug bool) {
6+
if debug {
7+
log.SetLevel(log.DebugLevel)
8+
}
9+
}

lw-billing/cmd/root.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"lw-billing/helpers"
6+
)
7+
8+
var cfgFile string
9+
10+
var rootCmd = &cobra.Command{
11+
Use: "lw-billing",
12+
Short: "",
13+
Long: "",
14+
}
15+
16+
func Execute() {
17+
if err := rootCmd.Execute(); err != nil {
18+
helpers.Bail("error starting app", err)
19+
}
20+
}

lw-billing/go.mod

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
module lw-billing
2+
3+
go 1.19
4+
5+
require (
6+
cloud.google.com/go/compute v1.18.0
7+
github.com/lacework-dev/scripts/lw-inventory v0.0.0-20230209001952-81d53cd35ba9
8+
github.com/sirupsen/logrus v1.9.0
9+
github.com/spf13/cobra v1.6.1
10+
github.com/spf13/viper v1.15.0
11+
google.golang.org/api v0.110.0
12+
google.golang.org/genproto v0.0.0-20230227214838-9b19f0bdc514
13+
)
14+
15+
require (
16+
cloud.google.com/go/compute/metadata v0.2.3 // indirect
17+
github.com/aws/aws-sdk-go-v2 v1.16.16 // indirect
18+
github.com/aws/aws-sdk-go-v2/config v1.17.1 // indirect
19+
github.com/aws/aws-sdk-go-v2/credentials v1.12.14 // indirect
20+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.12 // indirect
21+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 // indirect
22+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17 // indirect
23+
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.19 // indirect
24+
github.com/aws/aws-sdk-go-v2/service/ec2 v1.52.1 // indirect
25+
github.com/aws/aws-sdk-go-v2/service/ecs v1.18.15 // indirect
26+
github.com/aws/aws-sdk-go-v2/service/eks v1.21.8 // indirect
27+
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancing v1.14.12 // indirect
28+
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.12 // indirect
29+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.12 // indirect
30+
github.com/aws/aws-sdk-go-v2/service/rds v1.24.0 // indirect
31+
github.com/aws/aws-sdk-go-v2/service/redshift v1.26.4 // indirect
32+
github.com/aws/aws-sdk-go-v2/service/sso v1.11.17 // indirect
33+
github.com/aws/aws-sdk-go-v2/service/sts v1.16.13 // indirect
34+
github.com/aws/smithy-go v1.13.3 // indirect
35+
github.com/fsnotify/fsnotify v1.6.0 // indirect
36+
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
37+
github.com/golang/protobuf v1.5.2 // indirect
38+
github.com/google/uuid v1.3.0 // indirect
39+
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
40+
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
41+
github.com/hashicorp/hcl v1.0.0 // indirect
42+
github.com/inconshreveable/mousetrap v1.0.1 // indirect
43+
github.com/jmespath/go-jmespath v0.4.0 // indirect
44+
github.com/magiconair/properties v1.8.7 // indirect
45+
github.com/mitchellh/mapstructure v1.5.0 // indirect
46+
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
47+
github.com/spf13/afero v1.9.3 // indirect
48+
github.com/spf13/cast v1.5.0 // indirect
49+
github.com/spf13/jwalterweatherman v1.1.0 // indirect
50+
github.com/spf13/pflag v1.0.5 // indirect
51+
github.com/subosito/gotenv v1.4.2 // indirect
52+
go.opencensus.io v0.24.0 // indirect
53+
golang.org/x/net v0.7.0 // indirect
54+
golang.org/x/oauth2 v0.5.0 // indirect
55+
golang.org/x/sys v0.5.0 // indirect
56+
golang.org/x/text v0.7.0 // indirect
57+
google.golang.org/appengine v1.6.7 // indirect
58+
google.golang.org/grpc v1.53.0 // indirect
59+
google.golang.org/protobuf v1.28.1 // indirect
60+
gopkg.in/ini.v1 v1.67.0 // indirect
61+
gopkg.in/yaml.v3 v3.0.1 // indirect
62+
)

0 commit comments

Comments
 (0)