All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.metaeffekt.artifact.resolver.alpine.AlpineDownloaderAdapter Maven / Gradle / Ivy

/*
 * Copyright 2021-2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.metaeffekt.artifact.resolver.alpine;

import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.metaeffekt.artifact.resolver.ArtifactResolverConfig;
import org.metaeffekt.artifact.resolver.MarkerFileStatus;
import org.metaeffekt.artifact.resolver.ResolverResult;
import org.metaeffekt.artifact.resolver.download.WebAccess;
import org.metaeffekt.artifact.resolver.generic.AbstractDownloadingAdapter;
import org.metaeffekt.artifact.resolver.generic.FileLocation;
import org.metaeffekt.artifact.resolver.manager.DownloadEnvironmentManager;
import org.metaeffekt.artifact.resolver.manager.execenv.StableExecutionEnvironment;
import org.metaeffekt.artifact.resolver.manager.execenv.exception.EnvironmentInitializationFailure;
import org.metaeffekt.artifact.resolver.manager.execenv.exception.EnvironmentOperationFailure;
import org.metaeffekt.artifact.resolver.model.ArtifactPartResolver;
import org.metaeffekt.artifact.resolver.model.DownloadLocation;
import org.metaeffekt.artifact.resolver.model.ResolvedArtifactPart;
import org.metaeffekt.core.inventory.processor.model.Artifact;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;

import static org.metaeffekt.artifact.resolver.model.ArtifactPartType.SOURCE_ARCHIVE;

@Slf4j
public class AlpineDownloaderAdapter extends AbstractDownloadingAdapter {

    private final DownloadEnvironmentManager environmentManager;
    private final ArtifactResolverConfig resolverConfig;

    public AlpineDownloaderAdapter(@NonNull DownloadLocation downloadLocation,
                                   @NonNull WebAccess webAccess,
                                   @NonNull DownloadEnvironmentManager environmentManager,
                                   @NonNull ArtifactResolverConfig resolverConfig) {
        super(downloadLocation, webAccess);
        this.environmentManager = environmentManager;
        this.resolverConfig = resolverConfig;
    }

    public ArtifactPartResolver downloadSourceArchive(Artifact artifact, @NonNull AlpinePackageReference reference) {
        // pre-compute params since they are properly final
        final AlpineEnvironmentParameters params = new AlpineEnvironmentParameters(getWebAccess().getProxyConfig(),
                "v" + reference.getAlpineVersion());
        return new ArtifactPartResolver(artifact, SOURCE_ARCHIVE,
                // supplier
                () -> resolve(params, reference),
                // enricher
                rap -> enrich(rap)
        );
    }

    public ResolverResult resolve(AlpineEnvironmentParameters environmentParameters, AlpinePackageReference reference) {
        final FileLocation fileLocation = reference.deriveFileLocation();

        // where to put the downloaded file
        final File outputFile = deriveDownloadFile(fileLocation);

        final MarkerFileStatus markerFileStatus = handleMarkerFile(fileLocation, reference);

        // skip attempt if marker status indicates 'skip'
        if (markerFileStatus.isIndicateSkip()) return null;

        // if the marker is valid and the output file exists; return with the existing file
        if (!markerFileStatus.isInvalidMarker() && outputFile.exists()) {
            return resolve(outputFile, null);
        }

        // if we got here. we reattempt download.
        log.trace("Attempting download for reference [{}].", reference);

        // delete existing marker file
        final File markerFile = markerFileStatus.getMarkerFile();
        if (markerFile.exists() && !markerFile.delete()) {
            log.warn("Could not delete existing marker [{}]. Ignoring.", markerFile);
        }

        // recreate marker file: marks download attempt and signifies failure if the marker remains empty
        final File markerParentFile = markerFile.getParentFile();
        if (!markerParentFile.exists() && !markerParentFile.mkdirs()) {
            log.warn("Could neither find nor create marker directory [{}]. Ignoring.", markerParentFile.getAbsolutePath());
        }

        // FIXME: we should provide this a method in FileUtils; incredibly verbose for a simple operation
        try {
            if (!markerFile.createNewFile()) {
                log.warn("Could not create new marker [{}] for reference [{}]. Ignoring.", markerFile, reference);
            }
        } catch (IOException e) {
            log.warn("Could not create marker [{}] for reference [{}]. Ignoring.", markerFile, reference);
        }

        // attempt download/resolve
        String errorMessage = null;
        try {
            final StableExecutionEnvironment execEnv;
            try {
                execEnv = environmentManager.getEnvironment(environmentParameters);
            } catch (EnvironmentInitializationFailure e) {
                throw new IllegalStateException("Could not get working environment.", e);
            }

            // sanity check returned environment
            if (!(execEnv instanceof AlpineEnvironment)) {
                log.error("Environment lookup for alpine returned object [{}] instead of an alpine env.", execEnv.getClass().getName());
                return null;
            }

            ((AlpineEnvironment) execEnv).getSourceArchive(reference, outputFile.toPath(), resolverConfig);

            // the download appears to have succeeded if we got here, fill the marker file
            writeMarkerFile(reference, markerFile, outputFile);

        } catch (EnvironmentOperationFailure e) {
            errorMessage = "Could not load package source archive.";
            log.error("Operation on package [{}] failed.", reference, e);
            log.error(ExceptionUtils.getStackTrace(e));
        }

        return resolve(outputFile, errorMessage);
    }

    private static void writeMarkerFile(AlpinePackageReference reference, File markerFile, File outputFile) {
        try {
            Files.write(markerFile.toPath(), outputFile.getAbsolutePath().getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            log.warn("Could not write to marker [{}] for ref [{}].", markerFile, reference);
        }
    }

    private Artifact enrich(ResolvedArtifactPart resolvedArtifactPart) {
        // the resolved part needs this info set to be of any use
        final Artifact enrichedArtifact = new Artifact(resolvedArtifactPart.getOriginalArtifact());
        final File resolvedFile = resolvedArtifactPart.getResolvedFile();
        if (resolvedFile != null) {
            enrichedArtifact.set(SOURCE_ARCHIVE.modulatePathAttribute(), resolvedFile.getAbsolutePath());
        }
        return enrichedArtifact;
    }

    private MarkerFileStatus handleMarkerFile(FileLocation fileLocation, AlpinePackageReference reference) {
        final File markerFile = deriveMarkerFile(fileLocation,
                reference.getPkgname() + "-" + reference.getPkgver() + "-" + reference.getPkgrel());

        if (markerFile.exists()) {
            log.trace("Marker file for [{}] exists.", reference);
            try {
                long markerSize = Files.size(markerFile.toPath());
                if (markerSize <= 0) {
                    return new MarkerFileStatus(markerFile, null, false, true);
                }
            } catch (IOException e) {
                log.warn("Error getting size of marker file [{}].", markerFile);
                return new MarkerFileStatus(markerFile, null, false, true);
            }
        } else {
            return new MarkerFileStatus(markerFile, null, false, false);
        }

        // a marker file with content exists; there was a previous attempt
        // check whether a downloaded file has been included in the marker file
        try (InputStream inputStream = Files.newInputStream(markerFile.toPath())) {
            final String pathFromMarker = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
            final File referencedFile = new File(pathFromMarker);

            // sanity check previous output
            if (referencedFile.exists()) {
                if (!Files.isRegularFile(referencedFile.toPath()) || Files.size(referencedFile.toPath()) <= 0) {
                    // the previous output is likely invalid: when might an empty archive appear?
                    log.warn("Previous referenced file [{}] by marker [{}] for package [{}] failed the sanity checks.",
                            referencedFile, markerFile, reference);
                    return new MarkerFileStatus(markerFile, null, false, true);
                } else {
                    // all appears fine with the marker; deliver the reference file if it exists
                    if (referencedFile.exists()) {
                        return new MarkerFileStatus(markerFile, referencedFile, false, false);
                    }
                }
            }
        } catch (IOException e) {
            log.error("Unable to read output path from marker file [{}].", markerFile);
            return new MarkerFileStatus(markerFile, null, false, true);
        }

        // check whether a previous attempts failed recently.
        if (hasRecentFailedDownloadAttempt(markerFile)) {
            // download failed recently: we lazily skip it for now, considering it likely to fail again.
            log.info("Skipping failed download [{}]. Recent attempt failed for package: [{}].", reference, reference);
            return new MarkerFileStatus(markerFile, null, true, false);
        }

        // in any other cases return a status indicating to attempt another download
        return new MarkerFileStatus(markerFile, null, false, false);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy