ci_in_a_can 0.0.25 → 0.1.0

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
  SHA1:
3
- metadata.gz: 73df3df8205659926b06262cc3b7de93193c4112
4
- data.tar.gz: 2915333ad1956d539c0b55548fd2a207e2feb001
3
+ metadata.gz: 01a156efea17fc2fc3ea80032a133372068c96b4
4
+ data.tar.gz: f1a685129c6f48b798c93eec378c353701460140
5
5
  SHA512:
6
- metadata.gz: 2462767fcd35e1a8bb8b3cc96ce9f23abf194945de80d9af9f885123d2e62ab824c7b4deb86289a17aff7ad71e24d596b98e54ad622fb7f132a2c264041513df
7
- data.tar.gz: c5f4db5042869b114436fd561a818b1b8328da9a329a6e0ef48e5be711cd3225d2458f0b4c5e5609a9a8eb4af6241df17c8ba1cca4c21eb23ced61d6713c196a
6
+ metadata.gz: 7aa2a8ecf9eda070a3bb35efaf73c183199aaa95f6e85bdbdb2601f76f09056d2523f1733afcf4dc51cfc36dfa59c50276e9314a93039f0a954f04dba4557c7b
7
+ data.tar.gz: 5ddaab0fe750ed016db64bbf995a215f1824db6bb183e189a6a75a11473abdc31ab9e11c57415559e9c5905894c741b9999aef02f49bc1c1a2b4fec28a648791
data/README.md CHANGED
@@ -40,7 +40,8 @@ When any push is made to your Github repository, Github will send a notification
40
40
  1. A default rake task that runs all of your tests.
41
41
  2. An environment variable named GITHUB_AUTH_TOKEN. This is used to report results back to Github.
42
42
  3. An environment variable named SITE_URL. This will be the URL of your site, for things like providing Github with a link back to the site to show test results.
43
- 2. A server that will "just run" your application. Set up your own server with whatever dependencies your application needs.
43
+ 4. A server that will "just run" your application. Set up your own server with whatever dependencies your application needs.
44
+ 5. An environment variable named PASSPHRASE. You'll need to provide this if you want to make config changes. You may not need to do this, so this step is optional.
44
45
 
45
46
 
46
47
  ## Contributing
data/lib/ci_in_a_can.rb CHANGED
@@ -3,7 +3,9 @@ require 'uuid'
3
3
  require 'subtle'
4
4
 
5
5
  require_relative "ci_in_a_can/version"
6
+ require_relative "ci_in_a_can/view_models/view_model"
6
7
  Dir[File.dirname(__FILE__) + '/ci_in_a_can/*.rb'].each { |file| require file }
8
+ Dir[File.dirname(__FILE__) + '/ci_in_a_can/view_models/*.rb'].each { |file| require file }
7
9
 
8
10
  module CiInACan
9
11
 
@@ -13,6 +13,12 @@ module CiInACan
13
13
  end
14
14
 
15
15
  post %r{/repo/(.+)} do
16
+
17
+ if ENV['PASSPHRASE'] != params[:passphrase].to_s
18
+ redirect '/'
19
+ return
20
+ end
21
+
16
22
  params[:id] = params[:captures].first
17
23
  commands = params[:commands].gsub("\r\n", "\n").split("\n")
18
24
  commands = commands.map { |x| x.strip }.select { |x| x != '' }
