fastlyctl 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +34 -0
- data/LICENSE.txt +21 -0
- data/README.md +324 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/build.rb +3 -0
- data/exe/fastlyctl +6 -0
- data/fastlyctl.gemspec +38 -0
- data/lib/fastlyctl/cli.rb +43 -0
- data/lib/fastlyctl/clone_utils.rb +149 -0
- data/lib/fastlyctl/commands/acl.rb +88 -0
- data/lib/fastlyctl/commands/activate.rb +27 -0
- data/lib/fastlyctl/commands/clone.rb +40 -0
- data/lib/fastlyctl/commands/copy.rb +41 -0
- data/lib/fastlyctl/commands/create_service.rb +18 -0
- data/lib/fastlyctl/commands/dictionary.rb +75 -0
- data/lib/fastlyctl/commands/diff.rb +97 -0
- data/lib/fastlyctl/commands/domain.rb +47 -0
- data/lib/fastlyctl/commands/download.rb +85 -0
- data/lib/fastlyctl/commands/login.rb +58 -0
- data/lib/fastlyctl/commands/open.rb +24 -0
- data/lib/fastlyctl/commands/purge_all.rb +17 -0
- data/lib/fastlyctl/commands/skeleton.rb +24 -0
- data/lib/fastlyctl/commands/snippet.rb +105 -0
- data/lib/fastlyctl/commands/token.rb +53 -0
- data/lib/fastlyctl/commands/upload.rb +155 -0
- data/lib/fastlyctl/commands/watch.rb +49 -0
- data/lib/fastlyctl/fetcher.rb +250 -0
- data/lib/fastlyctl/utils.rb +72 -0
- data/lib/fastlyctl/version.rb +3 -0
- data/lib/fastlyctl.rb +28 -0
- metadata +155 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
module FastlyCTL
|
2
|
+
class CLI < Thor
|
3
|
+
desc "acl ACTION ACL_NAME IP_OR_SUBNET", "Manipulate ACLS.\n Actions:\n create: Create an ACL\n
|
4
|
+
delete: Delete an ACL\n
|
5
|
+
list: Provide a list of ACLs on this service\n
|
6
|
+
add: Add an IP/subnet to an ACL\n
|
7
|
+
remove: Remove an IP/subnet from an ACL\n
|
8
|
+
list_ips: List all IPs/subnets in the ACL\n
|
9
|
+
bulk_add: Perform operations on the ACL in bulk. A list of operations in JSON format should be specified in the ip field. Documentation on this format can be found here: https://docs.fastly.com/api/config#acl_entry_c352ca5aee49b7898535cce488e3ba82"
|
10
|
+
method_option :service, :aliases => ["--s"]
|
11
|
+
method_option :version, :aliases => ["--v"]
|
12
|
+
method_option :negate, :aliases => ["--n"]
|
13
|
+
def acl(action, name=false, ip=false)
|
14
|
+
id = FastlyCTL::Utils.parse_directory unless options[:service]
|
15
|
+
id ||= options[:service]
|
16
|
+
|
17
|
+
abort "Could not parse service id from directory. Specify service id with --service or use from within service directory." unless id
|
18
|
+
|
19
|
+
version = FastlyCTL::Fetcher.get_writable_version(id) unless options[:version]
|
20
|
+
version ||= options[:version]
|
21
|
+
|
22
|
+
case action
|
23
|
+
when "create"
|
24
|
+
abort "Must specify name for ACL" unless name
|
25
|
+
FastlyCTL::Fetcher.api_request(:post, "/service/#{id}/version/#{version}/acl", params: { name: name })
|
26
|
+
|
27
|
+
say("ACL #{name} created.")
|
28
|
+
when "delete"
|
29
|
+
abort "Must specify name for ACL" unless name
|
30
|
+
FastlyCTL::Fetcher.api_request(:delete, "/service/#{id}/version/#{version}/acl/#{name}")
|
31
|
+
|
32
|
+
say("ACL #{name} deleted.")
|
33
|
+
when "list"
|
34
|
+
resp = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/acl")
|
35
|
+
|
36
|
+
say("No ACLs on service in this version.") unless resp.length > 0
|
37
|
+
|
38
|
+
resp.each do |d|
|
39
|
+
puts "#{d["id"]} - #{d["name"]}"
|
40
|
+
end
|
41
|
+
when "add"
|
42
|
+
abort "Must specify name for ACL" unless name
|
43
|
+
abort "Must specify IP" unless ip
|
44
|
+
acl = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/acl/#{name}")
|
45
|
+
FastlyCTL::Fetcher.api_request(:post, "/service/#{id}/acl/#{acl["id"]}/entry", params: { ip: ip, negated: options.key?(:negate) ? "1" : "0" })
|
46
|
+
|
47
|
+
say("#{ip} added to ACL #{name}.")
|
48
|
+
when "remove"
|
49
|
+
abort "Must specify name for ACL" unless name
|
50
|
+
abort "Must specify IP for ACL entry" unless ip
|
51
|
+
acl = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/acl/#{name}")
|
52
|
+
entries = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/acl/#{acl["id"]}/entries")
|
53
|
+
|
54
|
+
entry = false
|
55
|
+
entries.each do |e|
|
56
|
+
if e["ip"] == ip
|
57
|
+
entry = e
|
58
|
+
break
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
abort "IP #{ip} not found in ACL" unless entry
|
63
|
+
|
64
|
+
FastlyCTL::Fetcher.api_request(:delete, "/service/#{id}/acl/#{acl["id"]}/entry/#{entry["id"]}")
|
65
|
+
|
66
|
+
say("IP #{ip} removed from ACL #{name}.")
|
67
|
+
when "list_ips"
|
68
|
+
abort "Must specify name for ACL" unless name
|
69
|
+
acl = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/acl/#{name}")
|
70
|
+
entries = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/acl/#{acl["id"]}/entries")
|
71
|
+
|
72
|
+
say("No items in ACL.") unless entries.length > 0
|
73
|
+
entries.each do |i|
|
74
|
+
puts "#{i["ip"]} - Negated: #{i["negated"] == "0" ? "false" : "true"}"
|
75
|
+
end
|
76
|
+
when "bulk_add"
|
77
|
+
abort "Must specify name for ACL" unless name
|
78
|
+
abort "Must specify JSON blob of operations in ip field. Documentation on this can be found here: https://docs.fastly.com/api/config#acl_entry_c352ca5aee49b7898535cce488e3ba82" unless ip
|
79
|
+
acl = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/acl/#{name}")
|
80
|
+
FastlyCTL::Fetcher.api_request(:patch, "/service/#{id}/acl/#{acl["id"]}/items", {body: ip, headers: {"Content-Type" => "application/json"}})
|
81
|
+
|
82
|
+
say("Bulk add operation completed successfully.")
|
83
|
+
else
|
84
|
+
abort "#{action} is not a valid command"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module FastlyCTL
|
2
|
+
class CLI < Thor
|
3
|
+
desc "activate", "Activates the latest writable service version, or the version number provided in the --version flag."
|
4
|
+
method_option :service, :aliases => ["--s"]
|
5
|
+
method_option :version, :aliases => ["--v"]
|
6
|
+
method_option :comment, :aliases => ["--c"]
|
7
|
+
def activate
|
8
|
+
id = FastlyCTL::Utils.parse_directory unless options[:service]
|
9
|
+
id ||= options[:service]
|
10
|
+
|
11
|
+
abort "Could not parse service id from directory. Use --s <service> to specify, vcl download, then try again." unless id
|
12
|
+
|
13
|
+
writable_version = FastlyCTL::Fetcher.get_writable_version(id) unless options[:version]
|
14
|
+
writable_version ||= options[:version].to_i
|
15
|
+
|
16
|
+
if options.key?(:comment)
|
17
|
+
FastlyCTL::Fetcher.api_request(:put, "/service/#{id}/version/#{writable_version}",{
|
18
|
+
params: {comment: options[:comment]}
|
19
|
+
})
|
20
|
+
end
|
21
|
+
|
22
|
+
FastlyCTL::Fetcher.api_request(:put, "/service/#{id}/version/#{writable_version}/activate")
|
23
|
+
|
24
|
+
say("Version #{writable_version} on #{id} activated.")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module FastlyCTL
|
2
|
+
class CLI < Thor
|
3
|
+
desc "clone SERVICE_ID TARGET_SERVICE_ID", "Clone a service version to another service."
|
4
|
+
method_option :version, :aliases => ["--v"]
|
5
|
+
method_option :skip_logging, :aliases => ["--sl"]
|
6
|
+
def clone(id,target_id)
|
7
|
+
version = FastlyCTL::Fetcher.get_active_version(id) unless options[:version]
|
8
|
+
version ||= options[:version]
|
9
|
+
|
10
|
+
target_version = FastlyCTL::Fetcher.api_request(:post, "/service/#{target_id}/version")["number"]
|
11
|
+
|
12
|
+
puts "Copying #{id} version #{version} to #{target_id} version #{target_version}..."
|
13
|
+
|
14
|
+
FastlyCTL::CloneUtils::OBJECT_TYPES.each do |type,meta|
|
15
|
+
next if (type.include?("logging/") && options.key?(:skip_logging))
|
16
|
+
|
17
|
+
response = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/#{type}")
|
18
|
+
response = [response] unless response.is_a?(Array)
|
19
|
+
|
20
|
+
next unless response.length > 0
|
21
|
+
|
22
|
+
puts "Copying #{response.length} " + (response.length == 1 ? FastlyCTL::CloneUtils.unpluralize(type) : FastlyCTL::CloneUtils.pluralize(type))
|
23
|
+
|
24
|
+
response.each do |obj|
|
25
|
+
FastlyCTL::CloneUtils.copy(obj,type,target_id,target_version)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
target_active_version = FastlyCTL::Fetcher.get_active_version(target_id)
|
30
|
+
response = FastlyCTL::Fetcher.api_request(:get, "/service/#{target_id}/version/#{target_active_version}/domain")
|
31
|
+
return unless response.length > 0
|
32
|
+
|
33
|
+
puts "Restoring #{response.length} " + (response.length == 1 ? "domain" : "domains" + " from #{target_id} version #{target_active_version}...")
|
34
|
+
|
35
|
+
response.each do |domain|
|
36
|
+
FastlyCTL::CloneUtils.copy(domain,"domain",target_id,target_version)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module FastlyCTL
|
2
|
+
class CLI < Thor
|
3
|
+
desc "copy SERVICE_ID TARGET_SERVICE_ID OBJECT_TYPE OBJECT_NAME", "Copy an object from one service to another\n Available Object Types: #{FastlyCTL::CloneUtils::OBJECT_TYPES.keys.join(', ')}"
|
4
|
+
method_option :version1, :aliases => ["--v1"]
|
5
|
+
method_option :version2, :aliases => ["--v2"]
|
6
|
+
def copy(id,target_id,obj_type,obj_name=false)
|
7
|
+
abort "Object name must be specified for all object types except settings" if (obj_name === false && obj_type != "settings")
|
8
|
+
|
9
|
+
source_version = FastlyCTL::Fetcher.get_active_version(id) unless options[:version1]
|
10
|
+
source_version ||= options[:version1]
|
11
|
+
target_version = FastlyCTL::Fetcher.get_writable_version(target_id) unless options[:version2]
|
12
|
+
target_version ||= options[:version2]
|
13
|
+
|
14
|
+
unless FastlyCTL::CloneUtils::OBJECT_TYPES.include?(obj_type)
|
15
|
+
abort "Object type #{obj_type} is invalid. Must be one of: #{FastlyCTL::CloneUtils::OBJECT_TYPES.keys.join(', ')}"
|
16
|
+
end
|
17
|
+
|
18
|
+
path = "/service/#{id}/version/#{source_version}/#{obj_type}"
|
19
|
+
path += "/#{obj_name}" unless obj_type == "settings"
|
20
|
+
obj = FastlyCTL::Fetcher.api_request(:get, path)
|
21
|
+
|
22
|
+
if (obj_type == "settings")
|
23
|
+
puts "Copying settings from #{id} version #{source_version} to #{target_id} version #{target_version}..."
|
24
|
+
else
|
25
|
+
existing_obj = FastlyCTL::Fetcher.api_request(:get, "/service/#{target_id}/version/#{target_version}/#{obj_type}/#{obj_name}",{
|
26
|
+
expected_responses: [200,404]
|
27
|
+
})
|
28
|
+
|
29
|
+
if existing_obj.key?("name")
|
30
|
+
abort unless yes?("A #{FastlyCTL::CloneUtils.unpluralize(obj_type)} named #{obj_name} already exists on #{target_id} version #{target_version}. Delete it and proceed?")
|
31
|
+
|
32
|
+
FastlyCTL::Fetcher.api_request(:delete,"/service/#{target_id}/version/#{target_version}/#{obj_type}/#{obj_name}")
|
33
|
+
end
|
34
|
+
|
35
|
+
puts "Copying #{FastlyCTL::CloneUtils.unpluralize(obj_type)} #{obj_name} from #{id} version #{source_version} to #{target_id} version #{target_version}..."
|
36
|
+
end
|
37
|
+
|
38
|
+
FastlyCTL::CloneUtils.copy(obj,obj_type,target_id,target_version)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module FastlyCTL
|
2
|
+
class CLI < Thor
|
3
|
+
desc "create_service SERVICE_NAME", "Create a blank service. If --customer is supplied and you are an admin, the command will move the service to that customer's account."
|
4
|
+
method_option :customer, :aliases => ["--c"]
|
5
|
+
def create_service(name)
|
6
|
+
service = FastlyCTL::Fetcher.api_request(:post, "/service", { params: { name: name }})
|
7
|
+
|
8
|
+
if options[:customer]
|
9
|
+
say("This command works by creating a service on your account and moving it to the target account.")
|
10
|
+
self.move(service["id"],options[:customer])
|
11
|
+
end
|
12
|
+
|
13
|
+
if yes?("Service #{service["id"]} has been created. Would you like to open the configuration page?")
|
14
|
+
FastlyCTL::Utils.open_service(service["id"])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module FastlyCTL
|
2
|
+
class CLI < Thor
|
3
|
+
desc "dictionary ACTION DICTIONARY_NAME KEY VALUE", "Manipulate edge dictionaries.\n Actions:\n create: Create a dictionary\n
|
4
|
+
delete: Delete a dictionary\n
|
5
|
+
list: Provide a list of dictionaries on this service\n
|
6
|
+
upsert: Update a key in a dictionary if it exists. Add the key if it does not.\n
|
7
|
+
remove: Remove a key from a dictionary\n
|
8
|
+
list_items: List all keys in the dictionary\n
|
9
|
+
bulk_add: Perform operations on the dictionary in bulk. A list of operations in JSON format should be specified in the key field. Documentation on this format can be found here: https://docs.fastly.com/api/config#dictionary_item_dc826ce1255a7c42bc48eb204eed8f7f"
|
10
|
+
method_option :service, :aliases => ["--s"]
|
11
|
+
method_option :version, :aliases => ["--v"]
|
12
|
+
def dictionary(action, name=false, key=false, value=false)
|
13
|
+
id = FastlyCTL::Utils.parse_directory unless options[:service]
|
14
|
+
id ||= options[:service]
|
15
|
+
|
16
|
+
abort "Could not parse service id from directory. Specify service id with --service or use from within service directory." unless id
|
17
|
+
|
18
|
+
version = FastlyCTL::Fetcher.get_writable_version(id) unless options[:version]
|
19
|
+
version ||= options[:version]
|
20
|
+
|
21
|
+
case action
|
22
|
+
when "create"
|
23
|
+
abort "Must specify name for dictionary" unless name
|
24
|
+
FastlyCTL::Fetcher.api_request(:post, "/service/#{id}/version/#{version}/dictionary", params: { name: name })
|
25
|
+
|
26
|
+
say("Dictionary #{name} created.")
|
27
|
+
when "delete"
|
28
|
+
abort "Must specify name for dictionary" unless name
|
29
|
+
FastlyCTL::Fetcher.api_request(:delete, "/service/#{id}/version/#{version}/dictionary/#{name}")
|
30
|
+
|
31
|
+
say("Dictionary #{name} deleted.")
|
32
|
+
when "list"
|
33
|
+
resp = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/dictionary")
|
34
|
+
|
35
|
+
say("No dictionaries on service in this version.") unless resp.length > 0
|
36
|
+
|
37
|
+
resp.each do |d|
|
38
|
+
puts "#{d["id"]} - #{d["name"]}"
|
39
|
+
end
|
40
|
+
when "upsert"
|
41
|
+
abort "Must specify name for dictionary" unless name
|
42
|
+
abort "Must specify key and value for dictionary item" unless (key && value)
|
43
|
+
dict = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/dictionary/#{name}")
|
44
|
+
FastlyCTL::Fetcher.api_request(:put, "/service/#{id}/dictionary/#{dict["id"]}/item/#{key}", params: { item_value: value })
|
45
|
+
|
46
|
+
say("Dictionary item #{key} set to #{value}.")
|
47
|
+
when "remove"
|
48
|
+
abort "Must specify name for dictionary" unless name
|
49
|
+
abort "Must specify key for dictionary item" unless key
|
50
|
+
dict = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/dictionary/#{name}")
|
51
|
+
FastlyCTL::Fetcher.api_request(:delete, "/service/#{id}/dictionary/#{dict["id"]}/item/#{key}")
|
52
|
+
|
53
|
+
say("Item #{key} removed from dictionary #{name}.")
|
54
|
+
when "list_items"
|
55
|
+
abort "Must specify name for dictionary" unless name
|
56
|
+
dict = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/dictionary/#{name}")
|
57
|
+
resp = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/dictionary/#{dict["id"]}/items")
|
58
|
+
|
59
|
+
say("No items in dictionary.") unless resp.length > 0
|
60
|
+
resp.each do |i|
|
61
|
+
puts "#{i["item_key"]} : #{i["item_value"]}"
|
62
|
+
end
|
63
|
+
when "bulk_add"
|
64
|
+
abort "Must specify name for dictionary" unless name
|
65
|
+
abort "Must specify JSON blob of operations in key field. Documentation on this can be found here: https://docs.fastly.com/api/config#dictionary_item_dc826ce1255a7c42bc48eb204eed8f7f" unless key
|
66
|
+
dict = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/dictionary/#{name}")
|
67
|
+
FastlyCTL::Fetcher.api_request(:patch, "/service/#{id}/dictionary/#{dict["id"]}/items", {body: key, headers: {"Content-Type" => "application/json"}})
|
68
|
+
|
69
|
+
say("Bulk add operation completed successfully.")
|
70
|
+
else
|
71
|
+
abort "#{action} is not a valid command"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module FastlyCTL
|
2
|
+
class CLI < Thor
|
3
|
+
desc "diff", "Diff two service versions. By default, diffs the active version of the service assumed from the current directory with the local VCL in the current directory. Options allow you to specify different versions and different services."
|
4
|
+
method_option :version1, :aliases => ["--v1"]
|
5
|
+
method_option :version2, :aliases => ["--v2"]
|
6
|
+
method_option :service1, :aliases => ["--s1"]
|
7
|
+
method_option :service2, :aliases => ["--s2"]
|
8
|
+
method_option :generated, :aliases => ["--g"]
|
9
|
+
def diff
|
10
|
+
if options[:service1]
|
11
|
+
service1 = options[:service1]
|
12
|
+
else
|
13
|
+
service1 = FastlyCTL::Utils.parse_directory
|
14
|
+
abort "Could not parse service id from directory" unless service1
|
15
|
+
end
|
16
|
+
if options[:service2]
|
17
|
+
service2 = options[:service2]
|
18
|
+
else
|
19
|
+
service2 = FastlyCTL::Utils.parse_directory
|
20
|
+
|
21
|
+
# use service1 for both if unspecified
|
22
|
+
service2 = service1 unless service2
|
23
|
+
end
|
24
|
+
|
25
|
+
# diffing different services - no references to local vcl here
|
26
|
+
if service1 != service2
|
27
|
+
version1 = options.key?(:version1) ? options[:version1] : FastlyCTL::Fetcher.get_active_version(service1)
|
28
|
+
version2 = options.key?(:version2) ? options[:version2] : FastlyCTL::Fetcher.get_active_version(service2)
|
29
|
+
end
|
30
|
+
|
31
|
+
# diffing the same service
|
32
|
+
if service1 == service2
|
33
|
+
# if both are specified, diff them
|
34
|
+
if options[:version1] && options[:version2]
|
35
|
+
version1 = options[:version1]
|
36
|
+
version2 = options[:version2]
|
37
|
+
end
|
38
|
+
# if version1 is not specified, diff local with version 2
|
39
|
+
if !options[:version1] && options[:version2]
|
40
|
+
version1 = false
|
41
|
+
version2 = options[:version2]
|
42
|
+
end
|
43
|
+
# if version2 is not specified, diff local with version 1
|
44
|
+
if options[:version1] && !options[:version2]
|
45
|
+
version1 = options[:version1]
|
46
|
+
version2 = false
|
47
|
+
end
|
48
|
+
if !options[:version1] && !options[:version2]
|
49
|
+
# if neither are specified, diff local with active version
|
50
|
+
version1 = FastlyCTL::Fetcher.get_active_version(service2)
|
51
|
+
version2 = false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
say("Diffing#{options[:generated] ? " generated VCL for" : ""} #{service1} #{version1 ? "version "+version1.to_s : "local VCL"} with #{service2} #{version2 ? "version "+version2.to_s : "local VCL"}.")
|
56
|
+
|
57
|
+
if version1
|
58
|
+
v1_vcls = FastlyCTL::Fetcher.get_vcl(service1, version1,options[:generated])
|
59
|
+
else
|
60
|
+
abort "Cannot diff generated VCL with local VCL" if options[:generated]
|
61
|
+
Dir.foreach(Dir.pwd) do |p|
|
62
|
+
next unless File.file?(p)
|
63
|
+
next unless p =~ /\.vcl$/
|
64
|
+
|
65
|
+
v1_vcls ||= Array.new
|
66
|
+
v1_vcls << {
|
67
|
+
"name" => p.chomp(".vcl"),
|
68
|
+
"content" => File.read(p)
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
if version2
|
74
|
+
v2_vcls = FastlyCTL::Fetcher.get_vcl(service2, version2,options[:generated])
|
75
|
+
else
|
76
|
+
abort "Cannot diff generated VCL with local VCL" if options[:generated]
|
77
|
+
Dir.foreach(Dir.pwd) do |p|
|
78
|
+
next unless File.file?(p)
|
79
|
+
next unless p =~ /\.vcl$/
|
80
|
+
|
81
|
+
v2_vcls ||= Array.new
|
82
|
+
v2_vcls << {
|
83
|
+
"name" => p.chomp(".vcl"),
|
84
|
+
"content" => File.read(p)
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
if options[:generated]
|
90
|
+
say(FastlyCTL::Utils.diff_generated(v1_vcls,v2_vcls))
|
91
|
+
else
|
92
|
+
say(FastlyCTL::Utils.diff_versions(v1_vcls,v2_vcls))
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module FastlyCTL
|
2
|
+
class CLI < Thor
|
3
|
+
desc "domain ACTION HOST", "Manipulate domains on a service. Available actions are create, delete, list and check. Create, delete and check take a host argument. Additionally, check can take the argument \"all\" to check all domains."
|
4
|
+
method_option :service, :aliases => ["--s"]
|
5
|
+
method_option :version, :aliases => ["--v"]
|
6
|
+
def domain(action,host=false)
|
7
|
+
id = FastlyCTL::Utils.parse_directory unless options[:service]
|
8
|
+
id ||= options[:service]
|
9
|
+
|
10
|
+
abort "Could not parse service id from directory. Use --s <service> to specify, vcl download, then try again." unless id
|
11
|
+
|
12
|
+
version = FastlyCTL::Fetcher.get_writable_version(id) unless options[:version]
|
13
|
+
version ||= options[:version].to_i
|
14
|
+
|
15
|
+
case action
|
16
|
+
when "create"
|
17
|
+
FastlyCTL::Fetcher.api_request(:post,"/service/#{id}/version/#{version}/domain",{
|
18
|
+
params: {
|
19
|
+
name: host,
|
20
|
+
}
|
21
|
+
})
|
22
|
+
say("#{host} created on #{id} version #{version}")
|
23
|
+
when "delete"
|
24
|
+
FastlyCTL::Fetcher.api_request(:delete,"/service/#{id}/version/#{version}/domain/#{host}")
|
25
|
+
say("#{host} deleted on #{id} version #{version}")
|
26
|
+
when "list"
|
27
|
+
domains = FastlyCTL::Fetcher.api_request(:get,"/service/#{id}/version/#{version}/domain")
|
28
|
+
say("Listing all domains for #{id} version #{version}")
|
29
|
+
domains.each do |d|
|
30
|
+
puts d["name"]
|
31
|
+
end
|
32
|
+
when "check"
|
33
|
+
if host == "all"
|
34
|
+
domains = FastlyCTL::Fetcher.api_request(:get,"/service/#{id}/version/#{version}/domain/check_all")
|
35
|
+
else
|
36
|
+
domains = [FastlyCTL::Fetcher.api_request(:get,"/service/#{id}/version/#{version}/domain/#{host}/check")]
|
37
|
+
end
|
38
|
+
|
39
|
+
domains.each do |d|
|
40
|
+
say("#{d[0]["name"]} -> #{d[1]}")
|
41
|
+
end
|
42
|
+
else
|
43
|
+
abort "#{action} is not a valid command"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module FastlyCTL
|
2
|
+
class CLI < Thor
|
3
|
+
desc "download VCL_NAME", "Download VCLs. If no name is specified, downloads all the VCLs on the service."
|
4
|
+
method_option :service, :aliases => ["--s"]
|
5
|
+
method_option :version, :aliases => ["--v"]
|
6
|
+
method_option :generated, :aliases => ["--g"]
|
7
|
+
def download(vcl_name=false)
|
8
|
+
parsed_id = FastlyCTL::Utils.parse_directory
|
9
|
+
|
10
|
+
if options[:service]
|
11
|
+
abort "Already in a service directory, go up one level in order to specify"\
|
12
|
+
"service id with --service." if parsed_id
|
13
|
+
id = options[:service]
|
14
|
+
parsed = false
|
15
|
+
else
|
16
|
+
abort "Could not parse service id from directory. Specify service id with "\
|
17
|
+
"--service option or use from within service directory." unless parsed_id
|
18
|
+
id = parsed_id
|
19
|
+
parsed = true
|
20
|
+
end
|
21
|
+
|
22
|
+
service = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/details")
|
23
|
+
|
24
|
+
version = FastlyCTL::Fetcher.get_active_version(id) unless options[:version]
|
25
|
+
version ||= options[:version]
|
26
|
+
|
27
|
+
if options[:generated]
|
28
|
+
generated = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/generated_vcl")
|
29
|
+
File.open("generated.vcl", 'w+') {|f| f.write(generated["content"]) }
|
30
|
+
abort "Generated VCL for version #{version} written to generated.vcl."
|
31
|
+
end
|
32
|
+
|
33
|
+
vcl = FastlyCTL::Fetcher.get_vcl(id, version)
|
34
|
+
snippet = FastlyCTL::Fetcher.get_snippets(id, version)
|
35
|
+
|
36
|
+
sname = service["name"]
|
37
|
+
if sname.include? "/"
|
38
|
+
sname = sname.tr("/","_")
|
39
|
+
end
|
40
|
+
|
41
|
+
folder_name = parsed ? "./" : "#{sname} - #{service["id"]}/"
|
42
|
+
Dir.mkdir(folder_name) unless (File.directory?(folder_name) || parsed)
|
43
|
+
|
44
|
+
if vcl
|
45
|
+
vcl.each do |v,k|
|
46
|
+
next if (vcl_name && vcl_name != v["name"])
|
47
|
+
|
48
|
+
filename = "#{folder_name}#{v["name"]}.vcl"
|
49
|
+
|
50
|
+
if File.exist?(filename)
|
51
|
+
unless yes?("Are you sure you want to overwrite #{filename}")
|
52
|
+
say("Skipping #{filename}")
|
53
|
+
next
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
File.open(filename, 'w+') {|f| f.write(v["content"]) }
|
58
|
+
|
59
|
+
say("VCL content for version #{version} written to #{filename}")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
if snippet
|
64
|
+
snippet.each do |s,k|
|
65
|
+
filename = "#{folder_name}#{s["name"]}.snippet"
|
66
|
+
|
67
|
+
if File.exist?(filename)
|
68
|
+
unless yes?("Are you sure you want to overwrite #{filename}")
|
69
|
+
say("Skipping #{filename}")
|
70
|
+
next
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
File.open(filename, 'w+') {|f| f.write(s["content"]) }
|
75
|
+
|
76
|
+
say("Snippet content for version #{version} written to #{filename}")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
unless vcl || snippet
|
81
|
+
say("No VCLs or snippets on this service, however a folder has been created. Create VCLs in this folder and upload.")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module FastlyCTL
|
2
|
+
class CLI < Thor
|
3
|
+
desc "login", "Logs into the app. Required before doing anything else."
|
4
|
+
def login
|
5
|
+
if FastlyCTL::Token
|
6
|
+
abort unless yes?("You already have an access token, are you sure you want to authenticate again?")
|
7
|
+
end
|
8
|
+
|
9
|
+
if yes?("Does your organization use SSO to login to Fastly? If so, type \"yes\" and create a 'global' or 'root' scoped token in your web browser. Copy the token to the file ~/.fastlyctl_token and save it.")
|
10
|
+
Launchy.open(FastlyCTL::FASTLY_APP + "/account/personal/tokens/new")
|
11
|
+
abort
|
12
|
+
end
|
13
|
+
|
14
|
+
say("Proceeding with username/password login...")
|
15
|
+
|
16
|
+
login_results = FastlyCTL::Fetcher.login
|
17
|
+
|
18
|
+
File.open(FastlyCTL::COOKIE_JAR , 'w+') {|f| f.write(JSON.dump(FastlyCTL::Cookies)) }
|
19
|
+
File.chmod(0600, FastlyCTL::COOKIE_JAR)
|
20
|
+
|
21
|
+
say("Creating root scoped token...")
|
22
|
+
|
23
|
+
if login_results[:user].include?("@fastly.com") && !login_results[:user].include?("+")
|
24
|
+
scope = "root"
|
25
|
+
else
|
26
|
+
scope = "global"
|
27
|
+
end
|
28
|
+
|
29
|
+
o = {
|
30
|
+
user: login_results[:user],
|
31
|
+
pass: login_results[:pass],
|
32
|
+
code: login_results[:code],
|
33
|
+
scope: scope,
|
34
|
+
name: "fastlyctl_token"
|
35
|
+
}
|
36
|
+
|
37
|
+
resp = FastlyCTL::Fetcher.create_token(o)
|
38
|
+
|
39
|
+
token = resp["access_token"]
|
40
|
+
token_id = resp["id"]
|
41
|
+
|
42
|
+
File.open(FastlyCTL::TOKEN_FILE , 'w+') {|f| f.write(token) }
|
43
|
+
File.chmod(0600, FastlyCTL::TOKEN_FILE)
|
44
|
+
|
45
|
+
resp = FastlyCTL::Fetcher.api_request(:get, "/tokens", { headers: {"Fastly-Key" => token}})
|
46
|
+
abort unless resp.count > 0
|
47
|
+
|
48
|
+
resp.each do |t|
|
49
|
+
next unless (t["name"] == "fastlyctl_token" && t["id"] != token_id)
|
50
|
+
|
51
|
+
if yes?("There was already a token created with the name fastlyctl_token. To avoid creating multiple tokens, should it be deleted?")
|
52
|
+
FastlyCTL::Fetcher.api_request(:delete, "/tokens/#{t["id"]}", {headers: {"Fastly-Key" => token}, expected_responses: [204]})
|
53
|
+
say("Token with id #{t["id"]} deleted.")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module FastlyCTL
|
2
|
+
class CLI < Thor
|
3
|
+
desc "open DOMAIN", "Find the service ID for a domain and open the Fastly app. You may also specify the service ID or assume the context of the directory you are in by omitting the domain."
|
4
|
+
method_option :service, :aliases => ["--s"]
|
5
|
+
def open(domain=false)
|
6
|
+
if (options[:service] && domain)
|
7
|
+
say("Both domain and service id supplied, using service id.")
|
8
|
+
domain = false
|
9
|
+
end
|
10
|
+
|
11
|
+
if options[:service]
|
12
|
+
id = options[:service]
|
13
|
+
elsif domain
|
14
|
+
id = FastlyCTL::Fetcher.domain_to_service_id(domain)
|
15
|
+
else
|
16
|
+
id = FastlyCTL::Utils.parse_directory
|
17
|
+
|
18
|
+
abort "Could not parse service id from directory. Use the --s <service> flag OR vcl open <domain>." unless id
|
19
|
+
end
|
20
|
+
|
21
|
+
FastlyCTL::Utils.open_service(id)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module FastlyCTL
|
2
|
+
class CLI < Thor
|
3
|
+
desc "purge_all", "Purge all content from a service."
|
4
|
+
method_option :service, :aliases => ["--s"]
|
5
|
+
def purge_all
|
6
|
+
parsed_id = FastlyCTL::Utils.parse_directory
|
7
|
+
|
8
|
+
id = FastlyCTL::Utils.parse_directory
|
9
|
+
|
10
|
+
abort "Could not parse service id from directory. Use --s <service> to specify, vcl download, then try again." unless (id || options[:service])
|
11
|
+
|
12
|
+
FastlyCTL::Fetcher.api_request(:post, "/service/#{id}/purge_all")
|
13
|
+
|
14
|
+
say("Purge all on #{id} completed.")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module FastlyCTL
|
2
|
+
class CLI < Thor
|
3
|
+
desc "skeleton NAME", "Create a skeleton VCL file with the current boilerplate."
|
4
|
+
method_option :service, :aliases => ["--s"]
|
5
|
+
def skeleton(name="main")
|
6
|
+
id = FastlyCTL::Utils.parse_directory unless options[:service]
|
7
|
+
id ||= options[:service]
|
8
|
+
abort "Could not parse service id from directory. Use --s <service> to specify, vcl download, then try again." unless id
|
9
|
+
|
10
|
+
filename = "#{name}.vcl"
|
11
|
+
version = FastlyCTL::Fetcher.get_active_version(id)
|
12
|
+
boilerplate = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/boilerplate")
|
13
|
+
|
14
|
+
if (File.exist?(filename))
|
15
|
+
say("#{filename} exists, please delete it if you want this command to overwrite it.")
|
16
|
+
abort
|
17
|
+
end
|
18
|
+
|
19
|
+
File.open(filename , 'w+') {|f| f.write(boilerplate) }
|
20
|
+
|
21
|
+
say("Boilerplate written to #{filename}.")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|