-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial support for Icinga Notifications
Inspired by the existing code for the Icinga DB, support for Icinga Notifications was added. Thus, there might be some level of code duplication between those two. The custom Icinga 2 configuration was sourced from the Icinga Notifications repository, but edited to not being parsed as a faulty Go template.
- Loading branch information
Showing
12 changed files
with
695 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
package notifications | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/docker/docker/api/types" | ||
"github.com/docker/docker/api/types/container" | ||
"github.com/docker/docker/api/types/mount" | ||
"github.com/docker/docker/api/types/network" | ||
"github.com/docker/docker/client" | ||
"github.com/docker/go-connections/nat" | ||
"github.com/icinga/icinga-testing/services" | ||
"github.com/icinga/icinga-testing/utils" | ||
"go.uber.org/zap" | ||
"os" | ||
"path/filepath" | ||
"sync" | ||
"sync/atomic" | ||
) | ||
|
||
type dockerBinaryCreator struct { | ||
logger *zap.Logger | ||
dockerClient *client.Client | ||
dockerNetworkId string | ||
containerNamePrefix string | ||
binaryPath string | ||
channelDirPath string | ||
containerCounter uint32 | ||
|
||
runningMutex sync.Mutex | ||
running map[*dockerBinaryInstance]struct{} | ||
} | ||
|
||
var _ Creator = (*dockerBinaryCreator)(nil) | ||
|
||
func NewDockerBinaryCreator( | ||
logger *zap.Logger, | ||
dockerClient *client.Client, | ||
containerNamePrefix string, | ||
dockerNetworkId string, | ||
binaryPath string, | ||
channelDirPath string, | ||
) Creator { | ||
binaryPath, err := filepath.Abs(binaryPath) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return &dockerBinaryCreator{ | ||
logger: logger.With(zap.Bool("icinga_notifications", true)), | ||
dockerClient: dockerClient, | ||
dockerNetworkId: dockerNetworkId, | ||
containerNamePrefix: containerNamePrefix, | ||
binaryPath: binaryPath, | ||
channelDirPath: channelDirPath, | ||
running: make(map[*dockerBinaryInstance]struct{}), | ||
} | ||
} | ||
|
||
func (i *dockerBinaryCreator) CreateIcingaNotifications( | ||
rdb services.RelationalDatabase, | ||
options ...services.IcingaNotificationsOption, | ||
) services.IcingaNotificationsBase { | ||
inst := &dockerBinaryInstance{ | ||
info: info{rdb: rdb}, | ||
logger: i.logger, | ||
icingaNotificationsDockerBinary: i, | ||
} | ||
|
||
configFile, err := os.CreateTemp("", "icinga_notifications.yml") | ||
if err != nil { | ||
panic(err) | ||
} | ||
idb := &services.IcingaNotifications{IcingaNotificationsBase: inst} | ||
for _, option := range options { | ||
option(idb) | ||
} | ||
if err = idb.WriteConfig(configFile); err != nil { | ||
panic(err) | ||
} | ||
inst.configFileName = configFile.Name() | ||
err = configFile.Close() | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
containerName := fmt.Sprintf("%s-%d", i.containerNamePrefix, atomic.AddUint32(&i.containerCounter, 1)) | ||
inst.logger = inst.logger.With(zap.String("container-name", containerName)) | ||
networkName, err := utils.DockerNetworkName(context.Background(), i.dockerClient, i.dockerNetworkId) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
dockerImage := "alpine:latest" | ||
err = utils.DockerImagePull(context.Background(), inst.logger, i.dockerClient, dockerImage, false) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
cont, err := i.dockerClient.ContainerCreate(context.Background(), &container.Config{ | ||
Cmd: []string{"/icinga-notifications-daemon", "-config", "/icinga_notifications.yml"}, | ||
Image: dockerImage, | ||
ExposedPorts: map[nat.Port]struct{}{nat.Port(defaultPort + "/tcp"): {}}, | ||
}, &container.HostConfig{ | ||
Mounts: []mount.Mount{{ | ||
Type: mount.TypeBind, | ||
Source: i.binaryPath, | ||
Target: "/icinga-notifications-daemon", | ||
ReadOnly: true, | ||
}, { | ||
Type: mount.TypeBind, | ||
Source: i.channelDirPath, | ||
Target: "/channel", | ||
ReadOnly: true, | ||
}, { | ||
Type: mount.TypeBind, | ||
Source: inst.configFileName, | ||
Target: "/icinga_notifications.yml", | ||
ReadOnly: true, | ||
}}, | ||
}, &network.NetworkingConfig{ | ||
EndpointsConfig: map[string]*network.EndpointSettings{ | ||
networkName: { | ||
NetworkID: i.dockerNetworkId, | ||
}, | ||
}, | ||
}, nil, containerName) | ||
if err != nil { | ||
inst.logger.Fatal("failed to create icinga-notifications container", zap.Error(err)) | ||
} | ||
inst.containerId = cont.ID | ||
inst.logger = inst.logger.With(zap.String("container-id", cont.ID)) | ||
inst.logger.Debug("created container") | ||
|
||
err = utils.ForwardDockerContainerOutput(context.Background(), i.dockerClient, cont.ID, | ||
false, utils.NewLineWriter(func(line []byte) { | ||
inst.logger.Debug("container output", | ||
zap.ByteString("line", line)) | ||
})) | ||
if err != nil { | ||
inst.logger.Fatal("failed to attach to container output", zap.Error(err)) | ||
} | ||
|
||
err = i.dockerClient.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{}) | ||
if err != nil { | ||
inst.logger.Fatal("failed to start container", zap.Error(err)) | ||
} | ||
inst.logger.Debug("started container") | ||
|
||
inst.info.host = utils.MustString(utils.DockerContainerAddress(context.Background(), i.dockerClient, cont.ID)) | ||
inst.info.port = defaultPort | ||
|
||
i.runningMutex.Lock() | ||
i.running[inst] = struct{}{} | ||
i.runningMutex.Unlock() | ||
|
||
return inst | ||
} | ||
|
||
func (i *dockerBinaryCreator) Cleanup() { | ||
i.runningMutex.Lock() | ||
instances := make([]*dockerBinaryInstance, 0, len(i.running)) | ||
for inst := range i.running { | ||
instances = append(instances, inst) | ||
} | ||
i.runningMutex.Unlock() | ||
|
||
for _, inst := range instances { | ||
inst.Cleanup() | ||
} | ||
} | ||
|
||
type dockerBinaryInstance struct { | ||
info | ||
icingaNotificationsDockerBinary *dockerBinaryCreator | ||
logger *zap.Logger | ||
containerId string | ||
configFileName string | ||
} | ||
|
||
var _ services.IcingaNotificationsBase = (*dockerBinaryInstance)(nil) | ||
|
||
func (i *dockerBinaryInstance) Cleanup() { | ||
i.icingaNotificationsDockerBinary.runningMutex.Lock() | ||
delete(i.icingaNotificationsDockerBinary.running, i) | ||
i.icingaNotificationsDockerBinary.runningMutex.Unlock() | ||
|
||
err := i.icingaNotificationsDockerBinary.dockerClient.ContainerRemove(context.Background(), i.containerId, types.ContainerRemoveOptions{ | ||
Force: true, | ||
RemoveVolumes: true, | ||
}) | ||
if err != nil { | ||
panic(err) | ||
} | ||
i.logger.Debug("removed container") | ||
|
||
err = os.Remove(i.configFileName) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package notifications | ||
|
||
import ( | ||
"github.com/icinga/icinga-testing/services" | ||
) | ||
|
||
// defaultPort of the Icinga Notifications Web Listener. | ||
const defaultPort string = "5680" | ||
|
||
type Creator interface { | ||
CreateIcingaNotifications(rdb services.RelationalDatabase, options ...services.IcingaNotificationsOption) services.IcingaNotificationsBase | ||
Cleanup() | ||
} | ||
|
||
// info provides a partial implementation of the services.IcingaNotificationsBase interface. | ||
type info struct { | ||
host string | ||
port string | ||
|
||
rdb services.RelationalDatabase | ||
} | ||
|
||
func (i *info) Host() string { | ||
return i.host | ||
} | ||
|
||
func (i *info) Port() string { | ||
return i.port | ||
} | ||
|
||
func (i *info) RelationalDatabase() services.RelationalDatabase { | ||
return i.rdb | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.