@@ -30,6 +36,8 @@ module CiInACan
30
36
  CiInACan::WebContent.full_page_of(
31
37
  <<EOF
32
38
  <form action="/repo/#{params[:id]}" method="post">
39
+ <label>Passphrase</label>
40
+ <input type="text" name="passphrase">
33
41
  <textarea name="commands">
34
42
  #{commands}
35
43
  </textarea>
@@ -41,91 +49,11 @@ EOF
41
49
 
42
50
  get '/test_result/:id' do
43
51
  test_result = CiInACan::TestResult.find(params[:id])
44
-
45
- CiInACan::WebContent.full_page_of(
46
- <<EOF
47
- <table class="table table-bordered">
48
- <tbody>
49
- <tr>
50
- <td>
51
- Id
52
- </td>
53
- <td>
54
- #{test_result.id}
55
- </td>
56
- </tr>
57
- <tr>
58
- <td>
59
- Repo
60
- </td>
61
- <td>
62
- <a href="/repo/#{test_result.repo}">
63
- #{test_result.repo}
64
- </a>
65
- </td>
66
- </tr>
67
- <tr>
68
- <td>
69
- Branch
70
- </td>
71
- <td>
72
- #{test_result.branch}
73
- </td>
74
- </tr>
75
- <tr>
76
- <td>
77
- Created At
78
- </td>
79
- <td>
80
- #{test_result.created_at.to_s}
81
- </td>
82
- </tr>
83
- <tr>
84
- <td>
85
- Passed
86
- </td>
87
- <td>
88
- #{test_result.passed ? 'Yes' : 'No'}
89
- </td>
90
- </tr>
91
- <tr>
92
- <td>
93
- Output
94
- </td>
95
- <td>
96
- #{test_result.output.to_s.gsub("\n", '<br />')}
97
- </td>
98
- </tr>
99
- </tbody>
100
- </table>
101
- EOF
102
- )
52
+ CiInACan::WebContent.full_page_of test_result.to_html
103
53
  end
104
54
 
105
55
  get '/' do
106
- run_html = CiInACan::LastRunList.all.map do |run|
107
- <<EOF
108
- <tr>
109
- <td>
110
- #{run.created_at}
111
- </td>
112
- <td>
113
- <a href="/repo/#{run.repo}">
114
- #{run.repo}
115
- </a>
116
- </td>
117
- <td>
118
- #{run.branch}
119
- </td>
120
- <td>
121
- #{run.passed ? 'Yes' : 'No'}
122
- </td>
123
- <td>
124
- <a href="/test_result/#{run.test_result_id}">#{run.sha}</a>
125
- </td>
126
- </tr>
127
- EOF
128
- end.join("\n")
56
+ run_html = CiInACan::Run.all.map { |r| r.to_html }.join("\n")
129
57
 
130
58
  CiInACan::WebContent.full_page_of(
131
59
  <<EOF
@@ -1,14 +1,19 @@
1
1
  module CiInACan
2
2
 
3
- module LastRunList
4
-
5
- class TestRunResult
6
- def initialize value
7
- @value = value
8
- end
9
- def method_missing(meth, *args, &blk)
10
- @value[meth]
11
- end
3
+ class Run
4
+
5
+ params_constructor
6
+
7
+ attr_accessor :created_at,
8
+ :test_result_id,
9
+ :passed,
10
+ :build_id,
11
+ :sha,
12
+ :repo,
13
+ :branch
14
+
15
+ def to_html
16
+ CiInACan::ViewModels::RunViewModel.new(self).to_html
12
17
  end
13
18
 
14
19
  def self.add build, test_result
@@ -26,7 +31,7 @@ module CiInACan
26
31
 
27
32
  def self.all
28
33
  blah = CiInACan::Persistence.hash_for("test_run_list")
29
- blah.sort_by { |x| x[0] }.reverse.map { |x| TestRunResult.new x[1] }
34
+ blah.sort_by { |x| x[0] }.reverse.map { |x| new x[1] }
30
35
  end
31
36
 
32
37
  end
@@ -34,6 +34,10 @@ module CiInACan
34
34
  created_at: created_at
35
35
  }.to_json
36
36
  end
37
+
38
+ def to_html
39
+ CiInACan::ViewModels::TestResultViewModel.new(self).to_html
40
+ end
37
41
 
38
42
  private
39
43
 
@@ -9,7 +9,7 @@ module CiInACan
9
9
  private
10
10
 
11
11
  def self.report_results_to_github build, test_result
12
- CiInACan::LastRunList.add build, test_result
12
+ CiInACan::Run.add build, test_result
13
13
  CiInACan::Github.report_complete_status_for build, test_result
14
14
  end
15
15
 
@@ -1,3 +1,3 @@
1
1
  module CiInACan
2
- VERSION = "0.0.25"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,35 @@
1
+ module CiInACan
2
+
3
+ module ViewModels
4
+
5
+ class RunViewModel < ViewModel
6
+
7
+ def to_html
8
+ <<EOF
9
+ <tr>
10
+ <td>
11
+ #{created_at}
12
+ </td>
13
+ <td>
14
+ <a href="/repo/#{repo}">
15
+ #{repo}
16
+ </a>
17
+ </td>
18
+ <td>
19
+ #{branch}
20
+ </td>
21
+ <td>
22
+ #{passed ? 'Yes' : 'No'}
23
+ </td>
24
+ <td>
25
+ <a href="/test_result/#{test_result_id}">#{sha}</a>
26
+ </td>
27
+ </tr>
28
+ EOF
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,70 @@
1
+ module CiInACan
2
+
3
+ module ViewModels
4
+
5
+ class TestResultViewModel < ViewModel
6
+
7
+ def to_html
8
+ <<EOF
9
+ <table class="table table-bordered">
10
+ <tbody>
11
+ <tr>
12
+ <td>
13
+ Id
14
+ </td>
15
+ <td>
16
+ #{id}
17
+ </td>
18
+ </tr>
19
+ <tr>
20
+ <td>
21
+ Repo
22
+ </td>
23
+ <td>
24
+ <a href="/repo/#{repo}">
25
+ #{repo}
26
+ </a>
27
+ </td>
28
+ </tr>
29
+ <tr>
30
+ <td>
31
+ Branch
32
+ </td>
33
+ <td>
34
+ #{branch}
35
+ </td>
36
+ </tr>
37
+ <tr>
38
+ <td>
39
+ Created At
40
+ </td>
41
+ <td>
42
+ #{created_at.to_s}
43
+ </td>
44
+ </tr>
45
+ <tr>
46
+ <td>
47
+ Passed
48
+ </td>
49
+ <td>
50
+ #{passed ? 'Yes' : 'No'}
51
+ </td>
52
+ </tr>
53
+ <tr>
54
+ <td>
55
+ Output
56
+ </td>
57
+ <td>
58
+ #{output.to_s.gsub("\n", '<br />')}
59
+ </td>
60
+ </tr>
61
+ </tbody>
62
+ </table>
63
+ EOF
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,19 @@
1
+ module CiInACan
2
+
3
+ module ViewModels
4
+
5
+ class ViewModel
6
+
7
+ def initialize value
8
+ @value = value
9
+ end
10
+
11
+ def method_missing(meth, *args, &blk)
12
+ @value.send(meth)
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -23,10 +23,18 @@ module CiInACan
23
23
 
24
24
  build = create_a_build_for(new_files.first, working_location)
25
25
 
26
+ delete new_files.first
27
+
26
28
  Runner.run build
29
+
27
30
  end
28
31
  end
29
32
 
33
+ def delete file
34
+ File.delete file
35
+ rescue
36
+ end
37
+
30
38
  def create_a_build_for file, working_location
31
39
  build = CiInACan::Build.parse File.read(file)
32
40
  build.id = UUID.new.generate
@@ -1,6 +1,6 @@
1
1
  require_relative '../spec_helper'
2
2
 
3
- describe CiInACan::LastRunList do
3
+ describe CiInACan::Run do
4
4
 
5
5
  before do
6
6
  clear_all_persisted_data
@@ -12,9 +12,9 @@ describe CiInACan::LastRunList do
12
12
  let(:test_result) { CiInACan::TestResult.new }
13
13
 
14
14
  it "should store information that we can pull out later" do
15
- CiInACan::LastRunList.add build, test_result
15
+ CiInACan::Run.add build, test_result
16
16
 
17
- results = CiInACan::LastRunList.all
17
+ results = CiInACan::Run.all
18
18
  results.count.must_equal 1
19
19
 
20
20
  results.first.created_at.must_equal test_result.created_at
@@ -24,9 +24,9 @@ describe CiInACan::LastRunList do
24
24
 
25
25
  test_result.id = UUID.new.generate
26
26
 
27
- CiInACan::LastRunList.add build, test_result
27
+ CiInACan::Run.add build, test_result
28
28
 
29
- results = CiInACan::LastRunList.all
29
+ results = CiInACan::Run.all
30
30
 
31
31
  results.first.test_result_id.must_equal test_result.id
32
32
 
@@ -34,32 +34,32 @@ describe CiInACan::LastRunList do
34
34
 
35
35
  it "should track the build id" do
36
36
  build.id = UUID.new.generate
37
- CiInACan::LastRunList.add build, test_result
38
- CiInACan::LastRunList.all.first.build_id.must_equal build.id
37
+ CiInACan::Run.add build, test_result
38
+ CiInACan::Run.all.first.build_id.must_equal build.id
39
39
  end
40
40
 
41
41
  it "should track the build id" do
42
42
  build.id = UUID.new.generate
43
- CiInACan::LastRunList.add build, test_result
44
- CiInACan::LastRunList.all.first.build_id.must_equal build.id
43
+ CiInACan::Run.add build, test_result
44
+ CiInACan::Run.all.first.build_id.must_equal build.id
45
45
  end
46
46
 
47
47
  it "should track the build sha" do
48
48
  build.sha = UUID.new.generate
49
- CiInACan::LastRunList.add build, test_result
50
- CiInACan::LastRunList.all.first.sha.must_equal build.sha
49
+ CiInACan::Run.add build, test_result
50
+ CiInACan::Run.all.first.sha.must_equal build.sha
51
51
  end
52
52
 
53
53
  it "should track the build repo" do
54
54
  build.repo = UUID.new.generate
55
- CiInACan::LastRunList.add build, test_result
56
- CiInACan::LastRunList.all.first.repo.must_equal build.repo
55
+ CiInACan::Run.add build, test_result
56
+ CiInACan::Run.all.first.repo.must_equal build.repo
57
57
  end
58
58
 
59
59
  it "should track the branch" do
60
60
  build.branch = UUID.new.generate
61
- CiInACan::LastRunList.add build, test_result
62
- CiInACan::LastRunList.all.first.branch.must_equal build.branch
61
+ CiInACan::Run.add build, test_result
62
+ CiInACan::Run.all.first.branch.must_equal build.branch
63
63
  end
64
64
 
65
65
  [true, false].each do |passed|
@@ -71,9 +71,9 @@ describe CiInACan::LastRunList do
71
71
  it "should track the test_result passed outcome" do
72
72
  test_result.passed = passed
73
73
 
74
- CiInACan::LastRunList.add build, test_result
74
+ CiInACan::Run.add build, test_result
75
75
 
76
- results = CiInACan::LastRunList.all
76
+ results = CiInACan::Run.all
77
77
 
78
78
  results.first.passed.must_equal passed
79
79
  end
@@ -94,9 +94,9 @@ describe CiInACan::LastRunList do
94
94
  CiInACan::TestResult.new(id: 'a', created_at: Time.parse('1/1/2014')),
95
95
  CiInACan::TestResult.new(id: 'b', created_at: Time.parse('1/2/2014'))]
