├── .gitignore ├── .rspec ├── CONTRIBUTING.md ├── Gemfile ├── History.md ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin └── dns_deploy ├── dnsdeploy.gemspec ├── lib ├── core_ext │ └── string.rb ├── dnsdeploy.rb └── dnsdeploy │ ├── base.rb │ ├── local.rb │ ├── record.rb │ └── version.rb └── spec ├── assets └── records.json ├── dnsdeploy └── base_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | *.bundle 19 | *.so 20 | *.o 21 | *.a 22 | mkmf.log 23 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | In the spirit of [free software][free-sw], **everyone** is encouraged to help 3 | improve this project. 4 | 5 | [free-sw]: http://www.fsf.org/licensing/essays/free-sw.html 6 | 7 | Here are some ways *you* can contribute: 8 | 9 | * by using alpha, beta, and prerelease versions 10 | * by reporting bugs 11 | * by suggesting new features 12 | * by writing or editing documentation 13 | * by writing specifications 14 | * by writing code ( **no patch is too small** : fix typos, add comments, clean up inconsistent whitespace ) 15 | * by refactoring code 16 | * by closing [issues][] 17 | * by reviewing patches 18 | 19 | [issues]: https://github.com/beanieboi/ffprober/issues 20 | 21 | ## Submitting an Issue 22 | We use the [GitHub issue tracker][issues] to track bugs and features. Before 23 | submitting a bug report or feature request, check to make sure it hasn't 24 | already been submitted. When submitting a bug report, please include a [Gist][] 25 | that includes a stack trace and any details that may be necessary to reproduce 26 | the bug, including your gem version, Ruby version, and operating system. 27 | Ideally, a bug report should include a pull request with failing specs. 28 | 29 | [gist]: https://gist.github.com/ 30 | 31 | ## Submitting a Pull Request 32 | 1. [Fork the repository.][fork] 33 | 2. [Create a topic branch.][branch] 34 | 3. Implement your feature or bug fix. 35 | 4. Add, commit, and push your changes. 36 | 5. [Submit a pull request.][pr] 37 | 38 | [fork]: http://help.github.com/fork-a-repo/ 39 | [branch]: http://learn.github.com/p/branching.html 40 | [pr]: http://help.github.com/send-pull-requests/ 41 | 42 | This file was taken from https://github.com/middleman/middleman-heroku/blob/master/CONTRIBUTING.md 43 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in dnsdeploy.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.0.2 / 2014-09-07 3 | ================== 4 | 5 | * load JSON only once 6 | 7 | 0.0.1 / 2014-09-06 8 | ================== 9 | 10 | * initial version 11 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 beanieboi 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DNSdeploy 2 | 3 | Continuous Deployment for your DNS Records 4 | 5 | ## Usage 6 | 7 | We wrote a [blog post][blogpost] on how to use dns-deploy with DNSimple. 8 | 9 | [Example repository][repository] 10 | 11 | [blogpost]: http://blog.codeship.io/2014/09/09/continuous-deployment-of-dns-records-with-dnsimple.html 12 | [repository]: https://github.com/codeship/dns-example 13 | 14 | ## Contributing 15 | 16 | see [CONTRIBUTING.md][contributing] 17 | 18 | [contributing]: https://github.com/beanieboi/ffprober/blob/master/CONTRIBUTING.md 19 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | -------------------------------------------------------------------------------- /bin/dns_deploy: -------------------------------------------------------------------------------- 1 | require_relative '../lib/dnsdeploy' 2 | 3 | records_file = ARGV.first 4 | 5 | deployer = Dnsdeploy::Base.new(records_file) 6 | deployer.validate 7 | deployer.update_records 8 | -------------------------------------------------------------------------------- /dnsdeploy.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'dnsdeploy/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "dnsdeploy" 8 | spec.version = Dnsdeploy::VERSION 9 | spec.authors = ["beanieboi"] 10 | spec.email = ["beanie@benle.de"] 11 | spec.summary = %q{Wrapper between DNSimple and records.json} 12 | spec.description = %q{Wrapper between DNSimple and records.json - deploy your DNS records} 13 | spec.homepage = "https://www.codeship.io" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_runtime_dependency "dnsimple-ruby", "~> 1.5" 22 | 23 | spec.add_development_dependency "bundler", "~> 1.6" 24 | spec.add_development_dependency "rake", "~> 10.3" 25 | spec.add_development_dependency "rspec", "~> 3.0" 26 | spec.add_development_dependency "pry", "~> 0.10" 27 | end 28 | -------------------------------------------------------------------------------- /lib/core_ext/string.rb: -------------------------------------------------------------------------------- 1 | class String 2 | COLORS = { red: 31, 3 | green: 32, 4 | yellow: 33, 5 | blue: 34, 6 | pink: 35 7 | } 8 | 9 | # colorization 10 | def colorize(color_code) 11 | "\e[#{color_code}m#{self}\e[0m" 12 | end 13 | 14 | COLORS.each do |color, code| 15 | define_method(color) { colorize(code) } 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/dnsdeploy.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'dnsimple' 3 | 4 | require_relative 'dnsdeploy/version' 5 | require_relative 'dnsdeploy/local' 6 | require_relative 'dnsdeploy/record' 7 | require_relative 'dnsdeploy/base' 8 | require_relative 'core_ext/string' 9 | 10 | DNSimple::Client.username = ENV['DNSIMPLE_USERNAME'] 11 | DNSimple::Client.api_token = ENV['DNSIMPLE_API_TOKEN'] 12 | 13 | module Dnsdeploy 14 | end 15 | -------------------------------------------------------------------------------- /lib/dnsdeploy/base.rb: -------------------------------------------------------------------------------- 1 | module Dnsdeploy 2 | class Base 3 | def initialize(records_file_path) 4 | @records_file_path = records_file_path 5 | @local_records_json = File.new(records_file_path).read 6 | end 7 | 8 | def self.update_records(records_file_path) 9 | self.new(records_file_path).update_records 10 | end 11 | 12 | def validate 13 | JSON.load(@local_records_json) 14 | puts "#{@records_file_path} is valid json".green 15 | rescue => e 16 | puts "unable to parse #{@records_file_path}".red 17 | exit(1) 18 | end 19 | 20 | def update_records 21 | local.domains.each_pair do |domain, dnsimple_domain| 22 | if dnsimple_domain.nil? 23 | puts "[ERROR] Domain #{domain} does not exists on DNSimple" 24 | else 25 | puts "[Processing] Domain #{domain}" 26 | 27 | # Delete records on DNSimple 28 | DNSimple::Record.all(dnsimple_domain).collect(&:destroy) 29 | 30 | # create records 31 | local.records(dnsimple_domain).each do |record| 32 | puts "[CREATE] #{record}".green 33 | begin 34 | DNSimple::Record.create(record.domain, record.name, record.record_type, 35 | record.content, { ttl: record.ttl, prio: record.prio }) 36 | rescue DNSimple::RequestError => e 37 | puts "[ERROR] #{e} #{record}".red 38 | @exit = 1 39 | end 40 | end 41 | end 42 | 43 | exit(@exit) if @exit 44 | end 45 | end 46 | 47 | def local 48 | @local ||= Dnsdeploy::Local.new(@local_records_json) 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/dnsdeploy/local.rb: -------------------------------------------------------------------------------- 1 | require 'singleton' 2 | 3 | module Dnsdeploy 4 | class Local 5 | def initialize(local_records_json) 6 | @local_records_json = local_records_json 7 | end 8 | 9 | def all_records 10 | @all_records ||= json.map do |record_set| 11 | domain = dnsimple_domain(record_set['zone']) 12 | create_records(domain, record_set['records']) 13 | end.flatten 14 | end 15 | 16 | def records(domain) 17 | all_records.select { |r| r.domain.name == domain.name } 18 | end 19 | 20 | def domains 21 | @domains ||= json.inject({}) do |memo, record_set| 22 | memo[record_set['zone']] = dnsimple_domain(record_set['zone']) 23 | memo 24 | end 25 | end 26 | 27 | def create_records(domain, json_records) 28 | json_records.map do |record| 29 | Record.new(domain: domain, name: record['name'], record_type: record['type'], 30 | content: record['value'], ttl: record['ttl'], prio: record['prio']) 31 | end 32 | end 33 | 34 | def json 35 | @json ||= JSON.load(@local_records_json) 36 | end 37 | 38 | def dnsimple_domain(zone) 39 | @dnsimple_domains ||= {} 40 | @dnsimple_domains[zone] ||= DNSimple::Domain.all.select { |d| d.name == zone }.first 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/dnsdeploy/record.rb: -------------------------------------------------------------------------------- 1 | module Dnsdeploy 2 | class Record < DNSimple::Record 3 | def to_s 4 | "#{self.name}.#{self.domain.name} #{self.record_type} #{self.content} (ttl: #{self.ttl})" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/dnsdeploy/version.rb: -------------------------------------------------------------------------------- 1 | module Dnsdeploy 2 | VERSION = "0.0.4" 3 | end 4 | -------------------------------------------------------------------------------- /spec/assets/records.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "zone": "example.com", 3 | "records": [ 4 | { "name": "", "type": "MX", "value": "alt1.aspmx.l.google.com", "prio": 5, "ttl":300 }, 5 | { "name": "", "type": "MX", "value": "alt2.aspmx.l.google.com", "prio": 5, "ttl":300 } 6 | ] 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /spec/dnsdeploy/base_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Dnsdeploy::Base do 4 | let(:name) { random_string } 5 | let(:record_type) { random_string } 6 | let(:content) { random_string } 7 | let(:ttl) { random_number } 8 | let(:prio) { random_number } 9 | let(:local) { double(:local, domains: { 'example.com' => domain }, records: [record]) } 10 | let(:record) { Dnsdeploy::Record.new(domain: domain, name: name, record_type: record_type, 11 | content: content, ttl: ttl, prio: prio ) } 12 | let(:records_file) { 'spec/assets/records.json' } 13 | 14 | subject(:base) { described_class.new(records_file) } 15 | 16 | before do 17 | allow(DNSimple::Record).to receive(:all).and_return([]) 18 | allow(Dnsdeploy::Local).to receive(:new).and_return local 19 | end 20 | 21 | context 'existing domain' do 22 | let(:domain) { DNSimple::Domain.new name: random_domain } 23 | 24 | it "should create records" do 25 | expect(DNSimple::Record).to receive(:create).with(domain, name, record_type, content, {ttl: ttl, prio: prio }) 26 | base.update_records 27 | end 28 | end 29 | 30 | context 'non existent domain' do 31 | let(:domain) { nil } 32 | 33 | it "should print an error" do 34 | expect(DNSimple::Record).to_not receive(:create) 35 | 36 | expect { base.update_records }.to output("[ERROR] Domain example.com does not exists on DNSimple\n").to_stdout 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | require 'json' 3 | Bundler.require 4 | 5 | require_relative '../lib/dnsdeploy' 6 | 7 | RSpec.configure do |config| 8 | # These two settings work together to allow you to limit a spec run 9 | # to individual examples or groups you care about by tagging them with 10 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 11 | # get run. 12 | config.filter_run :focus 13 | config.run_all_when_everything_filtered = true 14 | 15 | # Many RSpec users commonly either run the entire suite or an individual 16 | # file, and it's useful to allow more verbose output when running an 17 | # individual spec file. 18 | if config.files_to_run.one? 19 | # Use the documentation formatter for detailed output, 20 | # unless a formatter has already been configured 21 | # (e.g. via a command-line flag). 22 | config.default_formatter = 'doc' 23 | end 24 | 25 | # Print the 10 slowest examples and example groups at the 26 | # end of the spec run, to help surface which specs are running 27 | # particularly slow. 28 | config.profile_examples = 10 29 | 30 | # Run specs in random order to surface order dependencies. If you find an 31 | # order dependency and want to debug it, you can fix the order by providing 32 | # the seed, which is printed after each run. 33 | # --seed 1234 34 | config.order = :random 35 | 36 | # Seed global randomization in this process using the `--seed` CLI option. 37 | # Setting this allows you to use `--seed` to deterministically reproduce 38 | # test failures related to randomization by passing the same `--seed` value 39 | # as the one that triggered the failure. 40 | Kernel.srand config.seed 41 | 42 | # rspec-expectations config goes here. You can use an alternate 43 | # assertion/expectation library such as wrong or the stdlib/minitest 44 | # assertions if you prefer. 45 | config.expect_with :rspec do |expectations| 46 | # Enable only the newer, non-monkey-patching expect syntax. 47 | # For more details, see: 48 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 49 | expectations.syntax = :expect 50 | end 51 | 52 | # rspec-mocks config goes here. You can use an alternate test double 53 | # library (such as bogus or mocha) by changing the `mock_with` option here. 54 | config.mock_with :rspec do |mocks| 55 | # Enable only the newer, non-monkey-patching expect syntax. 56 | # For more details, see: 57 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 58 | mocks.syntax = :expect 59 | 60 | # Prevents you from mocking or stubbing a method that does not exist on 61 | # a real object. This is generally recommended. 62 | mocks.verify_partial_doubles = true 63 | end 64 | end 65 | 66 | def random_string letters=rand(10)+10 67 | [*('a'..'z')].sample(letters).join 68 | end 69 | 70 | def random_domain 71 | [random_string, ".", random_string(2)].join 72 | end 73 | 74 | def random_number 75 | rand 10000 76 | end 77 | --------------------------------------------------------------------------------