etcweb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.bowerrc +3 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +19 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +36 -0
  9. data/Rakefile +1 -0
  10. data/app/bower_components/jquery/.bower.json +37 -0
  11. data/app/bower_components/jquery/MIT-LICENSE.txt +21 -0
  12. data/app/bower_components/jquery/bower.json +27 -0
  13. data/app/bower_components/jquery/dist/jquery.js +9205 -0
  14. data/app/bower_components/jquery/dist/jquery.min.js +5 -0
  15. data/app/bower_components/jquery/dist/jquery.min.map +1 -0
  16. data/app/bower_components/jquery/src/ajax.js +786 -0
  17. data/app/bower_components/jquery/src/ajax/jsonp.js +89 -0
  18. data/app/bower_components/jquery/src/ajax/load.js +75 -0
  19. data/app/bower_components/jquery/src/ajax/parseJSON.js +13 -0
  20. data/app/bower_components/jquery/src/ajax/parseXML.js +28 -0
  21. data/app/bower_components/jquery/src/ajax/script.js +64 -0
  22. data/app/bower_components/jquery/src/ajax/var/nonce.js +5 -0
  23. data/app/bower_components/jquery/src/ajax/var/rquery.js +3 -0
  24. data/app/bower_components/jquery/src/ajax/xhr.js +136 -0
  25. data/app/bower_components/jquery/src/attributes.js +11 -0
  26. data/app/bower_components/jquery/src/attributes/attr.js +141 -0
  27. data/app/bower_components/jquery/src/attributes/classes.js +158 -0
  28. data/app/bower_components/jquery/src/attributes/prop.js +94 -0
  29. data/app/bower_components/jquery/src/attributes/support.js +35 -0
  30. data/app/bower_components/jquery/src/attributes/val.js +161 -0
  31. data/app/bower_components/jquery/src/callbacks.js +205 -0
  32. data/app/bower_components/jquery/src/core.js +497 -0
  33. data/app/bower_components/jquery/src/core/access.js +60 -0
  34. data/app/bower_components/jquery/src/core/init.js +123 -0
  35. data/app/bower_components/jquery/src/core/parseHTML.js +39 -0
  36. data/app/bower_components/jquery/src/core/ready.js +97 -0
  37. data/app/bower_components/jquery/src/core/var/rsingleTag.js +4 -0
  38. data/app/bower_components/jquery/src/css.js +450 -0
  39. data/app/bower_components/jquery/src/css/addGetHookIf.js +22 -0
  40. data/app/bower_components/jquery/src/css/curCSS.js +57 -0
  41. data/app/bower_components/jquery/src/css/defaultDisplay.js +70 -0
  42. data/app/bower_components/jquery/src/css/hiddenVisibleSelectors.js +15 -0
  43. data/app/bower_components/jquery/src/css/support.js +96 -0
  44. data/app/bower_components/jquery/src/css/swap.js +28 -0
  45. data/app/bower_components/jquery/src/css/var/cssExpand.js +3 -0
  46. data/app/bower_components/jquery/src/css/var/getStyles.js +12 -0
  47. data/app/bower_components/jquery/src/css/var/isHidden.js +13 -0
  48. data/app/bower_components/jquery/src/css/var/rmargin.js +3 -0
  49. data/app/bower_components/jquery/src/css/var/rnumnonpx.js +5 -0
  50. data/app/bower_components/jquery/src/data.js +178 -0
  51. data/app/bower_components/jquery/src/data/Data.js +181 -0
  52. data/app/bower_components/jquery/src/data/accepts.js +20 -0
  53. data/app/bower_components/jquery/src/data/var/data_priv.js +5 -0
  54. data/app/bower_components/jquery/src/data/var/data_user.js +5 -0
  55. data/app/bower_components/jquery/src/deferred.js +149 -0
  56. data/app/bower_components/jquery/src/deprecated.js +13 -0
  57. data/app/bower_components/jquery/src/dimensions.js +50 -0
  58. data/app/bower_components/jquery/src/effects.js +648 -0
  59. data/app/bower_components/jquery/src/effects/Tween.js +114 -0
  60. data/app/bower_components/jquery/src/effects/animatedSelector.js +13 -0
  61. data/app/bower_components/jquery/src/event.js +868 -0
  62. data/app/bower_components/jquery/src/event/ajax.js +13 -0
  63. data/app/bower_components/jquery/src/event/alias.js +39 -0
  64. data/app/bower_components/jquery/src/event/support.js +9 -0
  65. data/app/bower_components/jquery/src/exports/amd.js +24 -0
  66. data/app/bower_components/jquery/src/exports/global.js +32 -0
  67. data/app/bower_components/jquery/src/intro.js +44 -0
  68. data/app/bower_components/jquery/src/jquery.js +37 -0
  69. data/app/bower_components/jquery/src/manipulation.js +580 -0
  70. data/app/bower_components/jquery/src/manipulation/_evalUrl.js +18 -0
  71. data/app/bower_components/jquery/src/manipulation/support.js +32 -0
  72. data/app/bower_components/jquery/src/manipulation/var/rcheckableType.js +3 -0
  73. data/app/bower_components/jquery/src/offset.js +207 -0
  74. data/app/bower_components/jquery/src/outro.js +1 -0
  75. data/app/bower_components/jquery/src/queue.js +142 -0
  76. data/app/bower_components/jquery/src/queue/delay.js +22 -0
  77. data/app/bower_components/jquery/src/selector-native.js +172 -0
  78. data/app/bower_components/jquery/src/selector-sizzle.js +14 -0
  79. data/app/bower_components/jquery/src/selector.js +1 -0
  80. data/app/bower_components/jquery/src/serialize.js +111 -0
  81. data/app/bower_components/jquery/src/sizzle/dist/sizzle.js +2067 -0
  82. data/app/bower_components/jquery/src/sizzle/dist/sizzle.min.js +3 -0
  83. data/app/bower_components/jquery/src/sizzle/dist/sizzle.min.map +1 -0
  84. data/app/bower_components/jquery/src/traversing.js +199 -0
  85. data/app/bower_components/jquery/src/traversing/findFilter.js +100 -0
  86. data/app/bower_components/jquery/src/traversing/var/rneedsContext.js +6 -0
  87. data/app/bower_components/jquery/src/var/arr.js +3 -0
  88. data/app/bower_components/jquery/src/var/class2type.js +4 -0
  89. data/app/bower_components/jquery/src/var/concat.js +5 -0
  90. data/app/bower_components/jquery/src/var/hasOwn.js +5 -0
  91. data/app/bower_components/jquery/src/var/indexOf.js +5 -0
  92. data/app/bower_components/jquery/src/var/pnum.js +3 -0
  93. data/app/bower_components/jquery/src/var/push.js +5 -0
  94. data/app/bower_components/jquery/src/var/rnotwhite.js +3 -0
  95. data/app/bower_components/jquery/src/var/slice.js +5 -0
  96. data/app/bower_components/jquery/src/var/strundefined.js +3 -0
  97. data/app/bower_components/jquery/src/var/support.js +4 -0
  98. data/app/bower_components/jquery/src/var/toString.js +5 -0
  99. data/app/bower_components/jquery/src/wrap.js +79 -0
  100. data/app/stylesheets/application.scss +31 -0
  101. data/app/views/dir.haml +95 -0
  102. data/app/views/etcd_error.haml +5 -0
  103. data/app/views/etcvault_keys_select.haml +6 -0
  104. data/app/views/index.haml +1 -0
  105. data/app/views/key.haml +68 -0
  106. data/app/views/keys.haml +30 -0
  107. data/app/views/layout.haml +20 -0
  108. data/bin/console +14 -0
  109. data/bin/setup +7 -0
  110. data/config.ru +19 -0
  111. data/etcweb.gemspec +37 -0
  112. data/lib/etcweb.rb +6 -0
  113. data/lib/etcweb/app.rb +230 -0
  114. data/lib/etcweb/version.rb +3 -0
  115. metadata +331 -0