96
96
 
97
- test_results.each { |t| CiInACan::LastRunList.add build, t }
97
+ test_results.each { |t| CiInACan::Run.add build, t }
98
98
 
99
- results = CiInACan::LastRunList.all
99
+ results = CiInACan::Run.all
100
100
  results[0].test_result_id.must_equal 'c'
101
101
  results[1].test_result_id.must_equal 'b'
102
102
  results[2].test_result_id.must_equal 'a'
@@ -106,4 +106,22 @@ describe CiInACan::LastRunList do
106
106
 
107
107
  end
108
108
 
109
+ describe "to html" do
110
+ it "should show the results of run_view_model" do
111
+ html = Object.new
112
+ view_model = Object.new
113
+
114
+ run = CiInACan::Run.new
115
+
116
+ view_model.stubs(:to_html).returns html
117
+
118
+ CiInACan::ViewModels::RunViewModel
119
+ .stubs(:new)
120
+ .with(run)
121
+ .returns view_model
122
+
123
+ run.to_html.must_be_same_as html
124
+ end
125
+ end
126
+
109
127
  end
@@ -8,7 +8,7 @@ describe CiInACan::TestResultNotifier do
8
8
  let(:test_result) { CiInACan::TestResult.new }
9
9
 
10
10
  before do
11
- CiInACan::LastRunList.stubs(:add)
11
+ CiInACan::Run.stubs(:add)
12
12
  CiInACan::Github.stubs(:report_complete_status_for)
