-
Notifications
You must be signed in to change notification settings - Fork 112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implementation of APCUPSD Addon #372
base: master
Are you sure you want to change the base?
Changes from all commits
27b027c
43f18e6
420322f
8dc5d31
226e9ca
bd70793
8e60854
ee84afb
2fd0906
7f4f9d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package apcupsd | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/rocket-pool/smartnode/shared/types/addons" | ||
cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" | ||
) | ||
|
||
const ( | ||
ContainerID_Apcupsd cfgtypes.ContainerID = "apcupsd" | ||
ContainerID_ApcupsdExporter cfgtypes.ContainerID = "apcupsd_exporter" | ||
ApcupsdContainerName string = "addon_apcupsd" | ||
ApcupsdNetworkComposeTemplateName string = "addon_apcupsd.network" | ||
ApcupsdContainerComposeTemplateName string = "addon_apcupsd.container" | ||
ApcupsdConfigTemplateName string = "addon_apcupsd_config" | ||
ApcupsdConfigName string = "addon_apcupsd.conf" | ||
) | ||
|
||
type Apcupsd struct { | ||
cfg *ApcupsdConfig `yaml:"config,omitempty"` | ||
} | ||
|
||
func NewApcupsd() addons.SmartnodeAddon { | ||
return &Apcupsd{ | ||
cfg: NewConfig(), | ||
} | ||
} | ||
|
||
func (apcupsd *Apcupsd) GetName() string { | ||
return "APCUPS Monitor" | ||
} | ||
|
||
func (apcupsd *Apcupsd) GetDescription() string { | ||
return "This addon adds UPS monitoring to your node so you can monitor the status of your APCUPSD compatible UPS within grafana \n\nMade with love by killjoy.eth." | ||
} | ||
|
||
func (apcupsd *Apcupsd) GetConfig() cfgtypes.Config { | ||
return apcupsd.cfg | ||
} | ||
|
||
func (apcupsd *Apcupsd) GetContainerName() string { | ||
return fmt.Sprint(ContainerID_Apcupsd) | ||
} | ||
|
||
func (apcupsd *Apcupsd) GetEnabledParameter() *cfgtypes.Parameter { | ||
return &apcupsd.cfg.Enabled | ||
} | ||
|
||
func (apcupsd *Apcupsd) GetContainerTag() string { | ||
return containerTag | ||
} | ||
|
||
func (apcupsd *Apcupsd) UpdateEnvVars(envVars map[string]string) error { | ||
if apcupsd.cfg.Enabled.Value == true { | ||
cfgtypes.AddParametersToEnvVars(apcupsd.cfg.GetParameters(), envVars) | ||
} | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package apcupsd | ||
|
||
import ( | ||
"github.com/rocket-pool/smartnode/shared/types/config" | ||
) | ||
|
||
// Constants | ||
const ( | ||
containerTag string = "gersilex/apcupsd:v1.0.0" | ||
exporterContainerTag string = "jangrewe/apcupsd-exporter:latest" | ||
) | ||
|
||
type Mode string | ||
|
||
const ( | ||
Mode_Network Mode = "network" | ||
Mode_Container Mode = "docker" | ||
) | ||
|
||
// Configuration for the Graffiti Wall Writer | ||
type ApcupsdConfig struct { | ||
Title string `yaml:"-"` | ||
|
||
Enabled config.Parameter `yaml:"enabled,omitempty"` | ||
ApcupsdContainerTag config.Parameter `yaml:"apcupsdContainerTag,omitempty"` | ||
ApcupsdExporterContainerTag config.Parameter `yaml:"apcupsdExporterContainerTag,omitempty"` | ||
MetricsPort config.Parameter `yaml:"metricsPort,omitempty"` | ||
MountPoint config.Parameter `yaml:"mountPoint,omitempty"` | ||
Mode config.Parameter `yaml:"mode,omitempty"` | ||
NetworkAddress config.Parameter `yaml:"NetworkAddress,omitempty"` | ||
} | ||
|
||
// Creates a new configuration instance | ||
func NewConfig() *ApcupsdConfig { | ||
return &ApcupsdConfig{ | ||
Title: "APCUPSD Settings", | ||
|
||
Enabled: config.Parameter{ | ||
ID: "enabled", | ||
Name: "Enabled", | ||
Description: "Enable APCUPSD monitoring", | ||
Type: config.ParameterType_Bool, | ||
Default: map[config.Network]interface{}{config.Network_All: false}, | ||
AffectsContainers: []config.ContainerID{ContainerID_Apcupsd, ContainerID_ApcupsdExporter}, | ||
EnvironmentVariables: []string{"ADDON_APCUPSD_ENABLED"}, | ||
CanBeBlank: false, | ||
OverwriteOnUpgrade: false, | ||
}, | ||
Mode: config.Parameter{ | ||
ID: "mode", | ||
Name: "Mode", | ||
Description: "How would you like to run APCUPSD?\n Select `Container` if you'd like smart node to run apcupsd inside a container for you.\nSelect `network` mode if you want to connect to an instance of apcupsd running on your host machine or on your network.", | ||
Type: config.ParameterType_Choice, | ||
Default: map[config.Network]interface{}{config.Network_All: Mode_Container}, | ||
AffectsContainers: []config.ContainerID{ContainerID_Apcupsd, ContainerID_ApcupsdExporter}, | ||
EnvironmentVariables: []string{}, | ||
CanBeBlank: false, | ||
OverwriteOnUpgrade: false, | ||
Options: []config.ParameterOption{{ | ||
Name: "Container", | ||
Description: "Let the smart node run APCUPSD inside a container for you", | ||
Value: Mode_Container, | ||
}, { | ||
Name: "Network", | ||
Description: "Connect the APCUPSD exporter to an instance of APCUSD running on your host machine or on your network", | ||
Value: Mode_Network, | ||
}}, | ||
}, | ||
ApcupsdContainerTag: config.Parameter{ | ||
ID: "containerTag", | ||
Name: "APCUPSD Container Tag", | ||
Description: "The container tag name of the APCUPSD container.", | ||
Type: config.ParameterType_String, | ||
Default: map[config.Network]interface{}{config.Network_All: containerTag}, | ||
AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, | ||
EnvironmentVariables: []string{"ADDON_APCUPSD_CONTAINER_TAG"}, | ||
CanBeBlank: false, | ||
OverwriteOnUpgrade: true, | ||
}, | ||
ApcupsdExporterContainerTag: config.Parameter{ | ||
ID: "exporterContainerTag", | ||
Name: "APCUPSD Exporter Container Tag", | ||
Description: "The container tag name of the APCUPSD Prometheus Exporter.", | ||
Type: config.ParameterType_String, | ||
Default: map[config.Network]interface{}{config.Network_All: exporterContainerTag}, | ||
AffectsContainers: []config.ContainerID{ContainerID_ApcupsdExporter}, | ||
EnvironmentVariables: []string{"ADDON_APCUPSD_EXPORTER_CONTAINER_TAG"}, | ||
CanBeBlank: false, | ||
OverwriteOnUpgrade: true, | ||
}, | ||
MetricsPort: config.Parameter{ | ||
ID: "metricsPort", | ||
Name: "APCUPSD Exporter Metrics Port", | ||
Description: "The port the exporter should use to provide metrics to prometheus.", | ||
Type: config.ParameterType_String, | ||
Default: map[config.Network]interface{}{config.Network_All: "9162"}, | ||
AffectsContainers: []config.ContainerID{ContainerID_ApcupsdExporter, config.ContainerID_Prometheus}, | ||
EnvironmentVariables: []string{"ADDON_APCUPSD_METRICS_PORT"}, | ||
CanBeBlank: false, | ||
OverwriteOnUpgrade: false, | ||
}, | ||
MountPoint: config.Parameter{ | ||
ID: "mountPoint", | ||
Name: "APC USB Mount Location", | ||
Description: "The USB mount point for your APC device. This must be set correctly for the container to read data from your UPC. To determine the mount point on your system:\n1. Unplug the USB cable of your UPS and plug it back in.\n2. When your server detects the device an entry will show up when you run `sudo dmesg | grep usb`.\n3. Identify the mount point for your UPS. Often it is named `hiddev*` e.g. `hiddev0`,`hiddev1`... but may vary depending on how many peripherals you have connected.\n4. Verify the mount point for your distribution. Often this maps to `/dev/usb/hiddev*`\nThis is the value to enter in the field below. NOTE: If you reconnect your UPC this value may need to be updated.", | ||
Type: config.ParameterType_String, | ||
Default: map[config.Network]interface{}{config.Network_All: "/dev/usb/hiddev0"}, | ||
AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, | ||
EnvironmentVariables: []string{"ADDON_APCUPSD_MOUNT_POINT"}, | ||
CanBeBlank: false, | ||
OverwriteOnUpgrade: false, | ||
}, | ||
NetworkAddress: config.Parameter{ | ||
ID: "networkAddress", | ||
Name: "APCUPSD Network Address", | ||
Description: "The network address and port that should be used to connect to APCUPSD.\nIf you have apcupsd installed on your host you should use the default host.docker.internal:3551.", | ||
Type: config.ParameterType_String, | ||
Default: map[config.Network]interface{}{config.Network_All: "host.docker.internal:3551"}, | ||
AffectsContainers: []config.ContainerID{ContainerID_ApcupsdExporter}, | ||
EnvironmentVariables: []string{"ADDON_APCUPSD_NETWORK_ADDRESS"}, | ||
CanBeBlank: false, | ||
OverwriteOnUpgrade: false, | ||
}, | ||
} | ||
} | ||
|
||
// Get the parameters for this config | ||
func (cfg *ApcupsdConfig) GetParameters() []*config.Parameter { | ||
return []*config.Parameter{&cfg.Enabled, &cfg.Mode, &cfg.ApcupsdExporterContainerTag, &cfg.ApcupsdContainerTag, &cfg.MetricsPort, &cfg.MountPoint, &cfg.NetworkAddress} | ||
|
||
} | ||
|
||
// The the title for the config | ||
func (cfg *ApcupsdConfig) GetConfigTitle() string { | ||
return cfg.Title | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,15 @@ | ||
package addons | ||
|
||
import ( | ||
"github.com/rocket-pool/smartnode/addons/apcupsd" | ||
"github.com/rocket-pool/smartnode/addons/graffiti_wall_writer" | ||
"github.com/rocket-pool/smartnode/shared/types/addons" | ||
) | ||
|
||
func NewGraffitiWallWriter() addons.SmartnodeAddon { | ||
return graffiti_wall_writer.NewGraffitiWallWriter() | ||
} | ||
|
||
func NewApcupsd() addons.SmartnodeAddon { | ||
return apcupsd.NewApcupsd() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
package config | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/gdamore/tcell/v2" | ||
"github.com/rivo/tview" | ||
"github.com/rocket-pool/smartnode/shared/services/config" | ||
"github.com/rocket-pool/smartnode/shared/types/addons" | ||
) | ||
|
||
// The page wrapper for the APCUPSD addon config | ||
type AddonApcupsdPage struct { | ||
addonsPage *AddonsPage | ||
page *page | ||
layout *standardLayout | ||
masterConfig *config.RocketPoolConfig | ||
addon addons.SmartnodeAddon | ||
enabledBox *parameterizedFormItem | ||
modeBox *parameterizedFormItem | ||
exporterImage *parameterizedFormItem | ||
apcupsdImage *parameterizedFormItem | ||
mountPoint *parameterizedFormItem | ||
metricsPort *parameterizedFormItem | ||
apcupsdAddress *parameterizedFormItem | ||
} | ||
|
||
// Creates a new page for the APCUPSD addon settings | ||
func NewAddonApcupsdPage(addonsPage *AddonsPage, addon addons.SmartnodeAddon) *AddonApcupsdPage { | ||
|
||
configPage := &AddonApcupsdPage{ | ||
addonsPage: addonsPage, | ||
masterConfig: addonsPage.home.md.Config, | ||
addon: addon, | ||
} | ||
configPage.createContent() | ||
|
||
configPage.page = newPage( | ||
addonsPage.page, | ||
"settings-addon-apcupsd", | ||
addon.GetName(), | ||
addon.GetDescription(), | ||
configPage.layout.grid, | ||
) | ||
|
||
return configPage | ||
|
||
} | ||
|
||
// Get the underlying page | ||
func (configPage *AddonApcupsdPage) getPage() *page { | ||
return configPage.page | ||
} | ||
|
||
// Creates the content for the APCUPSD settings page | ||
func (configPage *AddonApcupsdPage) createContent() { | ||
|
||
// Create the layout | ||
configPage.layout = newStandardLayout() | ||
configPage.layout.createForm(&configPage.masterConfig.Smartnode.Network, fmt.Sprintf("%s Settings", configPage.addon.GetName())) | ||
|
||
// Return to the home page after pressing Escape | ||
configPage.layout.form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { | ||
if event.Key() == tcell.KeyEsc { | ||
// Close all dropdowns and break if one was open | ||
for _, param := range configPage.layout.parameters { | ||
dropDown, ok := param.item.(*DropDown) | ||
if ok && dropDown.open { | ||
dropDown.CloseList(configPage.addonsPage.home.md.app) | ||
return nil | ||
} | ||
} | ||
|
||
// Return to the home page | ||
configPage.addonsPage.home.md.setPage(configPage.addonsPage.page) | ||
return nil | ||
} | ||
return event | ||
}) | ||
|
||
// Get the parameters | ||
enabledParam := configPage.addon.GetEnabledParameter() | ||
// TODO: Don't like how I reference these by index here. Is there a better way? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any suggestion for a cleaner way to fetch these parameters? It seems I can only access functions as defined by the generic addon interface. |
||
modeParam := configPage.addon.GetConfig().GetParameters()[1] | ||
exporterImageParam := configPage.addon.GetConfig().GetParameters()[2] | ||
apcupsdImageParam := configPage.addon.GetConfig().GetParameters()[3] | ||
metricsPortParam := configPage.addon.GetConfig().GetParameters()[4] | ||
mountPointParam := configPage.addon.GetConfig().GetParameters()[5] | ||
networkAddress := configPage.addon.GetConfig().GetParameters()[6] | ||
|
||
// Set up the form items | ||
configPage.enabledBox = createParameterizedCheckbox(enabledParam) | ||
configPage.modeBox = createParameterizedDropDown(modeParam, configPage.layout.descriptionBox) | ||
configPage.exporterImage = createParameterizedStringField(exporterImageParam) | ||
configPage.apcupsdImage = createParameterizedStringField(apcupsdImageParam) | ||
configPage.metricsPort = createParameterizedStringField(metricsPortParam) | ||
configPage.mountPoint = createParameterizedStringField(mountPointParam) | ||
configPage.apcupsdAddress = createParameterizedStringField(networkAddress) | ||
|
||
// Map the parameters to the form items in the layout | ||
configPage.layout.mapParameterizedFormItems(configPage.enabledBox) | ||
configPage.layout.mapParameterizedFormItems(configPage.modeBox) | ||
configPage.layout.mapParameterizedFormItems(configPage.exporterImage) | ||
configPage.layout.mapParameterizedFormItems(configPage.apcupsdImage) | ||
configPage.layout.mapParameterizedFormItems(configPage.metricsPort) | ||
configPage.layout.mapParameterizedFormItems(configPage.mountPoint) | ||
configPage.layout.mapParameterizedFormItems(configPage.apcupsdAddress) | ||
|
||
// Set up the setting callbacks | ||
configPage.enabledBox.item.(*tview.Checkbox).SetChangedFunc(func(checked bool) { | ||
if enabledParam.Value == checked { | ||
return | ||
} | ||
enabledParam.Value = checked | ||
configPage.handleEnableChanged() | ||
}) | ||
configPage.modeBox.item.(*DropDown).SetSelectedFunc(func(text string, index int) { | ||
if configPage.modeBox.parameter.Value == configPage.modeBox.parameter.Options[index].Value { | ||
return | ||
} | ||
configPage.modeBox.parameter.Value = configPage.modeBox.parameter.Options[index].Value | ||
configPage.handleModeChanged() | ||
}) | ||
|
||
// Do the initial draw | ||
configPage.handleDraw() | ||
|
||
} | ||
|
||
// Handle all of the form changes when the Enabled box has changed | ||
func (configPage *AddonApcupsdPage) handleEnableChanged() { | ||
configPage.handleDraw() | ||
} | ||
|
||
// Handle all of the form changes when the Mode box has changed | ||
func (configPage *AddonApcupsdPage) handleModeChanged() { | ||
configPage.handleDraw() | ||
} | ||
|
||
func (configPage *AddonApcupsdPage) handleDraw() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please review my understanding of how form items should be drawn. |
||
configPage.layout.form.Clear(true) | ||
configPage.layout.form.AddFormItem(configPage.enabledBox.item) | ||
|
||
// Only add the supporting stuff if addon is enabled | ||
if configPage.addon.GetEnabledParameter().Value == false { | ||
return | ||
} | ||
configPage.addCommonFields() | ||
if configPage.modeBox.parameter.Value == configPage.modeBox.parameter.Options[0].Value { | ||
configPage.addContainerFields() | ||
} else { | ||
configPage.addNetworkFields() | ||
} | ||
configPage.layout.refresh() | ||
} | ||
|
||
func (configPage *AddonApcupsdPage) addCommonFields() { | ||
configPage.layout.form.AddFormItem(configPage.modeBox.item) | ||
configPage.layout.form.AddFormItem(configPage.exporterImage.item) | ||
configPage.layout.form.AddFormItem(configPage.metricsPort.item) | ||
} | ||
func (configPage *AddonApcupsdPage) addContainerFields() { | ||
configPage.layout.form.AddFormItem(configPage.apcupsdImage.item) | ||
configPage.layout.form.AddFormItem(configPage.mountPoint.item) | ||
} | ||
|
||
func (configPage *AddonApcupsdPage) addNetworkFields() { | ||
configPage.layout.form.AddFormItem(configPage.apcupsdAddress.item) | ||
} | ||
|
||
// Handle a bulk redraw request | ||
func (configPage *AddonApcupsdPage) handleLayoutChanged() { | ||
configPage.handleEnableChanged() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These docker images are provided by third parties.
Please let me know if in your estimation there is a security risk here and if these images need to be audited.