@@ -0,0 +1,31 @@
1
+ @import "bootstrap-sprockets";
2
+ @import "bootstrap";
3
+
4
+ .keys-footer {
5
+ margin-top: 40px;
6
+ border-top: 2px solid $gray_lighter;
7
+ padding-top: 40px;
8
+ }
9
+
10
+ .header-top {
11
+ h1 {
12
+ font-size: 24px;
13
+ margin: 0;
14
+ padding: 0;
15
+ }
16
+ height: 30px;
17
+ margin-top: 15px;
18
+ margin-bottom: 8px;
19
+
20
+ button {
21
+ margin-top: 0;
22
+ margin-bottom: 0;
23
+ padding-top: 0;
24
+ padding-bottom: 0;
25
+ }
26
+ }
27
+
28
+ .align-vertical {
29
+ display: flex;
30
+ align-items: center;
31
+ }
@@ -0,0 +1,95 @@
1
+ :javascript
2
+ jQuery(function($) {
3
+ $("#mkdir_form, #value_form").hide();
4
+ $("#mkdir_form, #value_form").removeClass("hidden");
5
+
6
+ $("#open_mkdir_form").click(function() {
7
+ $("#mkdir_form").show();
8
+ $("#value_form").hide();
9
+ $("#mkdir_form .form-control:first").focus();
10
+ });
11
+ $("#open_value_form").click(function() {
12
+ $("#mkdir_form").hide();
13
+ $("#value_form").show();
14
+ $("#value_form .form-control:first").focus();
15
+ });
16
+ });
17
+
18
+ .row
19
+ .col-md-12
20
+ %p
21
+ %button#open_mkdir_form.btn.btn-default
22
+ %span.glyphicon.glyphicon-folder-close{'aria-hidden' => true}
23
+ New directory
24
+ %button#open_value_form.btn.btn-default
25
+ %span.glyphicon.glyphicon-plus{'aria-hidden' => true}
26
+ New value
27
+
28
+ - action = "/keys#{node.key}"
29
+ - action = "/keys/" if action == "/keys"
30
+
31
+ .hidden.panel.panel-default#mkdir_form
32
+ .panel-heading New directory
33
+ .panel-body
34
+ %form.form{action: action, method: 'POST'}
35
+ %input{type: 'hidden', name: '_method', value: 'PUT'}
36
+ %input{type: 'hidden', name: 'dir', value: '1'}
37
+ .form-group
38
+ %input.form-control{type: 'text', name: 'child', placeholder: 'new key name'}
39
+ .form-group
40
+ %button.btn.btn-primary{type: 'submit'} Create
41
+
42
+ .hidden.panel.panel-default#value_form
43
+ .panel-heading New value
44
+ .panel-body
45
+ %form.form{action: action, method: 'POST'}
46
+ %input{type: 'hidden', name: '_method', value: 'PUT'}
47
+ .form-group
48
+ %input.form-control{type: 'text', name: 'child', placeholder: 'new key name'}
49
+ .form-group
50
+ %textarea.form-control{name: 'value', row: '2', placeholder: 'value'}
51
+ .form-group
52
+ .row
53
+ - if etcvault?
54
+ .col-md-4
55
+ != haml :etcvault_keys_select
56
+ .col-md-1
57
+ %button.btn.btn-primary{type: 'submit'} Create
58
+ .col-md-4
59
+ %button.btn.btn-default{type: 'submit', name: 'continue', value: '1'} Create and Continue
60
+
61
+ .row
62
+ .col-md-12
63
+ - if node.children.empty?
64
+ %p (empty directory)
65
+ .list-group
66
+ - node.children.sort_by(&:key).each do |child|
67
+ - name = child.key.split(?/).last
68
+ %a.list-group-item{href: "/keys#{child.key}"}
69
+ .row
70
+ - if child.dir
71
+ .col-md-5
72
+ #{name}/
73
+ .col-md-7
74
+ %span.glyphicon.glyphicon-folder-open{'aria-hidden' => true}
75
+ - else
76
+ .col-md-4= name
77
+ .col-md-1.text-right
78
+ - if child.etcvault
79
+ = child.etcvault['container'] && child.etcvault['container']['KeyName']
80
+ %span.glyphicon.glyphicon-briefcase{'aria-hidden' => true}
81
+ - elsif child.etcvault_error
82
+ %span.text-danger.glyphicon.glyphicon-briefcase{'aria-hidden' => true}
83
+ .col-md-7
84
+ %code= child.value
85
+ .row
86
+ .col-md-12
87
+ %form.form-inline{action: "/keys#{node.key}", method: 'POST', onsubmit: 'javascript:return confirm("Sure?")'}
88
+ %input{type: 'hidden', name: '_method', value: 'DELETE'}
89
+ %button.btn.btn-danger{type: 'submit'} Destroy
90
+ .checkbox
91
+ %label
92
+ %input{type: 'checkbox', name: 'recursive', value: '1', checked: false}
93
+ Recursive
94
+
95
+
@@ -0,0 +1,5 @@
1
+ - p error
2
+ .row
3
+ .col-md-12
4
+ %h2 Error: #{error.class}
5
+ %p= error.message
@@ -0,0 +1,6 @@
1
+ - name ||= 'etcvault_key'
2
+ - default_key_name ||= nil
3
+ %select.form-control{name: name}
4
+ %option{value: '', selected: default_key_name.nil?} No etcvault encryption
5
+ - etcvault_keys.each do |key_name|
6
+ %option{value: key_name, selected: default_key_name == key_name} Encrypt using etcvault: #{key_name}
@@ -0,0 +1 @@
1
+ %h1 It works
@@ -0,0 +1,68 @@
1
+ .row
2
+ .col-md-12
3
+ %h3
4
+ %span.glyphicon.glyphicon-tag{'aria-hidden' => true}
5
+ Value
6
+ %pre= node.value
7
+ .row
8
+ .col-md-12
9
+ %h3
10
+ %span.glyphicon.glyphicon-cog{'aria-hidden' => true}
11
+ Information
12
+ %p
13
+ %span
14
+ %span.label-default.label modified_index
15
+ = node.modified_index
16
+ %span
17
+ %span.label-default.label created_index
18
+ = node.created_index
19
+ - if node.ttl
20
+ %p
21
+ %span.label-default.label TTL
22
+ = node.ttl.inspect
23
+ - if node.expiration
24
+ %p
25
+ %span.label-default.label expiration
26
+ = node.expiration.inspect
27
+
28
+ - if node.etcvault || node.etcvault_error
29
+ .row
30
+ .col-md-12
31
+ %h3
32
+ %span.glyphicon.glyphicon-briefcase{'aria-hidden' => true}
33
+ Etcvault
34
+ - if node.etcvault
35
+ %p= node.etcvault.inspect
36
+ - if node.etcvault_error
37
+ %p
38
+ %span.label.label-danger Error
39
+ = node.etcvault_error.inspect
40
+
41
+ .row
42
+ .col-md-12
43
+ %h3
44
+ %span.glyphicon.glyphicon-edit{'aria-hidden' => true}
45
+ Modify
46
+ %form{action: "/keys#{node.key}", method: 'POST'}
47
+ %input{type: 'hidden', name: '_method', value: 'PUT'}
48
+ .form-group
49
+ %textarea.form-control{rows: '5', name: 'value'}= node.value
50
+ .form-group
51
+ - if etcvault?
52
+ .row
53
+ .col-md-4
54
+ - default_key_name = node.etcvault && node.etcvault['container'] && node.etcvault['container']['KeyName']
55
+ != haml :etcvault_keys_select, locals: {default_key_name: default_key_name}
56
+ .col-md-8
57
+ %button.btn.btn-primary{type: 'submit'} Save
58
+ - else
59
+ %button.btn.btn-primary{type: 'submit'} Save
60
+ .row
61
+ .col-md-12
62
+ %h3
63
+ %span.glyphicon.glyphicon-trash{'aria-hidden' => true}
64
+ Delete
65
+ %form{action: "/keys#{node.key}", method: 'POST', onsubmit: 'javascript:return confirm("Sure?")'}
66
+ %input{type: 'hidden', name: '_method', value: 'DELETE'}
67
+ .form-group
68
+ %button.btn.btn-danger{type: 'submit'} Destroy
@@ -0,0 +1,30 @@
1
+ - node = @etcd_response.node
2
+ .row
3
+ .col-md-12
4
+ %ol.breadcrumb
5
+ - active = (key.nil? || key.empty?) ? 'active' : nil
6
+ %li{class: active}
7
+ %a{href: '/keys/'}
8
+ %span.glyphicon.glyphicon-home{'aria-hidden' => true}
9
+ - if key
10
+ - paths = key.split(?/).tap(&:shift)
11
+ - paths.each_with_index do |part, i|
12
+ - active = (i.succ == key.size) ? 'active' : nil
13
+ %li{class: active}
14
+ %a{href: "/keys/#{paths[0..i].join(?/)}"}
15
+ = part
16
+
17
+ - if node.dir
18
+ != haml :dir, locals: {node: node}
19
+ - else
20
+ != haml :key, locals: {node: node}
21
+
22
+
23
+ %footer.keys-footer.row
24
+ .col-md-6
25
+ %span.label-default.label etcd_index
26
+ = @etcd_response.etcd_index
27
+ %span.label-default.label raft_index
28
+ = @etcd_response.raft_index
29
+ %span.label-default.label raft_term
30
+ = @etcd_response.raft_term
@@ -0,0 +1,20 @@
1
+ !!! 5
2
+ %html
3
+ %head
4
+ %meta{charset: 'utf-8'}
5
+ %script{type: 'application/javascript', src: '/assets/jquery/dist/jquery.min.js'}
6
+ %link{rel: 'stylesheet', type: 'text/css', href: '/assets/application.css'}
7
+ %title etcweb
8
+ %body
9
+ %header.header-top.container
10
+ .row.align-vertical
11
+ .col-md-8
12
+ %h1 etcweb
13
+ .col-md-4.text-right
14
+ - if auth_enabled? && current_user
15
+ Logged in as #{current_user[:info][:nickname] || current_user[:info][:name]}
16
+ %form{action: '/logout', method: 'POST', style: 'display: inline;'}
17
+ %button.btn.btn-link{type: 'submit'}
18
+ Log out
19
+ %section.container
20
+ != yield
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "etcweb"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,19 @@
1
+ require 'etcweb'
2
+ require 'omniauth'
3
+
4
+ use Rack::Session::Cookie, :key => 'etcweb_sess',
5
+ :secret => 'configuration'
6
+
7
+ use OmniAuth::Builder do
8
+ provider :developer
9
+ end
10
+
11
+ run Etcweb::App.rack(
12
+ etcd: {
13
+ host: '127.0.0.1', port: 4001,
14
+ },
15
+ etcvault: true,
16
+ auth: {
17
+ omniauth: 'developer',
18
+ },
19
+ )
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'etcweb/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "etcweb"
8
+ spec.version = Etcweb::VERSION
9
+ spec.authors = ["Shota Fukumori (sora_h)"]
10
+ spec.email = ["sorah@cookpad.com"]
11
+
12
+ spec.summary = %q{Web UI for etcd}
13
+ spec.description = nil
14
+ spec.homepage = "https://github.com/sorah/etcweb"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "sinatra", ">= 1.4.5"
23
+ spec.add_dependency "etcd", ">= 0.2.4"
24
+ spec.add_dependency "etcd-etcvault", ">= 1.1.0"
25
+ spec.add_dependency "sprockets", ">= 2.12.3", "< 3"
26
+ spec.add_dependency "faml", ">= 0.2.0"
27
+ spec.add_dependency "bootstrap-sass", '~> 3.3.4'
28
+ spec.add_dependency "sprockets-helpers"
29
+ spec.add_dependency "omniauth"
30
+
31
+ spec.add_development_dependency "bundler", "~> 1.9"
32
+ spec.add_development_dependency "rake", "~> 10.0"
33
+
34
+ spec.add_development_dependency "rspec", "~> 3.2.0"
35
+
36
+ spec.add_development_dependency "rack-test"
37
+ end
@@ -0,0 +1,6 @@
1
+ require "etcweb/version"
2
+ require 'etcweb/app'
3
+
4
+ module Etcweb
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,230 @@
1
+ require 'sinatra/base'
2
+ require 'json'
3
+ require 'faml'
4
+ require 'rack'
5
+ require 'sass'
6
+ require 'sprockets'
7
+ require 'sprockets/helpers'
8
+
9
+ require 'bootstrap-sass'
10
+
11
+ require 'etcd'
12
+ require 'etcd/etcvault'
13
+
14
+ require 'omniauth'
15
+
16
+ module Etcweb
17
+ class App < Sinatra::Base
18
+ set :method_override, true
19
+ set :show_exceptions, false
20
+ set :root, File.expand_path(File.join(__dir__, '..', '..', 'app'))
21
+ set :sprockets, Sprockets::Environment.new.tap { |env|
22
+ env.append_path "#{self.root}/javascripts"
23
+ env.append_path "#{self.root}/stylesheets"
24
+ env.append_path "#{self.root}/images"
25
+ env.append_path "#{self.root}/bower_components"
26
+ }
27
+
28
+ def self.initialize_etcd(config)
29
+ # etcd v0.2.4 (latest as of Mar 31, 2015) doesn't set TLS parameters in constructor
30
+ # https://github.com/ranjib/etcd-ruby/commit/bf2c7e6dee8b2c07f85cca8541d16dcbef67cc1a
31
+ Etcd.client(config).tap do |etcd|
32
+ etcd.config.ca_file = config[:ca_file]
33
+ etcd.config.ssl_cert = config[:ssl_cert]
34
+ etcd.config.ssl_key = config[:ssl_key]
35
+ end
36
+ end
37
+
38
+ def self.initialize_context(config)
39
+ {}.tap do |ctx|
40
+ ctx[:etcd] = initialize_etcd(config[:etcd] || {})
41
+ ctx[:config] = config
42
+ end
43
+ end
44
+
45
+ def self.rack(config={})
46
+ klass = self
47
+
48
+ context = initialize_context(config)
49
+ app = lambda { |env|
50
+ env['etcweb'] = context
51
+ klass.call(env)
52
+ }
53
+
54
+ Rack::Builder.app do
55
+ map '/assets' do
56
+ run klass.sprockets
57
+ end
58
+
59
+ map '/' do
60
+ run app
61
+ end
62
+ end
63
+ end
64
+
65
+ helpers do
66
+ include Sprockets::Helpers
67
+
68
+ def context
69
+ request.env['etcweb']
70
+ end
71
+
72
+ def etcd
73
+ context[:etcd]
74
+ end
75
+
76
+ def key
77
+ @key ||= "/#{params[:splat] && params[:splat].first}".tap do |k|
78
+ k.concat("/#{params[:child]}") if params[:child]
79
+ k.sub!(%r{^//}, '/') # for child of root
80
+ end
81
+ end
82
+
83
+ def etcvault?
84
+ !!context[:config][:etcvault]
85
+ end
86
+
87
+ def etcvault_keys_cache_ttl
88
+ context[:config][:etcvault_keys_cache_ttl] || 30
89
+ end
90
+
91
+ def etcvault_keys
92
+ return [] unless etcvault?
93
+ context[:etcvault_keys] ||= {}
94
+ if context[:etcvault_keys][:expiry].nil? || context[:etcvault_keys][:expiry] <= Time.now
95
+ context[:etcvault_keys] = {
96
+ expiry: Time.now + etcvault_keys_cache_ttl,
97
+ keys: etcd.etcvault_keys.sort
98
+ }
99
+ end
100
+ context[:etcvault_keys][:keys]
101
+ end
102
+
103
+ def auth_enabled?
104
+ context[:config][:auth]
105
+ end
106
+
107
+ def omniauth_strategy
108
+ context[:config][:auth][:omniauth] or raise 'no config.auth.omniauth specified'
109
+ end
110
+
111
+ def auth_allow_policy_proc
112
+ context[:config][:auth][:allow_policy_proc] || proc { true }
113
+ end
114
+
115
+ def auth_after_login_proc
116
+ context[:config][:auth][:after_login_proc] || proc { }
117
+ end
118
+
119
+ def current_user
120
+ session[:user]
121
+ end
122
+ end
123
+
124
+ before do
125
+ next if request.path_info.start_with?('/auth')
126
+ next unless auth_enabled?
127
+
128
+ if current_user && instance_eval(&auth_allow_policy_proc)
129
+ next
130
+ end
131
+
132
+ if request.get?
133
+ session[:back_to] = request.fullpath
134
+ redirect "/auth/#{omniauth_strategy}"
135
+ else
136
+ halt 401
137
+ end
138
+ end
139
+
140
+ post '/logout' do
141
+ session[:user] = nil
142
+ redirect '/'
143
+ end
144
+
145
+ post '/auth/:strategy/callback' do
146
+ auth = env['omniauth.auth']
147
+ session[:user] = {
148
+ uid: auth[:uid],
149
+ info: auth[:info],
150
+ provider: auth[:provider],
151
+ }
152
+ instance_eval(&auth_after_login_proc)
153
+ redirect session.delete(:back_to) || "/"
154
+ end
155
+
156
+ get '/' do
157
+ redirect "/keys/"
158
+ end
159
+
160
+ get '/keys' do
161
+ redirect "/keys/"
162
+ end
163
+
164
+ get '/keys/*' do
165
+ begin
166
+ @etcd_response = etcd.get(key)
167
+ rescue Etcd::NotDir, Etcd::KeyNotFound
168
+ halt 404
169
+ rescue Etcd::Error => e
170
+ status 400
171
+ return haml(:etcd_error, locals: {error: e})
172
+ end
173
+
174
+ haml :keys
175
+ end
176
+
177
+ put '/keys/*' do
178
+ options = {value: params[:value]}
179
+
180
+ if params[:etcvault_key] && !params[:etcvault_key].empty?
181
+ unless etcvault_keys.include?(params[:etcvault_key])
182
+ halt 404
183
+ end
184
+ options[:value] = "ETCVAULT::plain:#{params[:etcvault_key]}:#{options[:value]}::ETCVAULT"
185
+ end
186
+
187
+ if params[:dir]
188
+ options.replace(dir: true)
189
+ end
190
+
191
+ options[:ttl] = params[:ttl].to_i if params[:ttl]
192
+ options[:value] = options[:value].gsub("\r\n", "\n") if options[:value]
193
+
194
+ begin
195
+ @etcd_response = etcd.set(key, options)
196
+ rescue Etcd::Error => e
197
+ status 400
198
+ return haml(:etcd_error, locals: {error: e})
199
+ end
200
+
201
+ if params[:continue] != nil
202
+ parent = key.split(?/).tap(&:pop).join(?/)
203
+ redirect "/keys#{parent}"
204
+ else
205
+ redirect "/keys#{key}"
206
+ end
207
+ end
208
+
209
+ delete '/keys/*' do
210
+ begin
211
+ options = {recursive: !!params[:recursive]}
212
+ @etcd_response = etcd.delete(key, options)
213
+ rescue Etcd::NotDir, Etcd::KeyNotFound
214
+ halt 404
215
+ rescue Etcd::Error => e
216
+ status 400
217
+ return haml(:etcd_error, locals: {error: e})
218
+ end
219
+
220
+ parent = key.split(?/).tap(&:pop).join(?/)
221
+ redirect "/keys#{parent}"
222
+ end
223
+
224
+ get '/etcvault_keys' do
225
+ content_type :json
226
+
227
+ etcvault_keys.to_json
228
+ end
229
+ end
230
+ end