From 87929be026a34a46d7f4f5fc640e3457711a9e9f Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Fri, 8 Nov 2019 14:36:59 +0100 Subject: [PATCH] Updating service definitions format. THIS IS A BREAKING CHANGE Hopefully, this change will make it easier to remove data on service removal, but also to export and backup service data and to add per-service system users. A few obsolete service definitions have also been updated to the new format. Testing is still incomplete, you may want to wait a few commits before using this. --- services/authd.spec | 27 +++++------ services/gitea.spec | 26 ++++++---- services/kanban.spec | 8 +++ services/kanband.yaml | 11 ----- services/nginx.spec | 7 +-- services/postgresql.spec | 40 +++++++-------- services/www.spec | 3 +- src/service/environment.cr | 14 +++--- src/service/service.cr | 32 ++++++------ src/service/service_definition.cr | 81 +++++++++++++++++++------------ 10 files changed, 133 insertions(+), 116 deletions(-) create mode 100644 services/kanban.spec delete mode 100644 services/kanband.yaml diff --git a/services/authd.spec b/services/authd.spec index 29238f4..6ebddb5 100644 --- a/services/authd.spec +++ b/services/authd.spec @@ -1,22 +1,17 @@ name: authd -command: authd -K ${SERVICE_ROOT}/jwt_key -u ${SERVICE_ROOT}/passwd -g ${SERVICE_ROOT}/group -environment-variables: - - LD_LIBRARY_PATH=/usr/local/lib +command: authd -K jwt_key -u passwd -g group user: authd provides: auth -%pre-start - name: Creating IPC directory - unless-directory: /run/ipc + +%file /run/ipc + name: IPC directory command: install -d -m6777 /run/ipc -%pre-start - name: Creating JWT key - unless-file: ${SERVICE_ROOT}/jwt_key - command: head -c 64 /dev/urandom | base64 > ${SERVICE_ROOT}/jwt_key -%pre-start +%file jwt_key + name: JWT key + creation-command: head -c 64 /dev/urandom | base64 > jwt_key +%file passwd name: passwd file - unless-file: ${SERVICE_ROOT}/passwd - command: touch ${SERVICE_ROOT}/passwd -%pre-start + command: touch passwd +%file group name: group file - unless-file: ${SERVICE_ROOT}/group - command: touch ${SERVICE_ROOT}/group + command: touch group diff --git a/services/gitea.spec b/services/gitea.spec index 76f028d..8c290c9 100644 --- a/services/gitea.spec +++ b/services/gitea.spec @@ -1,15 +1,23 @@ -command: gitea -C . -w . -c ./custom/conf/app.ini +command: gitea -C . -w . -c gitea.cfg consumes: postgresql, http? requires-domain: true ports: http -%directory ${SERVICE_ROOT}/custom/conf - name: working directory +#%directory ${SERVICE_ROOT}/custom/conf +# name: working directory +# configuration: true -%configuration gitea.cfg ${SERVICE_ROOT}/custom/conf/app.ini - -%pre-start - name: gitea database creation +%file db-is-setup + name: postgresql database # 'command' is run only if this directory doesn't exist - unless-file: ${SERVICE_ROOT}/db_is_setup - command: pg_create_user.sh create_user_and_db && touch ${SERVICE_ROOT}/db_is_setup + creation-command: pg_create_user.sh create_user_and_db && touch db-is-setup + deletion-command: pg_remove_user.sh bla bla bla && rm db-is-setup + export-command: pg_export_user.sh bla bla bla # FIXME: Where/how does it export? + +# With syntaxic sugar. +%configuration gitea.cfg + +#%database +# type: postgresql +# from-token: postgresql + diff --git a/services/kanban.spec b/services/kanban.spec new file mode 100644 index 0000000..29bc408 --- /dev/null +++ b/services/kanban.spec @@ -0,0 +1,8 @@ +name: kanband +command: kanband -k ${AUTHD_ROOT}/jwt_key -S data +user: kanband +consumes: auth + +%file data + name: storage directory + creation-command: mkdir -p data && chown kanband:kanband data diff --git a/services/kanband.yaml b/services/kanband.yaml deleted file mode 100644 index ead7565..0000000 --- a/services/kanband.yaml +++ /dev/null @@ -1,11 +0,0 @@ -name: kanband -command: kanband -k /srv/%{ENVIRONMENT}/jwt_key -S /srv/%{ENVIRONMENT}/kanban -user: kanban -environment-variables: - - LD_LIBRARY_PATH=/usr/local/lib -consumes: - - token: auth -checks: - - name: storage directory creation - directory: /srv/%{ENVIRONMENT}/kanban - command: mkdir -p /srv/%{ENVIRONMENT}/kanban && chown kanban:kanban /srv/%{ENVIRONMENT}/kanban diff --git a/services/nginx.spec b/services/nginx.spec index f2e2985..5d26301 100644 --- a/services/nginx.spec +++ b/services/nginx.spec @@ -1,9 +1,6 @@ -command: nginx -c ${SERVICE_ROOT}/nginx.conf +command: nginx -c nginx.conf consumes: http? provides: www, http ports: http=80, https=443 -%directory ${SERVICE_ROOT}/ - name: working directory - -%configuration nginx.conf ${SERVICE_ROOT}/nginx.conf +%configuration nginx.conf diff --git a/services/postgresql.spec b/services/postgresql.spec index eec8db8..a66adf3 100644 --- a/services/postgresql.spec +++ b/services/postgresql.spec @@ -1,32 +1,28 @@ name: postgresql user: postgres -command: postgres -D ${SERVICE_ROOT} -k /tmp/postgresql-${ENVIRONMENT} +command: postgres -D ${SERVICE_ROOT}/db -k /tmp/postgresql-${ENVIRONMENT} #stop-command: kill -HUP ${PID} environment-variables: - - PGROOT=${SERVICE_ROOT} + - PGROOT=${SERVICE_ROOT} provides: postgresql ports: postgresql -%pre-start - name: database directory creation - unless-directory: ${SERVICE_ROOT} - command: mkdir -p ${SERVICE_ROOT} && chown postgres:postgres ${SERVICE_ROOT} +#%file db +# name: database directory +# command: mkdir -p db && chown postgres:postgres db -%pre-start - name: database creation - unless-file: ${SERVICE_ROOT}/base - command: su - postgres -c "initdb --locale en_US.UTF-8 -D '${SERVICE_ROOT}'" && rm ${SERVICE_ROOT}/postgresql.conf +%file db + name: database creation + creation-command: mkdir db && chown postgres:postgres db && su - postgres -c "initdb --locale en_US.UTF-8 -D '${SERVICE_ROOT}/db'" && rm db/postgresql.conf + #export-command: FIXME -%pre-start - name: database configuration - # once this file is created, there is no need to perform the command - unless-file: ${SERVICE_ROOT}/postgresql.conf - # gen-config inherits its parameters from the environment - command: gen-config postgresql.conf ${SERVICE_ROOT}/postgresql.conf && chown postgres:postgres ${SERVICE_ROOT}/postgresql.conf +%configuration db/postgresql.conf + # gen-config inherits its parameters from the environment + creation-command: gen-config postgresql.conf ${SERVICE_ROOT}/postgresql.conf && chown postgres:postgres ${SERVICE_ROOT}/postgresql.conf -%pre-start - name: sockets directory - unless-directory: /tmp/postgresql-${ENVIRONMENT} - # FIXME: impose permissions - command: mkdir -p /tmp/postgresql-${ENVIRONMENT} && chown postgres:postgres /tmp/postgresql-${ENVIRONMENT} - # FIXME: add postgresql-pre-start-db-dir around here +%file /tmp/${SERVICE_NAME}-${ENVIRONMENT} + name: sockets directory + unless-directory: /tmp/${SERVICE_NAME}-${ENVIRONMENT} + # FIXME: impose permissions + command: mkdir -p /tmp/${SERVICE_NAME}-${ENVIRONMENT} && chown postgres:postgres /tmp/${SERVICE_NAME}-${ENVIRONMENT} + # FIXME: add postgresql-pre-start-db-dir around here diff --git a/services/www.spec b/services/www.spec index d60fe9e..0ac03c0 100644 --- a/services/www.spec +++ b/services/www.spec @@ -2,5 +2,6 @@ command: none consumes: www requires-domain: true -%directory ${SERVICE_ROOT}/ +%file www name: data directory + creation-command: mkdir www diff --git a/src/service/environment.cr b/src/service/environment.cr index 2e1a53d..8002ee9 100644 --- a/src/service/environment.cr +++ b/src/service/environment.cr @@ -8,7 +8,7 @@ class Environment getter name : String getter type : Type = Type::Prefix - getter pre_start_hooks = Array(ServiceDefinition::Hook).new + getter files = Array(ServiceDefinition::FileDefinition).new # The place we’ll put services’ data and configuration. @root : String? @@ -20,12 +20,14 @@ class Environment def initialize(@name, type = "prefix") @type = Type.parse type - @pre_start_hooks = Array(ServiceDefinition::Hook).new + @files = Array(ServiceDefinition::FileDefinition).new # FIXME: Should this *really* be here? - @pre_start_hooks << ServiceDefinition::Hook.new "Creating data directory", - "mkdir -p /srv/${ENVIRONMENT} && chmod a+rwt /srv/${ENVIRONMENT}", - unless_directory: "/srv/${ENVIRONMENT}" + # FIXME: $ENVIRONMENT_ROOT + @files << ServiceDefinition::FileDefinition.new "/srv/${ENVIRONMENT}", + "environment root", + creation_command: "mkdir -p /srv/${ENVIRONMENT} && chmod a+rwt /srv/${ENVIRONMENT}", + deletion_command: "rmdir /srv/${ENVIRONMENT}" end def initialize(@name, specs : SpecParser) @@ -36,7 +38,7 @@ class Environment end specs.sections.select(&.name.==("check")).each do |check| - @pre_start_hooks << ServiceDefinition::Hook.new check + @files << ServiceDefinition::FileDefinition.new check end end diff --git a/src/service/service.cr b/src/service/service.cr index 58e157a..d69ab4e 100644 --- a/src/service/service.cr +++ b/src/service/service.cr @@ -193,6 +193,7 @@ class Service private def build_environment env = {} of String => String + env["SERVICE_NAME"] = name env["SERVICE_ROOT"] = root env["SERVICE_ID"] = full_id env["ENVIRONMENT"] = @environment.name @@ -240,32 +241,33 @@ class Service end end - def pre_start_hooks - @environment.pre_start_hooks + @reference.pre_start_hooks + def files + @environment.files + @reference.files end def start(pid_dir : String, log_dir : String) - pre_start_hooks.each do |hook| + FileUtils.mkdir_p root + + files.each do |file| run_hook = false - hook.unless_file.try do |file| - file = evaluate file - run_hook = true if ! File.exists? file + next unless creation_command = file.creation_command + + path = evaluate file.file_path + if path[0] != '/' + path = "#{root}/#{path}" end - hook.unless_directory.try do |directory| - directory = evaluate directory - run_hook = true if ! Dir.exists? directory - end + run_hook = (!File.exists? path) || file.is_configuration? - unless run_hook - next - end + next unless run_hook - puts " - #{hook.name}" + puts " - Creating #{file.name}" child = Process.fork do - Process.exec "sh", ["-c", hook.command], + Dir.cd root + + Process.exec "sh", ["-c", creation_command], output: Process::Redirect::Inherit, error: Process::Redirect::Inherit, env: build_environment diff --git a/src/service/service_definition.cr b/src/service/service_definition.cr index 7cca6a7..954ad57 100644 --- a/src/service/service_definition.cr +++ b/src/service/service_definition.cr @@ -19,21 +19,40 @@ class ServiceDefinition def initialize(@token) end end - struct Hook + struct FileDefinition getter name : String - getter command : String - getter unless_directory : String? - getter unless_file : String? + getter file_path : String - def initialize(@name, @command, @unless_file = nil, @unless_directory = nil) + getter creation_command : String? + getter deletion_command : String? + getter export_command : String? + + @configuration = false + + def initialize(@file_path, @name = @file_path, + @creation_command = nil, + @deletion_command = nil, + @export_command = nil, + @configuration = false) end def initialize(section : SpecParser::Section) - @name = section.content["name"].as_s - @command = section.content["command"].as_s + @file_path = section.options[0] + @name = section.content["name"]?.try(&.as_s) || @file_path - @unless_directory = section.content["unless-directory"]? + @creation_command = section.content["creation-command"]? .try &.as_s - @unless_file = section.content["unless-file"]?.try &.as_s + @deletion_command = section.content["deletion-command"]? + .try &.as_s + @export_command = section.content["export-command"]? + .try &.as_s + + #@unless_directory = section.content["unless-directory"]? + # .try &.as_s + #@unless_file = section.content["unless-file"]?.try &.as_s + end + + def is_configuration? + @configuration end end @@ -59,7 +78,7 @@ class ServiceDefinition getter provides : String? getter consumes : Array(Consumes) getter environment_variables : Array(String) - getter pre_start_hooks : Array(Hook) + getter files : Array(FileDefinition) getter provides : Array(Provides) getter port_definitions : Array(PortDefinition) getter non_runnable : Bool @@ -92,39 +111,39 @@ class ServiceDefinition STDERR.puts "warning: definition '#{@name}' has a 'requires-domain' entry with an invalid value" end - @pre_start_hooks = Array(Hook).new + @files = Array(FileDefinition).new sections.each do |section| case section.name - when "pre-start" - @pre_start_hooks << Hook.new section - when "directory" - directory = section.options[0]? - name = section.content["name"]?.try &.as_s - - if directory.nil? - STDERR.puts "warning: (#{@name}) %directory was not provided a path" - next - end - - pre_start_hooks << Hook.new (name || "directory: #{directory}"), - "mkdir -p \"#{directory}\"", - unless_directory: directory + when "file", "directory" + @files << FileDefinition.new section when "configuration" options = section.options[0].split /[ \t]/ template = options[0]? - target = options[1]? + name = section.content["name"]?.try &.as_s - if template.nil? || target.nil? - STDERR.puts "warning: (#{@name}) %configuration received less than 2 options" + if template.nil? + STDERR.puts "warning: (#{@name}) %configuration wasn’t provided a target." next end - pre_start_hooks << Hook.new (name || "configuration file: #{template}"), - "gen-config \"#{template}\" \"#{target}\"", - unless_file: target + target = options[1]? + unless target + target = template + template = File.basename target + end + + files << FileDefinition.new target, (name || target), + creation_command: "gen-config \"#{template}\" \"#{target}\"", + deletion_command: "rm \"#{target}\"", + configuration: true + when "database" + options = section.options[0].split /[ \t]/ + type = options[0]? || section.content["type"]?.try &.as_s + + # FIXME: %database is not currently implemented. end end end