envless-sdk 0.0.1
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 +7 -0
- data/CHANGELOG.md +24 -0
- data/README.md +171 -0
- data/lib/envless/sdk/api_error.rb +54 -0
- data/lib/envless/sdk/bodies.rb +49 -0
- data/lib/envless/sdk/client.rb +39 -0
- data/lib/envless/sdk/clock.rb +24 -0
- data/lib/envless/sdk/connection_pool.rb +83 -0
- data/lib/envless/sdk/connector.rb +26 -0
- data/lib/envless/sdk/constants.rb +262 -0
- data/lib/envless/sdk/crypto/cipher.rb +58 -0
- data/lib/envless/sdk/crypto/codec.rb +23 -0
- data/lib/envless/sdk/crypto/envelope.rb +41 -0
- data/lib/envless/sdk/crypto/keys.rb +42 -0
- data/lib/envless/sdk/crypto/passphrase.rb +38 -0
- data/lib/envless/sdk/crypto/passphrase_rules.rb +42 -0
- data/lib/envless/sdk/crypto/values.rb +31 -0
- data/lib/envless/sdk/crypto/workspace_key.rb +40 -0
- data/lib/envless/sdk/crypto_messages.rb +31 -0
- data/lib/envless/sdk/default_client.rb +55 -0
- data/lib/envless/sdk/defaults.rb +32 -0
- data/lib/envless/sdk/envelope.rb +48 -0
- data/lib/envless/sdk/environment.rb +42 -0
- data/lib/envless/sdk/errors.rb +23 -0
- data/lib/envless/sdk/exchange.rb +27 -0
- data/lib/envless/sdk/helpers.rb +49 -0
- data/lib/envless/sdk/http.rb +46 -0
- data/lib/envless/sdk/listing.rb +54 -0
- data/lib/envless/sdk/messages.rb +43 -0
- data/lib/envless/sdk/net_http_adapter.rb +84 -0
- data/lib/envless/sdk/numbers.rb +44 -0
- data/lib/envless/sdk/options.rb +56 -0
- data/lib/envless/sdk/pagination.rb +31 -0
- data/lib/envless/sdk/paths.rb +70 -0
- data/lib/envless/sdk/received.rb +28 -0
- data/lib/envless/sdk/redirects.rb +56 -0
- data/lib/envless/sdk/resource.rb +50 -0
- data/lib/envless/sdk/resources/environments.rb +61 -0
- data/lib/envless/sdk/resources/invites.rb +28 -0
- data/lib/envless/sdk/resources/keys.rb +36 -0
- data/lib/envless/sdk/resources/me.rb +11 -0
- data/lib/envless/sdk/resources/members.rb +36 -0
- data/lib/envless/sdk/resources/product_access.rb +30 -0
- data/lib/envless/sdk/resources/products.rb +62 -0
- data/lib/envless/sdk/resources/projects.rb +42 -0
- data/lib/envless/sdk/resources/roles.rb +40 -0
- data/lib/envless/sdk/resources/variable_changes.rb +57 -0
- data/lib/envless/sdk/resources/variables.rb +49 -0
- data/lib/envless/sdk/resources/versions.rb +60 -0
- data/lib/envless/sdk/resources/webhook_deliveries.rb +33 -0
- data/lib/envless/sdk/resources/webhooks.rb +44 -0
- data/lib/envless/sdk/resources/workspace.rb +26 -0
- data/lib/envless/sdk/resources/workspace_domain.rb +31 -0
- data/lib/envless/sdk/resources/workspaces.rb +24 -0
- data/lib/envless/sdk/results.rb +43 -0
- data/lib/envless/sdk/retry_policy.rb +36 -0
- data/lib/envless/sdk/rotation/entry_rotator.rb +61 -0
- data/lib/envless/sdk/rotation/key_ring.rb +34 -0
- data/lib/envless/sdk/rotation/ledger.rb +36 -0
- data/lib/envless/sdk/rotation/runner.rb +45 -0
- data/lib/envless/sdk/rotation/variable_rotator.rb +38 -0
- data/lib/envless/sdk/text.rb +71 -0
- data/lib/envless/sdk/transport.rb +62 -0
- data/lib/envless/sdk/update_notice.rb +66 -0
- data/lib/envless/sdk/urls.rb +70 -0
- data/lib/envless/sdk/variable_names.rb +55 -0
- data/lib/envless/sdk/version.rb +5 -0
- data/lib/envless/sdk/webhooks/headers.rb +49 -0
- data/lib/envless/sdk/webhooks/verify.rb +58 -0
- data/lib/envless/sdk/wire.rb +77 -0
- data/lib/envless/sdk.rb +77 -0
- data/sig/envless/sdk/adapter.rbs +60 -0
- data/sig/envless/sdk/client.rbs +31 -0
- data/sig/envless/sdk/crypto.rbs +92 -0
- data/sig/envless/sdk/errors.rbs +37 -0
- data/sig/envless/sdk/http.rbs +31 -0
- data/sig/envless/sdk/pagination.rbs +81 -0
- data/sig/envless/sdk/results.rbs +62 -0
- data/sig/envless/sdk/rotation.rbs +86 -0
- data/sig/envless/sdk/settings.rbs +81 -0
- data/sig/envless/sdk/stdlib.rbs +8 -0
- data/sig/envless/sdk/text.rbs +52 -0
- data/sig/envless/sdk/transport.rbs +79 -0
- data/sig/envless/sdk/urls.rbs +73 -0
- data/sig/envless/sdk/webhooks.rbs +23 -0
- data/sig/envless/sdk.rbs +36 -0
- data/sig/generated/constants.rbs +57 -0
- data/sig/generated/resources.rbs +209 -0
- data/sig/generated/types.rbs +466 -0
- data/sig/manifest.yaml +3 -0
- metadata +134 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Envless
|
|
4
|
+
module Urls
|
|
5
|
+
SCHEMES = %w[http https].freeze
|
|
6
|
+
TRAILING_SLASHES = %r{/+\z}
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def valid_base_url(value)
|
|
11
|
+
text = value.is_a?(URI::Generic) ? value.to_s : value
|
|
12
|
+
raise ArgumentError, "#{value.inspect} #{Messages::BASE_URL_SHAPE}" unless text.is_a?(String)
|
|
13
|
+
|
|
14
|
+
trimmed = Text.js_trim(text).sub(TRAILING_SLASHES, "")
|
|
15
|
+
uri = parse(trimmed)
|
|
16
|
+
raise ArgumentError, "#{value.inspect} #{Messages::BASE_URL_SHAPE}" unless usable?(uri)
|
|
17
|
+
raise ArgumentError, Messages::BASE_URL_USERINFO unless uri&.userinfo.nil?
|
|
18
|
+
|
|
19
|
+
trimmed
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def usable?(uri)
|
|
23
|
+
return false if uri.nil?
|
|
24
|
+
|
|
25
|
+
SCHEMES.include?(uri.scheme.to_s.downcase) && !uri.host.to_s.empty? && uri.query.nil? && uri.fragment.nil?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def parse(text)
|
|
29
|
+
URI.parse(text)
|
|
30
|
+
rescue URI::InvalidURIError
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def api_url(base_url, path)
|
|
35
|
+
unless path.is_a?(String) && path.start_with?("/") && !path.start_with?("//", "/\\")
|
|
36
|
+
raise ArgumentError, "#{path.inspect} #{Messages::PATH_SHAPE}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
"#{base_url}#{path}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def build_url(base_url, path, query = nil)
|
|
43
|
+
url = api_url(base_url, path)
|
|
44
|
+
pairs = (query || {}).filter_map do |key, value|
|
|
45
|
+
text = value.nil? ? "" : query_value(value)
|
|
46
|
+
"#{form(key.to_s)}=#{form(text)}" unless text.empty?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
pairs.empty? ? url : "#{url}?#{pairs.join("&")}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def query_value(value)
|
|
53
|
+
case value
|
|
54
|
+
when String, Symbol then value.to_s
|
|
55
|
+
when true, false, Integer then value.to_s
|
|
56
|
+
when Float then Numbers.text(value)
|
|
57
|
+
else raise ArgumentError, format(Messages::QUERY_SHAPE, kind: value.class)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def form(text)
|
|
62
|
+
URI.encode_www_form_component(Text.utf8(text))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def origin(url)
|
|
66
|
+
uri = URI.parse(url)
|
|
67
|
+
[uri.scheme.to_s.downcase, uri.host.to_s.downcase, uri.port]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Envless
|
|
4
|
+
module VariableNames
|
|
5
|
+
LIMITS = Constants::INPUT_LIMITS[:VARIABLE_NAME]
|
|
6
|
+
ERRORS = Constants::VARIABLE_NAME_ERRORS
|
|
7
|
+
REFUSAL = Constants::LOCAL_REFUSAL
|
|
8
|
+
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def validation(name)
|
|
12
|
+
text = (name.is_a?(String) || name.is_a?(Symbol)) ? Text.utf8(name.to_s) : nil
|
|
13
|
+
return refused(ERRORS[:REQUIRED]) if text.nil? || Text.utf16_length(text) < LIMITS[:MIN]
|
|
14
|
+
return refused(ERRORS[:TOO_LONG].sub("{{count}}", LIMITS[:MAX].to_s)) if Text.utf16_length(text) > LIMITS[:MAX]
|
|
15
|
+
return refused(ERRORS[:SHAPE]) unless Constants::ENV_KEY_PATTERN.match?(text)
|
|
16
|
+
|
|
17
|
+
Validation.new(valid: true, error: nil)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def refused(error)
|
|
21
|
+
Validation.new(valid: false, error:)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def assert(name)
|
|
25
|
+
check = validation(name)
|
|
26
|
+
return if check.valid?
|
|
27
|
+
|
|
28
|
+
raise ApiError.new("#{check.error} Received: #{received(name)}", status: REFUSAL[:STATUS],
|
|
29
|
+
code: Constants::ERROR_CODES[:BODY_INVALID], resource: REFUSAL[:VARIABLE_RESOURCE], field: REFUSAL[:NAME_FIELD])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def received(name)
|
|
33
|
+
return "\"#{name}\"" if name.is_a?(String) || name.is_a?(Symbol)
|
|
34
|
+
|
|
35
|
+
name.nil? ? "nil" : name.to_s
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def assert_input(body)
|
|
39
|
+
assert(Bodies.field(body, "name"))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def assert_patch(patch)
|
|
43
|
+
assert(Bodies.field(patch, "name")) if Bodies.field?(patch, "name")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def assert_inputs(items)
|
|
47
|
+
items.each { |item| assert_input(item) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def assert_changes(changes)
|
|
51
|
+
creates = Bodies.field(changes, "creates")
|
|
52
|
+
assert_inputs(Bodies.list(creates, "creates")) if Wire.truthy?(creates)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Envless
|
|
4
|
+
module Webhooks
|
|
5
|
+
module Headers
|
|
6
|
+
SEPARATOR = / /
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def read(headers, name)
|
|
11
|
+
value = (headers.respond_to?(:each_pair) || headers.is_a?(Array)) ? scan(headers, name) : lookup(headers, name)
|
|
12
|
+
value = value.first if value.is_a?(Array)
|
|
13
|
+
value.nil? ? "" : Text.utf8(value.to_s)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def scan(headers, name)
|
|
17
|
+
wanted = name.downcase
|
|
18
|
+
rack = "HTTP_#{name.upcase.tr("-", "_")}"
|
|
19
|
+
pairs = headers.is_a?(Array) ? headers : headers.each_pair
|
|
20
|
+
|
|
21
|
+
pairs.each do |pair|
|
|
22
|
+
key, value = pair
|
|
23
|
+
return value if key.to_s.downcase == wanted || key.to_s == rack
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def lookup(headers, name)
|
|
30
|
+
return nil if headers.nil? || headers.is_a?(Numeric) || headers.is_a?(String) || !headers.respond_to?(:[])
|
|
31
|
+
|
|
32
|
+
headers[name]
|
|
33
|
+
rescue ArgumentError, TypeError, NoMethodError
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def candidates(provided)
|
|
38
|
+
provided.split(SEPARATOR, -1).filter_map do |part|
|
|
39
|
+
version, candidate = part.split(Constants::WEBHOOK_SIGNATURE[:SEPARATOR], -1)
|
|
40
|
+
candidate.to_s if version == Constants::WEBHOOK_SIGNATURE[:VERSION]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def same?(expected, candidate)
|
|
45
|
+
candidate.bytesize == expected.bytesize && OpenSSL.fixed_length_secure_compare(expected, candidate)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Envless
|
|
4
|
+
module Webhooks
|
|
5
|
+
module Verify
|
|
6
|
+
HEADERS = Constants::WEBHOOK_HEADERS
|
|
7
|
+
MESSAGES = CryptoMessages
|
|
8
|
+
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def call(payload:, headers:, secret:, tolerance_seconds: nil)
|
|
12
|
+
raise ArgumentError, MESSAGES::WEBHOOK_PAYLOAD_SHAPE unless payload.is_a?(String)
|
|
13
|
+
|
|
14
|
+
tolerance = tolerance_of(tolerance_seconds)
|
|
15
|
+
id = Headers.read(headers, HEADERS[:ID])
|
|
16
|
+
timestamp = Headers.read(headers, HEADERS[:TIMESTAMP])
|
|
17
|
+
provided = Headers.read(headers, HEADERS[:SIGNATURE])
|
|
18
|
+
raise WebhookVerificationError, MESSAGES::WEBHOOK_MISSING_HEADERS if id.empty? || timestamp.empty? || provided.empty?
|
|
19
|
+
|
|
20
|
+
fresh!(timestamp, tolerance)
|
|
21
|
+
raise WebhookVerificationError, MESSAGES::WEBHOOK_MISSING_SECRET unless secret.is_a?(String) && !secret.empty?
|
|
22
|
+
|
|
23
|
+
body = payload.b
|
|
24
|
+
expected = Crypto::Codec.to_base64(OpenSSL::HMAC.digest("SHA256", Text.utf8(secret), "#{id}.#{timestamp}.".b + body))
|
|
25
|
+
matched = Headers.candidates(provided).any? { |candidate| Headers.same?(expected, candidate) }
|
|
26
|
+
raise WebhookVerificationError, MESSAGES::WEBHOOK_MISMATCH unless matched
|
|
27
|
+
|
|
28
|
+
parsed(body)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def tolerance_of(value)
|
|
32
|
+
return Constants::WEBHOOK_SIGNATURE[:TOLERANCE_SECONDS] if value.nil?
|
|
33
|
+
|
|
34
|
+
number = value.is_a?(Integer) || value.is_a?(Float) || value.is_a?(Rational)
|
|
35
|
+
return value if number && value.finite? && value >= 0
|
|
36
|
+
|
|
37
|
+
raise ArgumentError, MESSAGES::WEBHOOK_TOLERANCE_SHAPE
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def fresh!(timestamp, tolerance)
|
|
41
|
+
seconds = Numbers.js_number(timestamp)
|
|
42
|
+
raise WebhookVerificationError, MESSAGES::WEBHOOK_TIMESTAMP_SHAPE unless seconds.finite?
|
|
43
|
+
|
|
44
|
+
drift = (Time.now.to_i - seconds).abs
|
|
45
|
+
return unless drift > tolerance
|
|
46
|
+
|
|
47
|
+
raise WebhookVerificationError, format(MESSAGES::WEBHOOK_OUT_OF_TOLERANCE, drift: Numbers.text(drift),
|
|
48
|
+
tolerance: Numbers.text(tolerance))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parsed(body)
|
|
52
|
+
Wire.decode(Text.utf8(body))
|
|
53
|
+
rescue JSON::ParserError
|
|
54
|
+
raise WebhookVerificationError, MESSAGES::WEBHOOK_NOT_JSON
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Envless
|
|
4
|
+
module Wire
|
|
5
|
+
LONE_SURROGATE = /\\u(d[89ab]\h\h)(\\u(d[c-f]\h\h))?|\\u(d[c-f]\h\h)|\\./i
|
|
6
|
+
SURROGATE_ESCAPE = /\\u[dD][89a-fA-F]/
|
|
7
|
+
REPLACEMENT_ESCAPE = "\\ufffd"
|
|
8
|
+
ISO_INSTANT = "%Y-%m-%dT%H:%M:%S.%LZ"
|
|
9
|
+
MAX_DEPTH = 100
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def encode(value)
|
|
14
|
+
JSON.generate(ready(value), max_nesting: false)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def ready(value, depth = 0)
|
|
18
|
+
raise ArgumentError, Messages::JSON_DEPTH if depth > MAX_DEPTH
|
|
19
|
+
|
|
20
|
+
case value
|
|
21
|
+
when Hash then value.to_h { |key, item| [key_text(key), ready(item, depth + 1)] }
|
|
22
|
+
when Array, Set then value.map { |item| ready(item, depth + 1) }
|
|
23
|
+
when String, Symbol then Text.utf8(value.to_s)
|
|
24
|
+
when Integer, true, false, nil then value
|
|
25
|
+
when Float then finite(value)
|
|
26
|
+
else instant(value) || ready(structure(value), depth + 1)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def key_text(key)
|
|
31
|
+
return Text.utf8(key.to_s) if key.is_a?(String) || key.is_a?(Symbol) || key.is_a?(Integer)
|
|
32
|
+
|
|
33
|
+
raise ArgumentError, format(Messages::JSON_KEY_SHAPE, kind: key.class)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def finite(value)
|
|
37
|
+
return value if value.finite?
|
|
38
|
+
|
|
39
|
+
raise ArgumentError, format(Messages::NOT_FINITE, value:)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def instant(value)
|
|
43
|
+
return value.getutc.strftime(ISO_INSTANT) if value.is_a?(Time)
|
|
44
|
+
return value.new_offset("+00:00").strftime(ISO_INSTANT) if value.is_a?(DateTime)
|
|
45
|
+
|
|
46
|
+
value.iso8601 if value.is_a?(Date)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def structure(value)
|
|
50
|
+
return value.to_hash if value.respond_to?(:to_hash)
|
|
51
|
+
return value.to_h if value.is_a?(Struct) || value.is_a?(Data)
|
|
52
|
+
|
|
53
|
+
raise ArgumentError, format(Messages::JSON_SHAPE, kind: value.class)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def decode(text)
|
|
57
|
+
JSON.parse(SURROGATE_ESCAPE.match?(text) ? without_lone_surrogates(text) : text, symbolize_names: true)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def without_lone_surrogates(text)
|
|
61
|
+
text.gsub(LONE_SURROGATE) do |escape|
|
|
62
|
+
high, pair, _low, lone_low = Regexp.last_match&.captures
|
|
63
|
+
next escape if high.nil? && lone_low.nil?
|
|
64
|
+
next escape if high && pair
|
|
65
|
+
|
|
66
|
+
REPLACEMENT_ESCAPE
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def truthy?(value)
|
|
71
|
+
return false if value.nil? || value == false || value == ""
|
|
72
|
+
return !value.zero? && !(value.is_a?(Float) && value.nan?) if value.is_a?(Numeric)
|
|
73
|
+
|
|
74
|
+
true
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
data/lib/envless/sdk.rb
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require "json"
|
|
5
|
+
require "net/http"
|
|
6
|
+
require "openssl"
|
|
7
|
+
require "timeout"
|
|
8
|
+
require "uri"
|
|
9
|
+
require "zlib"
|
|
10
|
+
|
|
11
|
+
require_relative "sdk/version"
|
|
12
|
+
require_relative "sdk/constants"
|
|
13
|
+
require_relative "sdk/defaults"
|
|
14
|
+
require_relative "sdk/messages"
|
|
15
|
+
require_relative "sdk/crypto_messages"
|
|
16
|
+
require_relative "sdk/errors"
|
|
17
|
+
require_relative "sdk/api_error"
|
|
18
|
+
require_relative "sdk/text"
|
|
19
|
+
require_relative "sdk/numbers"
|
|
20
|
+
require_relative "sdk/wire"
|
|
21
|
+
require_relative "sdk/results"
|
|
22
|
+
require_relative "sdk/http"
|
|
23
|
+
require_relative "sdk/clock"
|
|
24
|
+
require_relative "sdk/options"
|
|
25
|
+
require_relative "sdk/urls"
|
|
26
|
+
require_relative "sdk/paths"
|
|
27
|
+
require_relative "sdk/environment"
|
|
28
|
+
require_relative "sdk/envelope"
|
|
29
|
+
require_relative "sdk/retry_policy"
|
|
30
|
+
require_relative "sdk/redirects"
|
|
31
|
+
require_relative "sdk/exchange"
|
|
32
|
+
require_relative "sdk/connector"
|
|
33
|
+
require_relative "sdk/connection_pool"
|
|
34
|
+
require_relative "sdk/received"
|
|
35
|
+
require_relative "sdk/net_http_adapter"
|
|
36
|
+
require_relative "sdk/update_notice"
|
|
37
|
+
require_relative "sdk/transport"
|
|
38
|
+
require_relative "sdk/listing"
|
|
39
|
+
require_relative "sdk/pagination"
|
|
40
|
+
require_relative "sdk/bodies"
|
|
41
|
+
require_relative "sdk/variable_names"
|
|
42
|
+
require_relative "sdk/resource"
|
|
43
|
+
require_relative "sdk/resources/me"
|
|
44
|
+
require_relative "sdk/resources/workspace_domain"
|
|
45
|
+
require_relative "sdk/resources/workspace"
|
|
46
|
+
require_relative "sdk/resources/workspaces"
|
|
47
|
+
require_relative "sdk/resources/product_access"
|
|
48
|
+
require_relative "sdk/resources/products"
|
|
49
|
+
require_relative "sdk/resources/projects"
|
|
50
|
+
require_relative "sdk/resources/environments"
|
|
51
|
+
require_relative "sdk/resources/variables"
|
|
52
|
+
require_relative "sdk/resources/variable_changes"
|
|
53
|
+
require_relative "sdk/resources/versions"
|
|
54
|
+
require_relative "sdk/resources/members"
|
|
55
|
+
require_relative "sdk/resources/invites"
|
|
56
|
+
require_relative "sdk/resources/roles"
|
|
57
|
+
require_relative "sdk/resources/keys"
|
|
58
|
+
require_relative "sdk/resources/webhooks"
|
|
59
|
+
require_relative "sdk/resources/webhook_deliveries"
|
|
60
|
+
require_relative "sdk/client"
|
|
61
|
+
require_relative "sdk/default_client"
|
|
62
|
+
require_relative "sdk/crypto/codec"
|
|
63
|
+
require_relative "sdk/crypto/envelope"
|
|
64
|
+
require_relative "sdk/crypto/workspace_key"
|
|
65
|
+
require_relative "sdk/crypto/keys"
|
|
66
|
+
require_relative "sdk/crypto/cipher"
|
|
67
|
+
require_relative "sdk/crypto/values"
|
|
68
|
+
require_relative "sdk/crypto/passphrase_rules"
|
|
69
|
+
require_relative "sdk/crypto/passphrase"
|
|
70
|
+
require_relative "sdk/rotation/key_ring"
|
|
71
|
+
require_relative "sdk/rotation/ledger"
|
|
72
|
+
require_relative "sdk/rotation/variable_rotator"
|
|
73
|
+
require_relative "sdk/rotation/entry_rotator"
|
|
74
|
+
require_relative "sdk/rotation/runner"
|
|
75
|
+
require_relative "sdk/webhooks/headers"
|
|
76
|
+
require_relative "sdk/webhooks/verify"
|
|
77
|
+
require_relative "sdk/helpers"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module Envless
|
|
2
|
+
module Connector
|
|
3
|
+
def self?.open: (URI::Generic uri, Float? timeout, Integer | Float keep_alive_timeout) -> Net::HTTP
|
|
4
|
+
def self?.decoded: (String? text) -> String?
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class ConnectionPool
|
|
8
|
+
CLOSE_ERRORS: Array[singleton(Exception)]
|
|
9
|
+
|
|
10
|
+
@max_idle: Integer
|
|
11
|
+
@keep_alive_timeout: Integer | Float
|
|
12
|
+
@idle: Hash[[String, String, Integer?], Array[Net::HTTP]]
|
|
13
|
+
@lock: Thread::Mutex
|
|
14
|
+
@pid: Integer
|
|
15
|
+
|
|
16
|
+
def initialize: (max_idle: Integer, keep_alive_timeout: Integer | Float) -> void
|
|
17
|
+
def checkout: (URI::Generic uri, Float? timeout) -> [Net::HTTP, bool]
|
|
18
|
+
def checkin: (URI::Generic uri, Net::HTTP connection) -> void
|
|
19
|
+
def discard: (Net::HTTP connection) -> void
|
|
20
|
+
def close: () -> nil
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def take: (URI::Generic uri) -> Net::HTTP?
|
|
25
|
+
def usable?: (Net::HTTP connection) -> bool
|
|
26
|
+
def forget_inherited: () -> void
|
|
27
|
+
def key: (URI::Generic uri) -> [String, String, Integer?]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class Received
|
|
31
|
+
@body: String
|
|
32
|
+
|
|
33
|
+
attr_accessor head: Net::HTTPResponse?
|
|
34
|
+
|
|
35
|
+
def initialize: () -> void
|
|
36
|
+
def append: (String chunk) -> String
|
|
37
|
+
def truncate: () -> String
|
|
38
|
+
def response: () -> HttpResponse
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class NetHttpAdapter
|
|
42
|
+
STALE_ERRORS: Array[singleton(Exception)]
|
|
43
|
+
BODY_ERRORS: Array[singleton(Exception)]
|
|
44
|
+
EMPTY_WRITES: Array[String]
|
|
45
|
+
|
|
46
|
+
@pool: ConnectionPool
|
|
47
|
+
|
|
48
|
+
def initialize: (?max_idle: Integer, ?keep_alive_timeout: Integer | Float) -> void
|
|
49
|
+
def call: (HttpRequest request) -> HttpResponse
|
|
50
|
+
def close: () -> nil
|
|
51
|
+
def inspect: () -> String
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def attempt: (URI::HTTP uri, HttpRequest request, Float? deadline, bool retried) -> HttpResponse
|
|
56
|
+
def transfer: (Net::HTTP connection, Net::HTTPGenericRequest outbound, Received received, Float? deadline) -> bool
|
|
57
|
+
def outbound: (URI::HTTP uri, HttpRequest request) -> Net::HTTPGenericRequest
|
|
58
|
+
def limit: (Net::HTTP connection, Float? deadline) -> void
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Envless
|
|
2
|
+
class Client
|
|
3
|
+
attr_reader raw: Transport
|
|
4
|
+
|
|
5
|
+
def initialize: (?String? token_argument, ?token: String?, ?base_url: (String | URI::Generic)?, ?adapter: _Adapter?, ?max_retries: Integer?, ?timeout: seconds?, ?user_agent: String?, ?disable_update_notice: bool) -> void
|
|
6
|
+
def close: () -> nil
|
|
7
|
+
def inspect: () -> String
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module Resources
|
|
11
|
+
class Workspace < Resource
|
|
12
|
+
@domain: WorkspaceDomain
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class WorkspaceDomain < Resource
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def domain_of: (untyped data) -> custom_domain_resource?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Products < Resource
|
|
22
|
+
@access: ProductAccess
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class Environments < Resource
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def set_exposed: (String project, String environment, bool exposed, (String | Symbol)? product, seconds? timeout) -> environment_resource
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module Envless
|
|
2
|
+
class WorkspaceKey
|
|
3
|
+
BITS: Integer
|
|
4
|
+
BYTES: Integer
|
|
5
|
+
|
|
6
|
+
@raw: String
|
|
7
|
+
|
|
8
|
+
def initialize: (String raw) -> void
|
|
9
|
+
def to_bytes: () -> String
|
|
10
|
+
def ==: (untyped other) -> bool
|
|
11
|
+
def eql?: (untyped other) -> bool
|
|
12
|
+
def hash: () -> Integer
|
|
13
|
+
def inspect: () -> String
|
|
14
|
+
def to_s: () -> String
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module Crypto
|
|
18
|
+
type key = WorkspaceKey | String
|
|
19
|
+
|
|
20
|
+
class CiphertextEnvelope < Data
|
|
21
|
+
attr_reader payload: String
|
|
22
|
+
attr_reader iterations: Integer?
|
|
23
|
+
|
|
24
|
+
def self.new: (String payload, Integer? iterations) -> CiphertextEnvelope
|
|
25
|
+
| (payload: String, iterations: Integer?) -> CiphertextEnvelope
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module Codec
|
|
29
|
+
def self?.to_base64: (String bytes) -> String
|
|
30
|
+
def self?.from_base64: (String value) -> String
|
|
31
|
+
def self?.from_base64_url: (String value) -> String
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module Envelope
|
|
35
|
+
CRYPTO: untyped
|
|
36
|
+
V1: String
|
|
37
|
+
V2: String
|
|
38
|
+
UNSUPPORTED: String
|
|
39
|
+
|
|
40
|
+
def self?.parse: (untyped encoded) -> CiphertextEnvelope
|
|
41
|
+
def self?.versioned: (String rest) -> CiphertextEnvelope
|
|
42
|
+
def self?.iterations: (String text) -> Integer
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module Keys
|
|
46
|
+
CRYPTO: untyped
|
|
47
|
+
DIGEST: String
|
|
48
|
+
|
|
49
|
+
def self?.key_of: (untyped key) -> WorkspaceKey
|
|
50
|
+
def self?.workspace_salt: (String workspace_unique_id) -> String
|
|
51
|
+
def self?.derive_key: (untyped passphrase, String salt, ?Integer iterations) -> WorkspaceKey
|
|
52
|
+
def self?.derive_workspace_key: (untyped passphrase, untyped workspace_unique_id) -> WorkspaceKey
|
|
53
|
+
def self?.import_workspace_key: (untyped value) -> WorkspaceKey
|
|
54
|
+
def self?.text: (untyped value, String name) -> String
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
module Cipher
|
|
58
|
+
CRYPTO: untyped
|
|
59
|
+
ALGORITHM: String
|
|
60
|
+
TAG_BYTES: Integer
|
|
61
|
+
|
|
62
|
+
def self?.encrypt_with_key: (untyped key, untyped plaintext) -> String
|
|
63
|
+
def self?.decrypt: (untyped key, untyped encoded) -> String
|
|
64
|
+
def self?.unseal: (WorkspaceKey key, String iv, String sealed) -> String
|
|
65
|
+
def self?.decrypt_with_key: (untyped key, untyped ciphertext) -> String
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
module Values
|
|
69
|
+
def self?.encrypt_value: (untyped plaintext, untyped passphrase, untyped workspace_unique_id) -> String
|
|
70
|
+
def self?.decrypt_value: (untyped ciphertext, untyped passphrase, untyped workspace_unique_id) -> String
|
|
71
|
+
def self?.ciphertext?: (untyped value) -> bool
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
module Passphrase
|
|
75
|
+
RULES: untyped
|
|
76
|
+
ERRORS: untyped
|
|
77
|
+
|
|
78
|
+
def self?.strength: (untyped passphrase) -> Validation
|
|
79
|
+
def self?.refused: (String error) -> Validation
|
|
80
|
+
def self?.with_count: (String message, Integer count) -> String
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
module PassphraseRules
|
|
84
|
+
RULES: untyped
|
|
85
|
+
NOT_ALPHANUMERIC: Regexp
|
|
86
|
+
|
|
87
|
+
def self?.repeated_run?: (Array[Integer] units) -> bool
|
|
88
|
+
def self?.sequential_run?: (Array[Integer] units) -> bool
|
|
89
|
+
def self?.weak_fragment?: (String text) -> bool
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Envless
|
|
2
|
+
class Error < StandardError
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
class NetworkError < Error
|
|
6
|
+
@timeout: bool
|
|
7
|
+
|
|
8
|
+
def initialize: (?String? message, ?timeout: bool) -> void
|
|
9
|
+
def timeout?: () -> bool
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class DecryptionError < Error
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class WebhookVerificationError < Error
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class ApiError < Error
|
|
19
|
+
attr_reader status: Integer?
|
|
20
|
+
attr_reader code: String
|
|
21
|
+
attr_reader resource: String?
|
|
22
|
+
attr_reader field: String?
|
|
23
|
+
attr_reader request_id: String?
|
|
24
|
+
attr_reader retry_after_seconds: Numeric?
|
|
25
|
+
|
|
26
|
+
def initialize: (?String? message, ?status: Integer?, ?code: String, ?resource: String?, ?field: String?, ?request_id: String?, ?retry_after_seconds: Numeric?) -> void
|
|
27
|
+
def detailed_message: (?highlight: bool, **untyped options) -> String
|
|
28
|
+
def inspect: () -> String
|
|
29
|
+
def auth?: () -> bool
|
|
30
|
+
def scope_missing?: () -> bool
|
|
31
|
+
def validation?: () -> bool
|
|
32
|
+
def not_found?: () -> bool
|
|
33
|
+
def conflict?: () -> bool
|
|
34
|
+
def rate_limited?: () -> bool
|
|
35
|
+
def needs_upgrade?: () -> bool
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Envless
|
|
2
|
+
class HttpRequest < Data
|
|
3
|
+
attr_reader method: String
|
|
4
|
+
attr_reader url: String
|
|
5
|
+
attr_reader headers: Hash[String, String]
|
|
6
|
+
attr_reader body: String?
|
|
7
|
+
attr_reader timeout: seconds?
|
|
8
|
+
|
|
9
|
+
def self.new: (String method, String url, Hash[String, String] headers, String? body, seconds? timeout) -> HttpRequest
|
|
10
|
+
| (method: String, url: String, headers: Hash[String, String], body: String?, timeout: seconds?) -> HttpRequest
|
|
11
|
+
|
|
12
|
+
def with: (?method: String, ?url: String, ?headers: Hash[String, String], ?body: String?, ?timeout: seconds?) -> HttpRequest
|
|
13
|
+
def inspect: () -> String
|
|
14
|
+
def to_s: () -> String
|
|
15
|
+
def pretty_print: (untyped printer) -> void
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class HttpResponse < Data
|
|
19
|
+
attr_reader status: Integer
|
|
20
|
+
attr_reader headers: Hash[String, String]
|
|
21
|
+
attr_reader body: String
|
|
22
|
+
attr_reader reason: String
|
|
23
|
+
|
|
24
|
+
def self.new: (Integer status, Hash[String, String] headers, String body, String reason) -> HttpResponse
|
|
25
|
+
| (status: Integer, headers: Hash[String, String], body: String, reason: String) -> HttpResponse
|
|
26
|
+
|
|
27
|
+
def self.from: (untyped response) -> HttpResponse
|
|
28
|
+
def self.header_text: (untyped value) -> String
|
|
29
|
+
def header: (String name) -> String?
|
|
30
|
+
end
|
|
31
|
+
end
|