From ef1243ea39fdaefaa90976b9bf70a3b2f1ccb74c Mon Sep 17 00:00:00 2001 From: Tom Wiesing Date: Wed, 14 Sep 2022 14:17:08 +0200 Subject: [PATCH] Rename packages --- cmd/backup.go | 4 +- cmd/purge.go | 4 +- cmd/snapshot.go | 4 +- internal/component/dis/dis.go | 6 +- internal/config/config.go | 115 ++++------------- internal/config/executable.go | 33 +++++ internal/config/file.go | 76 ------------ internal/config/read.go | 60 +++++++++ internal/config/validators.go | 105 ---------------- internal/core/core.go | 4 + internal/env/distillery.go | 63 ---------- internal/{env => wisski}/backup.go | 2 +- internal/{env => wisski}/component.go | 10 +- internal/wisski/distillery.go | 37 ++++++ internal/{env => wisski}/init.go | 16 +-- internal/{env => wisski}/instances.go | 2 +- internal/{env => wisski}/instances/barrel.env | 0 .../instances/barrel/.dockerignore | 0 .../instances/barrel/.env.sample | 0 .../instances/barrel/Dockerfile | 0 .../instances/barrel/conf/ports.conf | 0 .../instances/barrel/conf/wisski.conf | 0 .../instances/barrel/conf/wisski.ini | 0 .../instances/barrel/docker-compose.yml | 0 .../instances/barrel/patch/easyrdf.patch | 0 .../instances/barrel/patch/triples.patch | 0 .../instances/barrel/scripts/entrypoint.sh | 0 .../barrel/scripts/provision_container.sh | 0 .../instances/barrel/scripts/user_shell.sh | 0 .../barrel/wisskiutils/create_adapter.php | 0 .../barrel/wisskiutils/export_pathbuilder.php | 0 .../barrel/wisskiutils/list_uri_prefixes.php | 0 .../barrel/wisskiutils/set_trusted_host.sh | 0 .../barrel/wisskiutils/settings_php_get.sh | 0 .../barrel/wisskiutils/settings_php_set.sh | 0 .../{env => wisski}/instances/reserve.env | 0 .../instances/reserve/docker-compose.yml | 0 .../instances/reserve/index.html | 0 .../{env => wisski}/instances_provision.go | 6 +- internal/{env => wisski}/runtime.go | 2 +- internal/{env => wisski}/server.go | 2 +- internal/{env => wisski}/snapshot.go | 2 +- pkg/envreader/envreader.go | 117 ++++++++++++++++++ pkg/envreader/envreader_test.go | 42 +++++++ pkg/stringparser/parse.go | 60 +++++++++ pkg/stringparser/stringparser.go | 111 +++++++++++++++++ program.go | 10 +- 47 files changed, 524 insertions(+), 369 deletions(-) create mode 100644 internal/config/executable.go delete mode 100644 internal/config/file.go create mode 100644 internal/config/read.go delete mode 100644 internal/config/validators.go delete mode 100644 internal/env/distillery.go rename internal/{env => wisski}/backup.go (99%) rename internal/{env => wisski}/component.go (93%) create mode 100644 internal/wisski/distillery.go rename internal/{env => wisski}/init.go (78%) rename internal/{env => wisski}/instances.go (99%) rename internal/{env => wisski}/instances/barrel.env (100%) rename internal/{env => wisski}/instances/barrel/.dockerignore (100%) rename internal/{env => wisski}/instances/barrel/.env.sample (100%) rename internal/{env => wisski}/instances/barrel/Dockerfile (100%) rename internal/{env => wisski}/instances/barrel/conf/ports.conf (100%) rename internal/{env => wisski}/instances/barrel/conf/wisski.conf (100%) rename internal/{env => wisski}/instances/barrel/conf/wisski.ini (100%) rename internal/{env => wisski}/instances/barrel/docker-compose.yml (100%) rename internal/{env => wisski}/instances/barrel/patch/easyrdf.patch (100%) rename internal/{env => wisski}/instances/barrel/patch/triples.patch (100%) rename internal/{env => wisski}/instances/barrel/scripts/entrypoint.sh (100%) rename internal/{env => wisski}/instances/barrel/scripts/provision_container.sh (100%) rename internal/{env => wisski}/instances/barrel/scripts/user_shell.sh (100%) rename internal/{env => wisski}/instances/barrel/wisskiutils/create_adapter.php (100%) rename internal/{env => wisski}/instances/barrel/wisskiutils/export_pathbuilder.php (100%) rename internal/{env => wisski}/instances/barrel/wisskiutils/list_uri_prefixes.php (100%) rename internal/{env => wisski}/instances/barrel/wisskiutils/set_trusted_host.sh (100%) rename internal/{env => wisski}/instances/barrel/wisskiutils/settings_php_get.sh (100%) rename internal/{env => wisski}/instances/barrel/wisskiutils/settings_php_set.sh (100%) rename internal/{env => wisski}/instances/reserve.env (100%) rename internal/{env => wisski}/instances/reserve/docker-compose.yml (100%) rename internal/{env => wisski}/instances/reserve/index.html (100%) rename internal/{env => wisski}/instances_provision.go (94%) rename internal/{env => wisski}/runtime.go (92%) rename internal/{env => wisski}/server.go (97%) rename internal/{env => wisski}/snapshot.go (99%) create mode 100644 pkg/envreader/envreader.go create mode 100644 pkg/envreader/envreader_test.go create mode 100644 pkg/stringparser/parse.go create mode 100644 pkg/stringparser/stringparser.go diff --git a/cmd/backup.go b/cmd/backup.go index 0e30929..4492ce7 100644 --- a/cmd/backup.go +++ b/cmd/backup.go @@ -6,7 +6,7 @@ import ( wisski_distillery "github.com/FAU-CDI/wisski-distillery" "github.com/FAU-CDI/wisski-distillery/internal/core" - "github.com/FAU-CDI/wisski-distillery/internal/env" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/targz" "github.com/tkw1536/goprogram/exit" @@ -83,7 +83,7 @@ func (bk backup) Run(context wisski_distillery.Context) error { logging.LogOperation(func() error { // take a snapshot into the staging area! - backup := dis.Backup(context.IOStream, env.BackupDescription{ + backup := dis.Backup(context.IOStream, wisski.BackupDescription{ Dest: sPath, }) diff --git a/cmd/purge.go b/cmd/purge.go index dcb67d8..97a4ac5 100644 --- a/cmd/purge.go +++ b/cmd/purge.go @@ -5,7 +5,7 @@ import ( wisski_distillery "github.com/FAU-CDI/wisski-distillery" "github.com/FAU-CDI/wisski-distillery/internal/core" - "github.com/FAU-CDI/wisski-distillery/internal/env" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/tkw1536/goprogram/exit" ) @@ -57,7 +57,7 @@ func (p purge) Run(context wisski_distillery.Context) error { // load the instance (first via bookkeeping, then via defaults) logging.LogMessage(context.IOStream, "Checking bookkeeping table") instance, err := dis.Instance(slug) - if err == env.ErrInstanceNotFound { + if err == wisski.ErrInstanceNotFound { context.Println("Not found in bookkeeping table, assuming defaults") instance, err = dis.NewInstance(slug) } diff --git a/cmd/snapshot.go b/cmd/snapshot.go index 41a767c..ffefce7 100644 --- a/cmd/snapshot.go +++ b/cmd/snapshot.go @@ -6,7 +6,7 @@ import ( wisski_distillery "github.com/FAU-CDI/wisski-distillery" "github.com/FAU-CDI/wisski-distillery/internal/core" - "github.com/FAU-CDI/wisski-distillery/internal/env" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/FAU-CDI/wisski-distillery/pkg/logging" "github.com/FAU-CDI/wisski-distillery/pkg/targz" "github.com/tkw1536/goprogram/exit" @@ -86,7 +86,7 @@ func (bi snapshot) Run(context wisski_distillery.Context) error { // take a snapshot into the staging area! logging.LogOperation(func() error { - sreport := instance.Snapshot(context.IOStream, env.SnapshotDescription{ + sreport := instance.Snapshot(context.IOStream, wisski.SnapshotDescription{ Dest: sPath, Keepalive: bi.Keepalive, }) diff --git a/internal/component/dis/dis.go b/internal/component/dis/dis.go index 9b1bcb7..49d7a03 100644 --- a/internal/component/dis/dis.go +++ b/internal/component/dis/dis.go @@ -9,8 +9,6 @@ import ( type Dis struct { component.ComponentBase - - Executable string // path to the current executable } func (dis Dis) Name() string { @@ -37,12 +35,12 @@ func (dis Dis) Stack() component.Installable { "GLOBAL_AUTHORIZED_KEYS_FILE": dis.Config.GlobalAuthorizedKeysFile, "SELF_OVERRIDES_FILE": dis.Config.SelfOverridesFile, }, - CopyContextFiles: []string{core.Executable}, + CopyContextFiles: []string{dis.Config.CurrentExecutable()}, }) } func (dis Dis) Context(parent component.InstallationContext) component.InstallationContext { return component.InstallationContext{ - core.Executable: dis.Executable, + core.Executable: dis.Config.CurrentExecutable(), } } diff --git a/internal/config/config.go b/internal/config/config.go index 487c0bb..fb8872b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,46 +1,48 @@ -// Package config implements reading and validating a WissKIDistillery configuration file. +// Package config provides the distillery configuration package config import ( "fmt" - "io" "net/url" "reflect" "strings" - - "github.com/pkg/errors" ) -// Config represents the configuration of a distillery instance +// Config represents the configuration of a WissKI Distillery. +// +// Config is read from a byte stream using [Unmarshal]. +// +// Config contains many methods that do not require any interaction with any running components. +// Methods that require running components are instead store inside the [Distillery] or an appropriate [Component]. type Config struct { // Several docker-compose files are created to manage global services and the system itself. // On top of this all real-system space will be created under this directory. - DeployRoot string `env:"DEPLOY_ROOT" default:"/var/www/deploy" validator:"is_valid_abspath"` + DeployRoot string `env:"DEPLOY_ROOT" default:"/var/www/deploy" parser:"abspath"` // Each created Drupal Instance corresponds to a single domain name. // These domain names should either be a complete domain name or a sub-domain of a default domain. // This setting configures the default domain-name to create subdomains of. - DefaultDomain string `env:"DEFAULT_DOMAIN" default:"localhost.kwarc.info" validator:"is_valid_domain"` + DefaultDomain string `env:"DEFAULT_DOMAIN" default:"localhost.kwarc.info" parser:"domain"` // By default, the default domain redirects to the distillery repository. // If you want to change this, set an alternate domain name here. - SelfRedirect *url.URL `env:"SELF_REDIRECT" default:"" validator:"is_valid_https_url"` + SelfRedirect *url.URL `env:"SELF_REDIRECT" default:"" parser:"https_url"` // By default, only the 'self' domain above is caught. // To catch additional domains, add them here (comma seperated) - SelfExtraDomains []string `env:"SELF_EXTRA_DOMAINS" default:"" validator:"is_valid_domains"` + SelfExtraDomains []string `env:"SELF_EXTRA_DOMAINS" default:"" parser:"domains"` // You can override individual URLS in the homepage // Do this by adding URLs (without trailing '/'s) into a JSON file - SelfOverridesFile string `env:"SELF_OVERRIDES_FILE" default:"" validator:"is_valid_file"` + SelfOverridesFile string `env:"SELF_OVERRIDES_FILE" default:"" parser:"file"` // The system can support setting up certificate(s) automatically. // It can be enabled by setting an email for certbot certificates. // This email address can be configured here. - CertbotEmail string `env:"CERTBOT_EMAIL" default:"" validator:"is_valid_email"` + CertbotEmail string `env:"CERTBOT_EMAIL" default:"" parser:"email"` // Maximum age for backup in days - MaxBackupAge int `env:"MAX_BACKUP_AGE" default:"" validator:"is_valid_number"` + MaxBackupAge int `env:"MAX_BACKUP_AGE" default:"" parser:"number"` // Each Drupal instance requires a corresponding system user, database users and databases. // These are also set by the appropriate domain name. @@ -48,36 +50,37 @@ type Config struct { // The prefix to use can be configured here. // When changing these please consider that no system user may exist that has the same name as a mysql user. // This is a MariaDB restriction. - MysqlUserPrefix string `env:"MYSQL_USER_PREFIX" default:"mysql-factory-" validator:"is_valid_slug"` - MysqlDatabasePrefix string `env:"MYSQL_DATABASE_PREFIX" default:"mysql-factory-" validator:"is_valid_slug"` - GraphDBUserPrefix string `env:"GRAPHDB_USER_PREFIX" default:"mysql-factory-" validator:"is_valid_slug"` - GraphDBRepoPrefix string `env:"GRAPHDB_REPO_PREFIX" default:"mysql-factory-" validator:"is_valid_slug"` + MysqlUserPrefix string `env:"MYSQL_USER_PREFIX" default:"mysql-factory-" parser:"slug"` + MysqlDatabasePrefix string `env:"MYSQL_DATABASE_PREFIX" default:"mysql-factory-" parser:"slug"` + GraphDBUserPrefix string `env:"GRAPHDB_USER_PREFIX" default:"mysql-factory-" parser:"slug"` + GraphDBRepoPrefix string `env:"GRAPHDB_REPO_PREFIX" default:"mysql-factory-" parser:"slug"` // In addition to the filesystem the WissKI distillery requires a single SQL table. // It uses this database to store a list of installed things. - DistilleryBookkeepingDatabase string `env:"DISTILLERY_BOOKKEEPING_DATABASE" default:"distillery" validator:"is_valid_slug"` - DistilleryBookkeepingTable string `env:"DISTILLERY_BOOKKEEPING_TABLE" default:"distillery" validator:"is_valid_slug"` + DistilleryBookkeepingDatabase string `env:"DISTILLERY_BOOKKEEPING_DATABASE" default:"distillery" parser:"slug"` + DistilleryBookkeepingTable string `env:"DISTILLERY_BOOKKEEPING_TABLE" default:"distillery" parser:"slug"` // Various components use password-based-authentication. // These passwords are generated automatically. // This variable can be used to determine their length. - PasswordLength int `env:"PASSWORD_LENGTH" default:"64" validator:"is_valid_number"` + PasswordLength int `env:"PASSWORD_LENGTH" default:"64" parser:"number"` // A file to be used for global authorized_keys for the ssh server. - GlobalAuthorizedKeysFile string `env:"GLOBAL_AUTHORIZED_KEYS_FILE" default:"/var/www/deploy/authorized_keys" validator:"is_valid_file"` + GlobalAuthorizedKeysFile string `env:"GLOBAL_AUTHORIZED_KEYS_FILE" default:"/var/www/deploy/authorized_keys" parser:"file"` // admin credentials for graphdb - TriplestoreAdminUser string `env:"GRAPHDB_ADMIN_USER" default:"admin" validator:"is_nonempty"` - TriplestoreAdminPassword string `env:"GRAPHDB_ADMIN_PASSWORD" default:"" validator:"is_nonempty"` + TriplestoreAdminUser string `env:"GRAPHDB_ADMIN_USER" default:"admin" parser:"nonempty"` + TriplestoreAdminPassword string `env:"GRAPHDB_ADMIN_PASSWORD" default:"" parser:"nonempty"` // admin credentials for the Mysql database - MysqlAdminUser string `env:"MYSQL_ADMIN_USER" default:"admin" validator:"is_nonempty"` - MysqlAdminPassword string `env:"MYSQL_ADMIN_PASSWORD" default:"admin" validator:"is_nonempty"` + MysqlAdminUser string `env:"MYSQL_ADMIN_USER" default:"admin" parser:"nonempty"` + MysqlAdminPassword string `env:"MYSQL_ADMIN_PASSWORD" default:"admin" parser:"nonempty"` // ConfigPath is the path this configuration was loaded from (if any) ConfigPath string } +// String serializes this configuration into a string func (config Config) String() string { values := &strings.Builder{} @@ -100,67 +103,3 @@ func (config Config) String() string { return values.String() } - -func (config *Config) Unmarshal(src io.Reader) error { - // read all the values! - values, err := ReadAll(src) - if err != nil { - return err - } - - vConfig := reflect.ValueOf(config).Elem() - tConfig := vConfig.Type() - - // iterate over the types - numValues := tConfig.NumField() - for i := 0; i < numValues; i++ { - tField := tConfig.Field(i) - vField := vConfig.FieldByName(tField.Name) - - env := tField.Tag.Get("env") - dflt := tField.Tag.Get("default") - validator := tField.Tag.Get("validator") - - // skip it if it isn't loaded! - if env == "" { - continue - } - - // read the value with a default - value, ok := values[env] - if !ok || value == "" { - if dflt == "" { - continue - } - value = dflt - } - - // use the validator - vFunc, ok := knownValidators[validator] - if vFunc == nil || !ok { - return errors.Errorf("Unable to read %q refers to unknown validator %s", env, validator) - } - - // get the parsed value - checked, err := vFunc(value) - if err != nil { - return errors.Wrapf(err, "Unable to read %q: Validator %s", env, validator) - } - - // set the value of the field - var errSet interface{} - func() { - defer func() { - errSet = recover() - }() - vField.Set(reflect.ValueOf(checked)) - }() - - // capture any error - if errSet != nil { - return errors.Errorf("Unable to parse %q: validator %s returned %q", tField.Name, validator, errSet) - } - } - - return nil -} diff --git a/internal/config/executable.go b/internal/config/executable.go new file mode 100644 index 0000000..2f0bd50 --- /dev/null +++ b/internal/config/executable.go @@ -0,0 +1,33 @@ +package config + +import ( + "os" + "path/filepath" + + "github.com/FAU-CDI/wisski-distillery/internal/core" + "github.com/FAU-CDI/wisski-distillery/pkg/fsx" +) + +// ExecutablePath returns the path to the executable of this distillery. +func (cfg Config) ExecutablePath() string { + return filepath.Join(cfg.DeployRoot, core.Executable) +} + +// UsingDistilleryExecutable checks if the current process is using the distillery executable +func (cfg Config) UsingDistilleryExecutable() bool { + exe, err := os.Executable() + if err != nil { + return false + } + return fsx.SameFile(exe, cfg.ExecutablePath()) +} + +// CurrentExecutable returns the path to the current executable being used. +// When it does not exist, falls back to the default executable. +func (cfg Config) CurrentExecutable() string { + exe, err := os.Executable() + if err != nil || !fsx.IsFile(exe) { + return cfg.ExecutablePath() + } + return exe +} diff --git a/internal/config/file.go b/internal/config/file.go deleted file mode 100644 index 4b29cc7..0000000 --- a/internal/config/file.go +++ /dev/null @@ -1,76 +0,0 @@ -package config - -import ( - "bufio" - "io" - "strings" -) - -// Scanner scans an io.Reader for a source file -type Scanner struct { - src *bufio.Scanner - - key string - value string -} - -func NewScanner(r io.Reader) *Scanner { - return &Scanner{ - src: bufio.NewScanner(r), - } -} - -// Scanner advances the scanner to the next variable -func (scanner *Scanner) Scan() bool { - for scanner.src.Scan() { - // check that we don't have an empty or comment only line - tokens := strings.TrimSpace(scanner.src.Text()) - if len(tokens) == 0 || tokens[0] == '#' || strings.HasPrefix(tokens, "//") { - continue - } - - // check that we have a 'key=value' pair - values := strings.SplitN(tokens, "=", 2) - if len(values) != 2 { - continue - } - - // got a key = value - scanner.key = strings.TrimSpace(values[0]) - scanner.value = strings.TrimSpace(values[1]) - return true - } - scanner.key = "" - scanner.value = "" - return false -} - -// Data reads the current value from the scanner. -// When Scan() has not been called, or returned false, returns two empty strings. -func (scanner Scanner) Data() (key, value string) { - return scanner.key, scanner.value -} - -// Error returns an error (if any) -func (scanner Scanner) Error() error { - return scanner.src.Err() -} - -// ReadAll reads all key-value pairs from r. -// If a key occurs more than once, a later occurance overwrites a previous one. -func ReadAll(r io.Reader) (values map[string]string, err error) { - scanner := NewScanner(r) - - // read and store all values - values = make(map[string]string) - for scanner.Scan() { - key, value := scanner.Data() - values[key] = value - } - - // check if there was an error! - if err := scanner.Error(); err != nil { - return nil, err - } - return values, nil -} diff --git a/internal/config/read.go b/internal/config/read.go new file mode 100644 index 0000000..66ff1fa --- /dev/null +++ b/internal/config/read.go @@ -0,0 +1,60 @@ +package config + +import ( + "io" + "reflect" + + "github.com/FAU-CDI/wisski-distillery/pkg/envreader" + "github.com/FAU-CDI/wisski-distillery/pkg/stringparser" +) + +// Unmarshal updates this configuration from the provided [io.Reader]. +// +// Data is read using the [envreader.ReadAll] method, see the appropriate documentation for the file format. +// +// The `env` and `parser` reflect tags of the [Config] struct determine the keys to read from, and the types to expect. +// When a key is missing, it is set to the default value. +// +// See also [stringparser.Parse]. +func (config *Config) Unmarshal(src io.Reader) error { + // read all the values! + values, err := envreader.ReadAll(src) + if err != nil { + return err + } + + vConfig := reflect.ValueOf(config).Elem() + tConfig := vConfig.Type() + + // iterate over the types + numValues := tConfig.NumField() + for i := 0; i < numValues; i++ { + tField := tConfig.Field(i) + vField := vConfig.FieldByName(tField.Name) + + env := tField.Tag.Get("env") + dflt := tField.Tag.Get("default") + parser := tField.Tag.Get("parser") + + // skip it if it isn't loaded! + if env == "" { + continue + } + + // read the value with a default + value, ok := values[env] + if !ok || value == "" { + if dflt == "" { + continue + } + value = dflt + } + + // parse the value! + if err := stringparser.Parse(parser, value, vField); err != nil { + return err + } + } + + return nil +} diff --git a/internal/config/validators.go b/internal/config/validators.go deleted file mode 100644 index 20fbbaf..0000000 --- a/internal/config/validators.go +++ /dev/null @@ -1,105 +0,0 @@ -package config - -import ( - "net/url" - "regexp" - "strconv" - "strings" - - "github.com/FAU-CDI/wisski-distillery/pkg/fsx" - "github.com/pkg/errors" -) - -// Validator reads from the configuration file -type Validator func(s string) (interface{}, error) - -var knownValidators map[string]Validator = map[string]Validator{ - "is_valid_abspath": IsValidAbspath, - "is_valid_domain": IsValidDomain, - "is_valid_domains": IsValidDomains, - "is_valid_number": IsValidNumber, - "is_valid_https_url": IsValidHttpsURL, - "is_valid_slug": IsValidSlug, - "is_valid_file": IsValidFile, - "is_valid_email": IsValidEmail, - "is_nonempty": IsNonEmpty, -} - -func IsValidAbspath(s string) (interface{}, error) { - if !fsx.IsDirectory(s) { - return nil, errors.Errorf("%q does not exist or is not a directory", s) - } - return s, nil -} - -func IsValidFile(s string) (interface{}, error) { - if !fsx.IsFile(s) { - return nil, errors.Errorf("%q does not exist or is not a regular file", s) - } - return s, nil -} - -func IsNonEmpty(s string) (interface{}, error) { - if s == "" { - return nil, errors.New("value is empty") - } - return s, nil -} - -var regexpDomain = regexp.MustCompile(`^([a-zA-Z0-9][-a-zA-Z0-9]*\.)*[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer! - -func IsValidDomain(s string) (interface{}, error) { - if !regexpDomain.MatchString(s) { - return nil, errors.Errorf("%q is not a valid domain", s) - } - return s, nil -} -func IsValidDomains(s string) (interface{}, error) { - if len(s) == 0 { - return []string{}, nil - } - domains := strings.Split(s, ",") - for _, d := range domains { - if !regexpDomain.MatchString(d) { - return nil, errors.Errorf("%q is not a valid domain", d) - } - } - return domains, nil -} - -func IsValidNumber(s string) (interface{}, error) { - value, err := strconv.ParseInt(s, 10, 64) - return int(value), err -} - -func IsValidHttpsURL(s string) (interface{}, error) { - url, err := url.Parse(s) - if err != nil { - return nil, errors.Wrapf(err, "%q is not a valid URL", s) - } - if url.Scheme != "https" { - return nil, errors.Errorf("%q is not a valid https URL (%q)", s, url.Scheme) - } - return url, nil -} - -var regexpEmail = regexp.MustCompile(`^([-a-zA-Z0-9]+)\@([a-zA-Z0-9][-a-zA-Z0-9]*\.)*[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer! - -func IsValidEmail(s string) (interface{}, error) { - if s == "" { // no email provided - return "", nil - } - if !regexpEmail.MatchString(s) { - return nil, errors.Errorf("%q is not a valid email", s) - } - return s, nil -} - -var regexpSlug = regexp.MustCompile(`^[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer! - -func IsValidSlug(s string) (interface{}, error) { - if !regexpSlug.MatchString(s) { - return nil, errors.Errorf("%q is not a valid slug", s) - } - return s, nil -} diff --git a/internal/core/core.go b/internal/core/core.go index 6570ee9..6b90779 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -33,3 +33,7 @@ const AuthorizedKeys = "authorized_keys" // DefaultAuthorizedKeys contains a template for a new 'global_authorized_keys' file //go:embed bootstrap/global_authorized_keys var DefaultAuthorizedKeys []byte + +// PrefixConfig is the name for the global resolver prefix configuration. +// It should be found within the prefix component directory. +const PrefixConfig = "prefix.cfg" diff --git a/internal/env/distillery.go b/internal/env/distillery.go deleted file mode 100644 index b71dae3..0000000 --- a/internal/env/distillery.go +++ /dev/null @@ -1,63 +0,0 @@ -package env - -import ( - "context" - "os" - "path/filepath" - - "github.com/FAU-CDI/wisski-distillery/internal/config" - "github.com/FAU-CDI/wisski-distillery/internal/core" - "github.com/FAU-CDI/wisski-distillery/pkg/fsx" -) - -// Distillery represents an interface to the running distillery. -type Distillery struct { - // Config holds the configuration of the distillery. - // It is read directly from a configuration file. - Config *config.Config - - // Upstream holds information to connect to the various running - // distillery components. - // - // NOTE(twiesing): This is intended to eventually allow full remote management of the distillery. - // But for now this will just hold upstream configuration. - Upstream Upstream - - // components hold references to the various components of the distillery. - components -} - -// Upstream are the upstream urls connecting to the various external components. -type Upstream struct { - SQL string - Triplestore string -} - -// Context returns a new Context belonging to this distillery -func (dis *Distillery) Context() context.Context { - return context.Background() -} - -// ExecutablePath returns the path to the executable of this distillery. -func (dis *Distillery) ExecutablePath() string { - return filepath.Join(dis.Config.DeployRoot, core.Executable) -} - -// UsingDistilleryExecutable checks if the current process -func (dis *Distillery) UsingDistilleryExecutable() bool { - exe, err := os.Executable() - if err != nil { - return false - } - return fsx.SameFile(exe, dis.ExecutablePath()) -} - -// CurrentExecutable returns the path to the current executable being used. -// When it does not exist, falls back to the default executable. -func (dis *Distillery) CurrentExecutable() string { - exe, err := os.Executable() - if err != nil || !fsx.IsFile(exe) { - return dis.ExecutablePath() - } - return exe -} diff --git a/internal/env/backup.go b/internal/wisski/backup.go similarity index 99% rename from internal/env/backup.go rename to internal/wisski/backup.go index 6caaffd..9b0b25b 100644 --- a/internal/env/backup.go +++ b/internal/wisski/backup.go @@ -1,4 +1,4 @@ -package env +package wisski import ( "encoding/json" diff --git a/internal/env/component.go b/internal/wisski/component.go similarity index 93% rename from internal/env/component.go rename to internal/wisski/component.go index b909e3f..7b7d3cf 100644 --- a/internal/env/component.go +++ b/internal/wisski/component.go @@ -1,4 +1,4 @@ -package env +package wisski import ( "path/filepath" @@ -14,6 +14,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/component/ssh" "github.com/FAU-CDI/wisski-distillery/internal/component/triplestore" "github.com/FAU-CDI/wisski-distillery/internal/component/web" + "github.com/FAU-CDI/wisski-distillery/internal/core" ) // components holds the various components of the distillery @@ -94,15 +95,12 @@ func (dis *Distillery) Self() *self.Self { func (dis *Distillery) Resolver() *resolver.Resolver { return makeComponent(dis, &dis.components.resolver, func(resolver *resolver.Resolver) { - resolver.ConfigName = "prefix.cfg" // TODO: Move into core? - resolver.Executable = dis.CurrentExecutable() + resolver.ConfigName = core.PrefixConfig }) } func (d *Distillery) Dis() *dis.Dis { - return makeComponent(d, &d.components.dis, func(ddis *dis.Dis) { - ddis.Executable = d.CurrentExecutable() - }) + return makeComponent(d, &d.components.dis, nil) } func (dis *Distillery) SSH() *ssh.SSH { diff --git a/internal/wisski/distillery.go b/internal/wisski/distillery.go new file mode 100644 index 0000000..7ffc88d --- /dev/null +++ b/internal/wisski/distillery.go @@ -0,0 +1,37 @@ +package wisski + +import ( + "context" + + "github.com/FAU-CDI/wisski-distillery/internal/config" +) + +// Distillery represents a WissKI Distillery +// +// It is the main structure used to interact with different components. +type Distillery struct { + // Config holds the configuration of the distillery. + // It is read directly from a configuration file. + Config *config.Config + + // Upstream holds information to connect to the various running + // distillery components. + // + // NOTE(twiesing): This is intended to eventually allow full remote management of the distillery. + // But for now this will just hold upstream configuration. + Upstream Upstream + + // components hold references to the various components of the distillery. + components +} + +// Upstream are the upstream urls connecting to the various external components. +type Upstream struct { + SQL string + Triplestore string +} + +// Context returns a new Context belonging to this distillery +func (dis *Distillery) Context() context.Context { + return context.Background() +} diff --git a/internal/env/init.go b/internal/wisski/init.go similarity index 78% rename from internal/env/init.go rename to internal/wisski/init.go index aed58a7..ff7e03f 100644 --- a/internal/env/init.go +++ b/internal/wisski/init.go @@ -1,4 +1,4 @@ -package env +package wisski import ( "os" @@ -18,9 +18,9 @@ var errOpenConfig = exit.Error{ Message: "error loading configuration file: %s", } -// NewDistillery creates a new distillery object from a set of parameters and requirements -func NewDistillery(params core.Params, flags core.Flags, req core.Requirements) (env *Distillery, err error) { - env = &Distillery{ +// NewDistillery creates a new distillery from the provided flags +func NewDistillery(params core.Params, flags core.Flags, req core.Requirements) (dis *Distillery, err error) { + dis = &Distillery{ Upstream: Upstream{ SQL: "127.0.0.1:3306", Triplestore: "127.0.0.1:7200", @@ -28,8 +28,8 @@ func NewDistillery(params core.Params, flags core.Flags, req core.Requirements) } if flags.InternalInDocker { - env.Upstream.SQL = "sql:3306" - env.Upstream.Triplestore = "triplestore:7200" + dis.Upstream.SQL = "sql:3306" + dis.Upstream.Triplestore = "triplestore:7200" } // if we don't need to load the config, there is nothing to do @@ -54,9 +54,9 @@ func NewDistillery(params core.Params, flags core.Flags, req core.Requirements) defer f.Close() // unmarshal the config - env.Config = &config.Config{ + dis.Config = &config.Config{ ConfigPath: cfg, } - err = env.Config.Unmarshal(f) + err = dis.Config.Unmarshal(f) return } diff --git a/internal/env/instances.go b/internal/wisski/instances.go similarity index 99% rename from internal/env/instances.go rename to internal/wisski/instances.go index 9abe2d1..8461f12 100644 --- a/internal/env/instances.go +++ b/internal/wisski/instances.go @@ -1,4 +1,4 @@ -package env +package wisski import ( "bytes" diff --git a/internal/env/instances/barrel.env b/internal/wisski/instances/barrel.env similarity index 100% rename from internal/env/instances/barrel.env rename to internal/wisski/instances/barrel.env diff --git a/internal/env/instances/barrel/.dockerignore b/internal/wisski/instances/barrel/.dockerignore similarity index 100% rename from internal/env/instances/barrel/.dockerignore rename to internal/wisski/instances/barrel/.dockerignore diff --git a/internal/env/instances/barrel/.env.sample b/internal/wisski/instances/barrel/.env.sample similarity index 100% rename from internal/env/instances/barrel/.env.sample rename to internal/wisski/instances/barrel/.env.sample diff --git a/internal/env/instances/barrel/Dockerfile b/internal/wisski/instances/barrel/Dockerfile similarity index 100% rename from internal/env/instances/barrel/Dockerfile rename to internal/wisski/instances/barrel/Dockerfile diff --git a/internal/env/instances/barrel/conf/ports.conf b/internal/wisski/instances/barrel/conf/ports.conf similarity index 100% rename from internal/env/instances/barrel/conf/ports.conf rename to internal/wisski/instances/barrel/conf/ports.conf diff --git a/internal/env/instances/barrel/conf/wisski.conf b/internal/wisski/instances/barrel/conf/wisski.conf similarity index 100% rename from internal/env/instances/barrel/conf/wisski.conf rename to internal/wisski/instances/barrel/conf/wisski.conf diff --git a/internal/env/instances/barrel/conf/wisski.ini b/internal/wisski/instances/barrel/conf/wisski.ini similarity index 100% rename from internal/env/instances/barrel/conf/wisski.ini rename to internal/wisski/instances/barrel/conf/wisski.ini diff --git a/internal/env/instances/barrel/docker-compose.yml b/internal/wisski/instances/barrel/docker-compose.yml similarity index 100% rename from internal/env/instances/barrel/docker-compose.yml rename to internal/wisski/instances/barrel/docker-compose.yml diff --git a/internal/env/instances/barrel/patch/easyrdf.patch b/internal/wisski/instances/barrel/patch/easyrdf.patch similarity index 100% rename from internal/env/instances/barrel/patch/easyrdf.patch rename to internal/wisski/instances/barrel/patch/easyrdf.patch diff --git a/internal/env/instances/barrel/patch/triples.patch b/internal/wisski/instances/barrel/patch/triples.patch similarity index 100% rename from internal/env/instances/barrel/patch/triples.patch rename to internal/wisski/instances/barrel/patch/triples.patch diff --git a/internal/env/instances/barrel/scripts/entrypoint.sh b/internal/wisski/instances/barrel/scripts/entrypoint.sh similarity index 100% rename from internal/env/instances/barrel/scripts/entrypoint.sh rename to internal/wisski/instances/barrel/scripts/entrypoint.sh diff --git a/internal/env/instances/barrel/scripts/provision_container.sh b/internal/wisski/instances/barrel/scripts/provision_container.sh similarity index 100% rename from internal/env/instances/barrel/scripts/provision_container.sh rename to internal/wisski/instances/barrel/scripts/provision_container.sh diff --git a/internal/env/instances/barrel/scripts/user_shell.sh b/internal/wisski/instances/barrel/scripts/user_shell.sh similarity index 100% rename from internal/env/instances/barrel/scripts/user_shell.sh rename to internal/wisski/instances/barrel/scripts/user_shell.sh diff --git a/internal/env/instances/barrel/wisskiutils/create_adapter.php b/internal/wisski/instances/barrel/wisskiutils/create_adapter.php similarity index 100% rename from internal/env/instances/barrel/wisskiutils/create_adapter.php rename to internal/wisski/instances/barrel/wisskiutils/create_adapter.php diff --git a/internal/env/instances/barrel/wisskiutils/export_pathbuilder.php b/internal/wisski/instances/barrel/wisskiutils/export_pathbuilder.php similarity index 100% rename from internal/env/instances/barrel/wisskiutils/export_pathbuilder.php rename to internal/wisski/instances/barrel/wisskiutils/export_pathbuilder.php diff --git a/internal/env/instances/barrel/wisskiutils/list_uri_prefixes.php b/internal/wisski/instances/barrel/wisskiutils/list_uri_prefixes.php similarity index 100% rename from internal/env/instances/barrel/wisskiutils/list_uri_prefixes.php rename to internal/wisski/instances/barrel/wisskiutils/list_uri_prefixes.php diff --git a/internal/env/instances/barrel/wisskiutils/set_trusted_host.sh b/internal/wisski/instances/barrel/wisskiutils/set_trusted_host.sh similarity index 100% rename from internal/env/instances/barrel/wisskiutils/set_trusted_host.sh rename to internal/wisski/instances/barrel/wisskiutils/set_trusted_host.sh diff --git a/internal/env/instances/barrel/wisskiutils/settings_php_get.sh b/internal/wisski/instances/barrel/wisskiutils/settings_php_get.sh similarity index 100% rename from internal/env/instances/barrel/wisskiutils/settings_php_get.sh rename to internal/wisski/instances/barrel/wisskiutils/settings_php_get.sh diff --git a/internal/env/instances/barrel/wisskiutils/settings_php_set.sh b/internal/wisski/instances/barrel/wisskiutils/settings_php_set.sh similarity index 100% rename from internal/env/instances/barrel/wisskiutils/settings_php_set.sh rename to internal/wisski/instances/barrel/wisskiutils/settings_php_set.sh diff --git a/internal/env/instances/reserve.env b/internal/wisski/instances/reserve.env similarity index 100% rename from internal/env/instances/reserve.env rename to internal/wisski/instances/reserve.env diff --git a/internal/env/instances/reserve/docker-compose.yml b/internal/wisski/instances/reserve/docker-compose.yml similarity index 100% rename from internal/env/instances/reserve/docker-compose.yml rename to internal/wisski/instances/reserve/docker-compose.yml diff --git a/internal/env/instances/reserve/index.html b/internal/wisski/instances/reserve/index.html similarity index 100% rename from internal/env/instances/reserve/index.html rename to internal/wisski/instances/reserve/index.html diff --git a/internal/env/instances_provision.go b/internal/wisski/instances_provision.go similarity index 94% rename from internal/env/instances_provision.go rename to internal/wisski/instances_provision.go index 42ddfae..aeaaae8 100644 --- a/internal/env/instances_provision.go +++ b/internal/wisski/instances_provision.go @@ -1,10 +1,10 @@ -package env +package wisski import ( "path/filepath" - "github.com/FAU-CDI/wisski-distillery/internal/config" "github.com/FAU-CDI/wisski-distillery/pkg/bookkeeping" + "github.com/FAU-CDI/wisski-distillery/pkg/stringparser" "github.com/pkg/errors" ) @@ -37,7 +37,7 @@ var errInvalidSlug = errors.New("Not a valid slug") func (dis *Distillery) NewInstance(slug string) (i Instance, err error) { // make sure that the slug is valid! - if _, err := config.IsValidSlug(slug); err != nil { + if _, err := stringparser.ParseSlug(slug); err != nil { return i, errInvalidSlug } diff --git a/internal/env/runtime.go b/internal/wisski/runtime.go similarity index 92% rename from internal/env/runtime.go rename to internal/wisski/runtime.go index fac70f2..730f2ff 100644 --- a/internal/env/runtime.go +++ b/internal/wisski/runtime.go @@ -1,4 +1,4 @@ -package env +package wisski import "path/filepath" diff --git a/internal/env/server.go b/internal/wisski/server.go similarity index 97% rename from internal/env/server.go rename to internal/wisski/server.go index 36d16c2..03f9654 100644 --- a/internal/env/server.go +++ b/internal/wisski/server.go @@ -1,4 +1,4 @@ -package env +package wisski import ( "io" diff --git a/internal/env/snapshot.go b/internal/wisski/snapshot.go similarity index 99% rename from internal/env/snapshot.go rename to internal/wisski/snapshot.go index cd14c81..7ec04d3 100644 --- a/internal/env/snapshot.go +++ b/internal/wisski/snapshot.go @@ -1,4 +1,4 @@ -package env +package wisski import ( "encoding/json" diff --git a/pkg/envreader/envreader.go b/pkg/envreader/envreader.go new file mode 100644 index 0000000..f75af34 --- /dev/null +++ b/pkg/envreader/envreader.go @@ -0,0 +1,117 @@ +// Package envreader +package envreader + +import ( + "bufio" + "io" + "strings" +) + +// Scanner is a scanner for environment files. +// To create a new scanner use [NewScanner]. +// +// It scans through a reader and reads environment variables from it. +// Reads may be internally buffered. +// +// An environment variable is of the form: +// KEY=VALUE +// on a separate line. +// Keys and values are case-sensitive and may contain anything except for newline characters. +// Spaces around key and value are trimmed using [strings.TrimSpace]. +// Keys may not contain an '='. +// Lines not containing a '=' (e.g. blank lines) and those starting with '#' and '//' are ignored. +// +// To advance the scanner to the next key, value pair use [Scan]. +// To get the current (key, value) pair, use [Data]. +// +// A typical use-case of a scanner is as follows: +// +// scanner := NewScanner(r) +// for scanner.Scan() { +// // process any data .... +// fmt.Println(scanner.Data()) +// } +// if err := scanner.Err(); err != nil { +// // handle errors +// } +// +// For the common use case of reading a set of distinct keys from a file see [ReadAll]. +type Scanner struct { + s *bufio.Scanner + + // current key and value + key string + value string +} + +// NewScanner creates a new scanner from the underlying Reader +func NewScanner(r io.Reader) *Scanner { + return &Scanner{ + s: bufio.NewScanner(r), + } +} + +// Scanner advances the scanner until the next KEY=VALUE pair. +// +// If there are no more values left (e.g. the underlying reader returned io.EOF) +// or when an unexpected error occured, returns false. +// +// A caller should always check Err() to see if there was an error. +func (scanner *Scanner) Scan() bool { + var found bool + for scanner.s.Scan() { + // check that we don't have an empty or comment only line + tokens := strings.TrimSpace(scanner.s.Text()) + if len(tokens) == 0 || tokens[0] == '#' || strings.HasPrefix(tokens, "//") { + continue + } + + // check that we have a 'key=value' pair + scanner.key, scanner.value, found = strings.Cut(tokens, "=") + if !found { + continue + } + + // got a key = value + scanner.key = strings.TrimSpace(scanner.key) + scanner.value = strings.TrimSpace(scanner.value) + return true + } + + // nothing found + scanner.key = "" + scanner.value = "" + return false +} + +// Data reads the current value from the scanner. +// When Scan() has not been called, or returned false, returns two empty strings. +func (scanner Scanner) Data() (key, value string) { + return scanner.key, scanner.value +} + +// Err returns any error that occured on the underlying read. +// +// When no error occured, or the underlying read is io.EOF, returns nil. +func (scanner Scanner) Err() error { + return scanner.s.Err() +} + +// ReadAll creates a new [Scanner], and then reads all key/value pairs from r. +// If a key occurs more than once, only the last value is set in the returned map. +func ReadAll(r io.Reader) (values map[string]string, err error) { + scanner := NewScanner(r) + + // read and store all values + values = make(map[string]string) + for scanner.Scan() { + key, value := scanner.Data() + values[key] = value + } + + // check if there was an error! + if err := scanner.Err(); err != nil { + return nil, err + } + return values, nil +} diff --git a/pkg/envreader/envreader_test.go b/pkg/envreader/envreader_test.go new file mode 100644 index 0000000..bb9aeb2 --- /dev/null +++ b/pkg/envreader/envreader_test.go @@ -0,0 +1,42 @@ +// Package envreader +package envreader + +import ( + "fmt" + "strings" +) + +func ExampleNewScanner() { + scanner := NewScanner(strings.NewReader(` +lines without an equal sign are ignored + +// this line is a comment, even with an = sign +KEY=VALUE + +# this is also a comment = +spaces in keys = spaces in values +multiple=equal=signs +CaSe = SenSitiVe +empty value= +=empty key +`)) + + for scanner.Scan() { + key, value := scanner.Data() + fmt.Printf("%q %q\n", key, value) + } + + if err := scanner.Err(); err != nil { + fmt.Println(scanner.Err()) + } else { + fmt.Println("no error") + } + + // Output: "KEY" "VALUE" + // "spaces in keys" "spaces in values" + // "multiple" "equal=signs" + // "CaSe" "SenSitiVe" + // "empty value" "" + // "" "empty key" + // no error +} diff --git a/pkg/stringparser/parse.go b/pkg/stringparser/parse.go new file mode 100644 index 0000000..45e0945 --- /dev/null +++ b/pkg/stringparser/parse.go @@ -0,0 +1,60 @@ +package stringparser + +import ( + "reflect" + "strings" + + "github.com/pkg/errors" +) + +// Parse parses the provided value with the parser. +func Parse(name, value string, vField reflect.Value) error { + + // use the validator + parser, ok := knownParsers[strings.ToLower(name)] + if parser == nil || !ok { + return errors.Errorf("unknown parser %q", name) + } + + // get the parsed value + checked, err := parser(value) + if err != nil { + return errors.Wrapf(err, "parser %s returned error", name) + } + + // set the value of the field + var errSet interface{} + func() { + defer func() { + errSet = recover() + }() + vField.Set(reflect.ValueOf(checked)) + }() + + // capture any error + if errSet != nil { + return errors.Errorf("parser %s: set returned %v", name, errSet) + } + + return nil +} + +// knownParsers holds the known parsers +var knownParsers map[string]Parser[any] = map[string]Parser[any]{ + "abspath": asGenericParser(ParseAbspath), + "domain": asGenericParser(ParseValidDomain), + "domains": asGenericParser(ParseValidDomains), + "number": asGenericParser(ParseNumber), + "https_url": asGenericParser(ParseHttpsURL), + "slug": asGenericParser(ParseSlug), + "file": asGenericParser(ParseFile), + "email": asGenericParser(ParseEmail), + "nonempty": asGenericParser(ParseNonEmpty), +} + +func asGenericParser[T any](parser Parser[T]) Parser[any] { + return func(s string) (value any, err error) { + value, err = parser(s) + return + } +} diff --git a/pkg/stringparser/stringparser.go b/pkg/stringparser/stringparser.go new file mode 100644 index 0000000..951f729 --- /dev/null +++ b/pkg/stringparser/stringparser.go @@ -0,0 +1,111 @@ +// Package stringparser provides Parser +package stringparser + +import ( + "net/url" + "regexp" + "strconv" + "strings" + + "github.com/FAU-CDI/wisski-distillery/pkg/fsx" + "github.com/pkg/errors" +) + +// Parser is used to read a value from a string and turn it into a golang value. +// It is simultaniously used to validate particular setting. +// +// Parsers can be found in this package as functions called Parse*. +// They are refered to by their name, e.g. ParseNonempty can be refered to by the name 'Nonempty'. +// See [Parse]. +type Parser[T any] func(s string) (T, error) + +// ParseAbspath checks that s is an absolute path and returns it as-is +func ParseAbspath(s string) (string, error) { + if !fsx.IsDirectory(s) { + return "", errors.Errorf("%q does not exist or is not a directory", s) + } + return s, nil +} + +// ParseFile checks that s is a valid file and returns it as-is +func ParseFile(s string) (string, error) { + if !fsx.IsFile(s) { + return "", errors.Errorf("%q does not exist or is not a regular file", s) + } + return s, nil +} + +var errEmptyString = errors.New("value is empty") + +// ParseNonEmpty checks that s is a non-empty string and returns it as-is +func ParseNonEmpty(s string) (string, error) { + if s == "" { + return "", errEmptyString + } + return s, nil +} + +var regexpDomain = regexp.MustCompile(`^([a-zA-Z0-9][-a-zA-Z0-9]*\.)*[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer! + +// ParseValidDomain checks that s is a valid domain and returns it as-is +func ParseValidDomain(s string) (string, error) { + if !regexpDomain.MatchString(s) { + return "", errors.Errorf("%q is not a valid domain", s) + } + return s, nil +} + +// ParseValidDomains checks that s is a comma-seperated list of valid domains and returns them as-is +func ParseValidDomains(s string) ([]string, error) { + if len(s) == 0 { + return []string{}, nil + } + domains := strings.Split(s, ",") + for _, d := range domains { + if !regexpDomain.MatchString(d) { + return nil, errors.Errorf("%q is not a valid domain", d) + } + } + return domains, nil +} + +// ParseNumber parses s as a decimal integer +func ParseNumber(s string) (int, error) { + value, err := strconv.ParseInt(s, 10, 64) + return int(value), err +} + +// ParseHttpsURL parses a string into a url that starts with 'https://' +func ParseHttpsURL(s string) (*url.URL, error) { + url, err := url.Parse(s) + if err != nil { + return nil, errors.Wrapf(err, "%q is not a valid URL", s) + } + if url.Scheme != "https" { + return nil, errors.Errorf("%q is not a valid https URL (%q)", s, url.Scheme) + } + return url, nil +} + +var regexpEmail = regexp.MustCompile(`^([-a-zA-Z0-9]+)\@([a-zA-Z0-9][-a-zA-Z0-9]*\.)*[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer! + +// ParseEmail checks that s represents an email, and then returns it as is. +func ParseEmail(s string) (string, error) { + if s == "" { // no email provided + return "", nil + } + if !regexpEmail.MatchString(s) { + return "", errors.Errorf("%q is not a valid email", s) + } + return s, nil +} + +var regexpSlug = regexp.MustCompile(`^[a-zA-Z0-9][-a-zA-Z0-9]*$`) // TODO: Make this regexp nicer! + +// ParseSlug parses s as a slug and returns it as is. +func ParseSlug(s string) (string, error) { + if !regexpSlug.MatchString(s) { + return "", errors.Errorf("%q is not a valid slug", s) + } + return s, nil +} diff --git a/program.go b/program.go index c41df0c..7d4d64e 100644 --- a/program.go +++ b/program.go @@ -4,14 +4,14 @@ import ( "os/user" "github.com/FAU-CDI/wisski-distillery/internal/core" - "github.com/FAU-CDI/wisski-distillery/internal/env" + "github.com/FAU-CDI/wisski-distillery/internal/wisski" "github.com/tkw1536/goprogram" "github.com/tkw1536/goprogram/exit" ) // these define the ggman-specific program types // none of these are strictly needed, they're just around for convenience -type wdcliEnv = *env.Distillery +type wdcliEnv = *wisski.Distillery type wdcliParameters = core.Params type wdcliRequirements = core.Requirements type wdCliFlags = core.Flags @@ -41,15 +41,15 @@ func NewProgram() Program { // when not running inside docker and we need a distillery // then we should warn if we are not using the distillery executable. - if dis := context.Environment; !context.Args.Flags.InternalInDocker && context.Description.Requirements.NeedsDistillery && !dis.UsingDistilleryExecutable() { - context.EPrintf(warnNoDeployWdcli, core.Executable, dis.ExecutablePath()) + if dis := context.Environment; !context.Args.Flags.InternalInDocker && context.Description.Requirements.NeedsDistillery && !dis.Config.UsingDistilleryExecutable() { + context.EPrintf(warnNoDeployWdcli, core.Executable, dis.Config.ExecutablePath()) } return nil }, NewEnvironment: func(params wdcliParameters, context Context) (e wdcliEnv, err error) { - return env.NewDistillery(params, context.Args.Flags, context.Description.Requirements) + return wisski.NewDistillery(params, context.Args.Flags, context.Description.Requirements) }, } }