Skip to content

Commit

Permalink
Merge pull request #1 from cego/user-and-networking
Browse files Browse the repository at this point in the history
User and networking
  • Loading branch information
SlyngDK authored Jan 10, 2022
2 parents 1beccce + 188174e commit aeb6bfb
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 31 deletions.
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,4 @@ jobs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./latest/container-manager
asset_name: container-manager
asset_content_type: application/octet-stream
6 changes: 5 additions & 1 deletion config.example.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
interval: 5m
cleanup: false
logLevel: info
containers:
- name: <container name>
image: <repo:tag>
attachAllNetwork: true # Auto attach container to all available networks
ignoredNetworks: [] # Networks to not attach
volumes: []
volumes: []
user: root
networkMode: default
95 changes: 65 additions & 30 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import (

type Config struct {
Interval time.Duration `yaml:"interval"`
Cleanup bool `yaml:"cleanup"`
Containers []Container `yaml:"containers"`
LogLevel string `yaml:"logLevel"`
}

type Container struct {
Expand All @@ -31,14 +33,16 @@ type Container struct {
Volumes []string `yaml:"volumes"`
AttachAllNetwork bool `yaml:"attachAllNetwork"`
IgnoredNetworks []string `yaml:"ignoredNetworks"`
User string `yaml:"user"`
NetworkMode string `yaml:"networkMode"`
}

var ignoredNetworkNames = []string{"ingress", "host", "none"}

func main() {
l := logrus.New()
l.Out = os.Stdout
l.Level = logrus.DebugLevel
l.Level = logrus.InfoLevel

configFile := "config.yaml"
if len(os.Args) >= 2 {
Expand All @@ -47,6 +51,15 @@ func main() {

config := loadConfig(l, configFile)

if config.LogLevel != "" {
level, err := logrus.ParseLevel(config.LogLevel)
if err != nil {
l.WithError(err).Errorf("failed to parse log level: %s", config.LogLevel)
os.Exit(1)
}
l.Level = level
}

mgmt, err := NewManager(l)
if err != nil {
l.Error(err)
Expand Down Expand Up @@ -76,7 +89,10 @@ func main() {
} else if rawSig == syscall.SIGINT || rawSig == syscall.SIGTERM {
sig := rawSig.String()
l.WithField("signal", sig).Info("Caught signal, shutting down")
mgmt.stopContainers(config)
if config.Cleanup {
l.Infof("Cleaning up containers")
mgmt.stopContainers(config)
}
done <- true
}
}
Expand Down Expand Up @@ -105,6 +121,13 @@ func loadConfig(l *logrus.Logger, configFile string) *Config {
if config.Interval <= 0 {
config.Interval = 5 * time.Minute
}

for _, c := range config.Containers {
if c.NetworkMode == "" {
c.NetworkMode = "default"
}
}

return config
}

Expand All @@ -130,33 +153,36 @@ func (m *manager) run(config *Config) {
m.l.Error(err)
return
}
m.l.Debugf("Networks: %s", networkNames)

for _, c := range config.Containers {

var networks []string = nil
if c.AttachAllNetwork {
m.l.Debugf("Attaching all networks")
networks = make([]string, len(networkNames))
copy(networks, networkNames)
networks = remove(networks, c.IgnoredNetworks)
}
if len(networks) == 0 {
networks = append(networks, "bridge")
}

err = m.ensureContainer(c.Name, c.Image, networks, c.Volumes)
err = m.ensureContainer(c, networks)
if err != nil {
m.l.WithError(err).Errorf("failed to enure container %s", c.Name)
}
}
}

func (m *manager) ensureContainer(name, image string, networks, volumes []string) error {
containers, err := m.cli.ContainerList(m.ctx, types.ContainerListOptions{All: true, Filters: filters.NewArgs(filters.Arg("name", fmt.Sprintf("^/%s$", name)))})
func (m *manager) ensureContainer(config Container, networks []string) error {
containers, err := m.cli.ContainerList(m.ctx, types.ContainerListOptions{All: true, Filters: filters.NewArgs(filters.Arg("name", fmt.Sprintf("^/%s$", config.Name)))})
if err != nil {
return err
}

if len(containers) > 1 {
return fmt.Errorf("found more than one container matching %s", name)
return fmt.Errorf("found more than one container matching %s", config.Name)
}

create := false
Expand All @@ -173,20 +199,25 @@ func (m *manager) ensureContainer(name, image string, networks, volumes []string
}
sort.Strings(networkNames)

if image != c.Image {
m.l.Debugf("Image differ, recreate %s", name)
if config.Image != c.Image {
m.l.Debugf("Image differ, recreate %s", config.Name)
reCreate = true
}

if config.NetworkMode != c.HostConfig.NetworkMode {
m.l.Debugf("NetworkMode differ, recreate %s != %s", config.NetworkMode, c.HostConfig.NetworkMode)
reCreate = true
}

m.l.Debugf("Networks expected: %s", networks)
m.l.Debugf("Networks current: %s", networkNames)
if !reflect.DeepEqual(networks, networkNames) {
m.l.Debugf("Network config differ, recreate %s", name)
m.l.Debugf("Networks expected: %s", networks)
m.l.Debugf("Networks current: %s", networkNames)
m.l.Debugf("Network config differ, recreate %s", config.Name)
reCreate = true
}

volumesExpected := make([]string, len(volumes))
for i, v := range volumes {
volumesExpected := make([]string, len(config.Volumes))
for i, v := range config.Volumes {
volume, err := loader.ParseVolume(v)
if err != nil {
return err
Expand Down Expand Up @@ -219,7 +250,7 @@ func (m *manager) ensureContainer(name, image string, networks, volumes []string
}

if !stringSlicesEqual(volumesCurrent, volumesExpected) {
m.l.Debugf("Volume config differ, recreate %s", name)
m.l.Debugf("Volume config differ, recreate %s", config.Name)
m.l.Debugf("Volumes expected: %s", volumesExpected)
m.l.Debugf("Volumes Current: %s", volumesCurrent)
reCreate = true
Expand All @@ -230,76 +261,80 @@ func (m *manager) ensureContainer(name, image string, networks, volumes []string

if create || reCreate {
images, err := m.cli.ImageList(m.ctx, types.ImageListOptions{
Filters: filters.NewArgs(filters.Arg("reference", fmt.Sprintf("%s", image))),
Filters: filters.NewArgs(filters.Arg("reference", fmt.Sprintf("%s", config.Image))),
})
if err != nil {
return err
}

if len(images) == 0 {
m.l.Infof("Pulling image: %s\n", image)
out, err := m.cli.ImagePull(m.ctx, image, types.ImagePullOptions{})
m.l.Infof("Pulling image: %s\n", config.Image)
out, err := m.cli.ImagePull(m.ctx, config.Image, types.ImagePullOptions{})
if err != nil {
return err
}
defer out.Close()

io.Copy(ioutil.Discard, out)
m.l.Infof("Pulled image: %s\n", image)
m.l.Infof("Pulled image: %s\n", config.Image)
}
}

if reCreate {
m.l.Infof("Recreating container: %s\n", name)
m.l.Infof("Stopping old container: %s\n", name)
containerID := fmt.Sprintf("/%s", name)
m.l.Infof("Recreating container: %s\n", config.Name)
m.l.Infof("Stopping old container: %s\n", config.Name)
containerID := fmt.Sprintf("/%s", config.Name)
if state == "running" {
timeout := 30 * time.Second
err = m.cli.ContainerStop(m.ctx, containerID, &timeout)
if err != nil {
return err
}
}
m.l.Infof("Removing old container: %s\n", name)
m.l.Infof("Removing old container: %s\n", config.Name)
err = m.cli.ContainerRemove(m.ctx, containerID, types.ContainerRemoveOptions{RemoveVolumes: false, Force: true})
if err != nil {
return err
}
}

if create || reCreate {
m.l.Infof("Creating container: %s\n", name)
m.l.Infof("Creating container: %s\n", config.Name)
c, err := m.cli.ContainerCreate(m.ctx, &container.Config{
Image: image,
Image: config.Image,
Volumes: nil,
User: config.User,
}, &container.HostConfig{
Binds: volumes,
Binds: config.Volumes,
RestartPolicy: container.RestartPolicy{Name: "always"},
}, nil, nil, name)
NetworkMode: container.NetworkMode(config.NetworkMode),
}, nil, nil, config.Name)
if err != nil {
return err
}

m.l.Debugf("Networks to connect: %s", networks)
for _, n := range networks {
m.l.Debugf("Connecting %s to network %s", config.Name, n)
err = m.cli.NetworkConnect(m.ctx, n, c.ID, nil)
if err != nil {
return err
m.l.WithError(err).Errorf("failed to connect network %s", n)
}
}

}

c := m.getContainer(name)
c := m.getContainer(config.Name)
if c != nil {
if c.State != "running" {
m.l.Infof("Starting container %s with id %s\n", name, c.ID)
m.l.Infof("Starting container %s with id %s\n", config.Name, c.ID)

err = m.cli.ContainerStart(m.ctx, c.ID, types.ContainerStartOptions{})
if err != nil {
return err
}

m.l.Infof("Started container %s with id %s\n", name, c.ID)
m.l.Infof("Started container %s with id %s\n", config.Name, c.ID)
}
}

Expand Down

0 comments on commit aeb6bfb

Please sign in to comment.