cloudcost 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +29 -8
- data/lib/cloudcost/cli.rb +15 -9
- data/lib/cloudcost/server.rb +8 -2
- data/lib/cloudcost/server_list.rb +77 -20
- data/lib/cloudcost/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96a9e629c34c1cfb03aae4edbf523429e3cde9d2da296fbb836504d49eaf56e6
|
4
|
+
data.tar.gz: 0124a911411de640441fe5153221cec39c1910cb569734aed5f7713309ab6906
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d0702e48c0f676663f8207428d60e620be0c361ae06a40d5b890b09255b6cdad188f24fe9b6068a5114ccc9d4223f75123906395212edf60799e2b39eb5db28
|
7
|
+
data.tar.gz: cc6493969e142c28033874f470670df1e521a2e33f33b88f065d1a5ae3850469914588d71d5f6a872868d282a019b6ec43bcd331f88d2c140dd872a7df3c3ed5
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -7,12 +7,12 @@ Resources are fetched from the [API](https://www.cloudscale.ch/en/api/v1) and co
|
|
7
7
|
Please note that costs are always calculated based on the current usage.
|
8
8
|
Your actual bills are based on the effective usage over time and may include additional service fees, i.e. for data transfer or discounts.
|
9
9
|
|
10
|
-
##
|
10
|
+
## Installation
|
11
11
|
|
12
|
-
|
12
|
+
Install the gem:
|
13
13
|
|
14
14
|
```sh
|
15
|
-
|
15
|
+
gem install cloudcost
|
16
16
|
```
|
17
17
|
|
18
18
|
## Configure API-Auth
|
@@ -30,8 +30,6 @@ export CLOUDSCALE_API_TOKEN=HELPIMTRAPPEDINATOKENGENERATOR
|
|
30
30
|
|
31
31
|
or you can directly pass a token as a argument to the command: `--api-token HELPIMTRAPPEDINATOKENGENERATOR`
|
32
32
|
|
33
|
-
**NOTE:** The API_TOKEN does only require read access.
|
34
|
-
|
35
33
|
## Usage
|
36
34
|
|
37
35
|
### Help
|
@@ -66,7 +64,30 @@ Only show summarized usage:
|
|
66
64
|
cloudcost servers --summary
|
67
65
|
```
|
68
66
|
|
69
|
-
####
|
67
|
+
#### Group and summarize by tag
|
68
|
+
|
69
|
+
By using the `--tag-by` option, you can summarize usage by tag:
|
70
|
+
|
71
|
+
```sh
|
72
|
+
cloudcost servers --group-by budget-group
|
73
|
+
```
|
74
|
+
|
75
|
+
#### Influx Line Protocol output
|
76
|
+
|
77
|
+
The CLI also supports output in Influxdb Line Protocol format when using the `--group-by` option:
|
78
|
+
|
79
|
+
```sh
|
80
|
+
cloudcost servers --group-by budget-group --output influx
|
81
|
+
```
|
82
|
+
|
83
|
+
The output can directly ba pipped to the influxdb CLI:
|
84
|
+
|
85
|
+
```sh
|
86
|
+
cloudcost servers --group-by budget-group --profile prod | \
|
87
|
+
influx write --bucket my-bucket --org my-org --token my-super-secret-auth-token
|
88
|
+
```
|
89
|
+
|
90
|
+
#### CSV Output
|
70
91
|
|
71
92
|
Output in CSV format instead of a table:
|
72
93
|
|
@@ -91,13 +112,13 @@ cloudcost servers --name "^[^ocp|^k8s|^rancher].*"
|
|
91
112
|
Filter servers by tag key:
|
92
113
|
|
93
114
|
```sh
|
94
|
-
cloudcost servers --tag
|
115
|
+
cloudcost servers --tag service
|
95
116
|
```
|
96
117
|
|
97
118
|
Filter servers by tag value:
|
98
119
|
|
99
120
|
```sh
|
100
|
-
cloudcost servers --tag
|
121
|
+
cloudcost servers --tag service=ocp4
|
101
122
|
```
|
102
123
|
|
103
124
|
### Server Tags
|
data/lib/cloudcost/cli.rb
CHANGED
@@ -30,14 +30,16 @@ module Cloudcost
|
|
30
30
|
option :name, desc: "filter name by regex", aliases: %w[-n]
|
31
31
|
option :tag, desc: "filter servers by tag", aliases: %w[-t]
|
32
32
|
option :summary, desc: "display totals only", type: :boolean, aliases: %w[-S]
|
33
|
-
option :
|
33
|
+
option :group_by, desc: "group by tag", aliases: %w[-G]
|
34
|
+
option :output, default: "table", enum: %w[table csv influx], desc: "output format", aliases: %w[-o]
|
34
35
|
def servers
|
35
36
|
servers = load_servers(options)
|
36
|
-
|
37
|
-
|
37
|
+
if options[:output] == "table"
|
38
|
+
spinner = TTY::Spinner.new("[:spinner] Calculating costs...", clear: options[:csv])
|
39
|
+
spinner.auto_spin
|
40
|
+
end
|
38
41
|
output(servers, options) do |result|
|
39
|
-
spinner.success
|
40
|
-
puts
|
42
|
+
spinner.success("(done)") if spinner
|
41
43
|
puts result
|
42
44
|
end
|
43
45
|
rescue Excon::Error, TokenError, ProfileError, PricingError => e
|
@@ -108,15 +110,19 @@ module Cloudcost
|
|
108
110
|
end
|
109
111
|
|
110
112
|
def load_servers(options)
|
111
|
-
|
112
|
-
|
113
|
+
if options[:output] == "table"
|
114
|
+
spinner = TTY::Spinner.new("[:spinner] Loading servers...", clear: options[:csv])
|
115
|
+
spinner.auto_spin
|
116
|
+
end
|
113
117
|
servers = api_connection(options).get_servers(options).map { |server| Server.new(server) }
|
114
|
-
spinner.success "(#{servers.size} found)"
|
118
|
+
spinner.success "(#{servers.size} found)" if spinner
|
115
119
|
servers
|
116
120
|
end
|
117
121
|
|
118
122
|
def output(servers, options)
|
119
|
-
if options[:
|
123
|
+
if options[:group_by]
|
124
|
+
yield Cloudcost::ServerList.new(servers, options).grouped_cost_table
|
125
|
+
elsif options[:output] == "csv"
|
120
126
|
yield Cloudcost::ServerList.new(servers, options).to_csv
|
121
127
|
else
|
122
128
|
yield Cloudcost::ServerList.new(servers, options).cost_table
|
data/lib/cloudcost/server.rb
CHANGED
@@ -8,6 +8,8 @@ module Cloudcost
|
|
8
8
|
end
|
9
9
|
|
10
10
|
class Server
|
11
|
+
attr_accessor :data
|
12
|
+
|
11
13
|
def initialize(data)
|
12
14
|
@data = data
|
13
15
|
@total_storage_per_type = sum_up_storage_per_type
|
@@ -33,6 +35,10 @@ module Cloudcost
|
|
33
35
|
@data[:flavor][:memory_gb]
|
34
36
|
end
|
35
37
|
|
38
|
+
def volumes
|
39
|
+
@data[:volumes]
|
40
|
+
end
|
41
|
+
|
36
42
|
def tags
|
37
43
|
@data[:tags]
|
38
44
|
end
|
@@ -46,7 +52,7 @@ module Cloudcost
|
|
46
52
|
end
|
47
53
|
|
48
54
|
def server_costs_per_day
|
49
|
-
Pricing.server_costs_per_day(
|
55
|
+
Pricing.server_costs_per_day(flavor)
|
50
56
|
end
|
51
57
|
|
52
58
|
def storage_costs_per_day(type = :ssd)
|
@@ -59,7 +65,7 @@ module Cloudcost
|
|
59
65
|
|
60
66
|
def sum_up_storage_per_type
|
61
67
|
sum = {}
|
62
|
-
|
68
|
+
volumes.group_by { |volume| volume[:type].itself }.each do |group, vols|
|
63
69
|
sum.store(group.to_sym, 0)
|
64
70
|
vols.each { |volume| sum[volume[:type].to_sym] += volume[:size_gb] }
|
65
71
|
end
|
@@ -7,9 +7,9 @@ module Cloudcost
|
|
7
7
|
@options = options
|
8
8
|
end
|
9
9
|
|
10
|
-
def calculate_totals
|
10
|
+
def calculate_totals(servers = @servers)
|
11
11
|
totals = { vcpu: 0, memory: 0, ssd: 0, bulk: 0, cost: 0.0 }
|
12
|
-
|
12
|
+
servers.each do |server|
|
13
13
|
totals[:vcpu] += server.vcpu_count
|
14
14
|
totals[:memory] += server.memory_gb
|
15
15
|
totals[:ssd] += server.storage_size(:ssd)
|
@@ -20,33 +20,37 @@ module Cloudcost
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def headings
|
23
|
-
headings = @options[:summary]
|
23
|
+
headings = if @options[:summary]
|
24
|
+
[""]
|
25
|
+
elsif @options[:group_by]
|
26
|
+
["Group", "Servers"]
|
27
|
+
else
|
28
|
+
%w[Name UUID Flavor Tags]
|
29
|
+
end
|
24
30
|
headings.concat ["vCPU's", "Memory [GB]", "SSD [GB]", "Bulk [GB]", "CHF/day", "CHF/30-days"]
|
25
31
|
end
|
26
32
|
|
27
33
|
def rows
|
28
34
|
rows = []
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
]
|
43
|
-
end
|
35
|
+
@servers.sort_by(&:name).map do |server|
|
36
|
+
rows << [
|
37
|
+
server.name,
|
38
|
+
server.uuid,
|
39
|
+
server.flavor,
|
40
|
+
server.tags_to_s,
|
41
|
+
server.vcpu_count,
|
42
|
+
server.memory_gb,
|
43
|
+
server.storage_size(:ssd),
|
44
|
+
server.storage_size(:bulk),
|
45
|
+
format("%.2f", server.total_costs_per_day.round(2)),
|
46
|
+
format("%.2f", (server.total_costs_per_day * 30).round(2))
|
47
|
+
]
|
44
48
|
end
|
45
49
|
rows
|
46
50
|
end
|
47
51
|
|
48
|
-
def totals
|
49
|
-
totals = calculate_totals
|
52
|
+
def totals(servers = @servers)
|
53
|
+
totals = calculate_totals(servers)
|
50
54
|
total_row = @options[:summary] ? %w[Total] : ["Total", "", "", ""]
|
51
55
|
total_row.concat [
|
52
56
|
totals[:vcpu],
|
@@ -88,6 +92,58 @@ module Cloudcost
|
|
88
92
|
table
|
89
93
|
end
|
90
94
|
|
95
|
+
def grouped_cost_table
|
96
|
+
no_tag = "<no-tag>"
|
97
|
+
group_rows = @servers.group_by {|s| s.tags[@options[:group_by].to_sym] || no_tag }.map do |name, servers|
|
98
|
+
server_groups_data(name, servers).values.flatten
|
99
|
+
end.sort {|a, b| a[0] == no_tag ? 1 : a[0] <=> b[0] }
|
100
|
+
if @options[:output] == "csv"
|
101
|
+
CSV.generate do |csv|
|
102
|
+
csv << headings
|
103
|
+
group_rows.each { |row| csv << row }
|
104
|
+
end
|
105
|
+
elsif @options[:output] == "influx"
|
106
|
+
lines = []
|
107
|
+
group_rows.each do |row|
|
108
|
+
[
|
109
|
+
{ field: "server_count", position: 1, unit: "i" },
|
110
|
+
{ field: "vcpus", position: 2, unit: "i" },
|
111
|
+
{ field: "memory_gb", position: 3, unit: "i" },
|
112
|
+
{ field: "ssd_gb", position: 4, unit: "i" },
|
113
|
+
{ field: "bulk_gb", position: 5, unit: "i" },
|
114
|
+
{ field: "cost_per_day", position: 6, unit: "" },
|
115
|
+
].each do |field|
|
116
|
+
lines << "cloudscaleServerCosts,grouped_by=#{@options[:group_by]},group=#{row[0]},environment=#{@options[:profile] || "default"},currency=CHF #{field[:field]}=#{row[field[:position]]}#{field[:unit]}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
lines.join("\n")
|
120
|
+
else
|
121
|
+
table = Terminal::Table.new do |t|
|
122
|
+
t.title = "cloudscale.ch server costs grouped by tag \"#{@options[:group_by]}\""
|
123
|
+
t.title += " (#{@options[:profile]})" if @options[:profile]
|
124
|
+
t.headings = headings
|
125
|
+
end
|
126
|
+
table.rows = group_rows
|
127
|
+
(1..table.columns.size).each { |column| table.align_column(column, :right) }
|
128
|
+
table
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def server_groups_data(name, servers)
|
133
|
+
data = { name: name, count: 0, vcpu: 0, memory: 0, ssd: 0, bulk: 0, costs_daily: 0 }
|
134
|
+
servers.each do |server|
|
135
|
+
data[:count] += 1
|
136
|
+
data[:vcpu] += server.vcpu_count
|
137
|
+
data[:memory] += server.memory_gb
|
138
|
+
data[:ssd] += server.storage_size(:ssd)
|
139
|
+
data[:bulk] += server.storage_size(:bulk)
|
140
|
+
data[:costs_daily] += server.total_costs_per_day
|
141
|
+
end
|
142
|
+
data[:costs_monthly] = (data[:costs_daily] * 30).round(2)
|
143
|
+
data[:costs_daily] = data[:costs_daily].round(2)
|
144
|
+
data
|
145
|
+
end
|
146
|
+
|
91
147
|
def to_csv
|
92
148
|
CSV.generate do |csv|
|
93
149
|
csv << headings
|
@@ -98,5 +154,6 @@ module Cloudcost
|
|
98
154
|
end
|
99
155
|
end
|
100
156
|
end
|
157
|
+
|
101
158
|
end
|
102
159
|
end
|
data/lib/cloudcost/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cloudcost
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nik Wolfgramm
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-09-
|
11
|
+
date: 2021-09-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: excon
|