Skip to content

Commit

Permalink
custom nginx ingress annotations on web svcs (#3765)
Browse files Browse the repository at this point in the history
  • Loading branch information
ianedwards authored Oct 9, 2023
1 parent a363a96 commit a0f69ae
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 51 deletions.
26 changes: 13 additions & 13 deletions dashboard/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"@loadable/component": "^5.15.2",
"@material-ui/core": "^4.11.3",
"@material-ui/lab": "^4.0.0-alpha.61",
"@porter-dev/api-contracts": "^0.2.8",
"@porter-dev/api-contracts": "^0.2.11",
"@react-spring/web": "^9.6.1",
"@sentry/react": "^6.13.2",
"@sentry/tracing": "^6.13.2",
Expand Down
34 changes: 34 additions & 0 deletions dashboard/src/lib/porter-apps/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
deserializeHealthCheck,
domainsValidator,
healthcheckValidator,
ingressAnnotationsValidator,
serializeAutoscaling,
SerializedAutoscaling,
SerializedHealthcheck,
Expand All @@ -18,6 +19,7 @@ import {
serviceNumberValidator,
serviceStringValidator,
} from "./values";
import _ from "lodash";

export type DetectedServices = {
services: ClientService[];
Expand All @@ -37,6 +39,7 @@ const webConfigValidator = z.object({
domains: domainsValidator,
healthCheck: healthcheckValidator.optional(),
private: serviceBooleanValidator.optional(),
ingressAnnotations: ingressAnnotationsValidator.default([]),
});
export type ClientWebConfig = z.infer<typeof webConfigValidator>;

Expand Down Expand Up @@ -107,6 +110,7 @@ export type SerializedService = {
autoscaling?: SerializedAutoscaling;
healthCheck?: SerializedHealthcheck;
private?: boolean;
ingressAnnotations: Record<string, string>;
}
| {
type: "worker";
Expand Down Expand Up @@ -174,6 +178,7 @@ export function defaultSerialized({
healthCheck: defaultHealthCheck,
domains: [],
private: false,
ingressAnnotations: {},
},
}))
.with("worker", () => ({
Expand Down Expand Up @@ -225,6 +230,12 @@ export function serializeService(service: ClientService): SerializedService {
domains: config.domains.map((domain) => ({
name: domain.name.value,
})),
ingressAnnotations: Object.fromEntries(
config.ingressAnnotations.map((annotation) => [
annotation.key,
annotation.value,
])
),
private: config.private?.value,
},
})
Expand Down Expand Up @@ -325,6 +336,28 @@ export function deserializeService({
])
).map((domain) => ({ name: domain }));

const uniqueAnnotations = _.uniqBy(
[
...Object.entries(overrideWebConfig?.ingressAnnotations ?? {}).map(
(annotation) => {
return {
key: annotation[0],
value: annotation[1],
readOnly: true,
};
}
),
...Object.entries(config.ingressAnnotations).map((annotation) => {
return {
key: annotation[0],
value: annotation[1],
readOnly: false,
};
}),
],
"key"
);

return {
...baseService,
config: {
Expand All @@ -348,6 +381,7 @@ export function deserializeService({
)?.name
),
})),
ingressAnnotations: uniqueAnnotations,
private:
typeof config.private === "boolean" ||
typeof overrideWebConfig?.private === "boolean"
Expand Down
90 changes: 56 additions & 34 deletions dashboard/src/lib/porter-apps/values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,37 +115,45 @@ export function deserializeAutoscaling({
}: {
autoscaling?: SerializedAutoscaling;
override?: SerializedAutoscaling;
setDefaults: boolean;
setDefaults: boolean;
}): ClientAutoscaling | undefined {
return (
autoscaling ? {
enabled: ServiceField.boolean(autoscaling.enabled, override?.enabled),
minInstances: autoscaling.minInstances
? ServiceField.number(autoscaling.minInstances, override?.minInstances)
: ServiceField.number(1, undefined),
maxInstances: autoscaling.maxInstances
? ServiceField.number(autoscaling.maxInstances, override?.maxInstances)
: ServiceField.number(10, undefined),
cpuThresholdPercent: autoscaling.cpuThresholdPercent
? ServiceField.number(
autoscaling.cpuThresholdPercent,
override?.cpuThresholdPercent
)
: ServiceField.number(50, undefined),
memoryThresholdPercent: autoscaling.memoryThresholdPercent
? ServiceField.number(
autoscaling.memoryThresholdPercent,
override?.memoryThresholdPercent
)
: ServiceField.number(50, undefined),
} : (setDefaults ? {
return autoscaling
? {
enabled: ServiceField.boolean(autoscaling.enabled, override?.enabled),
minInstances: autoscaling.minInstances
? ServiceField.number(
autoscaling.minInstances,
override?.minInstances
)
: ServiceField.number(1, undefined),
maxInstances: autoscaling.maxInstances
? ServiceField.number(
autoscaling.maxInstances,
override?.maxInstances
)
: ServiceField.number(10, undefined),
cpuThresholdPercent: autoscaling.cpuThresholdPercent
? ServiceField.number(
autoscaling.cpuThresholdPercent,
override?.cpuThresholdPercent
)
: ServiceField.number(50, undefined),
memoryThresholdPercent: autoscaling.memoryThresholdPercent
? ServiceField.number(
autoscaling.memoryThresholdPercent,
override?.memoryThresholdPercent
)
: ServiceField.number(50, undefined),
}
: setDefaults
? {
enabled: ServiceField.boolean(false, undefined),
minInstances: ServiceField.number(1, undefined),
maxInstances: ServiceField.number(10, undefined),
cpuThresholdPercent: ServiceField.number(50, undefined),
memoryThresholdPercent: ServiceField.number(50, undefined),
} : undefined )
);
}
: undefined;
}

// Health Check
Expand Down Expand Up @@ -180,17 +188,19 @@ export function deserializeHealthCheck({
override?: SerializedHealthcheck;
setDefaults: boolean;
}): ClientHealthCheck | undefined {
return (
health ? {
enabled: ServiceField.boolean(health.enabled, override?.enabled),
httpPath: health.httpPath
? ServiceField.string(health.httpPath, override?.httpPath)
: ServiceField.string("", undefined),
} : (setDefaults ? {
return health
? {
enabled: ServiceField.boolean(health.enabled, override?.enabled),
httpPath: health.httpPath
? ServiceField.string(health.httpPath, override?.httpPath)
: ServiceField.string("", undefined),
}
: setDefaults
? {
enabled: ServiceField.boolean(false, undefined),
httpPath: ServiceField.string("", undefined),
} : undefined)
);
}
: undefined;
}

// Domains
Expand All @@ -200,3 +210,15 @@ export const domainsValidator = z.array(
})
);
export type ClientDomains = z.infer<typeof domainsValidator>;

// Ingress Annotations
export const ingressAnnotationsValidator = z.array(
z.object({
key: z.string(),
value: z.string(),
readOnly: z.boolean(),
})
);
export type ClientIngressAnnotations = z.infer<
typeof ingressAnnotationsValidator
>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import Button from "components/porter/Button";
import { ControlledInput } from "components/porter/ControlledInput";
import Spacer from "components/porter/Spacer";
import { PorterAppFormData } from "lib/porter-apps";
import React from "react";
import { useFieldArray, useFormContext } from "react-hook-form";
import styled from "styled-components";

type Props = {
index: number;
};

const IngressCustomAnnotations: React.FC<Props> = ({ index }) => {
const { control, register } = useFormContext<PorterAppFormData>();
const { remove, append, fields } = useFieldArray({
control,
name: `app.services.${index}.config.ingressAnnotations`,
});

return (
<div>
{fields.length !== 0
? fields.map((annotation, i) => {
return (
<>
<AnnotationContainer key={i}>
<ControlledInput
type="text"
placeholder="kubernetes.io/ingress.class"
disabled={annotation.readOnly}
width="275px"
disabledTooltip={
"You may only edit this field in your porter.yaml."
}
{...register(
`app.services.${index}.config.ingressAnnotations.${i}.key`
)}
/>
<ControlledInput
type="text"
placeholder="nginx"
disabled={annotation.readOnly}
width="275px"
disabledTooltip={
"You may only edit this field in your porter.yaml."
}
{...register(
`app.services.${index}.config.ingressAnnotations.${i}.value`
)}
/>
<DeleteButton
onClick={() => {
remove(i);
}}
>
<i className="material-icons">cancel</i>
</DeleteButton>
</AnnotationContainer>
<Spacer y={0.25} />
</>
);
})
: null}
<Button
onClick={() => {
append({
key: "",
value: "",
readOnly: false,
});
}}
>
+ Add Annotation
</Button>
</div>
);
};

export default IngressCustomAnnotations;

const AnnotationContainer = styled.div`
display: flex;
align-items: center;
gap: 5px;
`;

const DeleteButton = styled.div`
width: 15px;
height: 15px;
display: flex;
align-items: center;
margin-left: 8px;
margin-top: -3px;
justify-content: center;
> i {
font-size: 17px;
color: #ffffff44;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
:hover {
color: #ffffff88;
}
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { PorterAppFormData } from "lib/porter-apps";
import Checkbox from "components/porter/Checkbox";
import Text from "components/porter/Text";
import CustomDomains from "./CustomDomains";
import IngressCustomAnnotations from "./IngressCustomAnnotations";

type NetworkingProps = {
index: number;
Expand Down Expand Up @@ -95,6 +96,7 @@ const Networking: React.FC<NetworkingProps> = ({ index, service }) => {
<Spacer y={0.5} />
<CustomDomains index={index} />
<Spacer y={0.5} />
<IngressCustomAnnotations index={index} />
</>
)}
</>
Expand Down
Loading

0 comments on commit a0f69ae

Please sign in to comment.