Skip to content

Commit 22242ec

Browse files
Update to v0.2 (#2)
* update go version and dependencies * bump version * get full data for threshold based policies without aggregation to avoid internal server error * Specify supported condition types in README * fix typo * update deps * update dependencies * clarify execution period in calculation comments * move link in README * update deps * update Go version and GitHub action * update deps * update actions * update go version * update default duration to 12h * update readme * update deps * add summary flag and update readme * update goreleaser * update readme
1 parent f189fec commit 22242ec

File tree

6 files changed

+126
-101
lines changed

6 files changed

+126
-101
lines changed

.github/workflows/release.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,21 @@ jobs:
99
runs-on: ubuntu-latest
1010
steps:
1111
- name: Checkout
12-
uses: actions/checkout@v4.1.7
12+
uses: actions/checkout@v4.2.2
1313
- name: Unshallow
1414
run: git fetch --prune --unshallow
1515
- name: Set up Go
16-
uses: actions/setup-go@v5.0.2
16+
uses: actions/setup-go@v5.1.0
1717
with:
18-
go-version: 1.23
18+
go-version: 1.23.3
1919
- name: Import GPG key
2020
id: import_gpg
21-
uses: crazy-max/ghaction-import-gpg@v6.1.0
21+
uses: crazy-max/ghaction-import-gpg@v6.2.0
2222
with:
2323
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
2424
passphrase: ${{ secrets.PASSPHRASE }}
2525
- name: Run GoReleaser
26-
uses: goreleaser/goreleaser-action@v6.0.0
26+
uses: goreleaser/goreleaser-action@v6.1.0
2727
with:
2828
version: latest
2929
args: release --clean

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# IMPORTANT NOTE
22

3-
Google has postponed the launch to April 2026 and has implemented a preview function of the estimated cost when editing an alerting policy. This estimate is considered much more accurate because `appe` can currently only use the total number of time series in the estimation period (30 days). In reality, this may fluctuate causing `appe` to produce an over-estimation.
4-
As of this time, Google has not made the underlying data for their calculation available via any metric or API. If this changes in the future, `appe` will be updated to produce more accurate results.
3+
Google has postponed the launch to April 2026 and has implemented a preview function of the estimated cost when editing an alerting policy. This estimate is uses a very similar calculation to `appe` but only looks at the last few minutes / hours (the exact value is not known and seems to vary slightly). This will generally result in lower estimates than `appe` but you can change the length of the window `appe` goes back using the `-d` flag (eg. `-d 5m` or `-d 4h`) to get the same or similar results. In order to more closely match the results of the preview functionality, `appe`'s default has been changed to `12h` for now. This will likely change again if or when Google updates the built-in preview functionality again.
4+
Please note that both the preview function and `appe`'s default are not 100% accurate.
55

66
# appe - Alerting Policy Price Estimator
77

@@ -36,8 +36,6 @@ We recommend that you assign the following two roles for full compatibility:
3636
## Installation
3737
The easiest way to get and install `appe` is to download one of the pre-compiled binaries from the [releases](https://github.com/doitintl/gcp-tool-appe/releases). `appe` is a self-contained binary without any dependencies and can be run from anywhere. You do not need to download any runtime and there is no need for an installer.
3838

39-
See also https://cloud.google.com/docs/authentication/provide-credentials-adc#local-dev.
40-
4139
### macOS
4240
Since the binary is not signed with an Apple Developer Certificate, your Mac will likely report it as untrustworthy.
4341
There are two ways to deal with this:
@@ -62,6 +60,8 @@ If you are running `appe` locally, the easiest way to set up ADC is to use [gclo
6260
gcloud auth application-default login
6361
```
6462

63+
See also https://cloud.google.com/docs/authentication/provide-credentials-adc#local-dev.
64+
6565
## Usage
6666
Using `appe` is fairly straightforward
6767

@@ -107,10 +107,16 @@ You can also specify multiple organizations:
107107
```
108108
Note that you will need to specify the `--recursive` or `-r` flag to also scan subfolders.
109109

110+
### Supported Condition Types
111+
The following condition types are supported by `appe`:
112+
- Monitoring Query Language (MQL) via [projects.timeSeries.query](https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.timeSeries/query)
113+
- Threshold and Absence via [projects.timeSeries.list](https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.timeSeries/list)
114+
- Prometheus Query Language (PromQL / PQL) via [projects.location.prometheus.api.v1.query_range](https://cloud.google.com/monitoring/api/ref_v3/rest/v1/projects.location.prometheus.api.v1/query_range)
115+
110116
### All Flags
111117
```
112118
-c, --csvOut string Path to a CSV file to redirect output to. If this is not set, human-readable output will be given on stdout.
113-
-d, --duration duration The delta from now to go back in time for query. Default is 30 days. (default 720h0m0s)
119+
-d, --duration duration The delta from now to go back in time for query. Default is 12 hours. (default 12h0m0s)
114120
-e, --excludeFolder strings One or more folders to exclude. Separated by ",".
115121
-f, --folder strings One or more folders to scan. Use the "-r" flag to scan recursively. Separated by ",".
116122
-h, --help help for appe
@@ -120,6 +126,7 @@ Note that you will need to specify the `--recursive` or `-r` flag to also scan s
120126
-p, --project strings One or more projects to scan. Separated by ",".
121127
-q, --quotaProject string A quota or billing project. Useful if you don't have the serviceusage.services.use permission in the target project.
122128
-r, --recursive If parent should be scanned recursively. If this is not set, only projects at the root of the folder or organization will be scanned. (default false)
129+
-s, --summary Whether the output should just be a summary (sum of all scanned policies) (default false)
123130
-t, --testPermissions If the application should verify that the user has the necessary permissions before processing a project. (default false)
124131
--threads int Number of threads to use to process folders, projects and policies in parallel. (default 4)
125132
-v, --version version for appe

cmd/monitoring.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func processAlertPolicy(
158158
policyOut.Error = err.Error()
159159
break
160160
}
161-
// 60 (seconds) * 60 (minutes) * 24 (hours) * 30 (days) / 30 (step) * 0.35 (price) / 1000000 (per 1M) = 0.03024 (price per time series)
161+
// 60 (seconds) * 60 (minutes) * 24 (hours) * 30 (days) / 30 (execution period) * 0.35 (price) / 1000000 (per 1M) = 0.03024 (price per time series)
162162
policyOut.Price += 0.03024
163163
policyOut.TimeSeries++
164164
}
@@ -188,7 +188,7 @@ func processAlertPolicy(
188188
}
189189
// 60 (seconds) * 60 (minutes) * 24 (hours) * 30 (days) = 2592000
190190
// 2592000 * 0.35 (price) / 1000000 (per 1M) =
191-
// 0.9072 / step * time series = price for all time series with this condition
191+
// 0.9072 / execution period * time series = price for all time series with this condition
192192
policyOut.Price += 0.9072 / float64(seconds) * float64(len(pqlResp.Data.Result))
193193
policyOut.TimeSeries += len(pqlResp.Data.Result)
194194
}
@@ -216,7 +216,7 @@ func processAlertPolicy(
216216
if len(aggregations) > 1 {
217217
tsReq.SecondaryAggregation = aggregations[1]
218218
}
219-
if tsReq.Aggregation.GetCrossSeriesReducer().String() == "REDUCE_COUNT_FALSE" || tsReq.SecondaryAggregation.GetCrossSeriesReducer().String() == "REDUCE_COUNT_FALSE" {
219+
if tsReq.Aggregation == nil || tsReq.Aggregation.GetCrossSeriesReducer().String() == "REDUCE_COUNT_FALSE" || tsReq.SecondaryAggregation.GetCrossSeriesReducer().String() == "REDUCE_COUNT_FALSE" {
220220
tsReq.View = monitoringpb.ListTimeSeriesRequest_FULL
221221
}
222222
tsIt := metricClient.ListTimeSeries(ctx, tsReq)
@@ -229,7 +229,7 @@ func processAlertPolicy(
229229
policyOut.Error = err.Error()
230230
break
231231
}
232-
// 60 (seconds) * 60 (minutes) * 24 (hours) * 30 (days) / 30 (step) * 0.35 (price) / 1000000 (per 1M) = 0.03024 (price per time series)
232+
// 60 (seconds) * 60 (minutes) * 24 (hours) * 30 (days) / 30 (execution period) * 0.35 (price) / 1000000 (per 1M) = 0.03024 (price per time series)
233233
policyOut.Price += 0.03024
234234
policyOut.TimeSeries++
235235
}

cmd/root.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222

2323
// rootCmd represents the base command when called without any subcommands
2424
var rootCmd = &cobra.Command{
25-
Version: "0.1",
25+
Version: "0.2",
2626
Use: "appe",
2727
Short: "Alerting Policy Price Estimator",
2828
Long: `Scans for alerting policies in the specified projects, folder or orgs and approximates their cost by executing the queries defined in them against the monitoring API`,
@@ -90,6 +90,10 @@ func Execute() {
9090
if err != nil {
9191
log.Fatalln(err)
9292
}
93+
summary, err := rootCmd.Flags().GetBool("summary")
94+
if err != nil {
95+
log.Fatalln(err)
96+
}
9397
quotaProject, err := rootCmd.Flags().GetString("quotaProject")
9498
if err != nil {
9599
log.Fatalln(err)
@@ -182,7 +186,7 @@ func Execute() {
182186

183187
// If one or more individual policies should be analyzed, we need to first get them from the API.
184188
// We then put them directly on the policiesIn channel, which will be processes by threads that are spawned below.
185-
// Finally, we will close teh projectsIn channel once done, because the policiesIn channel will be closed automatically.
189+
// Finally, we will close the projectsIn channel once done, because the policiesIn channel will be closed automatically.
186190
if lenPol > 0 {
187191
if lenPol > int(threads) {
188192
threads = int64(lenPol)
@@ -274,6 +278,18 @@ func Execute() {
274278
csvWriter.Flush()
275279
}
276280
// Otherwise, the application will just output to stdout
281+
} else if summary {
282+
policiesSum := 0
283+
conditionsSum := 0
284+
timeSeriesSum := 0
285+
priceSum := 0.0
286+
for policy := range policiesOut {
287+
policiesSum++
288+
conditionsSum += policy.Conditions
289+
timeSeriesSum += policy.TimeSeries
290+
priceSum += policy.Price
291+
}
292+
log.Printf("Summary: You have %d policies with a combined total of %d conditions and %d time series. It will cost approximately $%f\n", policiesSum, conditionsSum, timeSeriesSum, priceSum)
277293
} else {
278294
for policy := range policiesOut {
279295
log.Printf("Alerting Policy %s (%s) has %d condition(s) and %d time series. It will cost approximately $%f\n", policy.DisplayName, policy.Name, policy.Conditions, policy.TimeSeries, policy.Price)
@@ -291,13 +307,15 @@ func init() {
291307
rootCmd.Flags().StringSliceP("excludeFolder", "e", nil, "One or more folders to exclude. Separated by \",\".")
292308
rootCmd.Flags().BoolP("testPermissions", "t", false, "If the application should verify that the user has the necessary permissions before processing a project. (default false)")
293309
rootCmd.Flags().BoolP("includeDisabled", "i", false, "If the application should also include disabled policies. (default false)")
310+
rootCmd.Flags().BoolP("summary", "s", false, "Whether the output should just be a summary (sum of all scanned policies) (default false)")
294311
rootCmd.Flags().BoolP("recursive", "r", false, "If parent should be scanned recursively. If this is not set, only projects at the root of the folder or organization will be scanned. (default false)")
295312
rootCmd.Flags().Int64("threads", 4, "Number of threads to use to process folders, projects and policies in parallel.")
296-
rootCmd.Flags().DurationP("duration", "d", 24*time.Hour*30, "The delta from now to go back in time for query. Default is 30 days.")
313+
rootCmd.Flags().DurationP("duration", "d", 12*time.Hour, "The delta from now to go back in time for query. Default is 12 hours.")
297314
rootCmd.MarkFlagsOneRequired("policy", "project", "folder", "organization")
298315
rootCmd.MarkFlagsMutuallyExclusive("policy", "project", "recursive")
299316
rootCmd.MarkFlagsMutuallyExclusive("policy", "testPermissions")
300317
rootCmd.MarkFlagsMutuallyExclusive("policy", "includeDisabled")
301318
rootCmd.MarkFlagsMutuallyExclusive("policy", "project", "excludeFolder")
302319
rootCmd.MarkFlagsMutuallyExclusive("policy", "project", "folder", "organization")
320+
rootCmd.MarkFlagsMutuallyExclusive("csvOut", "summary")
303321
}

go.mod

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,47 @@ module github.com/doitintl/gcp-tool-appe
33
go 1.23
44

55
require (
6-
cloud.google.com/go/iam v1.2.0
7-
cloud.google.com/go/monitoring v1.21.0
8-
cloud.google.com/go/resourcemanager v1.10.0
6+
cloud.google.com/go/iam v1.2.2
7+
cloud.google.com/go/monitoring v1.21.2
8+
cloud.google.com/go/resourcemanager v1.10.2
99
github.com/spf13/cobra v1.8.1
10-
google.golang.org/api v0.194.0
11-
google.golang.org/protobuf v1.34.2
10+
google.golang.org/api v0.209.0
11+
google.golang.org/protobuf v1.35.2
1212
)
1313

1414
require (
15-
cloud.google.com/go v0.115.1 // indirect
16-
cloud.google.com/go/auth v0.9.1 // indirect
17-
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
18-
cloud.google.com/go/compute/metadata v0.5.0 // indirect
19-
cloud.google.com/go/longrunning v0.6.0 // indirect
15+
cloud.google.com/go v0.116.0 // indirect
16+
cloud.google.com/go/auth v0.11.0 // indirect
17+
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
18+
cloud.google.com/go/compute/metadata v0.5.2 // indirect
19+
cloud.google.com/go/longrunning v0.6.3 // indirect
2020
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
2121
github.com/felixge/httpsnoop v1.0.4 // indirect
2222
github.com/go-logr/logr v1.4.2 // indirect
2323
github.com/go-logr/stdr v1.2.2 // indirect
2424
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
2525
github.com/google/s2a-go v0.1.8 // indirect
2626
github.com/google/uuid v1.6.0 // indirect
27-
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
28-
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
27+
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
28+
github.com/googleapis/gax-go/v2 v2.14.0 // indirect
2929
github.com/inconshreveable/mousetrap v1.1.0 // indirect
3030
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
3131
github.com/spf13/pflag v1.0.5 // indirect
3232
go.opencensus.io v0.24.0 // indirect
33-
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect
34-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
35-
go.opentelemetry.io/otel v1.28.0 // indirect
36-
go.opentelemetry.io/otel/metric v1.28.0 // indirect
37-
go.opentelemetry.io/otel/trace v1.28.0 // indirect
38-
golang.org/x/crypto v0.26.0 // indirect
39-
golang.org/x/net v0.28.0 // indirect
40-
golang.org/x/oauth2 v0.22.0 // indirect
41-
golang.org/x/sync v0.8.0 // indirect
42-
golang.org/x/sys v0.24.0 // indirect
43-
golang.org/x/text v0.17.0 // indirect
44-
golang.org/x/time v0.6.0 // indirect
45-
google.golang.org/genproto v0.0.0-20240822170219-fc7c04adadcd // indirect
46-
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect
47-
google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect
48-
google.golang.org/grpc v1.65.0 // indirect
33+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.57.0 // indirect
34+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 // indirect
35+
go.opentelemetry.io/otel v1.32.0 // indirect
36+
go.opentelemetry.io/otel/metric v1.32.0 // indirect
37+
go.opentelemetry.io/otel/trace v1.32.0 // indirect
38+
golang.org/x/crypto v0.29.0 // indirect
39+
golang.org/x/net v0.31.0 // indirect
40+
golang.org/x/oauth2 v0.24.0 // indirect
41+
golang.org/x/sync v0.9.0 // indirect
42+
golang.org/x/sys v0.27.0 // indirect
43+
golang.org/x/text v0.20.0 // indirect
44+
golang.org/x/time v0.8.0 // indirect
45+
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
46+
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect
47+
google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect
48+
google.golang.org/grpc v1.68.0 // indirect
4949
)

0 commit comments

Comments
 (0)