13
13
  end
14
14
 
@@ -25,7 +25,7 @@ describe CiInACan::TestResultNotifier do
25
25
  end
26
26
 
27
27
  it "should add the build and test result to the last run list" do
28
- CiInACan::LastRunList.expects(:add).with build, test_result
28
+ CiInACan::Run.expects(:add).with build, test_result
29
29
  CiInACan::TestResultNotifier.send_for build, test_result
30
30
  end
31
31
 
@@ -95,4 +95,22 @@ describe CiInACan::TestRunner do
95
95
 
96
96
  end
97
97
 
98
+ describe "to html" do
99
+ it "should show the results of test_result_view_model" do
100
+ html = Object.new
101
+ view_model = Object.new
102
+
103
+ test_result = CiInACan::TestResult.new
104
+
105
+ view_model.stubs(:to_html).returns html
106
+
107
+ CiInACan::ViewModels::TestResultViewModel
108
+ .stubs(:new)
109
+ .with(test_result)
110
+ .returns view_model
111
+
112
+ test_result.to_html.must_be_same_as html
113
+ end
114
+ end
115
+
98
116
  end
@@ -0,0 +1,20 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ describe CiInACan::ViewModels::ViewModel do
4
+
5
+ it "should serve as a wrapper around data provided to it" do
6
+
7
+ stub = Object.new
8
+ stub.stubs(:one).returns Object.new
9
+ stub.stubs(:two).returns Object.new
10
+ stub.stubs(:three).returns Object.new
11
+
12
+ view_model = CiInACan::ViewModels::ViewModel.new stub
13
+
14
+ view_model.one.must_be_same_as stub.one
15
+ view_model.two.must_be_same_as stub.two
16
+ view_model.three.must_be_same_as stub.three
17
+
18
+ end
19
+
20
+ end
@@ -2,6 +2,10 @@ require_relative '../spec_helper'
2
2
 
