cloudcost 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +29 -8
- data/lib/cloudcost/cli.rb +15 -9
- data/lib/cloudcost/pricing.rb +3 -1
- 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: f3d45a2bcff555d702572b129ad9031b4b5b62224715243d10a39185d044df5e
|
4
|
+
data.tar.gz: 14f89c7052221999e1781b7f7658137902542df9d0012ac903e7b9403fb448f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df8b5eeae07ab0bd56f0ab0616467b8e2a4dcf11b9bd2c5da4dae9cab1fffb82b05c6c4ec19c499566ccfa2af4d7763ea988ee5b8c2c9120a3d6f2bf5438e996
|
7
|
+
data.tar.gz: 92e260d8d279a4b4b7c5863565579233794e30a4a602408237252f7b627f7b1c04d0779282b8618d64c14411a52d7565e9e7963c7c07a430574c56614ea6c6d2
|
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/pricing.rb
CHANGED
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.0.2
|
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-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: excon
|