etcweb 1.0.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 (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