puppet 4.9.1 → 4.9.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puppet might be problematic. Click here for more details.
- data/Gemfile +1 -0
- data/lib/puppet/forge/repository.rb +1 -1
- data/lib/puppet/functions/assert_type.rb +3 -5
- data/lib/puppet/functions/yaml_data.rb +6 -1
- data/lib/puppet/pops/adapters.rb +10 -11
- data/lib/puppet/pops/evaluator/evaluator_impl.rb +2 -2
- data/lib/puppet/pops/evaluator/relationship_operator.rb +1 -1
- data/lib/puppet/pops/evaluator/runtime3_support.rb +1 -1
- data/lib/puppet/pops/loader/loader_paths.rb +4 -1
- data/lib/puppet/pops/loaders.rb +2 -0
- data/lib/puppet/pops/lookup/environment_data_provider.rb +10 -2
- data/lib/puppet/pops/lookup/hiera_config.rb +40 -8
- data/lib/puppet/pops/lookup/interpolation.rb +4 -2
- data/lib/puppet/pops/lookup/invocation.rb +2 -2
- data/lib/puppet/pops/lookup/lookup_adapter.rb +8 -2
- data/lib/puppet/pops/lookup/lookup_key_function_provider.rb +7 -3
- data/lib/puppet/pops/merge_strategy.rb +1 -1
- data/lib/puppet/pops/types/type_parser.rb +5 -11
- data/lib/puppet/ssl/certificate.rb +1 -1
- data/lib/puppet/ssl/certificate_request.rb +1 -1
- data/lib/puppet/version.rb +1 -1
- data/spec/lib/puppet_spec/language.rb +3 -1
- data/spec/unit/face/module/search_spec.rb +1 -1
- data/spec/unit/forge/repository_spec.rb +27 -0
- data/spec/unit/functions/lookup_spec.rb +396 -43
- data/spec/unit/network/http/factory_spec.rb +4 -0
- data/spec/unit/pops/evaluator/evaluating_parser_spec.rb +5 -0
- data/spec/unit/pops/evaluator/evaluator_rspec_helper.rb +28 -24
- data/spec/unit/pops/loaders/loaders_spec.rb +117 -22
- data/spec/unit/pops/types/ruby_generator_spec.rb +13 -13
- data/spec/unit/pops/types/type_calculator_spec.rb +13 -25
- data/spec/unit/pops/types/type_parser_spec.rb +1 -4
- data/spec/unit/resource/type_spec.rb +8 -2
- data/spec/unit/ssl/certificate_factory_spec.rb +2 -2
- data/spec/unit/ssl/certificate_request_spec.rb +2 -3
- data/spec/unit/util/http_proxy_spec.rb +4 -0
- metadata +2 -2
data/Gemfile
CHANGED
@@ -45,7 +45,7 @@ class Puppet::Forge
|
|
45
45
|
# Return a Net::HTTPResponse read for this +path+.
|
46
46
|
def make_http_request(path, io = nil)
|
47
47
|
Puppet.debug "HTTP GET #{@host}#{path}"
|
48
|
-
request = get_request_object(path)
|
48
|
+
request = get_request_object(@uri.path.chomp('/')+path)
|
49
49
|
return read_response(request, io)
|
50
50
|
end
|
51
51
|
|
@@ -50,7 +50,7 @@
|
|
50
50
|
#
|
51
51
|
# @since 4.0.0
|
52
52
|
#
|
53
|
-
Puppet::Functions.create_function(:assert_type
|
53
|
+
Puppet::Functions.create_function(:assert_type) do
|
54
54
|
dispatch :assert_type do
|
55
55
|
param 'Type', :type
|
56
56
|
param 'Any', :value
|
@@ -58,7 +58,6 @@ Puppet::Functions.create_function(:assert_type, Puppet::Functions::InternalFunct
|
|
58
58
|
end
|
59
59
|
|
60
60
|
dispatch :assert_type_s do
|
61
|
-
scope_param
|
62
61
|
param 'String', :type_string
|
63
62
|
param 'Any', :value
|
64
63
|
optional_block_param 'Callable[Type, Type]', :block
|
@@ -84,12 +83,11 @@ Puppet::Functions.create_function(:assert_type, Puppet::Functions::InternalFunct
|
|
84
83
|
value
|
85
84
|
end
|
86
85
|
|
87
|
-
# @param scope [Puppet::Parser::Scope] scope used when obtaining loader for defined types
|
88
86
|
# @param type_string [String] the type the value must be an instance of given in String form
|
89
87
|
# @param value [Object] the value to assert
|
90
88
|
#
|
91
|
-
def assert_type_s(
|
92
|
-
t = Puppet::Pops::Types::TypeParser.singleton.parse(type_string
|
89
|
+
def assert_type_s(type_string, value, &proc)
|
90
|
+
t = Puppet::Pops::Types::TypeParser.singleton.parse(type_string)
|
93
91
|
block_given? ? assert_type(t, value, &proc) : assert_type(t, value)
|
94
92
|
end
|
95
93
|
end
|
@@ -10,7 +10,12 @@ Puppet::Functions.create_function(:yaml_data) do
|
|
10
10
|
|
11
11
|
def yaml_data(options, context)
|
12
12
|
begin
|
13
|
-
|
13
|
+
path = options['path']
|
14
|
+
data = YAML.load_file(path)
|
15
|
+
unless data.is_a?(Hash)
|
16
|
+
Puppet.warning("#{path}: file does not contain a valid yaml hash")
|
17
|
+
data = {}
|
18
|
+
end
|
14
19
|
Puppet::Pops::Lookup::HieraConfig.symkeys_to_string(data.nil? ? {} : data)
|
15
20
|
rescue YAML::SyntaxError => ex
|
16
21
|
# Psych errors includes the absolute path to the file, so no need to add that
|
data/lib/puppet/pops/adapters.rb
CHANGED
@@ -118,19 +118,18 @@ module Adapters
|
|
118
118
|
|
119
119
|
# Finds the loader to use when loading originates from the source position of the given argument.
|
120
120
|
#
|
121
|
-
# @param [Model::PopsObject]
|
122
|
-
# @param [
|
123
|
-
# @param [
|
124
|
-
# @return [Loader
|
121
|
+
# @param instance [Model::PopsObject] The model object
|
122
|
+
# @param file [String] the file from where the model was parsed
|
123
|
+
# @param default_loader [Loader] the loader to return if no loader is found for the model
|
124
|
+
# @return [Loader] the found loader or default_loader if it could not be found
|
125
125
|
#
|
126
|
-
def self.loader_for_model_object(model,
|
127
|
-
|
128
|
-
|
129
|
-
|
126
|
+
def self.loader_for_model_object(model, file = nil, default_loader = nil)
|
127
|
+
loaders = Puppet.lookup(:loaders) { nil }
|
128
|
+
if loaders.nil?
|
129
|
+
default_loader
|
130
130
|
else
|
131
|
-
|
132
|
-
loader_name
|
133
|
-
loader_name.nil? ? loaders.private_environment_loader : loaders[loader_name]
|
131
|
+
loader_name = loader_name_by_source(loaders.environment, model, file)
|
132
|
+
loader_name.nil? ? default_loader || loaders.find_loader(nil) : loaders[loader_name]
|
134
133
|
end
|
135
134
|
end
|
136
135
|
|
@@ -309,7 +309,7 @@ class EvaluatorImpl
|
|
309
309
|
# A QualifiedReference (i.e. a capitalized qualified name such as Foo, or Foo::Bar) evaluates to a PType
|
310
310
|
#
|
311
311
|
def eval_QualifiedReference(o, scope)
|
312
|
-
type = Types::TypeParser.singleton.interpret(o
|
312
|
+
type = Types::TypeParser.singleton.interpret(o)
|
313
313
|
fail(Issues::UNKNOWN_RESOURCE_TYPE, o, {:type_name => type.type_string }) if type.is_a?(Types::PTypeReferenceType)
|
314
314
|
type
|
315
315
|
end
|
@@ -473,7 +473,7 @@ class EvaluatorImpl
|
|
473
473
|
keys = o.keys || []
|
474
474
|
if left.is_a?(Types::PHostClassType)
|
475
475
|
# Evaluate qualified references without errors no undefined types
|
476
|
-
keys = keys.map {|key| key.is_a?(Model::QualifiedReference) ? Types::TypeParser.singleton.interpret(key
|
476
|
+
keys = keys.map {|key| key.is_a?(Model::QualifiedReference) ? Types::TypeParser.singleton.interpret(key) : evaluate(key, scope) }
|
477
477
|
else
|
478
478
|
keys = keys.map {|key| evaluate(key, scope) }
|
479
479
|
# Resource[File] becomes File
|
@@ -56,7 +56,7 @@ class RelationshipOperator
|
|
56
56
|
# A string must be a type reference in string format
|
57
57
|
# @api private
|
58
58
|
def transform_String(o, scope)
|
59
|
-
assert_catalog_type(Types::TypeParser.singleton.parse(o
|
59
|
+
assert_catalog_type(Types::TypeParser.singleton.parse(o), scope)
|
60
60
|
end
|
61
61
|
|
62
62
|
# A qualified name is short hand for a class with this name
|
@@ -277,7 +277,7 @@ module Runtime3Support
|
|
277
277
|
|
278
278
|
def call_function(name, args, o, scope, &block)
|
279
279
|
file, line = extract_file_line(o)
|
280
|
-
loader = Adapters::LoaderAdapter.loader_for_model_object(o,
|
280
|
+
loader = Adapters::LoaderAdapter.loader_for_model_object(o, file)
|
281
281
|
if loader && func = loader.load(:function, name)
|
282
282
|
Puppet::Util::Profiler.profile(name, [:functions, name]) do
|
283
283
|
# Add stack frame when calling. See Puppet::Pops::PuppetStack
|
@@ -110,7 +110,10 @@ module Puppet::Pops::Loader::LoaderPaths
|
|
110
110
|
# Puppet name to path always skips the name-space as that is part of the generic path
|
111
111
|
# i.e. <module>/mymodule/functions/foo.pp is the function mymodule::foo
|
112
112
|
parts = typed_name.name_parts
|
113
|
-
|
113
|
+
if start_index_in_name > 0
|
114
|
+
return nil if start_index_in_name >= parts.size
|
115
|
+
parts = parts[start_index_in_name..-1]
|
116
|
+
end
|
114
117
|
"#{File.join(generic_path, parts)}.pp"
|
115
118
|
end
|
116
119
|
end
|
data/lib/puppet/pops/loaders.rb
CHANGED
@@ -16,6 +16,7 @@ class Loaders
|
|
16
16
|
attr_reader :public_environment_loader
|
17
17
|
attr_reader :private_environment_loader
|
18
18
|
attr_reader :implementation_registry
|
19
|
+
attr_reader :environment
|
19
20
|
|
20
21
|
def self.new(environment, for_agent = false)
|
21
22
|
obj = environment.loaders
|
@@ -30,6 +31,7 @@ class Loaders
|
|
30
31
|
# Protect against environment havoc
|
31
32
|
raise ArgumentError.new("Attempt to redefine already initialized loaders for environment") unless environment.loaders.nil?
|
32
33
|
environment.loaders = self
|
34
|
+
@environment = environment
|
33
35
|
@loaders_by_name = {}
|
34
36
|
|
35
37
|
add_loader_by_name(self.class.static_loader)
|
@@ -11,8 +11,16 @@ class EnvironmentDataProvider < ConfiguredDataProvider
|
|
11
11
|
protected
|
12
12
|
|
13
13
|
def assert_config_version(config)
|
14
|
-
|
15
|
-
|
14
|
+
if config.version > 3
|
15
|
+
config
|
16
|
+
else
|
17
|
+
if Puppet[:strict] == :error
|
18
|
+
raise Puppet::DataBinding::LookupError, "#{config.name} cannot be used in an environment"
|
19
|
+
else
|
20
|
+
Puppet.warn_once(:hiera_v3_at_env_root, config.config_path, 'hiera.yaml version 3 found at the environment root was ignored')
|
21
|
+
end
|
22
|
+
nil
|
23
|
+
end
|
16
24
|
end
|
17
25
|
|
18
26
|
# Return the root of the environment
|
@@ -12,11 +12,11 @@ class ScopeLookupCollectingInvocation < Invocation
|
|
12
12
|
|
13
13
|
def initialize(scope)
|
14
14
|
super(scope)
|
15
|
-
@scope_interpolations =
|
15
|
+
@scope_interpolations = []
|
16
16
|
end
|
17
17
|
|
18
|
-
def remember_scope_lookup(
|
19
|
-
@scope_interpolations
|
18
|
+
def remember_scope_lookup(*lookup_result)
|
19
|
+
@scope_interpolations << lookup_result
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
@@ -155,20 +155,28 @@ class HieraConfig
|
|
155
155
|
# @param parent_data_provider [DataProvider] The data provider that loaded this configuration
|
156
156
|
# @return [Array<DataProvider>] the data providers
|
157
157
|
def configured_data_providers(lookup_invocation, parent_data_provider)
|
158
|
-
|
159
|
-
unless @data_providers && scope_interpolations_stable?(scope)
|
158
|
+
unless @data_providers && scope_interpolations_stable?(lookup_invocation)
|
160
159
|
if @data_providers
|
161
160
|
lookup_invocation.report_text { 'Hiera configuration recreated due to change of scope variables used in interpolation expressions' }
|
162
161
|
end
|
163
|
-
slc_invocation = ScopeLookupCollectingInvocation.new(scope)
|
162
|
+
slc_invocation = ScopeLookupCollectingInvocation.new(lookup_invocation.scope)
|
164
163
|
@data_providers = create_configured_data_providers(slc_invocation, parent_data_provider)
|
165
164
|
@scope_interpolations = slc_invocation.scope_interpolations
|
166
165
|
end
|
167
166
|
@data_providers
|
168
167
|
end
|
169
168
|
|
170
|
-
def scope_interpolations_stable?(
|
171
|
-
|
169
|
+
def scope_interpolations_stable?(lookup_invocation)
|
170
|
+
scope = lookup_invocation.scope
|
171
|
+
@scope_interpolations.all? do |key, root_key, segments, old_value|
|
172
|
+
value = scope[root_key]
|
173
|
+
unless value.nil? || segments.empty?
|
174
|
+
found = '';
|
175
|
+
catch(:no_such_key) { found = sub_lookup(key, lookup_invocation, segments, value) }
|
176
|
+
value = found;
|
177
|
+
end
|
178
|
+
old_value.eql?(value)
|
179
|
+
end
|
172
180
|
end
|
173
181
|
|
174
182
|
# @api private
|
@@ -207,6 +215,30 @@ class HieraConfig
|
|
207
215
|
Hiera.logger = 'puppet'
|
208
216
|
end
|
209
217
|
end
|
218
|
+
|
219
|
+
unless Hiera::Interpolate.const_defined?(:PATCHED_BY_HIERA_5)
|
220
|
+
# Replace the class methods 'hiera_interpolate' and 'alias_interpolate' with a method that wires back and performs global
|
221
|
+
# lookups using the lookup framework. This is necessary since the classic Hiera is made aware only of custom backends.
|
222
|
+
class << Hiera::Interpolate
|
223
|
+
hiera_interpolate = Proc.new do |data, key, scope, extra_data, context|
|
224
|
+
override = context[:order_override]
|
225
|
+
invocation = Puppet::Pops::Lookup::Invocation.current
|
226
|
+
unless override.nil? && invocation.global_only?
|
227
|
+
invocation = Puppet::Pops::Lookup::Invocation.new(scope)
|
228
|
+
invocation.set_global_only
|
229
|
+
invocation.set_hiera_v3_location_overrides(override) unless override.nil?
|
230
|
+
end
|
231
|
+
Puppet::Pops::Lookup::LookupAdapter.adapt(scope.compiler).lookup(key, invocation, nil)
|
232
|
+
end
|
233
|
+
|
234
|
+
send(:remove_method, :hiera_interpolate)
|
235
|
+
send(:remove_method, :alias_interpolate)
|
236
|
+
send(:define_method, :hiera_interpolate, hiera_interpolate)
|
237
|
+
send(:define_method, :alias_interpolate, hiera_interpolate)
|
238
|
+
end
|
239
|
+
Hiera::Interpolate.send(:const_set, :PATCHED_BY_HIERA_5, true)
|
240
|
+
end
|
241
|
+
|
210
242
|
Hiera::Config.instance_variable_set(:@config, hiera3_config)
|
211
243
|
|
212
244
|
# Use a special lookup_key that delegates to the backend
|
@@ -20,7 +20,9 @@ module Interpolation
|
|
20
20
|
when Array
|
21
21
|
value.map { |element| interpolate(element, context, allow_methods) }
|
22
22
|
when Hash
|
23
|
-
|
23
|
+
result = {}
|
24
|
+
value.each_pair { |k, v| result[interpolate(k, context, allow_methods)] = interpolate(v, context, allow_methods) }
|
25
|
+
result
|
24
26
|
else
|
25
27
|
value
|
26
28
|
end
|
@@ -95,7 +97,7 @@ module Interpolation
|
|
95
97
|
catch(:no_such_key) { found = sub_lookup(key, lookup_invocation, segments, value) }
|
96
98
|
value = found;
|
97
99
|
end
|
98
|
-
lookup_invocation.remember_scope_lookup(key, value)
|
100
|
+
lookup_invocation.remember_scope_lookup(key, root_key, segments, value)
|
99
101
|
value
|
100
102
|
end
|
101
103
|
|
@@ -74,7 +74,7 @@ class Invocation
|
|
74
74
|
rescue Puppet::DataBinding::LookupError
|
75
75
|
raise
|
76
76
|
rescue Puppet::Error => detail
|
77
|
-
raise Puppet::DataBinding::LookupError.new(detail.message, detail)
|
77
|
+
raise Puppet::DataBinding::LookupError.new(detail.message, nil, nil, nil, detail)
|
78
78
|
ensure
|
79
79
|
@name_stack.pop
|
80
80
|
end
|
@@ -93,7 +93,7 @@ class Invocation
|
|
93
93
|
# values that the configuration was based on
|
94
94
|
#
|
95
95
|
# @api private
|
96
|
-
def remember_scope_lookup(
|
96
|
+
def remember_scope_lookup(*lookup_result)
|
97
97
|
# Does nothing by default
|
98
98
|
end
|
99
99
|
|
@@ -318,7 +318,10 @@ class LookupAdapter < DataAdapter
|
|
318
318
|
if mod.has_hiera_conf?
|
319
319
|
mp = ModuleDataProvider.new(module_name)
|
320
320
|
# A version 5 hiera.yaml trumps a data provider setting or binding in the module
|
321
|
-
|
321
|
+
mp_config = mp.config(lookup_invocation)
|
322
|
+
if mp_config.nil?
|
323
|
+
mp = nil
|
324
|
+
elsif mp_config.version >= 5
|
322
325
|
unless provider_name.nil? || Puppet[:strict] == :off
|
323
326
|
if binding
|
324
327
|
Puppet.warn_once(:deprecation, "ModuleBinding#data_provider-#{module_name}",
|
@@ -388,7 +391,10 @@ class LookupAdapter < DataAdapter
|
|
388
391
|
if config_path.exist?
|
389
392
|
ep = EnvironmentDataProvider.new
|
390
393
|
# A version 5 hiera.yaml trumps any data provider setting in the environment.conf
|
391
|
-
|
394
|
+
ep_config = ep.config(lookup_invocation)
|
395
|
+
if ep_config.nil?
|
396
|
+
ep = nil
|
397
|
+
elsif ep_config.version >= 5
|
392
398
|
unless provider_name.nil? || Puppet[:strict] == :off
|
393
399
|
Puppet.warn_once(:deprecation, 'environment.conf#data_provider',
|
394
400
|
"Defining environment_data_provider='#{provider_name}' in environment.conf is deprecated", env_path + 'environment.conf')
|
@@ -97,13 +97,17 @@ class V3BackendFunctionProvider < LookupKeyFunctionProvider
|
|
97
97
|
# Equivalent to Hiera :hash with default :native merge behavior. A Hash must be passed here
|
98
98
|
# to override possible Hiera deep merge config settings.
|
99
99
|
{ :behavior => :native }
|
100
|
-
when 'deep'
|
100
|
+
when 'deep', 'unconstrained_deep'
|
101
101
|
# Equivalent to Hiera :hash with :deeper merge behavior.
|
102
102
|
{ :behavior => :deeper }
|
103
|
+
when 'reverse_deep'
|
104
|
+
# Equivalent to Hiera :hash with :deep merge behavior.
|
105
|
+
{ :behavior => :deep }
|
103
106
|
when Hash
|
104
107
|
strategy = merge['strategy']
|
105
|
-
|
106
|
-
|
108
|
+
case strategy
|
109
|
+
when 'deep', 'unconstrained_deep', 'reverse_deep'
|
110
|
+
result = { :behavior => strategy == 'reverse_deep' ? :deep : :deeper }
|
107
111
|
# Remaining entries must have symbolic keys
|
108
112
|
merge.each_pair { |k,v| result[k.to_sym] = v unless k == 'strategy' }
|
109
113
|
result
|
@@ -25,9 +25,9 @@ class TypeParser
|
|
25
25
|
# parser.parse('Array[String]')
|
26
26
|
# parser.parse('Hash[Integer, Array[String]]')
|
27
27
|
#
|
28
|
-
# @param string [String] a string with the type expressed in stringified form as produced by the
|
28
|
+
# @param string [String] a string with the type expressed in stringified form as produced by the
|
29
29
|
# types {"#to_s} method.
|
30
|
-
# @param context [
|
30
|
+
# @param context [Loader::Loader] optional loader used as no adapted loader is found
|
31
31
|
# @return [PAnyType] a specialization of the PAnyType representing the type.
|
32
32
|
#
|
33
33
|
# @api public
|
@@ -44,11 +44,11 @@ class TypeParser
|
|
44
44
|
end
|
45
45
|
|
46
46
|
# @param ast [Puppet::Pops::Model::PopsObject] the ast to interpret
|
47
|
-
# @param context [
|
47
|
+
# @param context [Loader::Loader] optional loader used when no adapted loader is found
|
48
48
|
# @return [PAnyType] a specialization of the PAnyType representing the type.
|
49
49
|
#
|
50
50
|
# @api public
|
51
|
-
def interpret(ast, context)
|
51
|
+
def interpret(ast, context = nil)
|
52
52
|
result = @type_transformer.visit_this_1(self, ast, context)
|
53
53
|
raise_invalid_type_specification_error(ast) unless result.is_a?(PAnyType)
|
54
54
|
result
|
@@ -200,13 +200,7 @@ class TypeParser
|
|
200
200
|
|
201
201
|
# @api private
|
202
202
|
def loader_from_context(ast, context)
|
203
|
-
|
204
|
-
nil
|
205
|
-
elsif context.is_a?(Puppet::Pops::Loader::Loader)
|
206
|
-
context
|
207
|
-
else
|
208
|
-
Puppet::Pops::Adapters::LoaderAdapter.loader_for_model_object(ast, context)
|
209
|
-
end
|
203
|
+
Adapters::LoaderAdapter.loader_for_model_object(ast, nil, context)
|
210
204
|
end
|
211
205
|
|
212
206
|
# @api private
|
@@ -69,7 +69,7 @@ DOC
|
|
69
69
|
# is where the extensions are stored."
|
70
70
|
@extensions_tag ||= 3
|
71
71
|
|
72
|
-
@exts_seq ||= OpenSSL::ASN1.decode(content.to_der).value[0].find do |data|
|
72
|
+
@exts_seq ||= OpenSSL::ASN1.decode(content.to_der).value[0].value.find do |data|
|
73
73
|
(data.tag == @extensions_tag) && (data.tag_class == :CONTEXT_SPECIFIC)
|
74
74
|
end.value[0]
|
75
75
|
end
|
data/lib/puppet/version.rb
CHANGED
@@ -21,7 +21,9 @@ module PuppetSpec::Language
|
|
21
21
|
|
22
22
|
compiler.send(:instance_variable_set, :@catalog, catalog)
|
23
23
|
|
24
|
-
|
24
|
+
Puppet.override(:loaders => compiler.loaders) do
|
25
|
+
expect(evaluator.evaluate_string(compiler.topscope, resources)).to eq(true)
|
26
|
+
end
|
25
27
|
when Array
|
26
28
|
catalog = PuppetSpec::Compiler.compile_to_catalog(manifest)
|
27
29
|
|
@@ -171,7 +171,7 @@ describe "puppet module search" do
|
|
171
171
|
end
|
172
172
|
end
|
173
173
|
|
174
|
-
it "should accept the --
|
174
|
+
it "should accept the --module_repository option" do
|
175
175
|
forge = mock("Puppet::Forge")
|
176
176
|
searcher = mock("Searcher")
|
177
177
|
options[:module_repository] = "http://forge.example.com"
|