rhea 0.0.1 → 0.1.0

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.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +165 -2
  3. data/app/assets/javascripts/rhea/application.js +2 -0
  4. data/app/assets/javascripts/rhea/main.js +3 -0
  5. data/app/assets/javascripts/vendor/jquery-1.11.3.js +10351 -0
  6. data/app/assets/stylesheets/rhea/application.css.sass +1 -0
  7. data/app/assets/stylesheets/rhea/layout.css.sass +31 -0
  8. data/app/controllers/rhea/base_controller.rb +26 -0
  9. data/app/controllers/rhea/commands_controller.rb +168 -0
  10. data/app/controllers/rhea/events_controller.rb +8 -0
  11. data/app/controllers/rhea/nodes_controller.rb +7 -0
  12. data/app/controllers/rhea/system_services_controller.rb +7 -0
  13. data/app/helpers/rhea/helper.rb +42 -0
  14. data/app/views/rhea/command_types/_list.html.haml +5 -0
  15. data/app/views/rhea/commands/_form.html.haml +22 -0
  16. data/app/views/rhea/commands/_table.html.haml +51 -0
  17. data/app/views/rhea/commands/index.html.haml +51 -0
  18. data/app/views/rhea/errors/index.html.haml +2 -0
  19. data/app/views/rhea/events/index.html.haml +24 -0
  20. data/app/views/rhea/layouts/application.html.haml +35 -0
  21. data/app/views/rhea/nodes/index.html.haml +29 -0
  22. data/app/views/rhea/system_services/index.html.haml +2 -0
  23. data/config/routes.rb +21 -0
  24. data/docs/commands.gif +0 -0
  25. data/docs/commands.mov +0 -0
  26. data/docs/events.png +0 -0
  27. data/docs/logo.png +0 -0
  28. data/docs/nodes.png +0 -0
  29. data/fixtures/vcr_cassettes/Rhea_Kubernetes_Commands_All/_perform/an_existing_rc/returns_the_rc.yml +197 -0
  30. data/fixtures/vcr_cassettes/Rhea_Kubernetes_Commands_Delete/_perform/an_existing_rc/deletes_the_rc.yml +163 -0
  31. data/fixtures/vcr_cassettes/Rhea_Kubernetes_Commands_Export/_perform/an_existing_rc/returns_the_data.yml +197 -0
  32. data/fixtures/vcr_cassettes/Rhea_Kubernetes_Commands_Get/_perform/an_existing_rc/gets_the_rc.yml +196 -0
  33. data/fixtures/vcr_cassettes/Rhea_Kubernetes_Commands_Import/_perform/no_existing_rcs/creates_the_rcs.yml +197 -0
  34. data/fixtures/vcr_cassettes/Rhea_Kubernetes_Commands_Redeploy/_perform/an_existing_rc/redeploys_the_rc.yml +424 -0
  35. data/fixtures/vcr_cassettes/Rhea_Kubernetes_Commands_Reschedule/_perform/an_existing_rc/reschedules_the_rc.yml +421 -0
  36. data/fixtures/vcr_cassettes/Rhea_Kubernetes_Commands_Scale/_perform/no_existing_rc/creates_an_rc.yml +165 -0
  37. data/lib/rhea.rb +15 -1
  38. data/lib/rhea/command.rb +35 -0
  39. data/lib/rhea/command_type.rb +35 -0
  40. data/lib/rhea/engine.rb +16 -0
  41. data/lib/rhea/kubernetes.rb +5 -0
  42. data/lib/rhea/kubernetes/api.rb +15 -0
  43. data/lib/rhea/kubernetes/commands/all.rb +16 -0
  44. data/lib/rhea/kubernetes/commands/base.rb +28 -0
  45. data/lib/rhea/kubernetes/commands/delete.rb +18 -0
  46. data/lib/rhea/kubernetes/commands/export.rb +23 -0
  47. data/lib/rhea/kubernetes/commands/get.rb +18 -0
  48. data/lib/rhea/kubernetes/commands/import.rb +20 -0
  49. data/lib/rhea/kubernetes/commands/redeploy.rb +22 -0
  50. data/lib/rhea/kubernetes/commands/reschedule.rb +21 -0
  51. data/lib/rhea/kubernetes/commands/scale.rb +108 -0
  52. data/lib/rhea/kubernetes/configuration.rb +30 -0
  53. data/lib/rhea/kubernetes/events/recent.rb +23 -0
  54. data/lib/rhea/kubernetes/nodes/all.rb +45 -0
  55. data/lib/rhea/kubernetes/system_services.rb +22 -0
  56. data/lib/rhea/version.rb +1 -1
  57. data/rhea.gemspec +11 -3
  58. data/spec/lib/rhea/kubernetes/commands/all_spec.rb +29 -0
  59. data/spec/lib/rhea/kubernetes/commands/delete_spec.rb +22 -0
  60. data/spec/lib/rhea/kubernetes/commands/export_spec.rb +31 -0
  61. data/spec/lib/rhea/kubernetes/commands/get_spec.rb +28 -0
  62. data/spec/lib/rhea/kubernetes/commands/import_spec.rb +39 -0
  63. data/spec/lib/rhea/kubernetes/commands/redeploy_spec.rb +35 -0
  64. data/spec/lib/rhea/kubernetes/commands/reschedule_spec.rb +32 -0
  65. data/spec/lib/rhea/kubernetes/commands/scale_spec.rb +31 -0
  66. data/spec/spec_helper.rb +2 -0
  67. data/spec/support/kubernetes_spec_helper.rb +43 -0
  68. data/spec/support/vcr.rb +9 -0
  69. metadata +170 -11
