flipper-cloud 0.28.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/flipper/version.rb +1 -1
- metadata +14 -53
- data/docs/images/flipper_cloud.png +0 -0
- data/examples/cloud/app.ru +0 -12
- data/examples/cloud/basic.rb +0 -22
- data/examples/cloud/cloud_setup.rb +0 -4
- data/examples/cloud/forked.rb +0 -31
- data/examples/cloud/import.rb +0 -17
- data/examples/cloud/threaded.rb +0 -36
- data/flipper-cloud.gemspec +0 -28
- data/lib/flipper/cloud/configuration.rb +0 -189
- data/lib/flipper/cloud/dsl.rb +0 -27
- data/lib/flipper/cloud/engine.rb +0 -29
- data/lib/flipper/cloud/instrumenter.rb +0 -48
- data/lib/flipper/cloud/message_verifier.rb +0 -95
- data/lib/flipper/cloud/middleware.rb +0 -63
- data/lib/flipper/cloud/routes.rb +0 -13
- data/lib/flipper/cloud.rb +0 -57
- data/spec/flipper/cloud/configuration_spec.rb +0 -261
- data/spec/flipper/cloud/dsl_spec.rb +0 -82
- data/spec/flipper/cloud/engine_spec.rb +0 -95
- data/spec/flipper/cloud/message_verifier_spec.rb +0 -104
- data/spec/flipper/cloud/middleware_spec.rb +0 -289
- data/spec/flipper/cloud_spec.rb +0 -179
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: c22781f15c68a4a2bcc6fbcde3aca4336e4ddaaaec7c56702bafc78faaeefb11
         | 
| 4 | 
            +
              data.tar.gz: 4c60f975ce2f1922fc10f6184b4a9863d48041cc53aa2c3fd61895dd80762fe4
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: cd91a1b89e4782041a2c23d84ede4efae64ff15decc689820bce99456a948afeb70de5e4ba3e60b308a71a4303307b8f17be31c310e527661e8dd77814d5fe7b
         | 
| 7 | 
            +
              data.tar.gz: 2d6699d34f35a04593f5b973ac86517a8ada7ba41dc7924dcb5832796cb5687c19a2f0e84c90d67fe1c4f8c1a0dc45ad9679be202974619e15447fcb8dbaa7a4
         | 
    
        data/lib/flipper/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: flipper-cloud
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 1.0.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - John Nunemaker
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2023- | 
| 11 | 
            +
            date: 2023-08-23 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: flipper
         | 
| @@ -16,64 +16,31 @@ dependencies: | |
| 16 16 | 
             
                requirements:
         | 
| 17 17 | 
             
                - - "~>"
         | 
| 18 18 | 
             
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            -
                    version: 0. | 
| 19 | 
            +
                    version: 1.0.0
         | 
| 20 20 | 
             
              type: :runtime
         | 
| 21 21 | 
             
              prerelease: false
         | 
| 22 22 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 23 | 
             
                requirements:
         | 
| 24 24 | 
             
                - - "~>"
         | 
| 25 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            -
                    version: 0. | 
| 27 | 
            -
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            -
              name: brow
         | 
| 29 | 
            -
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            -
                requirements:
         | 
| 31 | 
            -
                - - "~>"
         | 
| 32 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            -
                    version: 0.4.1
         | 
| 34 | 
            -
              type: :runtime
         | 
| 35 | 
            -
              prerelease: false
         | 
| 36 | 
            -
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            -
                requirements:
         | 
| 38 | 
            -
                - - "~>"
         | 
| 39 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            -
                    version: 0.4.1
         | 
| 26 | 
            +
                    version: 1.0.0
         | 
| 41 27 | 
             
            description:
         | 
| 42 | 
            -
            email:
         | 
| 43 | 
            -
            - nunemaker@gmail.com
         | 
| 28 | 
            +
            email: support@flippercloud.io
         | 
| 44 29 | 
             
            executables: []
         | 
| 45 30 | 
             
            extensions: []
         | 
| 46 31 | 
             
            extra_rdoc_files: []
         | 
| 47 32 | 
             
            files:
         | 
| 48 | 
            -
            - docs/images/flipper_cloud.png
         | 
| 49 | 
            -
            - examples/cloud/app.ru
         | 
| 50 | 
            -
            - examples/cloud/basic.rb
         | 
| 51 | 
            -
            - examples/cloud/cloud_setup.rb
         | 
| 52 | 
            -
            - examples/cloud/forked.rb
         | 
| 53 | 
            -
            - examples/cloud/import.rb
         | 
| 54 | 
            -
            - examples/cloud/threaded.rb
         | 
| 55 | 
            -
            - flipper-cloud.gemspec
         | 
| 56 33 | 
             
            - lib/flipper-cloud.rb
         | 
| 57 | 
            -
            - lib/flipper/cloud.rb
         | 
| 58 | 
            -
            - lib/flipper/cloud/configuration.rb
         | 
| 59 | 
            -
            - lib/flipper/cloud/dsl.rb
         | 
| 60 | 
            -
            - lib/flipper/cloud/engine.rb
         | 
| 61 | 
            -
            - lib/flipper/cloud/instrumenter.rb
         | 
| 62 | 
            -
            - lib/flipper/cloud/message_verifier.rb
         | 
| 63 | 
            -
            - lib/flipper/cloud/middleware.rb
         | 
| 64 | 
            -
            - lib/flipper/cloud/routes.rb
         | 
| 65 34 | 
             
            - lib/flipper/version.rb
         | 
| 66 | 
            -
             | 
| 67 | 
            -
            - spec/flipper/cloud/dsl_spec.rb
         | 
| 68 | 
            -
            - spec/flipper/cloud/engine_spec.rb
         | 
| 69 | 
            -
            - spec/flipper/cloud/message_verifier_spec.rb
         | 
| 70 | 
            -
            - spec/flipper/cloud/middleware_spec.rb
         | 
| 71 | 
            -
            - spec/flipper/cloud_spec.rb
         | 
| 72 | 
            -
            homepage: https://github.com/jnunemaker/flipper
         | 
| 35 | 
            +
            homepage: https://www.flippercloud.io
         | 
| 73 36 | 
             
            licenses:
         | 
| 74 37 | 
             
            - MIT
         | 
| 75 38 | 
             
            metadata:
         | 
| 76 | 
            -
               | 
| 39 | 
            +
              documentation_uri: https://www.flippercloud.io/docs
         | 
| 40 | 
            +
              homepage_uri: https://www.flippercloud.io
         | 
| 41 | 
            +
              source_code_uri: https://github.com/flippercloud/flipper
         | 
| 42 | 
            +
              bug_tracker_uri: https://github.com/flippercloud/flipper/issues
         | 
| 43 | 
            +
              changelog_uri: https://github.com/flippercloud/flipper/blob/main/Changelog.md
         | 
| 77 44 | 
             
            post_install_message:
         | 
| 78 45 | 
             
            rdoc_options: []
         | 
| 79 46 | 
             
            require_paths:
         | 
| @@ -89,14 +56,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 89 56 | 
             
                - !ruby/object:Gem::Version
         | 
| 90 57 | 
             
                  version: '0'
         | 
| 91 58 | 
             
            requirements: []
         | 
| 92 | 
            -
            rubygems_version: 3. | 
| 59 | 
            +
            rubygems_version: 3.4.10
         | 
| 93 60 | 
             
            signing_key:
         | 
| 94 61 | 
             
            specification_version: 4
         | 
| 95 | 
            -
            summary:  | 
| 96 | 
            -
            test_files:
         | 
| 97 | 
            -
            - spec/flipper/cloud/configuration_spec.rb
         | 
| 98 | 
            -
            - spec/flipper/cloud/dsl_spec.rb
         | 
| 99 | 
            -
            - spec/flipper/cloud/engine_spec.rb
         | 
| 100 | 
            -
            - spec/flipper/cloud/message_verifier_spec.rb
         | 
| 101 | 
            -
            - spec/flipper/cloud/middleware_spec.rb
         | 
| 102 | 
            -
            - spec/flipper/cloud_spec.rb
         | 
| 62 | 
            +
            summary: "[DEPRECATED] This gem has been merged into the `flipper` gem"
         | 
| 63 | 
            +
            test_files: []
         | 
| Binary file | 
    
        data/examples/cloud/app.ru
    DELETED
    
    | @@ -1,12 +0,0 @@ | |
| 1 | 
            -
            # Usage (from the repo root):
         | 
| 2 | 
            -
            #   env FLIPPER_CLOUD_TOKEN=<token> FLIPPER_CLOUD_SYNC_SECRET=<secret> bundle exec rackup examples/cloud/app.ru -p 9999
         | 
| 3 | 
            -
            #   http://localhost:9999/
         | 
| 4 | 
            -
             | 
| 5 | 
            -
            require 'bundler/setup'
         | 
| 6 | 
            -
            require 'flipper/cloud'
         | 
| 7 | 
            -
             | 
| 8 | 
            -
            Flipper.configure do |config|
         | 
| 9 | 
            -
              config.default { Flipper::Cloud.new }
         | 
| 10 | 
            -
            end
         | 
| 11 | 
            -
             | 
| 12 | 
            -
            run Flipper::Cloud.app
         | 
    
        data/examples/cloud/basic.rb
    DELETED
    
    | @@ -1,22 +0,0 @@ | |
| 1 | 
            -
            # Usage (from the repo root):
         | 
| 2 | 
            -
            # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/basic.rb
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            require_relative "./cloud_setup"
         | 
| 5 | 
            -
            require 'bundler/setup'
         | 
| 6 | 
            -
            require 'flipper/cloud'
         | 
| 7 | 
            -
             | 
| 8 | 
            -
            Flipper[:stats].enable
         | 
| 9 | 
            -
             | 
| 10 | 
            -
            if Flipper[:stats].enabled?
         | 
| 11 | 
            -
              puts 'Enabled!'
         | 
| 12 | 
            -
            else
         | 
| 13 | 
            -
              puts 'Disabled!'
         | 
| 14 | 
            -
            end
         | 
| 15 | 
            -
             | 
| 16 | 
            -
            Flipper[:stats].disable
         | 
| 17 | 
            -
             | 
| 18 | 
            -
            if Flipper[:stats].enabled?
         | 
| 19 | 
            -
              puts 'Enabled!'
         | 
| 20 | 
            -
            else
         | 
| 21 | 
            -
              puts 'Disabled!'
         | 
| 22 | 
            -
            end
         | 
    
        data/examples/cloud/forked.rb
    DELETED
    
    | @@ -1,31 +0,0 @@ | |
| 1 | 
            -
            # Usage (from the repo root):
         | 
| 2 | 
            -
            # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/threaded.rb
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            require_relative "./cloud_setup"
         | 
| 5 | 
            -
            require 'bundler/setup'
         | 
| 6 | 
            -
            require 'flipper/cloud'
         | 
| 7 | 
            -
             | 
| 8 | 
            -
            pids = 5.times.map do |n|
         | 
| 9 | 
            -
              fork {
         | 
| 10 | 
            -
                # Check every second to see if the feature is enabled
         | 
| 11 | 
            -
                threads = []
         | 
| 12 | 
            -
                5.times do
         | 
| 13 | 
            -
                  threads << Thread.new do
         | 
| 14 | 
            -
                    loop do
         | 
| 15 | 
            -
                      sleep rand
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                      if Flipper[:stats].enabled?
         | 
| 18 | 
            -
                        puts "#{Process.pid} #{Time.now.to_i} Enabled!"
         | 
| 19 | 
            -
                      else
         | 
| 20 | 
            -
                        puts "#{Process.pid} #{Time.now.to_i} Disabled!"
         | 
| 21 | 
            -
                      end
         | 
| 22 | 
            -
                    end
         | 
| 23 | 
            -
                  end
         | 
| 24 | 
            -
                end
         | 
| 25 | 
            -
                threads.map(&:join)
         | 
| 26 | 
            -
              }
         | 
| 27 | 
            -
            end
         | 
| 28 | 
            -
             | 
| 29 | 
            -
            pids.each do |pid|
         | 
| 30 | 
            -
              Process.waitpid pid, 0
         | 
| 31 | 
            -
            end
         | 
    
        data/examples/cloud/import.rb
    DELETED
    
    | @@ -1,17 +0,0 @@ | |
| 1 | 
            -
            # Usage (from the repo root):
         | 
| 2 | 
            -
            #   env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/import.rb
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            require_relative "./cloud_setup"
         | 
| 5 | 
            -
            require 'bundler/setup'
         | 
| 6 | 
            -
            require 'flipper'
         | 
| 7 | 
            -
            require 'flipper/cloud'
         | 
| 8 | 
            -
             | 
| 9 | 
            -
            Flipper.enable(:test)
         | 
| 10 | 
            -
            Flipper.enable(:search)
         | 
| 11 | 
            -
            Flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
         | 
| 12 | 
            -
            Flipper.enable_percentage_of_time(:logging, 5)
         | 
| 13 | 
            -
             | 
| 14 | 
            -
            cloud = Flipper::Cloud.new
         | 
| 15 | 
            -
             | 
| 16 | 
            -
            # makes cloud identical to memory flipper
         | 
| 17 | 
            -
            cloud.import(Flipper)
         | 
    
        data/examples/cloud/threaded.rb
    DELETED
    
    | @@ -1,36 +0,0 @@ | |
| 1 | 
            -
            # Usage (from the repo root):
         | 
| 2 | 
            -
            # env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/threaded.rb
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            require_relative "./cloud_setup"
         | 
| 5 | 
            -
            require 'bundler/setup'
         | 
| 6 | 
            -
            require 'flipper/cloud'
         | 
| 7 | 
            -
            require "active_support/notifications"
         | 
| 8 | 
            -
            require "active_support/isolated_execution_state"
         | 
| 9 | 
            -
             | 
| 10 | 
            -
            ActiveSupport::Notifications.subscribe(/poller\.flipper/) do |*args|
         | 
| 11 | 
            -
              p args: args
         | 
| 12 | 
            -
            end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
            Flipper.configure do |config|
         | 
| 15 | 
            -
              config.default {
         | 
| 16 | 
            -
                Flipper::Cloud.new(local_adapter: config.adapter, instrumenter: ActiveSupport::Notifications)
         | 
| 17 | 
            -
              }
         | 
| 18 | 
            -
            end
         | 
| 19 | 
            -
             | 
| 20 | 
            -
            # Check every second to see if the feature is enabled
         | 
| 21 | 
            -
            threads = []
         | 
| 22 | 
            -
            10.times do
         | 
| 23 | 
            -
              threads << Thread.new do
         | 
| 24 | 
            -
                loop do
         | 
| 25 | 
            -
                  sleep rand
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                  if Flipper[:stats].enabled?
         | 
| 28 | 
            -
                    puts "#{Time.now.to_i} Enabled!"
         | 
| 29 | 
            -
                  else
         | 
| 30 | 
            -
                    puts "#{Time.now.to_i} Disabled!"
         | 
| 31 | 
            -
                  end
         | 
| 32 | 
            -
                end
         | 
| 33 | 
            -
              end
         | 
| 34 | 
            -
            end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
            threads.map(&:join)
         | 
    
        data/flipper-cloud.gemspec
    DELETED
    
    | @@ -1,28 +0,0 @@ | |
| 1 | 
            -
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            -
            require File.expand_path('../lib/flipper/version', __FILE__)
         | 
| 3 | 
            -
            require File.expand_path('../lib/flipper/metadata', __FILE__)
         | 
| 4 | 
            -
             | 
| 5 | 
            -
            flipper_cloud_files = lambda do |file|
         | 
| 6 | 
            -
              file =~ /cloud/
         | 
| 7 | 
            -
            end
         | 
| 8 | 
            -
             | 
| 9 | 
            -
            Gem::Specification.new do |gem|
         | 
| 10 | 
            -
              gem.authors       = ['John Nunemaker']
         | 
| 11 | 
            -
              gem.email         = ['nunemaker@gmail.com']
         | 
| 12 | 
            -
              gem.summary       = 'FlipperCloud.io adapter for Flipper'
         | 
| 13 | 
            -
              gem.license       = 'MIT'
         | 
| 14 | 
            -
              gem.homepage      = 'https://github.com/jnunemaker/flipper'
         | 
| 15 | 
            -
             | 
| 16 | 
            -
              extra_files = [
         | 
| 17 | 
            -
                'lib/flipper/version.rb',
         | 
| 18 | 
            -
              ]
         | 
| 19 | 
            -
              gem.files         = `git ls-files`.split("\n").select(&flipper_cloud_files) + extra_files
         | 
| 20 | 
            -
              gem.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n").select(&flipper_cloud_files)
         | 
| 21 | 
            -
              gem.name          = 'flipper-cloud'
         | 
| 22 | 
            -
              gem.require_paths = ['lib']
         | 
| 23 | 
            -
              gem.version       = Flipper::VERSION
         | 
| 24 | 
            -
              gem.metadata      = Flipper::METADATA
         | 
| 25 | 
            -
             | 
| 26 | 
            -
              gem.add_dependency 'flipper', "~> #{Flipper::VERSION}"
         | 
| 27 | 
            -
              gem.add_dependency "brow", "~> 0.4.1"
         | 
| 28 | 
            -
            end
         | 
| @@ -1,189 +0,0 @@ | |
| 1 | 
            -
            require "socket"
         | 
| 2 | 
            -
            require "flipper/adapters/http"
         | 
| 3 | 
            -
            require "flipper/adapters/poll"
         | 
| 4 | 
            -
            require "flipper/poller"
         | 
| 5 | 
            -
            require "flipper/adapters/memory"
         | 
| 6 | 
            -
            require "flipper/adapters/dual_write"
         | 
| 7 | 
            -
            require "flipper/adapters/sync/synchronizer"
         | 
| 8 | 
            -
            require "flipper/cloud/instrumenter"
         | 
| 9 | 
            -
            require "brow"
         | 
| 10 | 
            -
             | 
| 11 | 
            -
            module Flipper
         | 
| 12 | 
            -
              module Cloud
         | 
| 13 | 
            -
                class Configuration
         | 
| 14 | 
            -
                  # The set of valid ways that syncing can happpen.
         | 
| 15 | 
            -
                  VALID_SYNC_METHODS = Set[
         | 
| 16 | 
            -
                    :poll,
         | 
| 17 | 
            -
                    :webhook,
         | 
| 18 | 
            -
                  ].freeze
         | 
| 19 | 
            -
             | 
| 20 | 
            -
                  DEFAULT_URL = "https://www.flippercloud.io/adapter".freeze
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                  # Private: Keeps track of brow instances so they can be shared across
         | 
| 23 | 
            -
                  # threads.
         | 
| 24 | 
            -
                  def self.brow_instances
         | 
| 25 | 
            -
                    @brow_instances ||= Concurrent::Map.new
         | 
| 26 | 
            -
                  end
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                  # Public: The token corresponding to an environment on flippercloud.io.
         | 
| 29 | 
            -
                  attr_accessor :token
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                  # Public: The url for http adapter. Really should only be customized for
         | 
| 32 | 
            -
                   #        development work. Feel free to forget you ever saw this.
         | 
| 33 | 
            -
                  attr_reader :url
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                  # Public: net/http read timeout for all http requests (default: 5).
         | 
| 36 | 
            -
                  attr_accessor :read_timeout
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                  # Public: net/http open timeout for all http requests (default: 5).
         | 
| 39 | 
            -
                  attr_accessor :open_timeout
         | 
| 40 | 
            -
             | 
| 41 | 
            -
                  # Public: net/http write timeout for all http requests (default: 5).
         | 
| 42 | 
            -
                  attr_accessor :write_timeout
         | 
| 43 | 
            -
             | 
| 44 | 
            -
                  # Public: IO stream to send debug output too. Off by default.
         | 
| 45 | 
            -
                  #
         | 
| 46 | 
            -
                  #  # for example, this would send all http request information to STDOUT
         | 
| 47 | 
            -
                  #  configuration = Flipper::Cloud::Configuration.new
         | 
| 48 | 
            -
                  #  configuration.debug_output = STDOUT
         | 
| 49 | 
            -
                  attr_accessor :debug_output
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                  # Public: Instrumenter to use for the Flipper instance returned by
         | 
| 52 | 
            -
                  #         Flipper::Cloud.new (default: Flipper::Instrumenters::Noop).
         | 
| 53 | 
            -
                  #
         | 
| 54 | 
            -
                  #  # for example, to use active support notifications you could do:
         | 
| 55 | 
            -
                  #  configuration = Flipper::Cloud::Configuration.new
         | 
| 56 | 
            -
                  #  configuration.instrumenter = ActiveSupport::Notifications
         | 
| 57 | 
            -
                  attr_accessor :instrumenter
         | 
| 58 | 
            -
             | 
| 59 | 
            -
                  # Public: Local adapter that all reads should go to in order to ensure
         | 
| 60 | 
            -
                  # latency is low and resiliency is high. This adapter is automatically
         | 
| 61 | 
            -
                  # kept in sync with cloud.
         | 
| 62 | 
            -
                  #
         | 
| 63 | 
            -
                  #  # for example, to use active record you could do:
         | 
| 64 | 
            -
                  #  configuration = Flipper::Cloud::Configuration.new
         | 
| 65 | 
            -
                  #  configuration.local_adapter = Flipper::Adapters::ActiveRecord.new
         | 
| 66 | 
            -
                  attr_accessor :local_adapter
         | 
| 67 | 
            -
             | 
| 68 | 
            -
                  # Public: The Integer or Float number of seconds between attempts to bring
         | 
| 69 | 
            -
                  # the local in sync with cloud (default: 10).
         | 
| 70 | 
            -
                  attr_accessor :sync_interval
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                  # Public: The secret used to verify if syncs in the middleware should
         | 
| 73 | 
            -
                  # occur or not.
         | 
| 74 | 
            -
                  attr_accessor :sync_secret
         | 
| 75 | 
            -
             | 
| 76 | 
            -
                  def initialize(options = {})
         | 
| 77 | 
            -
                    @token = options.fetch(:token) { ENV["FLIPPER_CLOUD_TOKEN"] }
         | 
| 78 | 
            -
             | 
| 79 | 
            -
                    if @token.nil?
         | 
| 80 | 
            -
                      raise ArgumentError, "Flipper::Cloud token is missing. Please set FLIPPER_CLOUD_TOKEN or provide the token (e.g. Flipper::Cloud.new(token: 'token'))."
         | 
| 81 | 
            -
                    end
         | 
| 82 | 
            -
             | 
| 83 | 
            -
                    @read_timeout = options.fetch(:read_timeout) { ENV.fetch("FLIPPER_CLOUD_READ_TIMEOUT", 5).to_f }
         | 
| 84 | 
            -
                    @open_timeout = options.fetch(:open_timeout) { ENV.fetch("FLIPPER_CLOUD_OPEN_TIMEOUT", 5).to_f }
         | 
| 85 | 
            -
                    @write_timeout = options.fetch(:write_timeout) { ENV.fetch("FLIPPER_CLOUD_WRITE_TIMEOUT", 5).to_f }
         | 
| 86 | 
            -
                    @sync_interval = options.fetch(:sync_interval) { ENV.fetch("FLIPPER_CLOUD_SYNC_INTERVAL", 10).to_f }
         | 
| 87 | 
            -
                    @sync_secret = options.fetch(:sync_secret) { ENV["FLIPPER_CLOUD_SYNC_SECRET"] }
         | 
| 88 | 
            -
                    @local_adapter = options.fetch(:local_adapter) { Adapters::Memory.new }
         | 
| 89 | 
            -
                    @debug_output = options[:debug_output]
         | 
| 90 | 
            -
                    @adapter_block = ->(adapter) { adapter }
         | 
| 91 | 
            -
                    self.url = options.fetch(:url) { ENV.fetch("FLIPPER_CLOUD_URL", DEFAULT_URL) }
         | 
| 92 | 
            -
             | 
| 93 | 
            -
                    instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
         | 
| 94 | 
            -
             | 
| 95 | 
            -
                    # This is alpha. Don't use this unless you are me. And you are not me.
         | 
| 96 | 
            -
                    cloud_instrument = options.fetch(:cloud_instrument) { ENV["FLIPPER_CLOUD_INSTRUMENT"] == "1" }
         | 
| 97 | 
            -
                    @instrumenter = if cloud_instrument
         | 
| 98 | 
            -
                      Instrumenter.new(brow: brow, instrumenter: instrumenter)
         | 
| 99 | 
            -
                    else
         | 
| 100 | 
            -
                      instrumenter
         | 
| 101 | 
            -
                    end
         | 
| 102 | 
            -
                  end
         | 
| 103 | 
            -
             | 
| 104 | 
            -
                  # Public: Read or customize the http adapter. Calling without a block will
         | 
| 105 | 
            -
                  # perform a read. Calling with a block yields the cloud adapter
         | 
| 106 | 
            -
                  # for customization.
         | 
| 107 | 
            -
                  #
         | 
| 108 | 
            -
                  #   # for example, to instrument the http calls, you can wrap the http
         | 
| 109 | 
            -
                  #   # adapter with the intsrumented adapter
         | 
| 110 | 
            -
                  #   configuration = Flipper::Cloud::Configuration.new
         | 
| 111 | 
            -
                  #   configuration.adapter do |adapter|
         | 
| 112 | 
            -
                  #     Flipper::Adapters::Instrumented.new(adapter)
         | 
| 113 | 
            -
                  #   end
         | 
| 114 | 
            -
                  #
         | 
| 115 | 
            -
                  def adapter(&block)
         | 
| 116 | 
            -
                    if block_given?
         | 
| 117 | 
            -
                      @adapter_block = block
         | 
| 118 | 
            -
                    else
         | 
| 119 | 
            -
                      @adapter_block.call app_adapter
         | 
| 120 | 
            -
                    end
         | 
| 121 | 
            -
                  end
         | 
| 122 | 
            -
             | 
| 123 | 
            -
                  # Public: Set url for the http adapter.
         | 
| 124 | 
            -
                  attr_writer :url
         | 
| 125 | 
            -
             | 
| 126 | 
            -
                  def sync
         | 
| 127 | 
            -
                    Flipper::Adapters::Sync::Synchronizer.new(local_adapter, http_adapter, {
         | 
| 128 | 
            -
                      instrumenter: instrumenter,
         | 
| 129 | 
            -
                    }).call
         | 
| 130 | 
            -
                  end
         | 
| 131 | 
            -
             | 
| 132 | 
            -
                  def brow
         | 
| 133 | 
            -
                    self.class.brow_instances.compute_if_absent(url + token) do
         | 
| 134 | 
            -
                      uri = URI.parse(url)
         | 
| 135 | 
            -
                      uri.path = "#{uri.path}/events".squeeze("/")
         | 
| 136 | 
            -
             | 
| 137 | 
            -
                      Brow::Client.new({
         | 
| 138 | 
            -
                        url: uri.to_s,
         | 
| 139 | 
            -
                        headers: {
         | 
| 140 | 
            -
                          "Accept" => "application/json",
         | 
| 141 | 
            -
                          "Content-Type" => "application/json",
         | 
| 142 | 
            -
                          "User-Agent" => "Flipper v#{VERSION} via Brow v#{Brow::VERSION}",
         | 
| 143 | 
            -
                          "Flipper-Cloud-Token" => @token,
         | 
| 144 | 
            -
                        }
         | 
| 145 | 
            -
                      })
         | 
| 146 | 
            -
                    end
         | 
| 147 | 
            -
                  end
         | 
| 148 | 
            -
             | 
| 149 | 
            -
                  # Public: The method that will be used to synchronize local adapter with
         | 
| 150 | 
            -
                  # cloud. (default: :poll, will be :webhook if sync_secret is set).
         | 
| 151 | 
            -
                  def sync_method
         | 
| 152 | 
            -
                    sync_secret ? :webhook : :poll
         | 
| 153 | 
            -
                  end
         | 
| 154 | 
            -
             | 
| 155 | 
            -
                  private
         | 
| 156 | 
            -
             | 
| 157 | 
            -
                  def app_adapter
         | 
| 158 | 
            -
                    read_adapter = sync_method == :webhook ? local_adapter : poll_adapter
         | 
| 159 | 
            -
                    Flipper::Adapters::DualWrite.new(read_adapter, http_adapter)
         | 
| 160 | 
            -
                  end
         | 
| 161 | 
            -
             | 
| 162 | 
            -
                  def poller
         | 
| 163 | 
            -
                    Flipper::Poller.get(@url + @token, {
         | 
| 164 | 
            -
                      interval: sync_interval,
         | 
| 165 | 
            -
                      remote_adapter: http_adapter,
         | 
| 166 | 
            -
                      instrumenter: instrumenter,
         | 
| 167 | 
            -
                    }).tap(&:start)
         | 
| 168 | 
            -
                  end
         | 
| 169 | 
            -
             | 
| 170 | 
            -
                  def poll_adapter
         | 
| 171 | 
            -
                    Flipper::Adapters::Poll.new(poller, local_adapter)
         | 
| 172 | 
            -
                  end
         | 
| 173 | 
            -
             | 
| 174 | 
            -
                  def http_adapter
         | 
| 175 | 
            -
                    Flipper::Adapters::Http.new({
         | 
| 176 | 
            -
                      url: @url,
         | 
| 177 | 
            -
                      read_timeout: @read_timeout,
         | 
| 178 | 
            -
                      open_timeout: @open_timeout,
         | 
| 179 | 
            -
                      write_timeout: @write_timeout,
         | 
| 180 | 
            -
                      max_retries: 0, # we'll handle retries ourselves
         | 
| 181 | 
            -
                      debug_output: @debug_output,
         | 
| 182 | 
            -
                      headers: {
         | 
| 183 | 
            -
                        "Flipper-Cloud-Token" => @token,
         | 
| 184 | 
            -
                      },
         | 
| 185 | 
            -
                    })
         | 
| 186 | 
            -
                  end
         | 
| 187 | 
            -
                end
         | 
| 188 | 
            -
              end
         | 
| 189 | 
            -
            end
         | 
    
        data/lib/flipper/cloud/dsl.rb
    DELETED
    
    | @@ -1,27 +0,0 @@ | |
| 1 | 
            -
            require 'forwardable'
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            module Flipper
         | 
| 4 | 
            -
              module Cloud
         | 
| 5 | 
            -
                class DSL < SimpleDelegator
         | 
| 6 | 
            -
                  attr_reader :cloud_configuration
         | 
| 7 | 
            -
             | 
| 8 | 
            -
                  def initialize(cloud_configuration)
         | 
| 9 | 
            -
                    @cloud_configuration = cloud_configuration
         | 
| 10 | 
            -
                    super Flipper.new(@cloud_configuration.adapter, instrumenter: @cloud_configuration.instrumenter)
         | 
| 11 | 
            -
                  end
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                  def sync
         | 
| 14 | 
            -
                    @cloud_configuration.sync
         | 
| 15 | 
            -
                  end
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                  def sync_secret
         | 
| 18 | 
            -
                    @cloud_configuration.sync_secret
         | 
| 19 | 
            -
                  end
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                  def inspect
         | 
| 22 | 
            -
                    inspect_id = ::Kernel::format "%x", (object_id * 2)
         | 
| 23 | 
            -
                    %(#<#{self.class}:0x#{inspect_id} @cloud_configuration=#{cloud_configuration.inspect}, flipper=#{__getobj__.inspect}>)
         | 
| 24 | 
            -
                  end
         | 
| 25 | 
            -
                end
         | 
| 26 | 
            -
              end
         | 
| 27 | 
            -
            end
         | 
    
        data/lib/flipper/cloud/engine.rb
    DELETED
    
    | @@ -1,29 +0,0 @@ | |
| 1 | 
            -
            require "flipper/railtie"
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            module Flipper
         | 
| 4 | 
            -
              module Cloud
         | 
| 5 | 
            -
                class Engine < Rails::Engine
         | 
| 6 | 
            -
                  paths["config/routes.rb"] = ["lib/flipper/cloud/routes.rb"]
         | 
| 7 | 
            -
             | 
| 8 | 
            -
                  config.before_configuration do
         | 
| 9 | 
            -
                    config.flipper.cloud_path = "_flipper"
         | 
| 10 | 
            -
                  end
         | 
| 11 | 
            -
             | 
| 12 | 
            -
                  initializer "flipper.cloud.default", before: :load_config_initializers do |app|
         | 
| 13 | 
            -
                    Flipper.configure do |config|
         | 
| 14 | 
            -
                      config.default do
         | 
| 15 | 
            -
                        if ENV["FLIPPER_CLOUD_TOKEN"]
         | 
| 16 | 
            -
                          Flipper::Cloud.new(
         | 
| 17 | 
            -
                            local_adapter: config.adapter,
         | 
| 18 | 
            -
                            instrumenter: app.config.flipper.instrumenter
         | 
| 19 | 
            -
                          )
         | 
| 20 | 
            -
                        else
         | 
| 21 | 
            -
                          warn "Missing FLIPPER_CLOUD_TOKEN environment variable. Disabling Flipper::Cloud."
         | 
| 22 | 
            -
                          Flipper.new(config.adapter)
         | 
| 23 | 
            -
                        end
         | 
| 24 | 
            -
                      end
         | 
| 25 | 
            -
                    end
         | 
| 26 | 
            -
                  end
         | 
| 27 | 
            -
                end
         | 
| 28 | 
            -
              end
         | 
| 29 | 
            -
            end
         | 
| @@ -1,48 +0,0 @@ | |
| 1 | 
            -
            require "delegate"
         | 
| 2 | 
            -
            require "flipper/instrumenters/noop"
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            module Flipper
         | 
| 5 | 
            -
              module Cloud
         | 
| 6 | 
            -
                class Instrumenter < SimpleDelegator
         | 
| 7 | 
            -
                  def initialize(options = {})
         | 
| 8 | 
            -
                    @brow = options.fetch(:brow)
         | 
| 9 | 
            -
                    @instrumenter = options.fetch(:instrumenter, Instrumenters::Noop)
         | 
| 10 | 
            -
                    super @instrumenter
         | 
| 11 | 
            -
                  end
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                  def instrument(name, payload = {}, &block)
         | 
| 14 | 
            -
                    result = @instrumenter.instrument(name, payload, &block)
         | 
| 15 | 
            -
                    push name, payload
         | 
| 16 | 
            -
                    result
         | 
| 17 | 
            -
                  end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                  private
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                  def push(name, payload)
         | 
| 22 | 
            -
                    return unless name == Flipper::Feature::InstrumentationName
         | 
| 23 | 
            -
                    return unless :enabled? == payload[:operation]
         | 
| 24 | 
            -
             | 
| 25 | 
            -
                    dimensions = {
         | 
| 26 | 
            -
                      "feature" => payload[:feature_name].to_s,
         | 
| 27 | 
            -
                      "result" => payload[:result].to_s,
         | 
| 28 | 
            -
                    }
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                    if (thing = payload[:thing])
         | 
| 31 | 
            -
                      dimensions["flipper_id"] = thing.value.to_s
         | 
| 32 | 
            -
                    end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                    if (actors = payload[:actors])
         | 
| 35 | 
            -
                      dimensions["flipper_ids"] = actors.map { |actor| actor.value.to_s }
         | 
| 36 | 
            -
                    end
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                    event = {
         | 
| 39 | 
            -
                      type: "enabled",
         | 
| 40 | 
            -
                      dimensions: dimensions,
         | 
| 41 | 
            -
                      measures: {},
         | 
| 42 | 
            -
                      ts: Time.now.utc,
         | 
| 43 | 
            -
                    }
         | 
| 44 | 
            -
                    @brow.push event
         | 
| 45 | 
            -
                  end
         | 
| 46 | 
            -
                end
         | 
| 47 | 
            -
              end
         | 
| 48 | 
            -
            end
         | 
| @@ -1,95 +0,0 @@ | |
| 1 | 
            -
            require "openssl"
         | 
| 2 | 
            -
            require "digest/sha2"
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            module Flipper
         | 
| 5 | 
            -
              module Cloud
         | 
| 6 | 
            -
                class MessageVerifier
         | 
| 7 | 
            -
                  class InvalidSignature < StandardError; end
         | 
| 8 | 
            -
             | 
| 9 | 
            -
                  DEFAULT_VERSION = "v1"
         | 
| 10 | 
            -
             | 
| 11 | 
            -
                  def self.header(signature, timestamp, version = DEFAULT_VERSION)
         | 
| 12 | 
            -
                    raise ArgumentError, "timestamp should be an instance of Time" unless timestamp.is_a?(Time)
         | 
| 13 | 
            -
                    raise ArgumentError, "signature should be a string" unless signature.is_a?(String)
         | 
| 14 | 
            -
                    "t=#{timestamp.to_i},#{version}=#{signature}"
         | 
| 15 | 
            -
                  end
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                  def initialize(secret:, version: DEFAULT_VERSION)
         | 
| 18 | 
            -
                    @secret = secret
         | 
| 19 | 
            -
                    @version = version || DEFAULT_VERSION
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                    raise ArgumentError, "secret should be a string" unless @secret.is_a?(String)
         | 
| 22 | 
            -
                    raise ArgumentError, "version should be a string" unless @version.is_a?(String)
         | 
| 23 | 
            -
                  end
         | 
| 24 | 
            -
             | 
| 25 | 
            -
                  def generate(payload, timestamp)
         | 
| 26 | 
            -
                    raise ArgumentError, "timestamp should be an instance of Time" unless timestamp.is_a?(Time)
         | 
| 27 | 
            -
                    raise ArgumentError, "payload should be a string" unless payload.is_a?(String)
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                    OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), @secret, "#{timestamp.to_i}.#{payload}")
         | 
| 30 | 
            -
                  end
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                  def header(signature, timestamp)
         | 
| 33 | 
            -
                    self.class.header(signature, timestamp, @version)
         | 
| 34 | 
            -
                  end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                  # Public: Verifies the signature header for a given payload.
         | 
| 37 | 
            -
                  #
         | 
| 38 | 
            -
                  # Raises a InvalidSignature in the following cases:
         | 
| 39 | 
            -
                  # - the header does not match the expected format
         | 
| 40 | 
            -
                  # - no signatures found with the expected scheme
         | 
| 41 | 
            -
                  # - no signatures matching the expected signature
         | 
| 42 | 
            -
                  # - a tolerance is provided and the timestamp is not within the
         | 
| 43 | 
            -
                  #   tolerance
         | 
| 44 | 
            -
                  #
         | 
| 45 | 
            -
                  # Returns true otherwise.
         | 
| 46 | 
            -
                  def verify(payload, header, tolerance: nil)
         | 
| 47 | 
            -
                    begin
         | 
| 48 | 
            -
                      timestamp, signatures = get_timestamp_and_signatures(header)
         | 
| 49 | 
            -
                    rescue StandardError
         | 
| 50 | 
            -
                      raise InvalidSignature, "Unable to extract timestamp and signatures from header"
         | 
| 51 | 
            -
                    end
         | 
| 52 | 
            -
             | 
| 53 | 
            -
                    if signatures.empty?
         | 
| 54 | 
            -
                      raise InvalidSignature, "No signatures found with expected version #{@version}"
         | 
| 55 | 
            -
                    end
         | 
| 56 | 
            -
             | 
| 57 | 
            -
                    expected_sig = generate(payload, timestamp)
         | 
| 58 | 
            -
                    unless signatures.any? { |s| secure_compare(expected_sig, s) }
         | 
| 59 | 
            -
                      raise InvalidSignature, "No signatures found matching the expected signature for payload"
         | 
| 60 | 
            -
                    end
         | 
| 61 | 
            -
             | 
| 62 | 
            -
                    if tolerance && timestamp < Time.now - tolerance
         | 
| 63 | 
            -
                      raise InvalidSignature, "Timestamp outside the tolerance zone (#{Time.at(timestamp)})"
         | 
| 64 | 
            -
                    end
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                    true
         | 
| 67 | 
            -
                  end
         | 
| 68 | 
            -
             | 
| 69 | 
            -
                  private
         | 
| 70 | 
            -
             | 
| 71 | 
            -
                  # Extracts the timestamp and the signature(s) with the desired version
         | 
| 72 | 
            -
                  # from the header
         | 
| 73 | 
            -
                  def get_timestamp_and_signatures(header)
         | 
| 74 | 
            -
                    list_items = header.split(/,\s*/).map { |i| i.split("=", 2) }
         | 
| 75 | 
            -
                    timestamp = Integer(list_items.select { |i| i[0] == "t" }[0][1])
         | 
| 76 | 
            -
                    signatures = list_items.select { |i| i[0] == @version }.map { |i| i[1] }
         | 
| 77 | 
            -
                    [Time.at(timestamp), signatures]
         | 
| 78 | 
            -
                  end
         | 
| 79 | 
            -
             | 
| 80 | 
            -
                  # Private
         | 
| 81 | 
            -
                  def fixed_length_secure_compare(a, b)
         | 
| 82 | 
            -
                    raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
         | 
| 83 | 
            -
                    l = a.unpack "C#{a.bytesize}"
         | 
| 84 | 
            -
                    res = 0
         | 
| 85 | 
            -
                    b.each_byte { |byte| res |= byte ^ l.shift }
         | 
| 86 | 
            -
                    res == 0
         | 
| 87 | 
            -
                  end
         | 
| 88 | 
            -
             | 
| 89 | 
            -
                  # Private
         | 
| 90 | 
            -
                  def secure_compare(a, b)
         | 
| 91 | 
            -
                    fixed_length_secure_compare(::Digest::SHA256.digest(a), ::Digest::SHA256.digest(b)) && a == b
         | 
| 92 | 
            -
                  end
         | 
| 93 | 
            -
                end
         | 
| 94 | 
            -
              end
         | 
| 95 | 
            -
            end
         |