| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  | require "commands/deploy" | 
					
						
							|  |  |  | require "commands/status" | 
					
						
							|  |  |  | require "commands/server" | 
					
						
							|  |  |  | require "db/mongo/models/project" | 
					
						
							|  |  |  | require "workers/project_test_worker" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module Devops | 
					
						
							|  |  |  |   module Version2_0 | 
					
						
							|  |  |  |     module Handler | 
					
						
							|  |  |  |       class Project | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         extend DeployCommands | 
					
						
							|  |  |  |         extend StatusCommands | 
					
						
							|  |  |  |         extend ServerCommands | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def self.get_projects | 
					
						
							|  |  |  |           lambda { | 
					
						
							| 
									
										
										
										
											2015-02-19 11:27:56 +03:00
										 |  |  |             check_privileges("project", "r") | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             fields = [] | 
					
						
							|  |  |  |             if params.key?("fields") and params["fields"].is_a?(Array) | 
					
						
							| 
									
										
										
										
											2015-03-06 12:20:30 +03:00
										 |  |  |               Devops::Model::Project.fields.each do |k| | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |                 fields.push k if params["fields"].include?(k) | 
					
						
							|  |  |  |               end | 
					
						
							|  |  |  |             end | 
					
						
							| 
									
										
										
										
											2015-03-03 12:46:35 +03:00
										 |  |  |             archived = params.include?("archived") | 
					
						
							|  |  |  |             json settings.mongo.projects(nil, nil, fields, archived).map {|p| p.to_hash} | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |           } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def self.get_project | 
					
						
							|  |  |  |           lambda { | 
					
						
							| 
									
										
										
										
											2015-02-19 11:27:56 +03:00
										 |  |  |             check_privileges("project", "r") | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             json settings.mongo.project(params[:project]) | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def self.get_project_servers | 
					
						
							|  |  |  |           lambda { | 
					
						
							| 
									
										
										
										
											2015-02-19 11:27:56 +03:00
										 |  |  |             check_privileges("project", "r") | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             settings.mongo.project(params[:project]) | 
					
						
							|  |  |  |             json settings.mongo.servers(params[:project], params[:deploy_env]).map{|s| s.to_hash} | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # TODO: multi project | 
					
						
							|  |  |  |         def self.create_project | 
					
						
							|  |  |  |           lambda { | 
					
						
							| 
									
										
										
										
											2015-02-19 11:27:56 +03:00
										 |  |  |             check_privileges("project", "w") | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             body = create_object_from_json_body | 
					
						
							|  |  |  |             check_string(body["name"], "Parameter 'name' must be a not empty string") | 
					
						
							|  |  |  |             check_array(body["deploy_envs"], "Parameter 'deploy_envs' must be a not empty array of objects", Hash) | 
					
						
							| 
									
										
										
										
											2015-03-06 12:20:30 +03:00
										 |  |  |             p = Devops::Model::Project.new(body) | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             halt_response("Project '#{p.id}' already exist") if settings.mongo.is_project_exists?(p) | 
					
						
							| 
									
										
										
										
											2015-03-11 15:10:05 +03:00
										 |  |  |             puts "USER: #{request.env['REMOTE_USER']}" | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             p.add_authorized_user [request.env['REMOTE_USER']] | 
					
						
							|  |  |  |             settings.mongo.project_insert p | 
					
						
							|  |  |  |             roles_res = "" | 
					
						
							|  |  |  |             if p.multi? | 
					
						
							|  |  |  |               logger.info "Project '#{p.id}' with type 'multi' created" | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |               logger.info "Project '#{p.id}' created" | 
					
						
							|  |  |  |               roles = Project.create_roles p.id, p.deploy_envs, logger | 
					
						
							|  |  |  |               roles_res = ". " + Project.create_roles_response(roles) | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |             res = "Created" + roles_res | 
					
						
							|  |  |  |             create_response(res, nil, 201) | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # TODO: multi project | 
					
						
							|  |  |  |         def self.update_project | 
					
						
							|  |  |  |           lambda { | 
					
						
							| 
									
										
										
										
											2015-02-19 11:27:56 +03:00
										 |  |  |             check_privileges("project", "w") | 
					
						
							| 
									
										
										
										
											2015-03-06 12:20:30 +03:00
										 |  |  |             project = Devops::Model::Project.new(create_object_from_json_body) | 
					
						
							| 
									
										
										
										
											2015-02-19 14:16:07 +03:00
										 |  |  |             project.id = params[:project] | 
					
						
							|  |  |  |             old_project = settings.mongo.project params[:project] | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             settings.mongo.project_update project | 
					
						
							| 
									
										
										
										
											2015-03-06 12:20:30 +03:00
										 |  |  |             roles = Devops::Model::Project.create_new_roles(old_project, project, logger) | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             info = "Project '#{project.id}' has been updated." + Project.create_roles_response(roles) | 
					
						
							|  |  |  |             create_response(info) | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # TODO: multi project | 
					
						
							|  |  |  |         def self.update_project_users | 
					
						
							|  |  |  |           lambda { | 
					
						
							| 
									
										
										
										
											2015-02-19 11:27:56 +03:00
										 |  |  |             check_privileges("project", "w") | 
					
						
							|  |  |  |             body = create_object_from_json_body | 
					
						
							|  |  |  |             users = check_array(body["users"], "Parameter 'users' must be a not empty array of strings") | 
					
						
							|  |  |  |             deploy_env = check_string(body["deploy_env"], "Parameter 'deploy_env' must be a not empty string", true) | 
					
						
							|  |  |  |             project = settings.mongo.project(params[:id]) | 
					
						
							|  |  |  |             users = settings.mongo.users(users).map{|u| u.id} | 
					
						
							|  |  |  |             buf = users - users | 
					
						
							|  |  |  |             project.add_authorized_user users, deploy_env | 
					
						
							|  |  |  |             settings.mongo.project_update(project) | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             info = "Users '#{users.join("', '")}' have been added to '#{params[:id]}' project's authorized users" | 
					
						
							|  |  |  |             info << ", invalid users: '#{buf.join("', '")}'" unless buf.empty? | 
					
						
							|  |  |  |             create_response(info) | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # TODO: multi project | 
					
						
							|  |  |  |         def self.delete_project_users | 
					
						
							|  |  |  |           lambda { | 
					
						
							| 
									
										
										
										
											2015-02-19 11:27:56 +03:00
										 |  |  |             check_privileges("project", "w") | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             @project.remove_authorized_user @users, @deploy_env | 
					
						
							|  |  |  |             settings.mongo.project_update @project | 
					
						
							|  |  |  |             info = "Users '#{@users.join("', '")}' have been removed from '#{params[:id]}' project's authorized users" | 
					
						
							|  |  |  |             create_response(info) | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # TODO: multi project | 
					
						
							|  |  |  |         def self.set_project_env_run_list | 
					
						
							|  |  |  |           lambda { | 
					
						
							| 
									
										
										
										
											2015-02-19 11:27:56 +03:00
										 |  |  |             check_privileges("project", "w") | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             list = create_object_from_json_body(Array) | 
					
						
							|  |  |  |             check_array(list, "Body must contains not empty array of strings") | 
					
						
							|  |  |  |             project = settings.mongo.project(params[:id]) | 
					
						
							|  |  |  |             env = project.deploy_env params[:env] | 
					
						
							|  |  |  |             env.run_list = list | 
					
						
							|  |  |  |             settings.mongo.project_update project | 
					
						
							|  |  |  |             create_response("Updated environment '#{env.identifier}' with run_list '#{env.run_list.inspect}' in project '#{project.id}'") | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def self.delete_project | 
					
						
							|  |  |  |           lambda { | 
					
						
							| 
									
										
										
										
											2015-02-19 11:27:56 +03:00
										 |  |  |             check_privileges("project", "w") | 
					
						
							| 
									
										
										
										
											2015-02-19 14:16:07 +03:00
										 |  |  |             servers = settings.mongo.servers params[:project] | 
					
						
							|  |  |  |             raise DependencyError.new "Deleting #{params[:project]} is forbidden: Project has servers" if !servers.empty? | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             body = create_object_from_json_body(Hash, true) | 
					
						
							|  |  |  |             deploy_env = unless body.nil? | 
					
						
							|  |  |  |               check_string(body["deploy_env"], "Parameter 'deploy_env' should be a not empty string", true) | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |             info = if deploy_env.nil? | 
					
						
							| 
									
										
										
										
											2015-02-19 14:16:07 +03:00
										 |  |  |               settings.mongo.project_delete(params[:project]) | 
					
						
							|  |  |  |               "Project '#{params[:project]}' is deleted" | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             else | 
					
						
							| 
									
										
										
										
											2015-02-19 14:16:07 +03:00
										 |  |  |               project = settings.mongo.project(params[:project]) | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |               project.remove_env params[:deploy_env] | 
					
						
							|  |  |  |               settings.mongo.project_update project | 
					
						
							| 
									
										
										
										
											2015-02-19 14:16:07 +03:00
										 |  |  |               "Project '#{params[:project]}'. Deploy environment '#{params[:deploy_env]}' has been deleted" | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             end | 
					
						
							|  |  |  |             create_response(info) | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def self.deploy_project | 
					
						
							|  |  |  |           lambda { | 
					
						
							| 
									
										
										
										
											2015-02-19 11:27:56 +03:00
										 |  |  |             check_privileges("project", "x") | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             obj = create_object_from_json_body | 
					
						
							|  |  |  |             check_string(obj["deploy_env"], "Parameter 'deploy_env' should be a not empty string", true) | 
					
						
							|  |  |  |             check_array(obj["servers"], "Parameter 'servers' should be a not empty array of strings", String, true) | 
					
						
							|  |  |  |             project = settings.mongo.project(params[:id]) | 
					
						
							|  |  |  |             servers = settings.mongo.servers(params[:id], obj["deploy_env"], obj["servers"], true) | 
					
						
							|  |  |  |             keys = {} | 
					
						
							|  |  |  |             if obj.key?("trace") | 
					
						
							|  |  |  |               stream() do |out| | 
					
						
							|  |  |  |                 begin | 
					
						
							|  |  |  |                   out << (servers.empty? ? "No reserved servers to deploy\n" : "Deploy servers: '#{servers.map{|s| s.chef_node_name}.join("', '")}'\n") | 
					
						
							|  |  |  |                   status = [] | 
					
						
							|  |  |  |                   servers.each do |s| | 
					
						
							|  |  |  |                     logger.debug "Deploy server: #{s.inspect}" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     begin | 
					
						
							|  |  |  |                       settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] | 
					
						
							|  |  |  |                     rescue InvalidPrivileges, RecordNotFound  => e | 
					
						
							|  |  |  |                       out << e.message + "\n" | 
					
						
							|  |  |  |                       status.push 2
 | 
					
						
							|  |  |  |                       next | 
					
						
							|  |  |  |                     end | 
					
						
							|  |  |  |                     unless keys.key? s.key | 
					
						
							|  |  |  |                       k = settings.mongo.key s.key | 
					
						
							|  |  |  |                       keys[s.key] = k.path | 
					
						
							|  |  |  |                     end | 
					
						
							|  |  |  |                     status.push(deploy_server(out, s, keys[s.key])) | 
					
						
							|  |  |  |                   end | 
					
						
							|  |  |  |                   out << create_status(status) | 
					
						
							|  |  |  |                 rescue IOError => e | 
					
						
							|  |  |  |                   logger.error e.message | 
					
						
							|  |  |  |                 end | 
					
						
							|  |  |  |               end | 
					
						
							|  |  |  |             else | 
					
						
							| 
									
										
										
										
											2015-03-06 12:20:30 +03:00
										 |  |  |               dir = DevopsConfig[:report_dir_v2] | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |               files = [] | 
					
						
							|  |  |  |               uri = URI.parse(request.url) | 
					
						
							|  |  |  |               servers.each do |s| | 
					
						
							|  |  |  |                 project = begin | 
					
						
							|  |  |  |                   settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] | 
					
						
							|  |  |  |                 rescue InvalidPrivileges, RecordNotFound  => e | 
					
						
							|  |  |  |                   next | 
					
						
							|  |  |  |                 end | 
					
						
							| 
									
										
										
										
											2015-03-06 12:20:30 +03:00
										 |  |  |                 jid = DeployWorker.perform_async(dir, s.to_hash, [], DevopsConfig.config) | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |                 logger.info "Job '#{jid}' has been started" | 
					
						
							| 
									
										
										
										
											2015-03-06 12:20:30 +03:00
										 |  |  |                 uri.path =  "#{DevopsConfig[:url_prefix]}/v2.0/report/" + jid | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |                 files.push uri.to_s | 
					
						
							|  |  |  |               end | 
					
						
							|  |  |  |               json files | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-03 12:46:35 +03:00
										 |  |  |         def self.archive_project | 
					
						
							|  |  |  |           lambda { | 
					
						
							|  |  |  |             check_privileges("project", "w") | 
					
						
							|  |  |  |             project = settings.mongo.project(params[:project]) | 
					
						
							|  |  |  |             if project.nil? | 
					
						
							|  |  |  |               create_response("Project '#{params[:project]}' not found", nil, 404) | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |               settings.mongo.archive_project(params[:project]) | 
					
						
							|  |  |  |               info = "Project '#{params[:project]}' has been archived" | 
					
						
							|  |  |  |               create_response(info) | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def self.unarchive_project | 
					
						
							|  |  |  |           lambda { | 
					
						
							|  |  |  |             check_privileges("project", "w") | 
					
						
							|  |  |  |             project = settings.mongo.project(params[:project]) | 
					
						
							|  |  |  |             if project.nil? | 
					
						
							|  |  |  |               create_response("Project '#{params[:project]}' not found", nil, 404) | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |               settings.mongo.unarchive_project(params[:project]) | 
					
						
							|  |  |  |               info = "Project '#{params[:project]}' has been unarchived" | 
					
						
							|  |  |  |               create_response(info) | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |         def self.test_project | 
					
						
							|  |  |  |           lambda { | 
					
						
							| 
									
										
										
										
											2015-02-19 11:27:56 +03:00
										 |  |  |             check_privileges("project", "r") | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             project = settings.mongo.project(params[:id]) | 
					
						
							|  |  |  |             env = project.deploy_env params[:env] | 
					
						
							|  |  |  |             logger.info "Test project '#{project.id}' and environment '#{env.identifier}'" | 
					
						
							| 
									
										
										
										
											2015-03-06 12:20:30 +03:00
										 |  |  |             if env.provider == ::Provider::Static::PROVIDER | 
					
						
							|  |  |  |               msg = "Can not test environment with provider '#{::Provider::Static::PROVIDER}'" | 
					
						
							| 
									
										
										
										
											2015-03-03 12:46:35 +03:00
										 |  |  |               Logger.warn msg | 
					
						
							|  |  |  |               return [400, msg] | 
					
						
							|  |  |  |             end | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-03-06 12:20:30 +03:00
										 |  |  |             dir = DevopsConfig[:report_dir_v2] | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             uri = URI.parse(request.url) | 
					
						
							|  |  |  |             p = { | 
					
						
							|  |  |  |               :project => project.id, | 
					
						
							|  |  |  |               :env => env.identifier, | 
					
						
							|  |  |  |               :user => request.env['REMOTE_USER'] | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2015-03-06 12:20:30 +03:00
										 |  |  |             jid = ProjectTestWorker.perform_async(dir, p, DevopsConfig.config) | 
					
						
							| 
									
										
										
										
											2015-03-03 12:46:35 +03:00
										 |  |  |             Worker.set_status jid, Worker::STATUS::IN_QUEUE | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             logger.info "Job '#{jid}' has been created" | 
					
						
							| 
									
										
										
										
											2015-03-06 12:20:30 +03:00
										 |  |  |             uri.path = "#{DevopsConfig[:url_prefix]}/v2.0/report/" + jid | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |             files = [uri.to_s] | 
					
						
							|  |  |  |             sleep 1
 | 
					
						
							|  |  |  |             json files | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def self.create_roles project_id, envs, logger | 
					
						
							|  |  |  |           all_roles = KnifeCommands.roles | 
					
						
							|  |  |  |           return " Can't get roles list" if all_roles.nil? | 
					
						
							|  |  |  |           roles = {:new => [], :error => [], :exist => []} | 
					
						
							|  |  |  |           envs.each do |e| | 
					
						
							|  |  |  |             role_name = KnifeCommands.role_name(project_id, e.identifier) | 
					
						
							|  |  |  |             begin | 
					
						
							|  |  |  |               if all_roles.include? role_name | 
					
						
							|  |  |  |                 roles[:exist].push role_name | 
					
						
							|  |  |  |               else | 
					
						
							|  |  |  |                 KnifeCommands.create_role role_name, project_id, e.identifier | 
					
						
							|  |  |  |                 roles[:new].push role_name | 
					
						
							|  |  |  |                 logger.info "Role '#{role_name}' created" | 
					
						
							|  |  |  |               end | 
					
						
							|  |  |  |             rescue => er | 
					
						
							|  |  |  |               roles[:error].push role_name | 
					
						
							|  |  |  |               logger.error "Role '#{role_name}' can not be created: #{er.message}" | 
					
						
							|  |  |  |             end | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |           roles | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def self.create_new_roles old_project, new_project, logger | 
					
						
							|  |  |  |           old_project.deploy_envs.each do |e| | 
					
						
							|  |  |  |             new_project.remove_env(e.identifier) | 
					
						
							|  |  |  |           end | 
					
						
							| 
									
										
										
										
											2015-03-06 12:20:30 +03:00
										 |  |  |           Devops::Model::Project.create_roles new_project.id, new_project.deploy_envs, logger | 
					
						
							| 
									
										
										
										
											2014-12-22 14:22:04 +03:00
										 |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def self.create_roles_response roles | 
					
						
							|  |  |  |           if roles.is_a?(String) | 
					
						
							|  |  |  |             roles | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             info = "" | 
					
						
							|  |  |  |             info += " Project roles '#{roles[:new].join("', '")}' have been automaticaly created" unless roles[:new].empty? | 
					
						
							|  |  |  |             info += " Project roles '#{roles[:exist].join("', '")}' weren't created because they exist" unless roles[:exist].empty? | 
					
						
							|  |  |  |             info += " Project roles '#{roles[:error].join("', '")}' weren't created because of internal error" unless roles[:error].empty? | 
					
						
							|  |  |  |             info | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end | 
					
						
							|  |  |  | 
 |