Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

k8s first pass #8260

Merged
merged 6 commits into from
Mar 24, 2025
Merged

k8s first pass #8260

merged 6 commits into from
Mar 24, 2025

Conversation

prom3theu5
Copy link
Contributor

no ingress support directly yet

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

no ingress support directly yet
@github-actions github-actions bot added the area-integrations Issues pertaining to Aspire Integrations packages label Mar 23, 2025
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Mar 23, 2025
/// <summary>
/// Gets or sets the name of the Helm chart to be generated.
/// </summary>
public string HelmChartName { get; set; } = "aspire";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be the name of the application instead of aspire?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah! I just defaulted it to aspire, I thought the cli would probably pass it through

@prom3theu5
Copy link
Contributor Author

Gonna have to rework the unit tests - they work in xunit in my ide (Rider), but fail through dotnet test ran from cli. Strange...
I'll remove the embedded resources tomorrow, and extract them into string constants like the compose test does.
Will also remove the reusable method and just test each file directly, as its too hard to see which file actually fails there

@davidfowl
Copy link
Member

This is looking really promising @prom3theu5 !

@prom3theu5
Copy link
Contributor Author

prom3theu5 commented Mar 23, 2025

@davidfowl you ok with the dual context? (One for publisher, one for the state of a processed resource) There were so many cross cutting concerns in the single context felt better to split them into two and isolate responsibilities

@davidfowl
Copy link
Member

Yep! it looks much cleaner.

Still this strange case with tests failing from dotnet run, but not in the ide.
Comment on lines 106 to 108
"http" => 8080,
"https" => 8443,
_ => 9000,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What port is this supposed to be? The internal port? I think you can only choose a default for project resources. Otherwise, this has to throw right? The container needs to be configured to listen on this port.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The target port is returned, if something like sqlServer for example was configured, it should have its target port set as 1433 shouldn't it?
This was just the fall back for something that doesn't have a target port set, which probably means endpoint annotations on the resource are wrong
We can choose to only apply this for project resources, or else "explode" with an exception and end publishing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something like

 foreach (var endpoint in endpoints)
        {
            var internalPort = endpoint.TargetPort;

            if (resource is ProjectResource _)
            {
                internalPort ??= endpoint.UriScheme switch
                {
                    "http" => 8080,
                    "https" => 8443,
                    _ => throw new NotSupportedException($"Unsupported scheme: {endpoint.UriScheme}"),
                };
            }

            if (internalPort is null)
            {
                throw new InvalidOperationException("TargetPort for endpoint must be set");
            }

            EndpointMappings[endpoint.Name] = new(endpoint.UriScheme, resource.Name, internalPort.Value, internalPort.Value, endpoint.Name);
        }
        ```

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, either this or it becomes a parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's also "TargetPortEnvironmentVariable" to take into account, It's internal atm - but we'd probably want to make it public here so we can see if a value has been set too?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That shouldn't be required. When it is set the correct data is expressed in the model as an environment variable:

builder.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
{
context.EnvironmentVariables[env] = endpointReference.Property(EndpointProperty.TargetPort);
}));

All we need to do is model endpoints appropriately and it should all hang together.

The answer here might be to avoid doing anything clever and using a parameter if the value is not set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just done something along those lines in this: ce22f8a

However - if you see the message in the commit, I'm having a little issue with separation of env vars.
As you linked above, if we set WithEndpoint(env: "Port"), this forces an EV callback to populate an EnvVar called Port.
But i don't see a way without having access to

CleanShot 2025-03-24 at 01 17 03

to know that this env var value that has been specified in the AppHost is meant to be referenced by the endpoint. When its populated back into the ev - this "link" is lost if you are just interrogating the annotations

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might just be tired and missing something obvious lol

Copy link
Member

@davidfowl davidfowl Mar 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, you shouldn't need to do anything special to make it work. If you do:

builder.AddNpmApp("fe", "../app").WithHttpEndpoint(env: "PORT");

The "PORT" environment variable slot will have a reference expression to the target endpoint value.

I realize now that in the docker compose case we use the port allocator:

var internalPort = endpoint.TargetPort ?? composePublishingContext.PortAllocator.AllocatePort();

We could do the same here, or we could decide to both use the port allocator and to parameterize to set a default value.

int? targetPort = (resource, endpoint.UriScheme, endpoint.TargetPort, endpoint.Port) switch
{
// The port was specified so use it
(_, _, int target, _) => target,
// Container resources get their default listening port from the exposed port.
(ContainerResource, _, null, int port) => port,
// Check whether the project view this endpoint as Default (for its scheme).
// If so, we don't specify the target port, as it will get one from the deployment tool.
(ProjectResource project, string uriScheme, null, _) when IsHttpScheme(uriScheme) && !httpSchemesEncountered.Contains(uriScheme) => null,
// Allocate a dynamic port
_ => PortAllocator.AllocatePort()
};

We might need to revisit this logic in multiple places but lets treat this like a follow up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't realise the EnvVar had a reference expression, missed that yesterday ^^
Definitely will be a follow up on this some time this week

I opted to not use port allocator here purely because ClusterIP this wouldn't be an issue - its only if we had a specific NodePort binding to the same port we'd run into an issue, but two pods in two separate deployments can use the same port no problem

And fix niggles to do with formatting (editorconfig sets them to no space ^^)
…o add project defaults endpoint

- Still not sure how best to handle WithEndpoint where the assigned TargetPort comes from an Env var - how to ensure we dont get duplicate env vars when there is no publically accessible reference to the fact that the env var is actually an endpoint annotation value?
Getting at the actual value seems to require access to TargetPortEnvironmentVariable which is populated by

```csharp
        if (env is not null && builder.Resource is IResourceWithEndpoints resourceWithEndpoints and IResourceWithEnvironment)
        {
            annotation.TargetPortEnvironmentVariable = env;

            var endpointReference = new EndpointReference(resourceWithEndpoints, annotation);

            builder.WithAnnotation(new EnvironmentCallbackAnnotation(context =>
            {
                context.EnvironmentVariables[env] = endpointReference.Property(EndpointProperty.TargetPort);
            }));
        }

        return builder.WithAnnotation(annotation);
```

But thats just expanded back into a regular env var?
There's no concept of that being tired to the endpoint annotation at that point??
/// an integer or a string. It supports implicit and explicit conversions,
/// equality comparisons, and YAML serialization/deserialization handling.
/// </remarks>
public sealed record Int32OrStringV1(int? Number = null, string? Text = null) : IEquatable<int>, IEquatable<string>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DU! 😄 As a follow up I would just use object if this was internal storage.

Copy link
Member

@davidfowl davidfowl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of follow ups but great start!

@davidfowl davidfowl merged commit db8ebf2 into dotnet:main Mar 24, 2025
163 checks passed
@prom3theu5 prom3theu5 deleted the k8s branch March 24, 2025 22:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-integrations Issues pertaining to Aspire Integrations packages community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants