Skip to content

Commit

Permalink
adding most of kube-linter rules to our lint command
Browse files Browse the repository at this point in the history
  • Loading branch information
rmannibucau committed Aug 20, 2023
1 parent 66bd8ae commit b31f9f1
Show file tree
Hide file tree
Showing 37 changed files with 3,111 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.stream.Collector;
import java.util.stream.Stream;

import static io.yupiik.bundlebee.core.lang.CompletionFutures.all;
Expand Down Expand Up @@ -90,6 +92,11 @@ public class LintCommand implements CompletingExecutable {
@ConfigProperty(name = "bundlebee.lint.ignoredRules", defaultValue = "-")
private List<String> ignoredRules;

@Inject
@Description("Comma separated list of rules to use (others being ignored). `all` means use all discovered rules.")
@ConfigProperty(name = "bundlebee.lint.forcedRules", defaultValue = "all")
private List<String> forcedRules;

@Inject
@Description("Should remediation be shown (it is verbose so skipped by default).")
@ConfigProperty(name = "bundlebee.lint.showRemediation", defaultValue = "false")
Expand Down Expand Up @@ -147,33 +154,50 @@ public CompletionStage<?> execute() {
return visit(result).thenRun(() -> postProcess(result));
}

private void doLint(final AlveolusHandler.AlveolusContext ctx,
final String descriptor,
final JsonObject desc,
final LintErrors result,
final List<LintingCheck> checks, final Manifest manifest) {
private CompletionStage<List<DecoratedLintError>> doLint(final AlveolusHandler.AlveolusContext ctx,
final String descriptor,
final JsonObject desc,
final List<LintingCheck> checks,
final Manifest manifest) {
if (ignoredAlveoli.contains(ctx.getAlveolus().getName()) || ignoredDescriptors.contains(descriptor)) {
log.finest(() -> "Ignoring '" + descriptor + "' from alveolus '" + ctx.getAlveolus().getName() + "'");
return;
return completedFuture(List.of());
}

final var ignored = ofNullable(desc.getJsonArray("$bundlebeeIgnoredLintingRules")).orElse(EMPTY_JSON_ARRAY);

log.finest(() -> "Linting " + ctx.getAlveolus().getName() + ": " + desc);
final var ld = new LintingCheck.LintableDescriptor(ctx.getAlveolus().getName(), descriptor, desc);
result.errors.addAll(checks.stream()
// excluded validations from the manifest
.filter(c -> manifest.getIgnoredLintingRules() == null || manifest.getIgnoredLintingRules().stream()
.noneMatch(r -> Objects.equals(r.getName(), c.name())))
// excluded validations from the descriptor itself in $bundlebeeIgnoredLintingRules attribute
.filter(c -> ignored.isEmpty() || ignored.stream()
.filter(it -> it.getValueType() == JsonValue.ValueType.STRING)
.map(it -> ((JsonString) it).getString())
.noneMatch(excluded -> Objects.equals(excluded, c.name())))
.filter(c -> c.accept(ld))
.flatMap(c -> c.validate(ld)
.map(it -> new DecoratedLintError(it, ctx.getAlveolus().getName(), descriptor, c.remediation())))
.collect(toList()));
return all(checks.stream()
// excluded validations from the manifest
.filter(c -> manifest.getIgnoredLintingRules() == null || manifest.getIgnoredLintingRules().stream()
.noneMatch(r -> Objects.equals(r.getName(), c.name())))
// excluded validations from the descriptor itself in $bundlebeeIgnoredLintingRules attribute
.filter(c -> ignored.isEmpty() || ignored.stream()
.filter(it -> it.getValueType() == JsonValue.ValueType.STRING)
.map(it -> ((JsonString) it).getString())
.noneMatch(excluded -> Objects.equals(excluded, c.name())))
.filter(c -> c.accept(ld))
.map(c -> c.validate(ld)
.thenApply(errors -> errors
.map(it -> new DecoratedLintError(it, ctx.getAlveolus().getName(), descriptor, c.remediation()))
.collect(toList())))
.collect(toList()),
mergeLists(),
true);
}

private Collector<List<DecoratedLintError>, List<DecoratedLintError>, List<DecoratedLintError>> mergeLists() {
return Collector.of(
ArrayList::new,
(a, it) -> {
synchronized (a) {
a.addAll(it);
}
}, (a, b) -> {
a.addAll(b);
return a;
});
}

private void postProcess(final LintErrors result) {
Expand Down Expand Up @@ -207,7 +231,7 @@ private void postProcess(final LintErrors result) {
});
}

private CompletionStage<Void> visit(final LintErrors result) {
private CompletionStage<List<DecoratedLintError>> visit(final LintErrors result) {
final var cache = archives.newCache();
final var checks = this.checks.stream()
.filter(it -> {
Expand All @@ -216,23 +240,49 @@ private CompletionStage<Void> visit(final LintErrors result) {
(ignoredRules.contains(clazz.getSimpleName()) || ignoredRules.contains(it.name())) :
ignoredRules.contains(it.name()));
})
.filter(it -> (forcedRules.size() == 1 && "all".equals(forcedRules.get(0))) || forcedRules.contains(it.name()))
.collect(toList());
if (!(forcedRules.size() == 1 && "all".equals(forcedRules.get(0))) && forcedRules.size() != checks.size()) {
throw new IllegalArgumentException("Didn't find all requested rules: " + forcedRules.stream()
.filter(it -> checks.stream().noneMatch(c -> Objects.equals(c.name(), it)))
.collect(joining(", ", "", " missing.")));
}
return visitor
.findRootAlveoli(from, manifest, alveolus)
.thenCompose(alveoli -> all(
alveoli.stream()
.map(it -> visitor.executeOnceOnAlveolus(
null, it.getManifest(), it.getAlveolus(), null,
(ctx, desc) -> k8s.forDescriptor("Linting", desc.getContent(), desc.getExtension(), json -> {
doLint(ctx, desc.getConfiguration().getName(), json, result, checks, it.getManifest());
return completedFuture(true);
}),
cache, null, "inspected"))
.collect(toList()), toList(),
.map(it -> {
final var allLints = new CopyOnWriteArrayList<CompletionStage<List<DecoratedLintError>>>();
visitor.executeOnceOnAlveolus(
null, it.getManifest(), it.getAlveolus(), null,
(ctx, desc) -> k8s.forDescriptor(
"Linting", desc.getContent(), desc.getExtension(),
json -> {
final var promise = doLint(ctx, desc.getConfiguration().getName(), json, checks, it.getManifest());
allLints.add(promise);
return promise;
}),
cache, null, "inspected");
return all(allLints, mergeLists(), true);
})
.collect(toList()),
mergeLists(),
true))
.thenRun(() -> result.errors.addAll(checks.stream()
.flatMap(c -> c.afterAll().map(e -> new DecoratedLintError(e, e.getAlveolus(), e.getDescriptor(), c.remediation())))
.collect(toList())));
.thenCompose(errors -> {
result.errors.addAll(errors);
return all(
checks.stream()
.map(c -> c.afterAll().thenApply(afterAllErrors -> afterAllErrors
.map(e -> new DecoratedLintError(e, e.getAlveolus(), e.getDescriptor(), c.remediation()))
.collect(toList())))
.collect(toList()),
mergeLists(),
true);
})
.thenApply(errors -> {
result.errors.addAll(errors);
return result.errors;
});
}

private static class LintErrors extends RuntimeException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.CompletionStage;
import java.util.function.BiConsumer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@

import javax.json.JsonObject;
import javax.json.JsonValue;
import java.util.concurrent.CompletionStage;
import java.util.stream.Stream;

import static java.util.Optional.ofNullable;
import static java.util.concurrent.CompletableFuture.completedStage;

public interface LintingCheck {
String name();
Expand All @@ -33,15 +35,15 @@ public interface LintingCheck {

boolean accept(LintableDescriptor descriptor);

Stream<LintError> validate(LintableDescriptor descriptor);
CompletionStage<Stream<LintError>> validate(LintableDescriptor descriptor);

/**
* Enables to create validations with dependencies between descriptors, use {@link #accept(LintableDescriptor)} as a visitor.
*
* @return validation errors after all descriptors got visited.
*/
default Stream<ContextualLintError> afterAll() {
return Stream.empty();
default CompletionStage<Stream<ContextualLintError>> afterAll() {
return completedStage(Stream.empty());
}

@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2021-2023 - Yupiik SAS - https://www.yupiik.com
* 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 io.yupiik.bundlebee.core.command.impl.lint;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.stream.Stream;

import static java.util.concurrent.CompletableFuture.completedStage;

public interface SynchronousLintingCheck extends LintingCheck {
boolean accept(LintableDescriptor descriptor);

@Override
default CompletionStage<Stream<LintError>> validate(LintableDescriptor descriptor) {
try {
return completedStage(validateSync(descriptor));
} catch (final RuntimeException re) {
final var future = new CompletableFuture<Stream<LintError>>();
future.completeExceptionally(re);
return future;
}
}

Stream<LintError> validateSync(LintableDescriptor descriptor);

@Override
default CompletionStage<Stream<ContextualLintError>> afterAll() {
try {
return completedStage(afterAllSync());
} catch (final RuntimeException re) {
final var future = new CompletableFuture<Stream<ContextualLintError>>();
future.completeExceptionally(re);
return future;
}
}

default Stream<ContextualLintError> afterAllSync() {
return Stream.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
import java.util.stream.Stream;

import static io.yupiik.bundlebee.core.command.impl.lint.LintError.LintLevel.WARNING;
Expand Down Expand Up @@ -80,12 +79,12 @@ public boolean accept(final LintableDescriptor descriptor) { // capture role rel
}

@Override
public Stream<LintError> validate(final LintableDescriptor descriptor) {
public Stream<LintError> validateSync(final LintableDescriptor descriptor) {
return Stream.empty(); // validation done in after all to ensure we have roles
}

@Override // todo: handle aggregated roles by using kube client *if needed*
public Stream<ContextualLintError> afterAll() {
public Stream<ContextualLintError> afterAllSync() {
return Stream.concat(clusterRoleBindings.stream(), roleBindings.stream())
.flatMap(this::validateBinding);
}
Expand Down Expand Up @@ -167,8 +166,4 @@ private Stream<ContextualLintError> validateRole(final LintableDescriptor bindin
binding.getAlveolus(), binding.getName())))
.orElseGet(Stream::empty);
}

private Logger lazyLogger() {
return Logger.getLogger(getClass().getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@
*/
package io.yupiik.bundlebee.core.command.impl.lint.builtin;

import io.yupiik.bundlebee.core.command.impl.lint.LintingCheck;
import io.yupiik.bundlebee.core.command.impl.lint.SynchronousLintingCheck;
import lombok.RequiredArgsConstructor;

import java.util.Set;
import java.util.logging.Logger;

import static lombok.AccessLevel.PROTECTED;

@RequiredArgsConstructor(access = PROTECTED)
public abstract class CheckByKind implements LintingCheck {
public abstract class CheckByKind implements SynchronousLintingCheck {
private final Set<String> supportedKinds;

@Override
Expand All @@ -34,4 +35,8 @@ public boolean accept(final LintableDescriptor descriptor) {
return false;
}
}

protected Logger lazyLogger() {
return Logger.getLogger(getClass().getName());
}
}

0 comments on commit b31f9f1

Please sign in to comment.