Repository: conda-forge/conda-smithy

Issue:
Inject more secret tokens into conda build
I am (ab)using conda-smithy for some generic CI tasks. For example I build nightlies for a project and create coverage statistics as part of the `build.sh`. See https://github.com/flatsurf/exact-real for an example.

I would like to be able to upload these coverage statistics to coveralls or similar. For this I need to be able to inject a secret into the conda build environment. Currently, only the `BINSTAR_TOKEN` is passed on.

Is this use case considered beyond the scope of conda-smithy, or should I work on a PR for such a feature?

Currently, I use a patched conda-smithy:

* I need to load them from the CI configuration:
```patch
--- a/conda_smithy/templates/azure-pipelines-linux.yml.tmpl
+++ b/conda_smithy/templates/azure-pipelines-linux.yml.tmpl
@@ -37,3 +37,4 @@ jobs:
     displayName: Run docker build
     env:
       BINSTAR_TOKEN: $(BINSTAR_TOKEN)
+      COVERALLS_REPO_TOKEN: $(COVERALLS_REPO_TOKEN)
```

* Make them available inside docker:
```patch
--- a/conda_smithy/templates/run_docker_build.sh.tmpl
+++ b/conda_smithy/templates/run_docker_build.sh.tmpl
@@ -63,6 +63,7 @@ export UPLOAD_PACKAGES="${UPLOAD_PACKAGES:-True}"
            -e CONFIG \
            -e BINSTAR_TOKEN \
            -e HOST_USER_ID \
+           -e COVERALLS_REPO_TOKEN \
            -e UPLOAD_PACKAGES \
            -e GIT_BRANCH \
            -e UPLOAD_ON_BRANCH \
```

* Make them available to conda-build:
```patch
--- a/conda_smithy/templates/build_steps.sh.tmpl
+++ b/conda_smithy/templates/build_steps.sh.tmpl
@@ -12,3 +12,7 @@ export RECIPE_ROOT="${RECIPE_ROOT:-/home/conda/recipe_root}"
 export CI_SUPPORT="${FEEDSTOCK_ROOT}/.ci_support"
 export CONFIG_FILE="${CI_SUPPORT}/${CONFIG}.yaml"
 
+# Inject secrets into conda-build which filters the environment. If we
+# whitelisted explicitly in meta.yaml, these would be publicly readable in the
+# uploaded package at anaconda.org.
+export -p | grep COVERALLS_REPO_TOKEN >> /tmp/secrets || true
```

The last step is really not pretty. I am happy to hear any better ideas on how to achieve this :)


Discussion:
>  If we whitelisted explicitly in meta.yaml, these would be publicly readable in the uploaded package at anaconda.org.

I don't understand. Do you have an example?
Let me verify that this is still the case actually.
One problem is certainly, that the value of the environment variable is shown in the build log when using `build.script_env` in `meta.yml`:
```
/home/jule/proj/conda/miniconda3/lib/python3.7/site-packages/conda_build/environ.py:426: UserWarning: The environment variable 'MYVAR' is being passed through with value SECRETSECRETSECRET.  If you are splitting build and test phases with --no-test, please ensure that this value is also set similarly at test time.
```
@isuruf: Ok. So the value of the variable is not in the anconda upload (anymore?) But it shows up in the logs which is pretty bad of course.
If I remember correctly, azure hides these variables but that wouldn't work with other CIs probably.
We used to allow customizing `upload_or_check_non_existence.py` at https://github.com/conda-forge/conda-smithy/blob/6837490e2ba0e90abe909242abfa4338f256c084/conda_smithy/configure_feedstock.py#L633, but somebody has removed it. We should bring that back.
Or add a line at https://github.com/conda-forge/conda-forge-ci-setup-feedstock/blob/master/recipe/conda_forge_ci_setup/build_utils.py#L142 checking if there's a `recipe/post_process.py` and run that script.
Unfortunately, I need these variables in `build.sh`. What you are proposing happens after conda-build has finished, correct?
(I need them in build.sh because I need all the build artifacts intact. I could of course package all the build artifacts and the unzip them again later but it's probably annoying to figure out all the paths then when the environment variables set by conda-build are not around anymore.)
Okay. How about fixing `conda-build` to not echo those env variables?
That's probably a good idea. Then I'd just add the first two patches to conda-smithy.
@isuruf, the PR to conda-build passes tests. Feel free to weigh in there if you have anything to add :)

Resolving patch:
diff --git a/conda_smithy/configure_feedstock.py b/conda_smithy/configure_feedstock.py
--- a/conda_smithy/configure_feedstock.py
+++ b/conda_smithy/configure_feedstock.py
@@ -1482,6 +1482,7 @@ def _load_forge_config(forge_dir, exclusive_config_file):
         "bot": {"automerge": False},
         "conda_forge_output_validation": False,
         "private_upload": False,
+        "secrets": [],
     }
 
     forge_yml = os.path.join(forge_dir, "conda-forge.yml")
@@ -1531,6 +1532,16 @@ def _load_forge_config(forge_dir, exclusive_config_file):
             if "name" in config["azure"][f"settings_{plat}"]["pool"]:
                 del config["azure"][f"settings_{plat}"]["pool"]["vmImage"]
 
+    if config["conda_forge_output_validation"]:
+        config["secrets"] = sorted(
+            set(
+                config["secrets"]
+                + ["FEEDSTOCK_TOKEN", "STAGING_BINSTAR_TOKEN"]
+            )
+        )
+
+    config["secrets"] = sorted(set(config["secrets"] + ["BINSTAR_TOKEN"]))
+
     # An older conda-smithy used to have some files which should no longer exist,
     # remove those now.
     old_files = [


Test patch:
diff --git a/tests/recipes/click-test-feedstock/.appveyor.yml b/tests/recipes/click-test-feedstock/.appveyor.yml
--- a/tests/recipes/click-test-feedstock/.appveyor.yml
+++ b/tests/recipes/click-test-feedstock/.appveyor.yml
@@ -52,6 +52,6 @@ install:
 build: off
 
 test_script:
-    - conda build recipe -m .ci_support\appveyor_%CONFIG%.yaml --quiet
+    - conda build recipe -m .ci_support\appveyor_%CONFIG%.yaml --suppress-variables --quiet
 deploy_script:
     - cmd: upload_or_check_non_existence .\recipe conda-forge --channel=main -m .ci_support\appveyor_%CONFIG%.yaml
diff --git a/tests/recipes/click-test-feedstock/.circleci/run_docker_build.sh b/tests/recipes/click-test-feedstock/.circleci/run_docker_build.sh
--- a/tests/recipes/click-test-feedstock/.circleci/run_docker_build.sh
+++ b/tests/recipes/click-test-feedstock/.circleci/run_docker_build.sh
@@ -58,7 +58,7 @@ source run_conda_forge_build_setup
 # testing purposes: get conda-build from defaults
 conda install -n root --quiet --yes -c defaults conda-build
 
-conda build /recipe_root -m /feedstock_root/.ci_support/circle_${CONFIG}.yaml --quiet || exit 1
+conda build /recipe_root -m /feedstock_root/.ci_support/circle_${CONFIG}.yaml --quiet --suppress-variables || exit 1
 upload_or_check_non_existence /recipe_root conda-forge --channel=main -m /feedstock_root/.ci_support/circle_${CONFIG}.yaml || exit 1
 
 touch /feedstock_root/build_artefacts/conda-forge-build-done
diff --git a/tests/recipes/click-test-feedstock/.travis.yml b/tests/recipes/click-test-feedstock/.travis.yml
--- a/tests/recipes/click-test-feedstock/.travis.yml
+++ b/tests/recipes/click-test-feedstock/.travis.yml
@@ -53,6 +53,6 @@ install:
       conda install -n root --quiet --yes -c defaults conda-build
 
 script:
-  - conda build ./recipe -m ./.ci_support/travis_${CONFIG}.yaml
+  - conda build ./recipe -m ./.ci_support/travis_${CONFIG}.yaml --suppress-variables
 
   - upload_or_check_non_existence ./recipe conda-forge --channel=main -m ./.ci_support/travis_${CONFIG}.yaml
diff --git a/tests/test_configure_feedstock.py b/tests/test_configure_feedstock.py
--- a/tests/test_configure_feedstock.py
+++ b/tests/test_configure_feedstock.py
@@ -438,6 +438,54 @@ def test_readme_has_terminating_newline(noarch_recipe, jinja_env):
         assert readme_file.read() == b"\n"
 
 
+def test_secrets(py_recipe, jinja_env):
+    cnfgr_fdstk.render_azure(
+        jinja_env=jinja_env,
+        forge_config=py_recipe.config,
+        forge_dir=py_recipe.recipe,
+    )
+
+    run_docker_build = os.path.join(
+        py_recipe.recipe, ".scripts", "run_docker_build.sh"
+    )
+    with open(run_docker_build, "rb") as run_docker_build_file:
+        content = run_docker_build_file.read()
+    assert b"-e BINSTAR_TOKEN" in content
+
+    for config_yaml in os.listdir(
+        os.path.join(py_recipe.recipe, ".azure-pipelines")
+    ):
+        if config_yaml.endswith(".yaml"):
+            with open(config_yaml) as fo:
+                config = yaml.safe_load(fo)
+                if "jobs" in config:
+                    assert any(
+                        any(
+                            step.get("env", {}).get("BINSTAR_TOKEN", None)
+                            == "$(BINSTAR_TOKEN)"
+                            for step in job["steps"]
+                        )
+                        for job in config["jobs"]
+                    )
+
+    py_recipe.config["provider"]["linux_aarch64"] = "drone"
+    cnfgr_fdstk.render_drone(
+        jinja_env=jinja_env,
+        forge_config=py_recipe.config,
+        forge_dir=py_recipe.recipe,
+    )
+
+    with open(os.path.join(py_recipe.recipe, ".drone.yml")) as fo:
+        config = list(yaml.safe_load_all(fo))[-1]
+        assert any(
+            step.get("environment", {})
+            .get("BINSTAR_TOKEN", {})
+            .get("from_secret", None)
+            == "BINSTAR_TOKEN"
+            for step in config["steps"]
+        )
+
+
 def test_migrator_recipe(recipe_migration_cfep9, jinja_env):
     cnfgr_fdstk.render_azure(
         jinja_env=jinja_env,