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

Feature - Add Env. Vars Support #11

Merged
merged 10 commits into from
Feb 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"express": "^4.17.2",
"jsonwebtoken": "^8.5.1",
"mongoose": "^6.2.0",
"morgan": "^1.10.0",
"passport": "^0.5.2",
"passport-jwt": "^4.0.0",
"socket.io": "^4.4.1"
Expand Down
13 changes: 13 additions & 0 deletions api/src/models/Service.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ const ServiceSchema = new mongoose.Schema({
type: [{ from: String, to: String }],
required: false,
},
environments: {
type: [{ key: String, value: String }],
required: false,
default: [],
},
order: {
type: Number,
required: true,
Expand Down Expand Up @@ -77,6 +82,14 @@ ServiceSchema.methods.getServiceLabels = function () {
return labels;
};

ServiceSchema.methods.getEnvironments = function () {
const environments = this.environments.map((environment) => {
return `${environment.key}=${environment.value}`;
});

return environments;
};

const transformHostsToLabel = (service) => {
const label = `traefik.http.routers.${service.name}.rule`;
const hosts = service.hosts.map((host) => {
Expand Down
7 changes: 6 additions & 1 deletion api/src/routes/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,18 @@ router.put("/:name", async (req, res) => {
service.redirects = redirects;
}

const environments = updateRequest.environments;
if (environments) {
service.environments = environments;
}

const tag = updateRequest.tag;
if (tag) {
service.tag = tag;
}

await service.save();
if (hosts || image || redirects || tag) {
if (hosts || image || redirects || environments || tag) {
await updateContainer(service, service.image);
await startContainer(service);
}
Expand Down
2 changes: 2 additions & 0 deletions api/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const path = require("path");
const dotenv = require("dotenv");
const passport = require("passport");
const cors = require("cors");
const morgan = require("morgan");

const connectDB = require("./config/db");
const setupPassport = require("./config/passport");
Expand All @@ -24,6 +25,7 @@ app.use(
})
);

app.use(morgan("common"));
app.use(passport.initialize());
app.use(express.json());

Expand Down
42 changes: 38 additions & 4 deletions api/src/utils/docker.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,27 +75,59 @@ const updateContainer = async (service, image) => {
containerLabels[element.split("=")[0]] = element.split("=")[1];
});

const environments = service.getEnvironments();

const portBindings = {};
if (oldContainer.Ports.length > 0) {
oldContainer.Ports.forEach((port) => {
const { PrivatePort, PublicPort, Type } = port;
if (PrivatePort === undefined || PublicPort === undefined) {
return;
}
portBindings[`${PrivatePort}/${Type}`] = [
{
HostPort: `${PublicPort}`,
},
];
});
}

const hostConfig = {
NetworkMode: service.network,
};

if (portBindings && Object.keys(portBindings).length > 0) {
hostConfig.PortBindings = portBindings;
}

Object.keys(containerLabels).map((key) => {
if (!containerLabels[key] || containerLabels[key] === "") {
delete containerLabels[key];
}
});

await deleteContainer(service);

const container = await docker.createContainer({
Image: image.resolvedName,
name: service.name,
Labels: containerLabels,
HostConfig: {
NetworkMode: service.network,
},
HostConfig: hostConfig,
Env: environments,
});
service.containerId = container.id;
service.status = "created";
await service.save();
};

const getContainer = (containerId) => {
return docker.getContainer(containerId);
};

const inspectContainer = async (containerId) => {
const container = docker.getContainer(containerId);
return await container.inspect();
};

const getAllContainers = async () => {
const containers = await docker.listContainers({
all: true,
Expand All @@ -117,4 +149,6 @@ module.exports = {
updateContainer,
getAllContainers,
getAllImages,
getContainer,
inspectContainer,
};
7 changes: 3 additions & 4 deletions api/src/utils/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const { deleteContainer } = require("./docker");

const getOrCreateImage = async (resolvedName) => {
const { repository, imageName, tag } = parseResolvedName(resolvedName);

if (!imageName || !tag || !repository) {
return -1;
}
Expand Down Expand Up @@ -54,11 +53,11 @@ const createService = async (serviceRequest, image) => {
};

const parseResolvedName = (resolvedName) => {
const regex = /^(.+)\/(.+):(.+)$|^(.+):(.+)$|^(.+)/;
const regex = /^(.+)\/(.+):(.+)$|^(.+):(.+)$|^(.+)\/(.+)|^(.+)$/;
const match = regex.exec(resolvedName);

const repository = match[1] ?? "_";
const imageName = match[2] ?? match[4] ?? match[6];
const repository = match[1] ?? match[6] ?? "_";
const imageName = match[2] ?? match[4] ?? match[7] ?? match[8];
const tag = match[3] ?? match[5] ?? "latest";

return {
Expand Down
28 changes: 24 additions & 4 deletions api/src/utils/startup.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ const {
parseRedirectLabels,
parseTraefikerLabels,
} = require("./services");
const { getAllContainers, getAllImages } = require("./docker");
const {
getAllContainers,
getAllImages,
inspectContainer,
} = require("./docker");

const createImages = async () => {
const images = await getAllImages();
Expand Down Expand Up @@ -41,9 +45,18 @@ const createContainers = async (images) => {
) {
return Promise.resolve();
}
const image = images.find(
(image) => image.resolvedName === container.Image
);
const image = images.find((image) => {
const { repository, imageName, tag } = parseResolvedName(
container.Image
);
if (
image.repository === repository &&
image.name === imageName &&
image.tag === tag
) {
return true;
}
});

if (image === undefined) {
return Promise.resolve();
Expand All @@ -59,12 +72,19 @@ const createContainers = async (images) => {
const redirects = parseRedirectLabels(labels);
const traefikerLabels = parseTraefikerLabels(labels);

const inspectData = await inspectContainer(container.Id);
const environments = inspectData.Config.Env.map((env) => {
const [key, value] = env.split("=");
return { key: key, value: value };
});

const service = new Service({
name: serviceName,
status: container.State == "running" ? "running" : "stopped",
image: image._id,
hosts: hosts,
redirects: redirects,
environments,
order: i,
tag: traefikerLabels.tag === "" ? serviceName : traefikerLabels.tag,
containerId: container.Id,
Expand Down
33 changes: 33 additions & 0 deletions api/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ base64id@2.0.0, base64id@~2.0.0:
resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==

basic-auth@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
dependencies:
safe-buffer "5.1.2"

bcrypt-pbkdf@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
Expand Down Expand Up @@ -480,6 +487,11 @@ depd@~1.1.2:
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=

depd@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==

destroy@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
Expand Down Expand Up @@ -1317,6 +1329,17 @@ mongoose@^6.2.0:
regexp-clone "1.0.0"
sift "13.5.2"

morgan@^1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7"
integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==
dependencies:
basic-auth "~2.0.1"
debug "2.6.9"
depd "~2.0.0"
on-finished "~2.3.0"
on-headers "~1.0.2"

mpath@0.8.4:
version "0.8.4"
resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.8.4.tgz#6b566d9581621d9e931dd3b142ed3618e7599313"
Expand Down Expand Up @@ -1404,6 +1427,11 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"

on-headers@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==

once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
Expand Down Expand Up @@ -1628,6 +1656,11 @@ rimraf@^3.0.2:
dependencies:
glob "^7.1.3"

safe-buffer@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==

safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
Expand Down
6 changes: 3 additions & 3 deletions client/src/atoms/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ export const loadingFlagsState = atom({
},
});

export const redirectsModalState = atom({
key: "isAddingRedirects",
export const settingsModalState = atom({
key: "isEditingSettings",
default: {
isAddingRedirects: false,
isEditingSettings: false,
service: <Service>{},
},
});
2 changes: 1 addition & 1 deletion client/src/components/dashboard/table/DashboardTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ function DashboardTable() {
{ name: "Service Tag", screenReaderOnly: false },
{ name: "Image Name", screenReaderOnly: false },
{ name: "Service Hosts", screenReaderOnly: false },
{ name: "Add Redirects", screenReaderOnly: true },
{ name: "Run/Stop", screenReaderOnly: true },
{ name: "Edit", screenReaderOnly: true },
{ name: "Delete", screenReaderOnly: true },
{ name: "Config", screenReaderOnly: true },
{ name: "Order", screenReaderOnly: true },
];

Expand Down
8 changes: 4 additions & 4 deletions client/src/components/dashboard/table/DashboardTableBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useRecoilState } from "recoil";
import {
isCreatingServiceState,
loadingFlagsState,
redirectsModalState,
settingsModalState,
servicesState,
} from "../../../atoms/atoms";

Expand Down Expand Up @@ -50,7 +50,7 @@ function DashboardTableBody({ columns }: Props) {
isCreatingServiceState
);
const [loadingFlags, setLoadingFlags] = useRecoilState(loadingFlagsState);
const [, setRedirectsModalOptions] = useRecoilState(redirectsModalState);
const [, setSettingsModalOptions] = useRecoilState(settingsModalState);

const [serviceUnderEditing, setServiceUnderEditing] = useState<
Service | undefined
Expand Down Expand Up @@ -136,8 +136,8 @@ function DashboardTableBody({ columns }: Props) {
};

const redirectsClicked = (service: Service) => {
setRedirectsModalOptions({
isAddingRedirects: true,
setSettingsModalOptions({
isEditingSettings: true,
service: service,
});
};
Expand Down
33 changes: 16 additions & 17 deletions client/src/components/dashboard/table/DashboardTableRow.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Service } from "../../../types/Service";
import { Draggable } from "react-beautiful-dnd";
import { MenuIcon, SwitchHorizontalIcon } from "@heroicons/react/solid";
import { CogIcon, MenuIcon } from "@heroicons/react/solid";
import seedrandom from "seedrandom";

interface Props {
Expand Down Expand Up @@ -158,21 +158,7 @@ function DashboardTableRow({
</a>
))}
</td>
<td
className="hidden whitespace-nowrap text-right text-xs font-medium lg:table-cell lg:px-6
lg:py-4 lg:text-sm"
>
<button
className="flex content-center items-center text-orange-600 hover:text-orange-900 disabled:cursor-not-allowed disabled:text-gray-600 disabled:hover:text-gray-600"
onClick={() => {
redirectsClicked(service);
}}
disabled={isLoading}
>
<SwitchHorizontalIcon className="mr-2 h-5 w-5" />{" "}
Redirects
</button>
</td>

<td
className="hidden whitespace-nowrap text-right text-xs font-medium lg:table-cell lg:px-6
lg:py-4 lg:text-sm"
Expand Down Expand Up @@ -209,7 +195,20 @@ function DashboardTableRow({
Delete
</button>
</td>

<td
className="hidden whitespace-nowrap text-right text-xs font-medium lg:table-cell lg:px-6
lg:py-4 lg:text-sm"
>
<button
className="flex content-center items-center text-orange-600 hover:text-orange-900 disabled:cursor-not-allowed disabled:text-gray-600 disabled:hover:text-gray-600"
onClick={() => {
redirectsClicked(service);
}}
disabled={isLoading}
>
<CogIcon className="mr-2 h-5 w-5" />
</button>
</td>
<td className="hidden p-1 text-right lg:table-cell">
<div className="m-2 flex justify-end">
<MenuIcon className="h-5 w-5" />
Expand Down
Loading