Skip to content

Commit

Permalink
feat: manual proxy configuration, closes #58
Browse files Browse the repository at this point in the history
Also includes some tests refactoring to move towards better coverage and simpler unit tests #63.
  • Loading branch information
YuukanOO authored Sep 23, 2024
1 parent b9ec527 commit 73551d3
Show file tree
Hide file tree
Showing 37 changed files with 1,866 additions and 1,211 deletions.
1 change: 0 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
examples
Makefile
Dockerfile
compose.yml
docs
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ RUN npm ci
COPY ./cmd/serve/front .
RUN npm run build

FROM golang:1.21-alpine AS builder
FROM golang:1.23-alpine AS builder
# build-base needed to compile the sqlite3 dependency
RUN apk add --update-cache build-base
WORKDIR /app
Expand Down
3 changes: 2 additions & 1 deletion cmd/serve/front/src/components/target-card.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import Stack from '$components/stack.svelte';
import CleanupNotice from '$components/cleanup-notice.svelte';
import routes from '$lib/path';
import l from '$lib/localization';
import { type Target, TargetStatus } from '$lib/resources/targets';
export let data: Target;
Expand All @@ -27,7 +28,7 @@
<Stack direction="column">
<div>
<h2 class="title"><Link href={routes.editTarget(data.id)}>{data.name}</Link></h2>
<div class="url">{data.url}</div>
<div class="url">{data.url ?? l.translate('target.manual_proxy')}</div>
</div>
<CleanupNotice requested_at={data.cleanup_requested_at} />
</Stack>
Expand Down
10 changes: 6 additions & 4 deletions cmd/serve/front/src/lib/localization/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ const translations = {
'auth.signin.description': 'Please fill the form below to access your dashboard.',
// App
'app.no_targets': 'No targets found',
'app.no_targets.description':
'You need at least one target to deploy your application. Head to the <a href="/targets">create target</a> page to create one.',
'app.no_targets.description': `You need at least one target to deploy your application. Head to the <a href="${routes.createTarget}">create target</a> page to create one.`,
'app.not_found': "Looks like the application you're looking for does not exist. Head back to the",
'app.not_found.cta': 'homepage',
'app.blankslate': `Looks like you have no application yet. <br />Applications represents <strong>services you want to deploy</strong> on your infrastructure. Start by <a href="${routes.createApp}">creating one!</a>`,
Expand Down Expand Up @@ -43,8 +42,8 @@ This action is IRREVERSIBLE and will DELETE ALL DATA associated with this applic
'app.environment.staging': 'Staging settings',
'app.environment.target': 'Deploy target',
'app.environment.target.changed': 'Target changed',
'app.environment.target.changed.description': (url: string) =>
`If you change the target, resources related to this application deployed by seelf on <strong>${url}</strong> will be <strong>REMOVED</strong> and a new deployment on the new target will be queued if possible. If you want to backup something, do it before updating the target.`,
'app.environment.target.changed.description': (name: string) =>
`If you change the target, resources related to this application deployed by seelf on <strong>${name}</strong> will be <strong>REMOVED</strong> and a new deployment on the new target will be queued if possible. If you want to backup something, do it before updating the target.`,
'app.environment.vars': 'Environment variables',
'app.environment.vars.service.add': 'Add service variables',
'app.environment.vars.service.delete': 'Remove service variables',
Expand Down Expand Up @@ -77,6 +76,9 @@ This action is IRREVERSIBLE and will DELETE ALL DATA associated with this applic
'target.blankslate': `Looks like you have no target yet. <br />Targets determine on which host your <strong>applications will be deployed</strong> and which <strong>provider</strong> should be used. Start by <a href="${routes.createTarget}">creating one!</a>`,
'target.general': 'General settings',
'target.name.help': 'The name is being used only for display, it can be anything you want.',
'target.manual_proxy': 'Manual proxy',
'target.automatic_proxy_configuration': 'Expose services automatically',
'target.automatic_proxy_configuration.help': `If enabled, a proxy will be deployed on the target and your services will be <a target="_blank" href="https://yuukanoo.github.io/seelf/reference/providers/docker.html#exposing-services">automatically exposed</a>. If disabled, you will <strong>have to manually expose your services</strong> with your preferred solution. Updating this setting <strong>may</strong> require you to redeploy your applications.`,
'target.url.help':
'All applications deployed on this target will be available as a <strong>subdomain</strong> on this root URL (without path). It should be <strong>unique</strong> among targets. You <strong>MUST</strong> configure a <strong>wildcard DNS</strong> for subdomains such as <code>*.&lt;url above&gt;</code> redirects to this target IP.',
'target.provider': 'Provider',
Expand Down
9 changes: 6 additions & 3 deletions cmd/serve/front/src/lib/localization/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default {
'Remplissez le formulaire ci-dessous pour accéder au tableau de bord.',
// App
'app.no_targets': 'Aucune cible trouvée',
'app.no_targets.description': `Vous avez besoin d'au moins une cible pour pouvoir déployer votre application. Dirigez-vous vers la <a href="/targets/new">page de création</a> pour en créer une.`,
'app.no_targets.description': `Vous avez besoin d'au moins une cible pour pouvoir déployer votre application. Dirigez-vous vers la <a href="${routes.createTarget}">page de création</a> pour en créer une.`,
'app.not_found':
"Il semblerait que l'application que vous recherchez n'existe pas. Retournez à la",
'app.not_found.cta': "page d'accueil",
Expand Down Expand Up @@ -47,8 +47,8 @@ Cette action est IRRÉVERSIBLE et supprimera TOUTES LES DONNÉES associées sur
'app.environment.staging': 'Paramètres de staging',
'app.environment.target': 'Cible de déploiement',
'app.environment.target.changed': 'Cible mise à jour',
'app.environment.target.changed.description': (url: string) =>
`Si vous changez de cible, toutes les ressources liées à cette application déployées par seelf sur <strong>${url}</strong> seront <strong>SUPPRIMÉES</strong> et un déploiement sur la nouvelle cible sera programmé si possible. Si vous devez sauvegarder quelque chose, faites le avant de changer la cible.`,
'app.environment.target.changed.description': (name: string) =>
`Si vous changez de cible, toutes les ressources liées à cette application déployées par seelf sur <strong>${name}</strong> seront <strong>SUPPRIMÉES</strong> et un déploiement sur la nouvelle cible sera programmé si possible. Si vous devez sauvegarder quelque chose, faites le avant de changer la cible.`,
'app.environment.vars': "Variables d'environnement",
'app.environment.vars.service.add': 'Ajouter un service',
'app.environment.vars.service.delete': 'Supprimer le service',
Expand Down Expand Up @@ -83,6 +83,9 @@ Cette action est IRRÉVERSIBLE et supprimera TOUTES LES DONNÉES associées sur
'target.blankslate': `Aucune cible pour le moment. <br />Les cibles déterminent sur quel hôte vos <strong>applications seront déployées</strong>. Commencez par <a href="${routes.createTarget}">en créer une !</a>`,
'target.general': 'Paramètres généraux',
'target.name.help': `Le nom est utilisé uniquement pour l'affichage. Vous pouvez choisir ce que vous voulez.`,
'target.manual_proxy': 'Proxy manuel',
'target.automatic_proxy_configuration': 'Exposer les services automatiquement',
'target.automatic_proxy_configuration.help': `Si activé, un proxy sera déployé sur la cible et vos services seront <a target="_blank" href="https://yuukanoo.github.io/seelf/reference/providers/docker.html#exposing-services">automatiquement exposés</a>. Si désactivé, vous <strong>devrez faire le nécessaire</strong> pour rendre vos services accessibles en utilisant la méthode de votre choix. Changer ce paramètre <strong>pourra</strong> nécessiter le redéploiement de vos applications.`,
'target.url.help': `Toutes les applications déployées sur cette cible seront disponibles en tant que <strong>sous-domaine</strong> de cette URL racine (sans sous-chemin). Elle doit être <strong>unique</strong> parmi les cibles. Vous <strong>DEVEZ</strong> configurer un <strong>DNS wildcard</strong> pour les sous-domaines de telle sorte que <code>*.&lt;url configurée&gt;</code> redirige vers l'IP de cette cible.`,
'target.provider': 'Fournisseur',
'target.provider.docker.help': "Docker engine <strong>DOIT</strong> être installé sur l'hôte.",
Expand Down
2 changes: 1 addition & 1 deletion cmd/serve/front/src/lib/resources/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export type VersionControl = { url: string; token?: string };
export type TargetSummary = {
id: string;
name: string;
url: string;
url?: string;
};

export type AppDetail = {
Expand Down
6 changes: 3 additions & 3 deletions cmd/serve/front/src/lib/resources/targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export type ProviderTypes = ProviderConfigData['kind'];
export type Target = {
id: string;
name: string;
url: string;
url?: string;
provider: ProviderConfigData;
state: TargetState;
cleanup_requested_at?: string;
Expand All @@ -39,7 +39,7 @@ export type Target = {

export type CreateTarget = {
name: string;
url: string;
url?: string;
docker?: {
host?: string;
user?: string;
Expand All @@ -50,7 +50,7 @@ export type CreateTarget = {

export type UpdateTarget = {
name?: string;
url?: string;
url: Patch<string>;
docker?: {
host?: string;
user?: string;
Expand Down
47 changes: 34 additions & 13 deletions cmd/serve/front/src/routes/(main)/apps/app-form.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import type { Target } from '$lib/resources/targets';
import l from '$lib/localization';
import Dropdown, { type DropdownOption } from '$components/dropdown.svelte';
import ServiceUrl from './service-url.svelte';
export let handler: (data: any) => Promise<unknown>;
export let targets: Target[];
Expand All @@ -40,8 +41,8 @@
let prodScheme = l.translate('app.how.placeholder.scheme');
let prodUrl = l.translate('app.how.placeholder.url');
let stagingScheme = l.translate('app.how.placeholder.scheme');
let stagingUrl = l.translate('app.how.placeholder.url');
let stagingScheme = prodScheme;
let stagingUrl = prodUrl;
const targetsMap = targets.reduce<Record<string, Target>>((acc, value) => {
acc[value.id] = value;
Expand All @@ -51,19 +52,23 @@
$: appName = name || l.translate('app.how.placeholder.name');
$: {
try {
const u = new URL(targetsMap[production.target]?.url);
const u = new URL(targetsMap[production.target]?.url!);
prodScheme = u.protocol + '//';
prodUrl = u.hostname;
} catch {}
} catch {
prodScheme = prodUrl = '';
}
}
$: {
try {
const u = new URL(targetsMap[staging.target]?.url);
const u = new URL(targetsMap[staging.target]?.url!);
stagingScheme = u.protocol + '//';
stagingUrl = u.hostname;
} catch {}
} catch {
stagingScheme = stagingUrl = '';
}
}
const environmentText = l.translate('app.how.env');
Expand All @@ -73,7 +78,7 @@
const targetsOptions = targets.map((target) => ({
value: target.id,
label: `${target.url} - ${target.name}`
label: `${target.url ?? l.translate('target.manual_proxy')} - ${target.name}`
})) satisfies DropdownOption<string>[];
// Type $$Props to narrow the handler function based on wether this is an update or a new app
Expand Down Expand Up @@ -167,19 +172,35 @@
<tr>
<td data-label={environmentText}><strong>production</strong></td>
<td data-label={defaultServiceText}>
{prodScheme}{appName}.{prodUrl}
<ServiceUrl scheme={prodScheme} host={prodUrl} {appName} />
</td>
<td data-label={otherServicesTitleText}>
{prodScheme}dashboard.{appName}.{prodUrl}
<ServiceUrl
scheme={prodScheme}
host={prodUrl}
{appName}
prefix="dashboard."
/>
</td>
</tr>
<tr>
<td data-label={environmentText}><strong>staging</strong></td>
<td data-label={defaultServiceText}>
{stagingScheme}{appName}-staging.{stagingUrl}
<ServiceUrl
scheme={stagingScheme}
host={stagingUrl}
{appName}
suffix="-staging"
/>
</td>
<td data-label={otherServicesTitleText}>
{stagingScheme}dashboard.{appName}-staging.{stagingUrl}
<ServiceUrl
scheme={stagingScheme}
host={stagingUrl}
{appName}
prefix="dashboard."
suffix="-staging"
/>
</td>
</tr>
</tbody>
Expand Down Expand Up @@ -223,7 +244,7 @@
<Panel title="app.environment.target.changed" variant="warning">
<p>
{@html l.translate('app.environment.target.changed.description', [
initialData.production.target.url
initialData.production.target.name
])}
</p>
</Panel>
Expand Down Expand Up @@ -252,7 +273,7 @@
<Panel title="app.environment.target.changed" variant="warning">
<p>
{@html l.translate('app.environment.target.changed.description', [
initialData.production.target.url
initialData.production.target.name
])}
</p>
</Panel>
Expand Down
15 changes: 15 additions & 0 deletions cmd/serve/front/src/routes/(main)/apps/service-url.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
import l from '$lib/localization';
export let scheme: string;
export let host: string;
export let appName: string;
export let prefix = '';
export let suffix = '';
</script>

{#if scheme}
{scheme}{prefix}{appName}{suffix}.{host}
{:else}
- ({l.translate('target.manual_proxy')})
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
export let data;
const submit = (d: UpdateTarget) =>
service.update(data.target.id, d).then((t) => goto(routes.targets));
service.update(data.target.id, d).then(() => goto(routes.targets));
const {
loading: deleting,
Expand Down
21 changes: 15 additions & 6 deletions cmd/serve/front/src/routes/(main)/targets/target-form.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
let name = initialData?.name ?? '';
let url = initialData?.url ?? '';
let provider: ProviderTypes = initialData?.provider.kind ?? providerTypes[0];
let isRemote = !!initialData?.provider.data.host ?? false;
let isRemote = !!initialData?.provider.data.host || false;
let automaticProxyConfiguration = initialData ? !!initialData.url : true;
const docker = { ...initialData?.provider.data };
Expand All @@ -48,7 +49,7 @@
if (!initialData) {
formData = {
name,
url,
url: automaticProxyConfiguration ? url : undefined,
docker:
provider === 'docker'
? isRemote
Expand All @@ -64,7 +65,7 @@
} else {
formData = {
name,
url,
url: automaticProxyConfiguration ? (initialData?.url !== url ? url : undefined) : null,
docker:
provider === 'docker'
? isRemote
Expand Down Expand Up @@ -114,9 +115,17 @@
<TextInput autofocus label="name" bind:value={name} required remoteError={errors?.name}>
<p>{l.translate('target.name.help')}</p>
</TextInput>
<TextInput label="url" bind:value={url} required type="url" remoteError={errors?.url}>
<p>{@html l.translate('target.url.help')}</p>
</TextInput>
<Checkbox
label="target.automatic_proxy_configuration"
bind:checked={automaticProxyConfiguration}
>
<p>{@html l.translate('target.automatic_proxy_configuration.help')}</p>
</Checkbox>
{#if automaticProxyConfiguration}
<TextInput label="url" bind:value={url} required type="url" remoteError={errors?.url}>
<p>{@html l.translate('target.url.help')}</p>
</TextInput>
{/if}
</Stack>
</FormSection>

Expand Down
2 changes: 1 addition & 1 deletion docs/reference/providers/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Where `ENVIRONMENT` will be one of `production`, `staging`.

## Exposing services

Once a valid compose file has been found, **seelf** will apply some **heuristics** to determine which services should be exposed and where.
Once a valid compose file has been found and **only if** the target [manages the proxy itself](/reference/targets#proxy), **seelf** will apply some **heuristics** to determine which services should be exposed and where.

It will consider any service with **port mappings** to be exposed.

Expand Down
11 changes: 9 additions & 2 deletions docs/reference/targets.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@ Targets represents an **host** where your deployments will be exposed. When conf
For now, only one target per host is allowed.
:::

## Url
## Proxy configuration {#proxy}

The url **determine where your applications will be made available**. It should be a **root url** as applications will use subdomains on it.
When declaring a target, you must choose how the proxy (needed to make your services available from the outside world) should be managed:

- **Automatic**: **seelf** will deploy and configure a [traefik](https://traefik.io/traefik/) proxy on the target. Services urls will be automatically generated based on the [target's url](#url) and [service file](/reference/providers/docker#exposing-services) when deploying. Exposed services will also join the proxy network.
- **Manual**: you're in charge of **everything** related to services exposure. **seelf** will deploy services on this target without attempting to expose them in any way.

### Url

If the target manages the proxy itself, this url **determines where your applications will be made available**. It should be a **root url** as applications will use subdomains on it.

The scheme associated with this url (`http` or `https`) will determine if certificates should be generated or not.

Expand Down
Loading

0 comments on commit 73551d3

Please sign in to comment.