[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:
Yann Stepienik 2023-04-11 18:33:49 +01:00
parent fc94a56f6c
commit c3401064c6
18 changed files with 318 additions and 83 deletions

View file

@ -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

View file

@ -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 />

View file

@ -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);
}) })

View file

@ -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 />}>

View file

@ -33,7 +33,7 @@ const MainRoutes = {
element: <Navigate to="/ui" /> element: <Navigate to="/ui" />
}, },
{ {
path: '/ui/', path: '/ui',
element: <DashboardDefault /> element: <DashboardDefault />
}, },
{ {

View file

@ -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`);

View file

@ -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],

View file

@ -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": {

View file

@ -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();

View file

@ -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() {
gocron.Every(1).Day().At("00:00").Do(checkVersion) go func() {
<-gocron.Start() gocron.Every(1).Day().At("00:00").Do(checkVersion)
<-gocron.Start()
}()
} }

View file

@ -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
@ -48,6 +49,9 @@ func LoadConfig() utils.Config {
} }
utils.LoadBaseMainConfig(config) utils.LoadBaseMainConfig(config)
configJson, _ := json.MarshalIndent(config, "", " ")
utils.Debug("Loaded Configuration " + (string)(configJson))
return config return config
} }

View file

@ -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
} }

View file

@ -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")

View file

@ -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,24 +139,33 @@ 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

View file

@ -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)
}
} }
} }
}() }()
@ -40,4 +54,14 @@ func DockerListenEvents() error {
func onDockerCreated(containerID string) { 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()
} }

View file

@ -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")
//if network doesn't exists
_, err := DockerClient.NetworkInspect(DockerContext, netName, types.NetworkInspectOptions{})
if err != nil {
utils.Error("Container tries to connect to a non existing Cosmos network, resetting", err)
RemoveLabels(containerConfig, []string{"cosmos-network-name"})
return ConnectToSecureNetwork(containerConfig)
}
// else check if already connected
if(IsConnectedToNetwork(containerConfig, netName)) {
return false, nil
}
} }
netName := GetLabel(containerConfig, "cosmos-network-name") // at this point network is created and container is not connected to it
connected, err := IsConnectedToNetwork(containerConfig, netName) errCo := ConnectToNetworkSync(netName, containerConfig.ID)
if err != nil {
utils.Error("IsConnectedToNetwork", err) utils.Log("Created and connected to secure network: " + netName)
return false, err
}
if(connected) {
return false, nil
}
errCo := DockerClient.NetworkConnect(DockerContext, netName, containerConfig.ID, &network.EndpointSettings{})
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)
}
}
}
}

View file

@ -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

View file

@ -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
} }