google-cloud-env-2.3.1/0000755000004100000410000000000015020727635014722 5ustar www-datawww-datagoogle-cloud-env-2.3.1/CONTRIBUTING.md0000644000004100000410000000512315020727635017154 0ustar www-datawww-data# Contributing to Google Cloud Ruby Client 1. **Sign one of the contributor license agreements below.** 2. Fork the repo, develop and test your code changes. 3. Send a pull request. ## Contributor License Agreements Before we can accept your pull requests you'll need to sign a Contributor License Agreement (CLA): - **If you are an individual writing original source code** and **you own the intellectual property**, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual). - **If you work for a company that wants to allow you to contribute your work**, then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate). You can sign these electronically (just scroll to the bottom). After that, we'll be able to accept your pull requests. ## Setup In order to use the google-cloud-ruby console and run the project's tests, there is a small amount of setup: 1. Install Ruby. google-cloud-env requires Ruby 2.4+. You may choose to manage your Ruby and gem installations with [RVM](https://rvm.io/), [rbenv](https://github.com/rbenv/rbenv), or [chruby](https://github.com/postmodern/chruby). 2. Install [Bundler](http://bundler.io/). ```sh $ gem install bundler ``` 3. Install the project dependencies. ```sh $ bundle install ``` ## Tests All contributions should include tests that ensure the contributed code behaves as expected. To run the tests and code style checks together: ``` sh $ bundle exec rake ci ``` ### Unit Tests The project uses the [minitest](https://github.com/seattlerb/minitest) library, including [specs](https://github.com/seattlerb/minitest#specs), [mocks](https://github.com/seattlerb/minitest#mocks) and [minitest-autotest](https://github.com/seattlerb/minitest-autotest). To run the unit tests for a package: ``` sh $ bundle exec rake test ``` ### Coding Style Please follow the established coding style in the library. The style is is largely based on [The Ruby Style Guide](https://github.com/bbatsov/ruby-style-guide) with a few exceptions based on seattle-style: * Avoid parenthesis when possible, including in method definitions. * Always use double quotes strings. ([Option B](https://github.com/bbatsov/ruby-style-guide#strings)) You can check your code against these rules by running Rubocop like so: ```sh $ bundle exec rake rubocop ``` ## Code of Conduct Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. See {file:CODE_OF_CONDUCT.md Code of Conduct} for more information. google-cloud-env-2.3.1/SECURITY.md0000644000004100000410000000051115020727635016510 0ustar www-datawww-data# Security Policy To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). The Google Security Team will respond within 5 working days of your report on g.co/vulnz. We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. google-cloud-env-2.3.1/lib/0000755000004100000410000000000015020727635015470 5ustar www-datawww-datagoogle-cloud-env-2.3.1/lib/google-cloud-env.rb0000644000004100000410000000117715020727635021171 0ustar www-datawww-data# Copyright 2017 Google LLC # # Licensed 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 # # https://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 "google/cloud/env" require "google/cloud/env/version" google-cloud-env-2.3.1/lib/google/0000755000004100000410000000000015020727635016744 5ustar www-datawww-datagoogle-cloud-env-2.3.1/lib/google/cloud/0000755000004100000410000000000015020727635020052 5ustar www-datawww-datagoogle-cloud-env-2.3.1/lib/google/cloud/env/0000755000004100000410000000000015020727635020642 5ustar www-datawww-datagoogle-cloud-env-2.3.1/lib/google/cloud/env/lazy_value.rb0000644000004100000410000012067015020727635023350 0ustar www-datawww-data# frozen_string_literal: true # Copyright 2023 Google LLC # # Licensed 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 # # https://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 "English" module Google module Cloud class Env ## # @private # # A lazy value box with thread-safe memoization. The first time accessed # it will call a given block to compute its value, and will cache that # value. Subsequent requests will return the cached value. # # At most one thread will be allowed to run the computation; if another # thread is already in the middle of a computation, any new threads # requesting the value will wait until the existing computation is # complete, and will use that computation's result rather than kicking # off their own computation. # # If a computation fails with an exception, that exception will also be # memoized and reraised on subsequent accesses. A LazyValue can also be # configured so subsequent accesses will retry the computation if the # previous computation failed. The maximum number of retries is # configurable, as is the retry "interval", i.e. the time since the last # failure before an access will retry the computation. # # By default, a computation's memoized value (or final error after # retries have been exhausted) is maintained for the lifetime of the Ruby # process. However, a computation can also cause its result (or error) to # expire after a specified number of seconds, forcing a recomputation on # the next access following expiration, by calling # {LazyValue.expiring_value} or {LazyValue.raise_expiring_error}. # # We keep this private for now so we can move it in the future if we need # it to be available to other libraries. Currently it should not be used # outside of Google::Cloud::Env. # class LazyValue class << self ## # Creates a special object that can be returned from a computation to # indicate that a value expires after the given number of seconds. # Any access after the expiration will cause a recomputation. # # @param lifetime [Numeric] timeout in seconds # @param value [Object] the computation result # def expiring_value lifetime, value return value unless lifetime ExpiringValue.new lifetime, value end ## # Raise an error that, if it is the final result (i.e. retries have # been exhausted), will expire after the given number of seconds. Any # access after the expiration will cause a recomputation. If retries # will not have been exhausted, expiration is ignored. # # The error can be specified as an exception object, a string (in # which case a RuntimeError will be raised), or a class that descends # from Exception (in which case an error of that type will be # created, and passed any additional args given). # # @param lifetime [Numeric] timeout in seconds # @param error [String,Exception,Class] the error to raise # @param args [Array] any arguments to pass to an error constructor # def raise_expiring_error lifetime, error, *args raise error unless lifetime raise ExpiringError, lifetime if error.equal? $ERROR_INFO if error.is_a?(Class) && error.ancestors.include?(Exception) error = error.new(*args) elsif !error.is_a? Exception error = RuntimeError.new error.to_s end begin raise error rescue error.class raise ExpiringError, lifetime end end end ## # Create a LazyValue. # # You must pass a block that will be called to compute the value the # first time it is accessed. The block should evaluate to the desired # value, or raise an exception on error. To specify a value that # expires, use {LazyValue.expiring_value}. To raise an exception that # expires, use {LazyValue.raise_expiring_error}. # # You can optionally pass a retry manager, which controls how # subsequent accesses might try calling the block again if a compute # attempt fails with an exception. A retry manager should either be an # instance of {Retries} or an object that duck types it. # # @param retries [Retries] A retry manager. The default is a retry # manager that tries only once. # @param block [Proc] A block that can be called to attempt to compute # the value. # def initialize retries: nil, &block @retries = retries || Retries.new @compute_handler = block raise ArgumentError, "missing compute handler block" unless block # Internally implemented by a state machine, protected by a mutex that # ensures state transitions are consistent. The states themselves are # implicit in the values of the various instance variables. The # following are the major states: # # 1. **Pending** The value is not known and needs to be computed. # @retries.finished? is false. # @value is nil. # @error is nil if no previous attempt has yet been made to # compute the value, or set to the error that resulted from # the most recent attempt. # @expires_at is set to the monotonic time of the end of the # current retry delay, or nil if the next computation attempt # should happen immediately at the next access. # @computing_thread is nil. # @compute_notify is nil. # @backfill_notify is set if currently backfilling, otherwise nil. # From this state, calling #get will start computation (first # waiting on @backfill_notify if present). Calling #expire! will # have no effect. # # 2. **Computing** One thread has initiated computation. All other # threads will be blocked (waiting on @compute_notify) until the # computing thread finishes. # @retries.finished? is false. # @value and @error are nil. # @expires_at is set to the monotonic time when computing started. # @computing_thread is set to the thread that is computing. # @compute_notify is set. # @backfill_notify is nil. # From this state, calling #get will cause the thread to wait # (on @compute_notify) for the computing thread to complete. # Calling #expire! will have no effect. # When the computing thread finishes, it will transition either # to Finished if the computation was successful or failed with # no more retries, or back to Pending if computation failed with # at least one retry remaining. It might also set @backfill_notify # if other threads are waiting for completion. # # 3. **Finished** Computation has succeeded, or has failed and no # more retries remain. # @retries.finished? is true. # either @value or @error is set, and the other is nil, depending # on whether the final state is success or failure. (If both # are nil, it is considered a @value of nil.) # @expires_at is set to the monotonic time of expiration, or nil # if there is no expiration. # @computing_thread is nil. # @compute_notify is nil. # @backfill_notify is set if currently backfilling, otherwise nil. # From this state, calling #get will either return the result or # raise the error. If the current time exceeds @expires_at, # however, it will block on @backfill_notify (if present), and # and then transition to Pending first, and proceed from there. # Calling #expire! will block on @backfill_notify (if present) # and then transition to Pending, # # @backfill_notify can be set in the Pending or Finished states. This # happens when threads that had been waiting on the previous # computation are still clearing out and returning their results. # Backfill must complete before the next computation attempt can be # started from the Pending state, or before an expiration can take # place from the Finished state. This prevents an "overlap" situation # where a thread that had been waiting for a previous computation, # isn't able to return the new result before some other thread starts # a new computation or expires the value. Note that it is okay for # #set! to be called during backfill; the threads still backfilling # will simply return the new value. # # Note: One might ask if it would be simpler to extend the mutex # across the entire computation, having it protect the computation # itself, instead of the current approach of having explicit compute # and backfill states with notifications and having the mutex protect # only the state transition. However, this would not have been able # to satisfy the requirement that we be able to detect whether a # thread asked for the value during another thread's computation, # and thus should "share" in that computation's result even if it's # a failure (rather than kicking off a retry). Additionally, we # consider it dangerous to have the computation block run inside a # mutex, because arbitrary code can run there which might result in # deadlocks. @mutex = Thread::Mutex.new # The evaluated, cached value, which could be nil. @value = nil # The last error encountered @error = nil # If non-nil, this is the CLOCK_MONOTONIC time when the current state # expires. If the state is finished, this is the time the current # value or error expires (while nil means it never expires). If the # state is pending, this is the time the wait period before the next # retry expires (and nil means there is no delay.) If the state is # computing, this is the time when computing started. @expires_at = nil # Set to a condition variable during computation. Broadcasts when the # computation is complete. Any threads wanting to get the value # during computation must wait on this first. @compute_notify = nil # Set to a condition variable during backfill. Broadcasts when the # last backfill thread is complete. Any threads wanting to expire the # cache or start a new computation during backfill must wait on this # first. @backfill_notify = nil # The number of threads waiting on backfill. Used to determine # whether to activate backfill_notify when a computation completes. @backfill_count = 0 # The thread running the current computation. This is tested against # new requests to protect against deadlocks where a thread tries to # re-enter from its own computation. This is also tested when a # computation completes, to ensure that the computation is still # relevant (i.e. if #set! interrupts a computation, this is reset to # nil). @computing_thread = nil end ## # Returns the value. This will either return the value or raise an # error indicating failure to compute the value. # # If the value was previously cached, it will return that cached value, # otherwise it will either run the computation to try to determine the # value, or wait for another thread that is already running the # computation. Thus, this method could block. # # Any arguments passed will be forwarded to the block if called, but # are ignored if a cached value is returned. # # @return [Object] the value # @raise [Exception] if an error happened while computing the value # def get *extra_args @mutex.synchronize do # Wait for any backfill to complete, and handle expiration first # because it might change the state. wait_backfill do_expire if should_expire? # Main state handling if @retries.finished? # finished state: return value or error return cached_value elsif !@compute_notify.nil? # computing state: wait for the computing thread to finish then # return its result wait_compute return cached_value else # pending state cur_time = Process.clock_gettime Process::CLOCK_MONOTONIC # waiting for the next retry: return current error raise @error if @expires_at && cur_time < @expires_at # no delay: compute in the current thread enter_compute cur_time # and continue below end end # Gets here if we just transitioned from pending to compute perform_compute extra_args end ## # This method calls {#get} repeatedly until a final result is available # or retries have exhausted. # # Note: this method spins on {#get}, although honoring any retry delay. # Thus, it is best to call this only if retries are limited or a retry # delay has been configured. # # @param extra_args [Array] extra arguments to pass to the block # @param transient_errors [Array] An array of exception classes # that will be treated as transient and will allow await to # continue retrying. Exceptions omitted from this list will be # treated as fatal errors and abort the call. Default is # `[StandardError]`. # @param max_tries [Integer,nil] The maximum number of times this will # call {#get} before giving up, or nil for a potentially unlimited # number of attempts. Default is 1. # @param max_time [Numeric,nil] The maximum time in seconds this will # spend before giving up, or nil (the default) for a potentially # unlimited timeout. # @param delay_epsilon [Numeric] An extra delay in seconds to ensure # that retries happen after the retry delay period # # @return [Object] the value # @raise [Exception] if a fatal error happened, or retries have been # exhausted. # def await *extra_args, transient_errors: nil, max_tries: 1, max_time: nil, delay_epsilon: 0.0001 transient_errors ||= [StandardError] transient_errors = Array transient_errors expiry_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + max_time if max_time begin get(*extra_args) rescue *transient_errors # A snapshot of the state. It is possible that another thread has # changed this state since we received the error. This is okay # because our specification for this method is conservative: # whatever we return will have been correct at some point. state = internal_state # Don't retry unless we're in a state where retries can happen. raise if [:failed, :success].include? state[0] if max_tries # Handle retry countdown max_tries -= 1 raise unless max_tries.positive? end # Determine the next delay delay = determine_await_retry_delay state, expiry_time, delay_epsilon # nil means we've exceeded the max time raise if delay.nil? sleep delay if delay.positive? retry end end ## # Returns the current low-level state immediately without waiting for # computation. Returns a 3-tuple (i.e. a 3-element array) in which the # first element is a symbol indicating the overall state, as described # below, and the second and third elements are set accordingly. # # States (the first tuple element) are: # * `:pending` - The value has not been computed, or previous # computation attempts have failed but there are retries pending. The # second element will be the most recent error, or nil if no # computation attempt has yet happened. The third element will be the # monotonic time of the end of the current retry delay, or nil if # there will be no delay. # * `:computing` - A thread is currently computing the value. The # second element is nil. The third elements is the monotonic time # when the computation started. # * `:success` - The computation is finished, and the value is returned # in the second element. The third element may be a numeric value # indicating the expiration monotonic time, or nil for no expiration. # * `:failed` - The computation failed finally and no more retries will # be done. The error is returned in the second element. The third # element may be a numeric value indicating the expiration monotonic # time, or nil for no expiration. # # Future updates may add array elements without warning. Callers should # be prepared to ignore additional unexpected elements. # # @return [Array] # def internal_state @mutex.synchronize do if @retries.finished? if @error [:failed, @error, @expires_at] else [:success, @value, @expires_at] end elsif @compute_notify.nil? [:pending, @error, @expires_at] else [:computing, nil, @expires_at] end end end ## # Force this cache to expire immediately, if computation is complete. # Any cached value will be cleared, the retry count is reset, and the # next access will call the compute block as if it were the first # access. Returns true if this took place. Has no effect and returns # false if the computation is not yet complete (i.e. if a thread is # currently computing, or if the last attempt failed and retries have # not yet been exhausted.) # # @return [true,false] whether the cache was expired # def expire! @mutex.synchronize do wait_backfill return false unless @retries.finished? do_expire true end end ## # Set the cache value explicitly and immediately. If a computation is # in progress, it is "detached" and its result will no longer be # considered. # # @param value [Object] the value to set # @param lifetime [Numeric] the lifetime until expiration in seconds, # or nil (the default) for no expiration. # @return [Object] the value # def set! value, lifetime: nil @mutex.synchronize do @value = value @expires_at = determine_expiry lifetime @error = nil @retries.finish! if @compute_notify.nil? enter_backfill leave_compute end value end end private ## # @private # Internal type signaling a value with an expiration # class ExpiringValue def initialize lifetime, value @lifetime = lifetime @value = value end attr_reader :lifetime attr_reader :value end ## # @private # Internal type signaling an error with an expiration. # class ExpiringError < StandardError def initialize lifetime super() @lifetime = lifetime end attr_reader :lifetime end ## # @private # Perform computation, and transition state on completion. # This must be called from outside the mutex. # Returns the final value, or raises the final error. # def perform_compute extra_args value = @compute_handler.call(*extra_args) @mutex.synchronize do handle_success value end rescue Exception => e # rubocop:disable Lint/RescueException @mutex.synchronize do handle_failure e end end ## # @private # Either return the cached value or raise the cached error. # This must be called from within the mutex. # def cached_value raise @error if @error @value end ## # @private # Determine whether we should expire a cached value and compute a new # one. Happens in the Finished state if @expires_at is in the past. # This must be called from within the mutex. # def should_expire? @retries.finished? && @expires_at && Process.clock_gettime(Process::CLOCK_MONOTONIC) >= @expires_at end ## # @private # Reset this cache, transitioning to the Pending state and resetting # the retry count. # This must be called from within the mutex. # def do_expire @retries.reset! @value = @error = @expires_at = nil end ## # @private # Wait for backfill to complete if it is in progress, otherwise just # return immediately. # This must be called from within the mutex. # def wait_backfill @backfill_notify.wait @mutex while @backfill_notify end ## # @private # Wait for computation to complete. # Also adds the current thread to the backfill list, ensuring that the # computing thread will enter the backfill phase on completion. Once # computation is done, also checks whether the current thread is the # last one to backfill, and if so, turns off backfill mode. # This must be called from within the mutex. # def wait_compute if Thread.current.equal? @computing_thread raise ThreadError, "deadlock: tried to call LazyValue#get from its own computation" end @backfill_count += 1 begin @compute_notify.wait @mutex ensure @backfill_count -= 1 leave_backfill end end ## # @private # Initializes compute mode. # This must be called from within the mutex. # def enter_compute cur_time @computing_thread = Thread.current @compute_notify = Thread::ConditionVariable.new @expires_at = cur_time @value = @error = nil end ## # @private # Finishes compute mode, notifying threads waiting on it. # This must be called from within the mutex. # def leave_compute @computing_thread = nil @compute_notify.broadcast @compute_notify = nil end ## # @private # Checks for any threads that need backfill, and if so triggers # backfill mode. # This must be called from within the mutex. # def enter_backfill return unless @backfill_count.positive? @backfill_notify = Thread::ConditionVariable.new end ## # @private # Checks whether all threads are done with backfill, and if so notifies # threads waiting for backfill to finish. # This must be called from within the mutex. # def leave_backfill return unless @backfill_count.zero? @backfill_notify.broadcast @backfill_notify = nil end ## # @private # Sets state to reflect a successful computation (as long as this # computation wasn't interrupted by someone calling #set!). # Then returns the computed value. # This must be called from within the mutex. # def handle_success value expires_at = nil if value.is_a? ExpiringValue expires_at = determine_expiry value.lifetime value = value.value end if Thread.current.equal? @computing_thread @retries.finish! @error = nil @value = value @expires_at = expires_at enter_backfill leave_compute end value end ## # @private # Sets state to reflect a failed computation (as long as this # computation wasn't interrupted by someone calling #set!). # Then raises the error. # This must be called from within the mutex. # def handle_failure error expires_at = nil if error.is_a? ExpiringError expires_at = determine_expiry error.lifetime error = error.cause end if Thread.current.equal? @computing_thread retry_delay = @retries.next start_time: @expires_at @value = nil @error = error @expires_at = if retry_delay.nil? # No more retries; use the expiration for the error expires_at elsif retry_delay.positive? determine_expiry retry_delay end enter_backfill leave_compute end raise error end ## # @private # Determines the delay until the next retry during an await # def determine_await_retry_delay state, expiry_time, delay_epsilon cur_time = Process.clock_gettime Process::CLOCK_MONOTONIC next_run_time = if state[0] == :pending && state[2] # Run at end of the current retry delay, plus an epsilon, # if in pending state state[2] + delay_epsilon else # Default to run immediately otherwise cur_time end # Signal nil if we're past the max time return nil if expiry_time && next_run_time > expiry_time # No delay if we're already past the time we want to run return 0 if next_run_time < cur_time next_run_time - cur_time end ## # @private # Determines the expires_at value in monotonic time, given a lifetime. # def determine_expiry lifetime lifetime ? Process.clock_gettime(Process::CLOCK_MONOTONIC) + lifetime : nil end end ## # @private # # This expands on {LazyValue} by providing a lazy key-value dictionary. # Each key uses a separate LazyValue; hence multiple keys can be in the # process of computation concurrently and independently. # # We keep this private for now so we can move it in the future if we need # it to be available to other libraries. Currently it should not be used # outside of Google::Cloud::Env. # class LazyDict ## # Create a LazyDict. # # You must pass a block that will be called to compute the value the # first time it is accessed. The block takes the key as an argument and # should evaluate to the value for that key, or raise an exception on # error. To specify a value that expires, use # {LazyValue.expiring_value}. To raise an exception that expires, use # {LazyValue.raise_expiring_error}. # # You can optionally pass a retry manager, which controls how # subsequent accesses might try calling the block again if a compute # attempt fails with an exception. A retry manager should either be an # instance of {Retries} or an object that duck types it. # # @param retries [Retries,Proc] A retry manager. The default is a retry # manager that tries only once. You can provide either a static # retry manager or a Proc that returns a retry manager. # @param block [Proc] A block that can be called to attempt to compute # the value given the key. # def initialize retries: nil, &block @retries = retries @compute_handler = block @key_values = {} @mutex = Thread::Mutex.new end ## # Returns the value for the given key. This will either return the # value or raise an error indicating failure to compute the value. If # the value was previously cached, it will return that cached value, # otherwise it will either run the computation to try to determine the # value, or wait for another thread that is already running the # computation. # # Any arguments beyond the initial key argument will be passed to the # block if it is called, but are ignored if a cached value is returned. # # @param key [Object] the key # @param extra_args [Array] extra arguments to pass to the block # @return [Object] the value # @raise [Exception] if an error happened while computing the value # def get key, *extra_args lookup_key(key).get key, *extra_args end alias [] get ## # This method calls {#get} repeatedly until a final result is available # or retries have exhausted. # # Note: this method spins on {#get}, although honoring any retry delay. # Thus, it is best to call this only if retries are limited or a retry # delay has been configured. # # @param key [Object] the key # @param extra_args [Array] extra arguments to pass to the block # @param transient_errors [Array] An array of exception classes # that will be treated as transient and will allow await to # continue retrying. Exceptions omitted from this list will be # treated as fatal errors and abort the call. Default is # `[StandardError]`. # @param max_tries [Integer,nil] The maximum number of times this will # call {#get} before giving up, or nil for a potentially unlimited # number of attempts. Default is 1. # @param max_time [Numeric,nil] The maximum time in seconds this will # spend before giving up, or nil (the default) for a potentially # unlimited timeout. # # @return [Object] the value # @raise [Exception] if a fatal error happened, or retries have been # exhausted. # def await key, *extra_args, transient_errors: nil, max_tries: 1, max_time: nil lookup_key(key).await key, *extra_args, transient_errors: transient_errors, max_tries: max_tries, max_time: max_time end ## # Returns the current low-level state for the given key. Does not block # for computation. See {LazyValue#internal_state} for details. # # @param key [Object] the key # @return [Array] the low-level state # def internal_state key lookup_key(key).internal_state end ## # Force the cache for the given key to expire immediately, if # computation is complete. # # Any cached value will be cleared, the retry count is reset, and the # next access will call the compute block as if it were the first # access. Returns true if this took place. Has no effect and returns # false if the computation is not yet complete (i.e. if a thread is # currently computing, or if the last attempt failed and retries have # not yet been exhausted.) # # @param key [Object] the key # @return [true,false] whether the cache was expired # def expire! key lookup_key(key).expire! end ## # Force the values for all keys to expire immediately. # # @return [Array] A list of keys that were expired. A key is # *not* included if its computation is not yet complete (i.e. if a # thread is currently computing, or if the last attempt failed and # retries have not yet been exhausted.) # def expire_all! all_expired = [] @mutex.synchronize do @key_values.each do |key, value| all_expired << key if value.expire! end end all_expired end ## # Set the cache value for the given key explicitly and immediately. # If a computation is in progress, it is "detached" and its result will # no longer be considered. # # @param key [Object] the key # @param value [Object] the value to set # @param lifetime [Numeric] the lifetime until expiration in seconds, # or nil (the default) for no expiration. # @return [Object] the value # def set! key, value, lifetime: nil lookup_key(key).set! value, lifetime: lifetime end private ## # @private # Ensures that exactly one LazyValue exists for the given key, and # returns it. # def lookup_key key # Optimization: check for key existence and return quickly without # grabbing the mutex. This works because keys are never deleted. return @key_values[key] if @key_values.key? key @mutex.synchronize do if @key_values.key? key @key_values[key] else retries = if @retries.respond_to? :reset_dup @retries.reset_dup elsif @retries.respond_to? :call @retries.call end @key_values[key] = LazyValue.new retries: retries, &@compute_handler end end end end ## # @private # # A simple retry manager with optional delay and backoff. It retries # until either a configured maximum number of attempts has been # reached, or a configurable total time has elapsed since the first # failure. # # This class is not thread-safe by itself. Access should be protected # by an external mutex. # # We keep this private for now so we can move it in the future if we need # it to be available to other libraries. Currently it should not be used # outside of Google::Cloud::Env. # class Retries ## # Create and initialize a retry manager. # # @param max_tries [Integer,nil] Maximum number of attempts before we # give up altogether, or nil for no maximum. Default is 1, # indicating one attempt and no retries. # @param max_time [Numeric,nil] The maximum amount of time in seconds # until we give up altogether, or nil for no maximum. Default is # nil. # @param initial_delay [Numeric] Initial delay between attempts, in # seconds. Default is 0. # @param max_delay [Numeric,nil] Maximum delay between attempts, in # seconds, or nil for no max. Default is nil. # @param delay_multiplier [Numeric] Multipler applied to the delay # between attempts. Default is 1 for no change. # @param delay_adder [Numeric] Value added to the delay between # attempts. Default is 0 for no change. # @param delay_includes_time_elapsed [true,false] Whether to deduct any # time already elapsed from the retry delay. Default is false. # def initialize max_tries: 1, max_time: nil, initial_delay: 0, max_delay: nil, delay_multiplier: 1, delay_adder: 0, delay_includes_time_elapsed: false @max_tries = max_tries&.to_i raise ArgumentError, "max_tries must be positive" if @max_tries && !@max_tries.positive? @max_time = max_time raise ArgumentError, "max_time must be positive" if @max_time && !@max_time.positive? @initial_delay = initial_delay raise ArgumentError, "initial_delay must be nonnegative" if @initial_delay&.negative? @max_delay = max_delay raise ArgumentError, "max_delay must be nonnegative" if @max_delay&.negative? @delay_multiplier = delay_multiplier @delay_adder = delay_adder @delay_includes_time_elapsed = delay_includes_time_elapsed reset! end ## # Create a duplicate in the reset state # # @return [Retries] # def reset_dup Retries.new max_tries: @max_tries, max_time: @max_time, initial_delay: @initial_delay, max_delay: @max_delay, delay_multiplier: @delay_multiplier, delay_adder: @delay_adder, delay_includes_time_elapsed: @delay_includes_time_elapsed end ## # Returns true if the retry limit has been reached. # # @return [true,false] # def finished? @current_delay.nil? end ## # Reset to the initial attempt. # # @return [self] # def reset! @current_delay = :reset self end ## # Cause the retry limit to be reached immediately. # # @return [self] # def finish! @current_delay = nil self end ## # Advance to the next attempt. # # Returns nil if the retry limit has been reached. Otherwise, returns # the delay in seconds until the next retry (0 for no delay). Raises an # error if the previous call already returned nil. # # @param start_time [Numeric,nil] Optional start time in monotonic time # units. Used if delay_includes_time_elapsed is set. # @return [Numeric,nil] # def next start_time: nil raise "no tries remaining" if finished? cur_time = Process.clock_gettime Process::CLOCK_MONOTONIC if @current_delay == :reset setup_first_retry cur_time else advance_delay end advance_retry cur_time adjusted_delay start_time, cur_time end private def setup_first_retry cur_time @tries_remaining = @max_tries @deadline = @max_time ? cur_time + @max_time : nil @current_delay = @initial_delay end def advance_delay @current_delay = (@delay_multiplier * @current_delay) + @delay_adder @current_delay = @max_delay if @max_delay && @current_delay > @max_delay end def advance_retry cur_time @tries_remaining -= 1 if @tries_remaining @current_delay = nil if @tries_remaining&.zero? || (@deadline && cur_time + @current_delay > @deadline) end def adjusted_delay start_time, cur_time delay = @current_delay if @delay_includes_time_elapsed && start_time && delay delay -= cur_time - start_time delay = 0 if delay.negative? end delay end end end end end google-cloud-env-2.3.1/lib/google/cloud/env/compute_smbios.rb0000644000004100000410000001170015020727635024216 0ustar www-datawww-data# frozen_string_literal: true # Copyright 2023 Google LLC # # Licensed 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 # # https://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 "google/cloud/env/lazy_value" module Google module Cloud class Env ## # Access to the SMBIOS information needed to determine if this Ruby # process is running on a Google compute platform. # # This information lives at a file system path on Linux, but in the # Registry on Windows. # # You can provide an override to "mock out" the behavior of this object. # class ComputeSMBIOS ## # Create an SMBIOS access object # def initialize @product_name_cache = LazyValue.new { load_product_name } @override_product_name = nil end ## # Read the product name. On a Google compute platform, this should # include the word "Google". # # This method may read the file system (on Linux) or registry (on # Windows) the first time it is called, but it will cache the result # for subsequent calls. # # @return [String] Product name, or the empty string if not found. # def product_name @override_product_name || @product_name_cache.get.first end ## # The source of the product name data. Will be one of the following: # # * `:linux` - The data comes from the Linux SMBIOS under /sys # * `:windows` - The data comes from the Windows Registry # * `:error` - The data could not be obtained # * `:override` - The data comes from an override # # This method may read the file system (on Linux) or registry (on # Windows) the first time it is called, but it will cache the result # for subsequent calls. # # @return [Symbol] The source # def product_name_source @override_product_name ? :override : @product_name_cache.get.last end ## # Determine whether the SMBIOS state suggests that we are running on a # Google compute platform. # # This method may read the file system (on Linux) or registry (on # Windows) the first time it is called, but it will cache the result # for subsequent calls. # # @return [true,false] # def google_compute? product_name.include? "Google" end ## # The current override value for the product name, either a string # value, or nil to disable mocking. # # @return [nil,String] # attr_accessor :override_product_name ## # Run the given block with the product name mock modified. This is # generally used for debugging/testing/mocking. # # @param override_name [nil,String] # def with_override_product_name override_name old_override = @override_product_name begin @override_product_name = override_name yield ensure @override_product_name = old_override end end private # @private The Windows registry key path WINDOWS_KEYPATH = "SYSTEM\\HardwareConfig\\Current" # @private The Windows registry key name WINDOWS_KEYNAME = "SystemProductName" # @private The Linux file path LINUX_FILEPATH = "/sys/class/dmi/id/product_name" private_constant :WINDOWS_KEYPATH, :WINDOWS_KEYNAME, :LINUX_FILEPATH def load_product_name begin require "win32/registry" Win32::Registry::HKEY_LOCAL_MACHINE.open WINDOWS_KEYPATH do |reg| return [reg[WINDOWS_KEYNAME].to_s, :windows] end rescue LoadError # Fall through to the Linux routine rescue StandardError => e # With JRuby 10, a Fiddle::DLError is raised, In the case the fiddle gem is not # loadable, safely assert the error type without relying on the Fiddle namespace # having been loaded. raise e unless e.class.name == "Fiddle::DLError" # rubocop:disable Style/ClassEqualityComparison # Fall through to the Linux routine end begin File.open LINUX_FILEPATH do |file| return [file.readline(chomp: true), :linux] end rescue IOError, SystemCallError ["", :error] end end end end end end google-cloud-env-2.3.1/lib/google/cloud/env/compute_metadata.rb0000644000004100000410000007553615020727635024523 0ustar www-datawww-data# frozen_string_literal: true # Copyright 2023 Google LLC # # Licensed 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 # # https://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 "base64" require "faraday" require "json" require "google/cloud/env/compute_smbios" require "google/cloud/env/lazy_value" require "google/cloud/env/variables" module Google module Cloud class Env ## # A client for the Google metadata service. # class ComputeMetadata ## # The default host for the metadata server # @return [String] # DEFAULT_HOST = "http://169.254.169.254" ## # The default timeout in seconds for opening http connections # @return [Numeric] # DEFAULT_OPEN_TIMEOUT = 0.1 ## # The default timeout in seconds for request responses # @return [Numeric] # DEFAULT_REQUEST_TIMEOUT = 0.5 ## # The default number of retries # @return [Integer] # DEFAULT_RETRY_COUNT = 2 ## # The default timeout across retries # @return [nil] # DEFAULT_RETRY_TIMEOUT = nil ## # The default interval between retries, in seconds # @return [Numeric] # DEFAULT_RETRY_INTERVAL = 0.5 ## # The default time in seconds to wait for environment warmup. # @return [Numeric] # DEFAULT_WARMUP_TIME = 60 ## # @private # The base path of metadata server queries. # @return [String] # PATH_BASE = "/computeMetadata/v1" ## # @private # The standard set of headers # @return [Hash{String=>String}] # FLAVOR_HEADER = { "Metadata-Flavor" => "Google" }.freeze ## # Basic HTTP response object, returned by # {ComputeMetadata#lookup_response}. # # This object duck-types the `status`, `body`, and `headers` fields of # `Faraday::Response`. It also includes the CLOCK_MONOTONIC time when # the data was retrieved. # class Response ## # Create a response object. # # @param status [Integer] The HTTP status, normally 200 # @param body [String] The HTTP body as a string # @param headers [Hash{String=>String}] The HTTP response headers. # Normally, the `Metadata-Flavor` header must be set to the value # `Google`. # def initialize status, body, headers @status = status @body = body @headers = headers @retrieval_monotonic_time = Process.clock_gettime Process::CLOCK_MONOTONIC end ## # The HTTP status code # @return [Integer] # attr_reader :status ## # The HTTP response body # @return [String] # attr_reader :body ## # The HTTP response headers # @return [Hash{String=>String}] # attr_reader :headers # The CLOCK_MONOTONIC time at which this response was retrieved. # @return [Numeric] # attr_reader :retrieval_monotonic_time ## # Returns true if the metadata-flavor is correct for Google Cloud # @return [boolean] # def google_flavor? headers["Metadata-Flavor"] == "Google" end end ## # A set of overrides for metadata access. This is used in # {ComputeMetadata#overrides=} and {ComputeMetadata#with_overrides}. # Generally, you should create and populate an overrides object, then # set it using one of those methods. # # An empty overrides object that contains no data is interpreted as a # metadata server that does not respond and raises # MetadataServerNotResponding. Otherwise, the overrides specifies what # responses are returned for specified queries, and any query not # explicitly set will result in a 404. # class Overrides ## # Create an empty overrides object. # def initialize clear end ## # Add an override to the object, providing a full response. # # @param path [String] The key path (e.g. `project/project-id`) # @param response [Response] The response object to return. # @param query [Hash{String => String}] Any additional query # parameters for the request. # # @return [self] for chaining # def add_response path, response, query: nil @data[[path, query || {}]] = response self end ## # Add an override to the object, providing just a body string. # # @param path [String] The key path (e.g. `project/project-id`) # @param string [String] The response string to return. # @param query [Hash{String => String}] Any additional query # parameters for the request. # # @return [self] for chaining # def add path, string, query: nil, headers: nil headers = (headers || {}).merge FLAVOR_HEADER response = Response.new 200, string, headers add_response path, response, query: query end ## # Add an override for the ping request. # # @return [self] for chaining # def add_ping add nil, "computeMetadata/\n" end ## # Clear all data from these overrides # # @return [self] for chaining # def clear @data = {} self end ## # Look up a response from the override data. # # @param path [String] The key path (e.g. `project/project-id`) # @param query [Hash{String => String}] Any additional query # parameters for the request. # # @return [String] The response # @return [nil] if there is no data for the given query # def lookup path, query: nil @data[[path, query || {}]] end ## # Returns true if there is at least one override present # # @return [true, false] # def empty? @data.empty? end end ## # Create a compute metadata access object. # # @param variables [Google::Cloud::Env::Variables] Access object for # environment variables. If not provided, a default is created. # @param compute_smbios [Google::Cloud::Env::ComputeSMBIOS] Access # object for SMBIOS information. If not provided, a default is # created. # def initialize variables: nil, compute_smbios: nil @variables = variables || Variables.new @compute_smbios = compute_smbios || ComputeSMBIOS.new # This mutex protects the overrides and existence settings. # Those values won't change within a synchronize block. @mutex = Thread::Mutex.new reset! end ## # The host URL for the metadata server, including `http://`. # # @return [String] # attr_reader :host ## # The host URL for the metadata server, including `http://`. # # @param new_host [String] # def host= new_host new_host ||= @variables["GCE_METADATA_HOST"] || DEFAULT_HOST new_host = "http://#{new_host}" unless new_host.start_with? "http://" @host = new_host end ## # The default maximum number of times to retry a query for a key. # A value of 1 means 2 attempts (i.e. 1 retry). A value of nil means # there is no limit to the number of retries, although there could be # an overall timeout. # # Defaults to {DEFAULT_RETRY_COUNT}. # # @return [Integer,nil] # attr_accessor :retry_count ## # The default overall timeout across all retries of a lookup, in # seconds. A value of nil means there is no timeout, although there # could be a limit to the number of retries. # # Defaults to {DEFAULT_RETRY_TIMEOUT}. # # @return [Numeric,nil] # attr_accessor :retry_timeout ## # The time in seconds between retries. This time includes the time # spent by the previous attempt. # # Defaults to {DEFAULT_RETRY_INTERVAL}. # # @return [Numeric] # attr_accessor :retry_interval ## # A time in seconds allotted to environment warmup, during which # retries will not be ended. This handles certain environments in which # the Metadata Server might not be fully awake until some time after # application startup. A value of nil disables this warmup period. # # Defaults to {DEFAULT_WARMUP_TIME}. # # @return [Numeric,nil] # attr_accessor :warmup_time ## # The timeout for opening http connections in seconds. # # @return [Numeric] # def open_timeout connection.options.open_timeout end ## # The timeout for opening http connections in seconds. # # @param timeout [Numeric] # def open_timeout= timeout connection.options[:open_timeout] = timeout end ## # The total timeout for an HTTP request in seconds. # # @return [Numeric] # def request_timeout connection.options.timeout end ## # The total timeout for an HTTP request in seconds. # # @param timeout [Numeric] # def request_timeout= timeout connection.options[:timeout] = timeout end ## # Look up a particular key from the metadata server, and return a full # {Response} object. Could return a cached value if the key has been # queried before, otherwise this could block while trying to contact # the server through the given timeouts and retries. # # This returns a Response object even if the HTTP status is 404, so be # sure to check the status code to determine whether the key actually # exists. Unlike {#lookup}, this method does not return nil. # # @param path [String] The key path (e.g. `project/project-id`) # @param query [Hash{String => String}] Any additional query parameters # to send with the request. # @param open_timeout [Numeric] Timeout for opening http connections. # Defaults to {#open_timeout}. # @param request_timeout [Numeric] Timeout for entire http requests. # Defaults to {#request_timeout}. # @param retry_count [Integer,nil] Number of times to retry. A value of # 1 means 2 attempts (i.e. 1 retry). A value of nil indicates # retries are limited only by the timeout. Defaults to # {#retry_count}. # @param retry_timeout [Numeric,nil] Total timeout for retries. A value # of nil indicates no time limit, and retries are limited only by # count. Defaults to {#retry_timeout}. # # @return [Response] the data from the metadata server # @raise [MetadataServerNotResponding] if the Metadata Server is not # responding # def lookup_response path, query: nil, open_timeout: nil, request_timeout: nil, retry_count: :default, retry_timeout: :default query = canonicalize_query query if @overrides @mutex.synchronize do return lookup_override path, query if @overrides end end raise MetadataServerNotResponding unless gce_check retry_count = self.retry_count if retry_count == :default retry_count += 1 if retry_count retry_timeout = self.retry_timeout if retry_timeout == :default @cache.await [path, query], open_timeout, request_timeout, transient_errors: [MetadataServerNotResponding], max_tries: retry_count, max_time: retry_timeout end ## # Look up a particular key from the metadata server and return the data # as a string. Could return a cached value if the key has been queried # before, otherwise this could block while trying to contact the server # through the given timeouts and retries. # # This returns the HTTP body as a string, only if the call succeeds. If # the key is inaccessible or missing (i.e. the HTTP status was not 200) # or does not have the correct `Metadata-Flavor` header, then nil is # returned. If you need more detailed information, use # {#lookup_response}. # # @param path [String] The key path (e.g. `project/project-id`) # @param query [Hash{String => String}] Any additional query parameters # to send with the request. # @param open_timeout [Numeric] Timeout for opening http connections. # Defaults to {#open_timeout}. # @param request_timeout [Numeric] Timeout for entire http requests. # Defaults to {#request_timeout}. # @param retry_count [Integer,nil] Number of times to retry. A value of # 1 means 2 attempts (i.e. 1 retry). A value of nil indicates # retries are limited only by the timeout. Defaults to # {#retry_count}. # @param retry_timeout [Numeric,nil] Total timeout for retries. A value # of nil indicates no time limit, and retries are limited only by # count. Defaults to {#retry_timeout}. # # @return [String] the data from the metadata server # @return [nil] if the key is not present # @raise [MetadataServerNotResponding] if the Metadata Server is not # responding # def lookup path, query: nil, open_timeout: nil, request_timeout: nil, retry_count: :default, retry_timeout: :default response = lookup_response path, query: query, open_timeout: open_timeout, request_timeout: request_timeout, retry_count: retry_count, retry_timeout: retry_timeout return nil unless response.status == 200 && response.google_flavor? response.body end ## # Return detailed information about whether we think Metadata is # available. If we have not previously confirmed existence one way or # another, this could block while trying to contact the server through # the given timeouts and retries. # # @param open_timeout [Numeric] Timeout for opening http connections. # Defaults to {#open_timeout}. # @param request_timeout [Numeric] Timeout for entire http requests. # Defaults to {#request_timeout}. # @param retry_count [Integer,nil] Number of times to retry. A value of # 1 means 2 attempts (i.e. 1 retry). A value of nil indicates # retries are limited only by the timeout. Defaults to # {#retry_count}. # @param retry_timeout [Numeric,nil] Total timeout for retries. A value # of nil indicates no time limit, and retries are limited only by # count. Defaults to {#retry_timeout}. # # @return [:no] if we know the metadata server is not present # @return [:unconfirmed] if we believe metadata should be present but we # haven't gotten a confirmed response from it. This can happen if # SMBIOS says we're on GCE but we can't contact the Metadata Server # even through retries. # @return [:confirmed] if we have a confirmed response from metadata. # def check_existence open_timeout: nil, request_timeout: nil, retry_count: :default, retry_timeout: :default current = @existence return current if [:no, :confirmed].include? @existence begin lookup nil, open_timeout: open_timeout, request_timeout: request_timeout, retry_count: retry_count, retry_timeout: retry_timeout rescue MetadataServerNotResponding # Do nothing end @existence end ## # The current detailed existence status, without blocking on any # attempt to contact the metadata server. # # @return [nil] if we have no information at all yet # @return [:no] if we know the metadata server is not present # @return [:unconfirmed] if we believe metadata should be present but we # haven't gotten a confirmed response from it. # @return [:confirmed] if we have a confirmed response from metadata. # def existence_immediate @existence end ## # Assert that the Metadata Server should be present, and wait for a # confirmed connection to ensure it is up. This will generally run # at most {#warmup_time} seconds to wait out the expected maximum # warmup time, but a shorter timeout can be provided. # # @param timeout [Numeric,nil] a timeout in seconds, or nil to wait # until we have conclusively decided one way or the other. # @return [:confirmed] if we were able to confirm connection. # @raise [MetadataServerNotResponding] if we were unable to confirm # connection with the Metadata Server, either because the timeout # expired or because the server seems to be down # def ensure_existence timeout: nil timeout ||= @startup_time + warmup_time - Process.clock_gettime(Process::CLOCK_MONOTONIC) timeout = 1.0 if timeout < 1.0 check_existence retry_count: nil, retry_timeout: timeout raise MetadataServerNotResponding unless @existence == :confirmed @existence end ## # Get the expiration time for the given path. Returns the monotonic # time if the data has been retrieved and has an expiration, nil if the # data has been retrieved but has no expiration, or false if the data # has not yet been retrieved. # # @return [Numeric,nil,false] # def expiration_time_of path, query: nil state = @cache.internal_state [path, query] return false unless state[0] == :success state[2] end ## # The overrides, or nil if overrides are not present. # If present, overrides will answer all metadata queries, and actual # calls to the metadata server will be blocked. # # @return [Overrides,nil] # attr_reader :overrides ## # Set the overrides. You can also set nil to disable overrides. # If present, overrides will answer all metadata queries, and actual # calls to the metadata server will be blocked. # # @param new_overrides [Overrides,nil] # def overrides= new_overrides @mutex.synchronize do @existence = nil @overrides = new_overrides end end ## # Run the given block with the overrides replaced with the given set # (or nil to disable overrides in the block). The original overrides # setting is restored at the end of the block. This is used for # debugging/testing/mocking. # # @param temp_overrides [Overrides,nil] # def with_overrides temp_overrides old_overrides, old_existence = @mutex.synchronize do [@overrides, @existence] end begin @mutex.synchronize do @existence = nil @overrides = temp_overrides end yield ensure @mutex.synchronize do @existence = old_existence @overrides = old_overrides end end end ## # @private # The underlying Faraday connection. Can be used to customize the # connection for testing. # @return [Faraday::Connection] # attr_reader :connection ## # @private # The underlying LazyDict. Can be used to customize the cache for # testing. # @return [Google::Cloud::Env::LazyDict] # attr_reader :cache ## # @private # The variables access object # @return [Google::Cloud::Env::Variables] # attr_reader :variables ## # @private # The compute SMBIOS access object # @return [Google::Cloud::Env::ComputeSMBIOS] # attr_reader :compute_smbios ## # @private # Reset the cache, overrides, and all settings to default, for testing. # def reset! @mutex.synchronize do self.host = nil @connection = Faraday.new url: host self.open_timeout = DEFAULT_OPEN_TIMEOUT self.request_timeout = DEFAULT_REQUEST_TIMEOUT self.retry_count = DEFAULT_RETRY_COUNT self.retry_timeout = DEFAULT_RETRY_TIMEOUT self.retry_interval = DEFAULT_RETRY_INTERVAL self.warmup_time = DEFAULT_WARMUP_TIME @cache = create_cache @overrides = nil end reset_existence! end ## # @private # Clear the existence cache, for testing. # def reset_existence! @mutex.synchronize do @existence = nil @startup_time = Process.clock_gettime Process::CLOCK_MONOTONIC end self end private ## # @private # A list of exceptions that are considered transient. They trigger a # retry if received from an HTTP attempt, and they are not cached (i.e. # the cache lifetime is set to 0.) # TRANSIENT_EXCEPTIONS = [ Faraday::TimeoutError, Faraday::ConnectionFailed, Errno::EHOSTDOWN, Errno::ETIMEDOUT, Timeout::Error ].freeze private_constant :TRANSIENT_EXCEPTIONS ## # @private # # A buffer in seconds for token expiry. Our cache for the token will # expire approximately this many seconds before the declared expiry # time of the token itself. # # We want this value to be positive so that we provide some buffer to # offset any clock skew and Metadata Server latency that might affect # our calculation of the expiry time, but more importantly so that a # client has approximately this amount of time to use a token we give # them before it expires. # # We don't want this to be much higher, however, to keep the load down # on the Metadata Server. We've been advised by the compute/serverless # engineering teams to set this value less than 4 minutes because the # Metadata Server can refresh the token as late as 4 minutes before the # actual expiry of the previous token. If our cache expires and we # request a new token, we actually want to receive a new token rather # than the previous old token. See internal issue b/311414224. # TOKEN_EXPIRY_BUFFER = 210 private_constant :TOKEN_EXPIRY_BUFFER ## # @private # # Attempt to determine if we're on GCE (if we haven't previously), and # update the existence flag. Return true if we *could* be on GCE, or # false if we're definitely not. # def gce_check if @existence.nil? @mutex.synchronize do @existence ||= if @compute_smbios.google_compute? || maybe_gcf || maybe_gcr || maybe_gae :unconfirmed else :no end end end @existence != :no end # @private def maybe_gcf @variables["K_SERVICE"] && @variables["K_REVISION"] && @variables["GAE_RUNTIME"] end # @private def maybe_gcr @variables["K_SERVICE"] && @variables["K_REVISION"] && @variables["K_CONFIGURATION"] end # @private def maybe_gae @variables["GAE_SERVICE"] && @variables["GAE_RUNTIME"] end ## # @private # Create and return a new LazyDict cache for the metadata # def create_cache retries = proc do Google::Cloud::Env::Retries.new max_tries: nil, initial_delay: retry_interval, delay_includes_time_elapsed: true end Google::Cloud::Env::LazyDict.new retries: retries do |(path, query), open_timeout, request_timeout| internal_lookup path, query, open_timeout, request_timeout end end ## # @private # Look up the given path, without using the cache. # def internal_lookup path, query, open_timeout, request_timeout full_path = path ? "#{PATH_BASE}/#{path}" : "" http_response = connection.get full_path do |req| req.params = query if query req.headers = FLAVOR_HEADER req.options.timeout = request_timeout if request_timeout req.options.open_timeout = open_timeout if open_timeout end response = Response.new http_response.status, http_response.body, http_response.headers if path.nil? post_update_existence(response.status == 200 && response.google_flavor?, response.retrieval_monotonic_time) elsif response.google_flavor? post_update_existence true, response.retrieval_monotonic_time end lifetime = determine_data_lifetime path, response.body.strip LazyValue.expiring_value lifetime, response rescue *TRANSIENT_EXCEPTIONS post_update_existence false raise MetadataServerNotResponding end ## # @private # Update existence based on a received result # def post_update_existence success, current_time = nil return if @existence == :confirmed @mutex.synchronize do if success @existence = :confirmed elsif @existence != :confirmed current_time ||= Process.clock_gettime Process::CLOCK_MONOTONIC @existence = :no if current_time > @startup_time + warmup_time end end end ## # @private # Compute the lifetime of data, given the path and data. Returns the # value in seconds, or nil for nonexpiring data. # def determine_data_lifetime path, data case path when %r{instance/service-accounts/[^/]+/token} access_token_lifetime data when %r{instance/service-accounts/[^/]+/identity} identity_token_lifetime data end end ## # @private # Extract the lifetime of an access token # def access_token_lifetime data json = JSON.parse data rescue nil return 0 unless json.respond_to?(:key?) && json.key?("expires_in") lifetime = json["expires_in"].to_i - TOKEN_EXPIRY_BUFFER lifetime = 0 if lifetime.negative? lifetime end ## # @private # Extract the lifetime of an identity token # def identity_token_lifetime data return 0 unless data =~ /^[\w=-]+\.([\w=-]+)\.[\w=-]+$/ base64 = Base64.urlsafe_decode64 Regexp.last_match[1] json = JSON.parse base64 rescue nil return 0 unless json.respond_to?(:key?) && json&.key?("exp") lifetime = json["exp"].to_i - Time.now.to_i - TOKEN_EXPIRY_BUFFER lifetime = 0 if lifetime.negative? lifetime end ## # @private # Stringify keys in a query hash # def canonicalize_query query query&.transform_keys(&:to_s) end ## # @private # Lookup from overrides and return the result or raise. # This must be called from within the mutex, and assumes that # overrides is non-nil. # def lookup_override path, query if @overrides.empty? @existence = :no raise MetadataServerNotResponding end @existence = :confirmed result = @overrides.lookup path, query: query result ||= Response.new 404, "Not found", FLAVOR_HEADER result end end ## # Error raised when the compute metadata server is expected to be # present in the current environment, but couldn't be contacted. # class MetadataServerNotResponding < StandardError ## # Default message for the error # @return [String] # DEFAULT_MESSAGE = "The Google Metadata Server did not respond to queries. This " \ "could be because no Server is present, the Server has not yet " \ "finished starting up, the Server is running but overloaded, or " \ "Server could not be contacted due to networking issues." ## # Create a new MetadataServerNotResponding. # # @param message [String] Error message. If not provided, defaults to # {DEFAULT_MESSAGE}. # def initialize message = nil message ||= DEFAULT_MESSAGE super message end end end end end google-cloud-env-2.3.1/lib/google/cloud/env/file_system.rb0000644000004100000410000001024415020727635023513 0ustar www-datawww-data# frozen_string_literal: true # Copyright 2023 Google LLC # # Licensed 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 # # https://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 "google/cloud/env/lazy_value" module Google module Cloud class Env ## # Access to file system contents. # # This is a simple class that reads the contents of objects in the file # system, caching data so that subsequent accesses do not need to reread # the file system. # # You can also "mock" the file system by providing a hash of overrides. # If overrides are present, actual file system access is disabled; that # is, overrides are "all or nothing". # # This class does not provide any controls for data size. If you read a # large file, its contents will stay in memory for the lifetime of the # Ruby process. # class FileSystem ## # Create a file system access object with no overrides. # def initialize @overrides = nil @cache = LazyDict.new do |path, binary| if binary File.binread path else File.read path end rescue IOError, SystemCallError nil end # This mutex protects the overrides variable. Its setting (i.e. # whether nil or an overrides hash) will not change within a # synchronize block. @mutex = Thread::Mutex.new end ## # Read the given file from the file system and return its contents. # # @param path [String] The path to the file. # @param binary [boolean] Whether to read in binary mode. Defaults to # false. This must be consistent across multiple requests for the # same path; if it is not, an error will be raised. # @return [String] if the file exists. # @return [nil] if the file does not exist. # def read path, binary: false result = false @mutex.synchronize do result = @overrides[path] if @overrides end result = @cache.get(path, binary) if result == false if result && binary != (result.encoding == Encoding::ASCII_8BIT) raise IOError, "binary encoding flag mismatch" end result end ## # The overrides hash, or nil if overrides are not present. # The hash maps paths to contents of the file at that path. # # @return [Hash{String => String},nil] # attr_reader :overrides ## # Set the overrides hash. You can either provide a hash of file paths # to content, or nil to disable overrides. If overrides are present, # actual filesystem access is disabled; overrides are "all or nothing". # # @param new_overrides [Hash{String => String},nil] # def overrides= new_overrides @mutex.synchronize do @overrides = new_overrides end end ## # Run the given block with the overrides replaced with the given hash # (or nil to disable overrides in the block). The original overrides # setting is restored at the end of the block. This is used for # debugging/testing/mocking. # # @param temp_overrides [nil,Hash{String => String}] # def with_overrides temp_overrides old_overrides = @overrides begin @mutex.synchronize do @overrides = temp_overrides end yield ensure @mutex.synchronize do @overrides = old_overrides end end end end end end end google-cloud-env-2.3.1/lib/google/cloud/env/version.rb0000644000004100000410000000133715020727635022660 0ustar www-datawww-data# Copyright 2017 Google LLC # # Licensed 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 # # https://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. module Google module Cloud class Env ## # Library version # @return [String] # VERSION = "2.3.1".freeze end end end google-cloud-env-2.3.1/lib/google/cloud/env/variables.rb0000644000004100000410000000446115020727635023144 0ustar www-datawww-data# frozen_string_literal: true # Copyright 2023 Google LLC # # Licensed 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 # # https://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. module Google module Cloud class Env ## # Access to system environment variables. # # This is a hashlike object that controls access to environment variable # data. It supports temporarily changing the data source (i.e. swapping # ::ENV out for a different set of data) for mocking. # class Variables ## # Create an enviroment variables access object. This is initially # backed by the actual environment variables (i.e. ENV). # def initialize @backing_data = ::ENV end ## # Fetch the given environment variable from the backing data. # # @param key [String] # @return [String,nil] # def [] key @backing_data[key.to_s] end alias get [] ## # The backing data is a hash or hash-like object that represents the # environment variable data. This can either be the actual environment # variables object (i.e. ENV) or a substitute hash used for mocking. # # @return [Hash{String=>String}] # attr_accessor :backing_data ## # Run the given block with the backing data replaced with the given # hash. The original backing data is restored afterward. This is used # for debugging/testing/mocking. # # @param temp_backing_data [Hash{String=>String}] # def with_backing_data temp_backing_data old_backing_data = @backing_data begin @backing_data = temp_backing_data yield ensure @backing_data = old_backing_data end end end end end end google-cloud-env-2.3.1/lib/google/cloud/env.rb0000644000004100000410000004276215020727635021202 0ustar www-datawww-data# Copyright 2017 Google LLC # # Licensed 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 # # https://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 "json" require "google/cloud/env/compute_metadata" require "google/cloud/env/compute_smbios" require "google/cloud/env/file_system" require "google/cloud/env/variables" ## # Namespace of Google products # module Google ## # Namespace of Google Cloud products # module Cloud ## # ## Google Cloud hosting environment # # This library provides access to information about the application's # hosting environment if it is running on Google Cloud Platform. You can # use this library to determine which Google Cloud product is hosting your # application (e.g. App Engine, Kubernetes Engine), information about the # Google Cloud project hosting the application, information about the # virtual machine instance, authentication information, and so forth. # # ### Usage # # Obtain an instance of the environment info with: # # ```ruby # require "google/cloud/env" # env = Google::Cloud.env # ``` # # Then you can interrogate any fields using methods on the object. # # ```ruby # if env.app_engine? # # App engine specific logic # end # ``` # # Any item that does not apply to the current environment will return nil. # For example: # # ```ruby # unless env.app_engine? # service = env.app_engine_service_id # => nil # end # ``` # class Env ## # Create a new instance of the environment information. # Most clients should not need to call this directly. Obtain a singleton # instance of the information from `Google::Cloud.env`. # def initialize @variables = Variables.new @file_system = FileSystem.new @compute_smbios = ComputeSMBIOS.new @compute_metadata = ComputeMetadata.new variables: @variables, compute_smbios: @compute_smbios end ## # The variables access object. Use this to make direct queries for # environment variable information, or to mock out environment variables # for testing. # # @return [Google::Cloud::Env::Variables] # attr_reader :variables ## # The variables access object. Use this to make direct queries for # information from the file system, or to mock out the file system for # testing. # # @return [Google::Cloud::Env::FileSystem] # attr_reader :file_system ## # The compute SMBIOS access object. Use this to make direct queries for # compute SMBIOS information, or to mock out the SMBIOS for testing. # # @return [Google::Cloud::Env::ComputeSMBIOS] # attr_reader :compute_smbios ## # The compute metadata access object. Use this to make direct calls to # compute metadata or configure how metadata server queries are done, or # to mock out the metadata server for testing. # # @return [Google::Cloud::Env::ComputeMetadata] # attr_reader :compute_metadata ## # Determine whether the Google Compute Engine Metadata Service is running. # # This method is conservative. It may block for a short period (up to # about 1.5 seconds) while attempting to contact the server, but if this # fails, this method will return false, even though it is possible that a # future call could succeed. In particular, this might happen in # environments where there is a warmup time for the Metadata Server. # Early calls before the Server has warmed up may return false, while # later calls return true. # # @return [boolean] # def metadata? compute_metadata.check_existence == :confirmed end ## # Assert that the Metadata Server should be present, and wait for a # confirmed connection to ensure it is up. In general, this will run at # most {ComputeMetadata::DEFAULT_WARMUP_TIME} seconds to wait out the # expected maximum warmup time, but a shorter timeout can be provided. # # This method is useful call during application initialization to wait # for the Metadata Server to warm up and ensure that subsequent lookups # should succeed. # # @param timeout [Numeric,nil] a timeout in seconds, or nil to wait # until we have conclusively decided one way or the other. # @return [:confirmed] if we were able to confirm connection. # @raise [MetadataServerNotResponding] if we were unable to confirm # connection with the Metadata Server, either because the timeout # expired or because the server seems to be down # def ensure_metadata timeout: nil compute_metadata.ensure_existence timeout: timeout end ## # Retrieve info from the Google Compute Engine Metadata Service. # Returns `nil` if the given data is not present. # # @param [String] type Type of metadata to look up. Currently supported # values are "project" and "instance". # @param [String] entry Metadata entry path to look up. # @param query [Hash{String => String}] Any additional query parameters # to send with the request. # # @return [String] the data # @return [nil] if there is no data for the specified type and entry # @raise [MetadataServerNotResponding] if the Metadata Server is not # responding. This could either be because the metadata service is # not present in the current environment, or if it is expected to be # present but is overloaded or has not finished initializing. # def lookup_metadata type, entry, query: nil compute_metadata.lookup "#{type}/#{entry}", query: query end ## # Retrieve an HTTP response from the Google Compute Engine Metadata # Service. Returns a {ComputeMetadata::Response} with a status code, # data, and headers. The response could be 200 for success, 404 if the # given entry is not present, or other HTTP result code for authorization # or other errors. # # @param [String] type Type of metadata to look up. Currently supported # values are "project" and "instance". # @param [String] entry Metadata entry path to look up. # @param query [Hash{String => String}] Any additional query parameters # to send with the request. # # @return [Google::Cloud::Env::ComputeMetadata::Response] the response # @raise [MetadataServerNotResponding] if the Metadata Server is not # responding. This could either be because the metadata service is # not present in the current environment, or if it is expected to be # present but is overloaded or has not finished initializing. # def lookup_metadata_response type, entry, query: nil compute_metadata.lookup_response "#{type}/#{entry}", query: query end ## # Determine whether the application is running on a Knative-based # hosting platform, such as Cloud Run or Cloud Functions. # # @return [boolean] # def knative? variables["K_SERVICE"] ? true : false end ## # Determine whether the application is running on Google App Engine. # # @return [boolean] # def app_engine? variables["GAE_INSTANCE"] ? true : false end ## # Determine whether the application is running on Google App Engine # Flexible Environment. # # @return [boolean] # def app_engine_flexible? app_engine? && variables["GAE_ENV"] != "standard" end ## # Determine whether the application is running on Google App Engine # Standard Environment. # # @return [boolean] # def app_engine_standard? app_engine? && variables["GAE_ENV"] == "standard" end ## # Determine whether the application is running on Google Kubernetes # Engine (GKE). # # @return [boolean] # def kubernetes_engine? kubernetes_engine_cluster_name ? true : false end alias container_engine? kubernetes_engine? ## # Determine whether the application is running on Google Cloud Shell. # # @return [boolean] # def cloud_shell? variables["GOOGLE_CLOUD_SHELL"] ? true : false end ## # Determine whether the application is running on Google Compute Engine. # # Note that most other products (e.g. App Engine, Kubernetes Engine, # Cloud Shell) themselves use Compute Engine under the hood, so this # method will return true for all the above products. If you want to # determine whether the application is running on a "raw" Compute Engine # VM without using a higher level hosting product, use # {Env#raw_compute_engine?}. # # @return [boolean] # def compute_engine? compute_smbios.google_compute? end ## # Determine whether the application is running on "raw" Google Compute # Engine without using a higher level hosting product such as App # Engine or Kubernetes Engine. # # @return [boolean] # def raw_compute_engine? compute_engine? && !knative? && !app_engine? && !cloud_shell? && !kubernetes_engine? end ## # Returns the unique string ID of the project hosting the application, # or `nil` if the application is not running on Google Cloud. # # @return [String,nil] # def project_id variables["GOOGLE_CLOUD_PROJECT"] || variables["GCLOUD_PROJECT"] || variables["DEVSHELL_PROJECT_ID"] || compute_metadata.lookup("project/project-id") rescue MetadataServerNotResponding nil end ## # Returns the unique numeric ID of the project hosting the application, # or `nil` if the application is not running on Google Cloud. # # Caveat: this method does not work and returns `nil` on CloudShell. # # @return [Integer,nil] # def numeric_project_id # CloudShell's metadata server seems to run in a dummy project. # We can get the user's normal project ID via environment variables, # but the numeric ID from the metadata service is not correct. So # disable this for CloudShell to avoid confusion. return nil if cloud_shell? result = begin compute_metadata.lookup "project/numeric-project-id" rescue MetadataServerNotResponding nil end result&.to_i end ## # Returns the name of the VM instance hosting the application, or `nil` # if the application is not running on Google Cloud. # # @return [String,nil] # def instance_name variables["GAE_INSTANCE"] || compute_metadata.lookup("instance/name") rescue MetadataServerNotResponding nil end ## # Returns the description field (which may be the empty string) of the # VM instance hosting the application, or `nil` if the application is # not running on Google Cloud. # # @return [String,nil] # def instance_description compute_metadata.lookup "instance/description" rescue MetadataServerNotResponding nil end ## # Returns the zone (for example "`us-central1-c`") in which the instance # hosting the application lives. Returns `nil` if the application is # not running on Google Cloud. # # @return [String,nil] # def instance_zone result = compute_metadata.lookup "instance/zone" result&.split("/")&.last rescue MetadataServerNotResponding nil end ## # Returns the machine type of the VM instance hosting the application, # or `nil` if the application is not running on Google Cloud. # # @return [String,nil] # def instance_machine_type result = compute_metadata.lookup "instance/machine-type" result&.split("/")&.last rescue MetadataServerNotResponding nil end ## # Returns an array (which may be empty) of all tags set on the VM # instance hosting the application, or `nil` if the application is not # running on Google Cloud. # # @return [Array,nil] # def instance_tags result = compute_metadata.lookup "instance/tags" result.nil? ? nil : JSON.parse(result) rescue MetadataServerNotResponding nil end ## # Returns an array (which may be empty) of all attribute keys present # for the VM instance hosting the application, or `nil` if the # application is not running on Google Cloud. # # @return [Array,nil] # def instance_attribute_keys result = compute_metadata.lookup "instance/attributes/" result&.split rescue MetadataServerNotResponding nil end ## # Returns the value of the given instance attribute for the VM instance # hosting the application, or `nil` if the given key does not exist or # application is not running on Google Cloud. # # @param [String] key Attribute key to look up. # @return [String,nil] # def instance_attribute key compute_metadata.lookup "instance/attributes/#{key}" rescue MetadataServerNotResponding nil end ## # Returns the name of the running Knative service, or `nil` if the # current code is not running on Knative. # # @return [String,nil] # def knative_service_id variables["K_SERVICE"] end alias knative_service_name knative_service_id ## # Returns the revision of the running Knative service, or `nil` if the # current code is not running on Knative. # # @return [String,nil] # def knative_service_revision variables["K_REVISION"] end ## # Returns the name of the running App Engine service, or `nil` if the # current code is not running in App Engine. # # @return [String,nil] # def app_engine_service_id variables["GAE_SERVICE"] end alias app_engine_service_name app_engine_service_id ## # Returns the version of the running App Engine service, or `nil` if the # current code is not running in App Engine. # # @return [String,nil] # def app_engine_service_version variables["GAE_VERSION"] end ## # Returns the amount of memory reserved for the current App Engine # instance, or `nil` if the current code is not running in App Engine. # # @return [Integer,nil] # def app_engine_memory_mb result = variables["GAE_MEMORY_MB"] result&.to_i end ## # Returns the name of the Kubernetes Engine cluster hosting the # application, or `nil` if the current code is not running in # Kubernetes Engine. # # @return [String,nil] # def kubernetes_engine_cluster_name instance_attribute "cluster-name" rescue MetadataServerNotResponding nil end alias container_engine_cluster_name kubernetes_engine_cluster_name ## # Returns the name of the Kubernetes Engine namespace hosting the # application, or `nil` if the current code is not running in # Kubernetes Engine. # # @return [String,nil] # def kubernetes_engine_namespace_id # The Kubernetes namespace is difficult to obtain without help from # the application using the Downward API. The environment variable # below is set in some older versions of GKE, and the file below is # present in Kubernetes as of version 1.9, but it is possible that # alternatives will need to be found in the future. variables["GKE_NAMESPACE_ID"] || file_system.read("/var/run/secrets/kubernetes.io/serviceaccount/namespace") end alias container_engine_namespace_id kubernetes_engine_namespace_id ## # Determine whether the application is running in an environment where a # Google Cloud logging agent is expected to be running. In such an # environment, we expect that the standard output and error streams are # likely to be parsed by the logging agent and log entries are written to # the Google Cloud Logging service. # # @return [boolean] # def logging_agent_expected? compute_engine? && !cloud_shell? && (app_engine? || knative? || kubernetes_engine?) end ## # Returns the global instance of {Google::Cloud::Env}. # # @return [Google::Cloud::Env] # def self.get ::Google::Cloud.env end end @env = Env.new ## # Returns the global instance of {Google::Cloud::Env}. # # @return [Google::Cloud::Env] # def self.env @env end end end google-cloud-env-2.3.1/.yardopts0000644000004100000410000000024715020727635016573 0ustar www-datawww-data--no-private --title=Google Cloud Env --markup markdown --markup-provider redcarpet ./lib/**/*.rb - README.md CONTRIBUTING.md CHANGELOG.md CODE_OF_CONDUCT.md LICENSE google-cloud-env-2.3.1/CODE_OF_CONDUCT.md0000644000004100000410000000367715020727635017536 0ustar www-datawww-data# Contributor Code of Conduct As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery * Personal attacks * Trolling or insulting/derogatory comments * Public or private harassment * Publishing other's private information, such as physical or electronic addresses, without explicit permission * Other unethical or unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) google-cloud-env-2.3.1/LICENSE0000644000004100000410000002613515020727635015736 0ustar www-datawww-data Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed 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. google-cloud-env-2.3.1/README.md0000644000004100000410000000377515020727635016215 0ustar www-datawww-data# google-cloud-env This library provides information on the application hosting environment for apps running on Google Cloud Platform. This includes information on the Google compute product being used, the current project and credentials, and other information surfaced via environment variables, the file system, and the Metadata Server. - [google-cloud-env API documentation](https://googleapis.dev/ruby/google-cloud-env/latest) ## Supported Ruby Versions This library is supported on Ruby 3.0+. In general, Google provides official support for CRuby versions that are actively supported by the Ruby Core team -- that is, Ruby versions that are either in normal maintenance or in security maintenance -- and for about one year after end of life. Older versions of Ruby _may_ still work, but are unsupported and not recommended. See https://www.ruby-lang.org/en/downloads/branches/ for more details about the Ruby-Core support schedule. See https://cloud.google.com/ruby/getting-started/supported-ruby-versions for more details about Google's Ruby support schedule. ## Contributing Contributions to this library are always welcome and highly encouraged. See the [Contributing Guide](https://googleapis.dev/ruby/google-cloud-env/latest/file.CONTRIBUTING.html) for more information on how to get started. Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. See [Code of Conduct](https://googleapis.dev/ruby/google-cloud-env/latest/file.CODE_OF_CONDUCT.html) for more information. ## License This library is licensed under Apache 2.0. Full license text is available in [LICENSE](https://googleapis.dev/ruby/google-cloud-env/latest/file.LICENSE.html). ## Support Please [report bugs at the project on Github](https://github.com/googleapis/google-cloud-ruby/issues). Don't hesitate to [ask questions](http://stackoverflow.com/questions/tagged/google-cloud-platform+ruby) about the client or APIs on [StackOverflow](http://stackoverflow.com). google-cloud-env-2.3.1/CHANGELOG.md0000644000004100000410000001005415020727635016533 0ustar www-datawww-data# Release History ### 2.3.1 (2025-05-23) #### Bug Fixes * Compatibility fix for JRuby 10 on non-Windows systems ([#86](https://github.com/googleapis/ruby-cloud-env/issues/86)) ### 2.3.0 (2025-04-30) #### Features * updated required ruby version to 3.1 ([#84](https://github.com/googleapis/ruby-cloud-env/issues/84)) ### 2.2.2 (2025-03-13) #### Bug Fixes * Updated dependencies to include base64 for Ruby 3.4 ([#81](https://github.com/googleapis/ruby-cloud-env/issues/81)) ### 2.2.1 (2024-10-04) #### Bug Fixes * Fixed edge cases in token expiration extraction ([#73](https://github.com/googleapis/ruby-cloud-env/issues/73)) ### 2.2.0 (2024-08-22) #### Features * Provide a query for whether a logging agent is expected in the current environment ([#70](https://github.com/googleapis/ruby-cloud-env/issues/70)) * Require Ruby 3.0 or later ([#71](https://github.com/googleapis/ruby-cloud-env/issues/71)) ### 2.1.1 (2024-02-01) #### Bug Fixes * Setting non-empty overrides simulates metadata server existence setting ([#64](https://github.com/googleapis/ruby-cloud-env/issues/64)) ### 2.1.0 (2023-12-12) #### Features * Provide retrieval_monotonic_time on compute metadata response objects ([#62](https://github.com/googleapis/ruby-cloud-env/issues/62)) ### 2.0.1 (2023-12-01) #### Bug Fixes * Bad response status or flavor headers no longer signal positive metadata existence ([#61](https://github.com/googleapis/ruby-cloud-env/issues/61)) * Increase token expiry buffer to three and a half minutes ([#59](https://github.com/googleapis/ruby-cloud-env/issues/59)) ### 2.0.0 (2023-11-14) This is a major overhaul of the mechanisms underlying this gem, to improve reliability and provide better mocking interfaces. Environment interrogation calls are unchanged, but the mocking override parameters from 1.x have been removed in favor of the new interfaces, hence the semver-major version bump. This version has not yet added explicit calls for detecting newer runtimes such as Cloud Run and Cloud Functions. Those will come in the near future. #### Features * Update minimum Ruby version to 2.7 * Provide access objects for information sources (such as environment variables, file system, and metadata server) * Each access object has an interface for providing mock data for testing * Much more robust retry policy and detection mechanisms for the metadata server * Provide ensure_metadata and lookup_metadata_response calls at the top level interface ### 1.7.0 (2023-05-15) #### Features * Update minimum Ruby version to 2.6 ([#34](https://github.com/googleapis/ruby-cloud-env/issues/34)) ### 1.6.0 (2022-03-21) * Support for Faraday 2 ### 1.5.0 (2021-03-08) #### Features * Drop support for Ruby 2.4 and add support for Ruby 3.0 ### 1.4.0 / 2020-10-12 #### Features * Honor GCE_METADATA_HOST environment variable ### 1.3.3 / 2020-07-10 #### Bug Fixes * Project ID logic honors GOOGLE_CLOUD_PROJECT ### 1.3.2 / 2020-05-28 #### Documentation * Fix a few broken links ### 1.3.1 / 2020-03-02 #### Bug Fixes * support faraday 1.x ### 1.3.0 / 2019-10-23 Now requires Ruby 2.4 or later. #### Features * Recognize App Engine Standard and Knative ### 1.2.1 / 2019-08-23 #### Bug Fixes * Send Metadata-Flavor header when testing the metadata server root #### Documentation * Update documentation ### 1.2.0 / 2019-06-19 * Support separate timeout for connecting to the metadata server vs the entire request ### 1.1.0 / 2019-05-29 * Support disabling of the metadata cache * Support configurable retries when querying the metadata server * Support configuration of the metadata request timeout ### 1.0.5 / 2018-09-20 * Update documentation. * Change documentation URL to googleapis GitHub org. ### 1.0.4 / 2018-09-12 * Add missing documentation files to package. ### 1.0.3 / 2018-09-10 * Update documentation. ### 1.0.2 / 2018-06-28 * Use Kubernetes Engine names. * Alias old method names for backwards compatibility. * Handle EHOSTDOWN error when connecting to env. ### 1.0.1 / 2017-07-11 * Update gem spec homepage links. ### 1.0.0 / 2017-03-31 * Initial release google-cloud-env-2.3.1/google-cloud-env.gemspec0000644000004100000410000000457715020727635021452 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: google-cloud-env 2.3.1 ruby lib Gem::Specification.new do |s| s.name = "google-cloud-env".freeze s.version = "2.3.1" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "bug_tracker_uri" => "https://github.com/googleapis/ruby-cloud-env/issues", "changelog_uri" => "https://rubydoc.info/gems/google-cloud-env/2.3.1/CHANGELOG.md", "documentation_uri" => "https://rubydoc.info/gems/google-cloud-env/2.3.1", "source_code_uri" => "https://github.com/googleapis/ruby-cloud-env" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Daniel Azuma".freeze] s.date = "1980-01-02" s.description = "google-cloud-env provides information on the Google Cloud Platform hosting environment. Applications can use this library to determine hosting context information such as the project ID, whether App Engine is running, what tags are set on the VM instance, and much more.".freeze s.email = ["dazuma@google.com".freeze] s.files = [".yardopts".freeze, "CHANGELOG.md".freeze, "CODE_OF_CONDUCT.md".freeze, "CONTRIBUTING.md".freeze, "LICENSE".freeze, "README.md".freeze, "SECURITY.md".freeze, "lib/google-cloud-env.rb".freeze, "lib/google/cloud/env.rb".freeze, "lib/google/cloud/env/compute_metadata.rb".freeze, "lib/google/cloud/env/compute_smbios.rb".freeze, "lib/google/cloud/env/file_system.rb".freeze, "lib/google/cloud/env/lazy_value.rb".freeze, "lib/google/cloud/env/variables.rb".freeze, "lib/google/cloud/env/version.rb".freeze] s.homepage = "https://github.com/googleapis/ruby-cloud-env".freeze s.licenses = ["Apache-2.0".freeze] s.required_ruby_version = Gem::Requirement.new(">= 3.1".freeze) s.rubygems_version = "3.3.15".freeze s.summary = "Google Cloud Platform hosting environment information.".freeze if s.respond_to? :specification_version then s.specification_version = 4 end if s.respond_to? :add_runtime_dependency then s.add_runtime_dependency(%q.freeze, ["~> 0.2"]) s.add_runtime_dependency(%q.freeze, [">= 1.0", "< 3.a"]) else s.add_dependency(%q.freeze, ["~> 0.2"]) s.add_dependency(%q.freeze, [">= 1.0", "< 3.a"]) end end