summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/spack/spack/binary_distribution.py58
-rw-r--r--lib/spack/spack/ci.py30
-rw-r--r--lib/spack/spack/cmd/ci.py204
-rw-r--r--lib/spack/spack/test/bindist.py49
-rw-r--r--lib/spack/spack/test/cmd/ci.py4
-rw-r--r--share/spack/gitlab/cloud_e4s_pipelines.yml24
-rw-r--r--share/spack/gitlab/pr_pipeline.yml18
7 files changed, 261 insertions, 126 deletions
diff --git a/lib/spack/spack/binary_distribution.py b/lib/spack/spack/binary_distribution.py
index ed1ce02aac..d643bde7b3 100644
--- a/lib/spack/spack/binary_distribution.py
+++ b/lib/spack/spack/binary_distribution.py
@@ -698,10 +698,23 @@ def generate_package_index(cache_prefix):
enable_transaction_locking=False,
record_fields=['spec', 'ref_count'])
- file_list = (
- entry
- for entry in web_util.list_url(cache_prefix)
- if entry.endswith('.yaml'))
+ try:
+ file_list = (
+ entry
+ for entry in web_util.list_url(cache_prefix)
+ if entry.endswith('.yaml'))
+ except KeyError as inst:
+ msg = 'No packages at {0}: {1}'.format(cache_prefix, inst)
+ tty.warn(msg)
+ return
+ except Exception as err:
+ # If we got some kind of S3 (access denied or other connection
+ # error), the first non boto-specific class in the exception
+ # hierarchy is Exception. Just print a warning and return
+ msg = 'Encountered problem listing packages at {0}: {1}'.format(
+ cache_prefix, err)
+ tty.warn(msg)
+ return
tty.debug('Retrieving spec.yaml files from {0} to build index'.format(
cache_prefix))
@@ -763,10 +776,23 @@ def generate_key_index(key_prefix, tmpdir=None):
url_util.format(key_prefix),
'to build key index')))
- fingerprints = (
- entry[:-4]
- for entry in web_util.list_url(key_prefix, recursive=False)
- if entry.endswith('.pub'))
+ try:
+ fingerprints = (
+ entry[:-4]
+ for entry in web_util.list_url(key_prefix, recursive=False)
+ if entry.endswith('.pub'))
+ except KeyError as inst:
+ msg = 'No keys at {0}: {1}'.format(key_prefix, inst)
+ tty.warn(msg)
+ return
+ except Exception as err:
+ # If we got some kind of S3 (access denied or other connection
+ # error), the first non boto-specific class in the exception
+ # hierarchy is Exception. Just print a warning and return
+ msg = 'Encountered problem listing keys at {0}: {1}'.format(
+ key_prefix, err)
+ tty.warn(msg)
+ return
remove_tmpdir = False
@@ -1284,15 +1310,16 @@ def extract_tarball(spec, filename, allow_root=False, unsigned=False,
os.remove(filename)
-def try_direct_fetch(spec, force=False, full_hash_match=False):
+def try_direct_fetch(spec, force=False, full_hash_match=False, mirrors=None):
"""
Try to find the spec directly on the configured mirrors
"""
specfile_name = tarball_name(spec, '.spec.yaml')
lenient = not full_hash_match
found_specs = []
+ spec_full_hash = spec.full_hash()
- for mirror in spack.mirror.MirrorCollection().values():
+ for mirror in spack.mirror.MirrorCollection(mirrors=mirrors).values():
buildcache_fetch_url = url_util.join(
mirror.fetch_url, _build_cache_relative_path, specfile_name)
@@ -1312,7 +1339,7 @@ def try_direct_fetch(spec, force=False, full_hash_match=False):
# Do not recompute the full hash for the fetched spec, instead just
# read the property.
- if lenient or fetched_spec._full_hash == spec.full_hash():
+ if lenient or fetched_spec._full_hash == spec_full_hash:
found_specs.append({
'mirror_url': mirror.fetch_url,
'spec': fetched_spec,
@@ -1321,7 +1348,8 @@ def try_direct_fetch(spec, force=False, full_hash_match=False):
return found_specs
-def get_mirrors_for_spec(spec=None, force=False, full_hash_match=False):
+def get_mirrors_for_spec(spec=None, force=False, full_hash_match=False,
+ mirrors_to_check=None):
"""
Check if concrete spec exists on mirrors and return a list
indicating the mirrors on which it can be found
@@ -1329,7 +1357,7 @@ def get_mirrors_for_spec(spec=None, force=False, full_hash_match=False):
if spec is None:
return []
- if not spack.mirror.MirrorCollection():
+ if not spack.mirror.MirrorCollection(mirrors=mirrors_to_check):
tty.debug("No Spack mirrors are currently configured")
return {}
@@ -1354,7 +1382,9 @@ def get_mirrors_for_spec(spec=None, force=False, full_hash_match=False):
if not results:
results = try_direct_fetch(spec,
force=force,
- full_hash_match=full_hash_match)
+ full_hash_match=full_hash_match,
+ mirrors=mirrors_to_check)
+
if results:
binary_index.update_spec(spec, results)
diff --git a/lib/spack/spack/ci.py b/lib/spack/spack/ci.py
index c9054052e6..a3a2248238 100644
--- a/lib/spack/spack/ci.py
+++ b/lib/spack/spack/ci.py
@@ -33,12 +33,16 @@ import spack.repo
from spack.spec import Spec
import spack.util.spack_yaml as syaml
import spack.util.web as web_util
+import spack.util.gpg as gpg_util
+import spack.util.url as url_util
JOB_RETRY_CONDITIONS = [
'always',
]
+SPACK_PR_MIRRORS_ROOT_URL = 's3://spack-pr-mirrors'
+
spack_gpg = spack.main.SpackCommand('gpg')
spack_compiler = spack.main.SpackCommand('compiler')
@@ -529,6 +533,12 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
os.environ.get('SPACK_IS_PR_PIPELINE', '').lower() == 'true'
)
+ spack_pr_branch = os.environ.get('SPACK_PR_BRANCH', None)
+ pr_mirror_url = None
+ if spack_pr_branch:
+ pr_mirror_url = url_util.join(SPACK_PR_MIRRORS_ROOT_URL,
+ spack_pr_branch)
+
ci_mirrors = yaml_root['mirrors']
mirror_urls = [url for url in ci_mirrors.values()]
@@ -858,6 +868,9 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
'SPACK_CHECKOUT_VERSION': version_to_clone,
}
+ if pr_mirror_url:
+ output_object['variables']['SPACK_PR_MIRROR_URL'] = pr_mirror_url
+
sorted_output = {}
for output_key, output_value in sorted(output_object.items()):
sorted_output[output_key] = output_value
@@ -920,6 +933,14 @@ def import_signing_key(base64_signing_key):
tty.debug(signing_keys_output)
+def can_sign_binaries():
+ return len(gpg_util.signing_keys()) == 1
+
+
+def can_verify_binaries():
+ return len(gpg_util.public_keys()) >= 1
+
+
def configure_compilers(compiler_action, scope=None):
if compiler_action == 'INSTALL_MISSING':
tty.debug('Make sure bootstrapped compiler will be installed')
@@ -1095,12 +1116,15 @@ def read_cdashid_from_mirror(spec, mirror_url):
return int(contents)
-def push_mirror_contents(env, spec, yaml_path, mirror_url, build_id):
+def push_mirror_contents(env, spec, yaml_path, mirror_url, build_id,
+ sign_binaries):
if mirror_url:
- tty.debug('Creating buildcache')
+ unsigned = not sign_binaries
+ tty.debug('Creating buildcache ({0})'.format(
+ 'unsigned' if unsigned else 'signed'))
buildcache._createtarball(env, spec_yaml=yaml_path, add_deps=False,
output_location=mirror_url, force=True,
- allow_root=True)
+ allow_root=True, unsigned=unsigned)
if build_id:
tty.debug('Writing cdashid ({0}) to remote mirror: {1}'.format(
build_id, mirror_url))
diff --git a/lib/spack/spack/cmd/ci.py b/lib/spack/spack/cmd/ci.py
index e0b9b6e0fa..3dd97e53b0 100644
--- a/lib/spack/spack/cmd/ci.py
+++ b/lib/spack/spack/cmd/ci.py
@@ -138,6 +138,7 @@ def ci_rebuild(args):
cdash_build_name = get_env_var('SPACK_CDASH_BUILD_NAME')
related_builds = get_env_var('SPACK_RELATED_BUILDS_CDASH')
pr_env_var = get_env_var('SPACK_IS_PR_PIPELINE')
+ pr_mirror_url = get_env_var('SPACK_PR_MIRROR_URL')
gitlab_ci = None
if 'gitlab-ci' in yaml_root:
@@ -180,8 +181,6 @@ def ci_rebuild(args):
tty.debug('job_spec_pkg_name = {0}'.format(job_spec_pkg_name))
tty.debug('compiler_action = {0}'.format(compiler_action))
- spack_cmd = exe.which('spack')
-
cdash_report_dir = os.path.join(ci_artifact_dir, 'cdash_report')
temp_dir = os.path.join(ci_artifact_dir, 'jobs_scratch_dir')
job_log_dir = os.path.join(temp_dir, 'logs')
@@ -235,20 +234,17 @@ def ci_rebuild(args):
for next_entry in directory_list:
tty.debug(' {0}'.format(next_entry))
- # Make a copy of the environment file, so we can overwrite the changed
- # version in between the two invocations of "spack install"
- env_src_path = env.manifest_path
- env_dirname = os.path.dirname(env_src_path)
- env_filename = os.path.basename(env_src_path)
- env_copyname = '{0}_BACKUP'.format(env_filename)
- env_dst_path = os.path.join(env_dirname, env_copyname)
- shutil.copyfile(env_src_path, env_dst_path)
-
tty.debug('job concrete spec path: {0}'.format(job_spec_yaml_path))
if signing_key:
spack_ci.import_signing_key(signing_key)
+ can_sign = spack_ci.can_sign_binaries()
+ sign_binaries = can_sign and spack_is_pr_pipeline is False
+
+ can_verify = spack_ci.can_verify_binaries()
+ verify_binaries = can_verify and spack_is_pr_pipeline is False
+
spack_ci.configure_compilers(compiler_action)
spec_map = spack_ci.get_concrete_specs(
@@ -273,27 +269,76 @@ def ci_rebuild(args):
with open(root_spec_yaml_path, 'w') as fd:
fd.write(spec_map['root'].to_yaml(hash=ht.build_hash))
- if bindist.needs_rebuild(job_spec, remote_mirror_url, True):
- # Binary on remote mirror is not up to date, we need to rebuild
- # it.
- #
- # FIXME: ensure mirror precedence causes this local mirror to
- # be chosen ahead of the remote one when installing deps
+ # TODO: Refactor the spack install command so it's easier to use from
+ # python modules. Currently we use "exe.which('spack')" to make it
+ # easier to install packages from here, but it introduces some
+ # problems, e.g. if we want the spack command to have access to the
+ # mirrors we're configuring, then we have to use the "spack" command
+ # to add the mirrors too, which in turn means that any code here *not*
+ # using the spack command does *not* have access to the mirrors.
+ spack_cmd = exe.which('spack')
+ mirrors_to_check = {
+ 'ci_remote_mirror': remote_mirror_url,
+ }
+
+ def add_mirror(mirror_name, mirror_url):
+ m_args = ['mirror', 'add', mirror_name, mirror_url]
+ tty.debug('Adding mirror: spack {0}'.format(m_args))
+ mirror_add_output = spack_cmd(*m_args)
+ # Workaround: Adding the mirrors above, using "spack_cmd" makes
+ # sure they're available later when we use "spack_cmd" to install
+ # the package. But then we also need to add them to this dict
+ # below, so they're available in this process (we end up having to
+ # pass them to "bindist.get_mirrors_for_spec()")
+ mirrors_to_check[mirror_name] = mirror_url
+ tty.debug('spack mirror add output: {0}'.format(mirror_add_output))
+
+ # Configure mirrors
+ if pr_mirror_url:
+ add_mirror('ci_pr_mirror', pr_mirror_url)
+
+ if enable_artifacts_mirror:
+ add_mirror('ci_artifact_mirror', artifact_mirror_url)
+
+ tty.debug('listing spack mirrors:')
+ spack_cmd('mirror', 'list')
+ spack_cmd('config', 'blame', 'mirrors')
+
+ # Checks all mirrors for a built spec with a matching full hash
+ matches = bindist.get_mirrors_for_spec(
+ job_spec, force=False, full_hash_match=True,
+ mirrors_to_check=mirrors_to_check)
+
+ if matches:
+ # Got at full hash match on at least one configured mirror. All
+ # matches represent the fully up-to-date spec, so should all be
+ # equivalent. If artifacts mirror is enabled, we just pick one
+ # of the matches and download the buildcache files from there to
+ # the artifacts, so they're available to be used by dependent
+ # jobs in subsequent stages.
+ tty.debug('No need to rebuild {0}'.format(job_spec_pkg_name))
if enable_artifacts_mirror:
- mirror_add_output = spack_cmd(
- 'mirror', 'add', 'local_mirror', artifact_mirror_url)
- tty.debug('spack mirror add:')
- tty.debug(mirror_add_output)
+ matching_mirror = matches[0]['mirror_url']
+ tty.debug('Getting {0} buildcache from {1}'.format(
+ job_spec_pkg_name, matching_mirror))
+ tty.debug('Downloading to {0}'.format(build_cache_dir))
+ buildcache.download_buildcache_files(
+ job_spec, build_cache_dir, True, matching_mirror)
+ else:
+ # No full hash match anywhere means we need to rebuild spec
- mirror_list_output = spack_cmd('mirror', 'list')
- tty.debug('listing spack mirrors:')
- tty.debug(mirror_list_output)
+ # Build up common install arguments
+ install_args = [
+ '-d', '-v', '-k', 'install',
+ '--keep-stage',
+ '--require-full-hash-match',
+ ]
- # 2) build up install arguments
- install_args = ['-d', '-v', '-k', 'install', '--keep-stage']
+ if not verify_binaries:
+ install_args.append('--no-check-signature')
- # 3) create/register a new build on CDash (if enabled)
- cdash_args = []
+ # Add arguments to create + register a new build on CDash (if
+ # enabled)
if enable_cdash:
tty.debug('Registering build with CDash')
(cdash_build_id,
@@ -304,82 +349,63 @@ def ci_rebuild(args):
cdash_upload_url = '{0}/submit.php?project={1}'.format(
cdash_base_url, cdash_project_enc)
- cdash_args = [
+ install_args.extend([
'--cdash-upload-url', cdash_upload_url,
'--cdash-build', cdash_build_name,
'--cdash-site', cdash_site,
'--cdash-buildstamp', cdash_build_stamp,
- ]
+ ])
- spec_cli_arg = [job_spec_yaml_path]
+ install_args.append(job_spec_yaml_path)
- tty.debug('Installing package')
+ tty.debug('Installing {0} from source'.format(job_spec.name))
try:
- # Two-pass install is intended to avoid spack trying to
- # install from buildcache even though the locally computed
- # full hash is different than the one stored in the spec.yaml
- # file on the remote mirror.
- first_pass_args = install_args + [
- '--cache-only',
- '--only',
- 'dependencies',
- ]
- first_pass_args.extend(spec_cli_arg)
- tty.debug('First pass install arguments: {0}'.format(
- first_pass_args))
- spack_cmd(*first_pass_args)
-
- # Overwrite the changed environment file so it doesn't break
- # the next install invocation.
- tty.debug('Copying {0} to {1}'.format(
- env_dst_path, env_src_path))
- shutil.copyfile(env_dst_path, env_src_path)
-
- second_pass_args = install_args + [
- '--no-cache',
- '--only',
- 'package',
- ]
- second_pass_args.extend(cdash_args)
- second_pass_args.extend(spec_cli_arg)
- tty.debug('Second pass install arguments: {0}'.format(
- second_pass_args))
- spack_cmd(*second_pass_args)
- except Exception as inst:
- tty.error('Caught exception during install:')
- tty.error(inst)
+ tty.debug('spack install arguments: {0}'.format(
+ install_args))
+ spack_cmd(*install_args)
+ finally:
+ spack_ci.copy_stage_logs_to_artifacts(job_spec, job_log_dir)
+
+ # Create buildcache on remote mirror, either on pr-specific
+ # mirror or on mirror defined in spack environment
+ if spack_is_pr_pipeline:
+ buildcache_mirror_url = pr_mirror_url
+ else:
+ buildcache_mirror_url = remote_mirror_url
- spack_ci.copy_stage_logs_to_artifacts(job_spec, job_log_dir)
-
- # 4) create buildcache on remote mirror, but not if this is
- # running to test a spack PR
- if not spack_is_pr_pipeline:
+ try:
spack_ci.push_mirror_contents(
- env, job_spec, job_spec_yaml_path, remote_mirror_url,
- cdash_build_id)
-
- # 5) create another copy of that buildcache on "local artifact
- # mirror" (only done if cash reporting is enabled)
+ env, job_spec, job_spec_yaml_path, buildcache_mirror_url,
+ cdash_build_id, sign_binaries)
+ except Exception as inst:
+ # If the mirror we're pushing to is on S3 and there's some
+ # permissions problem, for example, we can't just target
+ # that exception type here, since users of the
+ # `spack ci rebuild' may not need or want any dependency
+ # on boto3. So we use the first non-boto exception type
+ # in the heirarchy:
+ # boto3.exceptions.S3UploadFailedError
+ # boto3.exceptions.Boto3Error
+ # Exception
+ # BaseException
+ # object
+ err_msg = 'Error msg: {0}'.format(inst)
+ if 'Access Denied' in err_msg:
+ tty.msg('Permission problem writing to mirror')
+ tty.msg(err_msg)
+
+ # Create another copy of that buildcache on "local artifact
+ # mirror" (only done if artifacts buildcache is enabled)
spack_ci.push_mirror_contents(env, job_spec, job_spec_yaml_path,
- artifact_mirror_url, cdash_build_id)
+ artifact_mirror_url, cdash_build_id,
+ sign_binaries)
- # 6) relate this build to its dependencies on CDash (if enabled)
+ # Relate this build to its dependencies on CDash (if enabled)
if enable_cdash:
spack_ci.relate_cdash_builds(
spec_map, cdash_base_url, cdash_build_id, cdash_project,
- artifact_mirror_url or remote_mirror_url)
- else:
- # There is nothing to do here unless "local artifact mirror" is
- # enabled, in which case, we need to download the buildcache to
- # the local artifacts directory to be used by dependent jobs in
- # subsequent stages
- tty.debug('No need to rebuild {0}'.format(job_spec_pkg_name))
- if enable_artifacts_mirror:
- tty.debug('Getting {0} buildcache'.format(job_spec_pkg_name))
- tty.debug('Downloading to {0}'.format(build_cache_dir))
- buildcache.download_buildcache_files(
- job_spec, build_cache_dir, True, remote_mirror_url)
+ artifact_mirror_url or pr_mirror_url or remote_mirror_url)
def ci(parser, args):
diff --git a/lib/spack/spack/test/bindist.py b/lib/spack/spack/test/bindist.py
index ef54e658f0..974c50c260 100644
--- a/lib/spack/spack/test/bindist.py
+++ b/lib/spack/spack/test/bindist.py
@@ -22,6 +22,7 @@ import spack.cmd.mirror as mirror
from spack.main import SpackCommand
import spack.mirror
import spack.util.gpg
+import spack.util.web as web_util
from spack.directory_layout import YamlDirectoryLayout
from spack.spec import Spec
@@ -622,3 +623,51 @@ def test_spec_needs_rebuild(install_mockery_mutable_config, mock_packages,
rebuild = bindist.needs_rebuild(s, mirror_url, rebuild_on_errors=True)
assert rebuild
+
+
+def test_generate_indices_key_error(monkeypatch, capfd):
+
+ def mock_list_url(url, recursive=False):
+ print('mocked list_url({0}, {1})'.format(url, recursive))
+ raise KeyError('Test KeyError handling')
+
+ monkeypatch.setattr(web_util, 'list_url', mock_list_url)
+
+ test_url = 'file:///fake/keys/dir'
+
+ # Make sure generate_key_index handles the KeyError
+ bindist.generate_key_index(test_url)
+
+ err = capfd.readouterr()[1]
+ assert 'Warning: No keys at {0}'.format(test_url) in err
+
+ # Make sure generate_package_index handles the KeyError
+ bindist.generate_package_index(test_url)
+
+ err = capfd.readouterr()[1]
+ assert 'Warning: No packages at {0}'.format(test_url) in err
+
+
+def test_generate_indices_exception(monkeypatch, capfd):
+
+ def mock_list_url(url, recursive=False):
+ print('mocked list_url({0}, {1})'.format(url, recursive))
+ raise Exception('Test Exception handling')
+
+ monkeypatch.setattr(web_util, 'list_url', mock_list_url)
+
+ test_url = 'file:///fake/keys/dir'
+
+ # Make sure generate_key_index handles the Exception
+ bindist.generate_key_index(test_url)
+
+ err = capfd.readouterr()[1]
+ expect = 'Encountered problem listing keys at {0}'.format(test_url)
+ assert expect in err
+
+ # Make sure generate_package_index handles the Exception
+ bindist.generate_package_index(test_url)
+
+ err = capfd.readouterr()[1]
+ expect = 'Encountered problem listing packages at {0}'.format(test_url)
+ assert expect in err
diff --git a/lib/spack/spack/test/cmd/ci.py b/lib/spack/spack/test/cmd/ci.py
index 4e19cf384d..82734c922a 100644
--- a/lib/spack/spack/test/cmd/ci.py
+++ b/lib/spack/spack/test/cmd/ci.py
@@ -733,9 +733,9 @@ spack:
install_cmd('--keep-stage', yaml_path)
- # env, spec, yaml_path, mirror_url, build_id
+ # env, spec, yaml_path, mirror_url, build_id, sign_binaries
ci.push_mirror_contents(
- env, concrete_spec, yaml_path, mirror_url, '42')
+ env, concrete_spec, yaml_path, mirror_url, '42', True)
buildcache_path = os.path.join(mirror_dir.strpath, 'build_cache')
diff --git a/share/spack/gitlab/cloud_e4s_pipelines.yml b/share/spack/gitlab/cloud_e4s_pipelines.yml
new file mode 100644
index 0000000000..67a74cf971
--- /dev/null
+++ b/share/spack/gitlab/cloud_e4s_pipelines.yml
@@ -0,0 +1,24 @@
+pr_pipeline:
+ only:
+ - /^github\/pr[\d]+_.*$/
+ variables:
+ SPACK_REF: ${CI_COMMIT_SHA}
+ SPACK_PR_BRANCH: ${CI_COMMIT_REF_NAME}
+ SPACK_IS_PR_PIPELINE: "True"
+ AWS_ACCESS_KEY_ID: ${PR_MIRRORS_AWS_ACCESS_KEY_ID}
+ AWS_SECRET_ACCESS_KEY: ${PR_MIRRORS_AWS_SECRET_ACCESS_KEY}
+ trigger:
+ project: spack/e4s
+ strategy: depend
+
+develop_pipeline:
+ only:
+ - /^github\/develop$/
+ variables:
+ SPACK_REF: ${CI_COMMIT_SHA}
+ AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
+ AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
+ SPACK_SIGNING_KEY: ${SPACK_SIGNING_KEY}
+ trigger:
+ project: spack/e4s
+ strategy: depend
diff --git a/share/spack/gitlab/pr_pipeline.yml b/share/spack/gitlab/pr_pipeline.yml
deleted file mode 100644
index 367a4b9077..0000000000
--- a/share/spack/gitlab/pr_pipeline.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-pr_pipeline:
- only:
- - external_pull_requests
- variables:
- SPACK_REF: ${CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_NAME}
- SPACK_IS_PR_PIPELINE: "True"
- trigger:
- project: spack/e4s
- strategy: depend
-
-merge_pipeline:
- only:
- - develop
- variables:
- SPACK_REF: ${CI_COMMIT_SHA}
- trigger:
- project: spack/e4s
- strategy: depend \ No newline at end of file