ghkv 0.1.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5c97162f2622d436bc7ff714e48923da10cedf1d
4
+ data.tar.gz: 495855df9966c7596efffb298f07ee485fb19182
5
+ SHA512:
6
+ metadata.gz: e80c15dc9ed67e506c4e12eac95d8abb826b4044de52ac747892b77978d333910ecc5f5841e332aaea379c230d0531b02203df92f6aa04ca2f6debcb239d6cea
7
+ data.tar.gz: b6af58f3050486f7e541ff251afc7d1baabc8c4fb3d9daee4ffa6cf12c1d981485868ed3a1c9f4dd3b44649baa40f7e2cc54cf860184f18e82ddf6a1684db470
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .idea
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ghkv.gemspec
4
+ gemspec
@@ -0,0 +1,53 @@
1
+ # Ghkv
2
+
3
+ `ghkv` is a gem (and corresponding CLI tool) to allow for quick and dirty
4
+ persistence when you really can't be bothered spining up a Redis instance.
5
+
6
+ ## Gem
7
+
8
+ ```ruby
9
+ require 'ghkv'
10
+
11
+ # token will be read from ENV['GHKV_TOKEN'] if not passed in
12
+ kv = Ghkv::Ghkv.new repo: 'consumer-data/github_build_notifier', token: '<some hex>'
13
+ kv['key'] = { "some" => "hash" }
14
+ kv['key'] # => { "some" => "hash" }
15
+ kv.keys # => ["some"]
16
+ # kv.delete 'key'
17
+ kv.save
18
+ ```
19
+
20
+ If your repo is hosted on GitHub enterprise, you can pass in an `api_url`
21
+ parameter (e.g. `https://git.example.com/api/v3`) or put it in
22
+ `ENV['GHKV_API_URL']`.
23
+
24
+ ## CLI
25
+
26
+ Tip: Put the following in your `~/.bash_profile` for quicker usage:
27
+
28
+ ```
29
+ # ~/.bash_profile
30
+ export GHKV_TOKEN=somehexvalue
31
+ ```
32
+
33
+ ```
34
+ $ ghkv
35
+ Usage:
36
+ ghkv [OPTIONS] SUBCOMMAND [ARG] ...
37
+
38
+ Parameters:
39
+ SUBCOMMAND subcommand
40
+ [ARG] ... subcommand arguments
41
+
42
+ Subcommands:
43
+ get Get value from KV and print to stdout
44
+ set Pass in value to store in KV by parameter or from stdin
45
+ list List all keys in KV, one per line
46
+ delete Delete key from KV
47
+
48
+ Options:
49
+ --repo REPO Qualified (user/repo) repo name
50
+ --token TOKEN GitHub API token (default: $GHKV_TOKEN)
51
+ --api API Base URL for enterprise GitHub (default: $GHKV_API_URL)
52
+ -h, --help print help
53
+ ```
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'ghkv'
3
+
4
+ Ghkv::CLI.run
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ghkv/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'ghkv'
8
+ spec.version = Ghkv::VERSION
9
+ spec.authors = ['Aidan Steele']
10
+ spec.email = ['aidan.steele@glassechidna.com.au']
11
+
12
+ spec.summary = %q{Use the GitHub API as a ghetto key-value store.}
13
+
14
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
15
+ spec.bindir = 'exe'
16
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.add_development_dependency 'bundler', '~> 1.10'
20
+ spec.add_development_dependency 'rake', '~> 10.0'
21
+ spec.add_development_dependency 'rspec'
22
+ spec.add_development_dependency 'pry-stack_explorer'
23
+ spec.add_development_dependency 'pry-rescue'
24
+
25
+ spec.add_runtime_dependency 'clamp', '>= 1.0'
26
+ end
@@ -0,0 +1,13 @@
1
+ require 'set'
2
+ require 'json'
3
+ require 'clamp'
4
+ require 'base64'
5
+ require 'net/http'
6
+
7
+ require 'ghkv/cli'
8
+ require 'ghkv/ghkv'
9
+ require 'ghkv/version'
10
+ require 'ghkv/http_client'
11
+
12
+ module Ghkv
13
+ end
@@ -0,0 +1,48 @@
1
+ module Ghkv
2
+ class CLI < Clamp::Command
3
+ option '--repo', 'REPO', 'Qualified (user/repo) repo name'
4
+ option '--token', 'TOKEN', 'GitHub API token', environment_variable: 'GHKV_TOKEN'
5
+ option '--api', 'API', 'Base URL for enterprise GitHub', environment_variable: 'GHKV_API_URL'
6
+
7
+ subcommand 'get', 'Get value from KV and print to stdout' do
8
+ parameter 'KEY', 'key'
9
+
10
+ def execute
11
+ puts ghkv[key].to_json
12
+ end
13
+ end
14
+
15
+ subcommand 'set', 'Pass in value to store in KV by parameter or from stdin' do
16
+ parameter 'KEY', 'key'
17
+ parameter '[VALUE]', 'value'
18
+
19
+ def execute
20
+ ghkv[key] = JSON.parse input
21
+ ghkv.save
22
+ end
23
+
24
+ def input
25
+ value || $stdin.read
26
+ end
27
+ end
28
+
29
+ subcommand 'list', 'List all keys in KV, one per line' do
30
+ def execute
31
+ puts ghkv.keys
32
+ end
33
+ end
34
+
35
+ subcommand 'delete', 'Delete key from KV' do
36
+ parameter 'KEY', 'key'
37
+
38
+ def execute
39
+ ghkv.delete key
40
+ ghkv.save
41
+ end
42
+ end
43
+
44
+ def ghkv
45
+ @ghkv ||= Ghkv.new repo: repo, api_url: api, token: token
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,110 @@
1
+ module Ghkv
2
+ class Ghkv
3
+ attr_accessor :http, :cache, :dirty, :deleted, :namespace
4
+
5
+ def initialize(repo:, api_url: nil, token: ENV['GHKV_TOKEN'])
6
+ api_url ||= ENV['GHKV_API_URL'] || 'https://api.github.com/'
7
+ @http = HttpClient.new base: "#{api_url}/repos/#{repo}/", token: token
8
+
9
+ @cache = {}
10
+ @dirty = Set.new
11
+ @deleted = Set.new
12
+ @namespace = 'ghkv'
13
+ end
14
+
15
+ def [](key)
16
+ cache[key] ||= get key
17
+ end
18
+
19
+ def []=(key, value)
20
+ dirty << key
21
+ deleted.delete key
22
+
23
+ cache[key] = value
24
+ end
25
+
26
+ def delete(key)
27
+ deleted << key
28
+ cache.delete key
29
+ end
30
+
31
+ def keys
32
+ prefix_len = "refs/#{namespace}/".length
33
+
34
+ url = refs_url namespace
35
+ resp = http.request :get, url
36
+
37
+ saved_keys = resp.map { |ref| ref['ref'][prefix_len..-1] }
38
+ (saved_keys + cache.keys).uniq - deleted.to_a
39
+ end
40
+
41
+ def save
42
+ deleted.each do |key|
43
+ delete_ref key
44
+ end
45
+
46
+ dirty.each do |key|
47
+ set key, cache[key]
48
+ end
49
+
50
+ true
51
+ end
52
+
53
+ private
54
+
55
+ def get(key)
56
+ url = refs_url("#{namespace}/#{key}")
57
+ resp = http.request(:get, url)
58
+ object = resp['object']
59
+ return nil if object.nil?
60
+
61
+ blob = http.request :get, object['url']
62
+ decoded = Base64.decode64 blob['content']
63
+ Marshal.load decoded
64
+ end
65
+
66
+ def set(key, value)
67
+ sha = post_blob value
68
+
69
+ if get(key).nil?
70
+ create_ref sha, key
71
+ else
72
+ update_ref sha, key
73
+ end
74
+ end
75
+
76
+ def delete_ref(key)
77
+ http.request :delete, refs_url("#{namespace}/#{key}")
78
+ end
79
+
80
+ def create_ref(sha, key)
81
+ http.request(
82
+ :post,
83
+ refs_url,
84
+ ref: "refs/#{namespace}/#{key}",
85
+ sha: sha
86
+ )
87
+ end
88
+
89
+ def update_ref(sha, key)
90
+ http.request(
91
+ :patch,
92
+ refs_url("#{namespace}/#{key}"),
93
+ force: true,
94
+ sha: sha
95
+ )
96
+ end
97
+
98
+ def post_blob(value)
99
+ encoded = Base64.encode64 Marshal.dump value
100
+ payload = { encoding: 'base64', content: encoded }
101
+ blob = http.request :post, 'git/blobs', payload
102
+ blob['sha']
103
+ end
104
+
105
+ def refs_url(key=nil)
106
+ "git/refs/#{key}".chomp('/')
107
+ end
108
+ end
109
+ end
110
+
@@ -0,0 +1,37 @@
1
+ module Ghkv
2
+ class HttpClient
3
+ attr_accessor :token
4
+ attr_accessor :verbose
5
+ attr_accessor :base
6
+
7
+ def initialize(base:, token:)
8
+ @base = URI.parse base
9
+ @token = token
10
+ end
11
+
12
+ def http_request(method, uri)
13
+ klass = Net::HTTP.const_get method.capitalize
14
+ klass.new(uri).tap do |r|
15
+ r.basic_auth token, 'x-oauth-basic'
16
+ end
17
+ end
18
+
19
+ def http(uri)
20
+ Net::HTTP.new(uri.host, uri.port).tap do |h|
21
+ h.set_debug_output($stdout) if verbose
22
+ h.use_ssl = uri.port == 443
23
+ end
24
+ end
25
+
26
+ def request(method, url, body=nil)
27
+ uri = base + url
28
+
29
+ req = http_request method, uri
30
+ req.body = body.to_json if body
31
+
32
+ client = http uri
33
+ resp = client.request req
34
+ JSON.parse(resp.body) rescue nil
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module Ghkv
2
+ VERSION = '0.1.1'
3
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ghkv
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Aidan Steele
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-09-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-stack_explorer
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-rescue
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: clamp
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '1.0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '1.0'
97
+ description:
98
+ email:
99
+ - aidan.steele@glassechidna.com.au
100
+ executables:
101
+ - ghkv
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - ".travis.yml"
108
+ - Gemfile
109
+ - README.md
110
+ - Rakefile
111
+ - exe/ghkv
112
+ - ghkv.gemspec
113
+ - lib/ghkv.rb
114
+ - lib/ghkv/cli.rb
115
+ - lib/ghkv/ghkv.rb
116
+ - lib/ghkv/http_client.rb
117
+ - lib/ghkv/version.rb
118
+ homepage:
119
+ licenses: []
120
+ metadata: {}
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project:
137
+ rubygems_version: 2.4.5
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Use the GitHub API as a ghetto key-value store.
141
+ test_files: []