mirror of
https://github.com/elastic/logstash.git
synced 2025-04-24 14:47:19 -04:00
Moved CloudSettingId class and tests to Java
(cherry picked from commit 2545fa45ba
)
This commit is contained in:
parent
cf7e2e308e
commit
b1e5c4f257
5 changed files with 392 additions and 308 deletions
|
@ -17,89 +17,6 @@
|
||||||
|
|
||||||
require "base64"
|
require "base64"
|
||||||
|
|
||||||
module LogStash module Util class CloudSettingId
|
module LogStash; module Util
|
||||||
|
java_import org.logstash.util.CloudSettingId
|
||||||
def self.cloud_id_encode(*args)
|
end; end
|
||||||
Base64.urlsafe_encode64(args.join("$"))
|
|
||||||
end
|
|
||||||
DOT_SEPARATOR = "."
|
|
||||||
CLOUD_PORT = "443"
|
|
||||||
|
|
||||||
attr_reader :original, :decoded, :label
|
|
||||||
attr_reader :elasticsearch_host, :elasticsearch_scheme, :elasticsearch_port
|
|
||||||
attr_reader :kibana_host, :kibana_scheme, :kibana_port
|
|
||||||
attr_reader :other_identifiers
|
|
||||||
|
|
||||||
# The constructor is expecting a 'cloud.id', a string in 2 variants.
|
|
||||||
# 1 part example: 'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRub3RhcmVhbCRpZGVudGlmaWVy'
|
|
||||||
# 2 part example: 'foobar:dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRub3RhcmVhbCRpZGVudGlmaWVy'
|
|
||||||
# The two part variant has a 'label' prepended with a colon separator. The label is not encoded.
|
|
||||||
# The 1 part (or second section of the 2 part variant) is base64 encoded.
|
|
||||||
# The original string before encoding has three segments separated by a dollar sign.
|
|
||||||
# e.g. 'us-east-1.aws.found.io$notareal$identifier'
|
|
||||||
# The first segment is the cloud base url, e.g. 'us-east-1.aws.found.io'
|
|
||||||
# The second segment is the elasticsearch host identifier, e.g. 'notareal'
|
|
||||||
# The third segment is the kibana host identifier, e.g. 'identifier'
|
|
||||||
# The 'cloud.id' value decoded into the #attr_reader ivars.
|
|
||||||
def initialize(value)
|
|
||||||
return if value.nil?
|
|
||||||
|
|
||||||
unless value.is_a?(String)
|
|
||||||
raise ArgumentError.new("Cloud Id must be String. Received: #{value.class}")
|
|
||||||
end
|
|
||||||
@original = value
|
|
||||||
@label, colon, encoded = @original.partition(":")
|
|
||||||
if encoded.empty?
|
|
||||||
@decoded = Base64.urlsafe_decode64(@label) rescue ""
|
|
||||||
@label = ""
|
|
||||||
else
|
|
||||||
@decoded = Base64.urlsafe_decode64(encoded) rescue ""
|
|
||||||
end
|
|
||||||
|
|
||||||
@decoded = @decoded.encode(Encoding::UTF_8, :invalid => :replace, :undef => :replace)
|
|
||||||
|
|
||||||
if @decoded.count("$") < 2
|
|
||||||
raise ArgumentError.new("Cloud Id, after decoding, is invalid. Format: '<segment1>$<segment2>$<segment3>'. Received: \"#{@decoded}\".")
|
|
||||||
end
|
|
||||||
|
|
||||||
segments = @decoded.split("$")
|
|
||||||
if segments.any?(&:empty?)
|
|
||||||
raise ArgumentError.new("Cloud Id, after decoding, is invalid. Format: '<segment1>$<segment2>$<segment3>'. Received: \"#{@decoded}\".")
|
|
||||||
end
|
|
||||||
cloud_base = segments.shift
|
|
||||||
cloud_host = "#{DOT_SEPARATOR}#{cloud_base}"
|
|
||||||
cloud_host, cloud_port = cloud_host.split(":")
|
|
||||||
cloud_port ||= CLOUD_PORT
|
|
||||||
|
|
||||||
@elasticsearch_host, @kibana_host, *@other_identifiers = segments
|
|
||||||
@elasticsearch_host, @elasticsearch_port = @elasticsearch_host.split(":")
|
|
||||||
@kibana_host, @kibana_port = @kibana_host.split(":") if @kibana_host
|
|
||||||
@elasticsearch_port ||= cloud_port
|
|
||||||
@kibana_port ||= cloud_port
|
|
||||||
@other_identifiers ||= []
|
|
||||||
|
|
||||||
if @elasticsearch_host == "undefined"
|
|
||||||
raise ArgumentError.new("Cloud Id, after decoding, elasticsearch segment is 'undefined', literally.")
|
|
||||||
end
|
|
||||||
@elasticsearch_scheme = "https"
|
|
||||||
@elasticsearch_host.concat(cloud_host)
|
|
||||||
@elasticsearch_host.concat(":#{@elasticsearch_port}")
|
|
||||||
|
|
||||||
if @kibana_host == "undefined"
|
|
||||||
raise ArgumentError.new("Cloud Id, after decoding, the kibana segment is 'undefined', literally. You may need to enable Kibana in the Cloud UI.")
|
|
||||||
end
|
|
||||||
|
|
||||||
@kibana_scheme = "https"
|
|
||||||
@kibana_host ||= String.new # non-sense really to have '.my-host:443' but we're mirroring others
|
|
||||||
@kibana_host.concat(cloud_host)
|
|
||||||
@kibana_host.concat(":#{@kibana_port}")
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
@decoded.to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
to_s
|
|
||||||
end
|
|
||||||
end end end
|
|
||||||
|
|
|
@ -50,26 +50,26 @@ describe LogStash::Setting::Modules do
|
||||||
subject { described_class.new("mycloudid", LogStash::Util::CloudSettingId) }
|
subject { described_class.new("mycloudid", LogStash::Util::CloudSettingId) }
|
||||||
context "when given a string which is not a cloud id" do
|
context "when given a string which is not a cloud id" do
|
||||||
it "should raise an exception" do
|
it "should raise an exception" do
|
||||||
expect { subject.set("foobarbaz") }.to raise_error(ArgumentError, /Cloud Id.*is invalid/)
|
expect { subject.set("foobarbaz") }.to raise_error(IllegalArgumentException, /Cloud Id.*is invalid/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when given a string which is empty" do
|
context "when given a string which is empty" do
|
||||||
it "should raise an exception" do
|
it "should raise an exception" do
|
||||||
expect { subject.set("") }.to raise_error(ArgumentError, /Cloud Id.*is invalid/)
|
expect { subject.set("") }.to raise_error(IllegalArgumentException, /Cloud Id.*is invalid/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when given a string which is has environment prefix only" do
|
context "when given a string which is has environment prefix only" do
|
||||||
it "should raise an exception" do
|
it "should raise an exception" do
|
||||||
expect { subject.set("testing:") }.to raise_error(ArgumentError, /Cloud Id.*is invalid/)
|
expect { subject.set("testing:") }.to raise_error(IllegalArgumentException, /Cloud Id.*is invalid/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when given a badly formatted encoded id" do
|
context "when given a badly formatted encoded id" do
|
||||||
it "should not raise an error" do
|
it "should not raise an error" do
|
||||||
encoded = Base64.urlsafe_encode64("foo$$bal")
|
encoded = Base64.urlsafe_encode64("foo$$bal")
|
||||||
expect { subject.set(encoded) }.to raise_error(ArgumentError, "Cloud Id, after decoding, is invalid. Format: '<segment1>$<segment2>$<segment3>'. Received: \"foo$$bal\".")
|
expect { subject.set(encoded) }.to raise_error(IllegalArgumentException, "Cloud Id, after decoding, is invalid. Format: '<segment1>$<segment2>$<segment3>'. Received: \"foo$$bal\".")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,218 +0,0 @@
|
||||||
# Licensed to Elasticsearch B.V. under one or more contributor
|
|
||||||
# license agreements. See the NOTICE file distributed with
|
|
||||||
# this work for additional information regarding copyright
|
|
||||||
# ownership. Elasticsearch B.V. licenses this file to you under
|
|
||||||
# the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
require "spec_helper"
|
|
||||||
require "logstash/util/cloud_setting_id"
|
|
||||||
|
|
||||||
describe LogStash::Util::CloudSettingId do
|
|
||||||
let(:input) { "foobar:dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRub3RhcmVhbCRpZGVudGlmaWVy" }
|
|
||||||
subject { described_class.new(input) }
|
|
||||||
|
|
||||||
describe "when given unacceptable input" do
|
|
||||||
it "a nil input does not raise an exception" do
|
|
||||||
expect{described_class.new(nil)}.not_to raise_exception
|
|
||||||
end
|
|
||||||
it "when given a nil input, the accessors are all nil" do
|
|
||||||
cloud_id = described_class.new(nil)
|
|
||||||
expect(cloud_id.original).to be_nil
|
|
||||||
expect(cloud_id.decoded).to be_nil
|
|
||||||
expect(cloud_id.label).to be_nil
|
|
||||||
expect(cloud_id.elasticsearch_host).to be_nil
|
|
||||||
expect(cloud_id.kibana_host).to be_nil
|
|
||||||
expect(cloud_id.elasticsearch_scheme).to be_nil
|
|
||||||
expect(cloud_id.kibana_scheme).to be_nil
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when a malformed value is given" do
|
|
||||||
let(:raw) {%w(first second)}
|
|
||||||
let(:input) { described_class.cloud_id_encode(*raw) }
|
|
||||||
it "raises an error" do
|
|
||||||
expect{subject}.to raise_exception(ArgumentError, "Cloud Id, after decoding, is invalid. Format: '<segment1>$<segment2>$<segment3>'. Received: \"#{raw[0]}$#{raw[1]}\".")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when at least one segment is empty" do
|
|
||||||
let(:raw) {["first", "", "third"]}
|
|
||||||
let(:input) { described_class.cloud_id_encode(*raw) }
|
|
||||||
it "raises an error" do
|
|
||||||
expect{subject}.to raise_exception(ArgumentError, "Cloud Id, after decoding, is invalid. Format: '<segment1>$<segment2>$<segment3>'. Received: \"#{raw[0]}$#{raw[1]}$#{raw[2]}\".")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when elasticsearch segment is undefined" do
|
|
||||||
let(:raw) {%w(us-east-1.aws.found.io undefined my-kibana)}
|
|
||||||
let(:input) { described_class.cloud_id_encode(*raw) }
|
|
||||||
it "raises an error" do
|
|
||||||
expect{subject}.to raise_exception(ArgumentError, "Cloud Id, after decoding, elasticsearch segment is 'undefined', literally.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when kibana segment is undefined" do
|
|
||||||
let(:raw) {%w(us-east-1.aws.found.io my-elastic-cluster undefined)}
|
|
||||||
let(:input) { described_class.cloud_id_encode(*raw) }
|
|
||||||
it "raises an error" do
|
|
||||||
expect{subject}.to raise_exception(ArgumentError, "Cloud Id, after decoding, the kibana segment is 'undefined', literally. You may need to enable Kibana in the Cloud UI.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "without a label" do
|
|
||||||
let(:input) { "dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRub3RhcmVhbCRpZGVudGlmaWVy" }
|
|
||||||
it "#label is empty" do
|
|
||||||
expect(subject.label).to be_empty
|
|
||||||
end
|
|
||||||
it "#decode is set" do
|
|
||||||
expect(subject.decoded).to eq("us-east-1.aws.found.io$notareal$identifier")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "when given acceptable input, the accessors:" do
|
|
||||||
it '#original has a value' do
|
|
||||||
expect(subject.original).to eq(input)
|
|
||||||
end
|
|
||||||
it '#decoded has a value' do
|
|
||||||
expect(subject.decoded).to eq("us-east-1.aws.found.io$notareal$identifier")
|
|
||||||
end
|
|
||||||
it '#label has a value' do
|
|
||||||
expect(subject.label).to eq("foobar")
|
|
||||||
end
|
|
||||||
it '#elasticsearch_host has a value' do
|
|
||||||
expect(subject.elasticsearch_host).to eq("notareal.us-east-1.aws.found.io:443")
|
|
||||||
end
|
|
||||||
it '#elasticsearch_scheme has a value' do
|
|
||||||
expect(subject.elasticsearch_scheme).to eq("https")
|
|
||||||
end
|
|
||||||
it '#kibana_host has a value' do
|
|
||||||
expect(subject.kibana_host).to eq("identifier.us-east-1.aws.found.io:443")
|
|
||||||
end
|
|
||||||
it '#kibana_scheme has a value' do
|
|
||||||
expect(subject.kibana_scheme).to eq("https")
|
|
||||||
end
|
|
||||||
it '#to_s has a value of #decoded' do
|
|
||||||
expect(subject.to_s).to eq(subject.decoded)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
context "when cloud id contains port descriptions for ES and Kibana" do
|
|
||||||
let(:input) { "different-es-kb-port:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2OjkyNDMkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA6OTI0NA==" }
|
|
||||||
|
|
||||||
it "decodes the elasticsearch port corretly" do
|
|
||||||
expect(subject.elasticsearch_host).to eq("ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:9243")
|
|
||||||
end
|
|
||||||
it "decodes the kibana port corretly" do
|
|
||||||
expect(subject.kibana_host).to eq("a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9244")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
context "when cloud id contains cloud port" do
|
|
||||||
let(:input) { "custom-port:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvOjkyNDMkYWMzMWViYjkwMjQxNzczMTU3MDQzYzM0ZmQyNmZkNDYkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA=" }
|
|
||||||
|
|
||||||
it "decodes the elasticsearch port corretly" do
|
|
||||||
expect(subject.elasticsearch_host).to eq("ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:9243")
|
|
||||||
end
|
|
||||||
it "decodes the kibana port corretly" do
|
|
||||||
expect(subject.kibana_host).to eq("a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9243")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
context "when cloud id only defines kibana port" do
|
|
||||||
let(:input) { "only-kb-set:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2JGE0YzA2MjMwZTQ4YzhmY2U3YmU4OGEwNzRhM2JiM2UwOjkyNDQ=" }
|
|
||||||
|
|
||||||
it "defaults the elasticsearch port to 443" do
|
|
||||||
expect(subject.elasticsearch_host).to eq("ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:443")
|
|
||||||
end
|
|
||||||
it "decodes the kibana port corretly" do
|
|
||||||
expect(subject.kibana_host).to eq("a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9244")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
context "when cloud id defines cloud port and kibana port" do
|
|
||||||
let(:input) { "host-and-kb-set:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvOjkyNDMkYWMzMWViYjkwMjQxNzczMTU3MDQzYzM0ZmQyNmZkNDYkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA6OTI0NA==" }
|
|
||||||
|
|
||||||
it "sets the elasticsearch port to cloud port" do
|
|
||||||
expect(subject.elasticsearch_host).to eq("ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:9243")
|
|
||||||
end
|
|
||||||
it "overrides cloud port with the kibana port" do
|
|
||||||
expect(subject.kibana_host).to eq("a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9244")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
context "when cloud id defines extra data" do
|
|
||||||
let(:input) { "extra-items:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2JGE0YzA2MjMwZTQ4YzhmY2U3YmU4OGEwNzRhM2JiM2UwJGFub3RoZXJpZCRhbmRhbm90aGVy" }
|
|
||||||
|
|
||||||
it "captures the elasticsearch host" do
|
|
||||||
expect(subject.elasticsearch_host).to eq("ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:443")
|
|
||||||
end
|
|
||||||
it "captures the kibana host" do
|
|
||||||
expect(subject.kibana_host).to eq("a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:443")
|
|
||||||
end
|
|
||||||
it "captures the remaining identifiers" do
|
|
||||||
expect(subject.other_identifiers).to eq(["anotherid", "andanother"])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "when given acceptable input (with empty kibana uuid), the accessors:" do
|
|
||||||
let(:input) { "a-test:ZWNlLmhvbWUubGFuJHRlc3Qk" } # ece.home.lan$test$
|
|
||||||
|
|
||||||
it '#original has a value' do
|
|
||||||
expect(subject.original).to eq(input)
|
|
||||||
end
|
|
||||||
it '#decoded has a value' do
|
|
||||||
expect(subject.decoded).to eq("ece.home.lan$test$")
|
|
||||||
end
|
|
||||||
it '#label has a value' do
|
|
||||||
expect(subject.label).to eq("a-test")
|
|
||||||
end
|
|
||||||
it '#elasticsearch_host has a value' do
|
|
||||||
expect(subject.elasticsearch_host).to eq("test.ece.home.lan:443")
|
|
||||||
end
|
|
||||||
it '#elasticsearch_scheme has a value' do
|
|
||||||
expect(subject.elasticsearch_scheme).to eq("https")
|
|
||||||
end
|
|
||||||
it '#kibana_host has a value' do
|
|
||||||
# NOTE: kibana part is not relevant -> this is how python/beats(go) code behaves
|
|
||||||
expect(subject.kibana_host).to eq(".ece.home.lan:443")
|
|
||||||
end
|
|
||||||
it '#kibana_scheme has a value' do
|
|
||||||
expect(subject.kibana_scheme).to eq("https")
|
|
||||||
end
|
|
||||||
it '#to_s has a value of #decoded' do
|
|
||||||
expect(subject.to_s).to eq(subject.decoded)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "a lengthy real-world input, the accessors:" do
|
|
||||||
let(:input) do
|
|
||||||
"ZWFzdHVzMi5henVyZS5lbGFzdGljLWNsb3VkLmNvbTo5MjQzJDQwYjM0MzExNmNmYTRlYmNiNzZjMTFlZTIzMjlmOTJkJDQzZDA5MjUyNTAyYzQxODlhMzc2ZmQwY2YyY2QwODQ4"
|
|
||||||
# eastus2.azure.elastic-cloud.com:9243$40b343116cfa4ebcb76c11ee2329f92d$43d09252502c4189a376fd0cf2cd0848
|
|
||||||
end
|
|
||||||
|
|
||||||
it '#original has a value' do
|
|
||||||
expect(subject.original).to eq(input)
|
|
||||||
end
|
|
||||||
it '#decoded has a value' do
|
|
||||||
expect(subject.decoded).to eq("eastus2.azure.elastic-cloud.com:9243$40b343116cfa4ebcb76c11ee2329f92d$43d09252502c4189a376fd0cf2cd0848")
|
|
||||||
end
|
|
||||||
it '#label has a value' do
|
|
||||||
expect(subject.label).to eq("")
|
|
||||||
end
|
|
||||||
it '#elasticsearch_host has a value' do
|
|
||||||
expect(subject.elasticsearch_host).to eq("40b343116cfa4ebcb76c11ee2329f92d.eastus2.azure.elastic-cloud.com:9243")
|
|
||||||
end
|
|
||||||
it '#kibana_host has a value' do
|
|
||||||
expect(subject.kibana_host).to eq("43d09252502c4189a376fd0cf2cd0848.eastus2.azure.elastic-cloud.com:9243")
|
|
||||||
end
|
|
||||||
it '#to_s has a value of #decoded' do
|
|
||||||
expect(subject.to_s).to eq(subject.decoded)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.logstash.util;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The constructor is expecting a 'cloud.id', a string in 2 variants.
|
||||||
|
* 1 part example: 'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRub3RhcmVhbCRpZGVudGlmaWVy'
|
||||||
|
* 2 part example: 'foobar:dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRub3RhcmVhbCRpZGVudGlmaWVy'
|
||||||
|
* The two part variant has a 'label' prepended with a colon separator. The label is not encoded.
|
||||||
|
* The 1 part (or second section of the 2 part variant) is base64 encoded.
|
||||||
|
* The original string before encoding has three segments separated by a dollar sign.
|
||||||
|
* e.g. 'us-east-1.aws.found.io$notareal$identifier'
|
||||||
|
* The first segment is the cloud base url, e.g. 'us-east-1.aws.found.io'
|
||||||
|
* The second segment is the elasticsearch host identifier, e.g. 'notareal'
|
||||||
|
* The third segment is the kibana host identifier, e.g. 'identifier'
|
||||||
|
* The 'cloud.id' value decoded into the various fields.
|
||||||
|
*/
|
||||||
|
public class CloudSettingId {
|
||||||
|
|
||||||
|
private static class HostAndPort {
|
||||||
|
static final HostAndPort NO_HOST = new HostAndPort("", null);
|
||||||
|
private final String host;
|
||||||
|
private final String port;
|
||||||
|
|
||||||
|
private HostAndPort(String host, String port) {
|
||||||
|
this.host = host;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
String portOrDefault(String defaultPort) {
|
||||||
|
return port == null ? defaultPort : port;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HostAndPort parseHostAndPort(String part, String guidanceMessageWhenHostEqualsUndefined) {
|
||||||
|
final String[] hostParts = part.split(":");
|
||||||
|
String host = hostParts[0];
|
||||||
|
if ("undefined".equals(host)) {
|
||||||
|
throw new IllegalArgumentException(guidanceMessageWhenHostEqualsUndefined);
|
||||||
|
}
|
||||||
|
String port = null;
|
||||||
|
if (hostParts.length > 1) {
|
||||||
|
port = hostParts[1];
|
||||||
|
}
|
||||||
|
return new HostAndPort(host, port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String DOT_SEPARATOR = ".";
|
||||||
|
public static final String CLOUD_PORT = "443";
|
||||||
|
|
||||||
|
private String original;
|
||||||
|
private String decoded;
|
||||||
|
private String label;
|
||||||
|
private String elasticsearchScheme;
|
||||||
|
private String elasticsearchHost;
|
||||||
|
private String elasticsearchPort;
|
||||||
|
private String kibanaScheme;
|
||||||
|
private String kibanaHost;
|
||||||
|
private String kibanaPort;
|
||||||
|
private String[] otherIdentifiers = new String[0];
|
||||||
|
|
||||||
|
public CloudSettingId(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
original = value;
|
||||||
|
final String[] parts = original.split(":");
|
||||||
|
label = parts[0];
|
||||||
|
String encoded = null;
|
||||||
|
if (parts.length > 1) {
|
||||||
|
encoded = parts[1];
|
||||||
|
}
|
||||||
|
if (encoded == null || encoded.isEmpty()) {
|
||||||
|
try {
|
||||||
|
decoded = new String(Base64.getUrlDecoder().decode(label), StandardCharsets.UTF_8);
|
||||||
|
} catch (IllegalArgumentException iaex) {
|
||||||
|
decoded = "";
|
||||||
|
}
|
||||||
|
label = "";
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
decoded = new String(Base64.getUrlDecoder().decode(encoded), StandardCharsets.UTF_8);
|
||||||
|
} catch (IllegalArgumentException iaex) {
|
||||||
|
decoded = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long separatorCount = decoded.chars().filter(c -> c == '$').count();
|
||||||
|
if (separatorCount < 2) {
|
||||||
|
throw new IllegalArgumentException("Cloud Id, after decoding, is invalid. Format: '<segment1>$<segment2>$<segment3>'. Received: \"" + decoded + "\".");
|
||||||
|
}
|
||||||
|
final String[] segments = decoded.split("\\$");
|
||||||
|
if (Arrays.stream(segments).anyMatch(String::isEmpty)) {
|
||||||
|
throw new IllegalArgumentException("Cloud Id, after decoding, is invalid. Format: '<segment1>$<segment2>$<segment3>'. Received: \"" + decoded + "\".");
|
||||||
|
}
|
||||||
|
String cloudBase = segments[0];
|
||||||
|
String cloudHost = DOT_SEPARATOR + cloudBase;
|
||||||
|
final String[] hostParts = cloudHost.split(":");
|
||||||
|
final HostAndPort cloud = new HostAndPort(hostParts[0], hostParts.length > 1 ? hostParts[1] : CLOUD_PORT);
|
||||||
|
|
||||||
|
final HostAndPort elasticsearch = HostAndPort.parseHostAndPort(segments[1], "Cloud Id, after decoding, elasticsearch segment is 'undefined', literally.");
|
||||||
|
elasticsearchPort = elasticsearch.portOrDefault(cloud.port);
|
||||||
|
elasticsearchHost = elasticsearch.host + cloud.host + ":" + elasticsearchPort;
|
||||||
|
elasticsearchScheme = "https";
|
||||||
|
|
||||||
|
final HostAndPort kibana;
|
||||||
|
if (segments.length > 2) {
|
||||||
|
kibana = HostAndPort.parseHostAndPort(segments[2], "Cloud Id, after decoding, the kibana segment is 'undefined', literally. You may need to enable Kibana in the Cloud UI.");
|
||||||
|
} else {
|
||||||
|
// non-sense really to have '.my-host:443' but we're mirroring others
|
||||||
|
kibana = HostAndPort.NO_HOST;
|
||||||
|
}
|
||||||
|
kibanaPort = kibana.portOrDefault(cloud.port);
|
||||||
|
kibanaHost = kibana.host + cloud.host + ":" + kibanaPort;
|
||||||
|
kibanaScheme = "https";
|
||||||
|
|
||||||
|
if (segments.length > 3) {
|
||||||
|
otherIdentifiers = Arrays.copyOfRange(segments, 3, segments.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOriginal() {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDecoded() {
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getElasticsearchScheme() {
|
||||||
|
return elasticsearchScheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getElasticsearchHost() {
|
||||||
|
return elasticsearchHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getElasticsearchPort() {
|
||||||
|
return elasticsearchPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKibanaScheme() {
|
||||||
|
return kibanaScheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKibanaHost() {
|
||||||
|
return kibanaHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKibanaPort() {
|
||||||
|
return kibanaPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getOtherIdentifiers() {
|
||||||
|
return otherIdentifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String cloudIdEncode(String... args) {
|
||||||
|
final String joinedArgs = String.join("$", args);
|
||||||
|
return Base64.getUrlEncoder().encodeToString(joinedArgs.getBytes());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,194 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.logstash.util;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class CloudSettingIdTest {
|
||||||
|
|
||||||
|
private String input = "foobar:dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRub3RhcmVhbCRpZGVudGlmaWVy";
|
||||||
|
private CloudSettingId sut;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException exceptionRule = ExpectedException.none();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
sut = new CloudSettingId(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// when given unacceptable input
|
||||||
|
@Test
|
||||||
|
public void testNullInputDoenstThrowAnException() {
|
||||||
|
new CloudSettingId(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNullInputMakesAllGettersReturnNull() {
|
||||||
|
sut = new CloudSettingId(null);
|
||||||
|
assertNull(sut.getOriginal());
|
||||||
|
assertNull(sut.getDecoded());
|
||||||
|
assertNull(sut.getLabel());
|
||||||
|
assertNull(sut.getElasticsearchHost());
|
||||||
|
assertNull(sut.getKibanaHost());
|
||||||
|
assertNull(sut.getElasticsearchScheme());
|
||||||
|
assertNull(sut.getKibanaScheme());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThrowExceptionWhenMalformedValueIsGiven() {
|
||||||
|
String[] raw = new String[] {"first", "second"};
|
||||||
|
String encoded = CloudSettingId.cloudIdEncode(raw);
|
||||||
|
exceptionRule.expect(IllegalArgumentException.class);
|
||||||
|
exceptionRule.expectMessage("Cloud Id, after decoding, is invalid. Format: '<segment1>$<segment2>$<segment3>'. Received: \"" + String.join("$", raw) + "\".");
|
||||||
|
|
||||||
|
new CloudSettingId(encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThrowExceptionWhenAtLeatOneSegmentIsEmpty() {
|
||||||
|
String[] raw = new String[] {"first", "", "third"};
|
||||||
|
String encoded = CloudSettingId.cloudIdEncode(raw);
|
||||||
|
exceptionRule.expect(IllegalArgumentException.class);
|
||||||
|
exceptionRule.expectMessage("Cloud Id, after decoding, is invalid. Format: '<segment1>$<segment2>$<segment3>'. Received: \"" + String.join("$", raw) + "\".");
|
||||||
|
|
||||||
|
new CloudSettingId(encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThrowExceptionWhenElasticSegmentSegmentIsUndefined() {
|
||||||
|
String[] raw = new String[] {"us-east-1.aws.found.io", "undefined", "my-kibana"};
|
||||||
|
String encoded = CloudSettingId.cloudIdEncode(raw);
|
||||||
|
exceptionRule.expect(IllegalArgumentException.class);
|
||||||
|
exceptionRule.expectMessage("Cloud Id, after decoding, elasticsearch segment is 'undefined', literally.");
|
||||||
|
|
||||||
|
new CloudSettingId(encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThrowExceptionWhenKibanaSegmentSegmentIsUndefined() {
|
||||||
|
String[] raw = new String[] {"us-east-1.aws.found.io", "my-elastic-cluster", "undefined"};
|
||||||
|
String encoded = CloudSettingId.cloudIdEncode(raw);
|
||||||
|
exceptionRule.expect(IllegalArgumentException.class);
|
||||||
|
exceptionRule.expectMessage("Cloud Id, after decoding, the kibana segment is 'undefined', literally. You may need to enable Kibana in the Cloud UI.");
|
||||||
|
|
||||||
|
new CloudSettingId(encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
// without a label
|
||||||
|
@Test
|
||||||
|
public void testDecodingWithoutLabelSegment() {
|
||||||
|
sut = new CloudSettingId("dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRub3RhcmVhbCRpZGVudGlmaWVy");
|
||||||
|
|
||||||
|
assertEquals("#label is empty", "", sut.getLabel());
|
||||||
|
assertEquals("#decode is set", "us-east-1.aws.found.io$notareal$identifier", sut.getDecoded());
|
||||||
|
}
|
||||||
|
|
||||||
|
// when given acceptable input, the accessors:
|
||||||
|
@Test
|
||||||
|
public void testAccessorsWithAcceptableInput() {
|
||||||
|
assertEquals("#original has a value", input, sut.getOriginal());
|
||||||
|
assertEquals("#decoded has a value", "us-east-1.aws.found.io$notareal$identifier", sut.getDecoded());
|
||||||
|
assertEquals("#label has a value", "foobar", sut.getLabel());
|
||||||
|
assertEquals("#elasticsearch_host has a value", "notareal.us-east-1.aws.found.io:443", sut.getElasticsearchHost());
|
||||||
|
assertEquals("#elasticsearch_scheme has a value", "https", sut.getElasticsearchScheme());
|
||||||
|
assertEquals("#kibana_host has a value", "identifier.us-east-1.aws.found.io:443", sut.getKibanaHost());
|
||||||
|
assertEquals("#kibana_scheme has a value", "https", sut.getKibanaScheme());
|
||||||
|
assertEquals("#to_s has a value of #decoded", sut.toString(), sut.getDecoded());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWhenCloudIdContainsPortDescriptionForESAndKibana() {
|
||||||
|
sut = new CloudSettingId("different-es-kb-port:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2OjkyNDMkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA6OTI0NA==");
|
||||||
|
|
||||||
|
assertEquals("decodes the elasticsearch port corretly", "ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:9243", sut.getElasticsearchHost());
|
||||||
|
assertEquals("decodes the kibana port corretly", "a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9244", sut.getKibanaHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWhenCloudIdContainsCloudPort() {
|
||||||
|
sut = new CloudSettingId("custom-port:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvOjkyNDMkYWMzMWViYjkwMjQxNzczMTU3MDQzYzM0ZmQyNmZkNDYkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA=");
|
||||||
|
|
||||||
|
assertEquals("decodes the elasticsearch port corretly", "ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:9243", sut.getElasticsearchHost());
|
||||||
|
assertEquals("decodes the kibana port corretly", "a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9243", sut.getKibanaHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWhenCloudIdOnlyDefinesKibanaPort() {
|
||||||
|
sut = new CloudSettingId("only-kb-set:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2JGE0YzA2MjMwZTQ4YzhmY2U3YmU4OGEwNzRhM2JiM2UwOjkyNDQ=");
|
||||||
|
|
||||||
|
assertEquals("defaults the elasticsearch port to 443", "ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:443", sut.getElasticsearchHost());
|
||||||
|
assertEquals("decodes the kibana port corretly", "a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9244", sut.getKibanaHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWhenCloudIdDefinesCloudPortAndKibanaPort() {
|
||||||
|
sut = new CloudSettingId("host-and-kb-set:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvOjkyNDMkYWMzMWViYjkwMjQxNzczMTU3MDQzYzM0ZmQyNmZkNDYkYTRjMDYyMzBlNDhjOGZjZTdiZTg4YTA3NGEzYmIzZTA6OTI0NA==");
|
||||||
|
|
||||||
|
assertEquals("sets the elasticsearch port to cloud port", "ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:9243", sut.getElasticsearchHost());
|
||||||
|
assertEquals("overrides cloud port with the kibana port", "a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:9244", sut.getKibanaHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWhenCloudIdDefinesExtraData() {
|
||||||
|
sut = new CloudSettingId("extra-items:dXMtY2VudHJhbDEuZ2NwLmNsb3VkLmVzLmlvJGFjMzFlYmI5MDI0MTc3MzE1NzA0M2MzNGZkMjZmZDQ2JGE0YzA2MjMwZTQ4YzhmY2U3YmU4OGEwNzRhM2JiM2UwJGFub3RoZXJpZCRhbmRhbm90aGVy");
|
||||||
|
|
||||||
|
assertEquals("captures the elasticsearch host", "ac31ebb90241773157043c34fd26fd46.us-central1.gcp.cloud.es.io:443", sut.getElasticsearchHost());
|
||||||
|
assertEquals("captures the kibana host", "a4c06230e48c8fce7be88a074a3bb3e0.us-central1.gcp.cloud.es.io:443", sut.getKibanaHost());
|
||||||
|
assertArrayEquals("captures the remaining identifiers", new String[] {"anotherid", "andanother"}, sut.getOtherIdentifiers());
|
||||||
|
}
|
||||||
|
|
||||||
|
// when given acceptable input (with empty kibana uuid), the accessors:
|
||||||
|
@Test
|
||||||
|
public void testGivenAcceptableInputEmptyKibanaUUID() {
|
||||||
|
input = "a-test:ZWNlLmhvbWUubGFuJHRlc3Qk";
|
||||||
|
sut = new CloudSettingId(input); // ece.home.lan$test$
|
||||||
|
|
||||||
|
assertEquals("#original has a value", input, sut.getOriginal());
|
||||||
|
assertEquals("#decoded has a value", "ece.home.lan$test$", sut.getDecoded());
|
||||||
|
assertEquals("#label has a value", "a-test", sut.getLabel());
|
||||||
|
assertEquals("#elasticsearch_host has a value", "test.ece.home.lan:443", sut.getElasticsearchHost());
|
||||||
|
assertEquals("#elasticsearch_scheme has a value", "https", sut.getElasticsearchScheme());
|
||||||
|
// NOTE: kibana part is not relevant -> this is how python/beats(go) code behaves
|
||||||
|
assertEquals("#kibana_host has a value", ".ece.home.lan:443", sut.getKibanaHost());
|
||||||
|
assertEquals("#kibana_scheme has a value", "https", sut.getKibanaScheme());
|
||||||
|
assertEquals("#toString has a value of #decoded", sut.getDecoded(), sut.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// a lengthy real-world input, the accessors:
|
||||||
|
@Test
|
||||||
|
public void testWithRealWorldInput() {
|
||||||
|
//eastus2.azure.elastic-cloud.com:9243$40b343116cfa4ebcb76c11ee2329f92d$43d09252502c4189a376fd0cf2cd0848
|
||||||
|
input = "ZWFzdHVzMi5henVyZS5lbGFzdGljLWNsb3VkLmNvbTo5MjQzJDQwYjM0MzExNmNmYTRlYmNiNzZjMTFlZTIzMjlmOTJkJDQzZDA5MjUyNTAyYzQxODlhMzc2ZmQwY2YyY2QwODQ4";
|
||||||
|
sut = new CloudSettingId(input);
|
||||||
|
|
||||||
|
assertEquals("#original has a value", input, sut.getOriginal());
|
||||||
|
assertEquals("#decoded has a value", "eastus2.azure.elastic-cloud.com:9243$40b343116cfa4ebcb76c11ee2329f92d$43d09252502c4189a376fd0cf2cd0848", sut.getDecoded());
|
||||||
|
assertEquals("#label has a value", "", sut.getLabel());
|
||||||
|
assertEquals("#elasticsearch_host has a value", "40b343116cfa4ebcb76c11ee2329f92d.eastus2.azure.elastic-cloud.com:9243", sut.getElasticsearchHost());
|
||||||
|
assertEquals("#kibana_host has a value", "43d09252502c4189a376fd0cf2cd0848.eastus2.azure.elastic-cloud.com:9243", sut.getKibanaHost());
|
||||||
|
assertEquals("#toString has a value of #decoded", sut.getDecoded(), sut.toString());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue