lono 7.3.2 → 7.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/lib/lono.rb +1 -0
  4. data/lib/lono/app_file/build.rb +22 -34
  5. data/lib/lono/app_file/build/lambda_layer/ruby_packager.rb +2 -33
  6. data/lib/lono/app_file/registry/item.rb +18 -45
  7. data/lib/lono/app_file/upload.rb +1 -25
  8. data/lib/lono/cfn/base.rb +8 -2
  9. data/lib/lono/cfn/create.rb +3 -2
  10. data/lib/lono/cfn/opts.rb +8 -0
  11. data/lib/lono/cfn/preview/changeset.rb +1 -0
  12. data/lib/lono/cfn/update.rb +1 -0
  13. data/lib/lono/configset.rb +1 -1
  14. data/lib/lono/configset/combiner.rb +40 -13
  15. data/lib/lono/configset/generator.rb +6 -1
  16. data/lib/lono/configset/list.rb +1 -1
  17. data/lib/lono/configset/register/dsl.rb +3 -3
  18. data/lib/lono/configset/resolver.rb +9 -5
  19. data/lib/lono/configset/s3_file/build.rb +33 -0
  20. data/lib/lono/configset/s3_file/item.rb +30 -0
  21. data/lib/lono/configset/s3_file/registry.rb +12 -0
  22. data/lib/lono/configset/s3_file/upload.rb +12 -0
  23. data/lib/lono/configset/strategy/base.rb +21 -1
  24. data/lib/lono/configset/strategy/dsl.rb +9 -6
  25. data/lib/lono/configset/strategy/erb.rb +6 -1
  26. data/lib/lono/configset/strategy/helpers/dsl.rb +8 -0
  27. data/lib/lono/configset/strategy/helpers/dsl/auth.rb +37 -0
  28. data/lib/lono/configset/strategy/{dsl/helpers → helpers/dsl}/core.rb +6 -1
  29. data/lib/lono/configset/strategy/helpers/dsl/package.rb +40 -0
  30. data/lib/lono/configset/strategy/helpers/dsl/syntax.rb +74 -0
  31. data/lib/lono/configset/strategy/{erb/helpers.rb → helpers/erb.rb} +2 -2
  32. data/lib/lono/extension/list.rb +2 -1
  33. data/lib/lono/extensions/loader.rb +2 -1
  34. data/lib/lono/file_uploader.rb +3 -109
  35. data/lib/lono/finder/base.rb +1 -1
  36. data/lib/lono/finder/blueprint/configset.rb +3 -3
  37. data/lib/lono/generate.rb +11 -1
  38. data/lib/lono/jade/registry.rb +10 -1
  39. data/lib/lono/jadespec.rb +4 -2
  40. data/lib/lono/registration/temp.rb +4 -2
  41. data/lib/lono/s3/uploader.rb +94 -0
  42. data/lib/lono/seed/base.rb +2 -1
  43. data/lib/lono/template/configset_injector.rb +6 -4
  44. data/lib/lono/template/generator.rb +1 -9
  45. data/lib/lono/template/helper.rb +2 -3
  46. data/lib/lono/template/post_processor.rb +9 -1
  47. data/lib/lono/template/strategy/dsl/builder/helpers.rb +1 -0
  48. data/lib/lono/template/strategy/dsl/builder/helpers/ec2_helper.rb +27 -0
  49. data/lib/lono/template/strategy/dsl/builder/helpers/s3_helper.rb +1 -0
  50. data/lib/lono/template/upload.rb +4 -98
  51. data/lib/lono/utils/item/file_methods.rb +29 -0
  52. data/lib/lono/utils/item/zip.rb +42 -0
  53. data/lib/lono/utils/rsync.rb +36 -0
  54. data/lib/lono/version.rb +1 -1
  55. data/lib/templates/blueprint/.gitignore +4 -7
  56. data/lib/templates/configset/%configset_name%.gemspec.tt +1 -1
  57. data/lib/templates/configset/.gitignore +2 -4
  58. data/lib/templates/extension/%extension_name%.gemspec.tt +2 -1
  59. data/lib/templates/extension/.gitignore +11 -17
  60. data/lib/templates/extension/lib/%extension_name%.rb.tt +3 -0
  61. data/lib/templates/extension/lib/%extension_name%/autoloader.rb.tt +23 -0
  62. data/lono.gemspec +1 -0
  63. metadata +32 -6
  64. data/lib/lono/configset/strategy/dsl/helpers.rb +0 -5
  65. data/lib/lono/configset/strategy/dsl/syntax.rb +0 -32
@@ -3,6 +3,7 @@ class Lono::Template::Strategy::Dsl::Builder
3
3
  module Helpers
4
4
  extend Memoist
5
5
  include CoreHelper
6
+ include Ec2Helper
6
7
  include FileHelper
7
8
  include LookupHelper
8
9
  include S3Helper
@@ -0,0 +1,27 @@
1
+ module Lono::Template::Strategy::Dsl::Builder::Helpers
2
+ module Ec2Helper
3
+ extend Memoist
4
+
5
+ def default_vpc
6
+ resp = ec2.describe_vpcs(filters: [name: "isDefault", values: ["true"]])
7
+ vpc = resp.vpcs.first
8
+ vpc ? vpc.vpc_id : "no default vpc found"
9
+ end
10
+ memoize :default_vpc
11
+
12
+ def default_subnets
13
+ return "no default subnets because no default vpc found" if default_vpc == "no default vpc found"
14
+ resp = ec2.describe_subnets(filters: [name: "vpc-id", values: [default_vpc]])
15
+ subnets = resp.subnets
16
+ subnets.map(&:subnet_id)
17
+ end
18
+ memoize :default_subnets
19
+
20
+ def key_pairs(regexp=nil)
21
+ resp = ec2.describe_key_pairs
22
+ key_names = resp.key_pairs.map(&:key_name)
23
+ key_names.select! { |k| k =~ regexp }
24
+ key_names
25
+ end
26
+ end
27
+ end
@@ -3,6 +3,7 @@ module Lono::Template::Strategy::Dsl::Builder::Helpers
3
3
  def s3_bucket
4
4
  Lono::S3::Bucket.name
5
5
  end
6
+ alias_method :lono_bucket_name, :s3_bucket
6
7
 
7
8
  def s3_key(name, options={})
8
9
  default = {type: "file"}
@@ -5,8 +5,6 @@ require 'digest'
5
5
 
6
6
  class Lono::Template
7
7
  class Upload < Lono::AbstractBase
8
- include Lono::AwsServices
9
-
10
8
  def initialize(options={})
11
9
  super
12
10
  @checksums = {}
@@ -14,104 +12,12 @@ class Lono::Template
14
12
  end
15
13
 
16
14
  def run
17
- load_checksums!
18
-
19
- say "Uploading CloudFormation templates..."
15
+ puts "Uploading CloudFormation templates..."
20
16
  paths = Dir.glob("#{Lono.config.output_path}/#{@blueprint}/templates/**/*")
21
17
  paths.select { |p| File.file?(p) }.each do |path|
22
- upload(path)
23
- end
24
- say "Templates uploaded to s3."
25
- end
26
-
27
- # Read existing files on s3 to grab their md5 checksum.
28
- # We do this so we can see if we should avoid re-uploading the s3 child template
29
- # entirely. If we upload a new child template that does not change AWS CloudFormation
30
- # is not smart enough to know that it not has changed. I think all AWS CloudFormation
31
- # does is check if the file's timestamp.
32
- #
33
- # Thought this would result in better AWS Change Set info but AWS still reports child
34
- # stacks being changed even though they should not be reported. Leaving this s3 checksum
35
- # in for now.
36
- def load_checksums!
37
- return if @options[:noop]
38
-
39
- resp = s3.list_objects(bucket: s3_bucket, prefix: @prefix)
40
- resp.contents.each do |object|
41
- @checksums[object.key] = strip_surrounding_quotes(object.etag)
42
- end
43
- @checksums
44
- end
45
-
46
- def strip_surrounding_quotes(string)
47
- string.sub(/^"/,'').sub(/"$/,'')
48
- end
49
-
50
- def upload(path)
51
- return if @options[:noop]
52
-
53
- path = path.sub("#{Lono.root}/",'')
54
- pretty_path = path.sub(/^\.\//, '')
55
- key = "#{@prefix}/#{pretty_path.sub(%r{output/templates/},'')}"
56
- s3_full_path = "s3://#{s3_bucket}/#{key}"
57
-
58
- local_checksum = Digest::MD5.hexdigest(IO.read(path))
59
- remote_checksum = remote_checksum(path)
60
- if local_checksum == remote_checksum
61
- say("Not modified: #{pretty_path} to #{s3_full_path}".color(:yellow)) unless @options[:noop]
62
- return # do not upload unless the checksum has changed
18
+ Lono::S3::Uploader.new(path).upload
63
19
  end
64
-
65
- resp = s3.put_object(
66
- body: IO.read(path),
67
- bucket: s3_bucket,
68
- key: key,
69
- storage_class: "REDUCED_REDUNDANCY"
70
- ) unless @options[:noop]
71
-
72
- # Example output:
73
- # Uploaded: output/templates/docker.yml to s3://boltops-dev/s3_folder/templates/development/docker.yml
74
- # Uploaded: output/templates/ecs/private.yml to s3://boltops-dev/s3_folder/templates/development/ecs/private.yml
75
- message = "Uploaded: #{pretty_path} to #{s3_full_path}".color(:green)
76
- message = "NOOP: #{message}" if @options[:noop]
77
- say message
78
- end
79
-
80
- # @checksums map has a key format: s3_folder/templates/development/docker.yml
81
- #
82
- # path = ./output/templates/docker.yml
83
- # s3_folder = s3://boltops-dev/s3_folder/templates/development/docker.yml
84
- def remote_checksum(path)
85
- # first convert the local path to the path format that is stored in @checksums keys
86
- # ./output/templates/docker.yml => s3_folder/templates/development/docker.yml
87
- pretty_path = path.sub(/^\.\//, '')
88
- key = "#{@prefix}/#{pretty_path.sub(%r{output/templates/},'')}"
89
- @checksums[key]
90
- end
91
-
92
- # https://s3.amazonaws.com/mybucket/s3_folder/templates/production/parent.yml
93
- def s3_https_url(template_path)
94
- "https://s3.amazonaws.com/#{s3_bucket}/#{@prefix}/#{template_path}"
95
- end
96
-
97
- # used for cfn/base.rb def set_template_url!(options)
98
- def s3_presigned_url(template_output_path)
99
- template_path = template_output_path.sub('output/templates/','')
100
- key = "#{@prefix}/#{template_path}"
101
- s3_presigner.presigned_url(:get_object, bucket: s3_bucket, key: key)
102
- end
103
-
104
- # Parse the s3_folder setting and remove the folder portion to leave the
105
- # "s3_bucket" portion
106
- # Example:
107
- # s3_bucket('s3://mybucket/templates/storage/path')
108
- # => mybucket
109
- def s3_bucket
110
- Lono::S3::Bucket.name
111
- end
112
-
113
- def say(message)
114
- puts message unless @options[:quiet]
20
+ puts "Templates uploaded to s3."
115
21
  end
116
22
  end
117
- end
23
+ end
@@ -0,0 +1,29 @@
1
+ module Lono::Utils::Item
2
+ module FileMethods
3
+ def exist?
4
+ File.exist?(output_path)
5
+ end
6
+
7
+ def directory?
8
+ File.directory?(output_path)
9
+ end
10
+
11
+ def file?
12
+ File.file?(output_path)
13
+ end
14
+
15
+ def s3_path
16
+ path = zip_file_path.gsub("#{Lono.root}/",'') # remove Lono.root
17
+ "#{Lono.env}/#{path}"
18
+ end
19
+
20
+ # full path
21
+ def zip_file_path
22
+ "#{File.dirname(output_path)}/#{zip_file_name}"
23
+ end
24
+
25
+ def zip_file_name
26
+ "#{File.basename(output_path)}-#{@type}-#{Lono::Md5.sum(output_path)}.zip"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,42 @@
1
+ require 'fileutils'
2
+
3
+ module Lono::Utils::Item
4
+ module Zip
5
+ def zip(item)
6
+ if item.directory?
7
+ zip_directory(item)
8
+ else
9
+ zip_file(item)
10
+ end
11
+ end
12
+
13
+ def zip_file(item)
14
+ path = item.output_path
15
+ zip_file = item.zip_file_name
16
+
17
+ puts "Zipping file and generating md5 named file from: #{path}"
18
+ command = "cd #{File.dirname(path)} && zip -q #{zip_file} #{File.basename(path)}" # create zipfile at same level of file
19
+ execute_zip(command)
20
+ end
21
+
22
+ def zip_directory(item)
23
+ path = item.output_path
24
+ zip_file = item.zip_file_name
25
+
26
+ puts "Zipping folder and generating md5 named file from: #{path}"
27
+ command = "cd #{path} && zip --symlinks -rq #{zip_file} ." # create zipfile witih directory
28
+ execute_zip(command)
29
+ FileUtils.mv("#{path}/#{zip_file}", "#{File.dirname(path)}/#{zip_file}") # move zip back to the parent directory
30
+ end
31
+
32
+ def execute_zip(command)
33
+ # puts "=> #{command}".color(:green) # uncomment to debug
34
+ `#{command}`
35
+ unless $?.success?
36
+ puts "ERROR: Fail to run #{command}".color(:red)
37
+ puts "Maybe zip is not installed or path is incorrect?"
38
+ exit 1
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,36 @@
1
+ require 'shellwords'
2
+
3
+ module Lono::Utils
4
+ module Rsync
5
+ def sh(command)
6
+ puts "=> #{command}"
7
+ out = `#{command}`
8
+ puts out if ENV['LONO_DEBUG_SH']
9
+ success = $?.success?
10
+ raise("ERROR: running command #{command}").color(:red) unless success
11
+ success
12
+ end
13
+
14
+ def rsync(src, dest)
15
+ # Using FileUtils.cp_r doesnt work if there are special files like socket files in the src dir.
16
+ # Instead of using this hack https://bugs.ruby-lang.org/issues/10104
17
+ # Using rsync to perform the copy.
18
+ src.chop! if src.ends_with?('/')
19
+ dest.chop! if dest.ends_with?('/')
20
+ check_rsync_installed!
21
+ # Ensures required trailing slashes
22
+ FileUtils.mkdir_p(File.dirname(dest))
23
+ sh "rsync -a --links --no-specials --no-devices #{Shellwords.escape(src)}/ #{Shellwords.escape(dest)}/"
24
+ end
25
+
26
+ @@rsync_installed = false
27
+ def check_rsync_installed!
28
+ return if @@rsync_installed # only check once
29
+ if system "type rsync > /dev/null 2>&1"
30
+ @@rsync_installed = true
31
+ else
32
+ raise "ERROR: Rsync is required. Rsync does not seem to be installed.".color(:red)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,3 +1,3 @@
1
1
  module Lono
2
- VERSION = "7.3.2"
2
+ VERSION = "7.4.0"
3
3
  end
@@ -1,14 +1,11 @@
1
1
  /.bundle/
2
+ /.rspec_status
2
3
  /.yardoc
3
4
  /_yardoc/
4
5
  /coverage/
5
6
  /doc/
7
+ /Gemfile.lock
8
+ /output
6
9
  /pkg/
7
10
  /spec/reports/
8
- /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
12
-
13
- output
14
- Gemfile.lock
11
+ /tmp/
@@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
7
7
  spec.summary = "Write a short summary because it's required." # TODO: Change me
8
8
  spec.description = "Write a longer description or delete this line." # TODO: Change me
9
9
  spec.homepage = "<%= ENV['LONO_ORG'] || "https://github.com/USER" %>/<%= configset_name %>"
10
- spec.license = "<%= ENV['LONO_LICENSE'] || '' %>"
10
+ spec.license = "<%= ENV['LONO_LICENSE'] || 'Nonstandard' %>"
11
11
 
12
12
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
13
13
  # to allow pushing to a single host or delete this section to allow pushing to any host.
@@ -1,12 +1,10 @@
1
1
  /.bundle/
2
+ /.rspec_status
2
3
  /.yardoc
3
4
  /_yardoc/
4
5
  /coverage/
5
6
  /doc/
7
+ /Gemfile.lock
6
8
  /pkg/
7
9
  /spec/reports/
8
10
  /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
12
- Gemfile.lock
@@ -36,7 +36,8 @@ Gem::Specification.new do |spec|
36
36
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
37
37
  spec.require_paths = ["lib"]
38
38
 
39
- spec.add_dependency "activesupport"
39
+ spec.add_dependency "activesupport", "~> 6"
40
+ spec.add_dependency "zeitwerk", "~> 2"
40
41
 
41
42
  spec.add_development_dependency "bundler"
42
43
  spec.add_development_dependency "byebug"
@@ -1,17 +1,11 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- .config
5
- .yardoc
6
- _yardoc
7
- coverage
8
- doc/
9
- InstalledFiles
10
- lib/bundler/man
11
- pkg
12
- rdoc
13
- spec/reports
14
- test/tmp
15
- test/version_tmp
16
- tmp
17
- Gemfile.lock
1
+ /.bundle/
2
+ /.rspec_status
3
+ /.yardoc
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /Gemfile.lock
8
+ /output
9
+ /pkg/
10
+ /spec/reports/
11
+ /tmp/
@@ -1,5 +1,8 @@
1
1
  $:.unshift(File.expand_path("../", __FILE__))
2
2
  require "<%= extension_underscore_name %>/version"
3
3
 
4
+ require "<%= extension_underscore_name %>/autoloader"
5
+ <%= extension_class_name %>::Autoloader.setup
6
+
4
7
  module <%= extension_class_name %>
5
8
  end
@@ -0,0 +1,23 @@
1
+ require "zeitwerk"
2
+
3
+ module <%= extension_class_name %>
4
+ class Autoloader
5
+ class Inflector < Zeitwerk::Inflector
6
+ def camelize(basename, _abspath)
7
+ map = { cli: "CLI", version: "VERSION" }
8
+ map[basename.to_sym] || super
9
+ end
10
+ end
11
+
12
+ class << self
13
+ def setup
14
+ loader = Zeitwerk::Loader.new
15
+ loader.inflector = Inflector.new
16
+ loader.push_dir(File.dirname(__dir__)) # lib
17
+ # loader.log!
18
+ loader.ignore("#{__dir__}/user_data")
19
+ loader.setup
20
+ end
21
+ end
22
+ end
23
+ end
@@ -27,6 +27,7 @@ Gem::Specification.new do |gem|
27
27
  gem.add_dependency "aws-sdk-iam" # lono seed
28
28
  gem.add_dependency "aws-sdk-s3"
29
29
  gem.add_dependency "aws-sdk-ssm"
30
+ gem.add_dependency "aws_data"
30
31
  gem.add_dependency "bundler", "~> 2"
31
32
  gem.add_dependency "cfn_camelizer"
32
33
  gem.add_dependency "cli-format"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lono
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.3.2
4
+ version: 7.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tung Nguyen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-17 00:00:00.000000000 Z
11
+ date: 2020-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: aws_data
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: bundler
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -504,13 +518,19 @@ files:
504
518
  - lib/lono/configset/register/dsl.rb
505
519
  - lib/lono/configset/register/project.rb
506
520
  - lib/lono/configset/resolver.rb
521
+ - lib/lono/configset/s3_file/build.rb
522
+ - lib/lono/configset/s3_file/item.rb
523
+ - lib/lono/configset/s3_file/registry.rb
524
+ - lib/lono/configset/s3_file/upload.rb
507
525
  - lib/lono/configset/strategy/base.rb
508
526
  - lib/lono/configset/strategy/dsl.rb
509
- - lib/lono/configset/strategy/dsl/helpers.rb
510
- - lib/lono/configset/strategy/dsl/helpers/core.rb
511
- - lib/lono/configset/strategy/dsl/syntax.rb
512
527
  - lib/lono/configset/strategy/erb.rb
513
- - lib/lono/configset/strategy/erb/helpers.rb
528
+ - lib/lono/configset/strategy/helpers/dsl.rb
529
+ - lib/lono/configset/strategy/helpers/dsl/auth.rb
530
+ - lib/lono/configset/strategy/helpers/dsl/core.rb
531
+ - lib/lono/configset/strategy/helpers/dsl/package.rb
532
+ - lib/lono/configset/strategy/helpers/dsl/syntax.rb
533
+ - lib/lono/configset/strategy/helpers/erb.rb
514
534
  - lib/lono/conventions.rb
515
535
  - lib/lono/core.rb
516
536
  - lib/lono/core/config.rb
@@ -609,6 +629,7 @@ files:
609
629
  - lib/lono/registration/user.rb
610
630
  - lib/lono/s3.rb
611
631
  - lib/lono/s3/bucket.rb
632
+ - lib/lono/s3/uploader.rb
612
633
  - lib/lono/script.rb
613
634
  - lib/lono/script/base.rb
614
635
  - lib/lono/script/build.rb
@@ -664,6 +685,7 @@ files:
664
685
  - lib/lono/template/strategy/dsl/builder/fn.rb
665
686
  - lib/lono/template/strategy/dsl/builder/helpers.rb
666
687
  - lib/lono/template/strategy/dsl/builder/helpers/core_helper.rb
688
+ - lib/lono/template/strategy/dsl/builder/helpers/ec2_helper.rb
667
689
  - lib/lono/template/strategy/dsl/builder/helpers/file_helper.rb
668
690
  - lib/lono/template/strategy/dsl/builder/helpers/lookup_helper.rb
669
691
  - lib/lono/template/strategy/dsl/builder/helpers/s3_helper.rb
@@ -691,7 +713,10 @@ files:
691
713
  - lib/lono/template/util.rb
692
714
  - lib/lono/upgrade.rb
693
715
  - lib/lono/user_data.rb
716
+ - lib/lono/utils/item/file_methods.rb
717
+ - lib/lono/utils/item/zip.rb
694
718
  - lib/lono/utils/pretty_time.rb
719
+ - lib/lono/utils/rsync.rb
695
720
  - lib/lono/utils/sure.rb
696
721
  - lib/lono/version.rb
697
722
  - lib/lono/yamler/loader.rb
@@ -725,6 +750,7 @@ files:
725
750
  - lib/templates/extension/Gemfile.tt
726
751
  - lib/templates/extension/Rakefile.tt
727
752
  - lib/templates/extension/lib/%extension_name%.rb.tt
753
+ - lib/templates/extension/lib/%extension_name%/autoloader.rb.tt
728
754
  - lib/templates/extension/lib/%extension_name%/helpers/mappings.rb.tt
729
755
  - lib/templates/extension/lib/%extension_name%/helpers/outputs.rb.tt
730
756
  - lib/templates/extension/lib/%extension_name%/helpers/parameters.rb.tt