[release] version 0.5.5
This commit is contained in:
parent
c8731e2fa7
commit
a6098f0507
|
@ -42,14 +42,63 @@ let isOnline = () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let newInstall = (req) => {
|
let newInstall = (req, onProgress) => {
|
||||||
return wrap(fetch('/cosmos/api/newInstall', {
|
if(req.step == '2') {
|
||||||
method: 'POST',
|
const requestOptions = {
|
||||||
headers: {
|
method: 'POST',
|
||||||
'Content-Type': 'application/json'
|
headers: {
|
||||||
},
|
'Content-Type': 'application/json'
|
||||||
body: JSON.stringify(req)
|
},
|
||||||
}))
|
body: JSON.stringify(req)
|
||||||
|
};
|
||||||
|
|
||||||
|
return fetch('/cosmos/api/newInstall', requestOptions)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The response body is a ReadableStream. This code reads the stream and passes chunks to the callback.
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
|
||||||
|
// Read the stream and pass chunks to the callback as they arrive
|
||||||
|
return new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
function read() {
|
||||||
|
return reader.read().then(({ done, value }) => {
|
||||||
|
if (done) {
|
||||||
|
controller.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Decode the UTF-8 text
|
||||||
|
let text = new TextDecoder().decode(value);
|
||||||
|
// Split by lines in case there are multiple lines in one chunk
|
||||||
|
let lines = text.split('\n');
|
||||||
|
for (let line of lines) {
|
||||||
|
if (line) {
|
||||||
|
// Call the progress callback
|
||||||
|
onProgress(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.enqueue(value);
|
||||||
|
return read();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return read();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch((e) => {
|
||||||
|
alert(e);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return wrap(fetch('/cosmos/api/newInstall', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(req)
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDemo = import.meta.env.MODE === 'demo';
|
const isDemo = import.meta.env.MODE === 'demo';
|
||||||
|
|
9
client/src/components/containers.jsx
Normal file
9
client/src/components/containers.jsx
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { WarningFilled } from "@ant-design/icons";
|
||||||
|
import { Tooltip } from "@mui/material";
|
||||||
|
|
||||||
|
export const ContainerNetworkWarning = ({container}) => (
|
||||||
|
container.HostConfig.NetworkMode != "bridge" && container.HostConfig.NetworkMode != "default" &&
|
||||||
|
<Tooltip title={`This container is using an incompatible network mode (${container.HostConfig.NetworkMode.slice(0, 16)}). If you want Cosmos to proxy to this container, enabling this option will change the network mode to bridge for you. Otherwise, you dont need to do anything, as the container is already isolated. Note that changing to bridge might break connectivity to other containers. To fix it, please use a private network and static ips instead.`}>
|
||||||
|
<WarningFilled style={{color: 'red', fontSize: '18px', paddingLeft: '10px', paddingRight: '10px'}} />
|
||||||
|
</Tooltip>
|
||||||
|
);
|
|
@ -14,6 +14,7 @@ import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import * as API from '../../api';
|
import * as API from '../../api';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
|
import LogsInModal from '../../components/logsInModal';
|
||||||
import { CosmosCheckbox, CosmosInputPassword, CosmosInputText, CosmosSelect } from '../config/users/formShortcuts';
|
import { CosmosCheckbox, CosmosInputPassword, CosmosInputText, CosmosSelect } from '../config/users/formShortcuts';
|
||||||
import AnimateButton from '../../components/@extended/AnimateButton';
|
import AnimateButton from '../../components/@extended/AnimateButton';
|
||||||
import { Box } from '@mui/system';
|
import { Box } from '@mui/system';
|
||||||
|
@ -25,6 +26,7 @@ const NewInstall = () => {
|
||||||
const [counter, setCounter] = useState(0);
|
const [counter, setCounter] = useState(0);
|
||||||
let [hostname, setHostname] = useState('');
|
let [hostname, setHostname] = useState('');
|
||||||
const [databaseEnable, setDatabaseEnable] = useState(true);
|
const [databaseEnable, setDatabaseEnable] = useState(true);
|
||||||
|
const [pullRequest, setPullRequest] = useState(null);
|
||||||
|
|
||||||
const refreshStatus = async () => {
|
const refreshStatus = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -118,27 +120,29 @@ const NewInstall = () => {
|
||||||
validate={(values) => {
|
validate={(values) => {
|
||||||
}}
|
}}
|
||||||
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
|
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
|
||||||
try {
|
setSubmitting(true);
|
||||||
setSubmitting(true);
|
|
||||||
const res = await API.newInstall({
|
setPullRequest(() => ((cb) => {
|
||||||
|
API.newInstall({
|
||||||
step: "2",
|
step: "2",
|
||||||
MongoDBMode: values.DBMode,
|
MongoDBMode: values.DBMode,
|
||||||
MongoDB: values.MongoDB,
|
MongoDB: values.MongoDB,
|
||||||
});
|
}, cb)
|
||||||
if(res.status == "OK") {
|
}));
|
||||||
if(values.DBMode === "DisableUserManagement") {
|
|
||||||
setDatabaseEnable(false);
|
|
||||||
}
|
|
||||||
setStatus({ success: true });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setStatus({ success: false });
|
|
||||||
setErrors({ submit: error.message });
|
|
||||||
setSubmitting(false);
|
|
||||||
}
|
|
||||||
}}>
|
}}>
|
||||||
{(formik) => (
|
{(formik) => (
|
||||||
<form noValidate onSubmit={formik.handleSubmit}>
|
<form noValidate onSubmit={formik.handleSubmit}>
|
||||||
|
<LogsInModal
|
||||||
|
request={pullRequest}
|
||||||
|
title="Installing Database..."
|
||||||
|
OnSuccess={() => {
|
||||||
|
if(formik.values.DBMode === "DisableUserManagement") {
|
||||||
|
setDatabaseEnable(false);
|
||||||
|
}
|
||||||
|
formik.setStatus({ success: true });
|
||||||
|
formik.setSubmitting(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Stack item xs={12} spacing={2}>
|
<Stack item xs={12} spacing={2}>
|
||||||
<CosmosSelect
|
<CosmosSelect
|
||||||
name="DBMode"
|
name="DBMode"
|
||||||
|
|
|
@ -16,8 +16,7 @@ const GetActions = ({
|
||||||
const isMiniMobile = useMediaQuery((theme) => theme.breakpoints.down('xsm'));
|
const isMiniMobile = useMediaQuery((theme) => theme.breakpoints.down('xsm'));
|
||||||
const [pullRequest, setPullRequest] = React.useState(null);
|
const [pullRequest, setPullRequest] = React.useState(null);
|
||||||
const [isUpdating, setIsUpdating] = React.useState(false);
|
const [isUpdating, setIsUpdating] = React.useState(false);
|
||||||
console.log(isMiniMobile)
|
|
||||||
|
|
||||||
const doTo = (action) => {
|
const doTo = (action) => {
|
||||||
setIsUpdating(true);
|
setIsUpdating(true);
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,30 @@ const DockerComposeImport = ({refresh}) => {
|
||||||
if(doc.services[key].user) {
|
if(doc.services[key].user) {
|
||||||
doc.services[key].user = '' + doc.services[key].user;
|
doc.services[key].user = '' + doc.services[key].user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convert labels:
|
||||||
|
if(doc.services[key].labels) {
|
||||||
|
if(Array.isArray(doc.services[key].labels)) {
|
||||||
|
let labels = {};
|
||||||
|
doc.services[key].labels.forEach((label) => {
|
||||||
|
const [key, value] = label.split('=');
|
||||||
|
labels[''+key] = ''+value;
|
||||||
|
});
|
||||||
|
doc.services[key].labels = labels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// convert network
|
||||||
|
if(doc.services[key].networks) {
|
||||||
|
if(Array.isArray(doc.services[key].networks)) {
|
||||||
|
let networks = {};
|
||||||
|
doc.services[key].networks.forEach((network) => {
|
||||||
|
networks[''+network] = {};
|
||||||
|
});
|
||||||
|
doc.services[key].networks = networks;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,7 @@ const NetworkContainerSetup = ({ config, containerInfo, refresh, newContainer, O
|
||||||
<div style={{ maxWidth: '1000px', width: '100%', margin: '', position: 'relative' }}>
|
<div style={{ maxWidth: '1000px', width: '100%', margin: '', position: 'relative' }}>
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialValues={{
|
||||||
|
networkMode: containerInfo.HostConfig.NetworkMode,
|
||||||
ports: Object.keys(containerInfo.NetworkSettings.Ports).map((port) => {
|
ports: Object.keys(containerInfo.NetworkSettings.Ports).map((port) => {
|
||||||
return {
|
return {
|
||||||
port: port.split('/')[0],
|
port: port.split('/')[0],
|
||||||
|
@ -237,6 +238,14 @@ const NetworkContainerSetup = ({ config, containerInfo, refresh, newContainer, O
|
||||||
</MainCard>
|
</MainCard>
|
||||||
<MainCard title={'Networks'}>
|
<MainCard title={'Networks'}>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
|
|
||||||
|
<CosmosInputText
|
||||||
|
label="Network Mode"
|
||||||
|
name="networkMode"
|
||||||
|
placeholder={'default'}
|
||||||
|
formik={formik}
|
||||||
|
/>
|
||||||
|
|
||||||
{networks && <Stack spacing={2}>
|
{networks && <Stack spacing={2}>
|
||||||
{Object.keys(containerInfo.NetworkSettings.Networks).map((networkName) => {
|
{Object.keys(containerInfo.NetworkSettings.Networks).map((networkName) => {
|
||||||
const network = networks.find((n) => n.Name === networkName);
|
const network = networks.find((n) => n.Name === networkName);
|
||||||
|
|
|
@ -99,7 +99,7 @@ const NewDockerServiceForm = () => {
|
||||||
variant="contained"
|
variant="contained"
|
||||||
fullWidth
|
fullWidth
|
||||||
endIcon={<ArrowRightOutlined />}
|
endIcon={<ArrowRightOutlined />}
|
||||||
disabled={currentTab === 4}
|
disabled={(currentTab === 4) || containerInfo.Name === '' || containerInfo.Config.Image === ''}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentTab(currentTab + 1);
|
setCurrentTab(currentTab + 1);
|
||||||
setMaxTab(Math.max(currentTab + 1, maxTab));
|
setMaxTab(Math.max(currentTab + 1, maxTab));
|
||||||
|
|
|
@ -108,8 +108,8 @@ const ContainerOverview = ({ containerInfo, config, refresh }) => {
|
||||||
)}
|
)}
|
||||||
<strong><ContainerOutlined /> Image</strong>
|
<strong><ContainerOutlined /> Image</strong>
|
||||||
<div style={info}>{Image}</div>
|
<div style={info}>{Image}</div>
|
||||||
<strong><DesktopOutlined /> Name</strong>
|
<strong><DesktopOutlined /> ID</strong>
|
||||||
<div style={info}>{Name}</div>
|
<div style={info}>{containerInfo.Id}</div>
|
||||||
<strong><InfoCircleOutlined /> IP Address</strong>
|
<strong><InfoCircleOutlined /> IP Address</strong>
|
||||||
<div style={info}>{IPAddress}</div>
|
<div style={info}>{IPAddress}</div>
|
||||||
<strong>
|
<strong>
|
||||||
|
|
|
@ -61,6 +61,9 @@ const DockerContainerSetup = ({config, containerInfo, OnChange, refresh, newCont
|
||||||
if (!values.image) {
|
if (!values.image) {
|
||||||
errors.image = 'Required';
|
errors.image = 'Required';
|
||||||
}
|
}
|
||||||
|
if (!values.name && newContainer) {
|
||||||
|
errors.name = 'Required';
|
||||||
|
}
|
||||||
// env keys and labels key mustbe unique
|
// env keys and labels key mustbe unique
|
||||||
const envKeys = values.envVars.map((envVar) => envVar.key);
|
const envKeys = values.envVars.map((envVar) => envVar.key);
|
||||||
const labelKeys = values.labels.map((label) => label.key);
|
const labelKeys = values.labels.map((label) => label.key);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// material-ui
|
// material-ui
|
||||||
import { AppstoreAddOutlined, CloseSquareOutlined, DeleteOutlined, PauseCircleOutlined, PlaySquareOutlined, PlusCircleOutlined, ReloadOutlined, RollbackOutlined, SearchOutlined, SettingOutlined, StopOutlined, UpCircleOutlined, UpSquareFilled } from '@ant-design/icons';
|
import { AlertFilled, AppstoreAddOutlined, CloseSquareOutlined, DeleteOutlined, PauseCircleOutlined, PlaySquareOutlined, PlusCircleOutlined, ReloadOutlined, RollbackOutlined, SearchOutlined, SettingOutlined, StopOutlined, UpCircleOutlined, UpSquareFilled, WarningFilled } from '@ant-design/icons';
|
||||||
import { Alert, Badge, Button, Card, Checkbox, Chip, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, IconButton, Input, InputAdornment, TextField, Tooltip, Typography } from '@mui/material';
|
import { Alert, Badge, Button, Card, Checkbox, Chip, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, IconButton, Input, InputAdornment, TextField, Tooltip, Typography } from '@mui/material';
|
||||||
import Grid2 from '@mui/material/Unstable_Grid2/Grid2';
|
import Grid2 from '@mui/material/Unstable_Grid2/Grid2';
|
||||||
import { Stack } from '@mui/system';
|
import { Stack } from '@mui/system';
|
||||||
|
@ -18,6 +18,7 @@ import ExposeModal from './exposeModal';
|
||||||
import GetActions from './actionBar';
|
import GetActions from './actionBar';
|
||||||
import ResponsiveButton from '../../components/responseiveButton';
|
import ResponsiveButton from '../../components/responseiveButton';
|
||||||
import DockerComposeImport from './containers/docker-compose';
|
import DockerComposeImport from './containers/docker-compose';
|
||||||
|
import { ContainerNetworkWarning } from '../../components/containers';
|
||||||
|
|
||||||
const Item = styled(Paper)(({ theme }) => ({
|
const Item = styled(Paper)(({ theme }) => ({
|
||||||
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
|
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
|
||||||
|
@ -185,7 +186,7 @@ const ServeApps = () => {
|
||||||
<img className="loading-image" alt="" src={getFirstRouteFavIcon(app)} width="40px" />
|
<img className="loading-image" alt="" src={getFirstRouteFavIcon(app)} width="40px" />
|
||||||
<Stack direction="column" spacing={0} alignItems="flex-start" style={{height: '40px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'no-wrap'}}>
|
<Stack direction="column" spacing={0} alignItems="flex-start" style={{height: '40px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'no-wrap'}}>
|
||||||
<Typography variant="h5" color="text.secondary">
|
<Typography variant="h5" color="text.secondary">
|
||||||
{app.Names[0].replace('/', '')}
|
{app.Names[0].replace('/', '')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography color="text.secondary" style={{fontSize: '80%', whiteSpace: 'nowrap', overflow: 'hidden', maxWidth: '100%', textOverflow: 'ellipsis'}}>
|
<Typography color="text.secondary" style={{fontSize: '80%', whiteSpace: 'nowrap', overflow: 'hidden', maxWidth: '100%', textOverflow: 'ellipsis'}}>
|
||||||
{app.Image}
|
{app.Image}
|
||||||
|
@ -251,7 +252,7 @@ const ServeApps = () => {
|
||||||
refreshServeApps();
|
refreshServeApps();
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
/> Force Secure Network
|
/> Force Secure Network <ContainerNetworkWarning container={app} />
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack style={{ fontSize: '80%' }} direction={"row"} alignItems="center">
|
<Stack style={{ fontSize: '80%' }} direction={"row"} alignItems="center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "cosmos-server",
|
"name": "cosmos-server",
|
||||||
"version": "0.5.4",
|
"version": "0.5.5",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "test-server.js",
|
"main": "test-server.js",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
@ -39,7 +39,7 @@ func AutoUpdateContainerRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
utils.Log("API: Set Auto Update "+status+" : " + containerName)
|
utils.Log("API: Set Auto Update "+status+" : " + containerName)
|
||||||
|
|
||||||
_, errEdit := EditContainer(container.ID, container)
|
_, errEdit := EditContainer(container.ID, container, false)
|
||||||
if errEdit != nil {
|
if errEdit != nil {
|
||||||
utils.Error("AutoUpdateContainer Edit", errEdit)
|
utils.Error("AutoUpdateContainer Edit", errEdit)
|
||||||
utils.HTTPError(w, "Internal server error: " + errEdit.Error(), http.StatusInternalServerError, "DS003")
|
utils.HTTPError(w, "Internal server error: " + errEdit.Error(), http.StatusInternalServerError, "DS003")
|
||||||
|
|
|
@ -167,377 +167,27 @@ func CreateServiceRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Method == "POST" {
|
if req.Method == "POST" {
|
||||||
// Enable streaming of response by setting appropriate headers
|
|
||||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
w.Header().Set("Transfer-Encoding", "chunked")
|
w.Header().Set("Transfer-Encoding", "chunked")
|
||||||
|
|
||||||
flusher, ok := w.(http.Flusher)
|
flusher, ok := w.(http.Flusher)
|
||||||
if !ok {
|
if !ok {
|
||||||
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
|
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.ConfigLock.Lock()
|
|
||||||
defer utils.ConfigLock.Unlock()
|
|
||||||
|
|
||||||
utils.Log("Starting creation of new service...")
|
|
||||||
fmt.Fprintf(w, "Starting creation of new service...\n")
|
|
||||||
flusher.Flush()
|
|
||||||
|
|
||||||
config := utils.ReadConfigFromFile()
|
|
||||||
configRoutes := config.HTTPConfig.ProxyConfig.Routes
|
|
||||||
|
|
||||||
decoder := json.NewDecoder(req.Body)
|
decoder := json.NewDecoder(req.Body)
|
||||||
var serviceRequest DockerServiceCreateRequest
|
var serviceRequest DockerServiceCreateRequest
|
||||||
err := decoder.Decode(&serviceRequest)
|
err := decoder.Decode(&serviceRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("CreateService - decode - ", err)
|
utils.Error("CreateService - decode - ", err)
|
||||||
utils.HTTPError(w, "Bad request: "+err.Error(), http.StatusBadRequest, "DS003")
|
fmt.Fprintf(w, "[OPERATION FAILED] Bad request: "+err.Error(), http.StatusBadRequest, "DS003")
|
||||||
|
flusher.Flush()
|
||||||
|
utils.HTTPError(w, "Bad request: " + err.Error(), http.StatusBadRequest, "DS003")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var rollbackActions []DockerServiceCreateRollback
|
CreateService(w, req, serviceRequest)
|
||||||
|
|
||||||
// Create networks
|
|
||||||
for networkToCreateName, networkToCreate := range serviceRequest.Networks {
|
|
||||||
utils.Log(fmt.Sprintf("Creating network %s...", networkToCreateName))
|
|
||||||
fmt.Fprintf(w, "Creating network %s...\n", networkToCreateName)
|
|
||||||
flusher.Flush()
|
|
||||||
|
|
||||||
// check if network already exists
|
|
||||||
_, err = DockerClient.NetworkInspect(DockerContext, networkToCreateName, doctype.NetworkInspectOptions{})
|
|
||||||
if err == nil {
|
|
||||||
utils.Error("CreateService: Network", err)
|
|
||||||
fmt.Fprintf(w, "[ERROR] Network %s already exists\n", networkToCreateName)
|
|
||||||
flusher.Flush()
|
|
||||||
Rollback(rollbackActions, w, flusher)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ipamConfig := make([]network.IPAMConfig, len(networkToCreate.IPAM.Config))
|
|
||||||
if networkToCreate.IPAM.Config != nil {
|
|
||||||
for i, config := range networkToCreate.IPAM.Config {
|
|
||||||
ipamConfig[i] = network.IPAMConfig{
|
|
||||||
Subnet: config.Subnet,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = DockerClient.NetworkCreate(DockerContext, networkToCreateName, doctype.NetworkCreate{
|
|
||||||
Driver: networkToCreate.Driver,
|
|
||||||
Attachable: networkToCreate.Attachable,
|
|
||||||
Internal: networkToCreate.Internal,
|
|
||||||
EnableIPv6: networkToCreate.EnableIPv6,
|
|
||||||
IPAM: &network.IPAM{
|
|
||||||
Driver: networkToCreate.IPAM.Driver,
|
|
||||||
Config: ipamConfig,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
utils.Error("CreateService: Rolling back changes because of -- Network", err)
|
|
||||||
fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Network creation error: "+err.Error())
|
|
||||||
flusher.Flush()
|
|
||||||
Rollback(rollbackActions, w, flusher)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rollbackActions = append(rollbackActions, DockerServiceCreateRollback{
|
|
||||||
Action: "remove",
|
|
||||||
Type: "network",
|
|
||||||
Name: networkToCreateName,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Write a response to the client
|
|
||||||
utils.Log(fmt.Sprintf("Network %s created", networkToCreateName))
|
|
||||||
fmt.Fprintf(w, "Network %s created\n", networkToCreateName)
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create volumes
|
|
||||||
for _, volume := range serviceRequest.Volumes {
|
|
||||||
utils.Log(fmt.Sprintf("Creating volume %s...", volume.Name))
|
|
||||||
fmt.Fprintf(w, "Creating volume %s...\n", volume.Name)
|
|
||||||
flusher.Flush()
|
|
||||||
|
|
||||||
_, err = DockerClient.VolumeCreate(DockerContext, volumetype.CreateOptions{
|
|
||||||
Driver: volume.Driver,
|
|
||||||
Name: volume.Name,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
utils.Error("CreateService: Rolling back changes because of -- Volume", err)
|
|
||||||
fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Volume creation error: "+err.Error())
|
|
||||||
flusher.Flush()
|
|
||||||
Rollback(rollbackActions, w, flusher)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rollbackActions = append(rollbackActions, DockerServiceCreateRollback{
|
|
||||||
Action: "remove",
|
|
||||||
Type: "volume",
|
|
||||||
Name: volume.Name,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Write a response to the client
|
|
||||||
utils.Log(fmt.Sprintf("Volume %s created", volume.Name))
|
|
||||||
fmt.Fprintf(w, "Volume %s created\n", volume.Name)
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// pull images
|
|
||||||
for _, container := range serviceRequest.Services {
|
|
||||||
// Write a response to the client
|
|
||||||
utils.Log(fmt.Sprintf("Pulling image %s", container.Image))
|
|
||||||
fmt.Fprintf(w, "Pulling image %s\n", container.Image)
|
|
||||||
flusher.Flush()
|
|
||||||
|
|
||||||
out, err := DockerClient.ImagePull(DockerContext, container.Image, doctype.ImagePullOptions{})
|
|
||||||
if err != nil {
|
|
||||||
utils.Error("CreateService: Rolling back changes because of -- Image pull", err)
|
|
||||||
fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Image pull error: "+err.Error())
|
|
||||||
flusher.Flush()
|
|
||||||
Rollback(rollbackActions, w, flusher)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
|
|
||||||
// wait for image pull to finish
|
|
||||||
scanner := bufio.NewScanner(out)
|
|
||||||
for scanner.Scan() {
|
|
||||||
fmt.Fprintf(w, "%s\n", scanner.Text())
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write a response to the client
|
|
||||||
utils.Log(fmt.Sprintf("Image %s pulled", container.Image))
|
|
||||||
fmt.Fprintf(w, "Image %s pulled\n", container.Image)
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create containers
|
|
||||||
for _, container := range serviceRequest.Services {
|
|
||||||
utils.Log(fmt.Sprintf("Creating container %s...", container.Name))
|
|
||||||
fmt.Fprintf(w, "Creating container %s...\n", container.Name)
|
|
||||||
flusher.Flush()
|
|
||||||
|
|
||||||
containerConfig := &conttype.Config{
|
|
||||||
Image: container.Image,
|
|
||||||
Env: container.Environment,
|
|
||||||
Labels: container.Labels,
|
|
||||||
ExposedPorts: nat.PortSet{},
|
|
||||||
WorkingDir: container.WorkingDir,
|
|
||||||
User: container.User,
|
|
||||||
Hostname: container.Hostname,
|
|
||||||
Domainname: container.Domainname,
|
|
||||||
MacAddress: container.MacAddress,
|
|
||||||
StopSignal: container.StopSignal,
|
|
||||||
StopTimeout: &container.StopGracePeriod,
|
|
||||||
Tty: container.Tty,
|
|
||||||
OpenStdin: container.StdinOpen,
|
|
||||||
}
|
|
||||||
|
|
||||||
if container.Command != "" {
|
|
||||||
containerConfig.Cmd = strings.Fields(container.Command)
|
|
||||||
}
|
|
||||||
|
|
||||||
if container.Entrypoint != "" {
|
|
||||||
containerConfig.Entrypoint = strslice.StrSlice(strings.Fields(container.Entrypoint))
|
|
||||||
}
|
|
||||||
|
|
||||||
// For Expose / Ports
|
|
||||||
for _, expose := range container.Expose {
|
|
||||||
exposePort := nat.Port(expose)
|
|
||||||
containerConfig.ExposedPorts[exposePort] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
PortBindings := nat.PortMap{}
|
|
||||||
|
|
||||||
for _, port := range container.Ports {
|
|
||||||
portContainer := strings.Split(port, ":")[0]
|
|
||||||
portHost := strings.Split(port, ":")[1]
|
|
||||||
|
|
||||||
containerConfig.ExposedPorts[nat.Port(portContainer)] = struct{}{}
|
|
||||||
PortBindings[nat.Port(portContainer)] = []nat.PortBinding{
|
|
||||||
{
|
|
||||||
HostIP: "",
|
|
||||||
HostPort: portHost,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create missing folders for bind mounts
|
|
||||||
for _, newmount := range container.Volumes {
|
|
||||||
if newmount.Type == mount.TypeBind {
|
|
||||||
if _, err := os.Stat(newmount.Source); os.IsNotExist(err) {
|
|
||||||
err := os.MkdirAll(newmount.Source, 0755)
|
|
||||||
if err != nil {
|
|
||||||
utils.Error("CreateService: Unable to create directory for bind mount", err)
|
|
||||||
fmt.Fprintf(w, "[ERROR] Unable to create directory for bind mount: "+err.Error())
|
|
||||||
flusher.Flush()
|
|
||||||
Rollback(rollbackActions, w, flusher)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if container.User != "" {
|
|
||||||
// Change the ownership of the directory to the container.User
|
|
||||||
userInfo, err := user.Lookup(container.User)
|
|
||||||
if err != nil {
|
|
||||||
utils.Error("CreateService: Unable to lookup user", err)
|
|
||||||
fmt.Fprintf(w, "[ERROR] Unable to lookup user " + container.User + "." +err.Error())
|
|
||||||
flusher.Flush()
|
|
||||||
} else {
|
|
||||||
uid, _ := strconv.Atoi(userInfo.Uid)
|
|
||||||
gid, _ := strconv.Atoi(userInfo.Gid)
|
|
||||||
err = os.Chown(newmount.Source, uid, gid)
|
|
||||||
if err != nil {
|
|
||||||
utils.Error("CreateService: Unable to change ownership of directory", err)
|
|
||||||
fmt.Fprintf(w, "[ERROR] Unable to change ownership of directory: "+err.Error())
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hostConfig := &conttype.HostConfig{
|
|
||||||
PortBindings: PortBindings,
|
|
||||||
Mounts: container.Volumes,
|
|
||||||
RestartPolicy: conttype.RestartPolicy{
|
|
||||||
Name: container.RestartPolicy,
|
|
||||||
},
|
|
||||||
Privileged: container.Privileged,
|
|
||||||
NetworkMode: conttype.NetworkMode(container.NetworkMode),
|
|
||||||
DNS: container.DNS,
|
|
||||||
DNSSearch: container.DNSSearch,
|
|
||||||
ExtraHosts: container.ExtraHosts,
|
|
||||||
Links: container.Links,
|
|
||||||
SecurityOpt: container.SecurityOpt,
|
|
||||||
StorageOpt: container.StorageOpt,
|
|
||||||
Sysctls: container.Sysctls,
|
|
||||||
Isolation: conttype.Isolation(container.Isolation),
|
|
||||||
CapAdd: container.CapAdd,
|
|
||||||
CapDrop: container.CapDrop,
|
|
||||||
}
|
|
||||||
|
|
||||||
// For Healthcheck
|
|
||||||
if len(container.HealthCheck.Test) > 0 {
|
|
||||||
containerConfig.Healthcheck = &conttype.HealthConfig{
|
|
||||||
Test: container.HealthCheck.Test,
|
|
||||||
Interval: time.Duration(container.HealthCheck.Interval) * time.Second,
|
|
||||||
Timeout: time.Duration(container.HealthCheck.Timeout) * time.Second,
|
|
||||||
StartPeriod: time.Duration(container.HealthCheck.StartPeriod) * time.Second,
|
|
||||||
Retries: container.HealthCheck.Retries,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For Devices
|
|
||||||
devices := []conttype.DeviceMapping{}
|
|
||||||
|
|
||||||
for _, device := range container.Devices {
|
|
||||||
deviceSplit := strings.Split(device, ":")
|
|
||||||
devices = append(devices, conttype.DeviceMapping{
|
|
||||||
PathOnHost: deviceSplit[0],
|
|
||||||
PathInContainer: deviceSplit[1],
|
|
||||||
CgroupPermissions: "rwm", // This can be "r", "w", "m", or any combination
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
hostConfig.Devices = devices
|
|
||||||
|
|
||||||
|
|
||||||
networkingConfig := &network.NetworkingConfig{
|
|
||||||
EndpointsConfig: make(map[string]*network.EndpointSettings),
|
|
||||||
}
|
|
||||||
|
|
||||||
for netName, netConfig := range container.Networks {
|
|
||||||
networkingConfig.EndpointsConfig[netName] = &network.EndpointSettings{
|
|
||||||
Aliases: netConfig.Aliases,
|
|
||||||
IPAddress: netConfig.IPV4Address,
|
|
||||||
GlobalIPv6Address: netConfig.IPV6Address,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = DockerClient.ContainerCreate(DockerContext, containerConfig, hostConfig, networkingConfig, nil, container.Name)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
utils.Error("CreateService: Rolling back changes because of -- Container", err)
|
|
||||||
fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Container creation error: "+err.Error())
|
|
||||||
flusher.Flush()
|
|
||||||
Rollback(rollbackActions, w, flusher)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rollbackActions = append(rollbackActions, DockerServiceCreateRollback{
|
|
||||||
Action: "remove",
|
|
||||||
Type: "container",
|
|
||||||
Name: container.Name,
|
|
||||||
})
|
|
||||||
|
|
||||||
// add routes
|
|
||||||
for _, route := range container.Routes {
|
|
||||||
// check if route already exists
|
|
||||||
exists := false
|
|
||||||
for _, configRoute := range configRoutes {
|
|
||||||
if configRoute.Name == route.Name {
|
|
||||||
exists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
configRoutes = append([]utils.ProxyRouteConfig{(utils.ProxyRouteConfig)(route)}, configRoutes...)
|
|
||||||
} else {
|
|
||||||
utils.Error("CreateService: Rolling back changes because of -- Route already exist", nil)
|
|
||||||
fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Route already exist")
|
|
||||||
flusher.Flush()
|
|
||||||
Rollback(rollbackActions, w, flusher)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write a response to the client
|
|
||||||
utils.Log(fmt.Sprintf("Container %s created", container.Name))
|
|
||||||
fmt.Fprintf(w, "Container %s created\n", container.Name)
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-order containers dpeneding on depends_on
|
|
||||||
startOrder, err := ReOrderServices(serviceRequest.Services)
|
|
||||||
if err != nil {
|
|
||||||
utils.Error("CreateService: Rolling back changes because of -- Container", err)
|
|
||||||
fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Container creation error: "+err.Error())
|
|
||||||
flusher.Flush()
|
|
||||||
Rollback(rollbackActions, w, flusher)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start all the newly created containers
|
|
||||||
for _, container := range startOrder {
|
|
||||||
err = DockerClient.ContainerStart(DockerContext, container.Name, doctype.ContainerStartOptions{})
|
|
||||||
if err != nil {
|
|
||||||
utils.Error("CreateService: Start Container", err)
|
|
||||||
fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Container start error: "+err.Error())
|
|
||||||
flusher.Flush()
|
|
||||||
Rollback(rollbackActions, w, flusher)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write a response to the client
|
|
||||||
utils.Log(fmt.Sprintf("Container %s started", container.Name))
|
|
||||||
fmt.Fprintf(w, "Container %s started\n", container.Name)
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the route configs
|
|
||||||
config.HTTPConfig.ProxyConfig.Routes = configRoutes
|
|
||||||
utils.SaveConfigTofile(config)
|
|
||||||
utils.NeedsRestart = true
|
|
||||||
|
|
||||||
// After all operations
|
|
||||||
utils.Log("CreateService: Operation succeeded. SERVICE STARTED")
|
|
||||||
fmt.Fprintf(w, "[OPERATION SUCCEEDED]. SERVICE STARTED\n")
|
|
||||||
flusher.Flush()
|
|
||||||
} else {
|
} else {
|
||||||
utils.Error("CreateService: Method not allowed" + req.Method, nil)
|
utils.Error("CreateService: Method not allowed" + req.Method, nil)
|
||||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||||
|
@ -545,6 +195,375 @@ func CreateServiceRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func CreateService(w http.ResponseWriter, req *http.Request, serviceRequest DockerServiceCreateRequest) error {
|
||||||
|
// Enable streaming of response by setting appropriate headers
|
||||||
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
w.Header().Set("Transfer-Encoding", "chunked")
|
||||||
|
|
||||||
|
flusher, ok := w.(http.Flusher)
|
||||||
|
if !ok {
|
||||||
|
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
|
||||||
|
return errors.New("Streaming unsupported!")
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.ConfigLock.Lock()
|
||||||
|
defer utils.ConfigLock.Unlock()
|
||||||
|
|
||||||
|
utils.Log("Starting creation of new service...")
|
||||||
|
fmt.Fprintf(w, "Starting creation of new service...\n")
|
||||||
|
flusher.Flush()
|
||||||
|
|
||||||
|
config := utils.ReadConfigFromFile()
|
||||||
|
configRoutes := config.HTTPConfig.ProxyConfig.Routes
|
||||||
|
|
||||||
|
var rollbackActions []DockerServiceCreateRollback
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Create networks
|
||||||
|
for networkToCreateName, networkToCreate := range serviceRequest.Networks {
|
||||||
|
utils.Log(fmt.Sprintf("Creating network %s...", networkToCreateName))
|
||||||
|
fmt.Fprintf(w, "Creating network %s...\n", networkToCreateName)
|
||||||
|
flusher.Flush()
|
||||||
|
|
||||||
|
// check if network already exists
|
||||||
|
_, err = DockerClient.NetworkInspect(DockerContext, networkToCreateName, doctype.NetworkInspectOptions{})
|
||||||
|
if err == nil {
|
||||||
|
utils.Error("CreateService: Network", err)
|
||||||
|
fmt.Fprintf(w, "[ERROR] Network %s already exists\n", networkToCreateName)
|
||||||
|
flusher.Flush()
|
||||||
|
Rollback(rollbackActions, w, flusher)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ipamConfig := make([]network.IPAMConfig, len(networkToCreate.IPAM.Config))
|
||||||
|
if networkToCreate.IPAM.Config != nil {
|
||||||
|
for i, config := range networkToCreate.IPAM.Config {
|
||||||
|
ipamConfig[i] = network.IPAMConfig{
|
||||||
|
Subnet: config.Subnet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = DockerClient.NetworkCreate(DockerContext, networkToCreateName, doctype.NetworkCreate{
|
||||||
|
Driver: networkToCreate.Driver,
|
||||||
|
Attachable: networkToCreate.Attachable,
|
||||||
|
Internal: networkToCreate.Internal,
|
||||||
|
EnableIPv6: networkToCreate.EnableIPv6,
|
||||||
|
IPAM: &network.IPAM{
|
||||||
|
Driver: networkToCreate.IPAM.Driver,
|
||||||
|
Config: ipamConfig,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("CreateService: Rolling back changes because of -- Network", err)
|
||||||
|
fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Network creation error: "+err.Error())
|
||||||
|
flusher.Flush()
|
||||||
|
Rollback(rollbackActions, w, flusher)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rollbackActions = append(rollbackActions, DockerServiceCreateRollback{
|
||||||
|
Action: "remove",
|
||||||
|
Type: "network",
|
||||||
|
Name: networkToCreateName,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Write a response to the client
|
||||||
|
utils.Log(fmt.Sprintf("Network %s created", networkToCreateName))
|
||||||
|
fmt.Fprintf(w, "Network %s created\n", networkToCreateName)
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create volumes
|
||||||
|
for _, volume := range serviceRequest.Volumes {
|
||||||
|
utils.Log(fmt.Sprintf("Creating volume %s...", volume.Name))
|
||||||
|
fmt.Fprintf(w, "Creating volume %s...\n", volume.Name)
|
||||||
|
flusher.Flush()
|
||||||
|
|
||||||
|
_, err = DockerClient.VolumeCreate(DockerContext, volumetype.CreateOptions{
|
||||||
|
Driver: volume.Driver,
|
||||||
|
Name: volume.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("CreateService: Rolling back changes because of -- Volume", err)
|
||||||
|
fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Volume creation error: "+err.Error())
|
||||||
|
flusher.Flush()
|
||||||
|
Rollback(rollbackActions, w, flusher)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rollbackActions = append(rollbackActions, DockerServiceCreateRollback{
|
||||||
|
Action: "remove",
|
||||||
|
Type: "volume",
|
||||||
|
Name: volume.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Write a response to the client
|
||||||
|
utils.Log(fmt.Sprintf("Volume %s created", volume.Name))
|
||||||
|
fmt.Fprintf(w, "Volume %s created\n", volume.Name)
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pull images
|
||||||
|
for _, container := range serviceRequest.Services {
|
||||||
|
// Write a response to the client
|
||||||
|
utils.Log(fmt.Sprintf("Pulling image %s", container.Image))
|
||||||
|
fmt.Fprintf(w, "Pulling image %s\n", container.Image)
|
||||||
|
flusher.Flush()
|
||||||
|
|
||||||
|
out, err := DockerClient.ImagePull(DockerContext, container.Image, doctype.ImagePullOptions{})
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("CreateService: Rolling back changes because of -- Image pull", err)
|
||||||
|
fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Image pull error: "+err.Error())
|
||||||
|
flusher.Flush()
|
||||||
|
Rollback(rollbackActions, w, flusher)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
// wait for image pull to finish
|
||||||
|
scanner := bufio.NewScanner(out)
|
||||||
|
for scanner.Scan() {
|
||||||
|
fmt.Fprintf(w, "%s\n", scanner.Text())
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a response to the client
|
||||||
|
utils.Log(fmt.Sprintf("Image %s pulled", container.Image))
|
||||||
|
fmt.Fprintf(w, "Image %s pulled\n", container.Image)
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create containers
|
||||||
|
for _, container := range serviceRequest.Services {
|
||||||
|
utils.Log(fmt.Sprintf("Creating container %s...", container.Name))
|
||||||
|
fmt.Fprintf(w, "Creating container %s...\n", container.Name)
|
||||||
|
flusher.Flush()
|
||||||
|
|
||||||
|
containerConfig := &conttype.Config{
|
||||||
|
Image: container.Image,
|
||||||
|
Env: container.Environment,
|
||||||
|
Labels: container.Labels,
|
||||||
|
ExposedPorts: nat.PortSet{},
|
||||||
|
WorkingDir: container.WorkingDir,
|
||||||
|
User: container.User,
|
||||||
|
Hostname: container.Hostname,
|
||||||
|
Domainname: container.Domainname,
|
||||||
|
MacAddress: container.MacAddress,
|
||||||
|
StopSignal: container.StopSignal,
|
||||||
|
StopTimeout: &container.StopGracePeriod,
|
||||||
|
Tty: container.Tty,
|
||||||
|
OpenStdin: container.StdinOpen,
|
||||||
|
}
|
||||||
|
|
||||||
|
if container.Command != "" {
|
||||||
|
containerConfig.Cmd = strings.Fields(container.Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
if container.Entrypoint != "" {
|
||||||
|
containerConfig.Entrypoint = strslice.StrSlice(strings.Fields(container.Entrypoint))
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Expose / Ports
|
||||||
|
for _, expose := range container.Expose {
|
||||||
|
exposePort := nat.Port(expose)
|
||||||
|
containerConfig.ExposedPorts[exposePort] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
PortBindings := nat.PortMap{}
|
||||||
|
|
||||||
|
for _, port := range container.Ports {
|
||||||
|
portContainer := strings.Split(port, ":")[0]
|
||||||
|
portHost := strings.Split(port, ":")[1]
|
||||||
|
|
||||||
|
containerConfig.ExposedPorts[nat.Port(portContainer)] = struct{}{}
|
||||||
|
PortBindings[nat.Port(portContainer)] = []nat.PortBinding{
|
||||||
|
{
|
||||||
|
HostIP: "",
|
||||||
|
HostPort: portHost,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create missing folders for bind mounts
|
||||||
|
for _, newmount := range container.Volumes {
|
||||||
|
if newmount.Type == mount.TypeBind {
|
||||||
|
if _, err := os.Stat(newmount.Source); os.IsNotExist(err) {
|
||||||
|
err := os.MkdirAll(newmount.Source, 0755)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("CreateService: Unable to create directory for bind mount", err)
|
||||||
|
fmt.Fprintf(w, "[ERROR] Unable to create directory for bind mount: "+err.Error())
|
||||||
|
flusher.Flush()
|
||||||
|
Rollback(rollbackActions, w, flusher)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if container.User != "" {
|
||||||
|
// Change the ownership of the directory to the container.User
|
||||||
|
userInfo, err := user.Lookup(container.User)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("CreateService: Unable to lookup user", err)
|
||||||
|
fmt.Fprintf(w, "[ERROR] Unable to lookup user " + container.User + "." +err.Error())
|
||||||
|
flusher.Flush()
|
||||||
|
} else {
|
||||||
|
uid, _ := strconv.Atoi(userInfo.Uid)
|
||||||
|
gid, _ := strconv.Atoi(userInfo.Gid)
|
||||||
|
err = os.Chown(newmount.Source, uid, gid)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("CreateService: Unable to change ownership of directory", err)
|
||||||
|
fmt.Fprintf(w, "[ERROR] Unable to change ownership of directory: "+err.Error())
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig := &conttype.HostConfig{
|
||||||
|
PortBindings: PortBindings,
|
||||||
|
Mounts: container.Volumes,
|
||||||
|
RestartPolicy: conttype.RestartPolicy{
|
||||||
|
Name: container.RestartPolicy,
|
||||||
|
},
|
||||||
|
Privileged: container.Privileged,
|
||||||
|
NetworkMode: conttype.NetworkMode(container.NetworkMode),
|
||||||
|
DNS: container.DNS,
|
||||||
|
DNSSearch: container.DNSSearch,
|
||||||
|
ExtraHosts: container.ExtraHosts,
|
||||||
|
Links: container.Links,
|
||||||
|
SecurityOpt: container.SecurityOpt,
|
||||||
|
StorageOpt: container.StorageOpt,
|
||||||
|
Sysctls: container.Sysctls,
|
||||||
|
Isolation: conttype.Isolation(container.Isolation),
|
||||||
|
CapAdd: container.CapAdd,
|
||||||
|
CapDrop: container.CapDrop,
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Healthcheck
|
||||||
|
if len(container.HealthCheck.Test) > 0 {
|
||||||
|
containerConfig.Healthcheck = &conttype.HealthConfig{
|
||||||
|
Test: container.HealthCheck.Test,
|
||||||
|
Interval: time.Duration(container.HealthCheck.Interval) * time.Second,
|
||||||
|
Timeout: time.Duration(container.HealthCheck.Timeout) * time.Second,
|
||||||
|
StartPeriod: time.Duration(container.HealthCheck.StartPeriod) * time.Second,
|
||||||
|
Retries: container.HealthCheck.Retries,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Devices
|
||||||
|
devices := []conttype.DeviceMapping{}
|
||||||
|
|
||||||
|
for _, device := range container.Devices {
|
||||||
|
deviceSplit := strings.Split(device, ":")
|
||||||
|
devices = append(devices, conttype.DeviceMapping{
|
||||||
|
PathOnHost: deviceSplit[0],
|
||||||
|
PathInContainer: deviceSplit[1],
|
||||||
|
CgroupPermissions: "rwm", // This can be "r", "w", "m", or any combination
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.Devices = devices
|
||||||
|
|
||||||
|
|
||||||
|
networkingConfig := &network.NetworkingConfig{
|
||||||
|
EndpointsConfig: make(map[string]*network.EndpointSettings),
|
||||||
|
}
|
||||||
|
|
||||||
|
for netName, netConfig := range container.Networks {
|
||||||
|
networkingConfig.EndpointsConfig[netName] = &network.EndpointSettings{
|
||||||
|
Aliases: netConfig.Aliases,
|
||||||
|
IPAddress: netConfig.IPV4Address,
|
||||||
|
GlobalIPv6Address: netConfig.IPV6Address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = DockerClient.ContainerCreate(DockerContext, containerConfig, hostConfig, networkingConfig, nil, container.Name)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("CreateService: Rolling back changes because of -- Container", err)
|
||||||
|
fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Container creation error: "+err.Error())
|
||||||
|
flusher.Flush()
|
||||||
|
Rollback(rollbackActions, w, flusher)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rollbackActions = append(rollbackActions, DockerServiceCreateRollback{
|
||||||
|
Action: "remove",
|
||||||
|
Type: "container",
|
||||||
|
Name: container.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
// add routes
|
||||||
|
for _, route := range container.Routes {
|
||||||
|
// check if route already exists
|
||||||
|
exists := false
|
||||||
|
for _, configRoute := range configRoutes {
|
||||||
|
if configRoute.Name == route.Name {
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
configRoutes = append([]utils.ProxyRouteConfig{(utils.ProxyRouteConfig)(route)}, configRoutes...)
|
||||||
|
} else {
|
||||||
|
utils.Error("CreateService: Rolling back changes because of -- Route already exist", nil)
|
||||||
|
fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Route already exist")
|
||||||
|
flusher.Flush()
|
||||||
|
Rollback(rollbackActions, w, flusher)
|
||||||
|
return errors.New("Route already exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a response to the client
|
||||||
|
utils.Log(fmt.Sprintf("Container %s created", container.Name))
|
||||||
|
fmt.Fprintf(w, "Container %s created\n", container.Name)
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-order containers dpeneding on depends_on
|
||||||
|
startOrder, err := ReOrderServices(serviceRequest.Services)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("CreateService: Rolling back changes because of -- Container", err)
|
||||||
|
fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Container creation error: "+err.Error())
|
||||||
|
flusher.Flush()
|
||||||
|
Rollback(rollbackActions, w, flusher)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start all the newly created containers
|
||||||
|
for _, container := range startOrder {
|
||||||
|
err = DockerClient.ContainerStart(DockerContext, container.Name, doctype.ContainerStartOptions{})
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("CreateService: Start Container", err)
|
||||||
|
fmt.Fprintf(w, "[ERROR] Rolling back changes because of -- Container start error: "+err.Error())
|
||||||
|
flusher.Flush()
|
||||||
|
Rollback(rollbackActions, w, flusher)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a response to the client
|
||||||
|
utils.Log(fmt.Sprintf("Container %s started", container.Name))
|
||||||
|
fmt.Fprintf(w, "Container %s started\n", container.Name)
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the route configs
|
||||||
|
config.HTTPConfig.ProxyConfig.Routes = configRoutes
|
||||||
|
utils.SaveConfigTofile(config)
|
||||||
|
utils.NeedsRestart = true
|
||||||
|
|
||||||
|
// After all operations
|
||||||
|
utils.Log("CreateService: Operation succeeded. SERVICE STARTED")
|
||||||
|
fmt.Fprintf(w, "[OPERATION SUCCEEDED]. SERVICE STARTED\n")
|
||||||
|
flusher.Flush()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func ReOrderServices(serviceMap map[string]ContainerCreateRequestContainer) ([]ContainerCreateRequestContainer, error) {
|
func ReOrderServices(serviceMap map[string]ContainerCreateRequestContainer) ([]ContainerCreateRequestContainer, error) {
|
||||||
startOrder := []ContainerCreateRequestContainer{}
|
startOrder := []ContainerCreateRequestContainer{}
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ func ManageContainerRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
case "unpause":
|
case "unpause":
|
||||||
err = DockerClient.ContainerUnpause(DockerContext, container.ID)
|
err = DockerClient.ContainerUnpause(DockerContext, container.ID)
|
||||||
case "recreate":
|
case "recreate":
|
||||||
_, err = EditContainer(container.ID, container)
|
_, err = EditContainer(container.ID, container, false)
|
||||||
case "update":
|
case "update":
|
||||||
out, errPull := DockerClient.ImagePull(DockerContext, imagename, doctype.ImagePullOptions{})
|
out, errPull := DockerClient.ImagePull(DockerContext, imagename, doctype.ImagePullOptions{})
|
||||||
if errPull != nil {
|
if errPull != nil {
|
||||||
|
@ -100,7 +100,7 @@ func ManageContainerRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
utils.Log("Container Update - Image pulled " + imagename)
|
utils.Log("Container Update - Image pulled " + imagename)
|
||||||
|
|
||||||
_, err = EditContainer(container.ID, container)
|
_, err = EditContainer(container.ID, container, false)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("Container Update - EditContainer", err)
|
utils.Error("Container Update - EditContainer", err)
|
||||||
|
|
|
@ -20,7 +20,7 @@ func NewDBRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(req.Method == "GET") {
|
if(req.Method == "GET") {
|
||||||
costr, err := NewDB()
|
costr, err := NewDB(w, req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("NewDB: Error while creating new DB", err)
|
utils.Error("NewDB: Error while creating new DB", err)
|
||||||
|
|
|
@ -37,9 +37,12 @@ func SecureContainerRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
"cosmos-force-network-secured": status,
|
"cosmos-force-network-secured": status,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// change network mode to bridge in case it was set to container
|
||||||
|
container.HostConfig.NetworkMode = "bridge"
|
||||||
|
|
||||||
utils.Log("API: Set Force network secured "+status+" : " + containerName)
|
utils.Log("API: Set Force network secured "+status+" : " + containerName)
|
||||||
|
|
||||||
_, errEdit := EditContainer(container.ID, container)
|
_, errEdit := EditContainer(container.ID, container, false)
|
||||||
if errEdit != nil {
|
if errEdit != nil {
|
||||||
utils.Error("ContainerSecureEdit", errEdit)
|
utils.Error("ContainerSecureEdit", errEdit)
|
||||||
utils.HTTPError(w, "Internal server error: " + errEdit.Error(), http.StatusInternalServerError, "DS003")
|
utils.HTTPError(w, "Internal server error: " + errEdit.Error(), http.StatusInternalServerError, "DS003")
|
||||||
|
|
|
@ -23,6 +23,7 @@ type ContainerForm struct {
|
||||||
Volumes []mount.Mount `json:"Volumes"`
|
Volumes []mount.Mount `json:"Volumes"`
|
||||||
// we make this a int so that we can ignore 0
|
// we make this a int so that we can ignore 0
|
||||||
Interactive int `json:"interactive"`
|
Interactive int `json:"interactive"`
|
||||||
|
NetworkMode string `json:"networkMode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateContainerRoute(w http.ResponseWriter, req *http.Request) {
|
func UpdateContainerRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -119,8 +120,11 @@ func UpdateContainerRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
container.Config.Tty = form.Interactive == 2
|
container.Config.Tty = form.Interactive == 2
|
||||||
container.Config.OpenStdin = form.Interactive == 2
|
container.Config.OpenStdin = form.Interactive == 2
|
||||||
}
|
}
|
||||||
|
if(form.NetworkMode != "") {
|
||||||
|
container.HostConfig.NetworkMode = containerType.NetworkMode(form.NetworkMode)
|
||||||
|
}
|
||||||
|
|
||||||
_, err = EditContainer(container.ID, container)
|
_, err = EditContainer(container.ID, container, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("UpdateContainer: EditContainer", err)
|
utils.Error("UpdateContainer: EditContainer", err)
|
||||||
utils.HTTPError(w, "Internal server error: "+err.Error(), http.StatusInternalServerError, "DS004")
|
utils.HTTPError(w, "Internal server error: "+err.Error(), http.StatusInternalServerError, "DS004")
|
||||||
|
|
|
@ -31,6 +31,13 @@ func BootstrapAllContainersFromTags() []error {
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UnsecureContainer(container types.ContainerJSON) (string, error) {
|
||||||
|
RemoveLabels(container, []string{
|
||||||
|
"cosmos-force-network-secured",
|
||||||
|
});
|
||||||
|
return EditContainer(container.ID, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
func BootstrapContainerFromTags(containerID string) error {
|
func BootstrapContainerFromTags(containerID string) error {
|
||||||
errD := Connect()
|
errD := Connect()
|
||||||
if errD != nil {
|
if errD != nil {
|
||||||
|
@ -71,6 +78,12 @@ func BootstrapContainerFromTags(containerID string) error {
|
||||||
if !isCosmosCon {
|
if !isCosmosCon {
|
||||||
needsRestart, errCT = ConnectToSecureNetwork(container)
|
needsRestart, errCT = ConnectToSecureNetwork(container)
|
||||||
if errCT != nil {
|
if errCT != nil {
|
||||||
|
utils.Warn("DockerContainerBootstrapConnectToSecureNetwork -- Cannot connect to network, removing force secure")
|
||||||
|
_, errUn := UnsecureContainer(container)
|
||||||
|
if errUn != nil {
|
||||||
|
utils.Fatal("DockerContainerBootstrapUnsecureContainer -- A broken container state is preventing Cosmos from functionning. Please remove the cosmos-force-secure label from the container "+container.Name+" manually", errUn)
|
||||||
|
return errCT
|
||||||
|
}
|
||||||
return errCT
|
return errCT
|
||||||
}
|
}
|
||||||
if needsRestart {
|
if needsRestart {
|
||||||
|
@ -84,7 +97,12 @@ func BootstrapContainerFromTags(containerID string) error {
|
||||||
utils.Log(container.Name+": Disconnecting from bridge network")
|
utils.Log(container.Name+": Disconnecting from bridge network")
|
||||||
errDisc := DockerClient.NetworkDisconnect(DockerContext, "bridge", containerID, true)
|
errDisc := DockerClient.NetworkDisconnect(DockerContext, "bridge", containerID, true)
|
||||||
if errDisc != nil {
|
if errDisc != nil {
|
||||||
utils.Error("Docker Network Disconnect", errDisc)
|
utils.Warn("DockerContainerBootstrapDisconnectFromBridge -- Cannot disconnect from Bridge, removing force secure")
|
||||||
|
_, errUn := UnsecureContainer(container)
|
||||||
|
if errUn != nil {
|
||||||
|
utils.Fatal("DockerContainerBootstrapUnsecureContainer -- A broken container state is preventing Cosmos from functionning. Please remove the cosmos-force-secure label from the container "+container.Name+" manually", errUn)
|
||||||
|
return errDisc
|
||||||
|
}
|
||||||
return errDisc
|
return errDisc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +117,7 @@ func BootstrapContainerFromTags(containerID string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(needsUpdate) {
|
if(needsUpdate) {
|
||||||
_, errEdit := EditContainer(containerID, container)
|
_, errEdit := EditContainer(containerID, container, false)
|
||||||
if errEdit != nil {
|
if errEdit != nil {
|
||||||
utils.Error("Docker Boostrap, couldn't update container: ", errEdit)
|
utils.Error("Docker Boostrap, couldn't update container: ", errEdit)
|
||||||
return errEdit
|
return errEdit
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
"fmt"
|
|
||||||
"bufio"
|
"bufio"
|
||||||
"strings"
|
"strings"
|
||||||
"github.com/azukaar/cosmos-server/src/utils"
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
|
@ -82,11 +81,8 @@ func Connect() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string, error) {
|
func EditContainer(oldContainerID string, newConfig types.ContainerJSON, noLock bool) (string, error) {
|
||||||
|
if(oldContainerID != "" && !noLock) {
|
||||||
utils.Debug("VOLUMES:" + fmt.Sprintf("%v", newConfig.HostConfig.Mounts))
|
|
||||||
|
|
||||||
if(oldContainerID != "") {
|
|
||||||
// no need to re-lock if we are reverting
|
// no need to re-lock if we are reverting
|
||||||
DockerNetworkLock <- true
|
DockerNetworkLock <- true
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -99,6 +95,17 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
||||||
return "", errD
|
return "", errD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(newConfig.HostConfig.NetworkMode != "bridge" &&
|
||||||
|
newConfig.HostConfig.NetworkMode != "default" &&
|
||||||
|
newConfig.HostConfig.NetworkMode != "host" &&
|
||||||
|
newConfig.HostConfig.NetworkMode != "none") {
|
||||||
|
if(!HasLabel(newConfig, "cosmos-force-network-mode")) {
|
||||||
|
AddLabels(newConfig, map[string]string{"cosmos-force-network-mode": string(newConfig.HostConfig.NetworkMode)})
|
||||||
|
} else {
|
||||||
|
newConfig.HostConfig.NetworkMode = container.NetworkMode(GetLabel(newConfig, "cosmos-force-network-mode"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newName := newConfig.Name
|
newName := newConfig.Name
|
||||||
oldContainer := newConfig
|
oldContainer := newConfig
|
||||||
|
@ -112,8 +119,6 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
||||||
// https://godoc.org/github.com/docker/docker/api/types#ContainerJSON
|
// https://godoc.org/github.com/docker/docker/api/types#ContainerJSON
|
||||||
oldContainer, err = DockerClient.ContainerInspect(DockerContext, oldContainerID)
|
oldContainer, err = DockerClient.ContainerInspect(DockerContext, oldContainerID)
|
||||||
|
|
||||||
utils.Debug("OLD VOLUMES:" + fmt.Sprintf("%v", oldContainer.HostConfig.Mounts))
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -138,7 +143,6 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
||||||
|
|
||||||
// if no name, use the same one, that will force Docker to create a hostname if not set
|
// if no name, use the same one, that will force Docker to create a hostname if not set
|
||||||
newName = oldContainer.Name
|
newName = oldContainer.Name
|
||||||
newConfig.Config.Hostname = newName
|
|
||||||
|
|
||||||
// stop and remove container
|
// stop and remove container
|
||||||
stopError := DockerClient.ContainerStop(DockerContext, oldContainerID, container.StopOptions{})
|
stopError := DockerClient.ContainerStop(DockerContext, oldContainerID, container.StopOptions{})
|
||||||
|
@ -168,6 +172,16 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
||||||
utils.Log("EditContainer - Revert started")
|
utils.Log("EditContainer - Revert started")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only force hostname if network is bridge or default, otherwise it will fail
|
||||||
|
if newConfig.HostConfig.NetworkMode == "bridge" || newConfig.HostConfig.NetworkMode == "default" {
|
||||||
|
newConfig.Config.Hostname = newName
|
||||||
|
} else {
|
||||||
|
// if not, remove hostname because otherwise it will try to keep the old one
|
||||||
|
newConfig.Config.Hostname = ""
|
||||||
|
// IDK Docker is weird, if you don't erase this it will break
|
||||||
|
newConfig.Config.ExposedPorts = nil
|
||||||
|
}
|
||||||
|
|
||||||
// recreate container with new informations
|
// recreate container with new informations
|
||||||
createResponse, createError := DockerClient.ContainerCreate(
|
createResponse, createError := DockerClient.ContainerCreate(
|
||||||
DockerContext,
|
DockerContext,
|
||||||
|
@ -177,7 +191,10 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
||||||
nil,
|
nil,
|
||||||
newName,
|
newName,
|
||||||
)
|
)
|
||||||
|
if createError != nil {
|
||||||
|
utils.Error("EditContainer - Failed to create container", createError)
|
||||||
|
}
|
||||||
|
|
||||||
utils.Log("EditContainer - Container recreated. Re-connecting networks " + createResponse.ID)
|
utils.Log("EditContainer - Container recreated. Re-connecting networks " + createResponse.ID)
|
||||||
|
|
||||||
// is force secure
|
// is force secure
|
||||||
|
@ -202,6 +219,10 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
||||||
|
|
||||||
runError := DockerClient.ContainerStart(DockerContext, createResponse.ID, types.ContainerStartOptions{})
|
runError := DockerClient.ContainerStart(DockerContext, createResponse.ID, types.ContainerStartOptions{})
|
||||||
|
|
||||||
|
if runError != nil {
|
||||||
|
utils.Error("EditContainer - Failed to run container", runError)
|
||||||
|
}
|
||||||
|
|
||||||
if createError != nil || runError != nil {
|
if createError != nil || runError != nil {
|
||||||
if(oldContainerID == "") {
|
if(oldContainerID == "") {
|
||||||
if(createError == nil) {
|
if(createError == nil) {
|
||||||
|
@ -217,12 +238,17 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
||||||
|
|
||||||
if(createError == nil) {
|
if(createError == nil) {
|
||||||
utils.Log("EditContainer - Killing new broken container")
|
utils.Log("EditContainer - Killing new broken container")
|
||||||
|
// attempt kill
|
||||||
|
DockerClient.ContainerKill(DockerContext, oldContainerID, "")
|
||||||
DockerClient.ContainerKill(DockerContext, createResponse.ID, "")
|
DockerClient.ContainerKill(DockerContext, createResponse.ID, "")
|
||||||
|
// attempt remove in case created state
|
||||||
|
DockerClient.ContainerRemove(DockerContext, oldContainerID, types.ContainerRemoveOptions{})
|
||||||
|
DockerClient.ContainerRemove(DockerContext, createResponse.ID, types.ContainerRemoveOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.Log("EditContainer - Reverting...")
|
utils.Log("EditContainer - Reverting...")
|
||||||
// attempt to restore container
|
// attempt to restore container
|
||||||
restored, restoreError := EditContainer("", oldContainer)
|
restored, restoreError := EditContainer("", oldContainer, false)
|
||||||
|
|
||||||
if restoreError != nil {
|
if restoreError != nil {
|
||||||
utils.Error("EditContainer - Failed to restore container", restoreError)
|
utils.Error("EditContainer - Failed to restore container", restoreError)
|
||||||
|
@ -245,12 +271,48 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
||||||
return restored, errors.New("Failed to edit container, but restored to previous state. Error was: " + errorWas)
|
return restored, errors.New("Failed to edit container, but restored to previous state. Error was: " + errorWas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recreating dependant containers
|
||||||
|
utils.Debug("Unlocking EDIT Container")
|
||||||
|
|
||||||
|
if oldContainerID != "" {
|
||||||
|
RecreateDepedencies(oldContainerID)
|
||||||
|
}
|
||||||
|
|
||||||
utils.Log("EditContainer - Container started. All done! " + createResponse.ID)
|
utils.Log("EditContainer - Container started. All done! " + createResponse.ID)
|
||||||
|
|
||||||
return createResponse.ID, nil
|
return createResponse.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RecreateDepedencies(containerID string) {
|
||||||
|
containers, err := ListContainers()
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("RecreateDepedencies", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
if container.ID == containerID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fullContainer, err := DockerClient.ContainerInspect(DockerContext, container.ID)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("RecreateDepedencies", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if network mode contains containerID
|
||||||
|
if strings.Contains(string(fullContainer.HostConfig.NetworkMode), containerID) {
|
||||||
|
utils.Log("RecreateDepedencies - Recreating " + container.Names[0])
|
||||||
|
_, err := EditContainer(container.ID, fullContainer, true)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("RecreateDepedencies - Failed to update - ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ListContainers() ([]types.Container, error) {
|
func ListContainers() ([]types.Container, error) {
|
||||||
errD := Connect()
|
errD := Connect()
|
||||||
if errD != nil {
|
if errD != nil {
|
||||||
|
@ -397,7 +459,7 @@ func CheckUpdatesAvailable() map[string]bool {
|
||||||
|
|
||||||
if needsUpdate && IsLabel(fullContainer, "cosmos-auto-update") {
|
if needsUpdate && IsLabel(fullContainer, "cosmos-auto-update") {
|
||||||
utils.Log("Downlaoded new update for " + container.Image + " ready to install")
|
utils.Log("Downlaoded new update for " + container.Image + " ready to install")
|
||||||
_, err := EditContainer(container.ID, fullContainer)
|
_, err := EditContainer(container.ID, fullContainer, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("CheckUpdatesAvailable - Failed to update - ", err)
|
utils.Error("CheckUpdatesAvailable - Failed to update - ", err)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4,6 +4,9 @@ import (
|
||||||
"github.com/azukaar/cosmos-server/src/utils"
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
|
||||||
// "github.com/docker/docker/client"
|
// "github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
@ -18,13 +21,13 @@ type VolumeMount struct {
|
||||||
Volume *types.Volume
|
Volume *types.Volume
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDB() (string, error) {
|
func NewDB(w http.ResponseWriter, req *http.Request) (string, error) {
|
||||||
id := utils.GenerateRandomString(3)
|
id := utils.GenerateRandomString(3)
|
||||||
mongoUser := "cosmos-" + utils.GenerateRandomString(5)
|
mongoUser := "cosmos-" + utils.GenerateRandomString(5)
|
||||||
mongoPass := utils.GenerateRandomString(24)
|
mongoPass := utils.GenerateRandomString(24)
|
||||||
monHost := "cosmos-mongo-" + id
|
monHost := "cosmos-mongo-" + id
|
||||||
|
|
||||||
imageName := "mongo:latest"
|
imageName := "mongo:5"
|
||||||
|
|
||||||
// if CPU is missing AVX, use 4.4
|
// if CPU is missing AVX, use 4.4
|
||||||
if runtime.GOARCH == "amd64" && !cpu.X86.HasAVX {
|
if runtime.GOARCH == "amd64" && !cpu.X86.HasAVX {
|
||||||
|
@ -32,28 +35,36 @@ func NewDB() (string, error) {
|
||||||
imageName = "mongo:4.4"
|
imageName = "mongo:4.4"
|
||||||
}
|
}
|
||||||
|
|
||||||
err := RunContainer(
|
service := DockerServiceCreateRequest{
|
||||||
imageName,
|
Services: map[string]ContainerCreateRequestContainer {},
|
||||||
monHost,
|
}
|
||||||
[]string{
|
|
||||||
|
service.Services[monHost] = ContainerCreateRequestContainer{
|
||||||
|
Name: monHost,
|
||||||
|
Image: imageName,
|
||||||
|
RestartPolicy: "always",
|
||||||
|
Environment: []string{
|
||||||
"MONGO_INITDB_ROOT_USERNAME=" + mongoUser,
|
"MONGO_INITDB_ROOT_USERNAME=" + mongoUser,
|
||||||
"MONGO_INITDB_ROOT_PASSWORD=" + mongoPass,
|
"MONGO_INITDB_ROOT_PASSWORD=" + mongoPass,
|
||||||
},
|
},
|
||||||
[]VolumeMount{
|
Labels: map[string]string{
|
||||||
|
"cosmos-force-network-secured": "true",
|
||||||
|
},
|
||||||
|
Volumes: []mount.Mount{
|
||||||
{
|
{
|
||||||
Destination: "/data/db",
|
Type: mount.TypeVolume,
|
||||||
Volume: &types.Volume{
|
Source: "cosmos-mongo-data-" + id,
|
||||||
Name: "cosmos-mongo-data-" + id,
|
Target: "/data/db",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Destination: "/data/configdb",
|
Type: mount.TypeVolume,
|
||||||
Volume: &types.Volume{
|
Source: "cosmos-mongo-config-" + id,
|
||||||
Name: "cosmos-mongo-config-" + id,
|
Target: "/data/configdb",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
};
|
||||||
|
|
||||||
|
err := CreateService(w, req, service)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -87,54 +98,13 @@ func RunContainer(imagename string, containername string, inputEnv []string, vol
|
||||||
mounts = append(mounts, mount)
|
mounts = append(mounts, mount)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define a PORT opening
|
|
||||||
// newport, err := natting.NewPort("tcp", port)
|
|
||||||
// if err != nil {
|
|
||||||
// fmt.Println("Unable to create docker port")
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Configured hostConfig:
|
|
||||||
// https://godoc.org/github.com/docker/docker/api/types/container#HostConfig
|
|
||||||
hostConfig := &container.HostConfig{
|
hostConfig := &container.HostConfig{
|
||||||
// PortBindings: natting.PortMap{
|
|
||||||
// newport: []natting.PortBinding{
|
|
||||||
// {
|
|
||||||
// HostIP: "0.0.0.0",
|
|
||||||
// HostPort: port,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
Mounts : mounts,
|
Mounts : mounts,
|
||||||
RestartPolicy: container.RestartPolicy{
|
RestartPolicy: container.RestartPolicy{
|
||||||
Name: "always",
|
Name: "always",
|
||||||
},
|
},
|
||||||
// LogConfig: container.LogConfig{
|
|
||||||
// Type: "json-file",
|
|
||||||
// Config: map[string]string{},
|
|
||||||
// },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define Network config
|
|
||||||
// https://godoc.org/github.com/docker/docker/api/types/network#NetworkingConfig
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// networkConfig := &network.NetworkingConfig{
|
|
||||||
// EndpointsConfig: map[string]*network.EndpointSettings{},
|
|
||||||
// }
|
|
||||||
// gatewayConfig := &network.EndpointSettings{
|
|
||||||
// Gateway: "gatewayname",
|
|
||||||
// }
|
|
||||||
// networkConfig.EndpointsConfig["bridge"] = gatewayConfig
|
|
||||||
|
|
||||||
// Define ports to be exposed (has to be same as hostconfig.portbindings.newport)
|
|
||||||
// exposedPorts := map[natting.Port]struct{}{
|
|
||||||
// newport: struct{}{},
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Configuration
|
|
||||||
// https://godoc.org/github.com/docker/docker/api/types/container#Config
|
|
||||||
config := &container.Config{
|
config := &container.Config{
|
||||||
Image: imagename,
|
Image: imagename,
|
||||||
Env: inputEnv,
|
Env: inputEnv,
|
||||||
|
@ -145,9 +115,6 @@ func RunContainer(imagename string, containername string, inputEnv []string, vol
|
||||||
// ExposedPorts: exposedPorts,
|
// ExposedPorts: exposedPorts,
|
||||||
}
|
}
|
||||||
|
|
||||||
//archi := runtime.GOARCH
|
|
||||||
|
|
||||||
// Creating the actual container. This is "nil,nil,nil" in every example.
|
|
||||||
cont, err := DockerClient.ContainerCreate(
|
cont, err := DockerClient.ContainerCreate(
|
||||||
DockerContext,
|
DockerContext,
|
||||||
config,
|
config,
|
||||||
|
@ -162,7 +129,6 @@ func RunContainer(imagename string, containername string, inputEnv []string, vol
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the actual container
|
|
||||||
DockerClient.ContainerStart(DockerContext, cont.ID, types.ContainerStartOptions{})
|
DockerClient.ContainerStart(DockerContext, cont.ID, types.ContainerStartOptions{})
|
||||||
utils.Log("Container created " + cont.ID)
|
utils.Log("Container created " + cont.ID)
|
||||||
|
|
||||||
|
|
|
@ -190,7 +190,7 @@ func StartServer() {
|
||||||
|
|
||||||
// need rewrite bc it catches too many things and prevent
|
// need rewrite bc it catches too many things and prevent
|
||||||
// client to be notified of the error
|
// client to be notified of the error
|
||||||
// router.Use(middleware.Recoverer)
|
|
||||||
router.Use(middleware.Logger)
|
router.Use(middleware.Logger)
|
||||||
router.Use(utils.SetSecurityHeaders)
|
router.Use(utils.SetSecurityHeaders)
|
||||||
|
|
||||||
|
|
12
src/icons.go
12
src/icons.go
|
@ -123,7 +123,7 @@ func GetFavicon(w http.ResponseWriter, req *http.Request) {
|
||||||
iconURL := icon.URL
|
iconURL := icon.URL
|
||||||
u, err := url.Parse(siteurl)
|
u, err := url.Parse(siteurl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("FaviconFetch failed to parse ", err)
|
utils.Debug("FaviconFetch failed to parse " + err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,21 +146,21 @@ func GetFavicon(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.Log("Favicon Trying to fetch " + iconURL)
|
utils.Debug("Favicon Trying to fetch " + iconURL)
|
||||||
|
|
||||||
// Fetch the favicon
|
// Fetch the favicon
|
||||||
resp, err := http.Get(iconURL)
|
resp, err := http.Get(iconURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("FaviconFetch", err)
|
utils.Debug("FaviconFetch" + err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if 200 and if image
|
// check if 200 and if image
|
||||||
if resp.StatusCode != 200 {
|
if resp.StatusCode != 200 {
|
||||||
utils.Error("FaviconFetch - " + iconURL + " - not 200 ", nil)
|
utils.Debug("FaviconFetch - " + iconURL + " - not 200 ")
|
||||||
continue
|
continue
|
||||||
} else if !strings.Contains(resp.Header.Get("Content-Type"), "image") && !strings.Contains(resp.Header.Get("Content-Type"), "octet-stream") {
|
} else if !strings.Contains(resp.Header.Get("Content-Type"), "image") && !strings.Contains(resp.Header.Get("Content-Type"), "octet-stream") {
|
||||||
utils.Error("FaviconFetch - " + iconURL + " - not image ", nil)
|
utils.Debug("FaviconFetch - " + iconURL + " - not image ")
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
utils.Log("Favicon found " + iconURL)
|
utils.Log("Favicon found " + iconURL)
|
||||||
|
@ -168,7 +168,7 @@ func GetFavicon(w http.ResponseWriter, req *http.Request) {
|
||||||
// Cache the response
|
// Cache the response
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("FaviconFetch - cant read ", err)
|
utils.Debug("FaviconFetch - cant read " + err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,13 +85,13 @@ func NewInstallRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
} else if (request.MongoDBMode == "Create") {
|
} else if (request.MongoDBMode == "Create") {
|
||||||
utils.Log("NewInstall: Create DB")
|
utils.Log("NewInstall: Create DB")
|
||||||
newConfig.DisableUserManagement = false
|
newConfig.DisableUserManagement = false
|
||||||
strco, err := docker.NewDB()
|
|
||||||
|
strco, err := docker.NewDB(w, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("NewInstall: Error creating MongoDB", err)
|
utils.Error("NewInstall: Error creating MongoDB", err)
|
||||||
utils.HTTPError(w, "New Install: Error creating MongoDB " + err.Error(),
|
|
||||||
http.StatusInternalServerError, "NI001")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newConfig.MongoDB = strco
|
newConfig.MongoDB = strco
|
||||||
utils.SaveConfigTofile(newConfig)
|
utils.SaveConfigTofile(newConfig)
|
||||||
utils.LoadBaseMainConfig(newConfig)
|
utils.LoadBaseMainConfig(newConfig)
|
||||||
|
|
Loading…
Reference in a new issue