dslimple 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 +7 -0
- data/.gitignore +11 -0
- data/.rubocop.yml +28 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +157 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/dslimple.gemspec +28 -0
- data/exe/dslimple +4 -0
- data/lib/dslimple/applier.rb +68 -0
- data/lib/dslimple/cli.rb +81 -0
- data/lib/dslimple/domain.rb +48 -0
- data/lib/dslimple/dsl/domain.rb +29 -0
- data/lib/dslimple/dsl/error.rb +4 -0
- data/lib/dslimple/dsl/record.rb +25 -0
- data/lib/dslimple/dsl.rb +65 -0
- data/lib/dslimple/exporter.rb +55 -0
- data/lib/dslimple/query.rb +60 -0
- data/lib/dslimple/query_builder.rb +86 -0
- data/lib/dslimple/record.rb +114 -0
- data/lib/dslimple/version.rb +3 -0
- data/lib/dslimple.rb +18 -0
- metadata +139 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: d3524b1943dd7ea604e2dc0405d806500a4c5c3f
         | 
| 4 | 
            +
              data.tar.gz: bbd8df2e9fa8193f6fa6941784503115335a0e9b
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: b50b823159fdbcb21b32c6db9723a0728f951affc184e354b3e786d48098042c552ce1dbf74c150f93db37aedb51d1803b7c03d6d51f8163d91b0871017b83da
         | 
| 7 | 
            +
              data.tar.gz: 4f1ea991bdbe56a6a6e8c58ccc444c4e22bacff0b0cc4da2f0a8796e49e897383720a2a0565414d50dcabb5908637b77a53901a1f63d73755c4522273b8a40da
         | 
    
        data/.gitignore
    ADDED
    
    
    
        data/.rubocop.yml
    ADDED
    
    | @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            AllCops:
         | 
| 2 | 
            +
              Include:
         | 
| 3 | 
            +
                - "*.gemspec"
         | 
| 4 | 
            +
              Exclude:
         | 
| 5 | 
            +
                - "vendor/**/*"
         | 
| 6 | 
            +
                - "db/schema.rb"
         | 
| 7 | 
            +
              DisplayCopNames: true
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Style/Alias:
         | 
| 10 | 
            +
              EnforcedStyle: prefer_alias_method
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            Style/ClassAndModuleChildren:
         | 
| 13 | 
            +
              EnforcedStyle: compact
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            Style/CaseEquality:
         | 
| 16 | 
            +
              Enabled: false
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            Style/Documentation:
         | 
| 19 | 
            +
              Enabled: false
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            Metrics/LineLength:
         | 
| 22 | 
            +
              Max: 160
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            Metrics/MethodLength:
         | 
| 25 | 
            +
              Max: 20
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            Metrics/AbcSize:
         | 
| 28 | 
            +
              Max: 25
         | 
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            The MIT License (MIT)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Copyright (c) 2016 Sho Kusano
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            +
            of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            +
            in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            +
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            +
            copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            +
            furnished to do so, subject to the following conditions:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            +
            all copies or substantial portions of the Software.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            +
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            +
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         | 
| 20 | 
            +
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
         | 
| 21 | 
            +
            THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,157 @@ | |
| 1 | 
            +
            # DSLimple
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            __DSLimple__ is a tool to manage [DNSimple](https://dnsimple.com/).
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            It defines the state of DNSimple using DSL, and updates DNSimple according to DSL.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ## Installation
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Add this line to your application's Gemfile:
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            ```ruby
         | 
| 12 | 
            +
            gem 'dslimple'
         | 
| 13 | 
            +
            ```
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            And then execute:
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                $ bundle
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            Or install it yourself as:
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                $ gem install dslimple
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ## Usage
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            ```shell
         | 
| 26 | 
            +
            export DLSIMPLE_EMAIL="..."
         | 
| 27 | 
            +
            export DLSIMPLE_API_TOKEN="..."
         | 
| 28 | 
            +
            dslimple export -f Domainfile
         | 
| 29 | 
            +
            vi Domainfile
         | 
| 30 | 
            +
            dslimple apply --dry-run -f Domainfile
         | 
| 31 | 
            +
            dslimple apply --yes -f Domainfile
         | 
| 32 | 
            +
            ```
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            ### Help
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            ```
         | 
| 37 | 
            +
            $ dslimple help
         | 
| 38 | 
            +
            Commands:
         | 
| 39 | 
            +
              dslimple apply           # Apply domain specifications
         | 
| 40 | 
            +
              dslimple export          # Export domain specifications
         | 
| 41 | 
            +
              dslimple help [COMMAND]  # Describe available commands or one specific command
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            Options:
         | 
| 44 | 
            +
              -e, [--email=EMAIL]                 # Your E-Mail address
         | 
| 45 | 
            +
              -t, [--api-token=API_TOKEN]         # Your API token
         | 
| 46 | 
            +
              -dt, [--domain-token=DOMAIN_TOKEN]  # Your Domain API token
         | 
| 47 | 
            +
                  [--sandbox], [--no-sandbox]     # Use sandbox API(at sandbox.dnsimple.com)
         | 
| 48 | 
            +
                                                  # Default: true
         | 
| 49 | 
            +
                  [--debug], [--no-debug]
         | 
| 50 | 
            +
            ```
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            #### help apply
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            ```
         | 
| 55 | 
            +
            $ dslimple help apply
         | 
| 56 | 
            +
            Usage:
         | 
| 57 | 
            +
              dslimple apply
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            Options:
         | 
| 60 | 
            +
              -o, [--only=one two three]                 # Specify domains for apply
         | 
| 61 | 
            +
              -d, [--dry-run], [--no-dry-run]
         | 
| 62 | 
            +
              -f, [--file=FILE]                          # Source Domainfile path
         | 
| 63 | 
            +
                                                         # Default: Domainfile
         | 
| 64 | 
            +
                  [--addition], [--no-addition]          # Add specified records
         | 
| 65 | 
            +
                                                         # Default: true
         | 
| 66 | 
            +
                  [--modification], [--no-modification]  # Modify specified records
         | 
| 67 | 
            +
                                                         # Default: true
         | 
| 68 | 
            +
                  [--deletion], [--no-deletion]          # Delete unspecified records
         | 
| 69 | 
            +
                                                         # Default: true
         | 
| 70 | 
            +
              -y, [--yes], [--no-yes]                    # Do not confirm on before apply
         | 
| 71 | 
            +
              -e, [--email=EMAIL]                        # Your E-Mail address
         | 
| 72 | 
            +
              -t, [--api-token=API_TOKEN]                # Your API token
         | 
| 73 | 
            +
              -dt, [--domain-token=DOMAIN_TOKEN]         # Your Domain API token
         | 
| 74 | 
            +
                  [--sandbox], [--no-sandbox]            # Use sandbox API(at sandbox.dnsimple.com)
         | 
| 75 | 
            +
                                                         # Default: true
         | 
| 76 | 
            +
                  [--debug], [--no-debug]
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            Apply domain specifications
         | 
| 79 | 
            +
            ```
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            #### help export
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            ```
         | 
| 84 | 
            +
            $ dslimple help export
         | 
| 85 | 
            +
            Usage:
         | 
| 86 | 
            +
              dslimple export
         | 
| 87 | 
            +
             | 
| 88 | 
            +
            Options:
         | 
| 89 | 
            +
              -o, [--only=one two three]             # Specify domains for export
         | 
| 90 | 
            +
              -f, [--file=FILE]                      # Export Domainfile path
         | 
| 91 | 
            +
                                                     # Default: Domainfile
         | 
| 92 | 
            +
              -d, [--dir=DIR]                        # Export directory path for split
         | 
| 93 | 
            +
                                                     # Default: ./domainfiles
         | 
| 94 | 
            +
              -s, [--split], [--no-split]            # Export with split by domains
         | 
| 95 | 
            +
              -m, [--modeline], [--no-modeline]      # Export with modeline for Vim
         | 
| 96 | 
            +
                  [--soa-and-ns], [--no-soa-and-ns]  # Export without SOA and NS records
         | 
| 97 | 
            +
              -e, [--email=EMAIL]                    # Your E-Mail address
         | 
| 98 | 
            +
              -t, [--api-token=API_TOKEN]            # Your API token
         | 
| 99 | 
            +
              -dt, [--domain-token=DOMAIN_TOKEN]     # Your Domain API token
         | 
| 100 | 
            +
                  [--sandbox], [--no-sandbox]        # Use sandbox API(at sandbox.dnsimple.com)
         | 
| 101 | 
            +
                                                     # Default: true
         | 
| 102 | 
            +
                  [--debug], [--no-debug]
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            Export domain specifications
         | 
| 105 | 
            +
            ```
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            ## Domainfile Examples
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            ### Basic
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            The following defines are all the same meaning
         | 
| 112 | 
            +
             | 
| 113 | 
            +
            ```ruby
         | 
| 114 | 
            +
            domain "example.com" do
         | 
| 115 | 
            +
              a_record ttl: 3600 do
         | 
| 116 | 
            +
                "0.0.0.0"
         | 
| 117 | 
            +
              end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
              record type: :a, ttl: 3600 do
         | 
| 120 | 
            +
                "0.0.0.0"
         | 
| 121 | 
            +
              end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
              a_record do
         | 
| 124 | 
            +
                ttl 3600
         | 
| 125 | 
            +
                content "0.0.0.0"
         | 
| 126 | 
            +
              end
         | 
| 127 | 
            +
            end
         | 
| 128 | 
            +
            ```
         | 
| 129 | 
            +
             | 
| 130 | 
            +
            ### Dynamic
         | 
| 131 | 
            +
             | 
| 132 | 
            +
            DSLimple's DSL works on ruby.
         | 
| 133 | 
            +
             | 
| 134 | 
            +
            ```ruby
         | 
| 135 | 
            +
            require 'open-uri'
         | 
| 136 | 
            +
            require 'json'
         | 
| 137 | 
            +
             | 
| 138 | 
            +
            domain "example.internal" do
         | 
| 139 | 
            +
              JSON.parse(open('http://my.internal.service/records.json', &:read)).each do |record_data|
         | 
| 140 | 
            +
                recored record_data['name'], record_data['options'] { record_data['content'] }
         | 
| 141 | 
            +
              end
         | 
| 142 | 
            +
            end
         | 
| 143 | 
            +
            ```
         | 
| 144 | 
            +
             | 
| 145 | 
            +
            ## Contributing
         | 
| 146 | 
            +
             | 
| 147 | 
            +
            Bug reports and pull requests are welcome on GitHub at https://github.com/zeny-io/dslimple.
         | 
| 148 | 
            +
             | 
| 149 | 
            +
             | 
| 150 | 
            +
            ## License
         | 
| 151 | 
            +
             | 
| 152 | 
            +
            The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
         | 
| 153 | 
            +
             | 
| 154 | 
            +
            ## Inspired by
         | 
| 155 | 
            +
             | 
| 156 | 
            +
            - [roadworker](https://github.com/winebarrel/roadworker)
         | 
| 157 | 
            +
             | 
    
        data/Rakefile
    ADDED
    
    
    
        data/bin/console
    ADDED
    
    | @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "bundler/setup"
         | 
| 4 | 
            +
            require "dslimple"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            # You can add fixtures and/or initialization code here to make experimenting
         | 
| 7 | 
            +
            # with your gem easier. You can also use a different console, if you like.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # (If you use this, don't forget to add pry to your Gemfile!)
         | 
| 10 | 
            +
            # require "pry"
         | 
| 11 | 
            +
            # Pry.start
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            require "irb"
         | 
| 14 | 
            +
            IRB.start
         | 
    
        data/bin/setup
    ADDED
    
    
    
        data/dslimple.gemspec
    ADDED
    
    | @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            # coding: utf-8
         | 
| 2 | 
            +
            lib = File.expand_path('../lib', __FILE__)
         | 
| 3 | 
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 | 
            +
            require 'dslimple/version'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Gem::Specification.new do |spec|
         | 
| 7 | 
            +
              spec.name          = 'dslimple'
         | 
| 8 | 
            +
              spec.version       = Dslimple::VERSION
         | 
| 9 | 
            +
              spec.authors       = ['Sho Kusano']
         | 
| 10 | 
            +
              spec.email         = ['sho-kusano@zeny.io']
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              spec.summary       = 'DSLimple is a tool to manage DNSimple.'
         | 
| 13 | 
            +
              spec.description   = 'DSLimple is a tool to manage DNSimple. It defines the state of DNSimple using DSL, and updates DNSimple according to DSL.'
         | 
| 14 | 
            +
              spec.homepage      = 'https://github.com/zeny-io/dslimple'
         | 
| 15 | 
            +
              spec.license       = 'MIT'
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              spec.files         = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
         | 
| 18 | 
            +
              spec.bindir        = 'exe'
         | 
| 19 | 
            +
              spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
         | 
| 20 | 
            +
              spec.require_paths = ['lib']
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              spec.add_development_dependency 'bundler', '~> 1.11'
         | 
| 23 | 
            +
              spec.add_development_dependency 'rake', '~> 10.0'
         | 
| 24 | 
            +
              spec.add_development_dependency 'rubocop'
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              spec.add_dependency 'dnsimple', '~> 2.1'
         | 
| 27 | 
            +
              spec.add_dependency 'thor', '~> 0.19'
         | 
| 28 | 
            +
            end
         | 
    
        data/exe/dslimple
    ADDED
    
    
| @@ -0,0 +1,68 @@ | |
| 1 | 
            +
            require 'dslimple'
         | 
| 2 | 
            +
            require 'pp'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class Dslimple::Applier
         | 
| 5 | 
            +
              OPERATION_COLORS = {
         | 
| 6 | 
            +
                addition: :green,
         | 
| 7 | 
            +
                modification: :yellow,
         | 
| 8 | 
            +
                deletion: :red
         | 
| 9 | 
            +
              }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              attr_reader :api_client, :shell, :options
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              def initialize(api_client, shell, options = {})
         | 
| 14 | 
            +
                @api_client = api_client
         | 
| 15 | 
            +
                @shell = shell
         | 
| 16 | 
            +
                @options = options
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              def execute
         | 
| 20 | 
            +
                dsl = Dslimple::DSL.new(options[:file], options)
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                dsl.execute
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                expected_domains = dsl.transform
         | 
| 25 | 
            +
                expected_domains.select! { |domain| options[:only].include?(domain.name) } if options[:only].any?
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                @buildler = Dslimple::QueryBuilder.new(fetch_domains, expected_domains)
         | 
| 28 | 
            +
                @buildler.execute
         | 
| 29 | 
            +
                queries = @buildler.filtered_queries(options)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                if queries.empty?
         | 
| 32 | 
            +
                  shell.say('No Changes', :bold)
         | 
| 33 | 
            +
                  return
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                show_plan(queries)
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                return if options[:dry_run] || !(options[:yes] || shell.yes?("Apply #{queries.size} changes. OK?(y/n) >"))
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                apply(queries)
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              def fetch_domains
         | 
| 44 | 
            +
                domains = api_client.domains.list.map { |domain| Dslimple::Domain.new(domain.name, api_client, id: domain.id) }
         | 
| 45 | 
            +
                domains.each(&:fetch_records!)
         | 
| 46 | 
            +
                domains.select! { |domain| options[:only].include?(domain.name) } if options[:only].any?
         | 
| 47 | 
            +
                domains
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              def show_plan(queries)
         | 
| 51 | 
            +
                shell.say("Changes", :bold)
         | 
| 52 | 
            +
                queries.each do |query|
         | 
| 53 | 
            +
                  show_query(query)
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              def apply(queries)
         | 
| 58 | 
            +
                shell.say('Apply', :bold)
         | 
| 59 | 
            +
                queries.each do |query|
         | 
| 60 | 
            +
                  show_query(query)
         | 
| 61 | 
            +
                  query.execute(api_client)
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
              def show_query(query)
         | 
| 66 | 
            +
                shell.say("#{shell.set_color(query.operation.to_s[0..2], OPERATION_COLORS[query.operation])} #{query.to_s}")
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
            end
         | 
    
        data/lib/dslimple/cli.rb
    ADDED
    
    | @@ -0,0 +1,81 @@ | |
| 1 | 
            +
            require 'thor'
         | 
| 2 | 
            +
            require 'dnsimple'
         | 
| 3 | 
            +
            require 'json'
         | 
| 4 | 
            +
            require 'dslimple'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class Dslimple::CLI < Thor
         | 
| 7 | 
            +
              include Thor::Actions
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              SANDBOX_API_ENDPOINT = 'https://api.sandbox.dnsimple.com'.freeze
         | 
| 10 | 
            +
              USER_AGENT = "DSLimple: Simple CLI DNSimple client(v#{Dslimple::VERSION})".freeze
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              class_option :email, type: :string, aliases: %w(-e), desc: 'Your E-Mail address'
         | 
| 13 | 
            +
              class_option :api_token, type: :string, aliases: %w(-t), desc: 'Your API token'
         | 
| 14 | 
            +
              class_option :domain_token, type: :string, aliases: %w(-dt), desc: 'Your Domain API token'
         | 
| 15 | 
            +
              class_option :sandbox, type: :boolean, default: ENV['DLSIMPLE_ENV'] == 'test', desc: 'Use sandbox API(at sandbox.dnsimple.com)'
         | 
| 16 | 
            +
              class_option :debug, type: :boolean, default: false
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              desc 'export', 'Export domain specifications'
         | 
| 19 | 
            +
              method_option :only, type: :array, default: [], aliases: %w(-o), desc: 'Specify domains for export'
         | 
| 20 | 
            +
              method_option :file, type: :string, default: 'Domainfile', aliases: %w(-f), desc: 'Export Domainfile path'
         | 
| 21 | 
            +
              method_option :dir, type: :string, default: './domainfiles', aliases: %w(-d), desc: 'Export directory path for split'
         | 
| 22 | 
            +
              method_option :split, type: :boolean, default: false, aliases: %w(-s), desc: 'Export with split by domains'
         | 
| 23 | 
            +
              method_option :modeline, type: :boolean, default: false, aliases: %w(-m), desc: 'Export with modeline for Vim'
         | 
| 24 | 
            +
              method_option :soa_and_ns, type: :boolean, default: false, desc: 'Export without SOA and NS records'
         | 
| 25 | 
            +
              def export
         | 
| 26 | 
            +
                exporter = Dslimple::Exporter.new(api_client, options)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                exporter.execute
         | 
| 29 | 
            +
              rescue => e
         | 
| 30 | 
            +
                rescue_from(e)
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              desc 'apply', 'Apply domain specifications'
         | 
| 34 | 
            +
              method_option :only, type: :array, default: [], aliases: %w(-o), desc: 'Specify domains for apply'
         | 
| 35 | 
            +
              method_option :dry_run, type: :boolean, default: false, aliases: %w(-d)
         | 
| 36 | 
            +
              method_option :file, type: :string, default: 'Domainfile', aliases: %w(-f), desc: 'Source Domainfile path'
         | 
| 37 | 
            +
              method_option :addition, type: :boolean, default: true, desc: 'Add specified records'
         | 
| 38 | 
            +
              method_option :modification, type: :boolean, default: true, desc: 'Modify specified records'
         | 
| 39 | 
            +
              method_option :deletion, type: :boolean, default: true, desc: 'Delete unspecified records'
         | 
| 40 | 
            +
              method_option :yes, type: :boolean, default: false, aliases: %w(-y), desc: 'Do not confirm on before apply'
         | 
| 41 | 
            +
              def apply
         | 
| 42 | 
            +
                applier = Dslimple::Applier.new(api_client, self, options)
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                applier.execute
         | 
| 45 | 
            +
              rescue => e
         | 
| 46 | 
            +
                rescue_from(e)
         | 
| 47 | 
            +
              rescue Dslimple::DSL::Error => e
         | 
| 48 | 
            +
                rescue_from(e)
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              private
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              def api_client
         | 
| 54 | 
            +
                @api_client ||= Dnsimple::Client.new(
         | 
| 55 | 
            +
                  username: options[:email] || ENV['DLSIMPLE_EMAIL'],
         | 
| 56 | 
            +
                  api_token: options[:api_token] || ENV['DLSIMPLE_API_TOKEN'],
         | 
| 57 | 
            +
                  domain_api_token: options[:domain_token] || ENV['DLSIMPLE_DOMAIN_TOKEN'],
         | 
| 58 | 
            +
                  api_endpoint: options[:sandbox] ? SANDBOX_API_ENDPOINT : nil,
         | 
| 59 | 
            +
                  user_agent: USER_AGENT
         | 
| 60 | 
            +
                )
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              def rescue_from(e)
         | 
| 64 | 
            +
                raise e if options[:debug]
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                case e
         | 
| 67 | 
            +
                when Dnsimple::AuthenticationError
         | 
| 68 | 
            +
                  error(set_color(e.message, :red, :bold))
         | 
| 69 | 
            +
                when Dnsimple::RequestError
         | 
| 70 | 
            +
                  error(set_color("#{e.message}: #{JSON.parse(e.response.body)['message']}", :yellow, :bold))
         | 
| 71 | 
            +
                when Dslimple::DSL::Error
         | 
| 72 | 
            +
                  error(set_color(e.message, :yellow, :bold))
         | 
| 73 | 
            +
                else
         | 
| 74 | 
            +
                  error(set_color("#{e.class}: #{e.message}", :red, :bold))
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
                e.backtrace.each do |bt|
         | 
| 77 | 
            +
                  say("  #{set_color('from', :green)} #{bt}")
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
                exit 1
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
            end
         | 
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            require 'dslimple'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Dslimple::Domain
         | 
| 4 | 
            +
              attr_reader :name, :id
         | 
| 5 | 
            +
              attr_accessor :api_client, :records
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              def initialize(name, api_client, options = {})
         | 
| 8 | 
            +
                @name = name
         | 
| 9 | 
            +
                @id = options[:id]
         | 
| 10 | 
            +
                @api_client = api_client
         | 
| 11 | 
            +
                @records = []
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def escaped_name
         | 
| 15 | 
            +
                Dslimple.escape_single_quote(name)
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def records_without_soa_ns
         | 
| 19 | 
            +
                records.select { |record| record.type != :soa && record.type != :ns }
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def fetch_records
         | 
| 23 | 
            +
                api_client.domains.records(name).map do |record|
         | 
| 24 | 
            +
                  Dslimple::Record.new(self, record.record_type, record.name, record.content, ttl: record.ttl, priority: record.priority, id: record.id)
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              def fetch_records!
         | 
| 29 | 
            +
                @records = fetch_records
         | 
| 30 | 
            +
                cleanup_records!
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              def cleanup_records!
         | 
| 34 | 
            +
                @records = Dslimple::Record.cleanup_records(records)
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              def ==(other)
         | 
| 38 | 
            +
                other.is_a?(Dslimple::Domain) && other.name == name
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
              alias_method :eql, :==
         | 
| 41 | 
            +
             | 
| 42 | 
            +
              def to_dsl(options = {})
         | 
| 43 | 
            +
                <<"EOD"
         | 
| 44 | 
            +
            domain '#{escaped_name}' do
         | 
| 45 | 
            +
            #{(options[:soa_and_ns] ? records : records_without_soa_ns).map(&:to_dsl).join("\n")}end
         | 
| 46 | 
            +
            EOD
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            require 'dslimple/dsl'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Dslimple::DSL::Domain
         | 
| 4 | 
            +
              attr_reader :name, :records
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def initialize(name, &block)
         | 
| 7 | 
            +
                @name = name
         | 
| 8 | 
            +
                @records = []
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                instance_eval(&block)
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              def record(name = {}, options = {}, &block)
         | 
| 14 | 
            +
                if name.is_a?(Hash)
         | 
| 15 | 
            +
                  options = options.merge(name)
         | 
| 16 | 
            +
                  name = ''
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                @records << Dslimple::DSL::Record.new(name, options, &block)
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              Dslimple::Record::RECORD_TYPES.each do |type|
         | 
| 23 | 
            +
                class_eval(<<-EOC)
         | 
| 24 | 
            +
                  def #{type}_record(name = {}, options = {}, &block)
         | 
| 25 | 
            +
                    record(name, options.merge(type: :#{type}), &block)
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                EOC
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            require 'dslimple/dsl'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Dslimple::DSL::Record
         | 
| 4 | 
            +
              attr_reader :name, :content, :options
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def initialize(name, options = {}, &block)
         | 
| 7 | 
            +
                @name = name
         | 
| 8 | 
            +
                @options = options
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                returned_content = instance_eval(&block)
         | 
| 11 | 
            +
                @content ||= returned_content
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def priority(n)
         | 
| 15 | 
            +
                @options[:priority] = n.to_s.to_i
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def ttl(n)
         | 
| 19 | 
            +
                @options[:ttl] = n.to_s.to_i
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              def content(c = nil)
         | 
| 23 | 
            +
                c ? @content = c : @content
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
    
        data/lib/dslimple/dsl.rb
    ADDED
    
    | @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            require 'pathname'
         | 
| 2 | 
            +
            require 'dslimple'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class Dslimple::DSL
         | 
| 5 | 
            +
              def initialize(file, context = {})
         | 
| 6 | 
            +
                @file = Pathname.new(file)
         | 
| 7 | 
            +
                @dir = @file.dirname
         | 
| 8 | 
            +
                @domains = []
         | 
| 9 | 
            +
                @files = []
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                @context = context
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              def execute
         | 
| 15 | 
            +
                evaluate(@file)
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def require(path)
         | 
| 19 | 
            +
                if @dir.join(path).exist?
         | 
| 20 | 
            +
                  evaluate(@dir.join(path))
         | 
| 21 | 
            +
                elsif @dir.join("#{path}.rb").exist?
         | 
| 22 | 
            +
                  evaluate(@dir.join("#{path}.rb"))
         | 
| 23 | 
            +
                else
         | 
| 24 | 
            +
                  Kernel.require(path)
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              def evaluate(file)
         | 
| 29 | 
            +
                @files << file.to_s
         | 
| 30 | 
            +
                instance_eval(File.read(file), file.to_s)
         | 
| 31 | 
            +
              rescue ScriptError => e
         | 
| 32 | 
            +
                raise Dslimple::DSL::Error, "#{e.class}: #{e.message}", cleanup_backtrace(e.backtrace)
         | 
| 33 | 
            +
              rescue StandardError => e
         | 
| 34 | 
            +
                raise Dslimple::DSL::Error, "#{e.class}: #{e.message}", cleanup_backtrace(e.backtrace)
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              def domain(name, &block)
         | 
| 38 | 
            +
                @domains << Dslimple::DSL::Domain.new(name, &block)
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              def transform
         | 
| 42 | 
            +
                @domains.map do |domain|
         | 
| 43 | 
            +
                  Dslimple::Domain.new(domain.name, nil).tap do |model|
         | 
| 44 | 
            +
                    model.records = domain.records.map do |record|
         | 
| 45 | 
            +
                      Dslimple::Record.new(model, record.options[:type], record.name, record.content, record.options)
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              private
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              def cleanup_backtrace(backtrace)
         | 
| 54 | 
            +
                return backtrace if @context[:debug]
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                backtrace.select do |bt|
         | 
| 57 | 
            +
                  path = bt.split(':')[0..-3].join(':')
         | 
| 58 | 
            +
                  @files.include?(path)
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
            end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            require 'dslimple/dsl/domain'
         | 
| 64 | 
            +
            require 'dslimple/dsl/record'
         | 
| 65 | 
            +
            require 'dslimple/dsl/error'
         | 
| @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            require 'pathname'
         | 
| 2 | 
            +
            require 'dslimple'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            class Dslimple::Exporter
         | 
| 5 | 
            +
              attr_reader :api_client, :options, :domains
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              def initialize(api_client, options)
         | 
| 8 | 
            +
                @api_client = api_client
         | 
| 9 | 
            +
                @options = options
         | 
| 10 | 
            +
                @domains = []
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              def execute
         | 
| 14 | 
            +
                @domains = fetch_domains
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                if options[:split] && options[:dir]
         | 
| 17 | 
            +
                  split_export(options[:dir], options[:file])
         | 
| 18 | 
            +
                else
         | 
| 19 | 
            +
                  export(options[:file], domains)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              def fetch_domains
         | 
| 24 | 
            +
                domains = api_client.domains.list.map { |domain| Dslimple::Domain.new(domain.name, api_client) }
         | 
| 25 | 
            +
                domains.each(&:fetch_records!)
         | 
| 26 | 
            +
                domains.select! { |domain| options[:only].include?(domain.name) } unless options[:only].empty?
         | 
| 27 | 
            +
                domains
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              def export(file, export_domains)
         | 
| 31 | 
            +
                File.open(file.to_s, 'w') do |fd|
         | 
| 32 | 
            +
                  write_modeline(fd)
         | 
| 33 | 
            +
                  fd.puts export_domains.map { |domain| domain.to_dsl(options) }.join("\n")
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              def split_export(dir, file)
         | 
| 38 | 
            +
                dir = Pathname.new(dir)
         | 
| 39 | 
            +
                file = Pathname.new(file)
         | 
| 40 | 
            +
                Dir.mkdir(dir.to_s) unless dir.directory?
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                File.open(file.to_s, 'w') do |fd|
         | 
| 43 | 
            +
                  write_modeline(fd)
         | 
| 44 | 
            +
                  domains.each do |domain|
         | 
| 45 | 
            +
                    domainfile = dir.join(domain.name)
         | 
| 46 | 
            +
                    export(domainfile, [domain])
         | 
| 47 | 
            +
                    fd.puts "require '#{domainfile.relative_path_from(file.dirname)}'"
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              def write_modeline(fd)
         | 
| 53 | 
            +
                fd << "# -*- mode: ruby -*-\n# vi: set ft=ruby :\n\n" if options[:modeline]
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
            end
         | 
| @@ -0,0 +1,60 @@ | |
| 1 | 
            +
            require 'dslimple'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Dslimple::Query
         | 
| 4 | 
            +
              attr_reader :operation, :target, :domain, :params
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              def initialize(operation, target, domain, params = {})
         | 
| 7 | 
            +
                @operation = operation
         | 
| 8 | 
            +
                @target = target
         | 
| 9 | 
            +
                @domain = domain
         | 
| 10 | 
            +
                @params = params
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              %i(addition modification deletion).each do |operation|
         | 
| 14 | 
            +
                class_eval(<<-EOC)
         | 
| 15 | 
            +
                def #{operation}?
         | 
| 16 | 
            +
                  operation == :#{operation}
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
                EOC
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              def to_s
         | 
| 22 | 
            +
                if target == :domain
         | 
| 23 | 
            +
                  "#{domain.name}"
         | 
| 24 | 
            +
                else
         | 
| 25 | 
            +
                  %(#{params[:record_type].to_s.rjust(5)} #{params[:name].to_s.rjust(10)}.#{domain.name} (#{record_options.join(', ')}) "#{params[:content]}")
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              def record_options
         | 
| 30 | 
            +
                options = []
         | 
| 31 | 
            +
                params.each_pair do |k, v|
         | 
| 32 | 
            +
                  options << "#{k}: #{v}" if %i(ttl prio).include?(k) && v
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
                options
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              def execute(api_client)
         | 
| 38 | 
            +
                __send__("execute_#{target}", api_client)
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              def execute_domain(api_client)
         | 
| 42 | 
            +
                case operation
         | 
| 43 | 
            +
                when :addition
         | 
| 44 | 
            +
                  api_client.domains.create(name: domain.name)
         | 
| 45 | 
            +
                when :deletion
         | 
| 46 | 
            +
                  api_client.domains.delete(domain.name)
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              def execute_record(api_client)
         | 
| 51 | 
            +
                case operation
         | 
| 52 | 
            +
                when :addition
         | 
| 53 | 
            +
                  api_client.domains.create_record(domain.name, params)
         | 
| 54 | 
            +
                when :modification
         | 
| 55 | 
            +
                  api_client.domains.update_record(domain.name, params[:id], params)
         | 
| 56 | 
            +
                when :deletion
         | 
| 57 | 
            +
                  api_client.domains.delete_record(domain.name, params[:id])
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
            end
         | 
| @@ -0,0 +1,86 @@ | |
| 1 | 
            +
            require 'dslimple'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Dslimple::QueryBuilder
         | 
| 4 | 
            +
              attr_reader :queries
         | 
| 5 | 
            +
              attr_reader :expected_domains, :current_domains
         | 
| 6 | 
            +
              attr_reader :append_records, :change_records, :delete_records
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              def initialize(current_domains, expected_domains)
         | 
| 9 | 
            +
                @current_domains = Hash[*current_domains.map { |domain| [domain.name, domain] }.flatten]
         | 
| 10 | 
            +
                @expected_domains = Hash[*expected_domains.map { |domain| [domain.name, domain] }.flatten]
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              def append_domains
         | 
| 14 | 
            +
                @append_domains ||= expected_domains.values.reject { |domain| current_domains.key?(domain.name) }
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              def delete_domains
         | 
| 18 | 
            +
                @delete_domains ||= current_domains.values.reject { |domain| expected_domains.key?(domain.name) }
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              def execute
         | 
| 22 | 
            +
                @append_records = append_domains.map(&:records_without_soa_ns).flatten
         | 
| 23 | 
            +
                @change_records = []
         | 
| 24 | 
            +
                @delete_records = delete_domains.map(&:records_without_soa_ns).flatten
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                expected_domains.each_pair do |name, domain|
         | 
| 27 | 
            +
                  execute_records(name, domain)
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                build_queries
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              def execute_records(domain_name, domain)
         | 
| 34 | 
            +
                current_domain = current_domains[domain_name]
         | 
| 35 | 
            +
                return unless current_domain
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                current_records = current_domain.records_without_soa_ns.dup
         | 
| 38 | 
            +
                domain.records_without_soa_ns.each do |record|
         | 
| 39 | 
            +
                  at = current_records.index { |current| current == record }
         | 
| 40 | 
            +
                  current_record = at ? current_records.slice!(at) : nil
         | 
| 41 | 
            +
                  like_record = current_records.find { |current| current === record }
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  if !like_record && !current_record
         | 
| 44 | 
            +
                    @append_records << record
         | 
| 45 | 
            +
                  elsif like_record
         | 
| 46 | 
            +
                    @change_records << [like_record, record]
         | 
| 47 | 
            +
                    current_records.delete(like_record)
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
                @delete_records.concat(current_records)
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              def build_queries
         | 
| 54 | 
            +
                @queries = []
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                append_domains.each do |domain|
         | 
| 57 | 
            +
                  @queries << Dslimple::Query.new(:addition, :domain, domain)
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                append_records.each do |record|
         | 
| 61 | 
            +
                  @queries << Dslimple::Query.new(:addition, :record, record.domain, record.to_params)
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                change_records.each do |old, new|
         | 
| 65 | 
            +
                  @queries << Dslimple::Query.new(:modification, :record, new.domain, new.to_params.merge(id: old.id))
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                delete_records.each do |record|
         | 
| 69 | 
            +
                  @queries << Dslimple::Query.new(:deletion, :record, record.domain, record.to_params)
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                delete_domains.each do |domain|
         | 
| 73 | 
            +
                  @queries << Dslimple::Query.new(:deletion, :domain, domain)
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                @queries
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
              def filtered_queries(options)
         | 
| 80 | 
            +
                queries.select do |query|
         | 
| 81 | 
            +
                  (query.addition? && options[:addition]) ||
         | 
| 82 | 
            +
                    (query.modification? && options[:modification]) ||
         | 
| 83 | 
            +
                    (query.deletion? && options[:deletion])
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
            end
         | 
| @@ -0,0 +1,114 @@ | |
| 1 | 
            +
            require 'dslimple'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Dslimple::Record
         | 
| 4 | 
            +
              RECORD_TYPES = %i(a alias cname mx spf url txt ns srv naptr ptr aaaa sshfp hinfo pool).freeze
         | 
| 5 | 
            +
              ALIAS_PREFIX = /\AALIAS for /
         | 
| 6 | 
            +
              SPF_PREFIX = /\Av=spf1 /
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              attr_reader :domain, :type, :name, :id, :ttl, :priority, :content
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def self.cleanup_records(records)
         | 
| 11 | 
            +
                records = records.dup
         | 
| 12 | 
            +
                alias_records = records.select(&:alias?)
         | 
| 13 | 
            +
                spf_records = records.select(&:spf?)
         | 
| 14 | 
            +
                txt_records = records.select { |record| record.like_spf? || record.like_alias? }
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                txt_records.each do |record|
         | 
| 17 | 
            +
                  reject = record.like_spf? ? spf_records.any? { |r| record.eql_spf?(r) } : alias_records.any? { |r| record.eql_alias?(r) }
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  records.delete(record) if reject
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                records
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              RECORD_TYPES.each do |type|
         | 
| 26 | 
            +
                class_eval(<<-EOC)
         | 
| 27 | 
            +
                  def #{type}?
         | 
| 28 | 
            +
                    type == :#{type}
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                EOC
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              def initialize(domain, type, name, content, options = {})
         | 
| 34 | 
            +
                @domain = domain
         | 
| 35 | 
            +
                @type = type.to_s.downcase.to_sym
         | 
| 36 | 
            +
                @name = name
         | 
| 37 | 
            +
                @content = content
         | 
| 38 | 
            +
                @ttl = options[:ttl]
         | 
| 39 | 
            +
                @priority = options[:priority]
         | 
| 40 | 
            +
                @id = options[:id]
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              def escaped_name
         | 
| 44 | 
            +
                Dslimple.escape_single_quote(name)
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
              def escaped_content
         | 
| 48 | 
            +
                Dslimple.escape_single_quote(content)
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              def like_spf?
         | 
| 52 | 
            +
                txt? && content.match(SPF_PREFIX)
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              def like_alias?
         | 
| 56 | 
            +
                txt? && content.match(ALIAS_PREFIX)
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              def eql_spf?(spf_record)
         | 
| 60 | 
            +
                spf_record.ttl == ttl && spf_record.content == content
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
              def eql_alias?(alias_record)
         | 
| 64 | 
            +
                alias_record.ttl == ttl && content == "ALIAS for #{alias_record.content}"
         | 
| 65 | 
            +
              end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
              def ==(other)
         | 
| 68 | 
            +
                other.is_a?(Dslimple::Record) && other.domain == domain && other.hash == hash
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
              alias_method :eql, :==
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              def ===(other)
         | 
| 73 | 
            +
                other.is_a?(Dslimple::Record) && other.domain == domain && other.rough_hash == rough_hash
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              def hash
         | 
| 77 | 
            +
                "#{type}:#{name}:#{content}:#{ttl}:#{priority}"
         | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
              def rough_hash
         | 
| 81 | 
            +
                "#{type}:#{name}:#{content}"
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
              def to_dsl_options
         | 
| 85 | 
            +
                options = []
         | 
| 86 | 
            +
                options << "'#{escaped_name}'" unless escaped_name.empty?
         | 
| 87 | 
            +
                options << "ttl: #{ttl}" if ttl
         | 
| 88 | 
            +
                options << "priority: #{priority}" if priority
         | 
| 89 | 
            +
                options.join(', ')
         | 
| 90 | 
            +
              end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
              def to_dsl
         | 
| 93 | 
            +
                <<"EOD"
         | 
| 94 | 
            +
              #{type}_record #{to_dsl_options} do
         | 
| 95 | 
            +
                '#{escaped_content}'
         | 
| 96 | 
            +
              end
         | 
| 97 | 
            +
            EOD
         | 
| 98 | 
            +
              end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
              def to_params
         | 
| 101 | 
            +
                {
         | 
| 102 | 
            +
                  id: id,
         | 
| 103 | 
            +
                  record_type: type.to_s.upcase,
         | 
| 104 | 
            +
                  name: name,
         | 
| 105 | 
            +
                  content: content,
         | 
| 106 | 
            +
                  ttl: ttl,
         | 
| 107 | 
            +
                  prio: priority
         | 
| 108 | 
            +
                }
         | 
| 109 | 
            +
              end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
              def inspect
         | 
| 112 | 
            +
                "<Dslimple::Record #{type.to_s.upcase} #{name}: #{content}>"
         | 
| 113 | 
            +
              end
         | 
| 114 | 
            +
            end
         | 
    
        data/lib/dslimple.rb
    ADDED
    
    | @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            require 'dslimple/version'
         | 
| 2 | 
            +
            require 'dslimple/domain'
         | 
| 3 | 
            +
            require 'dslimple/record'
         | 
| 4 | 
            +
            require 'dslimple/dsl'
         | 
| 5 | 
            +
            require 'dslimple/query'
         | 
| 6 | 
            +
            require 'dslimple/query_builder'
         | 
| 7 | 
            +
            require 'dslimple/exporter'
         | 
| 8 | 
            +
            require 'dslimple/applier'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            module Dslimple
         | 
| 11 | 
            +
              ESCAPE_SINGLE_QUOTE_REGEXP = /[^\\]'/
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              def self.escape_single_quote(string)
         | 
| 14 | 
            +
                string.gsub(ESCAPE_SINGLE_QUOTE_REGEXP) do |match|
         | 
| 15 | 
            +
                  match[0] + "\\'"
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,139 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: dslimple
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 1.0.0
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Sho Kusano
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: exe
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2016-02-13 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: bundler
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - "~>"
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '1.11'
         | 
| 20 | 
            +
              type: :development
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - "~>"
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '1.11'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: rake
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - "~>"
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '10.0'
         | 
| 34 | 
            +
              type: :development
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - "~>"
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '10.0'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: rubocop
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - ">="
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '0'
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - ">="
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '0'
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: dnsimple
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - "~>"
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '2.1'
         | 
| 62 | 
            +
              type: :runtime
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - "~>"
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: '2.1'
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: thor
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - "~>"
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: '0.19'
         | 
| 76 | 
            +
              type: :runtime
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - "~>"
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: '0.19'
         | 
| 83 | 
            +
            description: DSLimple is a tool to manage DNSimple. It defines the state of DNSimple
         | 
| 84 | 
            +
              using DSL, and updates DNSimple according to DSL.
         | 
| 85 | 
            +
            email:
         | 
| 86 | 
            +
            - sho-kusano@zeny.io
         | 
| 87 | 
            +
            executables:
         | 
| 88 | 
            +
            - dslimple
         | 
| 89 | 
            +
            extensions: []
         | 
| 90 | 
            +
            extra_rdoc_files: []
         | 
| 91 | 
            +
            files:
         | 
| 92 | 
            +
            - ".gitignore"
         | 
| 93 | 
            +
            - ".rubocop.yml"
         | 
| 94 | 
            +
            - Gemfile
         | 
| 95 | 
            +
            - LICENSE.txt
         | 
| 96 | 
            +
            - README.md
         | 
| 97 | 
            +
            - Rakefile
         | 
| 98 | 
            +
            - bin/console
         | 
| 99 | 
            +
            - bin/setup
         | 
| 100 | 
            +
            - dslimple.gemspec
         | 
| 101 | 
            +
            - exe/dslimple
         | 
| 102 | 
            +
            - lib/dslimple.rb
         | 
| 103 | 
            +
            - lib/dslimple/applier.rb
         | 
| 104 | 
            +
            - lib/dslimple/cli.rb
         | 
| 105 | 
            +
            - lib/dslimple/domain.rb
         | 
| 106 | 
            +
            - lib/dslimple/dsl.rb
         | 
| 107 | 
            +
            - lib/dslimple/dsl/domain.rb
         | 
| 108 | 
            +
            - lib/dslimple/dsl/error.rb
         | 
| 109 | 
            +
            - lib/dslimple/dsl/record.rb
         | 
| 110 | 
            +
            - lib/dslimple/exporter.rb
         | 
| 111 | 
            +
            - lib/dslimple/query.rb
         | 
| 112 | 
            +
            - lib/dslimple/query_builder.rb
         | 
| 113 | 
            +
            - lib/dslimple/record.rb
         | 
| 114 | 
            +
            - lib/dslimple/version.rb
         | 
| 115 | 
            +
            homepage: https://github.com/zeny-io/dslimple
         | 
| 116 | 
            +
            licenses:
         | 
| 117 | 
            +
            - MIT
         | 
| 118 | 
            +
            metadata: {}
         | 
| 119 | 
            +
            post_install_message: 
         | 
| 120 | 
            +
            rdoc_options: []
         | 
| 121 | 
            +
            require_paths:
         | 
| 122 | 
            +
            - lib
         | 
| 123 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 124 | 
            +
              requirements:
         | 
| 125 | 
            +
              - - ">="
         | 
| 126 | 
            +
                - !ruby/object:Gem::Version
         | 
| 127 | 
            +
                  version: '0'
         | 
| 128 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 129 | 
            +
              requirements:
         | 
| 130 | 
            +
              - - ">="
         | 
| 131 | 
            +
                - !ruby/object:Gem::Version
         | 
| 132 | 
            +
                  version: '0'
         | 
| 133 | 
            +
            requirements: []
         | 
| 134 | 
            +
            rubyforge_project: 
         | 
| 135 | 
            +
            rubygems_version: 2.5.1
         | 
| 136 | 
            +
            signing_key: 
         | 
| 137 | 
            +
            specification_version: 4
         | 
| 138 | 
            +
            summary: DSLimple is a tool to manage DNSimple.
         | 
| 139 | 
            +
            test_files: []
         |