1
2 package cmd
3
4 import (
5 "context"
6 "flag"
7 "fmt"
8 "os"
9 "strconv"
10 "strings"
11
12 "github.com/peterbourgon/ff/v3/ffcli"
13
14 "edge-infra.dev/pkg/edge/monitoring/billman/costs"
15 "edge-infra.dev/pkg/edge/monitoring/billman/edgesql"
16 "edge-infra.dev/pkg/lib/cli/commands"
17 )
18
19 const (
20
21
22 loggingRate = .50
23 metricsRate = .060
24 name = "billman"
25 )
26
27
28
29 type Config struct {
30 Cluster edgesql.EdgeCluster
31 ClusterID string
32 Period string
33 TopLevelProjectID string
34 Options costs.Options
35 LoggingRate float64
36 MetricsRate float64
37 DisplayType string
38 }
39
40 var (
41 clusterID string
42 period string
43 topLevelProjectID string
44 output string
45 dbHost string
46 dbUser string
47 dbName string
48 dbPassword string
49 showHeader bool
50 showDisclaimer bool
51 )
52
53 func SQLCreds(key string, defaultValue string) string {
54 value := os.Getenv(key)
55 if value == "" {
56 return defaultValue
57 }
58 return value
59 }
60
61 var GlobalFlags = func() *flag.FlagSet {
62 fset := flag.NewFlagSet("global", flag.ExitOnError)
63 fset.StringVar(&clusterID, "clusterID", "", "(required) Edge cluster ID")
64 fset.StringVar(&period, "period", "1d", "time window for the query. e.g.: 1d,7d")
65 fset.StringVar(&topLevelProjectID, "topLevelProjectID", "", "(required) project id for the foreman cluster")
66 fset.StringVar(&output, "output", "tab", "csv or tab output")
67 fset.StringVar(&dbHost, "dbHost", SQLCreds("SQL_CONNECTION_NAME", "unknown"), "(optional) SQL DB connection name")
68 fset.StringVar(&dbUser, "dbUser", SQLCreds("SQL_USER", "unknown"), "(optional) SQL DB user")
69 fset.StringVar(&dbName, "dbName", SQLCreds("SQL_DB_NAME", "unknown"), "(optional) SQL DB name")
70 fset.StringVar(&dbPassword, "dbPassword", SQLCreds("SQL_PASSWORD", "unknown"), "(optional) SQL DB password")
71 fset.BoolVar(&showHeader, "show-header", true, "if false don't print the table header")
72 fset.BoolVar(&showDisclaimer, "show-disclaimer", true, "if false don't print the disclaimer")
73 return fset
74 }()
75
76 func WithGlobalFlags(fset *flag.FlagSet) *flag.FlagSet {
77 GlobalFlags.VisitAll(func(f *flag.Flag) {
78 fset.Var(f.Value, f.Name, f.Usage)
79 })
80 return fset
81 }
82
83 func CheckRequiredFlags(flags *flag.FlagSet) error {
84 missingFlag := false
85 flags.VisitAll(func(f *flag.Flag) {
86 if strings.TrimSpace(f.Value.String()) == "" {
87 missingFlag = true
88 }
89 })
90 if missingFlag {
91 return flag.ErrHelp
92 }
93 return nil
94 }
95
96 func New() (*ffcli.Command, *Config) {
97 cfg := Config{}
98 fs := flag.NewFlagSet(name, flag.ExitOnError)
99 return &ffcli.Command{
100 Name: name,
101 ShortUsage: name + " [subcommand] [flags]",
102 FlagSet: WithGlobalFlags(fs),
103 Subcommands: []*ffcli.Command{
104 commands.Version(),
105 },
106 Exec: cfg.Exec,
107 }, &cfg
108 }
109
110
111
112 func (c *Config) Exec(context.Context, []string) error {
113 return flag.ErrHelp
114 }
115
116
117
118 func CheckPeriod(period string) error {
119 suffix := "d"
120 if !strings.HasSuffix(period, suffix) {
121 fmt.Println("period must be in days. e.g.: 1d, 7d")
122 return flag.ErrHelp
123 }
124
125 p := strings.TrimSuffix(period, suffix)
126 days, err := strconv.Atoi(p)
127 if err != nil {
128 return flag.ErrHelp
129 }
130
131 if days <= 0 || days >= 31 {
132 fmt.Println("period must be between 1-30d")
133 return flag.ErrHelp
134 }
135
136 return nil
137 }
138
139
140
141
142 func (c *Config) AfterParse() error {
143 err := CheckRequiredFlags(GlobalFlags)
144 if err != nil {
145 return err
146 }
147
148 sqlConfig := &edgesql.SQLConfig{
149 Host: dbHost,
150 User: dbUser,
151 DbName: dbName,
152 DbPassword: dbPassword,
153 }
154
155 db, err := sqlConfig.CheckConnection()
156 if err != nil {
157 fmt.Fprintf(os.Stderr, "error getting db connection: %v\n", err)
158 }
159
160 const noData = "unknown"
161 edgeCluster, err := edgesql.ClusterByClusterID(db, clusterID)
162 if err != nil {
163 fmt.Fprintf(os.Stderr, "error getting clusters from the edge db: %v\n", err)
164 edgeCluster.BannerEdgeID = noData
165 edgeCluster.BannerName = noData
166 edgeCluster.ClusterEdgeID = clusterID
167 edgeCluster.ClusterName = noData
168 edgeCluster.ProjectID = noData
169 }
170
171 c.Cluster = edgeCluster
172 c.ClusterID = clusterID
173 c.Period = period
174 c.TopLevelProjectID = topLevelProjectID
175 c.Options = costs.Options{
176 Output: output,
177 ShowDisclaimer: showDisclaimer,
178 ShowHeader: showHeader,
179 }
180 c.LoggingRate = loggingRate
181 c.MetricsRate = metricsRate
182 return nil
183 }
184
View as plain text