codily 0.1.0.beta

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ require 'fastly'
2
+ require 'codily/elements/base'
3
+ require 'codily/elements/service'
4
+
5
+ module Codily
6
+ module Elements
7
+ class ServiceBelonggingBase < Base
8
+ def initialize(*)
9
+ super
10
+
11
+ if fastly_obj
12
+ service_version = root.service_version_get(fastly_obj.service_id)
13
+ raise "[bug?] Root#service_version_set should be called before passing fastly obj to ServiceBelonggingBase.new" unless service_version
14
+ @hash[:_service_name] = service_version[:name]
15
+ @hash[:_service_id] = service_version[:id]
16
+ end
17
+ end
18
+
19
+ def self.parent_class
20
+ Service
21
+ end
22
+
23
+ def parent_key
24
+ service_name
25
+ end
26
+
27
+ def service_id
28
+ @hash[:_service_id] ||= begin
29
+ service_version = root.service_version_get(service_name)
30
+ if service_version
31
+ service_version[:id]
32
+ else
33
+ nil
34
+ end
35
+ end
36
+ end
37
+
38
+ def service_name
39
+ @hash[:_service_name]
40
+ end
41
+
42
+ def key
43
+ [service_name, name]
44
+ end
45
+
46
+ def as_hash
47
+ @hash.reject { |k,v| k == :_service_name || k == :_service_id }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,27 @@
1
+ require 'codily/elements/service_belongging_base'
2
+ require 'codily/utils'
3
+
4
+ module Codily
5
+ module Elements
6
+ class Settings < ServiceBelonggingBase
7
+ def setup
8
+ @hash = Utils.symbolize_keys(@hash)
9
+ end
10
+ def dsl_args
11
+ [as_hash]
12
+ end
13
+
14
+ def as_dsl_hash
15
+ {}
16
+ end
17
+
18
+ def key
19
+ service_name
20
+ end
21
+
22
+ def fastly_class
23
+ Fastly::Settings
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ require 'codily/elements/service_belongging_base'
2
+ require 'codily/elements/file_loadable'
3
+
4
+ module Codily
5
+ module Elements
6
+ class Vcl < ServiceBelonggingBase
7
+ include FileLoadable
8
+
9
+ def content(obj)
10
+ getset :content, file_loadable(obj)
11
+ end
12
+
13
+ def main(bool = nil)
14
+ getset :main, bool
15
+ end
16
+
17
+ def fastly_class
18
+ Fastly::VCL
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,177 @@
1
+ require 'codily/update_payload'
2
+
3
+ require 'codily/elements/service'
4
+
5
+ require 'codily/elements/condition'
6
+ require 'codily/elements/backend'
7
+ require 'codily/elements/cache_setting'
8
+ require 'codily/elements/dictionary'
9
+ require 'codily/elements/domain'
10
+ require 'codily/elements/gzip'
11
+ require 'codily/elements/header'
12
+ require 'codily/elements/healthcheck'
13
+ require 'codily/elements/request_setting'
14
+ require 'codily/elements/response_object'
15
+ require 'codily/elements/vcl'
16
+ require 'codily/elements/settings'
17
+
18
+ module Codily
19
+ class Engine
20
+ def initialize(fastly, present, desired, service_filter: nil)
21
+ @fastly = fastly
22
+ @present = present
23
+ @desired = desired
24
+
25
+ @service_filter = service_filter
26
+ end
27
+
28
+ attr_reader :fastly, :present, :desired, :service_filter
29
+
30
+ ORDER = [
31
+ Elements::Service,
32
+ Elements::Settings,
33
+ Elements::Condition,
34
+ Elements::Backend,
35
+ Elements::CacheSetting,
36
+ Elements::Dictionary,
37
+ Elements::Domain,
38
+ Elements::Gzip,
39
+ Elements::Header,
40
+ Elements::Healthcheck,
41
+ Elements::RequestSetting,
42
+ Elements::ResponseObject,
43
+ Elements::Vcl,
44
+ ]
45
+
46
+ def run(dry_run: false)
47
+ if dry_run
48
+ puts "(dry-run)"
49
+ puts
50
+ end
51
+
52
+ act_any = false
53
+ act = false
54
+
55
+ creations.each do |new_element|
56
+ act_any = act = true
57
+ puts "CREATE: #{new_element.inspect}"
58
+
59
+ hash = new_element.as_hash
60
+
61
+ if new_element.parent_class == Elements::Service
62
+ service_version = present.service_version_get(new_element.service_name)
63
+ hash[:service_id] = service_version[:id]
64
+ hash[:version] = service_version[:dev]
65
+ end
66
+
67
+ unless dry_run
68
+ new_obj = fastly.create(new_element.fastly_class, hash)
69
+
70
+ if new_element.class == Elements::Service
71
+ present.service_version_set(new_obj.name, new_obj.id, new_obj.versions)
72
+ end
73
+ end
74
+ end
75
+
76
+ puts if act
77
+ act = false
78
+
79
+ updates.each do |present_elem, desired_elem|
80
+ act_any = act = true
81
+ puts "UPDATE: - #{present_elem.inspect}"
82
+ puts " + #{desired_elem.inspect}"
83
+
84
+ payload = UpdatePayload.new(name: present_elem.name, id: present_elem.id, hash: desired_elem.as_hash)
85
+ if desired_elem.parent_class == Elements::Service
86
+ service_version = present.service_version_get(desired_elem.service_name)
87
+ payload.service_id = service_version[:id]
88
+ payload.version_number = service_version[:dev]
89
+ end
90
+
91
+ unless dry_run
92
+ fastly.update(desired_elem.fastly_class, payload)
93
+ end
94
+ end
95
+
96
+ puts if act
97
+ act = false
98
+
99
+ removals.each do |removed_element|
100
+ act_any = act = true
101
+ puts "DELETE: #{removed_element.inspect}"
102
+
103
+ unless dry_run
104
+ removed_element.fastly_obj.delete!
105
+ end
106
+ end
107
+
108
+ unless act_any
109
+ puts "No difference."
110
+ end
111
+ act_any
112
+ end
113
+
114
+ def creations
115
+ new_keys = desired_element_keys - present_element_keys
116
+ sort_elements(new_keys.map{ |_| desired.elements[_[0]][_[1]] })
117
+ end
118
+
119
+ def updates
120
+ common = present_element_keys & desired_element_keys
121
+
122
+ present_existing = common.map{ |_| present.elements[_[0]][_[1]] }
123
+ desired_existing = common.map{ |_| desired.elements[_[0]][_[1]] }
124
+
125
+ raise '!?' if present_existing.size != desired_existing.size
126
+
127
+ present_existing.zip(desired_existing).map do |present_elem, desired_elem|
128
+ if present_elem.as_hash != desired_elem.as_hash
129
+ [present_elem, desired_elem]
130
+ else
131
+ nil
132
+ end
133
+ end.compact.sort_by { |_| ORDER.index(_[0].class) }
134
+ end
135
+
136
+ def removals
137
+ removed_keys = present_element_keys - desired_element_keys
138
+ sort_elements(removed_keys.map{ |_| present.elements[_[0]][_[1]] })
139
+ end
140
+
141
+ private
142
+
143
+ def present_elements
144
+ @present_elements ||= filter_elements(present.all_elements)
145
+ end
146
+
147
+ def desired_elements
148
+ @desired_elements ||= filter_elements(desired.all_elements)
149
+ end
150
+
151
+ def present_element_keys
152
+ @present_element_keys ||= present_elements.map { |_| [_.class, _.key] }
153
+ end
154
+
155
+ def desired_element_keys
156
+ @desired_element_keys ||= desired_elements.map { |_| [_.class, _.key] }
157
+ end
158
+
159
+ def present_service_names
160
+ @present_service_names ||= present.list_element(Elements::Service).each_value.map(&:name)
161
+ end
162
+
163
+ def filter_elements(elems)
164
+ elems.select do |e|
165
+ if e.parent_class == Elements::Service
166
+ (@service_filter ? @service_filter.any? { |_| _ === e.service_name } : true) && present_service_names.include?(e.service_name)
167
+ else
168
+ true
169
+ end
170
+ end
171
+ end
172
+
173
+ def sort_elements(elems)
174
+ elems.sort_by { |_| ORDER.index(_.class) }
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,20 @@
1
+ require 'fastly'
2
+
3
+ class Fastly::RequestSetting
4
+ attr_accessor :timer_support
5
+ end
6
+
7
+ module Codily
8
+ module FastlyExt
9
+ def initialize(opts)
10
+ super
11
+ @opts = opts
12
+ end
13
+
14
+ def client(opts = nil)
15
+ (Thread.current[:fastly_client] ||= {})[self.__id__] ||= Fastly::Client.new(opts || @opts)
16
+ end
17
+ end
18
+ end
19
+
20
+ Fastly.__send__(:prepend, Codily::FastlyExt)
@@ -0,0 +1,77 @@
1
+ require 'fastly'
2
+ require 'codily/fastly_ext'
3
+
4
+ require 'codily/root'
5
+
6
+ require 'codily/elements/service'
7
+
8
+ require 'codily/elements/backend'
9
+ require 'codily/elements/cache_setting'
10
+ require 'codily/elements/condition'
11
+ require 'codily/elements/dictionary'
12
+ require 'codily/elements/domain'
13
+ require 'codily/elements/gzip'
14
+ require 'codily/elements/header'
15
+ require 'codily/elements/healthcheck'
16
+ require 'codily/elements/request_setting'
17
+ require 'codily/elements/response_object'
18
+ require 'codily/elements/vcl'
19
+ require 'codily/elements/settings'
20
+
21
+ module Codily
22
+ class Importer
23
+ def initialize(fastly, import_targets: {}, service_filter: nil, debug: false)
24
+ @fastly = fastly
25
+ @import_targets = import_targets
26
+ @service_filter = service_filter
27
+
28
+ @ran = false
29
+ @root = Codily::Root.new(debug: debug)
30
+ end
31
+
32
+ attr_reader :fastly, :root
33
+
34
+ def run
35
+ return self if @ran
36
+
37
+ fastly.list_services.each do |service|
38
+ if @service_filter
39
+ next unless @service_filter.any? { |_| _ === service.name }
40
+ end
41
+ service_version = root.service_version_set(service.name, service.id, service.versions)
42
+ import_version = @import_targets[service.id] || @import_targets[service.name] || service_version[:dev]
43
+
44
+ root.add_element Elements::Service.new(root, service)
45
+
46
+ threads = {
47
+ Elements::Backend => proc { fastly.list_backends(service_id: service.id, version: import_version) },
48
+ Elements::CacheSetting => proc { fastly.list_cache_settings(service_id: service.id, version: import_version) },
49
+ Elements::Condition => proc { fastly.list_conditions(service_id: service.id, version: import_version) },
50
+ Elements::Dictionary => proc { fastly.list_dictionaries(service_id: service.id, version: import_version) },
51
+ Elements::Domain => proc { fastly.list_domains(service_id: service.id, version: import_version) },
52
+ Elements::Gzip => proc { fastly.list_gzips(service_id: service.id, version: import_version) },
53
+ Elements::Header => proc { fastly.list_headers(service_id: service.id, version: import_version) },
54
+ Elements::Healthcheck => proc { fastly.list_healthchecks(service_id: service.id, version: import_version) },
55
+ Elements::RequestSetting => proc { fastly.list_request_settings(service_id: service.id, version: import_version) },
56
+ Elements::ResponseObject => proc { fastly.list_response_objects(service_id: service.id, version: import_version) },
57
+ Elements::Vcl => proc { fastly.list_vcls(service_id: service.id, version: import_version) },
58
+ Elements::Settings => proc { [fastly.get_settings(service.id, import_version)] },
59
+ }.map do |k, list_proc|
60
+ Thread.new(k) do |klass|
61
+ list_proc.call.map do |_|
62
+ klass.new(root, _)
63
+ end
64
+ end
65
+ end
66
+
67
+ threads.each(&:value)
68
+ threads.flat_map(&:value).each do |elem|
69
+ root.add_element elem
70
+ end
71
+ end
72
+
73
+ @ran = true
74
+ self
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,83 @@
1
+ require 'fastly'
2
+ require 'codily/elements/service'
3
+
4
+ module Codily
5
+ class Root
6
+ class AlreadyDefined < StandardError; end
7
+
8
+ def initialize(debug: false)
9
+ @debug = debug
10
+ @elements = {}
11
+
12
+ @service_versions = {}
13
+ @service_map_name_to_id = {}
14
+ end
15
+
16
+ attr_reader :elements
17
+ attr_accessor :debug
18
+
19
+ def as_hash
20
+ {}
21
+ end
22
+
23
+ def as_dsl_hash
24
+ as_hash
25
+ end
26
+
27
+ # XXX: is it okay having this here?
28
+ def service_version_set(service_name, service_id, versions)
29
+ @service_map_name_to_id[service_name] = service_id
30
+ dev = versions.reverse_each.find { |_| !_.locked }.number
31
+ active = (versions.reverse_each.find(&:active) || versions.reverse_each.find(&:locked)).number
32
+ @service_versions[service_id] = {dev: dev, active: active, name: service_name, id: service_id}
33
+ end
34
+
35
+ def service_version_get(name_or_id)
36
+ @service_versions[@service_map_name_to_id[name_or_id] || name_or_id]
37
+ end
38
+
39
+ def services
40
+ list_element(Elements::Service)
41
+ end
42
+
43
+ def service(name, &block)
44
+ raise AlreadyDefined if services.key?(name)
45
+ add_element(Elements::Service.new(self, {name: name}, &block))
46
+ end
47
+
48
+
49
+ def run_block(&block)
50
+ raise ArgumentError, 'block not given' unless block
51
+
52
+ instance_eval &block
53
+ self
54
+ end
55
+
56
+ def run_string(str, file = '(eval)', line = 1)
57
+ instance_eval str, file, line
58
+ self
59
+ end
60
+
61
+
62
+ def add_element(element)
63
+ h = (@elements[element.class] ||= {})
64
+ raise AlreadyDefined, "#{element.class.name}(#{element.key}) is already defined: (#{h.keys.inspect})" if h.key?(element.key)
65
+ if debug
66
+ puts "DEBUG: #{self.class}/#{'%x' % self.__id__}(add_element): #{element.class}(#{element.key.inspect}) #{element.as_hash.inspect}"
67
+ end
68
+ h[element.key] = element
69
+ end
70
+
71
+ def list_element(klass)
72
+ @elements[klass] ||= {}
73
+ end
74
+
75
+ def find_element(element)
76
+ list_element(element.class)[element.key]
77
+ end
78
+
79
+ def all_elements
80
+ @elements.each_value.flat_map(&:values)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,19 @@
1
+ module Codily
2
+ class UpdatePayload
3
+ def initialize(id: nil, service_id: nil, version_number: nil, name: nil, hash: nil)
4
+ @id = id
5
+ @service_id = service_id
6
+ @version_number = version_number
7
+ @name = name
8
+ @hash = hash
9
+ end
10
+
11
+ attr_accessor :id, :service_id, :version_number, :name, :hash
12
+
13
+ alias version version_number
14
+
15
+ def as_hash
16
+ @hash
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ module Codily
2
+ module Utils
3
+ def self.symbolize_keys(obj)
4
+ case obj
5
+ when Hash
6
+ Hash[
7
+ obj.map do |k, v|
8
+ [k.to_sym, symbolize_keys(v)]
9
+ end
10
+ ]
11
+ when Array
12
+ obj.map { |_| symbolize_keys(_) }
13
+ else
14
+ obj
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module Codily
2
+ VERSION = "0.1.0.beta"
3
+ end
data/lib/codily.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'codily/version'
2
+ require 'codily/cli'
3
+ require 'codily/importer'
4
+ require 'codily/dumper'
5
+ require 'codily/engine'
6
+
data/script/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "codily"
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/script/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: codily
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.beta
5
+ platform: ruby
6
+ authors:
7
+ - Sorah Fukumori
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-06-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fastly
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.12'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
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: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description:
70
+ email:
71
+ - her@sorah.jp
72
+ executables:
73
+ - codily
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - Gemfile
81
+ - LICENSE.txt
82
+ - README.md
83
+ - Rakefile
84
+ - bin/codily
85
+ - codily.gemspec
86
+ - lib/codily.rb
87
+ - lib/codily/cli.rb
88
+ - lib/codily/dumper.rb
89
+ - lib/codily/elements/backend.rb
90
+ - lib/codily/elements/base.rb
91
+ - lib/codily/elements/cache_setting.rb
92
+ - lib/codily/elements/condition.rb
93
+ - lib/codily/elements/dictionary.rb
94
+ - lib/codily/elements/domain.rb
95
+ - lib/codily/elements/file_loadable.rb
96
+ - lib/codily/elements/gzip.rb
97
+ - lib/codily/elements/header.rb
98
+ - lib/codily/elements/healthcheck.rb
99
+ - lib/codily/elements/request_setting.rb
100
+ - lib/codily/elements/response_object.rb
101
+ - lib/codily/elements/service.rb
102
+ - lib/codily/elements/service_belongging_base.rb
103
+ - lib/codily/elements/settings.rb
104
+ - lib/codily/elements/vcl.rb
105
+ - lib/codily/engine.rb
106
+ - lib/codily/fastly_ext.rb
107
+ - lib/codily/importer.rb
108
+ - lib/codily/root.rb
109
+ - lib/codily/update_payload.rb
110
+ - lib/codily/utils.rb
111
+ - lib/codily/version.rb
112
+ - script/console
113
+ - script/setup
114
+ homepage: https://github.com/sorah/codily
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">"
130
+ - !ruby/object:Gem::Version
131
+ version: 1.3.1
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 2.6.4
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Codificate Fastly configuration
138
+ test_files: []