dynomite 1.2.7 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +17 -2
- data/CHANGELOG.md +18 -0
- data/Gemfile +1 -5
- data/LICENSE.txt +22 -0
- data/README.md +6 -190
- data/Rakefile +13 -1
- data/dynomite.gemspec +9 -2
- data/exe/dynomite +14 -0
- data/lib/dynomite/associations/association.rb +126 -0
- data/lib/dynomite/associations/belongs_to.rb +35 -0
- data/lib/dynomite/associations/has_and_belongs_to_many.rb +19 -0
- data/lib/dynomite/associations/has_many.rb +19 -0
- data/lib/dynomite/associations/has_one.rb +19 -0
- data/lib/dynomite/associations/many_association.rb +257 -0
- data/lib/dynomite/associations/single_association.rb +157 -0
- data/lib/dynomite/associations.rb +248 -0
- data/lib/dynomite/autoloader.rb +25 -0
- data/lib/dynomite/cli.rb +48 -0
- data/lib/dynomite/client.rb +118 -0
- data/lib/dynomite/command.rb +89 -0
- data/lib/dynomite/completer/script.rb +6 -0
- data/lib/dynomite/completer/script.sh +10 -0
- data/lib/dynomite/completer.rb +159 -0
- data/lib/dynomite/config.rb +39 -0
- data/lib/dynomite/core.rb +18 -19
- data/lib/dynomite/engine.rb +45 -0
- data/lib/dynomite/erb.rb +5 -3
- data/lib/dynomite/error.rb +12 -0
- data/lib/dynomite/help/completion.md +20 -0
- data/lib/dynomite/help/completion_script.md +3 -0
- data/lib/dynomite/help/migrate.md +3 -0
- data/lib/dynomite/help.rb +9 -0
- data/lib/dynomite/install.rb +4 -0
- data/lib/dynomite/item/abstract.rb +15 -0
- data/lib/dynomite/item/components.rb +33 -0
- data/lib/dynomite/item/dsl.rb +101 -0
- data/lib/dynomite/item/id.rb +41 -0
- data/lib/dynomite/item/indexes/finder.rb +58 -0
- data/lib/dynomite/item/indexes/index.rb +21 -0
- data/lib/dynomite/item/indexes/primary_index.rb +18 -0
- data/lib/dynomite/item/indexes.rb +25 -0
- data/lib/dynomite/item/locking.rb +53 -0
- data/lib/dynomite/item/magic_fields.rb +66 -0
- data/lib/dynomite/item/primary_key.rb +85 -0
- data/lib/dynomite/item/query/delegates.rb +28 -0
- data/lib/dynomite/item/query/params/base.rb +42 -0
- data/lib/dynomite/item/query/params/expression_attribute.rb +79 -0
- data/lib/dynomite/item/query/params/filter.rb +41 -0
- data/lib/dynomite/item/query/params/function/attribute_exists.rb +21 -0
- data/lib/dynomite/item/query/params/function/attribute_type.rb +30 -0
- data/lib/dynomite/item/query/params/function/base.rb +33 -0
- data/lib/dynomite/item/query/params/function/begins_with.rb +32 -0
- data/lib/dynomite/item/query/params/function/contains.rb +7 -0
- data/lib/dynomite/item/query/params/function/size_fn.rb +37 -0
- data/lib/dynomite/item/query/params/helpers.rb +94 -0
- data/lib/dynomite/item/query/params/key_condition.rb +34 -0
- data/lib/dynomite/item/query/params.rb +115 -0
- data/lib/dynomite/item/query/partiql/executer.rb +72 -0
- data/lib/dynomite/item/query/partiql.rb +67 -0
- data/lib/dynomite/item/query/relation/chain.rb +125 -0
- data/lib/dynomite/item/query/relation/comparision_expression.rb +21 -0
- data/lib/dynomite/item/query/relation/comparision_map.rb +19 -0
- data/lib/dynomite/item/query/relation/delete.rb +38 -0
- data/lib/dynomite/item/query/relation/ids.rb +21 -0
- data/lib/dynomite/item/query/relation/math.rb +19 -0
- data/lib/dynomite/item/query/relation/where_field.rb +32 -0
- data/lib/dynomite/item/query/relation/where_group.rb +78 -0
- data/lib/dynomite/item/query/relation.rb +127 -0
- data/lib/dynomite/item/query.rb +7 -0
- data/lib/dynomite/item/read/find.rb +196 -0
- data/lib/dynomite/item/read/find_with_event.rb +42 -0
- data/lib/dynomite/item/read.rb +90 -0
- data/lib/dynomite/item/sti.rb +43 -0
- data/lib/dynomite/item/table_namespace.rb +43 -0
- data/lib/dynomite/item/typecaster.rb +106 -0
- data/lib/dynomite/item/waiter_methods.rb +18 -0
- data/lib/dynomite/item/write/base.rb +15 -0
- data/lib/dynomite/item/write/delete_item.rb +14 -0
- data/lib/dynomite/item/write/put_item.rb +99 -0
- data/lib/dynomite/item/write/update_item.rb +73 -0
- data/lib/dynomite/item/write.rb +204 -0
- data/lib/dynomite/item.rb +113 -286
- data/lib/dynomite/migration/dsl/accessor.rb +19 -0
- data/lib/dynomite/migration/dsl/index/base.rb +42 -0
- data/lib/dynomite/migration/dsl/index/gsi.rb +59 -0
- data/lib/dynomite/migration/dsl/index/lsi.rb +27 -0
- data/lib/dynomite/migration/dsl/index.rb +72 -0
- data/lib/dynomite/migration/dsl/primary_key.rb +62 -0
- data/lib/dynomite/migration/dsl/provisioned_throughput.rb +38 -0
- data/lib/dynomite/migration/dsl.rb +89 -142
- data/lib/dynomite/migration/file_info.rb +28 -0
- data/lib/dynomite/migration/generator.rb +30 -16
- data/lib/dynomite/migration/helpers.rb +7 -0
- data/lib/dynomite/migration/internal/migrate/create_schema_migrations.rb +17 -0
- data/lib/dynomite/migration/internal/models/schema_migration.rb +6 -0
- data/lib/dynomite/migration/runner.rb +178 -0
- data/lib/dynomite/migration/templates/create_table.rb +7 -23
- data/lib/dynomite/migration/templates/delete_table.rb +7 -0
- data/lib/dynomite/migration/templates/update_table.rb +3 -18
- data/lib/dynomite/migration.rb +53 -10
- data/lib/dynomite/reserved_words.rb +13 -3
- data/lib/dynomite/seed.rb +12 -0
- data/lib/dynomite/types.rb +22 -0
- data/lib/dynomite/version.rb +1 -1
- data/lib/dynomite/waiter.rb +40 -0
- data/lib/dynomite.rb +11 -17
- data/lib/generators/application_item/application_item_generator.rb +30 -0
- data/lib/generators/application_item/templates/application_item.rb.tt +4 -0
- data/lib/jets/commands/dynamodb_command.rb +29 -0
- data/lib/jets/commands/help/generate.md +33 -0
- data/lib/jets/commands/help/migrate.md +3 -0
- metadata +201 -17
- data/docs/migrations/long-example.rb +0 -127
- data/docs/migrations/short-example.rb +0 -40
- data/lib/dynomite/db_config.rb +0 -121
- data/lib/dynomite/errors.rb +0 -15
- data/lib/dynomite/log.rb +0 -15
- data/lib/dynomite/migration/common.rb +0 -86
- data/lib/dynomite/migration/dsl/base_secondary_index.rb +0 -73
- data/lib/dynomite/migration/dsl/global_secondary_index.rb +0 -4
- data/lib/dynomite/migration/dsl/local_secondary_index.rb +0 -8
- data/lib/dynomite/migration/executor.rb +0 -38
data/lib/dynomite/cli.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Dynomite
|
|
2
|
+
class CLI < Command
|
|
3
|
+
desc "migrate", "Run migrations"
|
|
4
|
+
long_desc Help.text(:migrate)
|
|
5
|
+
def migrate
|
|
6
|
+
Migration::Runner.new(options).run
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
desc "generate [name]", "Creates a migration for a DynamoDB table"
|
|
10
|
+
long_desc Help.text('generate')
|
|
11
|
+
option :action, desc: "create_table or update_table. Defaults to convention based on the name of the migration."
|
|
12
|
+
option :partition_key, default: "id:string", desc: "table's partition key"
|
|
13
|
+
option :sort_key, default: nil, desc: "table's sort key"
|
|
14
|
+
option :table_name, desc: "override the the conventional table name"
|
|
15
|
+
def generate(name)
|
|
16
|
+
Migration::Generator.new(name, options).generate
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
desc "seed", "Seed data"
|
|
20
|
+
long_desc Help.text(:seed)
|
|
21
|
+
def seed
|
|
22
|
+
Seed.new.run
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "install", "Install Dynomite"
|
|
26
|
+
long_desc Help.text(:install)
|
|
27
|
+
def install
|
|
28
|
+
Install.new.run
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
desc "completion *PARAMS", "Prints words for auto-completion."
|
|
32
|
+
long_desc Help.text(:completion)
|
|
33
|
+
def completion(*params)
|
|
34
|
+
Completer.new(CLI, *params).run
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
desc "completion_script", "Generates a script that can be eval to setup auto-completion."
|
|
38
|
+
long_desc Help.text(:completion_script)
|
|
39
|
+
def completion_script
|
|
40
|
+
Completer::Script.generate
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
desc "version", "prints version"
|
|
44
|
+
def version
|
|
45
|
+
puts VERSION
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require "aws-sdk-dynamodb"
|
|
2
|
+
require "erb"
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require "yaml"
|
|
6
|
+
|
|
7
|
+
module Dynomite
|
|
8
|
+
module Client
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
delegate :client, :desc_table, :show_request, :show_response,
|
|
11
|
+
:warn_scan, :log_debug, :logger,
|
|
12
|
+
to: :class
|
|
13
|
+
|
|
14
|
+
class_methods do
|
|
15
|
+
extend Memoist
|
|
16
|
+
|
|
17
|
+
@@client = nil
|
|
18
|
+
def client
|
|
19
|
+
return @@client if @@client
|
|
20
|
+
|
|
21
|
+
endpoint = Dynomite.config.endpoint
|
|
22
|
+
check_dynamodb_local!(endpoint)
|
|
23
|
+
|
|
24
|
+
# Normally, do not set the endpoint to use the current configured region.
|
|
25
|
+
# Probably want to stay in the same region anyway for db connections.
|
|
26
|
+
#
|
|
27
|
+
# List of regional endpoints: https://docs.aws.amazon.com/general/latest/gr/rande.html#ddb_region
|
|
28
|
+
# Example:
|
|
29
|
+
# endpoint: https://dynamodb.us-east-1.amazonaws.com
|
|
30
|
+
options = endpoint ? { endpoint: endpoint } : {}
|
|
31
|
+
log_level = Dynomite.config.log_level.to_s
|
|
32
|
+
# https://aws.amazon.com/blogs/developer/logging-requests/
|
|
33
|
+
# https://github.com/aws/aws-sdk-ruby/blob/249a0b34d0014dda50ecc8a09cd58e75e64b3ea4/gems/aws-sdk-core/lib/aws-sdk-core/log/formatter.rb#L212
|
|
34
|
+
if log_level == "debug"
|
|
35
|
+
options[:logger] = Dynomite.logger
|
|
36
|
+
options[:log_formatter] = Aws::Log::Formatter.colored # default short colored
|
|
37
|
+
end
|
|
38
|
+
@@client ||= Aws::DynamoDB::Client.new(options)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# useful for specs
|
|
42
|
+
def client=(client)
|
|
43
|
+
@@client = client
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def desc_table(table_name)
|
|
47
|
+
client.describe_table(table_name: table_name).table
|
|
48
|
+
end
|
|
49
|
+
memoize :desc_table
|
|
50
|
+
|
|
51
|
+
def show_request(params)
|
|
52
|
+
logger.info("REQUEST: #{JSON.dump(params)}")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def show_response(resp)
|
|
56
|
+
logger.info("RESPONSE: #{JSON.dump(resp)}")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# When endoint has been configured to point at dynamodb local: localhost:8000
|
|
60
|
+
# check if port 8000 is listening and timeout quickly. Or else it takes a
|
|
61
|
+
# for DynamoDB local to time out, about 10 seconds...
|
|
62
|
+
# This wastes less of the users time.
|
|
63
|
+
def check_dynamodb_local!(endpoint)
|
|
64
|
+
return unless endpoint && endpoint.include?("8000")
|
|
65
|
+
|
|
66
|
+
open = port_open?("127.0.0.1", 8000, 0.2)
|
|
67
|
+
unless open
|
|
68
|
+
raise "You have configured your app to use DynamoDB local, but it is not running. Please start DynamoDB local. Example: brew cask install dynamodb-local && dynamodb-local"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Thanks: https://gist.github.com/ashrithr/5305786
|
|
73
|
+
def port_open?(ip, port, seconds=1)
|
|
74
|
+
# => checks if a port is open or not
|
|
75
|
+
Timeout::timeout(seconds) do
|
|
76
|
+
begin
|
|
77
|
+
TCPSocket.new(ip, port).close
|
|
78
|
+
true
|
|
79
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError
|
|
80
|
+
false
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
rescue Timeout::Error
|
|
84
|
+
false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def warn_scan(message, show: nil)
|
|
88
|
+
warn_on_scan = show.nil? ? Dynomite.config.warn_on_scan : show
|
|
89
|
+
return unless warn_on_scan
|
|
90
|
+
message += <<~EOL
|
|
91
|
+
You can disable this warning by setting Dynomite.config.warn_on_scan: false
|
|
92
|
+
EOL
|
|
93
|
+
logger.info(message)
|
|
94
|
+
logger.info("Called from: #{call_line}") if call_line
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def call_line
|
|
98
|
+
caller.find { |l| l.include?(Dir.pwd) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def log_debug(params)
|
|
102
|
+
return unless ENV['DYNOMITE_DEBUG']
|
|
103
|
+
|
|
104
|
+
call_location = caller_locations[1].to_s # IE: dynomite/item/query/relation.rb:62:in `block in raw_pages'
|
|
105
|
+
return if call_location.blank? # edge cases
|
|
106
|
+
method_name = call_location.split('`').last.split(" ").last # IE: raw_pages'
|
|
107
|
+
method_name.gsub!("'", "") # IE: raw_pages
|
|
108
|
+
|
|
109
|
+
logger.info "#{self}##{method_name}" # IE: Dynomite::Item::Query::Relation#raw_pages
|
|
110
|
+
pp params
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def logger
|
|
114
|
+
Dynomite.logger
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require "thor"
|
|
2
|
+
|
|
3
|
+
# Override thor's long_desc identation behavior
|
|
4
|
+
# https://github.com/erikhuda/thor/issues/398
|
|
5
|
+
class Thor
|
|
6
|
+
module Shell
|
|
7
|
+
class Basic
|
|
8
|
+
def print_wrapped(message, options = {})
|
|
9
|
+
message = "\n#{message}" unless message[0] == "\n"
|
|
10
|
+
stdout.puts message
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module Dynomite
|
|
17
|
+
class Command < Thor
|
|
18
|
+
class << self
|
|
19
|
+
def dispatch(m, args, options, config)
|
|
20
|
+
# Allow calling for help via:
|
|
21
|
+
# dynomite command help
|
|
22
|
+
# dynomite command -h
|
|
23
|
+
# dynomite command --help
|
|
24
|
+
# dynomite command -D
|
|
25
|
+
#
|
|
26
|
+
# as well thor's normal way:
|
|
27
|
+
#
|
|
28
|
+
# dynomite help command
|
|
29
|
+
help_flags = Thor::HELP_MAPPINGS + ["help"]
|
|
30
|
+
if args.length > 1 && !(args & help_flags).empty?
|
|
31
|
+
args -= help_flags
|
|
32
|
+
args.insert(-2, "help")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# dynomite version
|
|
36
|
+
# dynomite --version
|
|
37
|
+
# dynomite -v
|
|
38
|
+
version_flags = ["--version", "-v"]
|
|
39
|
+
if args.length == 1 && !(args & version_flags).empty?
|
|
40
|
+
args = ["version"]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
super
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Override command_help to include the description at the top of the
|
|
47
|
+
# long_description.
|
|
48
|
+
def command_help(shell, command_name)
|
|
49
|
+
meth = normalize_command_name(command_name)
|
|
50
|
+
command = all_commands[meth]
|
|
51
|
+
alter_command_description(command)
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def alter_command_description(command)
|
|
56
|
+
return unless command
|
|
57
|
+
|
|
58
|
+
# Add description to beginning of long_description
|
|
59
|
+
long_desc = if command.long_description
|
|
60
|
+
"#{command.description}\n\n#{command.long_description}"
|
|
61
|
+
else
|
|
62
|
+
command.description
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# add reference url to end of the long_description
|
|
66
|
+
unless website.empty?
|
|
67
|
+
full_command = [command.ancestor_name, command.name].compact.join('-')
|
|
68
|
+
url = "#{website}/reference/dynomite-#{full_command}"
|
|
69
|
+
long_desc += "\n\nHelp also available at: #{url}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
command.long_description = long_desc
|
|
73
|
+
end
|
|
74
|
+
private :alter_command_description
|
|
75
|
+
|
|
76
|
+
# meant to be overriden
|
|
77
|
+
def website
|
|
78
|
+
""
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# https://github.com/erikhuda/thor/issues/244
|
|
82
|
+
# Deprecation warning: Thor exit with status 0 on errors. To keep this behavior, you must define `exit_on_failure?` in `Lono::CLI`
|
|
83
|
+
# You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION.
|
|
84
|
+
def exit_on_failure?
|
|
85
|
+
true
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
_dynomite() {
|
|
2
|
+
COMPREPLY=()
|
|
3
|
+
local word="${COMP_WORDS[COMP_CWORD]}"
|
|
4
|
+
local words=("${COMP_WORDS[@]}")
|
|
5
|
+
unset words[0]
|
|
6
|
+
local completion=$(dynomite completion ${words[@]})
|
|
7
|
+
COMPREPLY=( $(compgen -W "$completion" -- "$word") )
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
complete -F _dynomite dynomite
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
Code Explanation:
|
|
3
|
+
|
|
4
|
+
There are 3 types of things to auto-complete:
|
|
5
|
+
|
|
6
|
+
1. command: the command itself
|
|
7
|
+
2. parameters: command parameters.
|
|
8
|
+
3. options: command options
|
|
9
|
+
|
|
10
|
+
Here's an example:
|
|
11
|
+
|
|
12
|
+
mycli hello name --from me
|
|
13
|
+
|
|
14
|
+
* command: hello
|
|
15
|
+
* parameters: name
|
|
16
|
+
* option: --from
|
|
17
|
+
|
|
18
|
+
When command parameters are done processing, the remaining completion words will be options. We can tell that the command params are completed based on the method arity.
|
|
19
|
+
|
|
20
|
+
## Arity
|
|
21
|
+
|
|
22
|
+
For example, say you had a method for a CLI command with the following form:
|
|
23
|
+
|
|
24
|
+
ufo scale service count --cluster development
|
|
25
|
+
|
|
26
|
+
It's equivalent ruby method:
|
|
27
|
+
|
|
28
|
+
scale(service, count) = has an arity of 2
|
|
29
|
+
|
|
30
|
+
So typing:
|
|
31
|
+
|
|
32
|
+
ufo scale service count [TAB] # there are 3 parameters including the "scale" command according to Thor's CLI processing.
|
|
33
|
+
|
|
34
|
+
So the completion should only show options, something like this:
|
|
35
|
+
|
|
36
|
+
--noop --verbose --cluster
|
|
37
|
+
|
|
38
|
+
## Splat Arguments
|
|
39
|
+
|
|
40
|
+
When the ruby method has a splat argument, it's arity is negative. Here are some example methods and their arities.
|
|
41
|
+
|
|
42
|
+
ship(service) = 1
|
|
43
|
+
scale(service, count) = 2
|
|
44
|
+
ships(*services) = -1
|
|
45
|
+
foo(example, *rest) = -2
|
|
46
|
+
|
|
47
|
+
Fortunately, negative and positive arity values are processed the same way. So we take simply take the absolute value of the arity and process it the same.
|
|
48
|
+
|
|
49
|
+
Here are some test cases, hit TAB after typing the command:
|
|
50
|
+
|
|
51
|
+
dynomite completion
|
|
52
|
+
dynomite completion hello
|
|
53
|
+
dynomite completion hello name
|
|
54
|
+
dynomite completion hello name --
|
|
55
|
+
dynomite completion hello name --noop
|
|
56
|
+
|
|
57
|
+
dynomite completion
|
|
58
|
+
dynomite completion sub:goodbye
|
|
59
|
+
dynomite completion sub:goodbye name
|
|
60
|
+
|
|
61
|
+
## Subcommands and Thor::Group Registered Commands
|
|
62
|
+
|
|
63
|
+
Sometimes the commands are not simple thor commands but are subcommands or Thor::Group commands. A good specific example is the ufo tool.
|
|
64
|
+
|
|
65
|
+
* regular command: ufo ship
|
|
66
|
+
* subcommand: ufo docker
|
|
67
|
+
* Thor::Group command: ufo init
|
|
68
|
+
|
|
69
|
+
Auto-completion accounts for each of these type of commands.
|
|
70
|
+
=end
|
|
71
|
+
module Dynomite
|
|
72
|
+
class Completer
|
|
73
|
+
def initialize(command_class, *params)
|
|
74
|
+
@params = params
|
|
75
|
+
@current_command = @params[0]
|
|
76
|
+
@command_class = command_class # CLI initiall
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def run
|
|
80
|
+
if subcommand?(@current_command)
|
|
81
|
+
subcommand_class = @command_class.subcommand_classes[@current_command]
|
|
82
|
+
@params.shift # destructive
|
|
83
|
+
Completer.new(subcommand_class, *@params).run # recursively use subcommand
|
|
84
|
+
return
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# full command has been found!
|
|
88
|
+
unless found?(@current_command)
|
|
89
|
+
puts all_commands
|
|
90
|
+
return
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# will only get to here if command aws found (above)
|
|
94
|
+
arity = @command_class.instance_method(@current_command).arity.abs
|
|
95
|
+
if @params.size > arity or thor_group_command?
|
|
96
|
+
puts options_completion
|
|
97
|
+
else
|
|
98
|
+
puts params_completion
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def subcommand?(command)
|
|
103
|
+
@command_class.subcommands.include?(command)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# hacky way to detect that command is a registered Thor::Group command
|
|
107
|
+
def thor_group_command?
|
|
108
|
+
command_params(raw=true) == [[:rest, :args]]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def found?(command)
|
|
112
|
+
public_methods = @command_class.public_instance_methods(false)
|
|
113
|
+
command && public_methods.include?(command.to_sym)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# all top-level commands
|
|
117
|
+
def all_commands
|
|
118
|
+
commands = @command_class.all_commands.reject do |k,v|
|
|
119
|
+
v.is_a?(Thor::HiddenCommand)
|
|
120
|
+
end
|
|
121
|
+
commands.keys
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def command_params(raw=false)
|
|
125
|
+
params = @command_class.instance_method(@current_command).parameters
|
|
126
|
+
# Example:
|
|
127
|
+
# >> Sub.instance_method(:goodbye).parameters
|
|
128
|
+
# => [[:req, :name]]
|
|
129
|
+
# >>
|
|
130
|
+
raw ? params : params.map!(&:last)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def params_completion
|
|
134
|
+
offset = @params.size - 1
|
|
135
|
+
offset_params = command_params[offset..-1]
|
|
136
|
+
command_params[offset..-1].first
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def options_completion
|
|
140
|
+
used = ARGV.select { |a| a.include?('--') } # so we can remove used options
|
|
141
|
+
|
|
142
|
+
method_options = @command_class.all_commands[@current_command].options.keys
|
|
143
|
+
class_options = @command_class.class_options.keys
|
|
144
|
+
|
|
145
|
+
all_options = method_options + class_options + ['help']
|
|
146
|
+
|
|
147
|
+
all_options.map! { |o| "--#{o.to_s.gsub('_','-')}" }
|
|
148
|
+
filtered_options = all_options - used
|
|
149
|
+
filtered_options.uniq
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Useful for debugging. Using puts messes up completion.
|
|
153
|
+
def log(msg)
|
|
154
|
+
File.open("/tmp/complete.log", "a") do |file|
|
|
155
|
+
file.puts(msg)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Dynomite
|
|
2
|
+
class Config
|
|
3
|
+
attr_accessor :logger, :log_level, :namespace_separator, :endpoint, :env,
|
|
4
|
+
:default_count_method, :warn_on_scan, :default_namespace, :discover_fields,
|
|
5
|
+
:migration, :undeclared_field_behavior, :default_field_type, :update_strategy
|
|
6
|
+
def initialize
|
|
7
|
+
@logger = Logger.new($stderr)
|
|
8
|
+
@logger.formatter = ActiveSupport::Logger::SimpleFormatter.new
|
|
9
|
+
@log_level = nil
|
|
10
|
+
@namespace_separator = "_"
|
|
11
|
+
@endpoint = ENV['DYNOMITE_ENDPOINT'] # allow to use local dynamodb
|
|
12
|
+
@env = ActiveSupport::StringInquirer.new(ENV['DYNOMITE_ENV'] || "development")
|
|
13
|
+
@default_count_method = :count # slow but accurate. :item_count is faster but can be stale by 6 hours
|
|
14
|
+
@warn_on_scan = true
|
|
15
|
+
@discover_fields = false
|
|
16
|
+
@migration = ActiveSupport::OrderedOptions.new
|
|
17
|
+
@undeclared_field_behavior = :warn # warn silent error allow
|
|
18
|
+
# Not implemented: :datetime, :date, :float, :array, :set, :map
|
|
19
|
+
# as we aws-sdk-dynamodb handles it via :infer
|
|
20
|
+
@default_field_type = :infer # :string, :integer, :boolean, :time, :infer
|
|
21
|
+
@update_strategy = :put_item # :put_item, :update_item
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# User should use namespace. The default_namespace is only used internally so Jets can set it.
|
|
25
|
+
# Makes it easy to set the namespace from the Jets project namespace.
|
|
26
|
+
# Example:
|
|
27
|
+
#
|
|
28
|
+
# config/initializers/dynomite.rb
|
|
29
|
+
#
|
|
30
|
+
# Dynomite.configure do |config|
|
|
31
|
+
# config.namespace = Jets.project_namespace # IE: demo-dev
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
attr_writer :namespace
|
|
35
|
+
def namespace
|
|
36
|
+
ENV['DYNOMITE_NAMESPACE'] || @namespace || @default_namespace || 'dynomite'
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/dynomite/core.rb
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
require 'logger'
|
|
2
2
|
|
|
3
|
-
module Dynomite
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@@app_root = ENV['APP_ROOT'] || ENV['JETS_ROOT'] || ENV['RAILS_ROOT']
|
|
11
|
-
@@app_root = '.' if @@app_root.nil? || @app_root == ''
|
|
12
|
-
@@app_root = "#{@@app_root}/" unless @@app_root.ends_with?('/')
|
|
13
|
-
@@app_root
|
|
14
|
-
end
|
|
3
|
+
module Dynomite
|
|
4
|
+
module Core
|
|
5
|
+
@@root = nil
|
|
6
|
+
def root
|
|
7
|
+
return @@root if @@root
|
|
8
|
+
@@root = ENV['DYNOMITE_ROOT'] || ENV['JETS_ROOT'] || ENV['RAILS_ROOT'] || '.'
|
|
9
|
+
end
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
@@config = nil
|
|
12
|
+
def config
|
|
13
|
+
@@config ||= Config.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def logger
|
|
17
|
+
config.logger
|
|
18
|
+
end
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
def configure
|
|
21
|
+
yield(config)
|
|
22
|
+
end
|
|
24
23
|
end
|
|
25
24
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Dynomite
|
|
2
|
+
class Engine < ::Jets::Engine
|
|
3
|
+
config.after_initialize do
|
|
4
|
+
Dynomite.config.default_namespace = Jets.project_namespace # IE: demo-dev
|
|
5
|
+
Dynomite.config.migration.deletion_protection_enabled = Jets.env.production?
|
|
6
|
+
|
|
7
|
+
# Discover all the fields for all the models from attribute_definitions
|
|
8
|
+
# and create field methods. Has to be done after_initialize because
|
|
9
|
+
# need model names for the table_name.
|
|
10
|
+
quiet_dynamodb_logging do
|
|
11
|
+
Dynomite::Item.descendants.each do |klass|
|
|
12
|
+
klass.discover_fields!
|
|
13
|
+
end if Dynomite.config.discover_fields
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.default_log_level
|
|
18
|
+
# Note: On AWS Lambda, ARGV[0] is nil
|
|
19
|
+
if ARGV[0]&.include?("dynamodb") # IE: dynamodb:migrate dynamodb:seed
|
|
20
|
+
:info
|
|
21
|
+
else
|
|
22
|
+
Jets.env.development? ? :debug : :info
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.quiet_dynamodb_logging
|
|
27
|
+
if ENV['DYNOMITE_DEBUG']
|
|
28
|
+
# If in debug mode, then leave the log level alone which is debug in development
|
|
29
|
+
# This shows the describe_table calls on jets console bootup
|
|
30
|
+
Dynomite.config.log_level = default_log_level
|
|
31
|
+
else
|
|
32
|
+
# Otherwise, set the log level to info temporarily to quiet the describe_table calls
|
|
33
|
+
# Then reset the log level back to the user's configured log level.
|
|
34
|
+
user_log_level = Dynomite.config.log_level
|
|
35
|
+
Dynomite.config.log_level = :info
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
yield
|
|
39
|
+
|
|
40
|
+
Dynomite::Item.client = nil # reset client. Need to reset the client since it's cached
|
|
41
|
+
# Go back to the user's configured log level or the default log level if user did not set it.
|
|
42
|
+
Dynomite.config.log_level = user_log_level || default_log_level
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/dynomite/erb.rb
CHANGED
|
@@ -7,14 +7,12 @@ require 'erb'
|
|
|
7
7
|
# result = Dynomite::Erb.result(path, key1: "val1", key2: "val2")
|
|
8
8
|
#
|
|
9
9
|
class Dynomite::Erb
|
|
10
|
-
include Dynomite::Log
|
|
11
|
-
|
|
12
10
|
class << self
|
|
13
11
|
def result(path, variables={})
|
|
14
12
|
set_template_variables(variables)
|
|
15
13
|
template = IO.read(path)
|
|
16
14
|
begin
|
|
17
|
-
ERB.new(template,
|
|
15
|
+
ERB.new(template, trim_mode: '-').result(binding)
|
|
18
16
|
rescue Exception => e
|
|
19
17
|
log(e)
|
|
20
18
|
log(e.backtrace) if ENV['DEBUG']
|
|
@@ -49,5 +47,9 @@ class Dynomite::Erb
|
|
|
49
47
|
instance_variable_set(:"@#{key}", value)
|
|
50
48
|
end
|
|
51
49
|
end
|
|
50
|
+
|
|
51
|
+
def log(msg)
|
|
52
|
+
logger.info(msg)
|
|
53
|
+
end
|
|
52
54
|
end
|
|
53
55
|
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module Dynomite
|
|
2
|
+
class Error < StandardError
|
|
3
|
+
class InvalidPut < Error; end
|
|
4
|
+
class PrimaryKeyChangedError < Error; end
|
|
5
|
+
class RecordNotFound < Error; end
|
|
6
|
+
class RecordNotUnique < Error; end
|
|
7
|
+
class ReservedWord < Error; end
|
|
8
|
+
class StaleObject < Error; end
|
|
9
|
+
class UndeclaredFields < Error; end
|
|
10
|
+
class Validation < Error; end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## Examples
|
|
2
|
+
|
|
3
|
+
dynomite completion
|
|
4
|
+
|
|
5
|
+
Prints words for TAB auto-completion.
|
|
6
|
+
|
|
7
|
+
dynomite completion
|
|
8
|
+
dynomite completion hello
|
|
9
|
+
dynomite completion hello name
|
|
10
|
+
|
|
11
|
+
To enable, TAB auto-completion add the following to your profile:
|
|
12
|
+
|
|
13
|
+
eval $(dynomite completion_script)
|
|
14
|
+
|
|
15
|
+
Auto-completion example usage:
|
|
16
|
+
|
|
17
|
+
dynomite [TAB]
|
|
18
|
+
dynomite hello [TAB]
|
|
19
|
+
dynomite hello name [TAB]
|
|
20
|
+
dynomite hello name --[TAB]
|