fastlyctl 1.0.15 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/issue_template.md +4 -0
- data/README.md +28 -4
- data/fastlyctl.gemspec +2 -1
- data/lib/fastlyctl.rb +4 -4
- data/lib/fastlyctl/cli.rb +4 -1
- data/lib/fastlyctl/clone_utils.rb +2 -2
- data/lib/fastlyctl/commands/acl.rb +1 -1
- data/lib/fastlyctl/commands/condition.rb +1 -1
- data/lib/fastlyctl/commands/copy.rb +1 -1
- data/lib/fastlyctl/commands/dictionary.rb +3 -3
- data/lib/fastlyctl/commands/login.rb +8 -10
- data/lib/fastlyctl/commands/purge_all.rb +3 -4
- data/lib/fastlyctl/commands/snippet.rb +1 -1
- data/lib/fastlyctl/commands/tls.rb +70 -0
- data/lib/fastlyctl/commands/tls/managed.rb +119 -0
- data/lib/fastlyctl/commands/token.rb +13 -11
- data/lib/fastlyctl/commands/watch.rb +3 -2
- data/lib/fastlyctl/fetcher.rb +63 -79
- data/lib/fastlyctl/subcommand_patch.rb +5 -0
- data/lib/fastlyctl/utils.rb +20 -0
- data/lib/fastlyctl/version.rb +1 -1
- metadata +33 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f7ad45346735be1f793e4975d14f31c2ab9b6df7867c2456a5e18ed1547653d
|
4
|
+
data.tar.gz: c5f00dec1bf47c871bc207526bd09df6d185ee3469c383bc18c689493372b464
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5b073b0127e3ad3d5e386f7487492fe81ad3be57420ce9dd0dd253de76374e5ccb18916ec6f00fee3e64f9da59509a0e964e13c5dd6fcfa2d759fe9c73134b1
|
7
|
+
data.tar.gz: 8565d2fcbd646b7f8a4fc194e836e3bf0520c2338ebd43497ea23e2579222abe27a3b84df3f0794108f4531b35e318baff7740a03cc4a31d908448513bd71847
|
@@ -0,0 +1,4 @@
|
|
1
|
+
---
|
2
|
+
name: Tracking issue
|
3
|
+
about: DEPRECATED: FastlyCTL is no longer maintained or supported. Many features are already available in the official Fastly CLI (https://github.com/fastly/cli), which is actively maintained and developed. Please consider migrating if possible.
|
4
|
+
---
|
data/README.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# FastlyCTL [](https://rubygems.org/gems/fastlyctl)
|
2
2
|
|
3
|
+
**DEPRECATED**: FastlyCTL is no longer maintained or supported. Many features are already available in the official [Fastly CLI](https://github.com/fastly/cli), which is actively maintained and developed. Please consider migrating if possible.
|
4
|
+
|
5
|
+
---
|
6
|
+
|
3
7
|
A CLI for managing Fastly configurations with [Fastly's API](https://docs.fastly.com/api/config).
|
4
8
|
|
5
9
|
## Dependencies
|
@@ -321,6 +325,22 @@ Flags:
|
|
321
325
|
* --d: When used with the create command, specifies that the snippet should be dynamic.
|
322
326
|
* --y: Answer yes to all prompts
|
323
327
|
|
328
|
+
### tls
|
329
|
+
|
330
|
+
#### managed
|
331
|
+
|
332
|
+
Usage:
|
333
|
+
|
334
|
+
```
|
335
|
+
fastlyctl tls managed [subcommand] [domain]
|
336
|
+
```
|
337
|
+
|
338
|
+
Available Subcommands:
|
339
|
+
* create: Create a Managed TLS Subscription for `[domain]`
|
340
|
+
* status: Print the status of all Managed TLS Subscriptions
|
341
|
+
* challenges: Print the challenges available for the verification of a certificate for `[domain]`
|
342
|
+
* delete: Delete a Managed TLS Subscription for `[domain]`
|
343
|
+
|
324
344
|
### token
|
325
345
|
|
326
346
|
Manipulate tokens for an account.
|
@@ -371,11 +391,15 @@ Flags:
|
|
371
391
|
|
372
392
|
The `--debug` flag is available on any command. Using it will cause fastlyctl to print the libcurl output for any requests it makes.
|
373
393
|
|
374
|
-
## Contributing
|
375
|
-
|
376
|
-
Submit a pull request. Don't break anything.
|
377
|
-
|
378
394
|
## License
|
379
395
|
|
380
396
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
381
397
|
|
398
|
+
## Publishing
|
399
|
+
|
400
|
+
Bump version in `/lib/fastlyctl/version.rb`.
|
401
|
+
|
402
|
+
```bash
|
403
|
+
$ gem build fastlyctl.gemspec
|
404
|
+
$ gem push fastlyctl-1.0.X.gem
|
405
|
+
```
|
data/fastlyctl.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["stephen@fastly.com"]
|
11
11
|
|
12
12
|
spec.summary = %q{CLI tool for interacting with the Fastly API}
|
13
|
-
spec.description = %q{
|
13
|
+
spec.description = %q{DEPRECATED: FastlyCTL is no longer maintained or supported. Many features are already available in the official Fastly CLI (https://github.com/fastly/cli), which is actively maintained and developed. Please consider migrating if possible.}
|
14
14
|
spec.homepage = "http://www.github.com/fastly/fastlyctl"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
@@ -34,4 +34,5 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_runtime_dependency "thor", "~> 0.19.4"
|
35
35
|
spec.add_runtime_dependency 'diffy', '~> 3.2.1'
|
36
36
|
spec.add_runtime_dependency 'launchy', '~> 2.4.3', '>= 2.4.3'
|
37
|
+
spec.add_runtime_dependency 'openssl', '~> 2.1.2', '>= 2.1.2'
|
37
38
|
end
|
data/lib/fastlyctl.rb
CHANGED
@@ -6,23 +6,23 @@ require "uri"
|
|
6
6
|
require "launchy"
|
7
7
|
require "erb"
|
8
8
|
require "pp"
|
9
|
+
require "openssl"
|
9
10
|
|
10
11
|
require "fastlyctl/version"
|
11
12
|
require "fastlyctl/fetcher"
|
12
13
|
require "fastlyctl/clone_utils"
|
13
14
|
require "fastlyctl/utils"
|
15
|
+
require "fastlyctl/subcommand_patch"
|
14
16
|
require "fastlyctl/cli"
|
15
17
|
|
16
18
|
include ERB::Util
|
17
19
|
|
18
20
|
module FastlyCTL
|
19
|
-
COOKIE_JAR = ENV['HOME'] + "/.fastlyctl_cookie_jar"
|
20
21
|
TOKEN_FILE = ENV['HOME'] + "/.fastlyctl_token"
|
21
22
|
FASTLY_API = "https://api.fastly.com"
|
22
|
-
|
23
|
+
FASTLY_RT_API = "https://rt.fastly.com"
|
23
24
|
TANGO_PATH = "/configure/services/"
|
24
25
|
|
25
|
-
Cookies = File.exist?(FastlyCTL::COOKIE_JAR) ? JSON.parse(File.read(FastlyCTL::COOKIE_JAR)) : {}
|
26
26
|
# Don't allow header splitting with the key
|
27
|
-
Token = File.exist?(FastlyCTL::TOKEN_FILE) ? File.read(FastlyCTL::TOKEN_FILE) : false
|
27
|
+
Token = File.exist?(FastlyCTL::TOKEN_FILE) ? File.read(FastlyCTL::TOKEN_FILE) : (ENV['FASTLYCLI_TOKEN'] ? ENV['FASTLYCLI_TOKEN'] : false)
|
28
28
|
end
|
data/lib/fastlyctl/cli.rb
CHANGED
@@ -17,6 +17,7 @@ require "fastlyctl/commands/acl"
|
|
17
17
|
require "fastlyctl/commands/copy"
|
18
18
|
require "fastlyctl/commands/logging"
|
19
19
|
require "fastlyctl/commands/condition"
|
20
|
+
require "fastlyctl/commands/tls"
|
20
21
|
|
21
22
|
|
22
23
|
module FastlyCTL
|
@@ -24,7 +25,9 @@ module FastlyCTL
|
|
24
25
|
class_option :debug, :desc => 'Enabled debug mode output'
|
25
26
|
|
26
27
|
def initialize(a,b,c)
|
27
|
-
|
28
|
+
warn "DEPRECATED: FastlyCTL is no longer maintained or supported. Many features are already available in the official Fastly CLI (https://github.com/fastly/cli), which is actively maintained and developed. Please consider migrating if possible."
|
29
|
+
|
30
|
+
unless File.exist?(FastlyCTL::TOKEN_FILE) || ENV['FASTLYCLI_TOKEN']
|
28
31
|
if yes?("Unable to locate API token. Would you like to login first?")
|
29
32
|
self.login
|
30
33
|
end
|
@@ -59,12 +59,12 @@ module FastlyCTL
|
|
59
59
|
|
60
60
|
if main === true
|
61
61
|
# the "main-ness" of the vcl does not get carried over during creation. must explicitly set main
|
62
|
-
FastlyCTL::Fetcher.api_request(:put, "/service/#{sid}/version/#{version}/vcl/#{
|
62
|
+
FastlyCTL::Fetcher.api_request(:put, "/service/#{sid}/version/#{version}/vcl/#{FastlyCTL::Utils.percent_encode(obj["name"])}/main")
|
63
63
|
end
|
64
64
|
|
65
65
|
if type == "director"
|
66
66
|
backends.each do |b|
|
67
|
-
FastlyCTL::Fetcher.api_request(:post, "/service/#{sid}/version/#{version}/director/#{
|
67
|
+
FastlyCTL::Fetcher.api_request(:post, "/service/#{sid}/version/#{version}/director/#{FastlyCTL::Utils.percent_encode(obj["name"])}/backend/#{b}", body: obj )
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
@@ -20,7 +20,7 @@ module FastlyCTL
|
|
20
20
|
version = FastlyCTL::Fetcher.get_writable_version(id) unless options[:version]
|
21
21
|
version ||= options[:version]
|
22
22
|
|
23
|
-
encoded_name =
|
23
|
+
encoded_name = FastlyCTL::Utils.percent_encode(name) if name
|
24
24
|
|
25
25
|
case action
|
26
26
|
when "create"
|
@@ -32,7 +32,7 @@ module FastlyCTL
|
|
32
32
|
version = FastlyCTL::Fetcher.get_writable_version(id) unless options[:version]
|
33
33
|
version ||= options[:version].to_i
|
34
34
|
|
35
|
-
encoded_name =
|
35
|
+
encoded_name = FastlyCTL::Utils.percent_encode(name) if name
|
36
36
|
|
37
37
|
case action
|
38
38
|
when "list"
|
@@ -19,7 +19,7 @@ module FastlyCTL
|
|
19
19
|
path += "/#{obj_name}" unless obj_type == "settings"
|
20
20
|
obj = FastlyCTL::Fetcher.api_request(:get, path)
|
21
21
|
|
22
|
-
encoded_name =
|
22
|
+
encoded_name = FastlyCTL::Utils.percent_encode(obj_name)
|
23
23
|
|
24
24
|
if (obj_type == "settings")
|
25
25
|
puts "Copying settings from #{id} version #{source_version} to #{target_id} version #{target_version}..."
|
@@ -20,7 +20,7 @@ module FastlyCTL
|
|
20
20
|
version = FastlyCTL::Fetcher.get_writable_version(id) unless options[:version]
|
21
21
|
version ||= options[:version]
|
22
22
|
|
23
|
-
encoded_name =
|
23
|
+
encoded_name = FastlyCTL::Utils.percent_encode(name) if name
|
24
24
|
|
25
25
|
case action
|
26
26
|
when "create"
|
@@ -49,14 +49,14 @@ module FastlyCTL
|
|
49
49
|
abort "Must specify name for dictionary" unless name
|
50
50
|
abort "Must specify key and value for dictionary item" unless (key && value)
|
51
51
|
dict = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/dictionary/#{encoded_name}")
|
52
|
-
FastlyCTL::Fetcher.api_request(:put, "/service/#{id}/dictionary/#{dict["id"]}/item/#{key}", params: { item_value: value })
|
52
|
+
FastlyCTL::Fetcher.api_request(:put, "/service/#{id}/dictionary/#{dict["id"]}/item/#{FastlyCTL::Utils.percent_encode(key)}", params: { item_value: value })
|
53
53
|
|
54
54
|
say("Dictionary item #{key} set to #{value}.")
|
55
55
|
when "remove"
|
56
56
|
abort "Must specify name for dictionary" unless name
|
57
57
|
abort "Must specify key for dictionary item" unless key
|
58
58
|
dict = FastlyCTL::Fetcher.api_request(:get, "/service/#{id}/version/#{version}/dictionary/#{encoded_name}")
|
59
|
-
FastlyCTL::Fetcher.api_request(:delete, "/service/#{id}/dictionary/#{dict["id"]}/item/#{key}")
|
59
|
+
FastlyCTL::Fetcher.api_request(:delete, "/service/#{id}/dictionary/#{dict["id"]}/item/#{FastlyCTL::Utils.percent_encode(key)}")
|
60
60
|
|
61
61
|
say("Item #{key} removed from dictionary #{name}.")
|
62
62
|
when "list_items"
|
@@ -13,23 +13,21 @@ module FastlyCTL
|
|
13
13
|
|
14
14
|
say("Proceeding with username/password login...")
|
15
15
|
|
16
|
-
|
16
|
+
username = ask("Username: ")
|
17
|
+
password = ask("Password: ", :echo => false)
|
18
|
+
say("")
|
17
19
|
|
18
|
-
|
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?("+")
|
20
|
+
if username.include?("@fastly.com") && !username.include?("+")
|
24
21
|
scope = "root"
|
25
22
|
else
|
26
23
|
scope = "global"
|
27
24
|
end
|
28
25
|
|
26
|
+
say("Creating #{scope} scope token...")
|
27
|
+
|
29
28
|
o = {
|
30
|
-
|
31
|
-
|
32
|
-
code: login_results[:code],
|
29
|
+
username: username,
|
30
|
+
password: password,
|
33
31
|
scope: scope,
|
34
32
|
name: "fastlyctl_token"
|
35
33
|
}
|
@@ -3,11 +3,10 @@ module FastlyCTL
|
|
3
3
|
desc "purge_all", "Purge all content from a service."
|
4
4
|
method_option :service, :aliases => ["--s"]
|
5
5
|
def purge_all
|
6
|
-
|
6
|
+
id = FastlyCTL::Utils.parse_directory unless options[:service]
|
7
|
+
id ||= options[:service]
|
7
8
|
|
8
|
-
id
|
9
|
-
|
10
|
-
abort "Could not parse service id from directory. Use --s <service> to specify, vcl download, then try again." unless (id || options[:service])
|
9
|
+
abort "Could not parse service id from directory. Use --s <service> to specify, vcl download, then try again." unless id
|
11
10
|
|
12
11
|
FastlyCTL::Fetcher.api_request(:post, "/service/#{id}/purge_all")
|
13
12
|
|
@@ -18,7 +18,7 @@ module FastlyCTL
|
|
18
18
|
version = FastlyCTL::Fetcher.get_writable_version(id) unless options[:version]
|
19
19
|
version ||= options[:version].to_i
|
20
20
|
|
21
|
-
encoded_name =
|
21
|
+
encoded_name = FastlyCTL::Utils.percent_encode(name) if name
|
22
22
|
|
23
23
|
filename = options.key?(:filename) ? options[:filename] : "#{name}.snippet"
|
24
24
|
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "fastlyctl/commands/tls/managed"
|
2
|
+
|
3
|
+
module FastlyCTL
|
4
|
+
class TLSSubCmd < SubCommandBase
|
5
|
+
SubcommandPrefix = "tls"
|
6
|
+
|
7
|
+
desc "managed SUBCOMMAND ...ARGS", "Interface with Fastly Managed TLS Subscriptions (lets-encrypt)"
|
8
|
+
subcommand "managed", TLSManagedSubCmd
|
9
|
+
end
|
10
|
+
|
11
|
+
class CLI < Thor
|
12
|
+
desc "tls SUBCOMMAND ...ARGS", "Interface with Fastly TLS"
|
13
|
+
subcommand "tls", TLSSubCmd
|
14
|
+
end
|
15
|
+
|
16
|
+
module TLSUtils
|
17
|
+
def self.get_tls_configs
|
18
|
+
data = FastlyCTL::Fetcher.api_request(:get,"/tls/configurations",{use_vnd:true})["data"]
|
19
|
+
if data.length == 0
|
20
|
+
thor = Thor::Shell::Basic.new
|
21
|
+
thor.say "No TLS Configurations found. You may need to upgrade to a paid account if you are using a free account."
|
22
|
+
thor.say "If you need assistance, please contact support@fastly.com."
|
23
|
+
if (thor.yes?("Would you like to open the TLS configuration page in the Fastly app?"))
|
24
|
+
FastlyCTL::Utils.open_app_path("/network/domains")
|
25
|
+
end
|
26
|
+
abort
|
27
|
+
end
|
28
|
+
|
29
|
+
return data
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.select_tls_config(configs)
|
33
|
+
thor = Thor::Shell::Basic.new
|
34
|
+
if configs.length == 1
|
35
|
+
thor.say "Using TLS Configuration #{configs[0]["id"]} - #{configs[0]["name"]}"
|
36
|
+
return configs[0]
|
37
|
+
end
|
38
|
+
|
39
|
+
loop do
|
40
|
+
i = 1
|
41
|
+
configs.each do |c|
|
42
|
+
bulk = c["attributes"]["bulk"] ? " [Platform TLS]" : ""
|
43
|
+
thor.say("[#{i}]#{bulk} #{c["id"]} - #{c["name"]}\n")
|
44
|
+
i += 1
|
45
|
+
end
|
46
|
+
|
47
|
+
selected = thor.ask("Which TLS Configuration would you like to use? Please type the number next to the configuration(s) above.").to_i
|
48
|
+
if selected > 0 && selected <= (configs.length+1)
|
49
|
+
selected -= 1
|
50
|
+
thor.say "Using TLS Configuration #{configs[selected]["id"]} - #{configs[selected]["name"]}"
|
51
|
+
return configs[selected]
|
52
|
+
end
|
53
|
+
|
54
|
+
thor.say "#{selcted} is in invalid selection. Please try again."
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.print_challenges(tls_authorization)
|
59
|
+
thor = Thor::Shell::Basic.new
|
60
|
+
thor.say "\nIn order to verify your ownership of the domain, the Certificate Authority provided the following challenges:"
|
61
|
+
tls_authorization["attributes"]["challenges"].each do |challenge|
|
62
|
+
thor.say("\n#{challenge["type"]}: Create #{challenge["record_type"]} record for #{challenge["record_name"]} with value(s) of:")
|
63
|
+
challenge["values"].each do |val|
|
64
|
+
thor.say(" #{val}")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
thor.say("\nNote: If you don't want to move all traffic to Fastly right now, use the managed-dns option. The other options result in traffic for that hostname being directed to Fastly.")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module FastlyCTL
|
2
|
+
class TLSManagedSubCmd < SubCommandBase
|
3
|
+
SubcommandPrefix = "tls managed"
|
4
|
+
DomainRegex = /(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/
|
5
|
+
|
6
|
+
desc "create <domain>", "Create a Fastly Managed TLS Subscription for [domain]. A Certificate will be requested from lets-encrypt once you satisfy one of the challenges. You can learn more about the challenge types here: https://letsencrypt.org/docs/challenge-types/ and Fastly's API documentation here: https://docs.fastly.com/api/tls-subscriptions."
|
7
|
+
def create(domain)
|
8
|
+
abort "Must specify valid domain name" unless domain =~ DomainRegex
|
9
|
+
|
10
|
+
tls_configs = FastlyCTL::TLSUtils.get_tls_configs
|
11
|
+
tls_config = FastlyCTL::TLSUtils.select_tls_config(tls_configs)
|
12
|
+
|
13
|
+
payload = {
|
14
|
+
data: {
|
15
|
+
type: "tls_subscription",
|
16
|
+
attributes: {
|
17
|
+
certificate_authority: "lets-encrypt"
|
18
|
+
},
|
19
|
+
relationships: {
|
20
|
+
tls_domains: {
|
21
|
+
data: [
|
22
|
+
{
|
23
|
+
type: "tls_domain",
|
24
|
+
id: domain
|
25
|
+
}
|
26
|
+
]
|
27
|
+
},
|
28
|
+
tls_configuration: {
|
29
|
+
data: {
|
30
|
+
type: "tls_configuration",
|
31
|
+
id: tls_config["id"]
|
32
|
+
}
|
33
|
+
}
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
subscription = FastlyCTL::Fetcher.api_request(:post,"/tls/subscriptions", {
|
39
|
+
body: payload.to_json,
|
40
|
+
use_vnd: true
|
41
|
+
})
|
42
|
+
|
43
|
+
tls_authorization = FastlyCTL::Utils.filter_vnd(subscription["included"],"tls_authorization")
|
44
|
+
abort "Unable to fetch TLS Authorization for the domain." unless tls_authorization.length > 0
|
45
|
+
FastlyCTL::TLSUtils.print_challenges(tls_authorization[0])
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "status", "Print status of Fastly Managed TLS Subscriptions"
|
49
|
+
def status
|
50
|
+
subscriptions = FastlyCTL::Fetcher.api_request(:get,"/tls/subscriptions", {
|
51
|
+
use_vnd: true
|
52
|
+
})
|
53
|
+
|
54
|
+
if subscriptions["data"].length == 0
|
55
|
+
say("No Fastly Managed TLS Subscriptions found.")
|
56
|
+
abort
|
57
|
+
end
|
58
|
+
|
59
|
+
subscriptions["data"].each do |subscription|
|
60
|
+
output = subscription["relationships"]["tls_domains"]["data"][0]["id"]
|
61
|
+
output += " - " + subscription["attributes"]["certificate_authority"]
|
62
|
+
output += " - " + subscription["attributes"]["state"]
|
63
|
+
say(output)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
desc "challenges", "Print challenges available for a domain's verification."
|
68
|
+
def challenges(domain)
|
69
|
+
abort "Must specify valid domain name" unless domain =~ DomainRegex
|
70
|
+
|
71
|
+
domains = FastlyCTL::Fetcher.api_request(:get,"/tls/domains?include=tls_subscriptions.tls_authorizations", {
|
72
|
+
use_vnd: true
|
73
|
+
})
|
74
|
+
|
75
|
+
tls_authorizations = FastlyCTL::Utils.filter_vnd(domains["included"],"tls_authorization")
|
76
|
+
|
77
|
+
tls_authorizations.each do |tls_authorization|
|
78
|
+
tls_authorization["attributes"]["challenges"].each do |challenge|
|
79
|
+
if challenge["record_name"] == domain
|
80
|
+
FastlyCTL::TLSUtils.print_challenges(tls_authorization)
|
81
|
+
abort
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
say("#{domain} not found in domain list.")
|
87
|
+
end
|
88
|
+
|
89
|
+
desc "delete", "Delete a Fastly Managed TLS Subscription"
|
90
|
+
def delete(domain)
|
91
|
+
abort "Must specify valid domain name" unless domain =~ DomainRegex
|
92
|
+
|
93
|
+
activation = FastlyCTL::Fetcher.api_request(:get,"/tls/activations?filter[tls_domain.id]=#{domain}", {use_vnd: true})
|
94
|
+
|
95
|
+
if activation["data"].length >= 1
|
96
|
+
say("TLS is currently active for #{domain}. If you proceed, Fastly will no longer be able to serve TLS requests to clients for #{domain}.")
|
97
|
+
answer = ask("Please type the name of the domain to confirm deactivation and deletion of the Fastly Managed TLS subscription: ")
|
98
|
+
abort "Supplied domain does not match the domain requested for deletion--aborting." unless answer == domain
|
99
|
+
|
100
|
+
FastlyCTL::Fetcher.api_request(:delete,"/tls/activations/#{activation["data"][0]["id"]}",{use_vnd:true})
|
101
|
+
end
|
102
|
+
|
103
|
+
subscriptions = FastlyCTL::Fetcher.api_request(:get,"/tls/subscriptions", {
|
104
|
+
use_vnd: true
|
105
|
+
})
|
106
|
+
|
107
|
+
subscriptions["data"].each do |subscription|
|
108
|
+
next unless subscription["relationships"]["tls_domains"]["data"][0]["id"] == domain
|
109
|
+
|
110
|
+
FastlyCTL::Fetcher.api_request(:delete,"/tls/subscriptions/#{subscription["id"]}",{use_vnd:true})
|
111
|
+
|
112
|
+
say("TLS Subscription for #{domain} has been deleted.")
|
113
|
+
abort
|
114
|
+
end
|
115
|
+
|
116
|
+
say("No TLS Subscription found for #{domain}...")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -20,25 +20,27 @@ module FastlyCTL
|
|
20
20
|
scope = options[:scope]
|
21
21
|
scope ||= "global"
|
22
22
|
|
23
|
-
say("You must
|
23
|
+
say("You must authenticate again to create tokens.")
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
username = ask("Username: ")
|
26
|
+
password = ask("Password: ", :echo => false)
|
27
|
+
say("")
|
28
28
|
|
29
|
+
name = ask("What would you like to name your token? (enter here):")
|
29
30
|
o = {
|
30
|
-
|
31
|
-
|
32
|
-
code: login_results[:code],
|
31
|
+
username: username,
|
32
|
+
password: password,
|
33
33
|
scope: scope,
|
34
|
-
name: name
|
35
|
-
}
|
36
|
-
|
37
|
-
o[:services] = options[:services].split(",") if options[:services]
|
34
|
+
name: name || "fastlyctl_token"
|
35
|
+
}.compare_by_identity
|
38
36
|
|
37
|
+
options[:services].split(",").each do |v|
|
38
|
+
o["services[]"] = v
|
39
|
+
end if options[:services]
|
39
40
|
o[:customer] = options[:customer] if options[:customer]
|
40
41
|
|
41
42
|
resp = FastlyCTL::Fetcher.create_token(o)
|
43
|
+
say("token: #{resp["access_token"]}")
|
42
44
|
|
43
45
|
when "delete"
|
44
46
|
id = ask("What is the ID of the token you'd like to delete?")
|
@@ -13,7 +13,7 @@ module FastlyCTL
|
|
13
13
|
pop = pop.upcase if pop
|
14
14
|
|
15
15
|
while true
|
16
|
-
data = FastlyCTL::Fetcher.api_request(:get,"/rt/v1/channel/#{service}/ts/#{ts ? ts : 'h/limit/120'}", :endpoint => :
|
16
|
+
data = FastlyCTL::Fetcher.api_request(:get,"/rt/v1/channel/#{service}/ts/#{ts ? ts : 'h/limit/120'}", :endpoint => :rt)
|
17
17
|
|
18
18
|
unless data["Data"].length > 0
|
19
19
|
say("No data to display!")
|
@@ -33,7 +33,8 @@ module FastlyCTL
|
|
33
33
|
# gbps
|
34
34
|
uncacheable = agg["pass"] + agg["synth"] + agg["errors"]
|
35
35
|
bw = ((agg["resp_header_bytes"] + agg["resp_body_bytes"]).to_f * 8.0) / 1000000000.0
|
36
|
-
|
36
|
+
shield = agg["shield"] || 0
|
37
|
+
hit_rate = (1.0 - ((agg["miss"] - shield).to_f / ((agg["requests"] - uncacheable).to_f))) * 100.0
|
37
38
|
passes = agg["pass"]
|
38
39
|
miss_time = agg["miss"] > 0 ? ((agg["miss_time"] / agg["miss"]) * 1000).round(0) : 0
|
39
40
|
synth = agg["synth"]
|
data/lib/fastlyctl/fetcher.rb
CHANGED
@@ -5,28 +5,32 @@ module FastlyCTL
|
|
5
5
|
options[:params] ||= {}
|
6
6
|
options[:headers] ||= {}
|
7
7
|
options[:body] ||= nil
|
8
|
-
options[:
|
8
|
+
options[:disable_token] ||= false
|
9
9
|
options[:expected_responses] ||= [200]
|
10
|
+
options[:use_vnd] ||= false
|
10
11
|
|
11
12
|
headers = {"Accept" => "application/json", "Connection" => "close", "User-Agent" => "FastlyCTL: https://github.com/fastly/fastlyctl"}
|
12
13
|
|
13
14
|
if options[:endpoint] == :app
|
14
15
|
headers["Referer"] = FastlyCTL::FASTLY_APP
|
15
|
-
headers["X-CSRF-Token"] = FastlyCTL::Cookies["fastly.csrf"] if FastlyCTL::Cookies["fastly.csrf"]
|
16
16
|
headers["Fastly-API-Request"] = "true"
|
17
17
|
end
|
18
18
|
|
19
|
-
if FastlyCTL::Token && !options[:
|
19
|
+
if FastlyCTL::Token && !options[:disable_token]
|
20
20
|
headers["Fastly-Key"] = FastlyCTL::Token
|
21
|
-
else
|
22
|
-
headers["Cookie"] = "" if FastlyCTL::Cookies.length > 0
|
23
|
-
FastlyCTL::Cookies.each do |k,v|
|
24
|
-
headers["Cookie"] << "#{k}=#{v};"
|
25
|
-
end
|
26
21
|
end
|
27
22
|
|
28
23
|
headers["Content-Type"] = "application/x-www-form-urlencoded" if (method == :post || method == :put)
|
29
24
|
|
25
|
+
if options[:use_vnd]
|
26
|
+
headers["Accept"] = "application/vnd.api+json"
|
27
|
+
|
28
|
+
if (method == :post || method == :put)
|
29
|
+
headers["Content-Type"] = "application/vnd.api+json"
|
30
|
+
end
|
31
|
+
options[:expected_responses].push(*[201,202,203,204])
|
32
|
+
end
|
33
|
+
|
30
34
|
headers.merge!(options[:headers]) if options[:headers].count > 0
|
31
35
|
|
32
36
|
# dont allow header splitting on anything
|
@@ -34,7 +38,7 @@ module FastlyCTL
|
|
34
38
|
headers[k] = v.gsub(/\r|\n/,'')
|
35
39
|
end
|
36
40
|
|
37
|
-
url = "#{options[:endpoint] == :api ? FastlyCTL::FASTLY_API : FastlyCTL::
|
41
|
+
url = "#{options[:endpoint] == :api ? FastlyCTL::FASTLY_API : FastlyCTL::FASTLY_RT_API}#{path}"
|
38
42
|
|
39
43
|
response = Typhoeus::Request.new(
|
40
44
|
url,
|
@@ -44,35 +48,43 @@ module FastlyCTL
|
|
44
48
|
body: options[:body]
|
45
49
|
).run
|
46
50
|
|
47
|
-
if options[:expected_responses].include?(response.response_code)
|
48
|
-
if response.headers["Set-Cookie"]
|
49
|
-
response.headers["Set-Cookie"] = [response.headers["Set-Cookie"]] if response.headers["Set-Cookie"].is_a? String
|
50
|
-
response.headers["Set-Cookie"].each do |c|
|
51
|
-
name, value = c.match(/^([^=]*)=([^;]*).*/i).captures
|
52
|
-
FastlyCTL::Cookies[name] = value
|
53
|
-
end
|
54
|
-
end
|
55
|
-
else
|
51
|
+
if !options[:expected_responses].include?(response.response_code)
|
56
52
|
case response.response_code
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
53
|
+
when 400
|
54
|
+
error = "400: Bad API request--something was wrong with the request made by FastlyCTL."
|
55
|
+
when 403
|
56
|
+
error = "403: Access Denied by API. Run login command to authenticate."
|
57
|
+
when 404
|
58
|
+
error = "404: Service does not exist or bad path requested."
|
59
|
+
when 503
|
60
|
+
error = "503: Error from Fastly API--see details below."
|
61
|
+
when 0
|
62
|
+
error = "0: Network connection error occurred."
|
63
|
+
else
|
64
|
+
error = "API responded with status #{response.response_code}."
|
67
65
|
end
|
68
66
|
|
69
67
|
error += " Method: #{method.to_s.upcase}, Path: #{path}\n"
|
70
|
-
|
68
|
+
|
69
|
+
if (options[:use_vnd])
|
70
|
+
begin
|
71
|
+
error_resp = JSON.parse(response.response_body)
|
72
|
+
rescue JSON::ParserError
|
73
|
+
error_resp = {"errors" => [{"title" => "Error parsing response JSON","details" => "No further information available. Please file a github issue at https://github.com/fastly/fastlyctl"}]}
|
74
|
+
end
|
75
|
+
|
76
|
+
error_resp["errors"].each do |e|
|
77
|
+
next unless e.key?("title") && e.key?("detail")
|
78
|
+
error += e["title"] + " --- " + e["detail"] + "\n"
|
79
|
+
end
|
80
|
+
else
|
81
|
+
error += "Message from API: #{response.response_body}"
|
82
|
+
end
|
71
83
|
|
72
84
|
abort error
|
73
85
|
end
|
74
86
|
|
75
|
-
return response.response_body
|
87
|
+
return response.response_body unless (response.headers["Content-Type"] =~ /json$/)
|
76
88
|
|
77
89
|
if response.response_body.length > 1
|
78
90
|
begin
|
@@ -161,7 +173,7 @@ module FastlyCTL
|
|
161
173
|
end
|
162
174
|
|
163
175
|
def self.upload_snippet(service,version,content,name)
|
164
|
-
return FastlyCTL::Fetcher.api_request(:put, "/service/#{service}/version/#{version}/snippet/#{name}", {:endpoint => :api, body: {
|
176
|
+
return FastlyCTL::Fetcher.api_request(:put, "/service/#{service}/version/#{version}/snippet/#{FastlyCTL::Utils.percent_encode(name)}", {:endpoint => :api, body: {
|
165
177
|
content: content
|
166
178
|
}
|
167
179
|
})
|
@@ -178,7 +190,7 @@ module FastlyCTL
|
|
178
190
|
end
|
179
191
|
end
|
180
192
|
|
181
|
-
response = FastlyCTL::Fetcher.api_request(:put, "/service/#{service}/version/#{version}/vcl/#{name}", {:endpoint => :api, body: params, expected_responses: [200,404]})
|
193
|
+
response = FastlyCTL::Fetcher.api_request(:put, "/service/#{service}/version/#{version}/vcl/#{FastlyCTL::Utils.percent_encode(name)}", {:endpoint => :api, body: params, expected_responses: [200,404]})
|
182
194
|
|
183
195
|
# The VCL got deleted so recreate it.
|
184
196
|
if response["msg"] == "Record not found"
|
@@ -186,61 +198,33 @@ module FastlyCTL
|
|
186
198
|
end
|
187
199
|
end
|
188
200
|
|
189
|
-
def self.login
|
190
|
-
thor = Thor::Shell::Basic.new
|
191
|
-
|
192
|
-
user = thor.ask("Username: ")
|
193
|
-
pass = thor.ask("Password: ", :echo => false)
|
194
|
-
|
195
|
-
resp = FastlyCTL::Fetcher.api_request(:post, "/login", { :endpoint => :app, params: { user: user, password: pass}})
|
196
|
-
|
197
|
-
if resp["needs_two_factor_auth"]
|
198
|
-
two_factor = true
|
199
|
-
|
200
|
-
thor.say("\nTwo factor auth enabled on account, second factor needed.")
|
201
|
-
code = thor.ask('Please enter verification code:', echo: false)
|
202
|
-
|
203
|
-
resp = FastlyCTL::Fetcher.api_request(:post, "/two_factor_auth/verify", {force_session: true, :endpoint => :app, params: { token: code }} )
|
204
|
-
else
|
205
|
-
thor.say("\nTwo factor auth is NOT enabled. You should go do that immediately.")
|
206
|
-
end
|
207
|
-
|
208
|
-
thor.say("Login successful!")
|
209
|
-
|
210
|
-
return { user: user, pass: pass, two_factor: two_factor, code: code }
|
211
|
-
end
|
212
|
-
|
213
201
|
def self.create_token(options)
|
214
202
|
thor = Thor::Shell::Basic.new
|
215
203
|
|
216
204
|
headers = {}
|
217
|
-
|
218
|
-
|
219
|
-
FastlyCTL::Fetcher.api_request(:post, "/sudo", {
|
220
|
-
force_session: true,
|
205
|
+
resp = FastlyCTL::Fetcher.api_request(:post, "/tokens", {
|
206
|
+
disable_token: true,
|
221
207
|
endpoint: :api,
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
},
|
226
|
-
headers: headers
|
208
|
+
body: options,
|
209
|
+
headers: headers,
|
210
|
+
expected_responses: [200,400]
|
227
211
|
})
|
228
212
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
user: options[:user],
|
233
|
-
password: options[:pass]
|
234
|
-
}
|
235
|
-
|
236
|
-
params[:services] = options[:services] if options[:services]
|
213
|
+
if resp.has_key?("msg") && resp["msg"] == "2fa.verify"
|
214
|
+
thor.say("\nTwo factor auth enabled on account, second factor needed.")
|
215
|
+
code = thor.ask('Please enter verification code:', echo: false)
|
237
216
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
217
|
+
headers = {}
|
218
|
+
headers["Fastly-OTP"] = code
|
219
|
+
resp = FastlyCTL::Fetcher.api_request(:post, "/tokens", {
|
220
|
+
disable_token: true,
|
221
|
+
endpoint: :api,
|
222
|
+
body: options,
|
223
|
+
headers: headers
|
224
|
+
})
|
225
|
+
elsif resp.has_key?("msg")
|
226
|
+
abort "ERROR: #{resp}"
|
227
|
+
end
|
244
228
|
|
245
229
|
thor.say("\n#{resp["id"]} created.")
|
246
230
|
|
data/lib/fastlyctl/utils.rb
CHANGED
@@ -4,6 +4,10 @@ module FastlyCTL
|
|
4
4
|
Launchy.open(FastlyCTL::FASTLY_APP + FastlyCTL::TANGO_PATH + id)
|
5
5
|
end
|
6
6
|
|
7
|
+
def self.open_app_path(path)
|
8
|
+
Launchy.open(FastlyCTL::FASTLY_APP + path)
|
9
|
+
end
|
10
|
+
|
7
11
|
def self.parse_directory(path=false)
|
8
12
|
directory = Dir.pwd unless path
|
9
13
|
directory = path if path
|
@@ -68,5 +72,21 @@ module FastlyCTL
|
|
68
72
|
|
69
73
|
return diff
|
70
74
|
end
|
75
|
+
|
76
|
+
def self.percent_encode(string)
|
77
|
+
# CGI.escape replace whitespace to "+" which is "%20" in a percent-encoding manner
|
78
|
+
CGI.escape(string).gsub('+', '%20')
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.filter_vnd(haystack,needle)
|
82
|
+
results = []
|
83
|
+
haystack.each do |i|
|
84
|
+
next unless i["type"] == needle
|
85
|
+
|
86
|
+
results.push(i)
|
87
|
+
end
|
88
|
+
|
89
|
+
return results
|
90
|
+
end
|
71
91
|
end
|
72
92
|
end
|
data/lib/fastlyctl/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastlyctl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Basile
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-06-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: typhoeus
|
@@ -72,7 +72,29 @@ dependencies:
|
|
72
72
|
- - "~>"
|
73
73
|
- !ruby/object:Gem::Version
|
74
74
|
version: 2.4.3
|
75
|
-
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: openssl
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 2.1.2
|
82
|
+
- - "~>"
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 2.1.2
|
85
|
+
type: :runtime
|
86
|
+
prerelease: false
|
87
|
+
version_requirements: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: 2.1.2
|
92
|
+
- - "~>"
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 2.1.2
|
95
|
+
description: 'DEPRECATED: FastlyCTL is no longer maintained or supported. Many features
|
96
|
+
are already available in the official Fastly CLI (https://github.com/fastly/cli),
|
97
|
+
which is actively maintained and developed. Please consider migrating if possible.'
|
76
98
|
email:
|
77
99
|
- stephen@fastly.com
|
78
100
|
executables:
|
@@ -80,6 +102,7 @@ executables:
|
|
80
102
|
extensions: []
|
81
103
|
extra_rdoc_files: []
|
82
104
|
files:
|
105
|
+
- ".github/issue_template.md"
|
83
106
|
- ".gitignore"
|
84
107
|
- Gemfile
|
85
108
|
- Gemfile.lock
|
@@ -111,10 +134,13 @@ files:
|
|
111
134
|
- lib/fastlyctl/commands/purge_all.rb
|
112
135
|
- lib/fastlyctl/commands/skeleton.rb
|
113
136
|
- lib/fastlyctl/commands/snippet.rb
|
137
|
+
- lib/fastlyctl/commands/tls.rb
|
138
|
+
- lib/fastlyctl/commands/tls/managed.rb
|
114
139
|
- lib/fastlyctl/commands/token.rb
|
115
140
|
- lib/fastlyctl/commands/upload.rb
|
116
141
|
- lib/fastlyctl/commands/watch.rb
|
117
142
|
- lib/fastlyctl/fetcher.rb
|
143
|
+
- lib/fastlyctl/subcommand_patch.rb
|
118
144
|
- lib/fastlyctl/utils.rb
|
119
145
|
- lib/fastlyctl/version.rb
|
120
146
|
homepage: http://www.github.com/fastly/fastlyctl
|
@@ -122,7 +148,7 @@ licenses:
|
|
122
148
|
- MIT
|
123
149
|
metadata:
|
124
150
|
allowed_push_host: https://rubygems.org/
|
125
|
-
post_install_message:
|
151
|
+
post_install_message:
|
126
152
|
rdoc_options: []
|
127
153
|
require_paths:
|
128
154
|
- lib
|
@@ -137,8 +163,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
137
163
|
- !ruby/object:Gem::Version
|
138
164
|
version: '0'
|
139
165
|
requirements: []
|
140
|
-
rubygems_version: 3.0.
|
141
|
-
signing_key:
|
166
|
+
rubygems_version: 3.0.8
|
167
|
+
signing_key:
|
142
168
|
specification_version: 4
|
143
169
|
summary: CLI tool for interacting with the Fastly API
|
144
170
|
test_files: []
|