dapp 0.0.24 → 0.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.
Files changed (78) hide show
  1. checksums.yaml +13 -5
  2. data/bin/dapp +17 -1
  3. data/config/en/common.yml +23 -0
  4. data/config/en/net_status.yml +24 -0
  5. data/lib/dapp.rb +68 -12
  6. data/lib/dapp/application.rb +57 -0
  7. data/lib/dapp/application/git_artifact.rb +26 -0
  8. data/lib/dapp/application/logging.rb +120 -0
  9. data/lib/dapp/application/path.rb +31 -0
  10. data/lib/dapp/application/tags.rb +65 -0
  11. data/lib/dapp/build/stage/app_install.rb +19 -0
  12. data/lib/dapp/build/stage/app_setup.rb +19 -0
  13. data/lib/dapp/build/stage/base.rb +106 -0
  14. data/lib/dapp/build/stage/from.rb +40 -0
  15. data/lib/dapp/build/stage/infra_install.rb +23 -0
  16. data/lib/dapp/build/stage/infra_setup.rb +19 -0
  17. data/lib/dapp/build/stage/source_1.rb +31 -0
  18. data/lib/dapp/build/stage/source_1_archive.rb +31 -0
  19. data/lib/dapp/build/stage/source_2.rb +20 -0
  20. data/lib/dapp/build/stage/source_3.rb +27 -0
  21. data/lib/dapp/build/stage/source_4.rb +31 -0
  22. data/lib/dapp/build/stage/source_5.rb +51 -0
  23. data/lib/dapp/build/stage/source_base.rb +109 -0
  24. data/lib/dapp/builder/base.rb +44 -0
  25. data/lib/dapp/builder/chef.rb +242 -0
  26. data/lib/dapp/builder/chef/berksfile.rb +54 -0
  27. data/lib/dapp/builder/shell.rb +16 -0
  28. data/lib/dapp/cli.rb +10 -44
  29. data/lib/dapp/cli/base.rb +55 -0
  30. data/lib/dapp/cli/build.rb +6 -140
  31. data/lib/dapp/cli/flush.rb +19 -0
  32. data/lib/dapp/cli/flush/build_cache.rb +26 -0
  33. data/lib/dapp/cli/flush/stage_cache.rb +23 -0
  34. data/lib/dapp/cli/list.rb +19 -0
  35. data/lib/dapp/cli/push.rb +59 -0
  36. data/lib/dapp/cli/smartpush.rb +19 -0
  37. data/lib/dapp/config/application.rb +98 -0
  38. data/lib/dapp/config/chef.rb +20 -0
  39. data/lib/dapp/config/docker.rb +39 -0
  40. data/lib/dapp/config/git_artifact.rb +78 -0
  41. data/lib/dapp/config/main.rb +23 -0
  42. data/lib/dapp/config/shell.rb +40 -0
  43. data/lib/dapp/controller.rb +103 -0
  44. data/lib/dapp/docker_image.rb +51 -0
  45. data/lib/dapp/error/application.rb +6 -0
  46. data/lib/dapp/error/base.rb +10 -0
  47. data/lib/dapp/error/build.rb +6 -0
  48. data/lib/dapp/error/config.rb +6 -0
  49. data/lib/dapp/error/controller.rb +6 -0
  50. data/lib/dapp/error/shellout.rb +6 -0
  51. data/lib/dapp/filelock.rb +1 -1
  52. data/lib/dapp/git_artifact.rb +43 -272
  53. data/lib/dapp/git_repo/base.rb +10 -12
  54. data/lib/dapp/git_repo/own.rb +7 -3
  55. data/lib/dapp/git_repo/remote.rb +14 -20
  56. data/lib/dapp/helper/cli.rb +66 -0
  57. data/lib/dapp/helper/i18n.rb +20 -0
  58. data/lib/dapp/helper/log.rb +72 -0
  59. data/lib/dapp/helper/paint.rb +27 -0
  60. data/lib/dapp/helper/sha256.rb +14 -0
  61. data/lib/dapp/helper/shellout.rb +41 -0
  62. data/lib/dapp/helper/streaming.rb +49 -0
  63. data/lib/dapp/helper/trivia.rb +27 -0
  64. data/lib/dapp/stage_image.rb +98 -0
  65. data/lib/dapp/version.rb +3 -1
  66. metadata +207 -51
  67. data/lib/dapp/atomizer.rb +0 -56
  68. data/lib/dapp/builder.rb +0 -230
  69. data/lib/dapp/builder/cascade_tagging.rb +0 -48
  70. data/lib/dapp/builder/centos7.rb +0 -47
  71. data/lib/dapp/builder/chefify.rb +0 -107
  72. data/lib/dapp/builder/ci_tagging.rb +0 -53
  73. data/lib/dapp/builder/git_tagging.rb +0 -21
  74. data/lib/dapp/builder/manual_tagging.rb +0 -22
  75. data/lib/dapp/builder/ubuntu1404.rb +0 -20
  76. data/lib/dapp/builder/ubuntu1604.rb +0 -20
  77. data/lib/dapp/docker.rb +0 -207
  78. data/lib/dapp/git_repo/chronicler.rb +0 -44
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1e603aef0451675ef67a7958ce05bdaaa523ae17
4
- data.tar.gz: 51b4f14bd8c2080855893a4d7bfb76ebe75616f8
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NjE2N2UwNDhhMzAzYWM1YTEyNGY0YWJkM2UwYTEwYWQ0NjgxYzVmYw==
5
+ data.tar.gz: !binary |-
6
+ NjIzMGU2ODk4ZTZiMGRmNmY1MjFiZmM0MTQ4MDMzMWFhYWE5OGYxNg==
5
7
  SHA512:
6
- metadata.gz: f5f0dd3fce41121aa95383458dc0d3885f352d5eb5b48ebad39f9aa7d2003c9ce0d8bc42bb4f7aa2b166f5547a50ea71ffa9aef5f2f4f25522437c83b1eedaf5
7
- data.tar.gz: 8515b76cc3fe969542d3425dac8cdc497b81f8fa3fe49d8918e0be2ee2f7079461f27c6930c66973e00f794572e5a1b23fab8f56ff6718ada186ce101184bc66
8
+ metadata.gz: !binary |-
9
+ YWYyNTE0M2JmZDkzZTAzNjAxNzYzNjMyYzZhMTVlNTJjM2I4OTUzMWE1MTkz
10
+ MmRjMjJmMzE5ODE5MjNkZTE2ZjQwOWNiNjI1MWEwNjc2ZjMwOWMyY2YwZWU0
11
+ N2JkMzg3MmI2MTUwNGQ4MTM3N2M0YjdkZjk3OWQwYTBiMWNmZGM=
12
+ data.tar.gz: !binary |-
13
+ MzVjNzQ3MGE3NzVhOTU4YmFhMWE4ZTcwNGU3YmYyZjFiMGQwN2ZlNDJjZWI1
14
+ MDYxZjQyMTBmMWVhYzNkYTZjNGJiN2JmZTAyNTQ4NDk3NzY3ZDRkMjIyMjE2
15
+ NzVkMjdiN2YwMTY1YWFkYTE1NDA1YWIxMmFhOGU5NDRkODdlYzU=
data/bin/dapp CHANGED
@@ -5,4 +5,20 @@
5
5
  require 'rubygems'
6
6
  require 'dapp'
7
7
 
8
- Dapp::CLI.new.run
8
+ def net_status_message(exception)
9
+ net_status = exception.net_status.net_status_normalize(context: exception.net_status.delete(:context))
10
+ net_status[:message] || [net_status[:error], net_status[:code]].compact.join(': ')
11
+ end
12
+
13
+ begin
14
+ Dapp::CLI.new.run
15
+ rescue Dapp::Error::Shellout => e
16
+ $stderr.puts(net_status_message(e))
17
+ exit 1
18
+ rescue Dapp::Error::Base => e
19
+ $stderr.puts(Dapp::Helper::Paint.paint_string(net_status_message(e), :warning))
20
+ exit 1
21
+ rescue Interrupt => _e
22
+ $stderr.puts(Dapp::Helper::Paint.paint_string('Interrupted', :warning))
23
+ exit 1
24
+ end
@@ -0,0 +1,23 @@
1
+ en:
2
+ common:
3
+ image:
4
+ signature: "signature: %{signature}"
5
+ info: "date: %{date}\nsize: %{size} MB"
6
+ commands: "commands:"
7
+ process:
8
+ git_artifact_loading: 'loading git_artifact'
9
+ chefdk_loading: 'loading chefdk'
10
+ berks_vendor: 'berks_vendor'
11
+ git_artifact_fetch: "fetching remote git_artifact '%{name}'"
12
+ status:
13
+ process:
14
+ pushing: '[PUSHING]'
15
+ building: '[BUILDING]'
16
+ default: '[RUNNING]'
17
+ success:
18
+ default: '[OK]'
19
+ failed:
20
+ default: '[FAILED]'
21
+ state:
22
+ using_cache: '[USING CACHE]'
23
+ build: '[BUILD]'
@@ -0,0 +1,24 @@
1
+ en:
2
+ net_status:
3
+ missing_translation: "Missing translation for code: '%{code}'."
4
+ shellout:
5
+ shell_command_failed: ">>> START STREAM\n%{stream}\n>>> END STREAM"
6
+ application:
7
+ application_not_built: "Application hasn't been built yet!"
8
+ git_branch_without_name: "Application has specific revision that isn't associated with a branch name!"
9
+ ci_environment_required: 'CI environment required (Travis or GitLab CI)!'
10
+ build:
11
+ from_image_required: 'Missing from_image!'
12
+ image_already_untagged: "Image `%{name}` already untagged!"
13
+ image_not_exist: "Image `%{name}` not exist!"
14
+ image_already_tagged: 'Image with other id already tagged!'
15
+ built_id_not_defined: '`from.built_id` not defined!'
16
+ controller:
17
+ push_command_unexpected_apps: "Push command can process only one application!"
18
+ no_such_app: "No such app: '%{patterns}' in '%{path}'!"
19
+ dappfile_not_found: "Dappfile not found!"
20
+ config:
21
+ builder_type_conflict: 'Conflict between builder types!'
22
+ builder_type_unsupported: "Defined unsupported builder type `%{type}`!"
23
+ docker_from_not_defined: "Docker `from` not defined!"
24
+ git_artifact_unexpected_attribute: "'%{type}' git artifact doesn't have attribute '%{attr}'!"
data/lib/dapp.rb CHANGED
@@ -5,24 +5,80 @@ require 'digest'
5
5
  require 'timeout'
6
6
  require 'base64'
7
7
  require 'mixlib/shellout'
8
+ require 'securerandom'
9
+ require 'excon'
10
+ require 'json'
11
+ require 'ostruct'
12
+ require 'time'
13
+ require 'i18n'
14
+ require 'paint'
15
+
16
+ require 'net_status'
8
17
 
9
18
  require 'dapp/version'
19
+ require 'dapp/helper/cli'
20
+ require 'dapp/helper/trivia'
21
+ require 'dapp/helper/sha256'
22
+ require 'dapp/helper/i18n'
23
+ require 'dapp/helper/log'
24
+ require 'dapp/helper/paint'
25
+ require 'dapp/helper/streaming'
26
+ require 'dapp/helper/shellout'
10
27
  require 'dapp/cli'
28
+ require 'dapp/cli/base'
11
29
  require 'dapp/cli/build'
12
- require 'dapp/builder/chefify'
13
- require 'dapp/builder/centos7'
14
- require 'dapp/builder/ubuntu1404'
15
- require 'dapp/builder/ubuntu1604'
16
- require 'dapp/builder/ci_tagging'
17
- require 'dapp/builder/manual_tagging'
18
- require 'dapp/builder/git_tagging'
19
- require 'dapp/builder/cascade_tagging'
30
+ require 'dapp/cli/push'
31
+ require 'dapp/cli/smartpush'
32
+ require 'dapp/cli/list'
33
+ require 'dapp/cli/flush'
34
+ require 'dapp/cli/flush/stage_cache'
35
+ require 'dapp/cli/flush/build_cache'
20
36
  require 'dapp/filelock'
