• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jreleaser / jreleaser / #477

04 Apr 2025 05:53PM UTC coverage: 35.124% (-5.1%) from 40.183%
#477

push

github

aalmiray
fix(deploy): Add missing Forgejo messages

Related to #1842

18210 of 51845 relevant lines covered (35.12%)

0.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/core/jreleaser-engine/src/main/java/org/jreleaser/engine/catalog/Slsa.java
1
/*
2
 * SPDX-License-Identifier: Apache-2.0
3
 *
4
 * Copyright 2020-2025 The JReleaser authors.
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     https://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
package org.jreleaser.engine.catalog;
19

20
import com.fasterxml.jackson.databind.ObjectMapper;
21
import org.jreleaser.bundle.RB;
22
import org.jreleaser.engine.deploy.maven.ArtifactDeployers;
23
import org.jreleaser.model.JReleaserException;
24
import org.jreleaser.model.api.JReleaserCommand;
25
import org.jreleaser.model.api.hooks.ExecutionEvent;
26
import org.jreleaser.model.internal.JReleaserContext;
27
import org.jreleaser.model.internal.catalog.SlsaCataloger;
28
import org.jreleaser.model.internal.common.Artifact;
29
import org.jreleaser.model.internal.deploy.maven.Maven;
30
import org.jreleaser.model.internal.deploy.maven.MavenDeployer;
31
import org.jreleaser.model.internal.distributions.Distribution;
32
import org.jreleaser.model.internal.util.Artifacts;
33
import org.jreleaser.model.spi.catalog.CatalogProcessingException;
34
import org.jreleaser.model.spi.deploy.maven.Deployable;
35

36
import java.io.IOException;
37
import java.nio.file.FileSystem;
38
import java.nio.file.FileSystems;
39
import java.nio.file.Files;
40
import java.nio.file.Path;
41
import java.nio.file.PathMatcher;
42
import java.util.LinkedHashSet;
43
import java.util.Map;
44
import java.util.Objects;
45
import java.util.Set;
46
import java.util.TreeSet;
47

48
import static java.nio.charset.StandardCharsets.UTF_8;
49
import static org.jreleaser.engine.catalog.CatalogerSupport.fireCatalogEvent;
50
import static org.jreleaser.engine.checksum.Checksum.readHash;
51
import static org.jreleaser.model.api.catalog.SlsaCataloger.KEY_SKIP_SLSA;
52
import static org.jreleaser.model.internal.JReleaserSupport.supportedMavenDeployers;
53
import static org.jreleaser.util.Algorithm.SHA_256;
54
import static org.jreleaser.util.StringUtils.isNotBlank;
55

56
/**
57
 * @author Andres Almiray
58
 * @since 1.7.0
59
 */
