chef-telemetry 0.1.8 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/chef/telemeter.rb +162 -0
- data/lib/chef/telemeter/sender.rb +125 -0
- data/lib/chef/telemetry.rb +45 -0
- data/lib/chef/telemetry/client.rb +26 -0
- data/lib/chef/telemetry/decision.rb +67 -0
- data/lib/chef/telemetry/event.rb +44 -0
- data/lib/chef/telemetry/session.rb +39 -0
- data/lib/chef/telemetry/version.rb +5 -0
- metadata +11 -10
- data/lib/telemetry.rb +0 -43
- data/lib/telemetry/client.rb +0 -24
- data/lib/telemetry/decision.rb +0 -64
- data/lib/telemetry/event.rb +0 -42
- data/lib/telemetry/session.rb +0 -37
- data/lib/telemetry/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: faf29c758b1dbcddd569962312d55a7f9b7648ac2fed2896a962360f62d50b93
|
4
|
+
data.tar.gz: '079575327ecc06c86e9fb305cd4e0b6339ea443ad53800006f74baa710d9f266'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 35a60bfa480f9085eac39bd3f1699ab2926f1c270e947439c13bfbc585e97849b5cb155bf8eaadef41496fc0a62c13867dc25b3380ece12d1460e84fee8794aa
|
7
|
+
data.tar.gz: 4bd4017b173ba7a50f5888550c16e1b0616c0a2b6176ba719513bad27ece43ce32dca9208ad7a891e519e3d957e64c26ce8ef56e20ca2eace83383c743b81592
|
@@ -0,0 +1,162 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2018 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require "chef/telemetry/version"
|
19
|
+
require "benchmark"
|
20
|
+
require "forwardable"
|
21
|
+
require "singleton"
|
22
|
+
require "json"
|
23
|
+
require "digest/sha1"
|
24
|
+
require "securerandom"
|
25
|
+
require "yaml"
|
26
|
+
|
27
|
+
module Chef
|
28
|
+
# This definites the Telemeter interface. Implementation thoughts for
|
29
|
+
# when we unstub it:
|
30
|
+
# - let's track the call sequence; most of our calls will be nested inside
|
31
|
+
# a main 'timed_capture', and it would be good to see ordering within nested calls.
|
32
|
+
class Telemeter
|
33
|
+
include Singleton
|
34
|
+
DEFAULT_INSTALLATION_GUID = "00000000-0000-0000-0000-000000000000".freeze
|
35
|
+
|
36
|
+
class << self
|
37
|
+
extend Forwardable
|
38
|
+
def_delegators :instance, :setup, :timed_capture, :capture, :commit, :timed_run_capture
|
39
|
+
def_delegators :instance, :pending_event_count, :last_event, :enabled?
|
40
|
+
def_delegators :instance, :make_event_payload, :config
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :events_to_send, :run_timestamp, :config
|
44
|
+
|
45
|
+
def setup(config)
|
46
|
+
# TODO validate required & correct keys
|
47
|
+
# :payload_dir #required
|
48
|
+
# :session_file # required
|
49
|
+
# :installation_identifier_file # required
|
50
|
+
# :enabled # false, not required
|
51
|
+
# :dev_mode # false, not required
|
52
|
+
config[:dev_mode] ||= false
|
53
|
+
config[:enabled] ||= false
|
54
|
+
require "chef_core/telemeter/sender"
|
55
|
+
@config = config
|
56
|
+
Sender.start_upload_thread(config)
|
57
|
+
end
|
58
|
+
|
59
|
+
def enabled?
|
60
|
+
require "chef/telemetry/decision"
|
61
|
+
config[:enabled] && !Telemetry::Decision.env_opt_out?
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize
|
65
|
+
@events_to_send = []
|
66
|
+
@run_timestamp = Time.now.utc.strftime("%FT%TZ")
|
67
|
+
end
|
68
|
+
|
69
|
+
def timed_run_capture(arguments, &block)
|
70
|
+
timed_capture(:run, arguments: arguments, &block)
|
71
|
+
end
|
72
|
+
|
73
|
+
def timed_capture(name, data = {}, options = {})
|
74
|
+
time = Benchmark.measure { yield }
|
75
|
+
data[:duration] = time.real
|
76
|
+
capture(name, data, options)
|
77
|
+
end
|
78
|
+
|
79
|
+
def capture(name, data = {}, options = {})
|
80
|
+
# Adding it to the head of the list will ensure that the
|
81
|
+
# sequence of events is preserved when we send the final payload
|
82
|
+
payload = make_event_payload(name, data, options)
|
83
|
+
@events_to_send.unshift payload
|
84
|
+
end
|
85
|
+
|
86
|
+
def commit
|
87
|
+
if enabled?
|
88
|
+
session = convert_events_to_session
|
89
|
+
write_session(session)
|
90
|
+
end
|
91
|
+
@events_to_send = []
|
92
|
+
end
|
93
|
+
|
94
|
+
def make_event_payload(name, data, options = {})
|
95
|
+
payload = {
|
96
|
+
event: name,
|
97
|
+
properties: {
|
98
|
+
installation_id: installation_id,
|
99
|
+
run_timestamp: run_timestamp,
|
100
|
+
host_platform: host_platform,
|
101
|
+
},
|
102
|
+
}
|
103
|
+
if options[:flatten]
|
104
|
+
payload[:properties].merge! data
|
105
|
+
else
|
106
|
+
payload[:properties][:event_data] = data
|
107
|
+
end
|
108
|
+
payload
|
109
|
+
end
|
110
|
+
|
111
|
+
def installation_id
|
112
|
+
@installation_id ||=
|
113
|
+
begin
|
114
|
+
File.read(config[:installation_identifier_file]).chomp
|
115
|
+
rescue
|
116
|
+
Telemeter::Log.info "could not read #{config[:installation_identifier_file]} - using default id"
|
117
|
+
DEFAULT_INSTALLATION_GUID
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# For testing.
|
122
|
+
def pending_event_count
|
123
|
+
@events_to_send.length
|
124
|
+
end
|
125
|
+
|
126
|
+
def last_event
|
127
|
+
@events_to_send.last
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def host_platform
|
133
|
+
@host_platform ||= case RUBY_PLATFORM
|
134
|
+
when /mswin|mingw|windows/
|
135
|
+
"windows"
|
136
|
+
else
|
137
|
+
RUBY_PLATFORM.split("-")[1]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def convert_events_to_session
|
142
|
+
YAML.dump("version" => Telemetry::VERSION,
|
143
|
+
"entries" => @events_to_send)
|
144
|
+
end
|
145
|
+
|
146
|
+
def write_session(session)
|
147
|
+
File.write(next_filename, convert_events_to_session)
|
148
|
+
end
|
149
|
+
|
150
|
+
def next_filename
|
151
|
+
id = 0
|
152
|
+
filename = ""
|
153
|
+
loop do
|
154
|
+
id += 1
|
155
|
+
filename = File.join(config[:payload_dir], "telemetry-payload-#{id}.yml")
|
156
|
+
break unless File.exist?(filename)
|
157
|
+
end
|
158
|
+
filename
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
end # Chef
|
@@ -0,0 +1,125 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2018 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require "chef/telemetry"
|
19
|
+
require "chef/telemeter"
|
20
|
+
require "logger"
|
21
|
+
|
22
|
+
module Chef
|
23
|
+
class Telemeter
|
24
|
+
logger = ::Logger.new(STDERR) # TODO: maybe switch to file, maybe switch to mixlib
|
25
|
+
logger.level = Logger::ERROR
|
26
|
+
Log = logger # rubocop:disable Naming/ConstantName
|
27
|
+
|
28
|
+
class Sender
|
29
|
+
attr_reader :session_files, :config
|
30
|
+
|
31
|
+
def self.start_upload_thread(config)
|
32
|
+
# Find the files before we spawn the thread - otherwise
|
33
|
+
# we may accidentally pick up the current run's session file if it
|
34
|
+
# finishes before the thread scans for new files
|
35
|
+
session_files = Sender.find_session_files(config)
|
36
|
+
sender = Sender.new(session_files, config)
|
37
|
+
Thread.new { sender.run }
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.find_session_files(config)
|
41
|
+
Telemeter::Log.info("Looking for telemetry data to submit")
|
42
|
+
session_search = File.join(config[:payload_dir], "telemetry-payload-*.yml")
|
43
|
+
session_files = Dir.glob(session_search)
|
44
|
+
Telemeter::Log.info("Found #{session_files.length} sessions to submit")
|
45
|
+
session_files
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(session_files, config)
|
49
|
+
@session_files = session_files
|
50
|
+
@config = config
|
51
|
+
end
|
52
|
+
|
53
|
+
def run
|
54
|
+
if Telemeter.enabled?
|
55
|
+
Telemeter::Log.info("Telemetry enabled, beginning upload of previous session(s)")
|
56
|
+
# dev mode telemetry gets sent to a different location
|
57
|
+
|
58
|
+
if config[:dev_mode]
|
59
|
+
ENV["CHEF_TELEMETRY_ENDPOINT"] ||= "https://telemetry-acceptance.chef.io"
|
60
|
+
end
|
61
|
+
session_files.each { |path| process_session(path) }
|
62
|
+
else
|
63
|
+
# If telemetry is not enabled, just clean up and return. Even though
|
64
|
+
# the telemetry gem will not send if disabled, log output saying that we're submitting
|
65
|
+
# it when it has been disabled can be alarming.
|
66
|
+
Telemeter::Log.info("Telemetry disabled, clearing any existing session captures without sending them.")
|
67
|
+
session_files.each { |path| FileUtils.rm_rf(path) }
|
68
|
+
end
|
69
|
+
FileUtils.rm_rf(config[:session_file])
|
70
|
+
Telemeter::Log.info("Terminating, nothing more to do.")
|
71
|
+
rescue => e
|
72
|
+
Telemeter::Log.fatal "Sender thread aborted: '#{e}' failed at #{e.backtrace[0]}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def process_session(path)
|
76
|
+
Telemeter::Log.info("Processing telemetry entries from #{path}")
|
77
|
+
content = load_and_clear_session(path)
|
78
|
+
submit_session(content)
|
79
|
+
end
|
80
|
+
|
81
|
+
def submit_session(content)
|
82
|
+
# Each file contains the actions taken within a single run of the chef tool.
|
83
|
+
# Each run is one session, so we'll first remove remove the session file
|
84
|
+
# to force creating a new one.
|
85
|
+
FileUtils.rm_rf(config[:session_file])
|
86
|
+
# We'll use the version captured in the sesion file
|
87
|
+
entries = content["entries"]
|
88
|
+
total = entries.length
|
89
|
+
product_info = config[:product] || {}
|
90
|
+
telemetry = Telemetry.new(product: product_info[:name] || "chef-workstation",
|
91
|
+
origin: product_info[:origin] || "command-line",
|
92
|
+
product_version: product_info[:version] || content["version"],
|
93
|
+
install_context: product_info[:install_context] || "omnibus")
|
94
|
+
total = entries.length
|
95
|
+
entries.each_with_index do |entry, x|
|
96
|
+
submit_entry(telemetry, entry, x + 1, total)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def submit_entry(telemetry, entry, sequence, total)
|
101
|
+
Telemeter::Log.info("Submitting telemetry entry #{sequence}/#{total}: #{entry} ")
|
102
|
+
telemetry.deliver(entry)
|
103
|
+
Telemeter::Log.info("Entry #{sequence}/#{total} submitted.")
|
104
|
+
rescue => e
|
105
|
+
# No error handling in telemetry lib, so at least track the failrue
|
106
|
+
Telemeter::Log.error("Failed to send entry #{sequence}/#{total}: #{e}")
|
107
|
+
Telemeter::Log.error("Backtrace: #{e.backtrace} ")
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def load_and_clear_session(path)
|
113
|
+
content = File.read(path)
|
114
|
+
# We'll remove it now instead of after we parse or submit it -
|
115
|
+
# if we fail to deliver, we don't want to be stuck resubmitting it if the problem
|
116
|
+
# was due to payload. This is a trade-off - if we get a transient error, the
|
117
|
+
# payload will be lost.
|
118
|
+
# TODO: Improve error handling so we can intelligently decide whether to
|
119
|
+
# retry a failed load or failed submit.
|
120
|
+
FileUtils.rm_rf(path)
|
121
|
+
YAML.load(content)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end # Chef
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "chef/telemetry/client"
|
2
|
+
require "chef/telemetry/decision"
|
3
|
+
require "chef/telemetry/event"
|
4
|
+
require "chef/telemetry/session"
|
5
|
+
require "chef/telemetry/version"
|
6
|
+
|
7
|
+
module Chef
|
8
|
+
class Telemetry
|
9
|
+
attr_accessor :product, :origin, :product_version, :install_context
|
10
|
+
def initialize(product: nil, origin: "command-line",
|
11
|
+
product_version: "0.0.0",
|
12
|
+
install_context: "omnibus")
|
13
|
+
# Reference: https://github.com/chef/es-telemetry-pipeline/blob/0730c1e2605624a50d34bab6d036b73c31e0ab0e/schema/event.schema.json#L77
|
14
|
+
@product = product
|
15
|
+
@origin = origin
|
16
|
+
@product_version = product_version
|
17
|
+
@install_context = install_context # Valid: habitat, omnibus
|
18
|
+
end
|
19
|
+
|
20
|
+
def deliver(data = {})
|
21
|
+
unless opt_out?
|
22
|
+
payload = event.prepare(data)
|
23
|
+
client.await.fire(payload)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def event
|
28
|
+
@event ||= Event.new(product, session, origin,
|
29
|
+
install_context, product_version)
|
30
|
+
end
|
31
|
+
|
32
|
+
def session
|
33
|
+
@session ||= Session.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def opt_out?
|
37
|
+
@opt_out ||= Decision.opt_out?
|
38
|
+
end
|
39
|
+
|
40
|
+
def client
|
41
|
+
endpoint = ENV.fetch("CHEF_TELEMETRY_ENDPOINT", Client::TELEMETRY_ENDPOINT)
|
42
|
+
@client ||= Client.new(endpoint)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end # Chef
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "http"
|
2
|
+
require "concurrent"
|
3
|
+
|
4
|
+
module Chef
|
5
|
+
class Telemetry
|
6
|
+
class Client
|
7
|
+
include Concurrent::Async
|
8
|
+
|
9
|
+
TELEMETRY_ENDPOINT = "https://telemetry.chef.io".freeze
|
10
|
+
|
11
|
+
attr_reader :http
|
12
|
+
def initialize(endpoint = TELEMETRY_ENDPOINT)
|
13
|
+
super()
|
14
|
+
@http = HTTP.persistent(endpoint)
|
15
|
+
end
|
16
|
+
|
17
|
+
def fire(event)
|
18
|
+
http.post("/events", json: event).flush
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class OptOutClient
|
23
|
+
def fire(_); end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require "chef-config/path_helper"
|
2
|
+
require "chef-config/windows"
|
3
|
+
|
4
|
+
# Decision allows us to inspect whether the user has made a decision to opt in or opt out of telemetry.
|
5
|
+
module Chef
|
6
|
+
class Telemetry
|
7
|
+
module Decision
|
8
|
+
OPT_OUT_FILE = "telemetry_opt_out".freeze
|
9
|
+
OPT_IN_FILE = "telemetry_opt_in".freeze
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def opt_out?
|
13
|
+
# We check that the user has made a decision so that we can have a default setting for robots
|
14
|
+
user_opted_out? || env_opt_out? || local_opt_out? || !made?
|
15
|
+
end
|
16
|
+
|
17
|
+
# Check whether the user has made an explicit decision on their participation.
|
18
|
+
def made?
|
19
|
+
user_opted_in? || user_opted_out?
|
20
|
+
end
|
21
|
+
|
22
|
+
def user_opted_out?
|
23
|
+
File.exist?(File.join(home, OPT_OUT_FILE))
|
24
|
+
end
|
25
|
+
|
26
|
+
def user_opted_in?
|
27
|
+
File.exist?(File.join(home, OPT_IN_FILE))
|
28
|
+
end
|
29
|
+
|
30
|
+
def env_opt_out?
|
31
|
+
ENV.key?("CHEF_TELEMETRY_OPT_OUT")
|
32
|
+
end
|
33
|
+
|
34
|
+
def local_opt_out?
|
35
|
+
found = false
|
36
|
+
full_path = working_directory.split(File::SEPARATOR)
|
37
|
+
(full_path.length - 1).downto(0) do |i|
|
38
|
+
candidate = File.join(full_path[0..i], ".chef", OPT_OUT_FILE)
|
39
|
+
if File.exist?(candidate)
|
40
|
+
# TODO: push up logging
|
41
|
+
# Log.info "Found opt out at: #{candidate}"
|
42
|
+
found = true
|
43
|
+
break
|
44
|
+
end
|
45
|
+
end
|
46
|
+
found
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def working_directory
|
52
|
+
a = if ChefConfig.windows?
|
53
|
+
ENV["CD"]
|
54
|
+
else
|
55
|
+
ENV["PWD"]
|
56
|
+
end || Dir.pwd
|
57
|
+
|
58
|
+
a
|
59
|
+
end
|
60
|
+
|
61
|
+
def home
|
62
|
+
ChefConfig::PathHelper.home(".chef")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end # Chef
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Chef
|
2
|
+
class Telemetry
|
3
|
+
class Event
|
4
|
+
|
5
|
+
SKELETON = {
|
6
|
+
instance_id: "00000000-0000-0000-0000-000000000000",
|
7
|
+
message_version: 1.0,
|
8
|
+
payload_version: 1.0,
|
9
|
+
license_id: "00000000-0000-0000-0000-000000000000",
|
10
|
+
type: "track",
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
attr_reader :session, :product, :origin,
|
14
|
+
:product_version, :install_context
|
15
|
+
def initialize(product, session, origin = "command-line",
|
16
|
+
install_context = "omnibus", product_version = "0.0.0")
|
17
|
+
@product = product
|
18
|
+
@session = session
|
19
|
+
@origin = origin
|
20
|
+
@product_version = product_version
|
21
|
+
@install_context = install_context
|
22
|
+
end
|
23
|
+
|
24
|
+
def prepare(event)
|
25
|
+
time = timestamp
|
26
|
+
event[:properties][:timestamp] = time
|
27
|
+
body = SKELETON.dup
|
28
|
+
body.tap do |b|
|
29
|
+
b[:session_id] = session.id
|
30
|
+
b[:origin] = origin
|
31
|
+
b[:product] = product
|
32
|
+
b[:product_version] = product_version
|
33
|
+
b[:install_context] = install_context
|
34
|
+
b[:timestamp] = time
|
35
|
+
b[:payload] = event
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def timestamp
|
40
|
+
Time.now.utc.strftime("%FT%TZ")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
require "chef-config/path_helper"
|
3
|
+
|
4
|
+
module Chef
|
5
|
+
class Telemetry
|
6
|
+
class Session
|
7
|
+
def initialize
|
8
|
+
@id = if live_session?
|
9
|
+
File.read(session_file).chomp
|
10
|
+
else
|
11
|
+
new_session
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def id
|
16
|
+
FileUtils.touch(session_file)
|
17
|
+
@id
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def live_session?
|
23
|
+
expiry = Time.now - 600
|
24
|
+
File.file?(session_file) && File.stat(session_file).mtime > expiry
|
25
|
+
end
|
26
|
+
|
27
|
+
def session_file
|
28
|
+
File.join(ChefConfig::PathHelper.home(".chef"), "TELEMETRY_SESSION_ID").freeze
|
29
|
+
end
|
30
|
+
|
31
|
+
def new_session
|
32
|
+
id = SecureRandom.uuid
|
33
|
+
FileUtils.mkdir_p(File.dirname(session_file))
|
34
|
+
File.open(session_file, "w") { |f| f.write(id) }
|
35
|
+
id
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end # Chef
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chef-telemetry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chef Software, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-12-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -87,12 +87,14 @@ extensions: []
|
|
87
87
|
extra_rdoc_files: []
|
88
88
|
files:
|
89
89
|
- LICENSE
|
90
|
-
- lib/
|
91
|
-
- lib/
|
92
|
-
- lib/telemetry
|
93
|
-
- lib/telemetry/
|
94
|
-
- lib/telemetry/
|
95
|
-
- lib/telemetry/
|
90
|
+
- lib/chef/telemeter.rb
|
91
|
+
- lib/chef/telemeter/sender.rb
|
92
|
+
- lib/chef/telemetry.rb
|
93
|
+
- lib/chef/telemetry/client.rb
|
94
|
+
- lib/chef/telemetry/decision.rb
|
95
|
+
- lib/chef/telemetry/event.rb
|
96
|
+
- lib/chef/telemetry/session.rb
|
97
|
+
- lib/chef/telemetry/version.rb
|
96
98
|
homepage: https://github.com/chef/chef-telemetry
|
97
99
|
licenses:
|
98
100
|
- Apache-2.0
|
@@ -112,8 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
114
|
- !ruby/object:Gem::Version
|
113
115
|
version: '0'
|
114
116
|
requirements: []
|
115
|
-
|
116
|
-
rubygems_version: 2.7.6
|
117
|
+
rubygems_version: 3.0.3
|
117
118
|
signing_key:
|
118
119
|
specification_version: 4
|
119
120
|
summary: Send user actions to the Chef telemetry system. See Chef RFC-051 for further
|
data/lib/telemetry.rb
DELETED
@@ -1,43 +0,0 @@
|
|
1
|
-
require "telemetry/client"
|
2
|
-
require "telemetry/decision"
|
3
|
-
require "telemetry/event"
|
4
|
-
require "telemetry/session"
|
5
|
-
require "telemetry/version"
|
6
|
-
|
7
|
-
class Telemetry
|
8
|
-
attr_accessor :product, :origin, :product_version, :install_context
|
9
|
-
def initialize(product: nil, origin: "command-line",
|
10
|
-
product_version: "0.0.0",
|
11
|
-
install_context: "omnibus")
|
12
|
-
# Reference: https://github.com/chef/es-telemetry-pipeline/blob/0730c1e2605624a50d34bab6d036b73c31e0ab0e/schema/event.schema.json#L77
|
13
|
-
@product = product
|
14
|
-
@origin = origin
|
15
|
-
@product_version = product_version
|
16
|
-
@install_context = install_context # Valid: habitat, omnibus
|
17
|
-
end
|
18
|
-
|
19
|
-
def deliver(data = {})
|
20
|
-
unless opt_out?
|
21
|
-
payload = event.prepare(data)
|
22
|
-
client.await.fire(payload)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def event
|
27
|
-
@event ||= Event.new(product, session, origin,
|
28
|
-
install_context, product_version)
|
29
|
-
end
|
30
|
-
|
31
|
-
def session
|
32
|
-
@session ||= Session.new
|
33
|
-
end
|
34
|
-
|
35
|
-
def opt_out?
|
36
|
-
@opt_out ||= Decision.opt_out?
|
37
|
-
end
|
38
|
-
|
39
|
-
def client
|
40
|
-
endpoint = ENV.fetch("CHEF_TELEMETRY_ENDPOINT", Client::TELEMETRY_ENDPOINT)
|
41
|
-
@client ||= Client.new(endpoint)
|
42
|
-
end
|
43
|
-
end
|
data/lib/telemetry/client.rb
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
require "http"
|
2
|
-
require "concurrent"
|
3
|
-
|
4
|
-
class Telemetry
|
5
|
-
class Client
|
6
|
-
include Concurrent::Async
|
7
|
-
|
8
|
-
TELEMETRY_ENDPOINT = "https://telemetry.chef.io".freeze
|
9
|
-
|
10
|
-
attr_reader :http
|
11
|
-
def initialize(endpoint = TELEMETRY_ENDPOINT)
|
12
|
-
super()
|
13
|
-
@http = HTTP.persistent(endpoint)
|
14
|
-
end
|
15
|
-
|
16
|
-
def fire(event)
|
17
|
-
http.post("/events", json: event).flush
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
class OptOutClient
|
22
|
-
def fire(_); end
|
23
|
-
end
|
24
|
-
end
|
data/lib/telemetry/decision.rb
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
require "chef-config/path_helper"
|
2
|
-
require "chef-config/windows"
|
3
|
-
|
4
|
-
# Decision allows us to inspect whether the user has made a decision to opt in or opt out of telemetry.
|
5
|
-
class Telemetry
|
6
|
-
module Decision
|
7
|
-
OPT_OUT_FILE = "telemetry_opt_out".freeze
|
8
|
-
OPT_IN_FILE = "telemetry_opt_in".freeze
|
9
|
-
|
10
|
-
class << self
|
11
|
-
def opt_out?
|
12
|
-
# We check that the user has made a decision so that we can have a default setting for robots
|
13
|
-
user_opted_out? || env_opt_out? || local_opt_out? || !made?
|
14
|
-
end
|
15
|
-
|
16
|
-
# Check whether the user has made an explicit decision on their participation.
|
17
|
-
def made?
|
18
|
-
user_opted_in? || user_opted_out?
|
19
|
-
end
|
20
|
-
|
21
|
-
def user_opted_out?
|
22
|
-
File.exist?(File.join(home, OPT_OUT_FILE))
|
23
|
-
end
|
24
|
-
|
25
|
-
def user_opted_in?
|
26
|
-
File.exist?(File.join(home, OPT_IN_FILE))
|
27
|
-
end
|
28
|
-
|
29
|
-
def env_opt_out?
|
30
|
-
ENV.key?("CHEF_TELEMETRY_OPT_OUT")
|
31
|
-
end
|
32
|
-
|
33
|
-
def local_opt_out?
|
34
|
-
found = false
|
35
|
-
full_path = working_directory.split(File::SEPARATOR)
|
36
|
-
(full_path.length - 1).downto(0) do |i|
|
37
|
-
candidate = File.join(full_path[0..i], ".chef", OPT_OUT_FILE)
|
38
|
-
if File.exist?(candidate)
|
39
|
-
puts "Found opt out at: #{candidate}"
|
40
|
-
found = true
|
41
|
-
break
|
42
|
-
end
|
43
|
-
end
|
44
|
-
found
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def working_directory
|
50
|
-
a = if ChefConfig.windows?
|
51
|
-
ENV["CD"]
|
52
|
-
else
|
53
|
-
ENV["PWD"]
|
54
|
-
end || Dir.pwd
|
55
|
-
|
56
|
-
a
|
57
|
-
end
|
58
|
-
|
59
|
-
def home
|
60
|
-
ChefConfig::PathHelper.home(".chef")
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
data/lib/telemetry/event.rb
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
class Telemetry
|
2
|
-
class Event
|
3
|
-
|
4
|
-
SKELETON = {
|
5
|
-
instance_id: "00000000-0000-0000-0000-000000000000",
|
6
|
-
message_version: 1.0,
|
7
|
-
payload_version: 1.0,
|
8
|
-
license_id: "00000000-0000-0000-0000-000000000000",
|
9
|
-
type: "track",
|
10
|
-
}.freeze
|
11
|
-
|
12
|
-
attr_reader :session, :product, :origin,
|
13
|
-
:product_version, :install_context
|
14
|
-
def initialize(product, session, origin = "command-line",
|
15
|
-
install_context = "omnibus", product_version = "0.0.0")
|
16
|
-
@product = product
|
17
|
-
@session = session
|
18
|
-
@origin = origin
|
19
|
-
@product_version = product_version
|
20
|
-
@install_context = install_context
|
21
|
-
end
|
22
|
-
|
23
|
-
def prepare(event)
|
24
|
-
time = timestamp
|
25
|
-
event[:properties][:timestamp] = time
|
26
|
-
body = SKELETON.dup
|
27
|
-
body.tap do |b|
|
28
|
-
b[:session_id] = session.id
|
29
|
-
b[:origin] = origin
|
30
|
-
b[:product] = product
|
31
|
-
b[:product_version] = product_version
|
32
|
-
b[:install_context] = install_context
|
33
|
-
b[:timestamp] = time
|
34
|
-
b[:payload] = event
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def timestamp
|
39
|
-
Time.now.utc.strftime("%FT%TZ")
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
data/lib/telemetry/session.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
require "securerandom"
|
2
|
-
require "chef-config/path_helper"
|
3
|
-
|
4
|
-
class Telemetry
|
5
|
-
class Session
|
6
|
-
def initialize
|
7
|
-
@id = if live_session?
|
8
|
-
File.read(session_file).chomp
|
9
|
-
else
|
10
|
-
new_session
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def id
|
15
|
-
FileUtils.touch(session_file)
|
16
|
-
@id
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def live_session?
|
22
|
-
expiry = Time.now - 600
|
23
|
-
File.file?(session_file) && File.stat(session_file).mtime > expiry
|
24
|
-
end
|
25
|
-
|
26
|
-
def session_file
|
27
|
-
File.join(ChefConfig::PathHelper.home(".chef"), "TELEMETRY_SESSION_ID").freeze
|
28
|
-
end
|
29
|
-
|
30
|
-
def new_session
|
31
|
-
id = SecureRandom.uuid
|
32
|
-
FileUtils.mkdir_p(File.dirname(session_file))
|
33
|
-
File.open(session_file, "w") { |f| f.write(id) }
|
34
|
-
id
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
data/lib/telemetry/version.rb
DELETED