3
3
  describe CiInACan::Watcher do
4
4
 
5
+ before do
6
+ File.stubs(:delete)
7
+ end
8
+
5
9
  describe "watch" do
6
10
 
7
11
  let(:watching_location) { Object.new }
@@ -103,6 +107,61 @@ describe CiInACan::Watcher do
103
107
  CiInACan::Watcher.send(:build_callback, test.working_location).call [], [added_file], []
104
108
  end
105
109
 
110
+ it "should delete the file" do
111
+
112
+ CiInACan::Runner.stubs(:wl).returns test.working_location
113
+
114
+ uuid = Object.new
115
+ uuid.stubs(:generate).returns test.random_string
116
+ UUID.stubs(:new).returns uuid
117
+ CiInACan::Runner.stubs(:run)
118
+
119
+ File.expects(:delete).with added_file
120
+
121
+ CiInACan::Watcher.send(:build_callback, test.working_location).call [], [added_file], []
122
+ end
123
+
124
+ it "should not delete the file before it is read" do
125
+
126
+ content = Object.new
127
+ CiInACan::Runner.stubs(:wl).returns test.working_location
128
+
129
+ uuid = Object.new
130
+ uuid.stubs(:generate).returns test.random_string
131
+ UUID.stubs(:new).returns uuid
132
+ CiInACan::Runner.stubs(:run)
133
+
134
+ CiInACan::Build.stubs(:parse).with(content).returns build
135
+
136
+ delete_called = false
137
+ File.expects(:read).with do |added_file|
138
+ delete_called.must_equal false
139
+ true
140
+ end.returns content
141
+
142
+ File.expects(:delete).with do |added_file|
143
+ delete_called = true
144
+ true
145
+ end
146
+
147
+ CiInACan::Watcher.send(:build_callback, test.working_location).call [], [added_file], []
148
+ end
149
+
150
+ it "should not let an error in deleting a file bubble up" do
151
+
152
+ CiInACan::Runner.stubs(:wl).returns test.working_location
153
+
154
+ uuid = Object.new
155
+ uuid.stubs(:generate).returns test.random_string
156
+ UUID.stubs(:new).returns uuid
157
+ CiInACan::Runner.stubs(:run)
158
+
159
+ File.stubs(:delete).raises 'k'
160
+
161
+ # this should not throw
162
+ CiInACan::Watcher.send(:build_callback, test.working_location).call [], [added_file], []
163
+ end
164
+
106
165
  end
107
166
 
108
167
  end
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  require_relative '../lib/ci_in_a_can'
2
2
  require 'minitest/autorun'
3
3
  require 'minitest/spec'
4
- require 'minitest/pride'
5
4
  require 'subtle'
6
5
  require 'contrast'
7
6
  require 'timecop'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ci_in_a_can
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.25
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Darren Cauthon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-16 00:00:00.000000000 Z
11
+ date: 2014-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -231,13 +231,16 @@ files:
231
231
  - lib/ci_in_a_can/daemon.rb
232
232
  - lib/ci_in_a_can/github.rb
233
233
  - lib/ci_in_a_can/github_build_parser.rb
234
- - lib/ci_in_a_can/last_run_list.rb
235
234
  - lib/ci_in_a_can/persistence.rb
235
+ - lib/ci_in_a_can/run.rb
236
236
  - lib/ci_in_a_can/runner.rb
237
237
  - lib/ci_in_a_can/test_result.rb
238
238
  - lib/ci_in_a_can/test_result_notifier.rb
239
239
  - lib/ci_in_a_can/test_runner.rb
240
240
  - lib/ci_in_a_can/version.rb
241
+ - lib/ci_in_a_can/view_models/run_view_model.rb
242
+ - lib/ci_in_a_can/view_models/test_result_view_model.rb
243
+ - lib/ci_in_a_can/view_models/view_model.rb
241
244
  - lib/ci_in_a_can/watcher.rb
242
245
  - lib/ci_in_a_can/web_content.rb
243
246
  - sample.json
@@ -248,11 +251,12 @@ files:
248
251
  - spec/ci_in_a_can/cloner_spec.rb
249
252
  - spec/ci_in_a_can/github_build_parser_spec.rb
250
253
  - spec/ci_in_a_can/github_spec.rb
251
- - spec/ci_in_a_can/last_run_list_spec.rb
254
+ - spec/ci_in_a_can/run_spec.rb
252
255
  - spec/ci_in_a_can/runner_spec.rb
253
256
  - spec/ci_in_a_can/test_result_notifier_spec.rb
254
257
  - spec/ci_in_a_can/test_result_spec.rb
255
258
  - spec/ci_in_a_can/test_runner_spec.rb
259
+ - spec/ci_in_a_can/view_models/view_model_spec.rb
256
260
  - spec/ci_in_a_can/watcher_spec.rb
257
261
  - spec/spec_helper.rb
258
262
  - spec/temp/.gitkeep
@@ -288,11 +292,12 @@ test_files:
288
292
  - spec/ci_in_a_can/cloner_spec.rb
289
293
  - spec/ci_in_a_can/github_build_parser_spec.rb
290
294
  - spec/ci_in_a_can/github_spec.rb
291
- - spec/ci_in_a_can/last_run_list_spec.rb
295
+ - spec/ci_in_a_can/run_spec.rb
292
296
  - spec/ci_in_a_can/runner_spec.rb
293
297
  - spec/ci_in_a_can/test_result_notifier_spec.rb
294
298
  - spec/ci_in_a_can/test_result_spec.rb
295
299
  - spec/ci_in_a_can/test_runner_spec.rb
300
+ - spec/ci_in_a_can/view_models/view_model_spec.rb
296
301
  - spec/ci_in_a_can/watcher_spec.rb
297
302
  - spec/spec_helper.rb
298
303
  - spec/temp/.gitkeep