[release] v0.1.16
- Fix search - Fix bug where containers would lose their networks after being edited - Self-heal secure network configuration - Auto disconnect from orphan networks - Prevent bootstrapping from creating orphan networks - Monitor Docker and self-heal when docker daemon dies - Recreate lost secure networks (ex. when resetting Cosmos)
This commit is contained in:
parent
fc94a56f6c
commit
c3401064c6
13
changelog.md
13
changelog.md
|
@ -1,4 +1,17 @@
|
||||||
|
## Version 0.1.16
|
||||||
|
|
||||||
|
- Fix search
|
||||||
|
- Fix bug where containers would lose their networks after being edited
|
||||||
|
- Self-heal secure network configuration
|
||||||
|
- Auto disconnect from orphan networks
|
||||||
|
- Prevent bootstrapping from creating orphan networks
|
||||||
|
- Monitor Docker and self-heal when docker daemon dies
|
||||||
|
- Recreate lost secure networks (ex. when resetting Cosmos)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Version 0.1.15
|
## Version 0.1.15
|
||||||
|
|
||||||
- Ports is now freetype, in case container does not expose any
|
- Ports is now freetype, in case container does not expose any
|
||||||
- Container picker now tries to pick the best port as default
|
- Container picker now tries to pick the best port as default
|
||||||
- Hostname now default to container name
|
- Hostname now default to container name
|
||||||
|
|
|
@ -62,7 +62,6 @@ const ProxyManagement = () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
setConfig(con);
|
setConfig(con);
|
||||||
setNeedSave(true);
|
|
||||||
return con;
|
return con;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,12 +85,14 @@ const ProxyManagement = () => {
|
||||||
routes[key] = routes[key-1];
|
routes[key] = routes[key-1];
|
||||||
routes[key-1] = tmp;
|
routes[key-1] = tmp;
|
||||||
updateRoutes(routes);
|
updateRoutes(routes);
|
||||||
|
setNeedSave(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteRoute(key) {
|
function deleteRoute(key) {
|
||||||
routes.splice(key, 1);
|
routes.splice(key, 1);
|
||||||
updateRoutes(routes);
|
updateRoutes(routes);
|
||||||
|
setNeedSave(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function down(key) {
|
function down(key) {
|
||||||
|
@ -100,6 +101,7 @@ const ProxyManagement = () => {
|
||||||
routes[key] = routes[key+1];
|
routes[key] = routes[key+1];
|
||||||
routes[key+1] = tmp;
|
routes[key+1] = tmp;
|
||||||
updateRoutes(routes);
|
updateRoutes(routes);
|
||||||
|
setNeedSave(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +138,7 @@ const ProxyManagement = () => {
|
||||||
AuthEnabled: false,
|
AuthEnabled: false,
|
||||||
});
|
});
|
||||||
updateRoutes(routes);
|
updateRoutes(routes);
|
||||||
|
setNeedSave(true);
|
||||||
}}>Create</Button>
|
}}>Create</Button>
|
||||||
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
|
@ -40,7 +40,6 @@ const UserManagement = () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
API.users.list()
|
API.users.list()
|
||||||
.then(data => {
|
.then(data => {
|
||||||
console.log(data);
|
|
||||||
setRows(data.data);
|
setRows(data.data);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
})
|
})
|
||||||
|
|
|
@ -45,7 +45,6 @@ const ServeApps = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const testRoute = (route) => {
|
const testRoute = (route) => {
|
||||||
console.log(newRoute)
|
|
||||||
try {
|
try {
|
||||||
ValidateRoute.validateSync(route);
|
ValidateRoute.validateSync(route);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -206,7 +205,7 @@ const ServeApps = () => {
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Grid2 container spacing={2}>
|
<Grid2 container spacing={2}>
|
||||||
{serveApps && serveApps.filter(app => search.length < 2 || app.Names[0].includes(search)).map((app) => {
|
{serveApps && serveApps.filter(app => search.length < 2 || app.Names[0].toLowerCase().includes(search.toLowerCase())).map((app) => {
|
||||||
return <Grid2 style={gridAnim} xs={12} sm={6} md={6} lg={6} xl={4}>
|
return <Grid2 style={gridAnim} xs={12} sm={6} md={6} lg={6} xl={4}>
|
||||||
<Item>
|
<Item>
|
||||||
<Stack justifyContent='space-around' direction="column" spacing={2} padding={2} divider={<Divider orientation="horizontal" flexItem />}>
|
<Stack justifyContent='space-around' direction="column" spacing={2} padding={2} divider={<Divider orientation="horizontal" flexItem />}>
|
||||||
|
|
|
@ -33,7 +33,7 @@ const MainRoutes = {
|
||||||
element: <Navigate to="/ui" />
|
element: <Navigate to="/ui" />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/ui/',
|
path: '/ui',
|
||||||
element: <DashboardDefault />
|
element: <DashboardDefault />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,7 +18,6 @@ export default function ThemeCustomization({ children }) {
|
||||||
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ?
|
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ?
|
||||||
'dark' : 'light');
|
'dark' : 'light');
|
||||||
|
|
||||||
console.log(theme)
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
const themeTypography = Typography(`'Public Sans', sans-serif`);
|
const themeTypography = Typography(`'Public Sans', sans-serif`);
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
import { purple, pink, deepPurple } from '@mui/material/colors';
|
import { purple, pink, deepPurple } from '@mui/material/colors';
|
||||||
|
|
||||||
const Theme = (colors) => {
|
const Theme = (colors) => {
|
||||||
console.log(colors)
|
|
||||||
const { blue, red, gold, cyan, green, grey } = colors;
|
const { blue, red, gold, cyan, green, grey } = colors;
|
||||||
const greyColors = {
|
const greyColors = {
|
||||||
0: grey[0],
|
0: grey[0],
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "cosmos-server",
|
"name": "cosmos-server",
|
||||||
"version": "0.1.15",
|
"version": "0.1.16",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "test-server.js",
|
"main": "test-server.js",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
"build": " sh build.sh",
|
"build": " sh build.sh",
|
||||||
"dev": "npm run build && npm run start",
|
"dev": "npm run build && npm run start",
|
||||||
"dockerdevbuild": "sh build.sh && npm run client-build && docker build --tag cosmos-dev .",
|
"dockerdevbuild": "sh build.sh && npm run client-build && docker build --tag cosmos-dev .",
|
||||||
"dockerdevrun": "docker stop cosmos-dev; docker rm cosmos-dev; docker run -d -p 80:80 -p 443:443 -e DOCKER_HOST=tcp://host.docker.internal:2375 -e COSMOS_HOSTNAME=localhost -e COSMOS_MONGODB=$MONGODB -e COSMOS_LOG_LEVEL=DEBUG --restart=unless-stopped --name cosmos-dev cosmos-dev",
|
"dockerdevrun": "docker stop cosmos-dev; docker rm cosmos-dev; docker run -d -p 80:80 -p 443:443 -e DOCKER_HOST=tcp://host.docker.internal:2375 -e COSMOS_HOSTNAME=localhost -e COSMOS_MONGODB=$MONGODB -e COSMOS_LOG_LEVEL=DEBUG --restart=unless-stopped -h cosmos-dev --name cosmos-dev cosmos-dev",
|
||||||
"dockerdev": "npm run dockerdevbuild && npm run dockerdevrun"
|
"dockerdev": "npm run dockerdevbuild && npm run dockerdevrun"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
|
|
11
sponsors.js
11
sponsors.js
|
@ -56,18 +56,21 @@ const sponsorsGenerate = async () => {
|
||||||
function changelog() {
|
function changelog() {
|
||||||
// get the changes from last commit message
|
// get the changes from last commit message
|
||||||
const { execSync } = require('child_process')
|
const { execSync } = require('child_process')
|
||||||
const commitMessage = execSync('git log -1 --pretty=%B').toString()
|
let commitMessage = execSync('git log -1 --pretty=%B').toString()
|
||||||
if(!commitMessage.toLocaleLowerCase().startsWith('[release]')) {
|
|
||||||
|
if(!commitMessage.startsWith('[release]')) {
|
||||||
console.log('Not a release commit, skipping changelog update')
|
console.log('Not a release commit, skipping changelog update')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const version = packageJson.version
|
const version = packageJson.version
|
||||||
|
|
||||||
|
commitMessage = commitMessage.replace('[release]', 'Version')
|
||||||
|
|
||||||
// open changelog.md
|
// open changelog.md
|
||||||
const changelog = fs.readFileSync('./changelog.md', 'utf8')
|
const changelog = fs.readFileSync('./changelog.md', 'utf8')
|
||||||
|
|
||||||
// add the new changes to the top of the changelog
|
// add the new changes to the top of the changelog
|
||||||
const newChangelog = `## Version ${version} \n ${commitMessage}\n\n${changelog}`
|
const newChangelog = `## ${commitMessage}\n\n${changelog}`
|
||||||
|
|
||||||
// write the new changelog
|
// write the new changelog
|
||||||
fs.writeFileSync('./changelog.md', newChangelog)
|
fs.writeFileSync('./changelog.md', newChangelog)
|
||||||
|
@ -84,4 +87,4 @@ function changelog() {
|
||||||
}
|
}
|
||||||
|
|
||||||
sponsorsGenerate()
|
sponsorsGenerate()
|
||||||
changelog();
|
// changelog();
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"github.com/azukaar/cosmos-server/src/utils"
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
|
// "github.com/azukaar/cosmos-server/src/docker"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -64,6 +65,8 @@ func checkVersion() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func CRON() {
|
func CRON() {
|
||||||
|
go func() {
|
||||||
gocron.Every(1).Day().At("00:00").Do(checkVersion)
|
gocron.Every(1).Day().At("00:00").Do(checkVersion)
|
||||||
<-gocron.Start()
|
<-gocron.Start()
|
||||||
|
}()
|
||||||
}
|
}
|
|
@ -24,6 +24,7 @@ func LoadConfig() utils.Config {
|
||||||
decoder := json.NewDecoder(file)
|
decoder := json.NewDecoder(file)
|
||||||
config := utils.Config{}
|
config := utils.Config{}
|
||||||
err = decoder.Decode(&config)
|
err = decoder.Decode(&config)
|
||||||
|
|
||||||
// check file is not empty
|
// check file is not empty
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// check error is not empty
|
// check error is not empty
|
||||||
|
@ -49,5 +50,8 @@ func LoadConfig() utils.Config {
|
||||||
|
|
||||||
utils.LoadBaseMainConfig(config)
|
utils.LoadBaseMainConfig(config)
|
||||||
|
|
||||||
|
configJson, _ := json.MarshalIndent(config, "", " ")
|
||||||
|
utils.Debug("Loaded Configuration " + (string)(configJson))
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
|
@ -21,7 +21,7 @@ func SecureContainerRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
if(req.Method == "GET") {
|
if(req.Method == "GET") {
|
||||||
container, err := DockerClient.ContainerInspect(DockerContext, containerName)
|
container, err := DockerClient.ContainerInspect(DockerContext, containerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("ContainerSecure", err)
|
utils.Error("ContainerSecureInscpect", err)
|
||||||
utils.HTTPError(w, "Internal server error: " + err.Error(), http.StatusInternalServerError, "DS002")
|
utils.HTTPError(w, "Internal server error: " + err.Error(), http.StatusInternalServerError, "DS002")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ func SecureContainerRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
_, errEdit := EditContainer(container.ID, container)
|
_, errEdit := EditContainer(container.ID, container)
|
||||||
if errEdit != nil {
|
if errEdit != nil {
|
||||||
utils.Error("ContainerSecure", errEdit)
|
utils.Error("ContainerSecureEdit", errEdit)
|
||||||
utils.HTTPError(w, "Internal server error: " + err.Error(), http.StatusInternalServerError, "DS003")
|
utils.HTTPError(w, "Internal server error: " + err.Error(), http.StatusInternalServerError, "DS003")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package docker
|
||||||
import (
|
import (
|
||||||
"github.com/azukaar/cosmos-server/src/utils"
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BootstrapAllContainersFromTags() []error {
|
func BootstrapAllContainersFromTags() []error {
|
||||||
|
@ -30,18 +31,27 @@ func BootstrapAllContainersFromTags() []error {
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func BootstrapContainerFromTags(containerID string) error {
|
func BootstrapContainerFromTags(containerID string) error {
|
||||||
errD := Connect()
|
errD := Connect()
|
||||||
if errD != nil {
|
if errD != nil {
|
||||||
return errD
|
return errD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selfContainer := types.ContainerJSON{}
|
||||||
|
if os.Getenv("HOSTNAME") != "" {
|
||||||
|
var errS error
|
||||||
|
selfContainer, errS = DockerClient.ContainerInspect(DockerContext, os.Getenv("HOSTNAME"))
|
||||||
|
if errS != nil {
|
||||||
|
utils.Error("DockerContainerBootstrapSelfInspect", errS)
|
||||||
|
return errS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
utils.Log("Bootstrap Container From Tags: " + containerID)
|
utils.Log("Bootstrap Container From Tags: " + containerID)
|
||||||
|
|
||||||
container, err := DockerClient.ContainerInspect(DockerContext, containerID)
|
container, err := DockerClient.ContainerInspect(DockerContext, containerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("Docker Container Bootstrap Inspect", err)
|
utils.Error("DockerContainerBootstrapInspect", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,12 +61,10 @@ func BootstrapContainerFromTags(containerID string) error {
|
||||||
utils.Log(container.Name+": Checking Force network secured")
|
utils.Log(container.Name+": Checking Force network secured")
|
||||||
|
|
||||||
// check if connected to bridge and to a cosmos network
|
// check if connected to bridge and to a cosmos network
|
||||||
isCon, errC := IsConnectedToNetwork(container, "bridge")
|
isCon := IsConnectedToNetwork(container, "bridge")
|
||||||
isCosmosCon, _ := IsConnectedToASecureCosmosNetwork(container)
|
isCosmosCon, _ := IsConnectedToASecureCosmosNetwork(selfContainer, container)
|
||||||
|
|
||||||
if errC != nil {
|
if isCon || !isCosmosCon {
|
||||||
return errC
|
|
||||||
} else if isCon || !isCosmosCon {
|
|
||||||
utils.Log(container.Name+": Needs isolating on a secured network")
|
utils.Log(container.Name+": Needs isolating on a secured network")
|
||||||
needsRestart := false
|
needsRestart := false
|
||||||
var errCT error
|
var errCT error
|
||||||
|
@ -66,7 +74,7 @@ func BootstrapContainerFromTags(containerID string) error {
|
||||||
return errCT
|
return errCT
|
||||||
}
|
}
|
||||||
if needsRestart {
|
if needsRestart {
|
||||||
utils.Log(container.Name+": Will connect to new network after restart")
|
utils.Log(container.Name+": Will restart to apply changes")
|
||||||
needsUpdate = true
|
needsUpdate = true
|
||||||
} else {
|
} else {
|
||||||
utils.Log(container.Name+": Connected to new network")
|
utils.Log(container.Name+": Connected to new network")
|
||||||
|
|
|
@ -3,13 +3,12 @@ package docker
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
"github.com/azukaar/cosmos-server/src/utils"
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
// natting "github.com/docker/go-connections/nat"
|
// natting "github.com/docker/go-connections/nat"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
// network "github.com/docker/docker/api/types/network"
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,6 +36,18 @@ func getIdFromName(name string) (string, error) {
|
||||||
var DockerIsConnected = false
|
var DockerIsConnected = false
|
||||||
|
|
||||||
func Connect() error {
|
func Connect() error {
|
||||||
|
if DockerClient != nil {
|
||||||
|
// check if connection is still alive
|
||||||
|
ping, err := DockerClient.Ping(DockerContext)
|
||||||
|
if ping.APIVersion != "" && err == nil {
|
||||||
|
DockerIsConnected = true
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
DockerIsConnected = false
|
||||||
|
DockerClient = nil
|
||||||
|
utils.Error("Docker Connection died, will try to connect again", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
if DockerClient == nil {
|
if DockerClient == nil {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
client, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
client, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||||
|
@ -69,11 +80,17 @@ func Connect() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func EditContainer(containerID string, newConfig types.ContainerJSON) (string, error) {
|
func EditContainer(containerID string, newConfig types.ContainerJSON) (string, error) {
|
||||||
|
DockerNetworkLock <- true
|
||||||
|
defer func() {
|
||||||
|
<-DockerNetworkLock
|
||||||
|
utils.Debug("Unlocking EDIT Container")
|
||||||
|
}()
|
||||||
|
|
||||||
errD := Connect()
|
errD := Connect()
|
||||||
if errD != nil {
|
if errD != nil {
|
||||||
return "", errD
|
return "", errD
|
||||||
}
|
}
|
||||||
utils.Log("Container updating " + containerID)
|
utils.Log("EditContainer - Container updating " + containerID)
|
||||||
|
|
||||||
// get container informations
|
// get container informations
|
||||||
// https://godoc.org/github.com/docker/docker/api/types#ContainerJSON
|
// https://godoc.org/github.com/docker/docker/api/types#ContainerJSON
|
||||||
|
@ -98,7 +115,19 @@ func EditContainer(containerID string, newConfig types.ContainerJSON) (string, e
|
||||||
return "", removeError
|
return "", removeError
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.Log("Container stopped " + containerID)
|
// wait for container to be destroyed
|
||||||
|
//
|
||||||
|
for {
|
||||||
|
_, err := DockerClient.ContainerInspect(DockerContext, containerID)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
utils.Log("EditContainer - Waiting for container to be destroyed")
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Log("EditContainer - Container stopped " + containerID)
|
||||||
|
|
||||||
// recreate container with new informations
|
// recreate container with new informations
|
||||||
createResponse, createError := DockerClient.ContainerCreate(
|
createResponse, createError := DockerClient.ContainerCreate(
|
||||||
|
@ -110,25 +139,34 @@ func EditContainer(containerID string, newConfig types.ContainerJSON) (string, e
|
||||||
newName,
|
newName,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// re-connect to networks
|
||||||
|
for networkName, _ := range oldContainer.NetworkSettings.Networks {
|
||||||
|
errNet := ConnectToNetworkSync(networkName, createResponse.ID)
|
||||||
|
if errNet != nil {
|
||||||
|
utils.Error("EditContainer - Failed to connect to network " + networkName, errNet)
|
||||||
|
} else {
|
||||||
|
utils.Debug("EditContainer - New Container connected to network " + networkName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
runError := DockerClient.ContainerStart(DockerContext, createResponse.ID, types.ContainerStartOptions{})
|
runError := DockerClient.ContainerStart(DockerContext, createResponse.ID, types.ContainerStartOptions{})
|
||||||
|
|
||||||
if runError != nil {
|
if runError != nil {
|
||||||
return "", runError
|
return "", runError
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.Log("Container recreated " + createResponse.ID)
|
utils.Log("EditContainer - Container recreated " + createResponse.ID)
|
||||||
|
|
||||||
if createError != nil {
|
if createError != nil {
|
||||||
// attempt to restore container
|
// attempt to restore container
|
||||||
_, restoreError := DockerClient.ContainerCreate(DockerContext, oldContainer.Config, nil, nil, nil, oldContainer.Name)
|
_, restoreError := DockerClient.ContainerCreate(DockerContext, oldContainer.Config, nil, nil, nil, oldContainer.Name)
|
||||||
if restoreError != nil {
|
if restoreError != nil {
|
||||||
utils.Error("Failed to restore Docker Container after update failure", restoreError)
|
utils.Error("EditContainer - Failed to restore Docker Container after update failure", restoreError)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", createError
|
return "", createError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return createResponse.ID, nil
|
return createResponse.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +212,14 @@ func AddLabels(containerConfig types.ContainerJSON, labels map[string]string) er
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RemoveLabels(containerConfig types.ContainerJSON, labels []string) error {
|
||||||
|
for _, label := range labels {
|
||||||
|
delete(containerConfig.Config.Labels, label)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func IsLabel(containerConfig types.ContainerJSON, label string) bool {
|
func IsLabel(containerConfig types.ContainerJSON, label string) bool {
|
||||||
if containerConfig.Config.Labels[label] == "true" {
|
if containerConfig.Config.Labels[label] == "true" {
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -25,11 +25,25 @@ func DockerListenEvents() error {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
utils.Error("Docker Event Error", err)
|
utils.Error("Docker Event Error", err)
|
||||||
|
// Check connection
|
||||||
|
errD := Connect()
|
||||||
|
if errD != nil {
|
||||||
|
utils.Fatal("Docker connection died, couldn't recover... Restarting", errD)
|
||||||
|
}
|
||||||
|
msgs, errs = DockerClient.Events(context.Background(), types.EventsOptions{})
|
||||||
|
|
||||||
case msg := <-msgs:
|
case msg := <-msgs:
|
||||||
utils.Debug("Docker Event: " + msg.Type + " " + msg.Action + " " + msg.Actor.ID)
|
utils.Debug("Docker Event: " + msg.Type + " " + msg.Action + " " + msg.Actor.ID)
|
||||||
if msg.Type == "container" && msg.Action == "start" {
|
if msg.Type == "container" && msg.Action == "start" {
|
||||||
onDockerCreated(msg.Actor.ID)
|
onDockerCreated(msg.Actor.ID)
|
||||||
}
|
}
|
||||||
|
// on container destroy and network disconnect
|
||||||
|
if msg.Type == "container" && msg.Action == "destroy" {
|
||||||
|
onDockerDestroyed(msg.Actor.ID)
|
||||||
|
}
|
||||||
|
if msg.Type == "network" && msg.Action == "disconnect" {
|
||||||
|
onNetworkDisconnect(msg.Actor.ID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -41,3 +55,13 @@ func onDockerCreated(containerID string) {
|
||||||
utils.Debug("onDockerCreated: " + containerID)
|
utils.Debug("onDockerCreated: " + containerID)
|
||||||
BootstrapContainerFromTags(containerID)
|
BootstrapContainerFromTags(containerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func onDockerDestroyed(containerID string) {
|
||||||
|
utils.Debug("onDockerDestroyed: " + containerID)
|
||||||
|
NetworkCleanUp()
|
||||||
|
}
|
||||||
|
|
||||||
|
func onNetworkDisconnect(networkID string) {
|
||||||
|
utils.Debug("onNetworkDisconnect: " + networkID)
|
||||||
|
NetworkCleanUp()
|
||||||
|
}
|
|
@ -2,6 +2,8 @@ package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/azukaar/cosmos-server/src/utils"
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
|
|
||||||
|
@ -10,6 +12,9 @@ import (
|
||||||
natting "github.com/docker/go-connections/nat"
|
natting "github.com/docker/go-connections/nat"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// use a semaphore lock
|
||||||
|
var DockerNetworkLock = make(chan bool, 1)
|
||||||
|
|
||||||
func CreateCosmosNetwork() (string, error) {
|
func CreateCosmosNetwork() (string, error) {
|
||||||
// check if network exists already
|
// check if network exists already
|
||||||
networks, err := DockerClient.NetworkList(DockerContext, types.NetworkListOptions{})
|
networks, err := DockerClient.NetworkList(DockerContext, types.NetworkListOptions{})
|
||||||
|
@ -35,25 +40,27 @@ func CreateCosmosNetwork() (string, error) {
|
||||||
utils.Log("Creating new secure network: " + newNeworkName)
|
utils.Log("Creating new secure network: " + newNeworkName)
|
||||||
|
|
||||||
// create network
|
// create network
|
||||||
_, err = DockerClient.NetworkCreate(DockerContext, newNeworkName, types.NetworkCreate{
|
newNetHan, err := DockerClient.NetworkCreate(DockerContext, newNeworkName, types.NetworkCreate{
|
||||||
Attachable: true,
|
Attachable: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
utils.Debug("New network created: " + newNeworkName + " with id " + newNetHan.ID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("Docker Network Create", err)
|
utils.Error("Docker Network Create", err)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
//if running in Docker, connect to main network
|
//if running in Docker, connect to main network
|
||||||
utils.Debug("HOSTNAME: " + os.Getenv("HOSTNAME"))
|
// utils.Debug("HOSTNAME: " + os.Getenv("HOSTNAME"))
|
||||||
if os.Getenv("HOSTNAME") != "" {
|
// if os.Getenv("HOSTNAME") != "" {
|
||||||
err := DockerClient.NetworkConnect(DockerContext, newNeworkName, os.Getenv("HOSTNAME"), &network.EndpointSettings{})
|
// err := DockerClient.NetworkConnect(DockerContext, newNeworkName, os.Getenv("HOSTNAME"), &network.EndpointSettings{})
|
||||||
|
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
utils.Error("Docker Network Connect", err)
|
// utils.Error("Docker Network Connect", err)
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return newNeworkName, nil
|
return newNeworkName, nil
|
||||||
}
|
}
|
||||||
|
@ -65,41 +72,53 @@ func ConnectToSecureNetwork(containerConfig types.ContainerJSON) (bool, error) {
|
||||||
return false, errD
|
return false, errD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needsRestart := false
|
||||||
|
netName := ""
|
||||||
|
|
||||||
if(!HasLabel(containerConfig, "cosmos-network-name")) {
|
if(!HasLabel(containerConfig, "cosmos-network-name")) {
|
||||||
newNetwork, errNC := CreateCosmosNetwork()
|
newNetwork, errNC := CreateCosmosNetwork()
|
||||||
if errNC != nil {
|
if errNC != nil {
|
||||||
utils.Error("Docker Network Create", errNC)
|
utils.Error("DockerSecureNetworkCreate", errNC)
|
||||||
return false, errNC
|
return false, errNC
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.Log("Connected to new secure network for container: " + newNetwork)
|
utils.Log("Added label to new secure network for container: " + newNetwork)
|
||||||
|
|
||||||
AddLabels(containerConfig, map[string]string{
|
AddLabels(containerConfig, map[string]string{
|
||||||
"cosmos-network-name": newNetwork,
|
"cosmos-network-name": newNetwork,
|
||||||
})
|
})
|
||||||
|
|
||||||
return true, nil
|
needsRestart = true
|
||||||
}
|
netName = newNetwork
|
||||||
|
} else {
|
||||||
|
netName = GetLabel(containerConfig, "cosmos-network-name")
|
||||||
|
|
||||||
netName := GetLabel(containerConfig, "cosmos-network-name")
|
//if network doesn't exists
|
||||||
|
_, err := DockerClient.NetworkInspect(DockerContext, netName, types.NetworkInspectOptions{})
|
||||||
connected, err := IsConnectedToNetwork(containerConfig, netName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("IsConnectedToNetwork", err)
|
utils.Error("Container tries to connect to a non existing Cosmos network, resetting", err)
|
||||||
return false, err
|
RemoveLabels(containerConfig, []string{"cosmos-network-name"})
|
||||||
|
return ConnectToSecureNetwork(containerConfig)
|
||||||
}
|
}
|
||||||
if(connected) {
|
|
||||||
|
// else check if already connected
|
||||||
|
if(IsConnectedToNetwork(containerConfig, netName)) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
errCo := DockerClient.NetworkConnect(DockerContext, netName, containerConfig.ID, &network.EndpointSettings{})
|
// at this point network is created and container is not connected to it
|
||||||
|
|
||||||
|
errCo := ConnectToNetworkSync(netName, containerConfig.ID)
|
||||||
|
|
||||||
|
utils.Log("Created and connected to secure network: " + netName)
|
||||||
|
|
||||||
if errCo != nil {
|
if errCo != nil {
|
||||||
utils.Error("Docker Network Connect", errCo)
|
utils.Error("ConnectToSecureNetworkConnect", errCo)
|
||||||
return false, errCo
|
return false, errCo
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return needsRestart, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnexposeAllPorts(container *types.ContainerJSON) error {
|
func UnexposeAllPorts(container *types.ContainerJSON) error {
|
||||||
|
@ -120,22 +139,21 @@ func GetAllPorts(container types.ContainerJSON) []string {
|
||||||
return ports
|
return ports
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsConnectedToNetwork(containerConfig types.ContainerJSON, networkName string) (bool, error) {
|
func IsConnectedToNetwork(containerConfig types.ContainerJSON, networkName string) bool {
|
||||||
if(containerConfig.NetworkSettings == nil) {
|
if(containerConfig.NetworkSettings == nil) {
|
||||||
utils.Error("IsConnectedToNetwork: NetworkSettings is nil", nil)
|
return false
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, _ := range containerConfig.NetworkSettings.Networks {
|
for name, _ := range containerConfig.NetworkSettings.Networks {
|
||||||
if name == networkName {
|
if name == networkName {
|
||||||
return true, nil
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsConnectedToASecureCosmosNetwork(containerConfig types.ContainerJSON) (bool, error) {
|
func IsConnectedToASecureCosmosNetwork(self types.ContainerJSON, containerConfig types.ContainerJSON) (bool, error) {
|
||||||
if(containerConfig.NetworkSettings == nil) {
|
if(containerConfig.NetworkSettings == nil) {
|
||||||
utils.Error("IsConnectedToASecureCosmosNetwork: NetworkSettings is nil", nil)
|
utils.Error("IsConnectedToASecureCosmosNetwork: NetworkSettings is nil", nil)
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -148,12 +166,13 @@ func IsConnectedToASecureCosmosNetwork(containerConfig types.ContainerJSON) (boo
|
||||||
for name, _ := range containerConfig.NetworkSettings.Networks {
|
for name, _ := range containerConfig.NetworkSettings.Networks {
|
||||||
if name == GetLabel(containerConfig, "cosmos-network-name") {
|
if name == GetLabel(containerConfig, "cosmos-network-name") {
|
||||||
if os.Getenv("HOSTNAME") != "" {
|
if os.Getenv("HOSTNAME") != "" {
|
||||||
// TODO: Check if connected to network first
|
if(!IsConnectedToNetwork(self, name)) {
|
||||||
DockerClient.NetworkConnect(DockerContext, name, os.Getenv("HOSTNAME"), &network.EndpointSettings{})
|
err := DockerClient.NetworkConnect(DockerContext, name, os.Getenv("HOSTNAME"), &network.EndpointSettings{})
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// utils.Error("Docker Network Connect EXISTING ", err)
|
utils.Error("Docker Network Connect EXISTING ", err)
|
||||||
// return false, err
|
return false, err
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
@ -161,3 +180,124 @@ func IsConnectedToASecureCosmosNetwork(containerConfig types.ContainerJSON) (boo
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ConnectToNetworkIfNotConnected(containerConfig types.ContainerJSON, networkName string) error {
|
||||||
|
if(IsConnectedToNetwork(containerConfig, networkName)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ConnectToNetworkSync(networkName, containerConfig.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConnectToNetworkSync(networkName string, containerID string) error {
|
||||||
|
err := DockerClient.NetworkConnect(DockerContext, networkName, containerID, &network.EndpointSettings{})
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("ConnectToNetworkSync", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for connection to be established
|
||||||
|
retries := 10
|
||||||
|
for {
|
||||||
|
newContainer, err := DockerClient.ContainerInspect(DockerContext, containerID)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("ConnectToNetworkSync", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if(IsConnectedToNetwork(newContainer, networkName)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
retries--
|
||||||
|
if retries == 0 {
|
||||||
|
return errors.New("ConnectToNetworkSync: Timeout")
|
||||||
|
}
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
utils.Log("Connected "+containerID+" to network: " + networkName)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkCleanUp() {
|
||||||
|
DockerNetworkLock <- true
|
||||||
|
defer func() { <-DockerNetworkLock }()
|
||||||
|
|
||||||
|
config := utils.GetMainConfig()
|
||||||
|
|
||||||
|
utils.Log("Cleaning up orphan networks...")
|
||||||
|
|
||||||
|
// list every network
|
||||||
|
networks, err := DockerClient.NetworkList(DockerContext, types.NetworkListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("NetworkCleanUpList", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// check if network is empty or has only self as container
|
||||||
|
for _, networkHollow := range networks {
|
||||||
|
utils.Debug("Checking network: " + networkHollow.Name)
|
||||||
|
|
||||||
|
if(networkHollow.Name == "bridge" || networkHollow.Name == "host" || networkHollow.Name == "none") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// inspect network because the Docker API is a complete mess :)
|
||||||
|
network, err := DockerClient.NetworkInspect(DockerContext, networkHollow.ID, types.NetworkInspectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("NetworkCleanUpInspect", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if(len(network.Containers) > 1) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Debug("Ready to Check network: " + network.Name)
|
||||||
|
|
||||||
|
if(config.DockerConfig.SkipPruneNetwork){
|
||||||
|
utils.Debug("Skipping network prune")
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!config.DockerConfig.SkipPruneNetwork && len(network.Containers) == 0) {
|
||||||
|
utils.Log("Removing orphan network: " + network.Name)
|
||||||
|
err := DockerClient.NetworkRemove(DockerContext, network.ID)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("DockerNetworkCleanupRemove", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
self := os.Getenv("HOSTNAME")
|
||||||
|
if self == "" {
|
||||||
|
utils.Warn("Skipping zombie network cleanup because not a docker cosmos container")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Debug("Checking self name: " + self)
|
||||||
|
utils.Debug("Checking non-empty network: " + network.Name)
|
||||||
|
|
||||||
|
containsCosmos := false
|
||||||
|
for _, container := range network.Containers {
|
||||||
|
utils.Debug("Checking name: " + container.Name)
|
||||||
|
if(container.Name == self) {
|
||||||
|
containsCosmos = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(containsCosmos) {
|
||||||
|
utils.Log("Disconnecting and removing zombie network: " + network.Name)
|
||||||
|
err := DockerClient.NetworkDisconnect(DockerContext, network.ID, self, true)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("DockerNetworkCleanupDisconnect", err)
|
||||||
|
}
|
||||||
|
err = DockerClient.NetworkRemove(DockerContext, network.ID)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("DockerNetworkCleanupRemove", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
"encoding/json"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"github.com/go-chi/chi/middleware"
|
"github.com/go-chi/chi/middleware"
|
||||||
|
@ -36,13 +35,7 @@ func startHTTPServer(router *mux.Router) {
|
||||||
|
|
||||||
func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
|
func startHTTPSServer(router *mux.Router, tlsCert string, tlsKey string) {
|
||||||
config := utils.GetMainConfig()
|
config := utils.GetMainConfig()
|
||||||
|
serverHostname := "0.0.0.0"
|
||||||
|
|
||||||
// check if Docker overwrite Hostname
|
|
||||||
serverHostname := "0.0.0.0" //utils.GetMainConfig().HTTPConfig.Hostname
|
|
||||||
// if os.Getenv("HOSTNAME") != "" {
|
|
||||||
// serverHostname = os.Getenv("HOSTNAME")
|
|
||||||
// }
|
|
||||||
|
|
||||||
cfg := simplecert.Default
|
cfg := simplecert.Default
|
||||||
|
|
||||||
|
@ -147,9 +140,6 @@ func StartServer() {
|
||||||
serverPortHTTP = config.HTTPPort
|
serverPortHTTP = config.HTTPPort
|
||||||
serverPortHTTPS = config.HTTPSPort
|
serverPortHTTPS = config.HTTPSPort
|
||||||
|
|
||||||
configJson, _ := json.MarshalIndent(config, "", " ")
|
|
||||||
utils.Debug("Configuration" + (string)(configJson))
|
|
||||||
|
|
||||||
var tlsCert = config.TLSCert
|
var tlsCert = config.TLSCert
|
||||||
var tlsKey= config.TLSKey
|
var tlsKey= config.TLSKey
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,7 @@ type Config struct {
|
||||||
DisableUserManagement bool
|
DisableUserManagement bool
|
||||||
NewInstall bool `validate:"boolean"`
|
NewInstall bool `validate:"boolean"`
|
||||||
HTTPConfig HTTPConfig `validate:"required,dive,required"`
|
HTTPConfig HTTPConfig `validate:"required,dive,required"`
|
||||||
|
DockerConfig DockerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPConfig struct {
|
type HTTPConfig struct {
|
||||||
|
@ -92,6 +93,10 @@ type HTTPConfig struct {
|
||||||
SSLEmail string `validate:"omitempty,email"`
|
SSLEmail string `validate:"omitempty,email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DockerConfig struct {
|
||||||
|
SkipPruneNetwork bool
|
||||||
|
}
|
||||||
|
|
||||||
type ProxyConfig struct {
|
type ProxyConfig struct {
|
||||||
Routes []ProxyRouteConfig
|
Routes []ProxyRouteConfig
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue