Skip to content

Commit

Permalink
Introduction of resource lifecycle
Browse files Browse the repository at this point in the history
  • Loading branch information
JonathanGiles committed Jul 3, 2024
1 parent e340937 commit 6a1d115
Show file tree
Hide file tree
Showing 12 changed files with 91 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ public String getDescription() {
return "Provides resources for Azure Storage";
}

@Override
public List<Class<? extends Resource<?>>> getAvailableResources() {
return List.of(AzureStorageResource.class, AzureStorageBlobsResource.class);
}

public AzureStorageResource addAzureStorage(String name) {
return DistributedApplication.getInstance().addResource(new AzureStorageResource(name));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,6 @@ resource roleAssignment_r0wA6OpKE 'Microsoft.Authorization/roleAssignments@2022-
}
}

output blobEndpoint string = storageAccount_1XR3Um8QY.properties.primaryEndpoints.blob
output blobEndpoint string = storageAccount_1XR3Um8QY.properties.primaryEndpoints.blob
output queueEndpoint string = storageAccount_1XR3Um8QY.properties.primaryEndpoints.queue
output tableEndpoint string = storageAccount_1XR3Um8QY.properties.primaryEndpoints.table
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ public String getDescription() {
return "Provides support for working with Spring applications";
}

@Override
public List<Class<? extends Resource<?>>> getAvailableResources() {
return List.of(SpringProject.class);
}

/**
* Adds a new Spring project to the app host.
* @param name The name of the spring project.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ public SpringProject(String name) {
withBinding(new Binding(Binding.Scheme.HTTPS, Binding.Protocol.TCP, Binding.Transport.HTTP).withTargetPort(8080));
}

@Override
public void onResourcePrecommit() {
super.onResourcePrecommit();
introspect();
}

@Override
public void introspect() {
// we add the available strategies to the aspire manifest and leave it to azd to try its best...
Expand Down
8 changes: 2 additions & 6 deletions aspire4j/aspire4j-maven-tools/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,12 @@
<artifactId>aspire4j-maven-tools</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven-plugin-tools.version>3.13.1</maven-plugin-tools.version>
</properties>

<dependencies>
<!-- Apache velocity for templating support -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,19 @@ class AspireManifest {
<T extends Resource<?>> T addResource(T resource) {
Objects.requireNonNull(resource);

// We eagerly do the introspection, so that the resource is ready to be used
// before the user does any further calls
if (resource instanceof IntrospectiveResource introspectiveResource) {
introspectiveResource.introspect();
if (resources.containsKey(resource.getName())) {
throw new IllegalArgumentException("Resource with name " + resource.getName() + " already exists in manifest");
}

resources.put(resource.getName(), resource);
resource.onResourceAdded();
return resource;
}

<T extends Resource<?>> T removeResource(T resource) {
Objects.requireNonNull(resource);
resources.remove(resource.getName());
resource.onResourceRemoved();
return resource;
}

Expand Down Expand Up @@ -69,5 +75,6 @@ public void substituteResource(Resource<?> oldResource, Resource<?>... newResour
}
}
resources = newResourcesMap;
oldResource.onResourceRemoved();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,7 @@ public void printExtensions() {
public void printExtensions(PrintStream out) {
out.println("Available Aspire4J Extensions:");
extensions.forEach(e -> {
out.println(" " + e.getName() + " (" + e.getClass().getSimpleName() + ".class): " + e.getDescription());
e.getAvailableResources().forEach(r -> out.println(" - " + r.getSimpleName()));
out.println(" - " + e.getName() + " (" + e.getClass().getSimpleName() + ".class): " + e.getDescription());
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,4 @@ public interface Extension {
* @return The description of the extension.
*/
String getDescription();

/**
* Returns a list of resources that are available in this extension. These resources can then be used by calling the
* {@link DistributedApplication#withExtension(Class)} method.
*
* @return A list of resources that are available in this extension.
*/
List<Class<? extends Resource<?>>> getAvailableResources();
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.logging.Logger;

import com.microsoft.aspire.implementation.json.RelativePathSerializer;
import com.microsoft.aspire.resources.traits.ResourceWithLifecycle;
import com.microsoft.aspire.resources.traits.ResourceWithTemplate;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
Expand Down Expand Up @@ -52,6 +53,9 @@ private void writeManifest(DistributedApplication app) {
System.exit(-1);
}

// run the precommit lifecycle hook on all resources
app.manifest.getResources().values().forEach(ResourceWithLifecycle::onResourcePrecommit);

LOGGER.info("Validating models...");
// Firstly, disable the info logging messages that are printed by Hibernate Validator
Logger.getLogger("org.hibernate.validator.internal.util.Version").setLevel(Level.OFF);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

/**
*
* @param <T>
*/
@JsonPropertyOrder({"type", "params"})
public abstract class Resource<T extends Resource<T>> implements SelfAware<T> {
public abstract class Resource<T extends Resource<T>> implements ResourceWithLifecycle, SelfAware<T> {

@Valid
@NotNull(message = "Resource Type cannot be null")
Expand All @@ -35,7 +39,7 @@ public final String getName() {
return name;
}

public <T extends Resource<?>> T copyInto(T newResource) {
public void copyInto(Resource<?> newResource) {
// TODO this is incomplete
// look at the traits of this resource, and copy them into the new resource
if (this instanceof ResourceWithArguments<?> oldResource && newResource instanceof ResourceWithArguments<?> _newResource) {
Expand All @@ -56,6 +60,5 @@ public <T extends Resource<?>> T copyInto(T newResource) {
if (this instanceof ResourceWithEnvironment<?> oldResource && newResource instanceof ResourceWithEnvironment<?> _newResource) {
oldResource.getEnvironment().forEach(_newResource::withEnvironment);
}
return newResource;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
package com.microsoft.aspire.resources.traits;

/**
* Interface for resources that can introspect themselves. This is useful for resources that need to look at the
* environment they are running in to determine their configuration, for example to look at referenced projects, or
* to determine properties from remote resources.
* <p>
* Note that the Aspire App Host never directly calls the introspect() method. Instead, calling the introspect() method
* is the responsibility of the resource creator. This is because the resource creator is the one who knows when the
* resource is ready to be introspected. Introspective resources should implement the appropriate lifecycle methods
* in {@link ResourceWithLifecycle} to ensure that they are introspected at the right time.
*
* @see ResourceWithLifecycle
*/
public interface IntrospectiveResource {
void introspect();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.microsoft.aspire.resources.traits;

/**
* All resources have a lifecycle, beginning with creation and ending with the resource being written out to the
* aspire manifest. Within this lifecycle, there are times when the resource should ideally be configured, introspected,
* and then written out. The stages of a resources lifecycle are:
*
* <ol>
* <li>Resource is created - Resource constructor is called.</li>
* <li>Resource is added to the distributed application - onResourceAdded() is called.</li>
* <li>Prior to writing the resource to the aspire manifest, onResourcePrecommit() is called on all resources in the
* order they were added to the distributed application.</li>
* </ol>
*
* <p>The most important point to understand about resource lifecycles is that resources work best when they are
* configured as early as possible, as this allows for other resources to glean more information from them. A resource
* that keeps its cards close to its chest only hurts the resources around it!</p>
*
* @see com.microsoft.aspire.resources.Resource
* @see IntrospectiveResource
*/
public interface ResourceWithLifecycle {

/**
* This method is called immediately after the resource is added to the distributed application. It is the earliest
* time that the resource can be configured.
*/
default void onResourceAdded() {

}

/**
* This method is called immediately after the resource is removed from the distributed application.
*/
default void onResourceRemoved() {

}

/**
* Prior to writing the resource to the aspire manifest, onResourcePrecommit() is called on all resources in the
* order they were added to the distributed application.
*/
default void onResourcePrecommit() {

}
}

0 comments on commit 6a1d115

Please sign in to comment.