aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@users.noreply.github.com>2024-07-20 11:56:16 -0400
committerGitHub <noreply@github.com>2024-07-20 11:56:16 -0400
commit9489d31f6314322d26ec43196284b94069d6cd3a (patch)
tree3c314ff91b187faa2ba0f9ca2faf866d4fb97610
parent07dc44efdc5c2fbc2b34c969e623d3b0bc0df15a (diff)
downloadstasis-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.md197
-rw-r--r--include/core.h12
-rw-r--r--include/envctl.h37
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/delivery.c12
-rw-r--r--src/envctl.c125
-rw-r--r--src/globals.c20
-rw-r--r--src/stasis_main.c75
-rw-r--r--tests/CMakeLists.txt9
-rw-r--r--tests/data/generic.ini51
-rw-r--r--tests/rt_generic.sh87
11 files changed, 500 insertions, 126 deletions
diff --git a/README.md b/README.md
index baa0864..14cd650 100644
--- a/README.md
+++ b/README.md
@@ -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