Skip to content

Conversation

@skkra0
Copy link
Collaborator

@skkra0 skkra0 commented Jan 2, 2026

Changes

/chart

  • Created a chart for installing Spilo, based on this config from the spilo repository
    • The annotations field on the Chart.yaml is used for anvilops-specific metadata. For instance, under the key anvilops-values is some JSON that could be used for generating form fields for the chart on the frontend.
  • Added some environment variables.

/openapi

  • Added an endpoint for listing available AnvilOps charts. This returns some information on the Spilo chart.
  • Added a third source for configs, helm.
  • Request schemas for createApp and createAppGroup use the DeploymentConfig object.
    • This means the cpu and memory are formatted on the frontend.
  • Split DeploymentConfig into two schemas, WorkloadConfigOptions(appType: "workload", source: git or image) and HelmConfigOptions(appType: "helm", source: helm).
    • WorkloadConfigOptions contains fields that are common between git and image configs e.g. port, subdomain, collectLogs. It also has either git config fields(source: "git", dockerfilePath, builder, repositoryId, etc) or image config fields(just imageTag).

/backend

/prisma

  • Wrote a migration that adds new enums and creates a HelmConfig table, renames the DeploymentConfig table to WorkloadConfig and renames related items accordingly, then recreates DeploymentConfig. It contains an appType, which indicates whether to point to a WorkloadConfig or a HelmConfig. The configId on App and Deployment still point to a DeploymentConfig.

/db

  • Added WorkloadConfig(create), GitConfig(create), and HelmConfig(create) types and updated the repos
    • I tried to largely hide how the different types of configs are actually represented in the database. The repos add the appropriate appType and source fields before returning config objects, and those fields are also used to decide how to insert a new config.
    • To avoid confusion, ids are omitted from the config objects. The id of a config is never used, since an update always results in a new Deployment and DeploymentConfig.

/lib

  • Installed helm in the backend and wrote a module for running helm to retrieve and upgrade charts.
  • Added some environment variables.

/service

  • Updated handlers for helm and workload configs
  • Wrote some -Service classes to simplify handler implementations
    • Moved config generation and validating logic to a class DeploymentConfigService. It is also responsible for picking up the commitHash and commitMessage of a git config.
    • Moved some logic for validating apps to a class AppService, which calls methods of DeploymentConfigService
    • Moved deployment and deployment canceling logic under a class DeploymentService
    • Many of the dependencies of the classes are passed into the constructor. This can be useful for testing, because instead of mocking modules, the test can just pass in fake versions of the objects used.
  • Updated handlers accordingly
  • Some endpoints error when the specified app has a Helm config, such as getAppLogs, -File

/frontend

  • Organized components under /components/config and /components/diff
  • Wrote types for form states and moved config / form state generation and formatting to /lib/form.ts
  • Wrote placeholders for helm config-related components

To-do

  • Test redeployments and project selection on anvilops-staging

skkra0 added 18 commits October 26, 2025 10:47
- Add TARGETARCH buildarg to migration job Dockerfile for local testing
- Fix workload migration to use correct appType parameter and sequence starting value
- Omit id from Workload and Helm configs to avoid confusion
- List all missing env vars
- Fix incorrect image registry URL
- Ignore not found error when deleting a namespace
- Fix various API handlers
- Fix async error handling during app validation
- Do not await deployment process after validation and creating a Deployment
- Update git version
- Write types for the form states of configs of different sources
- Add helper functions for form state and config generation
- Move source or app type-specific code into separate components
- Separate DiffInput into DiffInput and DiffSelect for speed
@skkra0 skkra0 requested a review from FluxCapacitor2 January 2, 2026 23:28
skkra0 and others added 8 commits January 5, 2026 19:07
Tested on an x86_64 system (TARGETARCH=amd64). Works with `docker build` and from Tilt. Should work on 64-bit ARM systems; won't work on 32-bit ARM since Tini's binary naming convention doesn't line up.

Source: https://github.com/BretFisher/multi-platform-docker-build
Not sure if this changes semantics but it matches the convention of the other functions in the file. Maybe try/catch behavior is different?
Copy link
Collaborator

@FluxCapacitor2 FluxCapacitor2 left a comment

Choose a reason for hiding this comment

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

Phew! This PR is gigantic. I've looked over everything but I haven't tested it myself yet.

I made some small changes that shouldn't change any behavior. Here is that diff if you'd like to review: https://github.com/PurdueRCAC/AnvilOps/compare/9eebc7979352eaa53f3d0ee9ef962cd7f0daa93f..ec207db68386c72474b82623e8cb45446bcf2883.

General notes:

  1. I really like the idea of data-driven Helm templates that allow us to create forms with JSON and validate with JSON schemas. Great idea.
  2. The refactoring generally looks good and I like the way you're doing dependency injection in src/service/helper/*.
  3. Thanks for splitting off the frontend form stuff into separate files based on configuration area. That makes it a lot nicer to work with.
  4. Administrators should have the ability to disable Helm deployments via environment variable. Maybe even Git and Image deployments too for completeness.
  5. Of course, documentation needs to be updated to explain how administrators configure a Helm OCI repo & how users use the charts.
  6. On the subject of documentation, I think the codebase is separated enough that we should document the directory structure somewhere.

Everything else is in individual review comments.

async create({
appId,
appType,
config,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why can't you use config.appType here instead of relying on a separate parameter? If you use config.appType, I think you can take advantage of the discriminated union to get rid of the type casts in this method.

enum AppType {
workload
helm
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should make the capitalization consistent on all of our enums. Can be addressed in another PR though.

id Int @id @default(autoincrement())
deployment Deployment?
appType AppType
workloadConfig WorkloadConfig? @relation(fields: [workloadConfigId], references: [id], onDelete: Cascade)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the @relation should be specified on the WorkloadConfig and HelmConfig models instead of on the DeploymentConfig model. Currently, if the WorkloadConfig is deleted, them the DeploymentConfig is deleted, not the other way around. Is this intended?

I wish we could make the deploymentConfig field in WorkloadConfig and HelmConfig required.

}
}

return runHelm(args);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I worry about running Helm in the same container as the AnvilOps web server. Ideally, we'd run helm in a K8s Job so that we can lock it down better, similar to what's happening in builder.ts and import.ts. I think Rancher does this too when installing Helm charts, but I haven't checked.

If we want to keep it like this for the sake of simplicity, we have to make sure that Go templates can't be used to run arbitrary code or access sensitive information (including via Sprig, the library that Helm uses for extra template utilities) and can't lead to resource exhaustion via large or circular templates.


export async function getRepositoriesByProject(projectName: string) {
return fetch(
`${env.REGISTRY_PROTOCOL}://${env.REGISTRY_API_URL}/projects/${projectName}/repositories`,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Based on the comments in env.ts,

${env.REGISTRY_PROTOCOL}://${env.REGISTRY_API_URL}

would evaluate to something like

https://https://myregistry.host.name

Either the comment in env.ts:157 should be updated to note that the protocol shouldn't be included (would probably break things elsewhere?), or REGISTRY_PROTOCOL should be removed here.

<TabsTrigger value="overview">
<span>Overview</span>
</TabsTrigger>
{!!app.activeDeployment && (
Copy link
Collaborator

Choose a reason for hiding this comment

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

The Status tab should also require a Workload deployment type. It's currently only designed to grab info from the StatefulSet that AnvilOps creates, although we could rework it in the future to work in a more generic way for Helm deployments.

if (!isWorkloadConfig(app.config)) {
return (
<div className="text-center py-8">
<p>Configuration editing is not available for Helm-based apps.</p>
Copy link
Collaborator

Choose a reason for hiding this comment

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

I assume this is temporary since users need a place to edit the Helm chart values, but if not, the whole tab should be hidden just like what I do for the Files and Logs tabs when they don't have any content.

if (!isWorkloadConfig(app.config)) {
return (
<div className="text-center py-8">
<p>File browser is not available for Helm-based apps.</p>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should be unnecessary since the tab is hidden

)}
<p className="flex items-center gap-2">
<Network size={16} />
Internal address
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the internal address should be hidden too. We don't know what the Service name is, if any.

*
* @throws DeploymentError
*/
private async deployGit({
Copy link
Collaborator

Choose a reason for hiding this comment

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

This method has quite a few responsibilities. For example, there are instances where we call deployGit and it actually doesn't deploy and instead creates a pending check run for a future deployment. Maybe at least the checkRun.pending === true case should be split off into a separate method?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants