Skip to content

Commit

Permalink
Add Artifacts Manifest Extension (#1649)
Browse files Browse the repository at this point in the history
Explicitly list main container image in artifacts extension
  • Loading branch information
Timthetic authored Jun 13, 2024
1 parent 062e833 commit cef592c
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 3 deletions.
5 changes: 5 additions & 0 deletions changelog/@unreleased/pr-1649.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type: improvement
improvement:
description: add artifacts manifest extension
links:
- https://github.com/palantir/sls-packaging/pull/1649
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.palantir.gradle.dist.artifacts.ArtifactLocator;
import com.palantir.logsafe.SafeArg;
import com.palantir.logsafe.exceptions.SafeRuntimeException;
import groovy.lang.Closure;
Expand All @@ -30,6 +31,8 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
import org.gradle.api.Action;
import org.gradle.api.DomainObjectSet;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.RegularFileProperty;
Expand Down Expand Up @@ -59,6 +62,7 @@ public class BaseDistributionExtension {
private final ListProperty<ProductDependency> productDependencies;
private final SetProperty<ProductId> optionalProductDependencies;
private final SetProperty<ProductId> ignoredProductDependencies;
private final DomainObjectSet<ArtifactLocator> artifacts;
private final ProviderFactory providerFactory;
private final MapProperty<String, Object> manifestExtensions;
private final RegularFileProperty configurationYml;
Expand All @@ -75,6 +79,8 @@ public BaseDistributionExtension(Project project) {
productDependencies = project.getObjects().listProperty(ProductDependency.class);
optionalProductDependencies = project.getObjects().setProperty(ProductId.class);
ignoredProductDependencies = project.getObjects().setProperty(ProductId.class);
artifacts = project.getObjects().domainObjectSet(ArtifactLocator.class);
artifacts.whenObjectAdded(ArtifactLocator::isValid);

serviceGroup.set(project.provider(() -> project.getGroup().toString()));
serviceName.set(project.provider(project::getName));
Expand Down Expand Up @@ -131,6 +137,23 @@ public final void setProductType(ProductType productType) {
this.productType.set(productType);
}

public final DomainObjectSet<ArtifactLocator> getArtifacts() {
return artifacts;
}

/** Lazily configures and adds a {@link ArtifactLocator}. */
public final void artifact(@DelegatesTo(ArtifactLocator.class) Closure<ArtifactLocator> closure) {
ArtifactLocator artifactLocator = project.getObjects().newInstance(ArtifactLocator.class);
project.configure(artifactLocator, closure);
artifacts.add(artifactLocator);
}

public final void artifact(Action<ArtifactLocator> action) {
ArtifactLocator artifactLocator = project.getObjects().newInstance(ArtifactLocator.class);
action.execute(artifactLocator);
artifacts.add(artifactLocator);
}

/**
* The product dependencies of this distribution.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* (c) Copyright 2024 Palantir Technologies Inc. All rights reserved.
*
* 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 com.palantir.gradle.dist.artifacts;

import com.google.common.base.Preconditions;
import com.palantir.logsafe.exceptions.SafeIllegalArgumentException;
import java.net.URI;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;

public interface ArtifactLocator {
@Input
Property<String> getType();

@Input
Property<String> getUri();

default void isValid() {
Preconditions.checkNotNull(getType().get(), "type must be specified");
Preconditions.checkNotNull(getUri().get(), "uri must be specified");
uriIsValid(getUri().get());
}

private static void uriIsValid(String uri) {
try {
// Throws IllegalArgumentException if URI does not conform to RFC 2396
URI.create(uri);
} catch (IllegalArgumentException e) {
throw new SafeIllegalArgumentException("uri is not valid", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* (c) Copyright 2024 Palantir Technologies Inc. All rights reserved.
*
* 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 com.palantir.gradle.dist.artifacts;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonIgnoreProperties(ignoreUnknown = true)
public final class JsonArtifactLocator {

@JsonProperty("type")
@SuppressWarnings("unused")
private String type;

@JsonProperty("uri")
@SuppressWarnings("unused")
private String uri;

public JsonArtifactLocator(String type, String uri) {
this.type = type;
this.uri = uri;
}

public static JsonArtifactLocator from(ArtifactLocator artifactLocator) {
return new JsonArtifactLocator(
artifactLocator.getType().get(), artifactLocator.getUri().get());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import com.palantir.gradle.dist.SchemaMigration;
import com.palantir.gradle.dist.SchemaVersionLockFile;
import com.palantir.gradle.dist.SlsManifest;
import com.palantir.gradle.dist.artifacts.ArtifactLocator;
import com.palantir.gradle.dist.artifacts.JsonArtifactLocator;
import com.palantir.gradle.dist.pdeps.ProductDependencies;
import com.palantir.gradle.dist.pdeps.ProductDependencyManifest;
import com.palantir.gradle.dist.pdeps.ResolveProductDependenciesTask;
Expand Down Expand Up @@ -65,6 +67,8 @@

public abstract class CreateManifestTask extends DefaultTask {

public static final String CREATE_MANIFEST_TASK_NAME = "createManifest";

@Input
public abstract SetProperty<ProductId> getInRepoProductIds();

Expand All @@ -80,6 +84,9 @@ public abstract class CreateManifestTask extends DefaultTask {
@Input
public abstract MapProperty<String, Object> getManifestExtensions();

@Input
public abstract SetProperty<ArtifactLocator> getArtifacts();

@InputFile
public abstract RegularFileProperty getProductDependenciesFile();

Expand Down Expand Up @@ -144,6 +151,8 @@ final void createManifest() throws IOException {
ensureSchemaLockfileIsUpToDate(schemaMigrations);
}

validateEmptyArtifactsExtension();

ObjectMappers.jsonMapper.writeValue(
getManifestFile().getAsFile().get(),
SlsManifest.builder()
Expand All @@ -154,9 +163,22 @@ final void createManifest() throws IOException {
.productVersion(getProjectVersion())
.putAllExtensions(getManifestExtensions().get())
.putExtensions("product-dependencies", productDependencies)
.putExtensions(
"artifacts",
getArtifacts().get().stream()
.map(JsonArtifactLocator::from)
.collect(Collectors.toList()))
.build());
}

private void validateEmptyArtifactsExtension() {
Preconditions.checkArgument(
!getManifestExtensions().get().containsKey("artifacts"),
"Specifying artifacts directly the using the manifest-extensions block in the 'distributions' "
+ "extension is not allowed. Please use the 'artifact' closure in the 'distributions' "
+ "extension to add artifacts instead.");
}

private List<SchemaMigration> getSchemaMigrations() {
Object raw = getManifestExtensions().get().get("schema-migrations");
if (raw == null) {
Expand Down Expand Up @@ -294,7 +316,7 @@ public static TaskProvider<CreateManifestTask> createManifestTask(Project projec
ProductDependencies.registerProductDependencyTasks(project, ext);

TaskProvider<CreateManifestTask> createManifest = project.getTasks()
.register("createManifest", CreateManifestTask.class, task -> {
.register(CREATE_MANIFEST_TASK_NAME, CreateManifestTask.class, task -> {
task.getServiceName().set(ext.getDistributionServiceName());
task.getServiceGroup().set(ext.getDistributionServiceGroup());
task.getProductType().set(ext.getProductType());
Expand All @@ -303,6 +325,7 @@ public static TaskProvider<CreateManifestTask> createManifestTask(Project projec
.set(resolveProductDependenciesTask.flatMap(
ResolveProductDependenciesTask::getManifestFile));
task.getManifestExtensions().set(ext.getManifestExtensions());
task.getArtifacts().addAll(ext.getArtifacts());
task.getInRepoProductIds()
.set(project.provider(() -> ProductDependencyIntrospectionPlugin.getInRepoProductIds(
project.getRootProject())
Expand Down Expand Up @@ -341,10 +364,10 @@ public boolean isSatisfiedBy(Task _task) {
// We want `./gradlew --write-locks` to magically fix up the product-dependencies.lock file
// We can't do this at configuration time because it would mess up gradle-consistent-versions.
StartParameter startParam = project.getGradle().getStartParameter();
if (startParam.isWriteDependencyLocks() && !startParam.getTaskNames().contains("createManifest")) {
if (startParam.isWriteDependencyLocks() && !startParam.getTaskNames().contains(CREATE_MANIFEST_TASK_NAME)) {
List<String> taskNames = ImmutableList.<String>builder()
.addAll(startParam.getTaskNames())
.add("createManifest")
.add(CREATE_MANIFEST_TASK_NAME)
.build();
startParam.setTaskNames(taskNames);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,30 @@ class CreateManifestTaskIntegrationSpec extends IntegrationSpec {
writeLocksTask << ['--write-locks', 'writeProductDependenciesLocks', 'wPDL']
}

def 'write artifacts to manifest'() {
buildFile << """
distribution {
artifact {
type = "oci"
uri = "registry.example.io/foo/bar:v1.3.0"
}
}
""".stripIndent()

when:
def buildResult = runTasksSuccessfully('createManifest')

then:
buildResult.wasExecuted('createManifest')
def manifest = ObjectMappers.jsonMapper.readValue(file('build/deployment/manifest.yml').text, Map)
manifest.get("extensions").get("artifacts") == [
[
"type": "oci",
"uri" : "registry.example.io/foo/bar:v1.3.0"
]
]
}

def "check depends on createManifest"() {
when:
def result = runTasks(':check')
Expand Down
42 changes: 42 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,48 @@ dependencies {
}
```

### Container Images

You can specify container images that your product requires using the `artifact` declaration on the `distribution`
extension. For example, if a container could be pulled from `registry.example.io/foo/bar:v1.3.0`, you could add the
following to the `distribution` extension. The entry should contain the URI for the artifact in the `uri` field and
should always contain `'oci'` (the only currently supported type) in the `type` field:

```gradle
distribution {
artifact {
type = 'oci'
uri = 'registry.example.io/foo/bar:v1.3.0'
}
}
```

The result will be embedded in the `deployment/manifest.yml` file. The file will look something like this:

```json
{
"manifest-version" : "1.0",
"product-type" : "service.v1",
"product-group" : "com.example.foo",
"product-name" : "bar",
"product-version" : "1.1.0",
"extensions" : {
"product-dependencies" : [ {
"product-group" : "com.example",
"product-name" : "dependency",
"minimum-version" : "1.0.0",
"recommended-version" : "1.0.0",
"maximum-version" : "1.x.x",
"optional" : false
} ],
"artifacts" : [ {
"type": "oci",
"uri": "registry.example.io/foo/bar:v1.1.0"
} ]
}
}
```

### Schema versions

sls-packaging also maintains a lockfile, `schema-versions.lock`, which should be checked in to Git.
Expand Down

0 comments on commit cef592c

Please sign in to comment.