chef-telemetry 0.1.8 → 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.
- 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