choria-mcorpc-support 0.0.1

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 (133) hide show
  1. checksums.yaml +7 -0
  2. data/bin/mco +64 -0
  3. data/lib/mcollective.rb +63 -0
  4. data/lib/mcollective/agent.rb +5 -0
  5. data/lib/mcollective/agents.rb +149 -0
  6. data/lib/mcollective/aggregate.rb +85 -0
  7. data/lib/mcollective/aggregate/average.ddl +33 -0
  8. data/lib/mcollective/aggregate/average.rb +29 -0
  9. data/lib/mcollective/aggregate/base.rb +40 -0
  10. data/lib/mcollective/aggregate/result.rb +9 -0
  11. data/lib/mcollective/aggregate/result/base.rb +25 -0
  12. data/lib/mcollective/aggregate/result/collection_result.rb +19 -0
  13. data/lib/mcollective/aggregate/result/numeric_result.rb +13 -0
  14. data/lib/mcollective/aggregate/sum.ddl +33 -0
  15. data/lib/mcollective/aggregate/sum.rb +18 -0
  16. data/lib/mcollective/aggregate/summary.ddl +33 -0
  17. data/lib/mcollective/aggregate/summary.rb +53 -0
  18. data/lib/mcollective/application.rb +365 -0
  19. data/lib/mcollective/application/completion.rb +104 -0
  20. data/lib/mcollective/application/describe_filter.rb +87 -0
  21. data/lib/mcollective/application/facts.rb +62 -0
  22. data/lib/mcollective/application/find.rb +23 -0
  23. data/lib/mcollective/application/help.rb +28 -0
  24. data/lib/mcollective/application/inventory.rb +344 -0
  25. data/lib/mcollective/application/ping.rb +82 -0
  26. data/lib/mcollective/application/plugin.rb +369 -0
  27. data/lib/mcollective/application/rpc.rb +111 -0
  28. data/lib/mcollective/applications.rb +134 -0
  29. data/lib/mcollective/cache.rb +145 -0
  30. data/lib/mcollective/client.rb +353 -0
  31. data/lib/mcollective/config.rb +245 -0
  32. data/lib/mcollective/connector.rb +18 -0
  33. data/lib/mcollective/connector/base.rb +26 -0
  34. data/lib/mcollective/data.rb +91 -0
  35. data/lib/mcollective/data/agent_data.ddl +22 -0
  36. data/lib/mcollective/data/agent_data.rb +17 -0
  37. data/lib/mcollective/data/base.rb +67 -0
  38. data/lib/mcollective/data/collective_data.ddl +20 -0
  39. data/lib/mcollective/data/collective_data.rb +9 -0
  40. data/lib/mcollective/data/fact_data.ddl +28 -0
  41. data/lib/mcollective/data/fact_data.rb +55 -0
  42. data/lib/mcollective/data/fstat_data.ddl +89 -0
  43. data/lib/mcollective/data/fstat_data.rb +56 -0
  44. data/lib/mcollective/data/result.rb +45 -0
  45. data/lib/mcollective/ddl.rb +113 -0
  46. data/lib/mcollective/ddl/agentddl.rb +253 -0
  47. data/lib/mcollective/ddl/base.rb +217 -0
  48. data/lib/mcollective/ddl/dataddl.rb +56 -0
  49. data/lib/mcollective/ddl/discoveryddl.rb +52 -0
  50. data/lib/mcollective/ddl/validatorddl.rb +6 -0
  51. data/lib/mcollective/discovery.rb +143 -0
  52. data/lib/mcollective/discovery/flatfile.ddl +11 -0
  53. data/lib/mcollective/discovery/flatfile.rb +48 -0
  54. data/lib/mcollective/discovery/mc.ddl +11 -0
  55. data/lib/mcollective/discovery/mc.rb +30 -0
  56. data/lib/mcollective/discovery/stdin.ddl +11 -0
  57. data/lib/mcollective/discovery/stdin.rb +68 -0
  58. data/lib/mcollective/exceptions.rb +28 -0
  59. data/lib/mcollective/facts.rb +39 -0
  60. data/lib/mcollective/facts/base.rb +100 -0
  61. data/lib/mcollective/facts/yaml_facts.rb +65 -0
  62. data/lib/mcollective/generators.rb +7 -0
  63. data/lib/mcollective/generators/agent_generator.rb +51 -0
  64. data/lib/mcollective/generators/base.rb +46 -0
  65. data/lib/mcollective/generators/data_generator.rb +51 -0
  66. data/lib/mcollective/generators/templates/action_snippet.erb +13 -0
  67. data/lib/mcollective/generators/templates/data_input_snippet.erb +7 -0
  68. data/lib/mcollective/generators/templates/ddl.erb +8 -0
  69. data/lib/mcollective/generators/templates/plugin.erb +7 -0
  70. data/lib/mcollective/log.rb +118 -0
  71. data/lib/mcollective/logger.rb +5 -0
  72. data/lib/mcollective/logger/base.rb +77 -0
  73. data/lib/mcollective/logger/console_logger.rb +61 -0
  74. data/lib/mcollective/logger/file_logger.rb +53 -0
  75. data/lib/mcollective/logger/syslog_logger.rb +53 -0
  76. data/lib/mcollective/matcher.rb +224 -0
  77. data/lib/mcollective/matcher/parser.rb +128 -0
  78. data/lib/mcollective/matcher/scanner.rb +241 -0
  79. data/lib/mcollective/message.rb +248 -0
  80. data/lib/mcollective/monkey_patches.rb +152 -0
  81. data/lib/mcollective/optionparser.rb +197 -0
  82. data/lib/mcollective/pluginmanager.rb +180 -0
  83. data/lib/mcollective/pluginpackager.rb +98 -0
  84. data/lib/mcollective/pluginpackager/agent_definition.rb +94 -0
  85. data/lib/mcollective/pluginpackager/debpackage_packager.rb +237 -0
  86. data/lib/mcollective/pluginpackager/modulepackage_packager.rb +127 -0
  87. data/lib/mcollective/pluginpackager/ospackage_packager.rb +59 -0
  88. data/lib/mcollective/pluginpackager/rpmpackage_packager.rb +180 -0
  89. data/lib/mcollective/pluginpackager/standard_definition.rb +69 -0
  90. data/lib/mcollective/pluginpackager/templates/debian/Makefile.erb +7 -0
  91. data/lib/mcollective/pluginpackager/templates/debian/changelog.erb +5 -0
  92. data/lib/mcollective/pluginpackager/templates/debian/compat.erb +1 -0
  93. data/lib/mcollective/pluginpackager/templates/debian/control.erb +15 -0
  94. data/lib/mcollective/pluginpackager/templates/debian/copyright.erb +8 -0
  95. data/lib/mcollective/pluginpackager/templates/debian/rules.erb +6 -0
  96. data/lib/mcollective/pluginpackager/templates/module/Modulefile.erb +5 -0
  97. data/lib/mcollective/pluginpackager/templates/module/README.md.erb +37 -0
  98. data/lib/mcollective/pluginpackager/templates/module/_manifest.pp.erb +9 -0
  99. data/lib/mcollective/pluginpackager/templates/redhat/rpm_spec.erb +63 -0
  100. data/lib/mcollective/registration/base.rb +91 -0
  101. data/lib/mcollective/rpc.rb +182 -0
  102. data/lib/mcollective/rpc/actionrunner.rb +158 -0
  103. data/lib/mcollective/rpc/agent.rb +374 -0
  104. data/lib/mcollective/rpc/audit.rb +38 -0
  105. data/lib/mcollective/rpc/client.rb +1066 -0
  106. data/lib/mcollective/rpc/helpers.rb +321 -0
  107. data/lib/mcollective/rpc/progress.rb +63 -0
  108. data/lib/mcollective/rpc/reply.rb +87 -0
  109. data/lib/mcollective/rpc/request.rb +86 -0
  110. data/lib/mcollective/rpc/result.rb +90 -0
  111. data/lib/mcollective/rpc/stats.rb +294 -0
  112. data/lib/mcollective/runnerstats.rb +90 -0
  113. data/lib/mcollective/security.rb +26 -0
  114. data/lib/mcollective/security/base.rb +244 -0
  115. data/lib/mcollective/shell.rb +126 -0
  116. data/lib/mcollective/ssl.rb +285 -0
  117. data/lib/mcollective/util.rb +579 -0
  118. data/lib/mcollective/validator.rb +85 -0
  119. data/lib/mcollective/validator/array_validator.ddl +7 -0
  120. data/lib/mcollective/validator/array_validator.rb +9 -0
  121. data/lib/mcollective/validator/ipv4address_validator.ddl +7 -0
  122. data/lib/mcollective/validator/ipv4address_validator.rb +16 -0
  123. data/lib/mcollective/validator/ipv6address_validator.ddl +7 -0
  124. data/lib/mcollective/validator/ipv6address_validator.rb +16 -0
  125. data/lib/mcollective/validator/length_validator.ddl +7 -0
  126. data/lib/mcollective/validator/length_validator.rb +11 -0
  127. data/lib/mcollective/validator/regex_validator.ddl +7 -0
  128. data/lib/mcollective/validator/regex_validator.rb +9 -0
  129. data/lib/mcollective/validator/shellsafe_validator.ddl +7 -0
  130. data/lib/mcollective/validator/shellsafe_validator.rb +13 -0
  131. data/lib/mcollective/validator/typecheck_validator.ddl +7 -0
  132. data/lib/mcollective/validator/typecheck_validator.rb +28 -0
  133. metadata +215 -0
@@ -0,0 +1,46 @@
1
+ module MCollective
2
+ module Generators
3
+ class Base
4
+ attr_accessor :meta, :plugin_name, :mod_name
5
+ def initialize(name, description, author, license, version, url, timeout)
6
+ @meta = {:name => name,
7
+ :description => description,
8
+ :author => author,
9
+ :license => license,
10
+ :version => version,
11
+ :url => url,
12
+ :timeout => timeout}
13
+ end
14
+
15
+ def create_metadata_string
16
+ ddl_template = File.read(File.join(File.dirname(__FILE__), "templates", "ddl.erb"))
17
+ ERB.new(ddl_template, nil, "-").result(binding)
18
+ end
19
+
20
+ def create_plugin_string
21
+ plugin_template = File.read(File.join(File.dirname(__FILE__), "templates", "plugin.erb"))
22
+ ERB.new(plugin_template, nil, "-").result(binding)
23
+ end
24
+
25
+ def write_plugins
26
+ begin
27
+ Dir.mkdir @plugin_name
28
+ dirname = File.join(@plugin_name, @mod_name.downcase)
29
+ Dir.mkdir dirname
30
+ puts "Created plugin directory : #{@plugin_name}"
31
+
32
+ File.open(File.join(dirname, "#{@plugin_name}.ddl"), "w"){|f| f.puts @ddl}
33
+ puts "Created DDL file : #{File.join(dirname, "#{@plugin_name}.ddl")}"
34
+
35
+ File.open(File.join(dirname, "#{@plugin_name}.rb"), "w"){|f| f.puts @plugin}
36
+ puts "Created #{@mod_name} file : #{File.join(dirname, "#{@plugin_name}.rb")}"
37
+ rescue Errno::EEXIST
38
+ raise "cannot generate '#{@plugin_name}' : plugin directory already exists."
39
+ rescue Exception => e
40
+ FileUtils.rm_rf(@plugin_name) if File.directory?(@plugin_name)
41
+ raise "cannot generate plugin - #{e}"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,51 @@
1
+ module MCollective
2
+ module Generators
3
+ class DataGenerator<Base
4
+
5
+ attr_accessor :ddl, :content
6
+
7
+ def initialize(plugin_name, outputs = [], name = nil, description = nil, author = nil ,
8
+ license = nil, version = nil, url = nil, timeout = nil)
9
+
10
+ super(name, description, author, license, version, url, timeout)
11
+ @mod_name = "Data"
12
+ @pclass = "Base"
13
+ @plugin_name = plugin_name
14
+ @outputs = outputs
15
+ @ddl = create_ddl
16
+ @content = create_plugin_content
17
+ @plugin = create_plugin_string
18
+ write_plugins
19
+ end
20
+
21
+ def create_ddl
22
+ query_text = "dataquery :description => \"Query information\" do\n"
23
+ query_text += ERB.new(File.read(File.join(File.dirname(__FILE__), "templates", "data_input_snippet.erb"))).result
24
+
25
+ @outputs.each_with_index do |output,i|
26
+ query_text += "%2s%s" % [" ", "output :#{output},\n"]
27
+ query_text += "%9s%s" % [" ", ":description => \"%DESCRIPTION%\",\n"]
28
+ query_text += "%9s%s" % [" ", ":display_as => \"%DESCRIPTION%\"\n"]
29
+ query_text += "\n" unless @outputs.size == (i + 1)
30
+ end
31
+
32
+ query_text += "end"
33
+
34
+ # Use inherited method to create metadata part of the ddl
35
+ create_metadata_string + query_text
36
+ end
37
+
38
+ def create_plugin_content
39
+ content_text = "%6s%s" % [" ", "query do |what|\n"]
40
+
41
+ @outputs.each do |output|
42
+ content_text += "%8s%s" % [" ", "result[:#{output}] = nil\n"]
43
+ end
44
+ content_text += "%6s%s" % [" ", "end\n"]
45
+
46
+ # Add actions to agent file
47
+ content_text
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,13 @@
1
+ # Example Input
2
+ input :name,
3
+ :prompt => "%PROMPT%",
4
+ :description => "%DESCRIPTION%",
5
+ :type => %TYPE%,
6
+ :validation => '%VALIDATION%',
7
+ :optional => %OPTIONAL%,
8
+ :maxlength => %MAXLENGTH%
9
+
10
+ # Example output
11
+ output :name,
12
+ :description => "%DESCRIPTION%",
13
+ :display_as => "%DISPLAYAS%"
@@ -0,0 +1,7 @@
1
+ input :query,
2
+ :prompt => "%PROMP%",
3
+ :description => "%DESCRIPTION%",
4
+ :type => %TYPE%,
5
+ :validation => %VALIDATION%,
6
+ :maxlength => %MAXLENGTH%
7
+
@@ -0,0 +1,8 @@
1
+ metadata :name => "<%= meta[:name] || "%FULLNANE%" -%>",
2
+ :description => "<%= meta[:description] || "%DESCRIPTION%" -%>",
3
+ :author => "<%= meta[:author] || "%AUTHOR%" -%>",
4
+ :license => "<%= meta[:license] || "%LICENSE%" -%>",
5
+ :version => "<%= meta[:version] || "%VERSION%" -%>",
6
+ :url => "<%= meta[:url] || "%URL%" -%>",
7
+ :timeout => <%= meta[:timeout] || "%TIMEOUT%" %>
8
+
@@ -0,0 +1,7 @@
1
+ module MCollective
2
+ module <%= @mod_name%>
3
+ class <%= @plugin_name.capitalize -%><<%= @pclass%>
4
+ <%= @content%>
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,118 @@
1
+ module MCollective
2
+ # A simple class that allows logging at various levels.
3
+ class Log
4
+ class << self
5
+ @logger = nil
6
+
7
+ # Obtain the class name of the currently configured logger
8
+ def logger
9
+ @logger.class
10
+ end
11
+
12
+ # Logs at info level
13
+ def info(msg)
14
+ log(:info, msg)
15
+ end
16
+
17
+ # Logs at warn level
18
+ def warn(msg)
19
+ log(:warn, msg)
20
+ end
21
+
22
+ # Logs at debug level
23
+ def debug(msg)
24
+ log(:debug, msg)
25
+ end
26
+
27
+ # Logs at fatal level
28
+ def fatal(msg)
29
+ log(:fatal, msg)
30
+ end
31
+
32
+ # Logs at error level
33
+ def error(msg)
34
+ log(:error, msg)
35
+ end
36
+
37
+ # handle old code that relied on this class being a singleton
38
+ def instance
39
+ self
40
+ end
41
+
42
+ # increments the active log level
43
+ def cycle_level
44
+ @logger.cycle_level if @configured
45
+ end
46
+
47
+ # reopen log files
48
+ def reopen
49
+ if @configured
50
+ @logger.reopen
51
+ end
52
+ end
53
+
54
+ # logs a message at a certain level
55
+ def log(level, msg)
56
+ configure unless @configured
57
+
58
+ raise "Unknown log level" unless [:error, :fatal, :debug, :warn, :info].include?(level)
59
+
60
+ if @logger
61
+ @logger.log(level, from, msg)
62
+ else
63
+ t = Time.new.strftime("%H:%M:%S")
64
+
65
+ STDERR.puts "#{t}: #{level}: #{from}: #{msg}"
66
+ end
67
+ end
68
+
69
+ # sets the logger class to use
70
+ def set_logger(logger)
71
+ @logger = logger
72
+ end
73
+
74
+ # configures the logger class, if the config has not yet been loaded
75
+ # we default to the console logging class and do not set @configured
76
+ # so that future calls to the log method will keep attempting to configure
77
+ # the logger till we eventually get a logging preference from the config
78
+ # module
79
+ def configure(logger=nil)
80
+ unless logger
81
+ logger_type = "console"
82
+
83
+ config = Config.instance
84
+
85
+ if config.configured
86
+ logger_type = config.logger_type
87
+ @configured = true
88
+ end
89
+
90
+ require "mcollective/logger/#{logger_type.downcase}_logger"
91
+
92
+ logger_class = MCollective::Logger.const_get("#{logger_type.capitalize}_logger")
93
+
94
+ set_logger(logger_class.new)
95
+ else
96
+ set_logger(logger)
97
+ @configured = true
98
+ end
99
+
100
+ @logger.start
101
+ rescue Exception => e
102
+ @configured = false
103
+ STDERR.puts "Could not start logger: #{e.class} #{e}"
104
+ end
105
+
106
+ # figures out the filename that called us
107
+ def from
108
+ path, line, method = execution_stack[3].split(/:(\d+)/)
109
+ "%s:%s%s" % [File.basename(path), line, method]
110
+ end
111
+
112
+ # this method is here to facilitate testing and from
113
+ def execution_stack
114
+ caller
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,5 @@
1
+ module MCollective
2
+ module Logger
3
+ require "mcollective/logger/base"
4
+ end
5
+ end
@@ -0,0 +1,77 @@
1
+ module MCollective
2
+ module Logger
3
+ # A base class for logging providers.
4
+ #
5
+ # Logging providers should provide the following:
6
+ #
7
+ # * start - all you need to do to setup your logging
8
+ # * set_logging_level - set your logging to :info, :warn, etc
9
+ # * valid_levels - a hash of maps from :info to your internal level name
10
+ # * log - what needs to be done to log a specific message
11
+ class Base
12
+ attr_reader :active_level
13
+
14
+ def initialize
15
+ @known_levels = [:debug, :info, :warn, :error, :fatal]
16
+
17
+ # Sanity check the class that impliments the logging
18
+ @known_levels.each do |lvl|
19
+ raise "Logger class did not specify a map for #{lvl}" unless valid_levels.include?(lvl)
20
+ end
21
+ end
22
+
23
+ # Figures out the next level and sets it
24
+ def cycle_level
25
+ lvl = get_next_level
26
+ set_level(lvl)
27
+
28
+ log(lvl, "", "Logging level is now #{lvl.to_s.upcase}")
29
+ end
30
+
31
+ # Sets a new level and record it in @active_level
32
+ def set_level(level)
33
+ set_logging_level(level)
34
+ @active_level = level.to_sym
35
+ end
36
+
37
+ def start
38
+ raise "The logging class did not supply a start method"
39
+ end
40
+
41
+ def log(level, from, msg)
42
+ raise "The logging class did not supply a log method"
43
+ end
44
+
45
+ def reopen
46
+ # reopen may not make sense to all Loggers, but we expect it of the API
47
+ end
48
+
49
+ private
50
+ def map_level(level)
51
+ raise "Logger class do not know how to handle #{level} messages" unless valid_levels.include?(level.to_sym)
52
+
53
+ valid_levels[level.to_sym]
54
+ end
55
+
56
+ # Gets the next level in the list, cycles down to the firt once it reaches the end
57
+ def get_next_level
58
+ # if all else fails, always go to debug mode
59
+ nextlvl = :debug
60
+
61
+ if @known_levels.index(@active_level) == (@known_levels.size - 1)
62
+ nextlvl = @known_levels.first
63
+ else
64
+ idx = @known_levels.index(@active_level) + 1
65
+ nextlvl = @known_levels[idx]
66
+ end
67
+
68
+ nextlvl
69
+ end
70
+
71
+ # Abstract methods to ensure the logging implimentations supply what they should
72
+ def valid_levels
73
+ raise "The logging class did not supply a valid_levels method"
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,61 @@
1
+ module MCollective
2
+ module Logger
3
+ # Implements a syslog based logger using the standard ruby syslog class
4
+ class Console_logger<Base
5
+ def start
6
+ set_level(:info)
7
+
8
+ config = Config.instance
9
+ set_level(config.loglevel.to_sym) if config.configured
10
+ end
11
+
12
+ def set_logging_level(level)
13
+ # nothing to do here, we ignore high levels when we log
14
+ end
15
+
16
+ def valid_levels
17
+ {:info => :info,
18
+ :warn => :warning,
19
+ :debug => :debug,
20
+ :fatal => :crit,
21
+ :error => :err}
22
+ end
23
+
24
+ def log(level, from, msg, normal_output=STDERR, last_resort_output=STDERR)
25
+ if @known_levels.index(level) >= @known_levels.index(@active_level)
26
+ time = Time.new.strftime("%Y/%m/%d %H:%M:%S")
27
+
28
+ normal_output.puts("%s %s: %s %s" % [colorize(level, level), time, from, msg])
29
+ end
30
+ rescue
31
+ # if this fails we probably cant show the user output at all,
32
+ # STDERR it as last resort
33
+ last_resort_output.puts("#{level}: #{msg}")
34
+ end
35
+
36
+ # Set some colors for various logging levels, will honor the
37
+ # color configuration option and return nothing if its configured
38
+ # not to
39
+ def color(level)
40
+ colorize = Config.instance.color
41
+
42
+ colors = {:error => Util.color(:red),
43
+ :fatal => Util.color(:red),
44
+ :warn => Util.color(:yellow),
45
+ :info => Util.color(:green),
46
+ :reset => Util.color(:reset)}
47
+
48
+ if colorize
49
+ return colors[level] || ""
50
+ else
51
+ return ""
52
+ end
53
+ end
54
+
55
+ # Helper to return a string in specific color
56
+ def colorize(level, msg)
57
+ "%s%s%s" % [ color(level), msg, color(:reset) ]
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,53 @@
1
+ require 'logger'
2
+
3
+ module MCollective
4
+ module Logger
5
+ # Impliments a file based logger using the standard ruby logger class
6
+ #
7
+ # To configure you should set:
8
+ #
9
+ # - config.logfile
10
+ # - config.keeplogs defaults to 2097152
11
+ # - config.max_log_size defaults to 5
12
+ class File_logger<Base
13
+ def start
14
+ config = Config.instance
15
+
16
+ @logger = ::Logger.new(config.logfile, config.keeplogs, config.max_log_size)
17
+ @logger.formatter = ::Logger::Formatter.new
18
+
19
+ set_level(config.loglevel.to_sym)
20
+ end
21
+
22
+ def set_logging_level(level)
23
+ @logger.level = map_level(level)
24
+ rescue Exception => e
25
+ @logger.level = ::Logger::DEBUG
26
+ log(:error, "", "Could not set logging to #{level} using debug instead: #{e.class} #{e}")
27
+ end
28
+
29
+ def valid_levels
30
+ {:info => ::Logger::INFO,
31
+ :warn => ::Logger::WARN,
32
+ :debug => ::Logger::DEBUG,
33
+ :fatal => ::Logger::FATAL,
34
+ :error => ::Logger::ERROR}
35
+ end
36
+
37
+ def log(level, from, msg)
38
+ @logger.add(map_level(level)) { "#{from} #{msg}" }
39
+ rescue
40
+ # if this fails we probably cant show the user output at all,
41
+ # STDERR it as last resort
42
+ STDERR.puts("#{level}: #{msg}")
43
+ end
44
+
45
+ def reopen
46
+ level = @logger.level
47
+ @logger.close
48
+ start
49
+ @logger.level = level
50
+ end
51
+ end
52
+ end
53
+ end