diff options
| author | Joseph Hunkeler <jhunkeler@users.noreply.github.com> | 2024-07-20 11:56:16 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-07-20 11:56:16 -0400 | 
| commit | 9489d31f6314322d26ec43196284b94069d6cd3a (patch) | |
| tree | 3c314ff91b187faa2ba0f9ca2faf866d4fb97610 | |
| parent | 07dc44efdc5c2fbc2b34c969e623d3b0bc0df15a (diff) | |
| download | stasis-9489d31f6314322d26ec43196284b94069d6cd3a.tar.gz | |
Regression tests, envctl, and bug fixes (#13)
* Found too many bugs
* Implements a regression test
* Moves and completely refactors the envctl code
* Allows the user to keep @STR@ values in output files (if you want full control over where external packages comes from post-build)
* Fixes wording in a few places
* envctl redaction is not implemented yet. The original redaction code hasn't been modified.
* Use generic.ini instead of bare_minimum.ini
| -rw-r--r-- | README.md | 197 | ||||
| -rw-r--r-- | include/core.h | 12 | ||||
| -rw-r--r-- | include/envctl.h | 37 | ||||
| -rw-r--r-- | src/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/delivery.c | 12 | ||||
| -rw-r--r-- | src/envctl.c | 125 | ||||
| -rw-r--r-- | src/globals.c | 20 | ||||
| -rw-r--r-- | src/stasis_main.c | 75 | ||||
| -rw-r--r-- | tests/CMakeLists.txt | 9 | ||||
| -rw-r--r-- | tests/data/generic.ini | 51 | ||||
| -rw-r--r-- | tests/rt_generic.sh | 87 | 
11 files changed, 500 insertions, 126 deletions
| @@ -141,80 +141,83 @@ stasis mydelivery.ini  # Configuration -## Environment variables - -| Name                                      | Purpose                                                                 |  -|-------------------------------------------|-------------------------------------------------------------------------| -| TMPDIR                                    | Change default path to store temporary data                             | -| STASIS_ROOT                               | Change default path to write STASIS's data                              | -| STASIS_SYSCONFDIR                         | Change default path to search for configuration files                   |  -| STASIS_CPU_COUNT<br/>(alias: CPU_COUNT)   | Number of available CPUs                                                | -| STASIS_GH_TOKEN<br/>(alias: GITHUB_TOKEN) | GitHub API token<br/>(Scope: "Contents" repository permissions (write)) | -| STASIS_JF_ARTIFACTORY_URL                 | Artifactory service URL (ending in `/artifactory`)                      |  -| STASIS_JF_ACCESS_TOKEN                    | Artifactory Access Token                                                |  -| STASIS_JF_USER                            | Artifactory username                                                    |  -| STASIS_JF_PASSWORD                        | Artifactory password                                                    |  -| STASIS_JF_SSH_KEY_PATH                    | Path to SSH public key file                                             | -| STASIS_JF_SSH_PASSPHRASE                  | Password associated with SSH public key file                            |  -| STASIS_JF_CLIENT_CERT_CERT_PATH           | Path to OpenSSL cert files                                              |  -| STASIS_JF_CLIENT_CERT_KEY_PATH            | OpenSSL key file (in cert path)                                         |  -| STASIS_JF_REPO                            | Artifactory "generic" repository to write to                            |  - -# Variable expansion - -## Template strings - -Template strings can be accessed using the `{{ subject.key }}` notation in any STASIS configuration file. - -| Name                        | Purpose                                                                                                                 | -|-----------------------------|-------------------------------------------------------------------------------------------------------------------------| -| meta.name                   | Delivery name                                                                                                           | -| meta.version                | Delivery version                                                                                                        | -| meta.codename               | Delivery codename                                                                                                       | -| meta.mission                | Delivery mission                                                                                                        | -| meta.python                 | Python version (e.g. 3.11)                                                                                              | -| meta.python_compact         | Python (e.g. 311)                                                                                                       | -| info.time_str_epoch         | UNIX Epoch timestamp                                                                                                    | -| info.release_name           | Rendered delivery release name                                                                                          | -| info.build_name             | Rendered delivery build name                                                                                            | -| info.build_number           | Rendered delivery build number                                                                                          | -| storage.tmpdir              | Ohymcal temp directory                                                                                                  | -| storage.delivery_dir        | STASIS delivery output directory                                                                                        | -| storage.results_dir         | STASIS test results directory                                                                                           | -| storage.conda_artifact_dir  | STASIS conda package directory                                                                                          | -| storage.wheel_artifact_dir  | STASIS wheel package directory                                                                                          | -| storage.build_sources_dir   | STASIS sources directory                                                                                                | -| storage.build_docker_dir    | STASIS docker directory                                                                                                 | -| conda.installer_name        | Conda distribution name                                                                                                 | -| conda.installer_version     | Conda distribution version                                                                                              | -| conda.installer_platform    | Conda target platform                                                                                                   | -| conda.installer_arch        | Conda target architecture                                                                                               | -| conda.installer_baseurl     | Conda installer URL                                                                                                     | -| system.arch                 | System CPU Architecture                                                                                                 | -| system.platform             | System Platform (OS)                                                                                                    | -| deploy.docker.registry      | Docker registry                                                                                                         | -| deploy.jfrog.repo           | Artifactory destination repository                                                                                      | -| workaround.tox_posargs      | Return populated `-c` and `--root` tox arguments.<br/>Force-enables positional arguments in tox's command line parser.  | -| workaround.conda_reactivate | Reinitialize the conda runtime environment.<br/>Use this after calling `conda install` from within a `[test:*].script`. | +## Command Line Options + +| Long Option         | Short Option | Purpose                                                        | +|:--------------------|:------------:|:---------------------------------------------------------------| +| --help              |      -h      | Display usage statement                                        | +| --version           |      -V      | Display program version                                        | +| --continue-on-error |      -C      | Allow tests to fail                                            | +| --config ARG        |    -c ARG    | Read STASIS configuration file                                 | +| --python ARG        |    -p ARG    | Override version of Python in configuration                    | +| --verbose           |      -v      | Increase output verbosity                                      | +| --unbuffered        |      -U      | Disable line buffering                                         | +| --update-base       |     n/a      | Update conda installation prior to STATIS environment creation | +| --overwrite         |     n/a      | Overwrite an existing release                                  | +| --no-docker         |     n/a      | Do not build docker images                                     | +| --no-artifactory    |     n/a      | Do not upload artifacts to Artifactory                         | +| --no-testing        |     n/a      | Do not execute test scripts                                    | +| --no-rewrite        |     n/a      | Do not rewrite paths and URLs in output files                  | +| DELIVERY_FILE       |     n/a      | STASIS delivery file                                           | -The template engine also provides an interface to environment variables using the `{{ env:VARIABLE_NAME }}` notation. -```ini -[meta] -name = {{ env:MY_DYNAMIC_DELIVERY_NAME }} -version = {{ env:MY_DYNAMIC_DELIVERY_VERSION }} -python = {{ env:MY_DYNAMIC_PYTHON_VERSION }} -``` - -## Template Functions -Template functions can be accessed using the `{{ func:NAME(ARG,...) }}` notation. - -| Name                          | Purpose                                      | -|-------------------------------|----------------------------------------------| -| get_github_release_notes_auto | Generate release notes for all test contexts | +## Environment variables -# Delivery files +| Name                            | Purpose                                                                 |  +|---------------------------------|-------------------------------------------------------------------------| +| TMPDIR                          | Change default path to store temporary data                             | +| STASIS_ROOT                     | Change default path to write STASIS's data                              | +| STASIS_SYSCONFDIR               | Change default path to search for configuration files                   |  +| STASIS_CPU_COUNT                | Number of available CPUs                                                | +| STASIS_GH_TOKEN                 | GitHub API token<br/>(Scope: "Contents" repository permissions (write)) | +| STASIS_JF_ARTIFACTORY_URL       | Artifactory service URL (ending in `/artifactory`)                      |  +| STASIS_JF_ACCESS_TOKEN          | Artifactory Access Token                                                |  +| STASIS_JF_USER                  | Artifactory username                                                    |  +| STASIS_JF_PASSWORD              | Artifactory password                                                    |  +| STASIS_JF_SSH_KEY_PATH          | Path to SSH public key file                                             | +| STASIS_JF_SSH_PASSPHRASE        | Password associated with SSH public key file                            |  +| STASIS_JF_CLIENT_CERT_CERT_PATH | Path to OpenSSL cert files                                              |  +| STASIS_JF_CLIENT_CERT_KEY_PATH  | OpenSSL key file (in cert path)                                         |  +| STASIS_JF_REPO                  | Artifactory "generic" repository to write to                            |  + +## Main configuration (stasis.ini) + +The default path to the configuration file is `[CMAKE_INSTALL_PREFIX]/etc/stasis/stasis.ini`. You may override this by setting the `STASIS_SYSCONFDIR` environment variable to a path that points elsewhere.  + +### Sections + +#### default  + +| Name                           |  Type   | Purpose                                                              | +|--------------------------------|:-------:|----------------------------------------------------------------------| +| continue_on_error              | Boolean | Keep going even if a test fails                                      | +| always_update_base_environment | Boolean | Update all packages in the base to the latest release                | +| conda_fresh_start              | Boolean | Remove conda installation during initialization                      | +| conda_install_prefix           | String  | Install conda in a custom prefix path                                | +| conda_packages                 |  List   | Conda packages to be installed/overridden in the `base` environment  | +| pip_packages                   |  List   | Python packages to be installed/overridden in the `base` environment | +| conda_staging_url              | String  | URL to conda channel                                                 | + +### jfrog_cli_download + +| Name           |  Type  | Purpose                                                               | +|----------------|:------:|-----------------------------------------------------------------------| +| url            | String | Base URL of JFrog CLI release server                                  | +| project        | String | Product identifier (i.e. `jfrog-cli`)                                 | +| version_series | String | Product version series (i.e. `v2-jf`)                                 | +| version        | String | Product version to install. `[RELEASE]` downloads the latest version. | +| filename       | String | Product file name (i.e. `jf`)                                         | + +### deploy:artifactory + +| Name |  Type  | Purpose                                              | +|------|:------:|------------------------------------------------------| +| url  | String | Set artifactory service URL (ending in /artifactory) | +| repo | String | Set artifactory repository                           | + + +# Delivery configuration  ## Sections @@ -285,6 +288,60 @@ The `deploy:docker` section controls how Docker images are created, when a `Dock  | image_compression | String | Compression program (with arguments)         | N        |  | build_args        | List   | Values passed to `docker build --build-args` | N        |  | tags              | List   | Docker image tag(s)                          | Y        | +# Variable expansion + +## Template strings + +Template strings can be accessed using the `{{ subject.key }}` notation in any STASIS configuration file. + +| Name                        | Purpose                                                                                                                 | +|-----------------------------|-------------------------------------------------------------------------------------------------------------------------| +| meta.name                   | Delivery name                                                                                                           | +| meta.version                | Delivery version                                                                                                        | +| meta.codename               | Delivery codename                                                                                                       | +| meta.mission                | Delivery mission                                                                                                        | +| meta.python                 | Python version (e.g. 3.11)                                                                                              | +| meta.python_compact         | Python (e.g. 311)                                                                                                       | +| info.time_str_epoch         | UNIX Epoch timestamp                                                                                                    | +| info.release_name           | Rendered delivery release name                                                                                          | +| info.build_name             | Rendered delivery build name                                                                                            | +| info.build_number           | Rendered delivery build number                                                                                          | +| storage.tmpdir              | Ohymcal temp directory                                                                                                  | +| storage.delivery_dir        | STASIS delivery output directory                                                                                        | +| storage.results_dir         | STASIS test results directory                                                                                           | +| storage.conda_artifact_dir  | STASIS conda package directory                                                                                          | +| storage.wheel_artifact_dir  | STASIS wheel package directory                                                                                          | +| storage.build_sources_dir   | STASIS sources directory                                                                                                | +| storage.build_docker_dir    | STASIS docker directory                                                                                                 | +| conda.installer_name        | Conda distribution name                                                                                                 | +| conda.installer_version     | Conda distribution version                                                                                              | +| conda.installer_platform    | Conda target platform                                                                                                   | +| conda.installer_arch        | Conda target architecture                                                                                               | +| conda.installer_baseurl     | Conda installer URL                                                                                                     | +| system.arch                 | System CPU Architecture                                                                                                 | +| system.platform             | System Platform (OS)                                                                                                    | +| deploy.docker.registry      | Docker registry                                                                                                         | +| deploy.jfrog.repo           | Artifactory destination repository                                                                                      | +| workaround.tox_posargs      | Return populated `-c` and `--root` tox arguments.<br/>Force-enables positional arguments in tox's command line parser.  | +| workaround.conda_reactivate | Reinitialize the conda runtime environment.<br/>Use this after calling `conda install` from within a `[test:*].script`. | + +The template engine also provides an interface to environment variables using the `{{ env:VARIABLE_NAME }}` notation. + +```ini +[meta] +name = {{ env:MY_DYNAMIC_DELIVERY_NAME }} +version = {{ env:MY_DYNAMIC_DELIVERY_VERSION }} +python = {{ env:MY_DYNAMIC_PYTHON_VERSION }} +``` + +## Template Functions + +Template functions can be accessed using the `{{ func:NAME(ARG,...) }}` notation. + +| Name                          | Purpose                                      | +|-------------------------------|----------------------------------------------| +| get_github_release_notes_auto | Generate release notes for all test contexts | +  # Mission files diff --git a/include/core.h b/include/core.h index d065096..1ea0f5e 100644 --- a/include/core.h +++ b/include/core.h @@ -21,6 +21,7 @@  #define HTTP_ERROR(X) X >= 400  #include "config.h" +#include "envctl.h"  #include "template.h"  #include "utils.h"  #include "copy.h" @@ -59,11 +60,6 @@          } \      } while (0) -struct EnvCtl { -    unsigned flags; -    const char *name[10]; -}; -  struct STASIS_GLOBAL {      bool verbose; //!< Enable verbose output      bool always_update_base_environment; //!< Update base environment immediately after activation @@ -73,6 +69,7 @@ struct STASIS_GLOBAL {      bool enable_artifactory; //!< Enable artifactory uploads      bool enable_testing; //!< Enable package testing      bool enable_overwrite; //!< Enable release file clobbering +    bool enable_rewrite_spec_stage_2; //!< Enable automatic @STR@ replacement in output files      struct StrList *conda_packages; //!< Conda packages to install after initial activation      struct StrList *pip_packages; //!< Pip packages to install after initial activation      char *tmpdir; //!< Path to temporary storage directory @@ -93,13 +90,10 @@ struct STASIS_GLOBAL {          char *repo;          char *url;      } jfrog; -    struct EnvCtl envctl[]; +    struct EnvCtl *envctl;  };  extern struct STASIS_GLOBAL globals; -#define STASIS_ENVCTL_PASSTHRU 0 << 1 -#define STASIS_ENVCTL_REQUIRED 1 << 1 -#define STASIS_ENVCTL_REDACT 2 << 1  extern const char *VERSION;  extern const char *AUTHOR;  extern const char *BANNER; diff --git a/include/envctl.h b/include/envctl.h new file mode 100644 index 0000000..c8ef357 --- /dev/null +++ b/include/envctl.h @@ -0,0 +1,37 @@ +#ifndef STASIS_ENVCTL_H +#define STASIS_ENVCTL_H + +#include <stdlib.h> + +#define STASIS_ENVCTL_PASSTHRU 0 +#define STASIS_ENVCTL_REQUIRED 1 << 1 +#define STASIS_ENVCTL_REDACT 1 << 2 +#define STASIS_ENVCTL_DEFAULT_ALLOC 100 + +#define STASIS_ENVCTL_RET_FAIL (-1) +#define STASIS_ENVCTL_RET_SUCCESS 1 +#define STASIS_ENVCTL_RET_IGNORE 2 +typedef int (envctl_except_fn)(const void *, const void *); + +struct EnvCtl_Item { +    unsigned flags; //<! One or more STASIS_ENVCTL_* flags +    const char *name; //<! Environment variable name +    envctl_except_fn *callback; +}; + +struct EnvCtl { +    size_t num_alloc; +    size_t num_used; +    struct EnvCtl_Item **item; +}; + +struct EnvCtl *envctl_init(); +int envctl_register(struct EnvCtl **envctl, unsigned flags, envctl_except_fn *callback, const char *name); +unsigned envctl_get_flags(const struct EnvCtl *envctl, const char *name); +unsigned envctl_check_required(unsigned flags); +unsigned envctl_check_redact(unsigned flags); +int envctl_check_present(const struct EnvCtl_Item *item, const char *name); +void envctl_do_required(const struct EnvCtl *envctl, int verbose); +void envctl_free(struct EnvCtl **envctl); + +#endif // STASIS_ENVCTL_H
\ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a7b06f7..2399dc5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,6 +24,7 @@ add_library(stasis_core STATIC          junitxml.c          github.c          template_func_proto.c +        envctl.c  )  add_executable(stasis diff --git a/src/delivery.c b/src/delivery.c index b27ab08..d7b9b99 100644 --- a/src/delivery.c +++ b/src/delivery.c @@ -1676,7 +1676,7 @@ void delivery_rewrite_spec(struct Delivery *ctx, char *filename, unsigned stage)          }          remove(tempfile);          guard_free(tempfile); -    } else if (stage == DELIVERY_REWRITE_SPEC_STAGE_2) { +    } else if (globals.enable_rewrite_spec_stage_2 && stage == DELIVERY_REWRITE_SPEC_STAGE_2) {          // Replace "local" channel with the staging URL          if (ctx->storage.conda_staging_url) {              file_replace_text(filename, "@CONDA_CHANNEL@", ctx->storage.conda_staging_url, 0); @@ -1684,15 +1684,19 @@ void delivery_rewrite_spec(struct Delivery *ctx, char *filename, unsigned stage)              sprintf(output, "%s/%s/%s/%s/packages/conda", globals.jfrog.url, globals.jfrog.repo, ctx->meta.mission, ctx->info.build_name);              file_replace_text(filename, "@CONDA_CHANNEL@", output, 0);          } else { -            msg(STASIS_MSG_WARN, "conda_staging_url is not configured\n", filename); -            file_replace_text(filename, "  - @CONDA_CHANNEL@", "", 0); +            msg(STASIS_MSG_WARN, "conda_staging_dir is not configured. Using fallback: '%s'\n", ctx->storage.conda_artifact_dir); +            file_replace_text(filename, "@CONDA_CHANNEL@", ctx->storage.conda_artifact_dir, 0);          }          if (ctx->storage.wheel_staging_url) {              file_replace_text(filename, "@PIP_ARGUMENTS@", ctx->storage.wheel_staging_url, 0); -        } else if (globals.jfrog.repo) { +        } else if (globals.jfrog.url && globals.jfrog.repo) {              sprintf(output, "--extra-index-url %s/%s/%s/%s/packages/wheels", globals.jfrog.url, globals.jfrog.repo, ctx->meta.mission, ctx->info.build_name);              file_replace_text(filename, "@PIP_ARGUMENTS@", output, 0); +        } else { +            msg(STASIS_MSG_WARN, "wheel_staging_dir is not configured. Using fallback: '%s'\n", ctx->storage.wheel_artifact_dir); +            sprintf(output, "--extra-index-url file://%s", ctx->storage.wheel_artifact_dir); +            file_replace_text(filename, "@PIP_ARGUMENTS@", output, 0);          }      }  } diff --git a/src/envctl.c b/src/envctl.c new file mode 100644 index 0000000..78dd760 --- /dev/null +++ b/src/envctl.c @@ -0,0 +1,125 @@ +#include "envctl.h" +#include "core.h" + +struct EnvCtl *envctl_init() { +    struct EnvCtl *result; + +    result = calloc(1, sizeof(*result)); +    if (!result) { +        return NULL; +    } + +    result->num_alloc = STASIS_ENVCTL_DEFAULT_ALLOC; +    result->item = calloc(result->num_alloc + 1, sizeof(result->item)); +    if (!result->item) { +        guard_free(result); +        return NULL; +    } + +    return result; +} + +static int callback_builtin_nop(const void *a, const void *b) { +    return STASIS_ENVCTL_RET_SUCCESS; +} + +int envctl_register(struct EnvCtl **envctl, unsigned flags, envctl_except_fn *callback, const char *name) { +    if ((*envctl)->num_used == (*envctl)->num_alloc) { +        (*envctl)->num_alloc += STASIS_ENVCTL_DEFAULT_ALLOC; +        struct EnvCtl_Item **tmp = realloc((*envctl)->item, (*envctl)->num_alloc + 1 * sizeof((*envctl)->item)); +        if (!tmp) { +            return 1; +        } else { +            (*envctl)->item = tmp; +        } +    } + +    struct EnvCtl_Item **item = (*envctl)->item; +    item[(*envctl)->num_used] = calloc(1, sizeof(*item[0])); +    if (!item[(*envctl)->num_used]) { +        return 1; +    } +    if (!callback) { +        callback = &callback_builtin_nop; +    } +    item[(*envctl)->num_used]->callback = callback; +    item[(*envctl)->num_used]->name = name; +    item[(*envctl)->num_used]->flags = flags; + +    (*envctl)->num_used++; +    return 0; +} + +size_t envctl_get_index(const struct EnvCtl *envctl, const char *name) { +    for (size_t i = 0; i < envctl->num_used; i++) { +        if (!strcmp(envctl->item[i]->name, name)) { +            // pack state flag, outer (struct) index and inner (name) index +            return 1L << 63L | i; +        } +    } +    return 0; +} + +void envctl_decode_index(size_t in_i, size_t *state, size_t *out_i, size_t *name_i) { +    *state = ((in_i >> 63L) & 1); +    *out_i = in_i & 0xffffffffL; +} + +unsigned envctl_check_required(unsigned flags) { +    return flags & STASIS_ENVCTL_REQUIRED; +} + +unsigned envctl_check_redact(unsigned flags) { +    return flags & STASIS_ENVCTL_REDACT; +} + +int envctl_check_present(const struct EnvCtl_Item *item, const char *name) { +    return ((!strcmp(item->name, name)) && getenv(name)) ? 1 : 0; +} + +unsigned envctl_get_flags(const struct EnvCtl *envctl, const char *name) { +    size_t poll_index = envctl_get_index(envctl, name); +    size_t id = 0; +    size_t name_id = 0; +    size_t state = 0; +    envctl_decode_index(poll_index, &state, &id, &name_id); +    if (!state) { +        return 0; +    } else { +        fprintf(stderr, "managed environment variable: %s\n", name); +    } +    return envctl->item[id]->flags; +} + +void envctl_do_required(const struct EnvCtl *envctl, int verbose) { +    for (size_t i = 0; i < envctl->num_used; i++) { +        struct EnvCtl_Item *item = envctl->item[i]; +        const char *name = item->name; +        envctl_except_fn *callback = item->callback; + +        if (verbose) { +            msg(STASIS_MSG_L2, "Verifying %s\n", name); +        } +        int code = callback((const void *) item, (const void *) name); +        if (code == STASIS_ENVCTL_RET_IGNORE || code == STASIS_ENVCTL_RET_SUCCESS) { +            continue; +        } else if (code == STASIS_ENVCTL_RET_FAIL) { +            fprintf(stderr, "\n%s must be set. Exiting.\n", name); +            exit(1); +        } else { +            fprintf(stderr, "\nan unknown envctl callback code occurred: %d\n", code); +            exit(1); +        } +    } +} + +void envctl_free(struct EnvCtl **envctl) { +    if (!envctl) { +        return; +    } +    for (size_t i = 0; i < (*envctl)->num_used; i++) { +        guard_free((*envctl)->item[i]); +    } +    guard_free((*envctl)->item); +    guard_free(*envctl); +}
\ No newline at end of file diff --git a/src/globals.c b/src/globals.c index 18a32b5..5fdf05d 100644 --- a/src/globals.c +++ b/src/globals.c @@ -36,22 +36,7 @@ struct STASIS_GLOBAL globals = {          .enable_docker = true,          .enable_artifactory = true,          .enable_testing = true, -        .envctl = { -            {.flags = STASIS_ENVCTL_PASSTHRU, .name = {"TMPDIR", NULL}}, -            {.flags = STASIS_ENVCTL_PASSTHRU, .name = {"STASIS_ROOT", NULL}}, -            {.flags = STASIS_ENVCTL_PASSTHRU, .name = {"STASIS_SYSCONFDIR", NULL}}, -            {.flags = STASIS_ENVCTL_PASSTHRU, .name = {"STASIS_CPU_COUNT", "CPU_COUNT", NULL}}, -            {.flags = STASIS_ENVCTL_REQUIRED | STASIS_ENVCTL_REDACT, .name={"STASIS_GH_TOKEN", "GITHUB_TOKEN", NULL}}, -            {.flags = STASIS_ENVCTL_REDACT, .name = {"STASIS_JF_ACCESS_TOKEN", NULL}}, -            {.flags = STASIS_ENVCTL_PASSTHRU, .name = {"STASIS_JF_USER", NULL}}, -            {.flags = STASIS_ENVCTL_REDACT, .name = {"STASIS_JF_PASSWORD", NULL}}, -            {.flags = STASIS_ENVCTL_REDACT, .name = {"STASIS_JF_SSH_KEY_PATH", NULL}}, -            {.flags = STASIS_ENVCTL_REDACT, .name = {"STASIS_JF_SSH_PASSPHRASE", NULL}}, -            {.flags = STASIS_ENVCTL_REDACT, .name = {"STASIS_JF_CLIENT_CERT_CERT_PATH", NULL}}, -            {.flags = STASIS_ENVCTL_REDACT, .name = {"STASIS_JF_CLIENT_CERT_KEY_PATH", NULL}}, -            {.flags = STASIS_ENVCTL_REQUIRED, .name = {"STASIS_JF_REPO", NULL}}, -            {.flags = 0, .name = {NULL}}, -        } +        .enable_rewrite_spec_stage_2 = true,  };  void globals_free() { @@ -71,4 +56,7 @@ void globals_free() {      guard_free(globals.jfrog.remote_filename);      guard_free(globals.workaround.tox_posargs);      guard_free(globals.workaround.conda_reactivate); +    if (globals.envctl) { +        envctl_free(&globals.envctl); +    }  } diff --git a/src/stasis_main.c b/src/stasis_main.c index 9348140..7e2262a 100644 --- a/src/stasis_main.c +++ b/src/stasis_main.c @@ -10,6 +10,7 @@  #define OPT_NO_ARTIFACTORY 1002  #define OPT_NO_TESTING 1003  #define OPT_OVERWRITE 1004 +#define OPT_NO_REWRITE_SPEC_STAGE_2 1005  static struct option long_options[] = {          {"help", no_argument, 0, 'h'},          {"version", no_argument, 0, 'V'}, @@ -23,6 +24,7 @@ static struct option long_options[] = {          {"no-docker", no_argument, 0, OPT_NO_DOCKER},          {"no-artifactory", no_argument, 0, OPT_NO_ARTIFACTORY},          {"no-testing", no_argument, 0, OPT_NO_TESTING}, +        {"no-rewrite", no_argument, 0, OPT_NO_REWRITE_SPEC_STAGE_2},          {0, 0, 0, 0},  }; @@ -39,6 +41,7 @@ const char *long_options_help[] = {          "Do not build docker images",          "Do not upload artifacts to Artifactory",          "Do not execute test scripts", +        "Do not rewrite paths and URLs in output files",          NULL,  }; @@ -98,36 +101,53 @@ static void usage(char *progname) {      }  } -static int get_envctl_key_index_(size_t i) { -    for (int x = 0; x < (int)(sizeof(globals.envctl[i].name) / sizeof(*globals.envctl[i].name)); x++) { -        const char *name = globals.envctl[i].name[x]; -        if (!name) { -            return -1; -        } -        const char *data = getenv(name); -        if (data != NULL) { -            return x; +static int callback_except_jf(const void *a, const void *b) { +    const struct EnvCtl_Item *item = a; +    const char *name = b; + +    if (!globals.enable_artifactory) { +        return STASIS_ENVCTL_RET_IGNORE; +    } + +    if (envctl_check_required(item->flags)) { +        const char *content = getenv(name); +        if (!content || isempty((char *) content)) { +            return STASIS_ENVCTL_RET_FAIL;          }      } -    return -1; + +    return STASIS_ENVCTL_RET_SUCCESS; +} + +static int callback_except_gh(const void *a, const void *b) { +    const struct EnvCtl_Item *item = a; +    const char *name = b; +    //printf("GH exception check: %s\n", name); +    if (envctl_check_required(item->flags) && envctl_check_present(item, name)) { +        return STASIS_ENVCTL_RET_SUCCESS; +    } + +    return STASIS_ENVCTL_RET_FAIL;  }  static void check_system_env_requirements() {      msg(STASIS_MSG_L1, "Checking environment\n"); -    for (size_t i = 0; globals.envctl[i].name[0] != NULL; i++) { -        unsigned int flags = globals.envctl[i].flags; -        int key = get_envctl_key_index_(i); -        if (key < 0) { -            if (flags & STASIS_ENVCTL_REQUIRED) { -                if (!strcmp(globals.envctl[i].name[0], "STASIS_JF_REPO") && !globals.enable_artifactory) { -                    continue; -                } -                msg(STASIS_MSG_L2 | STASIS_MSG_ERROR, "Environment variable '%s' must be defined.\n", -                    globals.envctl[i].name[0]); -                exit(1); -            } -        } -    } +    globals.envctl = envctl_init(); +    envctl_register(&globals.envctl, STASIS_ENVCTL_PASSTHRU, NULL, "TMPDIR"); +    envctl_register(&globals.envctl, STASIS_ENVCTL_PASSTHRU, NULL, "STASIS_ROOT"); +    envctl_register(&globals.envctl, STASIS_ENVCTL_PASSTHRU, NULL, "STASIS_SYSCONFDIR"); +    envctl_register(&globals.envctl, STASIS_ENVCTL_PASSTHRU, NULL, "STASIS_CPU_COUNT"); +    envctl_register(&globals.envctl, STASIS_ENVCTL_REQUIRED | STASIS_ENVCTL_REDACT, callback_except_gh, "STASIS_GH_TOKEN"); +    envctl_register(&globals.envctl, STASIS_ENVCTL_REQUIRED, callback_except_jf, "STASIS_JF_ARTIFACTORY_URL"); +    envctl_register(&globals.envctl, STASIS_ENVCTL_REDACT, NULL, "STASIS_JF_ACCESS_TOKEN"); +    envctl_register(&globals.envctl, STASIS_ENVCTL_PASSTHRU, NULL, "STASIS_JF_USER"); +    envctl_register(&globals.envctl, STASIS_ENVCTL_REDACT, NULL, "STASIS_JF_PASSWORD"); +    envctl_register(&globals.envctl, STASIS_ENVCTL_REDACT, NULL, "STASIS_JF_SSH_KEY_PATH"); +    envctl_register(&globals.envctl, STASIS_ENVCTL_REDACT, NULL, "STASIS_JF_SSH_PASSPHRASE"); +    envctl_register(&globals.envctl, STASIS_ENVCTL_REDACT, NULL, "STASIS_JF_CLIENT_CERT_CERT_PATH"); +    envctl_register(&globals.envctl, STASIS_ENVCTL_REDACT, NULL, "STASIS_JF_CLIENT_CERT_KEY_PATH"); +    envctl_register(&globals.envctl, STASIS_ENVCTL_REQUIRED, callback_except_jf, "STASIS_JF_REPO"); +    envctl_do_required(globals.envctl, globals.verbose);  }  static void check_system_requirements(struct Delivery *ctx) { @@ -244,6 +264,9 @@ int main(int argc, char *argv[]) {              case OPT_NO_TESTING:                  globals.enable_testing = false;                  break; +            case OPT_NO_REWRITE_SPEC_STAGE_2: +                globals.enable_rewrite_spec_stage_2 = false; +                break;              case '?':              default:                  exit(1); @@ -649,10 +672,10 @@ int main(int argc, char *argv[]) {              msg(STASIS_MSG_L1, "Uploading artifacts\n");              delivery_artifact_upload(&ctx);          } else { -            msg(STASIS_MSG_L1 | STASIS_MSG_WARN, "Artifact uploading is disabled\n"); +            msg(STASIS_MSG_L1 | STASIS_MSG_WARN, "Artifactory upload is disabled by CLI argument\n");          }      } else { -        msg(STASIS_MSG_L1 | STASIS_MSG_WARN, "Artifact uploading is disabled. deploy:artifactory is not configured\n"); +        msg(STASIS_MSG_L1 | STASIS_MSG_WARN, "Artifactory upload is disabled. deploy:artifactory is not configured\n");      }      msg(STASIS_MSG_L1, "Cleaning up\n"); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f3a45e8..f06638e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,15 +2,20 @@ include_directories(          ${CMAKE_SOURCE_DIR}/include          ${CMAKE_BINARY_DIR}/include  ) +find_program(BASH_PROGRAM bash)  set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/tests)  set(CTEST_BINARY_DIRECTORY ${PROJECT_BINARY_DIR}/tests)  set(nix_gnu_cflags -Wno-error -Wno-unused-parameter -Wno-discarded-qualifiers)  set(nix_clang_cflags -Wno-unused-parameter -Wno-incompatible-pointer-types-discards-qualifiers)  set(win_msvc_cflags /Wall) - +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/data/generic.ini ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)  file(GLOB files "test_*.c") +if (BASH_PROGRAM) +    add_test (rt_generic ${BASH_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/rt_generic.sh) +endif() +  foreach(file ${files})      string(REGEX REPLACE "(^.*/|\\.[^.]*$)" "" file_without_ext ${file})      add_executable(${file_without_ext} ${file}) @@ -29,4 +34,6 @@ foreach(file ${files})      set_tests_properties(${file_without_ext}              PROPERTIES              SKIP_RETURN_CODE 127) +    set_property(TEST ${file_without_ext} +            PROPERTY ENVIRONMENT "STASIS_SYSCONFDIR=${CMAKE_SOURCE_DIR}")  endforeach()
\ No newline at end of file diff --git a/tests/data/generic.ini b/tests/data/generic.ini new file mode 100644 index 0000000..ef0180b --- /dev/null +++ b/tests/data/generic.ini @@ -0,0 +1,51 @@ +[meta] +mission = generic +name = GENERIC +version = 1.2.3 +rc = 1 +final = false +based_on = +python = 3.11 + + +[conda] +installer_name = Miniforge3 +installer_version = 24.3.0-0 +installer_platform = {{env:STASIS_CONDA_PLATFORM}} +installer_arch = {{env:STASIS_CONDA_ARCH}} +installer_baseurl = https://github.com/conda-forge/miniforge/releases/download/{{conda.installer_version}} +;conda_packages = +pip_packages = +    firewatch + + +[runtime] +PYTHONUNBUFFERED = 1 + + +[test:firewatch] +version = 0.0.4 +repository = https://github.com/astroconda/firewatch +script = +    pip install -e '.' + + +[deploy:artifactory:delivery] +files = +    {{ storage.output_dir }}/** +dest = {{ meta.mission }}/{{ info.build_name }}/ + + +[deploy:docker] +;registry = bytesalad.stsci.edu +image_compression = zstd -v -9 -c +build_args = +    SNAPSHOT_INPUT={{ info.release_name }}.yml +    SNAPSHOT_PKGDIR=packages +tags = +    {{ meta.name }}:{{ info.build_number }}-py{{ meta.python_compact }} +    {{ deploy.docker.registry }}/{{ meta.name }}:{{ info.build_number }}-py{{ meta.python_compact }} +test_script = +    source /etc/profile +    python -m pip freeze +    mamba info diff --git a/tests/rt_generic.sh b/tests/rt_generic.sh new file mode 100644 index 0000000..200f66f --- /dev/null +++ b/tests/rt_generic.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +set -x +unset STASIS_SYSCONFDIR +if [ -n "$GITHUB_TOKEN" ] && [ -z "$STASIS_GH_TOKEN"]; then +    export STASIS_GH_TOKEN="$GITHUB_TOKEN" +else +    export STASIS_GH_TOKEN="anonymous" +fi + +topdir=$(pwd) + +ws="rt_workspace" +mkdir -p "$ws" +ws="$(realpath $ws)" + +prefix="$ws"/local +mkdir -p "$prefix" + +bdir="$ws"/build +mkdir -p "$bdir" + +pushd "$bdir" +cmake -DCMAKE_INSTALL_PREFIX="$prefix" "${topdir}"/../.. +make install +export PATH="$prefix/bin:$PATH" +popd + +pushd "$ws" +    type -P stasis +    type -P stasis_indexer + +    stasis --no-docker --no-artifactory --unbuffered -v "$topdir"/generic.ini +    retcode=$? + +    set +x + +    echo "#### Files ####" +    find stasis/*/output | sort +    echo + +    echo "#### Contents ####" +    files=$(find stasis/*/output -type f \( -name '*.yml' -o -name '*.md' -o -name '*.stasis' \) | sort) +    for x in $files; do +        echo +        echo "FILENAME: $x" +        echo +        cat "$x" +        echo "[EOF]" +        echo + +        fail_on_main=( +            "(null)" +        ) +        for cond in "${fail_on_main[@]}"; do +            if grep --color -H -n "$cond" "$x" >&2; then +                echo "ERROR DETECTED IN $x!" >&2 +                retcode=2 +            fi +        done +    done + +    # Something above failed, so drop out. Don't bother indexing. +    # Don't clean up either. +    (( retcode )) && exit $retcode + +    fail_on_indexer=( +        "(null)" +    ) +    logfile="stasis_indexer.log" +    set -x +    stasis_indexer --web --unbuffered -v stasis/* 2>&1 | tee "$logfile" + +    set +x +    find output + +    for cond in "${fail_on_indexer[@]}"; do +        if grep --color -H -n "$cond" "$logfile" >&2; then +            echo "ERROR DETECTED IN INDEX OPERATION!" >&2 +            exit 1 +        fi +    done + +popd + +rm -rf "$ws" + +exit $retcode
\ No newline at end of file | 