60
public final class Slsa {
61
    private static final String GLOB_PREFIX = "glob:";
62
    private static final String REGEX_PREFIX = "regex:";
63

64
    private Slsa() {
65
        // noop
66
    }
67

68
    public static void catalog(JReleaserContext context) {
69
        context.getLogger().increaseIndent();
×
70
        context.getLogger().setPrefix("slsa");
×
71

72
        SlsaCataloger slsa = context.getModel().getCatalog().getSlsa();
×
73
        if (!slsa.isEnabled()) {
×
74
            context.getLogger().info(RB.$("catalogers.not.enabled"));
×
75
            context.getLogger().decreaseIndent();
×
76
            context.getLogger().restorePrefix();
×
77
            return;
×
78
        }
79

80
        try {
81
            fireCatalogEvent(ExecutionEvent.before(JReleaserCommand.CATALOG.toStep()), context, slsa);
×
82
            attestation(context, slsa);
×
83
            fireCatalogEvent(ExecutionEvent.success(JReleaserCommand.CATALOG.toStep()), context, slsa);
×
84
        } catch (CatalogProcessingException e) {
×
85
            fireCatalogEvent(ExecutionEvent.failure(JReleaserCommand.CATALOG.toStep(), e), context, slsa);
×
86
            throw new JReleaserException(RB.$("ERROR_unexpected_error"), e);
×
87
        } finally {
88
            context.getLogger().decreaseIndent();
×
89
            context.getLogger().restorePrefix();
×
90
        }
91
    }
×
92

93
    private static void attestation(JReleaserContext context, SlsaCataloger slsa) throws CatalogProcessingException {
94
        Set<PathMatcher> includes = new LinkedHashSet<>();
×
95
        Set<PathMatcher> excludes = new LinkedHashSet<>();
×
96

97
        FileSystem fileSystem = FileSystems.getDefault();
×
98
        for (String s : slsa.getIncludes()) {
×
99
            includes.add(fileSystem.getPathMatcher(normalize(s)));
×
100
        }
×
101
        for (String s : slsa.getExcludes()) {
×
102
            excludes.add(fileSystem.getPathMatcher(normalize(s)));
×
103
        }
×
104

105
        if (includes.isEmpty()) {
×
106
            includes.add(fileSystem.getPathMatcher(GLOB_PREFIX + "**/*"));
×
107
        }
108

109
        Attestation attestation = new Attestation(slsa.getResolvedAttestationName(context));
×
110

111
        context.getLogger().info(attestation.getName());
×
112

113
        if (slsa.isFiles()) {
×
114
            for (Artifact artifact : Artifacts.resolveFiles(context)) {
×
115
                if (!artifact.isActiveAndSelected() || artifact.extraPropertyIsTrue(KEY_SKIP_SLSA) ||
×
116
                    artifact.isOptional(context) && !artifact.resolvedPathExists() &&
×
117
                        !isIncluded(context, artifact, includes, excludes)) continue;
×
118
                readHash(context, SHA_256, artifact);
×
119
                addSubject(context, attestation, artifact);
×
120
            }
×
121
        }
122

123
        if (slsa.isArtifacts()) {
×
124
            for (Distribution distribution : context.getModel().getActiveDistributions()) {
×
125
                for (Artifact artifact : distribution.getArtifacts()) {
×
126
                    if (!artifact.isActiveAndSelected()) continue;
×
127
                    artifact.getEffectivePath(context, distribution);
×
128
                    if (artifact.isOptional(context) && !artifact.resolvedPathExists() &&
×
129
                        !isIncluded(context, artifact, includes, excludes)) continue;
×
130
                    readHash(context, distribution, SHA_256, artifact);
×
131
                    addSubject(context, attestation, artifact);
×
132
                }
×
133
            }
×
134
        }
135

136
        if (slsa.isDeployables()) {
×
137
            for (Deployable deployable : collectDeployables(context)) {
×
138
                if (!deployable.isPom() && !deployable.isArtifact()) continue;
×
139
                Artifact artifact = Artifact.of(deployable.getLocalPath());
×
140
                if (!isIncluded(context, artifact, includes, excludes)) continue;
×
141
                readHash(context, SHA_256, artifact);
×
142
                addSubject(context, attestation, artifact);
×
143
            }
×
144
        }
145

146
        if (attestation.getSubjects().isEmpty()) {
×
147
            context.getLogger().info(RB.$("catalog.no.artifacts"));
×
148
            context.getLogger().decreaseIndent();
×
149
            context.getLogger().restorePrefix();
×
150
            return;
×
151
        }
152

153
        String newContent = null;
×
154
        try {
155
            Attestations attestations = new Attestations();
×
156
            attestations.getAttestations().add(attestation);
×
157

158
            ObjectMapper objectMapper = new ObjectMapper();
×
159
            newContent = objectMapper.writerWithDefaultPrettyPrinter()
×
160
                .writeValueAsString(attestations) + System.lineSeparator();
×
161
        } catch (IOException e) {
×
162
            throw new JReleaserException(RB.$("ERROR_unexpected_error"), e);
×
163
        }
×
164

165
        Path attestationFile = context.getCatalogsDirectory()
×
166
            .resolve(slsa.getType())
×
167
            .resolve(attestation.getName());
×
168

169
        try {
170
            if (Files.exists(attestationFile)) {
×
171
                String oldContent = new String(Files.readAllBytes(attestationFile), UTF_8);
×
172
                if (newContent.equals(oldContent)) {
×
173
                    // no need to write down the same content
174
                    context.getLogger().info(RB.$("catalog.slsa.not.changed"));
×
175
                    context.getLogger().restorePrefix();
×
176
                    context.getLogger().decreaseIndent();
×
177
                    return;
×
178
                }
179
            }
180
        } catch (IOException ignored) {
×
181
            // OK
182
        }
×
183

184
        try {
185
            if (isNotBlank(newContent)) {
×
186
                Files.createDirectories(attestationFile.getParent());
×
187
                Files.write(attestationFile, newContent.getBytes(UTF_8));
×
188
            } else {
189
                Files.deleteIfExists(attestationFile);
×
190
            }
191
        } catch (IOException e) {
×
192
            throw new JReleaserException(RB.$("ERROR_unexpected_error_writing_file", attestationFile.toAbsolutePath()), e);
×
193
        }
×
194
    }
×
195

196
    private static String normalize(String pattern) {
197
        if (pattern.startsWith(GLOB_PREFIX) || pattern.startsWith(REGEX_PREFIX)) return pattern;
×
198
        return GLOB_PREFIX + pattern;
×
199
    }
200

201
    private static boolean isIncluded(JReleaserContext context, Artifact artifact, Set<PathMatcher> includes, Set<PathMatcher> excludes) {
202
        Path path = artifact.getEffectivePath(context);
×
203

204
        return includes.stream().anyMatch(matcher -> matcher.matches(path)) &&
×
205
            excludes.stream().noneMatch(matcher -> matcher.matches(path));
×
206
    }
207

208
    private static void addSubject(JReleaserContext context, Attestation attestation, Artifact artifact) {
209
        String artifactFileName = artifact.getEffectivePath(context).getFileName().toString();
×
210
        attestation.addSubject(artifactFileName, artifact.getHash(SHA_256));
×
211
        context.getLogger().debug("- " + artifactFileName);
×
212
    }
×
213

214
    private static Set<Deployable> collectDeployables(JReleaserContext context) {
215
        Set<String> stagingRepositories = new TreeSet<>();
×
216
        Set<Deployable> deployables = new TreeSet<>();
×
217
        Maven maven = context.getModel().getDeploy().getMaven();
×
218

219
        if (!context.getIncludedDeployerTypes().isEmpty()) {
×
220
            for (String deployerType : context.getIncludedDeployerTypes()) {
×
221
                if (!supportedMavenDeployers().contains(deployerType)) continue;
×
222

223
                Map<String, MavenDeployer<?>> deployers = maven.findMavenDeployersByType(deployerType);
×
224

225
                if (deployers.isEmpty()) return deployables;
×
226

227
                if (!context.getIncludedDeployerNames().isEmpty()) {
×
228
                    for (String deployerName : context.getIncludedDeployerNames()) {
×
229
                        if (!deployers.containsKey(deployerName)) continue;
×
230

231
                        MavenDeployer<?> deployer = deployers.get(deployerName);
×
232
                        if (!deployer.isEnabled()) continue;
×
233

234
                        handleDeployer(context, stagingRepositories, deployables, deployer);
×
235
                    }
×
236
                } else {
237
                    for (MavenDeployer<?> deployer : deployers.values()) {
×
238
                        handleDeployer(context, stagingRepositories, deployables, deployer);
×
239
                    }
×
240
                }
241
            }
×
242
        } else if (!context.getIncludedDeployerNames().isEmpty()) {
×
243
            for (String deployerName : context.getIncludedDeployerNames()) {
×
244
                maven.findAllActiveMavenDeployers().stream()
×
245
                    .filter(a -> deployerName.equals(a.getName()))
×
246
                    .forEach(deployer -> handleDeployer(context, stagingRepositories, deployables, deployer));
×
247
            }
×
248
        } else {
249
            for (MavenDeployer<?> deployer : maven.findAllActiveMavenDeployers()) {
×
250
                if (context.getExcludedDeployerTypes().contains(deployer.getType()) ||
×
251
                    context.getExcludedDeployerNames().contains(deployer.getName())) {
×
252
                    continue;
×
253
                }
254

255
                handleDeployer(context, stagingRepositories, deployables, deployer);
×
256
            }
×
257
        }
258

259
        return deployables;
×
260
    }
261

262
    private static void handleDeployer(JReleaserContext context, Set<String> stagingRepositories, Set<Deployable> deployables, MavenDeployer<?> deployer) {
263
        org.jreleaser.model.spi.deploy.maven.MavenDeployer<?, ?> artifactMavenDeployer = ArtifactDeployers.findMavenDeployer(context, deployer);
×
264
        for (String stagingRepository : deployer.getStagingRepositories()) {
×
265
            if (stagingRepositories.contains(stagingRepository)) continue;
×
266
            artifactMavenDeployer.collectDeployables(deployables, stagingRepository);
×
267
        }
×
268
    }
×
269

270
    private static class Attestations {
×
271
        private final Set<Attestation> attestations = new LinkedHashSet<>();
×
272

273
        public Integer getVersion() {
274
            return 1;
×
275
        }
276

277
        public Set<Attestation> getAttestations() {
278
            return attestations;
×
279
        }
280
    }
281

282
    private static class Attestation {
283
        private final String name;
284
        private final Set<Subject> subjects = new TreeSet<>();
×
285

286
        private Attestation(String name) {
×
287
            this.name = name;
×
288
        }
×
289

290
        public String getName() {
291
            return name;
×
292
        }
293

294
        public Set<Subject> getSubjects() {
295
            return subjects;
×
296
        }
297

298
        public void addSubject(String name, String sha256) {
299
            subjects.add(new Subject(name, sha256));
×
300
        }
×
301
    }
302

303
    private static class Subject implements Comparable<Subject> {
304
        private final String name;
305
        private final Digest digest;
306

307
        private Subject(String name, String sha256) {
×
308
            this.name = name;
×
309
            this.digest = new Digest(sha256);
×
310
        }
×
311

312
        public String getName() {
313
            return name;
×
314
        }
315

316
        public Digest getDigest() {
317
            return digest;
×
318
        }
319

320
        @Override
321
        public boolean equals(Object o) {
322
            if (this == o) return true;
×
323
            if (o == null || getClass() != o.getClass()) return false;
×
324
            Subject subject = (Subject) o;
×
325
            return name.equals(subject.name);
×
326
        }
327

328
        @Override
329
        public int hashCode() {
330
            return Objects.hash(name);
×
331
        }
332

333
        @Override
334
        public int compareTo(Subject o) {
335
            if (null == o) return -1;
×
336
            return this.name.compareTo(o.name);
×
337
        }
338
    }
339

340
    private static class Digest {
341
        private final String sha256;
342

343
        private Digest(String sha256) {
×
344
            this.sha256 = sha256;
×
345
        }
×
346

347
        public String getSha256() {
348
            return sha256;
×
349
        }
350
    }
351
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc