feat: specific git commit deployment

feat: revert to specific image
fix: no system wide docker registries
This commit is contained in:
Andras Bacsai
2022-11-30 15:22:07 +01:00
parent a08bb25bfa
commit 9913e7b70b
20 changed files with 494 additions and 231 deletions

View File

@@ -268,7 +268,7 @@
<a
id="settings"
sveltekit:prefetch
href={$appSession.teamId === '0' ? '/settings/coolify' : '/settings/ssh'}
href={$appSession.teamId === '0' ? '/settings/coolify' : '/settings/docker'}
class="icons hover:text-settings"
class:text-settings={$page.url.pathname.startsWith('/settings')}
class:bg-coolgray-500={$page.url.pathname.startsWith('/settings')}

View File

@@ -13,7 +13,7 @@
<a
id="git"
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
target="_blank"
target="_blank noreferrer"
class="no-underline"
>
{#if application.gitSource?.type === 'gitlab'}
@@ -165,7 +165,9 @@
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/logs`}
>
<a
href={$status.application.overallStatus !== 'stopped' ? `/applications/${$page.params.id}/logs` : ''}
href={$status.application.overallStatus !== 'stopped'
? `/applications/${$page.params.id}/logs`
: ''}
class="no-underline w-full"
><svg
xmlns="http://www.w3.org/2000/svg"
@@ -216,12 +218,38 @@
<li class="menu-title">
<span>Advanced</span>
</li>
<li
class="rounded"
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/revert`}
>
<a href={`/applications/${$page.params.id}/revert`} class="no-underline w-full">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M20 5v14l-12 -7z" />
<line x1="4" y1="5" x2="4" y2="19" />
</svg>
Revert</a
>
</li>
<li
class="rounded"
class:text-stone-600={$status.application.overallStatus !== 'healthy'}
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/usage`}
>
<a href={$status.application.overallStatus === 'healthy' ? `/applications/${$page.params.id}/usage` : ''} class="no-underline w-full"
<a
href={$status.application.overallStatus === 'healthy'
? `/applications/${$page.params.id}/usage`
: ''}
class="no-underline w-full"
><svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"

View File

@@ -96,6 +96,18 @@
async function handleDeploySubmit(forceRebuild = false) {
if (!$isDeploymentEnabled) return;
if (application.gitCommitHash && !application.settings.isPublicRepository) {
const sure = await confirm(
`Are you sure you want to deploy a specific commit (${application.gitCommitHash})? This will disable the "Automatic Deployment" feature to prevent accidental overwrites of incoming commits.`
);
if (!sure) {
return;
} else {
await post(`/applications/${id}/settings`, {
autodeploy: false
});
}
}
if (!statusInterval) {
statusInterval = setInterval(async () => {
await getStatus();

View File

@@ -46,73 +46,48 @@
<div class="flex flex-col justify-center w-full">
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row mx-auto gap-4">
{#each registries.public as registry}
<button
on:click={() => handleSubmit(registry.id)}
class="box-selection hover:bg-primary relative"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="absolute top-0 left-0 -m-4 h-12 w-12 text-sky-500"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
{#if registries.length > 0}
{#each registries as registry}
<button
on:click={() => handleSubmit(registry.id)}
class="box-selection hover:bg-primary relative"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
/>
<path d="M5 10h3v3h-3z" />
<path d="M8 10h3v3h-3z" />
<path d="M11 10h3v3h-3z" />
<path d="M8 7h3v3h-3z" />
<path d="M11 7h3v3h-3z" />
<path d="M11 4h3v3h-3z" />
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
<line x1="10" y1="16" x2="10" y2="16.01" />
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
class="absolute top-0 left-0 -m-4 h-12 w-12 text-sky-500"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
/>
<path d="M5 10h3v3h-3z" />
<path d="M8 10h3v3h-3z" />
<path d="M11 10h3v3h-3z" />
<path d="M8 7h3v3h-3z" />
<path d="M11 7h3v3h-3z" />
<path d="M11 4h3v3h-3z" />
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
<line x1="10" y1="16" x2="10" y2="16.01" />
</svg>
<div class="font-bold text-xl text-center truncate">{registry.name}</div>
<div class="text-center truncate">{registry.url}</div>
<div>public</div>
</button>
{/each}
{#each registries.private as registry}
<button
on:click={() => handleSubmit(registry.id)}
class="box-selection hover:bg-primary relative"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="absolute top-0 left-0 -m-4 h-12 w-12 text-sky-500"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
/>
<path d="M5 10h3v3h-3z" />
<path d="M8 10h3v3h-3z" />
<path d="M11 10h3v3h-3z" />
<path d="M8 7h3v3h-3z" />
<path d="M11 7h3v3h-3z" />
<path d="M11 4h3v3h-3z" />
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
<line x1="10" y1="16" x2="10" y2="16.01" />
</svg>
<div class="font-bold text-xl text-center truncate">{registry.name}</div>
<div class="text-center truncate">{registry.url}</div>
<div>private</div>
</button>
{/each}
<div class="font-bold text-xl text-center truncate">{registry.name}</div>
<div class="text-center truncate">{registry.url}</div>
</button>
{/each}
{:else}
<div class="flex flex-col items-center gap-2">
<div class="text-center text-xl font-bold pb-4">No registries found.</div>
<div class="flex gap-2">
<a class="btn btn-sm" href={from || `/applications/${id}`}>Go back</a>
<a class="btn btn-sm btn-primary" href={`/settings/docker`}>Add a Docker Registry</a>
</div>
</div>
{/if}
</div>
</div>

View File

@@ -515,40 +515,37 @@
>
{/if}
</div>
{#if application.settings.isPublicRepository}
<div class="grid grid-cols-2 items-center">
<label for="repository">Git commit</label>
<div class="flex gap-2">
<input
class="w-full"
disabled={isDisabled}
placeholder="default: latest commit"
bind:value={application.gitCommitHash}
/>
<a
href="{application.gitSource
.htmlUrl}/{application.repository}/commits/{application.branch}"
target="_blank"
rel="noreferrer"
class="btn btn-primary text-xs"
>Commits<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
stroke-width="3"
stroke="currentColor"
class="w-3 h-3 text-white ml-2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25"
/>
</svg></a
<div class="grid grid-cols-2 items-center">
<label for="repository">Git commit</label>
<div class="flex gap-2">
<input
class="w-full"
disabled={isDisabled}
placeholder="default: latest commit"
bind:value={application.gitCommitHash}
/>
<a
href="{application.gitSource
.htmlUrl}/{application.repository}/commits/{application.branch}"
target="_blank noreferrer"
class="btn btn-primary text-xs"
>Commits<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
stroke-width="3"
stroke="currentColor"
class="w-3 h-3 text-white ml-2"
>
</div>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25"
/>
</svg></a
>
</div>
{/if}
</div>
<div class="grid grid-cols-2 items-center">
<label for="repository">{$t('application.git_repository')}</label>
{#if isDisabled || application.settings.isPublicRepository}
@@ -575,7 +572,7 @@
<input
class="capitalize w-full"
disabled={isDisabled}
value={application.dockerRegistry.name}
value={application.dockerRegistry?.name || 'DockerHub (unauthenticated)'}
/>
{:else}
<a
@@ -583,7 +580,7 @@
class="no-underline"
>
<input
value={application.dockerRegistry.name}
value={application.dockerRegistry?.name || 'DockerHub (unauthenticated)'}
id="registry"
class="cursor-pointer hover:bg-coolgray-500 capitalize w-full"
/></a

View File

@@ -0,0 +1,119 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, stuff, url }) => {
try {
const response = await get(`/applications/${params.id}/images`);
return {
props: {
application: stuff.application,
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
export let application: any;
export let imagesAvailables: any;
export let runningImage: any;
import { page } from '$app/stores';
import { get, post } from '$lib/api';
import { status, addToast } from '$lib/store';
import { errorNotification } from '$lib/common';
const { id } = $page.params;
async function revertApplication(image: any) {
const sure = confirm(`Are you sure you want to revert to ${image.tag} ?`);
if (sure) {
try {
$status.application.initialLoading = true;
$status.application.loading = true;
const imageId = `${image.repository}:${image.tag}`;
await post(`/applications/${id}/restart`, { imageId });
addToast({
type: 'success',
message: 'Revert successful.'
});
} catch (error) {
return errorNotification(error);
} finally {
$status.application.initialLoading = false;
$status.application.loading = false;
}
}
}
</script>
<div class="w-full">
<div class="mx-auto w-full">
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2">
<div class="title font-bold pb-3">Revert Application</div>
</div>
<div>
You can revert application to a previously built image. Currently only locally stored images
supported.
</div>
<br />
<div class="pb-4">
If you do not want the next commit to overwrite the reverted application, temporary disable <span
class="text-yellow-400 font-bold">Automatic Deployment</span
>
feature <a href={`/applications/${id}/features`}>here</a>.
</div>
<div
class="px-4 lg:pb-10 pb-6 flex flex-wrap items-center justify-center lg:justify-start gap-8"
>
{#each imagesAvailables as image}
<div class="gap-2 py-4 m-2">
<div class="flex flex-col justify-center items-center">
<div class="text-xl font-bold">
{image.tag}
</div>
<div>
<a
class="flex no-underline text-xs my-4"
href="{application.gitSource.htmlUrl}/{application.repository}/commit/{image.tag}"
target="_blank noreferrer"
>
<button class="btn btn-sm">
Check Commit
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
stroke-width="3"
stroke="currentColor"
class="w-3 h-3 text-white ml-2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M4.5 19.5l15-15m0 0H8.25m11.25 0v11.25"
/>
</svg>
</button></a
>
{#if image.repository + ':' + image.tag !== runningImage}
<button
class="btn btn-sm btn-primary w-full"
on:click={() => revertApplication(image)}>Revert Now</button
>
{:else}
<button class="btn btn-sm btn-primary w-full btn-disabled bg-transparent underline"
>Currently Used</button
>
{/if}
</div>
</div>
</div>
{/each}
</div>
</div>
</div>

View File

@@ -4,10 +4,10 @@
</script>
<ul class="menu border bg-coolgray-100 border-coolgray-200 rounded p-2 space-y-2">
<li class="menu-title">
<span>General</span>
</li>
{#if $appSession.teamId === '0'}
<li class="menu-title">
<span>General</span>
</li>
<li class="rounded" class:bg-coollabs={$page.url.pathname === `/settings/coolify`}>
<a href={`/settings/coolify`} class="no-underline w-full"
><svg
@@ -27,35 +27,35 @@
</svg>Coolify Settings</a
>
</li>
<li class="rounded" class:bg-coollabs={$page.url.pathname === `/settings/docker`}>
<a href={`/settings/docker`} class="no-underline w-full">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
/>
<path d="M5 10h3v3h-3z" />
<path d="M8 10h3v3h-3z" />
<path d="M11 10h3v3h-3z" />
<path d="M8 7h3v3h-3z" />
<path d="M11 7h3v3h-3z" />
<path d="M11 4h3v3h-3z" />
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
<line x1="10" y1="16" x2="10" y2="16.01" />
</svg>Docker Registries</a
>
</li>
{/if}
<li class="rounded" class:bg-coollabs={$page.url.pathname === `/settings/docker`}>
<a href={`/settings/docker`} class="no-underline w-full">
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M22 12.54c-1.804 -.345 -2.701 -1.08 -3.523 -2.94c-.487 .696 -1.102 1.568 -.92 2.4c.028 .238 -.32 1.002 -.557 1h-14c0 5.208 3.164 7 6.196 7c4.124 .022 7.828 -1.376 9.854 -5c1.146 -.101 2.296 -1.505 2.95 -2.46z"
/>
<path d="M5 10h3v3h-3z" />
<path d="M8 10h3v3h-3z" />
<path d="M11 10h3v3h-3z" />
<path d="M8 7h3v3h-3z" />
<path d="M11 7h3v3h-3z" />
<path d="M11 4h3v3h-3z" />
<path d="M4.571 18c1.5 0 2.047 -.074 2.958 -.78" />
<line x1="10" y1="16" x2="10" y2="16.01" />
</svg>Docker Registries</a
>
</li>
<li class="menu-title">
<span>Keys & Certificates</span>
</li>

View File

@@ -22,17 +22,13 @@
import { errorNotification } from '$lib/common';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { addToast } from '$lib/store';
const publicRegistries = registries.public;
const privateRegistries = registries.private;
let isModalActive = false;
let newRegistry = {
name: '',
username: '',
password: '',
url: '',
isSystemWide: false
url: ''
};
async function handleSubmit() {
@@ -71,6 +67,37 @@
}
}
}
async function addRegistry(type: string) {
switch (type) {
case 'dockerhub':
newRegistry = {
name: 'Docker Hub',
username: '',
password: '',
url: 'https://index.docker.io/v1/'
};
await handleSubmit();
break;
case 'gcrio':
newRegistry = {
name: 'Google Container Registry',
username: '',
password: '',
url: 'https://gcr.io'
};
await handleSubmit();
break;
case 'github':
newRegistry = {
name: 'GitHub Container Registry',
username: '',
password: '',
url: 'https://ghcr.io'
};
await handleSubmit();
break;
}
}
</script>
<div class="w-full">
@@ -81,57 +108,34 @@
>Add Docker Registry</label
>
</div>
<div class="flex items-center pb-4 gap-2">
<div class="text-xs">Quick Action</div>
<button class="btn btn-sm text-xs" on:click={() => addRegistry('dockerhub')}>DockerHub</button>
<button class="btn btn-sm text-xs" on:click={() => addRegistry('gcrio')}
>Google Container Registry (gcr.io)</button
>
<button class="btn btn-sm text-xs" on:click={() => addRegistry('github')}
>GitHub Container Registry (ghcr.io)</button
>
</div>
{#if registries.length > 0}
<div class="mx-auto w-full">
<table class="table w-full">
<thead>
<tr>
<th>Name</th>
<th>SystemWide</th>
<th>Username</th>
<th>Password</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{#each publicRegistries as registry}
{#each registries as registry}
<tr>
<td>{registry.name}<div class="text-xs">{registry.url}</div></td>
<td>{(registry.isSystemWide && 'Yes') || 'No'}</td>
<td>
<CopyPasswordField
name="username"
id="Username"
bind:value={registry.username}
placeholder="Username"
/></td
>
<td
><CopyPasswordField
isPasswordField={true}
name="Password"
id="Password"
bind:value={registry.password}
placeholder="Password"
/></td
>{registry.name}
<div class="text-xs">{registry.url}</div></td
>
<td>
<button on:click={() => setRegistry(registry)} class="btn btn-sm btn-primary"
>Set</button
>
{#if registry.id !== '0'}
<button
on:click={() => deleteDockerRegistry(registry.id)}
class="btn btn-sm btn-error">Delete</button
>
{/if}
</td>
</tr>
{/each}
{#each privateRegistries as registry}
<tr>
<td>{registry.name} <div class="text-xs">{registry.url}</div></td>
<td>{(registry.isSystemWide && 'Yes') || 'No'}</td>
<td>
<CopyPasswordField
name="username"
@@ -166,6 +170,7 @@
</tbody>
</table>
</div>
{/if}
</div>
{#if isModalActive}