[release] version 0.5.5
This commit is contained in:
parent
c8731e2fa7
commit
a6098f0507
|
@ -42,14 +42,63 @@ let isOnline = () => {
|
|||
});
|
||||
}
|
||||
|
||||
let newInstall = (req) => {
|
||||
return wrap(fetch('/cosmos/api/newInstall', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(req)
|
||||
}))
|
||||
let newInstall = (req, onProgress) => {
|
||||
if(req.step == '2') {
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
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';
|
||||
|
|
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 { Formik } from 'formik';
|
||||
import LogsInModal from '../../components/logsInModal';
|
||||
import { CosmosCheckbox, CosmosInputPassword, CosmosInputText, CosmosSelect } from '../config/users/formShortcuts';
|
||||
import AnimateButton from '../../components/@extended/AnimateButton';
|
||||
import { Box } from '@mui/system';
|
||||
|
@ -25,6 +26,7 @@ const NewInstall = () => {
|
|||
const [counter, setCounter] = useState(0);
|
||||
let [hostname, setHostname] = useState('');
|
||||
const [databaseEnable, setDatabaseEnable] = useState(true);
|
||||
const [pullRequest, setPullRequest] = useState(null);
|
||||
|
||||
const refreshStatus = async () => {
|
||||
try {
|
||||
|
@ -118,27 +120,29 @@ const NewInstall = () => {
|
|||
validate={(values) => {
|
||||
}}
|
||||
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
|
||||
try {
|
||||
setSubmitting(true);
|
||||
const res = await API.newInstall({
|
||||
setSubmitting(true);
|
||||
|
||||
setPullRequest(() => ((cb) => {
|
||||
API.newInstall({
|
||||
step: "2",
|
||||
MongoDBMode: values.DBMode,
|
||||
MongoDB: values.MongoDB,
|
||||
});
|
||||
if(res.status == "OK") {
|
||||
if(values.DBMode === "DisableUserManagement") {
|
||||
setDatabaseEnable(false);
|
||||
}
|
||||
setStatus({ success: true });
|
||||
}
|
||||
} catch (error) {
|
||||
setStatus({ success: false });
|
||||
setErrors({ submit: error.message });
|
||||
setSubmitting(false);
|
||||
}
|
||||
}, cb)
|
||||
}));
|
||||
}}>
|
||||
{(formik) => (
|
||||
<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}>
|
||||
<CosmosSelect
|
||||
name="DBMode"
|
||||
|
|
|
@ -16,7 +16,6 @@ const GetActions = ({
|
|||
const isMiniMobile = useMediaQuery((theme) => theme.breakpoints.down('xsm'));
|
||||
const [pullRequest, setPullRequest] = React.useState(null);
|
||||
const [isUpdating, setIsUpdating] = React.useState(false);
|
||||
console.log(isMiniMobile)
|
||||
|
||||
const doTo = (action) => {
|
||||
setIsUpdating(true);
|
||||
|
|
|
@ -116,6 +116,30 @@ const DockerComposeImport = ({refresh}) => {
|
|||
if(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' }}>
|
||||
<Formik
|
||||
initialValues={{
|
||||
networkMode: containerInfo.HostConfig.NetworkMode,
|
||||
ports: Object.keys(containerInfo.NetworkSettings.Ports).map((port) => {
|
||||
return {
|
||||
port: port.split('/')[0],
|
||||
|
@ -237,6 +238,14 @@ const NetworkContainerSetup = ({ config, containerInfo, refresh, newContainer, O
|
|||
</MainCard>
|
||||
<MainCard title={'Networks'}>
|
||||
<Stack spacing={2}>
|
||||
|
||||
<CosmosInputText
|
||||
label="Network Mode"
|
||||
name="networkMode"
|
||||
placeholder={'default'}
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
{networks && <Stack spacing={2}>
|
||||
{Object.keys(containerInfo.NetworkSettings.Networks).map((networkName) => {
|
||||
const network = networks.find((n) => n.Name === networkName);
|
||||
|
|
|
@ -99,7 +99,7 @@ const NewDockerServiceForm = () => {
|
|||
variant="contained"
|
||||
fullWidth
|
||||
endIcon={<ArrowRightOutlined />}
|
||||
disabled={currentTab === 4}
|
||||
disabled={(currentTab === 4) || containerInfo.Name === '' || containerInfo.Config.Image === ''}
|
||||
onClick={() => {
|
||||
setCurrentTab(currentTab + 1);
|
||||
setMaxTab(Math.max(currentTab + 1, maxTab));
|
||||
|
|
|
@ -108,8 +108,8 @@ const ContainerOverview = ({ containerInfo, config, refresh }) => {
|
|||
)}
|
||||
<strong><ContainerOutlined /> Image</strong>
|
||||
<div style={info}>{Image}</div>
|
||||
<strong><DesktopOutlined /> Name</strong>
|
||||
<div style={info}>{Name}</div>
|
||||
<strong><DesktopOutlined /> ID</strong>
|
||||
<div style={info}>{containerInfo.Id}</div>
|
||||
<strong><InfoCircleOutlined /> IP Address</strong>
|
||||
<div style={info}>{IPAddress}</div>
|
||||
<strong>
|
||||
|
|
|
@ -61,6 +61,9 @@ const DockerContainerSetup = ({config, containerInfo, OnChange, refresh, newCont
|
|||
if (!values.image) {
|
||||
errors.image = 'Required';
|
||||
}
|
||||
if (!values.name && newContainer) {
|
||||
errors.name = 'Required';
|
||||
}
|
||||
// env keys and labels key mustbe unique
|
||||
const envKeys = values.envVars.map((envVar) => envVar.key);
|
||||
const labelKeys = values.labels.map((label) => label.key);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// 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 Grid2 from '@mui/material/Unstable_Grid2/Grid2';
|
||||
import { Stack } from '@mui/system';
|
||||
|
@ -18,6 +18,7 @@ import ExposeModal from './exposeModal';
|
|||
import GetActions from './actionBar';
|
||||
import ResponsiveButton from '../../components/responseiveButton';
|
||||
import DockerComposeImport from './containers/docker-compose';
|
||||
import { ContainerNetworkWarning } from '../../components/containers';
|
||||
|
||||
const Item = styled(Paper)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
|
||||
|
@ -185,7 +186,7 @@ const ServeApps = () => {
|
|||
<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'}}>
|
||||
<Typography variant="h5" color="text.secondary">
|
||||
{app.Names[0].replace('/', '')}
|
||||
{app.Names[0].replace('/', '')}
|
||||
</Typography>
|
||||
<Typography color="text.secondary" style={{fontSize: '80%', whiteSpace: 'nowrap', overflow: 'hidden', maxWidth: '100%', textOverflow: 'ellipsis'}}>
|
||||
{app.Image}
|
||||
|
@ -251,7 +252,7 @@ const ServeApps = () => {
|
|||
refreshServeApps();
|
||||
})
|
||||
}}
|
||||
/> Force Secure Network
|
||||
/> Force Secure Network <ContainerNetworkWarning container={app} />
|
||||
</Stack>
|
||||
<Stack style={{ fontSize: '80%' }} direction={"row"} alignItems="center">
|
||||
<Checkbox
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.5.4",
|
||||
"version": "0.5.5",
|
||||
"description": "",
|
||||
"main": "test-server.js",
|
||||
"bugs": {
|
||||
|
|
|
@ -39,7 +39,7 @@ func AutoUpdateContainerRoute(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
utils.Log("API: Set Auto Update "+status+" : " + containerName)
|
||||
|
||||
_, errEdit := EditContainer(container.ID, container)
|
||||
_, errEdit := EditContainer(container.ID, container, false)
|
||||
if errEdit != nil {
|
||||
utils.Error("AutoUpdateContainer Edit", errEdit)
|
||||
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" {
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
if !ok {
|
||||
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(req.Body)
|
||||
var serviceRequest DockerServiceCreateRequest
|
||||
err := decoder.Decode(&serviceRequest)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
var rollbackActions []DockerServiceCreateRollback
|
||||
|
||||
// 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()
|
||||
CreateService(w, req, serviceRequest)
|
||||
} else {
|
||||
utils.Error("CreateService: Method not allowed" + req.Method, nil)
|
||||
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) {
|
||||
startOrder := []ContainerCreateRequestContainer{}
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ func ManageContainerRoute(w http.ResponseWriter, req *http.Request) {
|
|||
case "unpause":
|
||||
err = DockerClient.ContainerUnpause(DockerContext, container.ID)
|
||||
case "recreate":
|
||||
_, err = EditContainer(container.ID, container)
|
||||
_, err = EditContainer(container.ID, container, false)
|
||||
case "update":
|
||||
out, errPull := DockerClient.ImagePull(DockerContext, imagename, doctype.ImagePullOptions{})
|
||||
if errPull != nil {
|
||||
|
@ -100,7 +100,7 @@ func ManageContainerRoute(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
utils.Log("Container Update - Image pulled " + imagename)
|
||||
|
||||
_, err = EditContainer(container.ID, container)
|
||||
_, err = EditContainer(container.ID, container, false)
|
||||
|
||||
if err != nil {
|
||||
utils.Error("Container Update - EditContainer", err)
|
||||
|
|
|
@ -20,7 +20,7 @@ func NewDBRoute(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
if(req.Method == "GET") {
|
||||
costr, err := NewDB()
|
||||
costr, err := NewDB(w, req)
|
||||
|
||||
if err != nil {
|
||||
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,
|
||||
});
|
||||
|
||||
// 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)
|
||||
|
||||
_, errEdit := EditContainer(container.ID, container)
|
||||
_, errEdit := EditContainer(container.ID, container, false)
|
||||
if errEdit != nil {
|
||||
utils.Error("ContainerSecureEdit", errEdit)
|
||||
utils.HTTPError(w, "Internal server error: " + errEdit.Error(), http.StatusInternalServerError, "DS003")
|
||||
|
|
|
@ -23,6 +23,7 @@ type ContainerForm struct {
|
|||
Volumes []mount.Mount `json:"Volumes"`
|
||||
// we make this a int so that we can ignore 0
|
||||
Interactive int `json:"interactive"`
|
||||
NetworkMode string `json:"networkMode"`
|
||||
}
|
||||
|
||||
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.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 {
|
||||
utils.Error("UpdateContainer: EditContainer", err)
|
||||
utils.HTTPError(w, "Internal server error: "+err.Error(), http.StatusInternalServerError, "DS004")
|
||||
|
|
|
@ -31,6 +31,13 @@ func BootstrapAllContainersFromTags() []error {
|
|||
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 {
|
||||
errD := Connect()
|
||||
if errD != nil {
|
||||
|
@ -71,6 +78,12 @@ func BootstrapContainerFromTags(containerID string) error {
|
|||
if !isCosmosCon {
|
||||
needsRestart, errCT = ConnectToSecureNetwork(container)
|
||||
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
|
||||
}
|
||||
if needsRestart {
|
||||
|
@ -84,7 +97,12 @@ func BootstrapContainerFromTags(containerID string) error {
|
|||
utils.Log(container.Name+": Disconnecting from bridge network")
|
||||
errDisc := DockerClient.NetworkDisconnect(DockerContext, "bridge", containerID, true)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +117,7 @@ func BootstrapContainerFromTags(containerID string) error {
|
|||
}
|
||||
|
||||
if(needsUpdate) {
|
||||
_, errEdit := EditContainer(containerID, container)
|
||||
_, errEdit := EditContainer(containerID, container, false)
|
||||
if errEdit != nil {
|
||||
utils.Error("Docker Boostrap, couldn't update container: ", errEdit)
|
||||
return errEdit
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
"fmt"
|
||||
"bufio"
|
||||
"strings"
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
|
@ -82,11 +81,8 @@ func Connect() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string, error) {
|
||||
|
||||
utils.Debug("VOLUMES:" + fmt.Sprintf("%v", newConfig.HostConfig.Mounts))
|
||||
|
||||
if(oldContainerID != "") {
|
||||
func EditContainer(oldContainerID string, newConfig types.ContainerJSON, noLock bool) (string, error) {
|
||||
if(oldContainerID != "" && !noLock) {
|
||||
// no need to re-lock if we are reverting
|
||||
DockerNetworkLock <- true
|
||||
defer func() {
|
||||
|
@ -100,6 +96,17 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
oldContainer := newConfig
|
||||
|
||||
|
@ -112,8 +119,6 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
|||
// https://godoc.org/github.com/docker/docker/api/types#ContainerJSON
|
||||
oldContainer, err = DockerClient.ContainerInspect(DockerContext, oldContainerID)
|
||||
|
||||
utils.Debug("OLD VOLUMES:" + fmt.Sprintf("%v", oldContainer.HostConfig.Mounts))
|
||||
|
||||
if err != nil {
|
||||
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
|
||||
newName = oldContainer.Name
|
||||
newConfig.Config.Hostname = newName
|
||||
|
||||
// stop and remove container
|
||||
stopError := DockerClient.ContainerStop(DockerContext, oldContainerID, container.StopOptions{})
|
||||
|
@ -168,6 +172,16 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
|||
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
|
||||
createResponse, createError := DockerClient.ContainerCreate(
|
||||
DockerContext,
|
||||
|
@ -177,6 +191,9 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
|||
nil,
|
||||
newName,
|
||||
)
|
||||
if createError != nil {
|
||||
utils.Error("EditContainer - Failed to create container", createError)
|
||||
}
|
||||
|
||||
utils.Log("EditContainer - Container recreated. Re-connecting networks " + createResponse.ID)
|
||||
|
||||
|
@ -202,6 +219,10 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
|||
|
||||
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(oldContainerID == "") {
|
||||
if(createError == nil) {
|
||||
|
@ -217,12 +238,17 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
|||
|
||||
if(createError == nil) {
|
||||
utils.Log("EditContainer - Killing new broken container")
|
||||
// attempt kill
|
||||
DockerClient.ContainerKill(DockerContext, oldContainerID, "")
|
||||
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...")
|
||||
// attempt to restore container
|
||||
restored, restoreError := EditContainer("", oldContainer)
|
||||
restored, restoreError := EditContainer("", oldContainer, false)
|
||||
|
||||
if restoreError != nil {
|
||||
utils.Error("EditContainer - Failed to restore container", restoreError)
|
||||
|
@ -246,11 +272,47 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON) (string
|
|||
}
|
||||
}
|
||||
|
||||
// Recreating dependant containers
|
||||
utils.Debug("Unlocking EDIT Container")
|
||||
|
||||
if oldContainerID != "" {
|
||||
RecreateDepedencies(oldContainerID)
|
||||
}
|
||||
|
||||
utils.Log("EditContainer - Container started. All done! " + createResponse.ID)
|
||||
|
||||
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) {
|
||||
errD := Connect()
|
||||
if errD != nil {
|
||||
|
@ -397,7 +459,7 @@ func CheckUpdatesAvailable() map[string]bool {
|
|||
|
||||
if needsUpdate && IsLabel(fullContainer, "cosmos-auto-update") {
|
||||
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 {
|
||||
utils.Error("CheckUpdatesAvailable - Failed to update - ", err)
|
||||
} else {
|
||||
|
|
|
@ -4,6 +4,9 @@ import (
|
|||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
"io"
|
||||
"os"
|
||||
"net/http"
|
||||
|
||||
|
||||
// "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types"
|
||||
|
@ -18,13 +21,13 @@ type VolumeMount struct {
|
|||
Volume *types.Volume
|
||||
}
|
||||
|
||||
func NewDB() (string, error) {
|
||||
func NewDB(w http.ResponseWriter, req *http.Request) (string, error) {
|
||||
id := utils.GenerateRandomString(3)
|
||||
mongoUser := "cosmos-" + utils.GenerateRandomString(5)
|
||||
mongoPass := utils.GenerateRandomString(24)
|
||||
monHost := "cosmos-mongo-" + id
|
||||
|
||||
imageName := "mongo:latest"
|
||||
imageName := "mongo:5"
|
||||
|
||||
// if CPU is missing AVX, use 4.4
|
||||
if runtime.GOARCH == "amd64" && !cpu.X86.HasAVX {
|
||||
|
@ -32,28 +35,36 @@ func NewDB() (string, error) {
|
|||
imageName = "mongo:4.4"
|
||||
}
|
||||
|
||||
err := RunContainer(
|
||||
imageName,
|
||||
monHost,
|
||||
[]string{
|
||||
service := DockerServiceCreateRequest{
|
||||
Services: map[string]ContainerCreateRequestContainer {},
|
||||
}
|
||||
|
||||
service.Services[monHost] = ContainerCreateRequestContainer{
|
||||
Name: monHost,
|
||||
Image: imageName,
|
||||
RestartPolicy: "always",
|
||||
Environment: []string{
|
||||
"MONGO_INITDB_ROOT_USERNAME=" + mongoUser,
|
||||
"MONGO_INITDB_ROOT_PASSWORD=" + mongoPass,
|
||||
},
|
||||
[]VolumeMount{
|
||||
Labels: map[string]string{
|
||||
"cosmos-force-network-secured": "true",
|
||||
},
|
||||
Volumes: []mount.Mount{
|
||||
{
|
||||
Destination: "/data/db",
|
||||
Volume: &types.Volume{
|
||||
Name: "cosmos-mongo-data-" + id,
|
||||
},
|
||||
Type: mount.TypeVolume,
|
||||
Source: "cosmos-mongo-data-" + id,
|
||||
Target: "/data/db",
|
||||
},
|
||||
{
|
||||
Destination: "/data/configdb",
|
||||
Volume: &types.Volume{
|
||||
Name: "cosmos-mongo-config-" + id,
|
||||
},
|
||||
Type: mount.TypeVolume,
|
||||
Source: "cosmos-mongo-config-" + id,
|
||||
Target: "/data/configdb",
|
||||
},
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
err := CreateService(w, req, service)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -87,54 +98,13 @@ func RunContainer(imagename string, containername string, inputEnv []string, vol
|
|||
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{
|
||||
// PortBindings: natting.PortMap{
|
||||
// newport: []natting.PortBinding{
|
||||
// {
|
||||
// HostIP: "0.0.0.0",
|
||||
// HostPort: port,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
Mounts : mounts,
|
||||
RestartPolicy: container.RestartPolicy{
|
||||
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{
|
||||
Image: imagename,
|
||||
Env: inputEnv,
|
||||
|
@ -145,9 +115,6 @@ func RunContainer(imagename string, containername string, inputEnv []string, vol
|
|||
// ExposedPorts: exposedPorts,
|
||||
}
|
||||
|
||||
//archi := runtime.GOARCH
|
||||
|
||||
// Creating the actual container. This is "nil,nil,nil" in every example.
|
||||
cont, err := DockerClient.ContainerCreate(
|
||||
DockerContext,
|
||||
config,
|
||||
|
@ -162,7 +129,6 @@ func RunContainer(imagename string, containername string, inputEnv []string, vol
|
|||
return err
|
||||
}
|
||||
|
||||
// Run the actual container
|
||||
DockerClient.ContainerStart(DockerContext, cont.ID, types.ContainerStartOptions{})
|
||||
utils.Log("Container created " + cont.ID)
|
||||
|
||||
|
|
|
@ -190,7 +190,7 @@ func StartServer() {
|
|||
|
||||
// need rewrite bc it catches too many things and prevent
|
||||
// client to be notified of the error
|
||||
// router.Use(middleware.Recoverer)
|
||||
|
||||
router.Use(middleware.Logger)
|
||||
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
|
||||
u, err := url.Parse(siteurl)
|
||||
if err != nil {
|
||||
utils.Error("FaviconFetch failed to parse ", err)
|
||||
utils.Debug("FaviconFetch failed to parse " + err.Error())
|
||||
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
|
||||
resp, err := http.Get(iconURL)
|
||||
if err != nil {
|
||||
utils.Error("FaviconFetch", err)
|
||||
utils.Debug("FaviconFetch" + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
// check if 200 and if image
|
||||
if resp.StatusCode != 200 {
|
||||
utils.Error("FaviconFetch - " + iconURL + " - not 200 ", nil)
|
||||
utils.Debug("FaviconFetch - " + iconURL + " - not 200 ")
|
||||
continue
|
||||
} 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
|
||||
} else {
|
||||
utils.Log("Favicon found " + iconURL)
|
||||
|
@ -168,7 +168,7 @@ func GetFavicon(w http.ResponseWriter, req *http.Request) {
|
|||
// Cache the response
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
utils.Error("FaviconFetch - cant read ", err)
|
||||
utils.Debug("FaviconFetch - cant read " + err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -85,13 +85,13 @@ func NewInstallRoute(w http.ResponseWriter, req *http.Request) {
|
|||
} else if (request.MongoDBMode == "Create") {
|
||||
utils.Log("NewInstall: Create DB")
|
||||
newConfig.DisableUserManagement = false
|
||||
strco, err := docker.NewDB()
|
||||
|
||||
strco, err := docker.NewDB(w, req)
|
||||
if err != nil {
|
||||
utils.Error("NewInstall: Error creating MongoDB", err)
|
||||
utils.HTTPError(w, "New Install: Error creating MongoDB " + err.Error(),
|
||||
http.StatusInternalServerError, "NI001")
|
||||
return
|
||||
}
|
||||
|
||||
newConfig.MongoDB = strco
|
||||
utils.SaveConfigTofile(newConfig)
|
||||
utils.LoadBaseMainConfig(newConfig)
|
||||
|
|
Loading…
Reference in a new issue