@@ -0,0 +1,21 @@
1
+ module Rhea
2
+ module Kubernetes
3
+ module Commands
4
+ class Reschedule < Base
5
+ attr_accessor :command
6
+
7
+ def initialize(command_attributes)
8
+ self.command = Command.new(command_attributes)
9
+ end
10
+
11
+ def perform
12
+ command_attributes = command.attributes.slice(:image, :expression)
13
+ controller = Get.new(command_attributes).perform
14
+ process_count = controller.process_count
15
+ Scale.new(command_attributes.merge(process_count: 0)).perform
16
+ Scale.new(command_attributes.merge(process_count: process_count)).perform
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,108 @@
1
+ module Rhea
2
+ module Kubernetes
3
+ module Commands
4
+ class Scale < Base
5
+ attr_accessor :command
6
+
7
+ def initialize(command_attributes)
8
+ self.command = Command.new(command_attributes)
9
+ end
10
+
11
+ def perform
12
+ if is_replication_controller_running?
13
+ scale_replication_controller
14
+ else
15
+ start_replication_controller
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def is_replication_controller_running?
22
+ api.get_replication_controllers(label_selector: "name=#{command.key}").length > 0
23
+ end
24
+
25
+ def start_replication_controller
26
+ parsed_command_expression = parse_command_expression
27
+ raw_command_expression = parsed_command_expression[:raw_command_expression]
28
+ env_vars = parsed_command_expression[:env_vars]
29
+ formatted_env_vars = format_env_vars(env_vars)
30
+
31
+ container_config = {
32
+ 'name' => command.key,
33
+ 'image' => command.image,
34
+ 'env' => formatted_env_vars,
35
+ 'command' => raw_command_expression.split(/\s+/)
36
+ }.merge(Rhea.configuration.container_options)
37
+
38
+ controller = Kubeclient::ReplicationController.new
39
+ controller.metadata = {
40
+ 'name' => command.key,
41
+ 'namespace' => NAMESPACE,
42
+ 'labels' => {
43
+ 'name' => command.key
44
+ },
45
+ 'annotations' => {
46
+ 'rhea_command' => command.expression
47
+ }
48
+ }
49
+ controller.spec = {
50
+ 'replicas' => command.process_count,
51
+ 'selector' => {
52
+ 'name' => command.key
53
+ },
54
+ 'template' => {
55
+ 'metadata' => {
56
+ 'labels' => {
57
+ 'name' => command.key
58
+ },
59
+ 'annotations' => {
60
+ 'rhea_command' => command.expression
61
+ }
62
+ },
63
+ 'spec' => {
64
+ 'containers' => [container_config]
65
+ }
66
+ }
67
+ }
68
+
69
+ api.create_replication_controller(controller)
70
+ end
71
+
72
+ def scale_replication_controller
73
+ controller = api.get_replication_controllers(label_selector: "name=#{command.key}").first
74
+ controller.spec.replicas = command.process_count
75
+ api.update_replication_controller(controller)
76
+ end
77
+
78
+ def parse_command_expression
79
+ match = command.expression.match(/((?:[A-Z]+=[^\s]+\s+)+)?(.*)/)
80
+ env_vars_string = match[1]
81
+ raw_command_expression = match[2]
82
+ env_vars = {}
83
+ if env_vars_string.present?
84
+ env_vars_strings = env_vars_string.strip.split(/\s+/)
85
+ env_vars_strings.each do |string|
86
+ name, value = string.split('=')
87
+ env_vars[name] = value
88
+ end
89
+ end
90
+ {
91
+ raw_command_expression: raw_command_expression,
92
+ env_vars: env_vars
93
+ }
94
+ end
95
+
96
+ def format_env_vars(env_vars)
97
+ hash = env_vars.merge(Rhea.configuration.env_vars)
98
+ hash.map do |name, value|
99
+ {
100
+ 'name' => name,
101
+ 'value' => value
102
+ }
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,30 @@
1
+ module Rhea
2
+ class Configuration
3
+ attr_accessor :command_types, :container_options, :default_command_type_key, :default_image, :env_vars, :kube_api
4
+
5
+ def initialize
6
+ self.command_types = [
7
+ {
8
+ key: 'default',
9
+ name: 'Default',
10
+ format: '$INPUT'
11
+ },
12
+ {
13
+ key: 'resque',
14
+ name: 'Resque',
15
+ format: 'QUEUES=$INPUT rake resque:work'
16
+ },
17
+ {
18
+ key: 'sidekiq',
19
+ name: 'Sidekiq',
20
+ format: 'bundle exec sidekiq $INPUT'
21
+ }
22
+ ]
23
+ self.container_options = {}
24
+ self.default_command_type_key = 'default'
25
+ self.default_image = nil
26
+ self.env_vars = {}
27
+ self.kube_api = {}
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ module Rhea
2
+ module Kubernetes
3
+ module Events
4
+ class Recent
5
+ def perform
6
+ api = Rhea::Kubernetes::Api.new
7
+ events = api.get_events
8
+ events.map! do |event|
9
+ OpenStruct.new(
10
+ hostname: event.source.host,
11
+ message: event.message,
12
+ type: event.reason,
13
+ resource_type: event.involvedObject.kind,
14
+ resource_id: event.involvedObject.name,
15
+ created_at: Time.parse(event.lastTimestamp)
16
+ )
17
+ end
18
+ events.sort_by(&:created_at).reverse
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,45 @@
1
+ module Rhea
2
+ module Kubernetes
3
+ module Nodes
4
+ class All
5
+ def perform
6
+ api = Rhea::Kubernetes::Api.new
7
+ pods = api.get_pods
8
+ hostnames_nodes = {}
9
+ pods.each do |pod|
10
+ command_expression = pod.metadata.annotations.rhea_command
11
+ next if command_expression.nil?
12
+ hostname = pod.spec.nodeName
13
+ hostnames_nodes[hostname] ||= {}
14
+ hostnames_nodes[hostname][:image] = pod[:table][:spec][:containers][0]['image']
15
+
16
+ started_at = pod.status.startTime
17
+ if started_at
18
+ started_at = Time.parse(started_at)
19
+ last_started_at = hostnames_nodes[hostname][:last_started_at]
20
+ if last_started_at.nil? || started_at > last_started_at
21
+ hostnames_nodes[hostname][:last_started_at] = started_at
22
+ end
23
+ end
24
+
25
+ phase = pod.status.phase
26
+ containers = pod.spec.containers
27
+ containers.each do |container|
28
+ image = container.image
29
+ command = Command.new(
30
+ expression: command_expression,
31
+ image: image
32
+ )
33
+ hostnames_nodes[hostname][:commands_phases] ||= {}
34
+ hostnames_nodes[hostname][:commands_phases][command] ||= []
35
+ hostnames_nodes[hostname][:commands_phases][command] << phase
36
+ end
37
+ end
38
+ hostnames_nodes.map do |hostname, node|
39
+ OpenStruct.new(node.merge(hostname: hostname))
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,22 @@
1
+ module Rhea
2
+ module Kubernetes
3
+ module SystemServices
4
+ module_function
5
+
6
+ def service_names_urls
7
+ @service_names_urls ||= begin
8
+ api_url = Rhea.configuration.kube_api[:url]
9
+ root_url = api_url.sub('/api/', '/')
10
+ services_url = "#{api_url}v1/proxy/namespaces/kube-system/services/"
11
+ {
12
+ 'Kubernetes UI' => "#{services_url}kube-ui/",
13
+ 'Grafana' => "#{services_url}monitoring-grafana/",
14
+ 'Kibana' => "#{services_url}kibana-logging/",
15
+ 'Logs' => "#{services_url}logs/",
16
+ 'Swagger UI' => "#{root_url}swagger-ui/"
17
+ }
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,3 @@
1
1
  module Rhea
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -3,8 +3,8 @@ require File.expand_path('../lib/rhea/version', __FILE__)
3
3
  Gem::Specification.new do |s|
4
4
  s.authors = ['Tom Benner']
5
5
  s.email = ['tombenner@gmail.com']
6
- s.description = s.summary = %q{}
7
- s.homepage = 'https://github.com/tombenner/rhea'
6
+ s.description = s.summary = %q{A web UI for managing a Kubernetes cluster}
7
+ s.homepage = 'https://github.com/entelo/rhea'
8
8
 
9
9
  s.files = `git ls-files`.split($\)
10
10
  s.name = 'rhea'
@@ -12,5 +12,13 @@ Gem::Specification.new do |s|
12
12
  s.version = Rhea::VERSION
13
13
  s.license = 'MIT'
14
14
 
15
- s.add_development_dependency 'rspec'
15
+ s.add_dependency 'kubeclient', '~> 0.3.0'
16
+ s.add_dependency 'haml', '~> 4.0'
17
+ s.add_dependency 'kaminari', '~> 0.16'
18
+ s.add_dependency 'sass-rails', '~> 5.0'
19
+
20
+ s.add_development_dependency 'rspec', '~> 3.0'
21
+ s.add_development_dependency 'rspec-rails', '~> 3.0'
22
+ s.add_development_dependency 'vcr', '~> 2.9'
23
+ s.add_development_dependency 'webmock', '~> 1.8'
16
24
  end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rhea::Kubernetes::Commands::All, :vcr do
4
+ include KubernetesSpecHelper
5
+
6
+ before(:each) { delete_replication_controllers }
7
+ after(:each) { delete_replication_controllers }
8
+
9
+ describe '#perform' do
10
+ context 'an existing rc' do
11
+ let(:process_count) { 1 }
12
+
13
+ before :each do
14
+ Rhea::Kubernetes::Commands::Scale.new(expression: command_expression, process_count: process_count).perform
15
+ end
16
+
17
+ it 'returns the rc' do
18
+ replication_controllers = described_class.new.perform
19
+ expected_attributes = {
20
+ expression: command_expression,
21
+ image: kube_image,
22
+ process_count: process_count
23
+ }
24
+ expect(replication_controllers.length).to eq(1)
25
+ expect(replication_controllers[0].attributes).to include(expected_attributes)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rhea::Kubernetes::Commands::Delete, :vcr do
4
+ include KubernetesSpecHelper
5
+
6
+ before(:each) { delete_replication_controllers }
7
+ after(:each) { delete_replication_controllers }
8
+
9
+ describe '#perform' do
10
+ context 'an existing rc' do
11
+ before :each do
12
+ Rhea::Kubernetes::Commands::Scale.new(expression: command_expression, process_count: 1).perform
13
+ end
14
+
15
+ it 'deletes the rc' do
16
+ described_class.new(expression: command_expression).perform
17
+ url = "#{kube_authed_api_url}replicationcontrollers/#{kube_replication_controller_name}"
18
+ expect(WebMock).to have_requested(:delete, url)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rhea::Kubernetes::Commands::Export, :vcr do
4
+ include KubernetesSpecHelper
5
+
6
+ before(:each) { delete_replication_controllers }
7
+ after(:each) { delete_replication_controllers }
8
+
9
+ describe '#perform' do
10
+ context 'an existing rc' do
11
+ let(:process_count) { 1 }
12
+
13
+ before :each do
14
+ Rhea::Kubernetes::Commands::Scale.new(expression: command_expression, process_count: process_count).perform
15
+ end
16
+
17
+ it 'returns the data' do
18
+ data = described_class.new.perform
19
+ expect(data[:version]).to eq(Rhea::VERSION)
20
+ expect(data[:created_at]).to be_a(Time)
21
+ expect(data[:commands]).to eq([
22
+ {
23
+ expression: command_expression,
24
+ image: kube_image,
25
+ process_count: process_count
26
+ }
27
+ ])
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rhea::Kubernetes::Commands::Get, :vcr do
4
+ include KubernetesSpecHelper
5
+
6
+ before(:each) { delete_replication_controllers }
7
+ after(:each) { delete_replication_controllers }
8
+
9
+ describe '#perform' do
10
+ context 'an existing rc' do
11
+ let(:process_count) { 1 }
12
+
13
+ before :each do
14
+ Rhea::Kubernetes::Commands::Scale.new(expression: command_expression, image: kube_image, process_count: process_count).perform
15
+ end
16
+
17
+ it 'gets the rc' do
18
+ replication_controller = described_class.new(expression: command_expression).perform
19
+ expected_attributes = {
20
+ expression: command_expression,
21
+ image: kube_image,
22
+ process_count: process_count
23
+ }
24
+ expect(replication_controller.attributes).to include(expected_attributes)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rhea::Kubernetes::Commands::Import, :vcr do
4
+ include KubernetesSpecHelper
5
+
6
+ before(:each) { delete_replication_controllers }
7
+ after(:each) { delete_replication_controllers }
8
+
9
+ describe '#perform' do
10
+ context 'no existing rcs' do
11
+ let(:process_count) { 1 }
12
+ let(:data) do
13
+ {
14
+ version: Rhea::VERSION,
15
+ created_at: Time.now,
16
+ commands: [
17
+ {
18
+ expression: command_expression,
19
+ image: kube_image,
20
+ process_count: process_count
21
+ }.stringify_keys
22
+ ]
23
+ }.stringify_keys
24
+ end
25
+
26
+ it 'creates the rcs' do
27
+ described_class.new(data).perform
28
+ replication_controllers = Rhea::Kubernetes::Commands::All.new.perform
29
+ expected_attributes = {
30
+ expression: command_expression,
31
+ image: kube_image,
32
+ process_count: process_count
33
+ }
34
+ expect(replication_controllers.length).to eq(1)
35
+ expect(replication_controllers[0].attributes).to include(expected_attributes)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rhea::Kubernetes::Commands::Redeploy, :vcr do
4
+ include KubernetesSpecHelper
5
+
6
+ before(:each) { delete_replication_controllers }
7
+ after(:each) { delete_replication_controllers }
8
+
9
+ describe '#perform' do
10
+ context 'an existing rc' do
11
+ let(:process_count) { 2 }
12
+
13
+ before :each do
14
+ Rhea::Kubernetes::Commands::Scale.new(expression: command_expression, process_count: process_count).perform
15
+ end
16
+
17
+ it 'redeploys the rc' do
18
+ described_class.new(expression: command_expression).perform
19
+ replication_controller = Rhea::Kubernetes::Commands::Get.new(expression: command_expression).perform
20
+
21
+ expected_attributes = {
22
+ expression: command_expression,
23
+ image: kube_image,
24
+ process_count: process_count
25
+ }
26
+ expect(replication_controller.attributes).to include(expected_attributes)
27
+
28
+ # One POST for the initial creation, and a second POST for the recreation
29
+ expect(WebMock).to have_requested(:post, "#{kube_authed_api_url}replicationcontrollers").twice
30
+ expect(WebMock).to have_requested(:delete, "#{kube_authed_api_url}replicationcontrollers/#{kube_replication_controller_name}")
31
+ expect(WebMock).to have_requested(:put, "#{kube_authed_api_url}replicationcontrollers/#{kube_replication_controller_name}")
32
+ end
33
+ end
34
+ end
35
+ end