21
- require 'dapp/builder'
22
- require 'dapp/docker'
23
- require 'dapp/atomizer'
37
+ require 'dapp/config/application'
38
+ require 'dapp/config/main'
39
+ require 'dapp/config/chef'
40
+ require 'dapp/config/shell'
41
+ require 'dapp/config/git_artifact'
42
+ require 'dapp/config/docker'
43
+ require 'dapp/builder/base'
44
+ require 'dapp/builder/chef'
45
+ require 'dapp/builder/chef/berksfile'
46
+ require 'dapp/builder/shell'
47
+ require 'dapp/build/stage/base'
48
+ require 'dapp/build/stage/source_base'
49
+ require 'dapp/build/stage/from'
50
+ require 'dapp/build/stage/infra_install'
51
+ require 'dapp/build/stage/infra_setup'
52
+ require 'dapp/build/stage/app_install'
53
+ require 'dapp/build/stage/app_setup'
54
+ require 'dapp/build/stage/source_1_archive'
55
+ require 'dapp/build/stage/source_1'
56
+ require 'dapp/build/stage/source_2'
57
+ require 'dapp/build/stage/source_3'
58
+ require 'dapp/build/stage/source_4'
59
+ require 'dapp/build/stage/source_5'
60
+ require 'dapp/controller'
61
+ require 'dapp/application/git_artifact'
62
+ require 'dapp/application/logging'
63
+ require 'dapp/application/path'
64
+ require 'dapp/application/tags'
65
+ require 'dapp/application'
66
+ require 'dapp/docker_image'
67
+ require 'dapp/stage_image'
24
68
  require 'dapp/git_repo/base'
25
69
  require 'dapp/git_repo/own'
26
- require 'dapp/git_repo/chronicler'
27
70
  require 'dapp/git_repo/remote'
28
71
  require 'dapp/git_artifact'
72
+ require 'dapp/error/base'
73
+ require 'dapp/error/application'
74
+ require 'dapp/error/build'
75
+ require 'dapp/error/config'
76
+ require 'dapp/error/controller'
77
+ require 'dapp/error/shellout'
78
+
79
+ # Dapp
80
+ module Dapp
81
+ def self.root
82
+ File.expand_path('../..', __FILE__)
83
+ end
84
+ end
@@ -0,0 +1,57 @@
1
+ module Dapp
2
+ # Application
3
+ class Application
4
+ include Helper::Log
5
+ include Helper::Shellout
6
+ include Helper::I18n
7
+ include Helper::Sha256
8
+ include Logging
9
+ include GitArtifact
10
+ include Path
11
+ include Tags
12
+ include Dapp::Filelock
13
+
14
+ attr_reader :config
15
+ attr_reader :cli_options
16
+ attr_reader :ignore_git_fetch
17
+
18
+ def initialize(config:, cli_options:, ignore_git_fetch: false)
19
+ @config = config
20
+ @cli_options = cli_options
21
+
22
+ @build_path = cli_options[:build_dir] || home_path('build')
23
+ @build_cache_path = cli_options[:build_cache_dir] || home_path('build_cache')
24
+
25
+ @last_stage = Build::Stage::Source5.new(self)
26
+ @ignore_git_fetch = ignore_git_fetch
27
+ end
28
+
29
+ def build!
30
+ last_stage.build!
31
+ last_stage.save_in_cache!
32
+ end
33
+
34
+ def export!(repo)
35
+ fail Error::Application, code: :application_not_built unless last_stage.image.tagged? || dry_run?
36
+
37
+ tags.each do |tag|
38
+ image_name = [repo, tag].join(':')
39
+ if dry_run?
40
+ log_state(image_name, state: 'PUSH', styles: { status: :success })
41
+ else
42
+ log_process(image_name, process: t(code: 'status.process.pushing')) do
43
+ last_stage.image.export!(image_name, log_verbose: log_verbose?, log_time: log_time?)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def builder
50
+ @builder ||= Builder.const_get(config._builder.capitalize).new(self)
51
+ end
52
+
53
+ protected
54
+
55
+ attr_reader :last_stage
56
+ end # Application
57
+ end # Dapp
@@ -0,0 +1,26 @@
1
+ module Dapp
2
+ # Application
3
+ class Application
4
+ # GitArtifact
5
+ module GitArtifact
6
+ def git_artifacts
7
+ [*local_git_artifacts, *remote_git_artifacts].compact
8
+ end
9
+
10
+ def local_git_artifacts
11
+ @local_git_artifact_list ||= Array(config._git_artifact._local).map do |ga_config|
12
+ repo = GitRepo::Own.new(self)
13
+ Dapp::GitArtifact.new(repo, **ga_config._artifact_options)
14
+ end
15
+ end
16
+
17
+ def remote_git_artifacts
18
+ @remote_git_artifact_list ||= Array(config._git_artifact._remote).map do |ga_config|
19
+ repo = GitRepo::Remote.new(self, ga_config._name, url: ga_config._url, ssh_key_path: ga_config._ssh_key_path)
20
+ repo.fetch!(ga_config._branch)
21
+ Dapp::GitArtifact.new(repo, **ga_config._artifact_options)
22
+ end
23
+ end
24
+ end # GitArtifact
25
+ end # Application
26
+ end # Dapp
@@ -0,0 +1,120 @@
1
+ module Dapp
2
+ # Application
3
+ class Application
4
+ # Logging
5
+ module Logging
6
+ def log?
7
+ !log_quiet?
8
+ end
9
+
10
+ def log_quiet?
11
+ cli_options[:log_quiet]
12
+ end
13
+
14
+ def log_verbose?
15
+ cli_options[:log_verbose]
16
+ end
17
+
18
+ def log_time?
19
+ cli_options[:log_time]
20
+ end
21
+
22
+ def dry_run?
23
+ cli_options[:dry_run]
24
+ end
25
+
26
+ DEFAULT_STYLE = {
27
+ message: :step,
28
+ process: :secondary,
29
+ status: :secondary,
30
+ success: :success,
31
+ failed: :warning,
32
+ time: :default
33
+ }.freeze
34
+
35
+ def log_state(message, state:, styles: {})
36
+ styles[:message] ||= DEFAULT_STYLE[:message]
37
+ styles[:status] ||= DEFAULT_STYLE[:status]
38
+
39
+ message = slice(message)
40
+ state = rjust(state, message)
41
+ formatted_message = paint_string(message, styles[:message])
42
+ formatted_status = paint_string(state, styles[:status])
43
+
44
+ log "#{formatted_message}#{formatted_status}"
45
+ end
46
+
47
+ def log_process(message, process: nil, short: false, style: {}, &blk)
48
+ style[:message] ||= DEFAULT_STYLE[:message]
49
+ style[:process] ||= DEFAULT_STYLE[:process]
50
+ style[:failed] ||= DEFAULT_STYLE[:failed]
51
+ style[:success] ||= DEFAULT_STYLE[:success]
52
+
53
+ if log_verbose? && !short
54
+ process ||= t(code: 'status.process.default')
55
+ log_process_verbose(message, process: process, style: style, &blk)
56
+ else
57
+ log_process_short(message, style: style, &blk)
58
+ end
59
+ end
60
+
61
+ def log_secondary_process(message, **kvargs, &blk)
62
+ log_process(message, **kvargs.merge(style: { message: :secondary, success: :secondary }), &blk)
63
+ end
64
+
65
+ def log_process_verbose(message, process:, style: {}, &blk)
66
+ process = paint_string(rjust(process, message), style[:process])
67
+ info = paint_string(message, style[:message]) + process
68
+ success_message = paint_string(slice(message), style[:message]) +
69
+ paint_string(rjust(t(code: 'status.success.default'), message), style[:success])
70
+ failed_message = paint_string(slice(message) +
71
+ rjust(t(code: 'status.failed.default'), message), style[:failed])
72
+ log_process_default(info, success_message, failed_message, &blk)
73
+ end
74
+
75
+ def log_process_short(message, style: {}, &blk)
76
+ info = "#{paint_string(slice(message), style[:message])} ... "
77
+ success_message = paint_string(rjust(t(code: 'status.success.default'), info), style[:success])
78
+ failed_message = paint_string(rjust(t(code: 'status.failed.default'), info), style[:failed])
79
+ log_process_default(info, success_message, failed_message, inline: true, &blk)
80
+ end
81
+
82
+ def log_process_default(info, success_message, failed_message, inline: false)
83
+ log info, inline: inline
84
+ message = success_message
85
+ start = Time.now
86
+ yield
87
+ rescue Error::Base, SignalException, StandardError => _e
88
+ message = failed_message
89
+ raise
90
+ ensure
91
+ time = paint_string("#{(Time.now - start).round(2)} sec", DEFAULT_STYLE[:time])
92
+ log "#{message} #{time}", indent: !inline, time: !inline
93
+ end
94
+
95
+ def rjust(str, start_string)
96
+ str.rjust(free_space(start_string))
97
+ end
98
+
99
+ def slice(str)
100
+ if (index = free_space(t(code: 'state.using_cache'))) >= 0 # free space by longest status
101
+ str.slice(0..index)
102
+ else
103
+ str.slice(0)
104
+ end
105
+ end
106
+
107
+ def free_space(str)
108
+ base_time = log_time? ? log_time.length : 0
109
+ indent = log_indent.length
110
+ str = Paint.unpaint(str.to_s).length
111
+ time = 15
112
+ terminal_width - base_time - str - indent - time
113
+ end
114
+
115
+ def terminal_width
116
+ @terminal_width ||= `tput cols`.strip.to_i
117
+ end
118
+ end # Logging
119
+ end # Application
120
+ end # Dapp
@@ -0,0 +1,31 @@
1
+ module Dapp
2
+ # Application
3
+ class Application
4
+ # Path
5
+ module Path
6
+ def build_cache_path(*path)
7
+ make_path(@build_cache_path, *path).expand_path.tap do |p|
8
+ FileUtils.mkdir_p p.parent
9
+ end
10
+ end
11
+
12
+ def home_path(*path)
13
+ make_path(config._home_path, *path).expand_path
14
+ end
15
+
16
+ def build_path(*path)
17
+ make_path(@build_path, *path).expand_path.tap { |p| FileUtils.mkdir_p p.parent }
18
+ end
19
+
20
+ def container_build_path(*path)
21
+ make_path('/.build', *path)
22
+ end
23
+
24
+ private
25
+
26
+ def make_path(base, *path)
27
+ path.compact.map(&:to_s).inject(Pathname.new(base), &:+)
28
+ end
29
+ end # Path
30
+ end # Application
31
+ end # Dapp
@@ -0,0 +1,65 @@
1
+ module Dapp
2
+ # Application
3
+ class Application
4
+ # Tags
5
+ module Tags
6
+ protected
7
+
8
+ def git_repo
9
+ @git_repo ||= GitRepo::Own.new(self)
10
+ end
11
+
12
+ def tags
13
+ tags = simple_tags + branch_tags + commit_tags + build_tags + ci_tags
14
+ tags << :latest if tags.empty?
15
+ tags
16
+ end
17
+
18
+ def simple_tags
19
+ cli_options[:tag]
20
+ end
21
+
22
+ def branch_tags
23
+ return [] unless cli_options[:tag_branch]
24
+ fail Error::Application, code: :git_branch_without_name if (branch = git_repo.branch) == 'HEAD'
25
+ [branch]
26
+ end
27
+
28
+ def commit_tags
29
+ return [] unless cli_options[:tag_commit]
30
+ commit = git_repo.latest_commit
31
+ [commit]
32
+ end
33
+
34
+ def build_tags
35
+ return [] unless cli_options[:tag_build_id]
36
+
37
+ if ENV['GITLAB_CI']
38
+ build_id = ENV['CI_BUILD_ID']
39
+ elsif ENV['TRAVIS']
40
+ build_id = ENV['TRAVIS_BUILD_NUMBER']
41
+ else
42
+ fail Error::Application, code: :ci_environment_required
43
+ end
44
+
45
+ [build_id]
46
+ end
47
+
48
+ def ci_tags
49
+ return [] unless cli_options[:tag_ci]
50
+
51
+ if ENV['GITLAB_CI']
52
+ branch = ENV['CI_BUILD_REF_NAME']
53
+ tag = ENV['CI_BUILD_TAG']
54
+ elsif ENV['TRAVIS']
55
+ branch = ENV['TRAVIS_BRANCH']
56
+ tag = ENV['TRAVIS_TAG']
57
+ else
58
+ fail Error::Application, code: :ci_environment_required
59
+ end
60
+
61
+ [branch, tag].compact
62
+ end
63
+ end # Tags
64
+ end # Application
65
+ end # Dapp