cloudcost 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b5926bdf2873133e1d8b85b04c56210a6ee1a886a233693527462d5747d43ee
4
- data.tar.gz: d9ac68dc18b82a7925134099d99bbf8abfa1f9cee540a4c2cc23932ed1c6ebab
3
+ metadata.gz: f3d45a2bcff555d702572b129ad9031b4b5b62224715243d10a39185d044df5e
4
+ data.tar.gz: 14f89c7052221999e1781b7f7658137902542df9d0012ac903e7b9403fb448f6
5
5
  SHA512:
6
- metadata.gz: f466284d61657746d4e31f56e8a3c4f96bcf930460bcfb9f87a578c13c5edbd56ef302f3dd31608b0af99e580205416c8885e0c85edec1caf9de6abf6d682fba
7
- data.tar.gz: f9941021d5ecc30c31b5453e72074eefc57c12c9570b9e3489ac45450d896d79136f343bc22235a1c6a3f1776ce2cd8d440436b5f69e4f6d2218324522222bd5
6
+ metadata.gz: df8b5eeae07ab0bd56f0ab0616467b8e2a4dcf11b9bd2c5da4dae9cab1fffb82b05c6c4ec19c499566ccfa2af4d7763ea988ee5b8c2c9120a3d6f2bf5438e996
7
+ data.tar.gz: 92e260d8d279a4b4b7c5863565579233794e30a4a602408237252f7b627f7b1c04d0779282b8618d64c14411a52d7565e9e7963c7c07a430574c56614ea6c6d2
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
2
  vendor
3
3
  pkg
4
+ data
4
5
  .bundle
5
6
  cloudscale.ini
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
- ## Setup
10
+ ## Installation
11
11
 
12
- Ruby is required, install dependencies using bundler:
12
+ Install the gem:
13
13
 
14
14
  ```sh
15
- bundle install
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
- #### Output CSV
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 pitc_service
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 pitc_service=ocp4
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 :output, default: "table", enum: %w[table csv], desc: "output format", aliases: %w[-o]
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
- spinner = TTY::Spinner.new("[:spinner] Calculating costs...", clear: options[:csv])
37
- spinner.auto_spin
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 "(done)"
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
- spinner = TTY::Spinner.new("[:spinner] Loading servers...", clear: options[:csv])
112
- spinner.auto_spin
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[:output] == "csv"
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
@@ -2,7 +2,9 @@
2
2
 
3
3
  require "yaml"
4
4
 
5
- PRICING = YAML.load_file("data/pricing.yml")
5
+ PRICING = YAML.load_file( File.join(
6
+ File.expand_path("../..", __dir__), "data/pricing.yml")
7
+ )
6
8
 
7
9
  module Cloudcost
8
10
  class PricingError < StandardError
@@ -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(@data[:flavor][:slug])
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
- @data[:volumes].group_by { |volume| volume[:type].itself }.each do |group, vols|
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
- @servers.each do |server|
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] ? [""] : %w[Name UUID Flavor Tags]
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
- unless @options[:summary]
30
- @servers.sort_by(&:name).map do |server|
31
- rows << [
32
- server.name,
33
- server.uuid,
34
- server.flavor,
35
- server.tags_to_s,
36
- server.vcpu_count,
37
- server.memory_gb,
38
- server.storage_size(:ssd),
39
- server.storage_size(:bulk),
40
- format("%.2f", server.total_costs_per_day.round(2)),
41
- format("%.2f", (server.total_costs_per_day * 30).round(2))
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cloudcost
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.2"
5
5
  end
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.1
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-07 00:00:00.000000000 Z
11
+ date: 2021-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: excon