From 27b027c009c42a0b95509a34b2d678e8fb6b0c61 Mon Sep 17 00:00:00 2001 From: Dan Imbrogno Date: Wed, 7 Jun 2023 10:41:51 -0400 Subject: [PATCH 1/7] Initial test of adding addon --- addons/apcupsd/addon.go | 54 ++++++++++++++++++++ addons/apcupsd/config.go | 48 +++++++++++++++++ shared/services/config/rocket-pool-config.go | 1 + shared/services/rocketpool/client.go | 26 ++++++++++ 4 files changed, 129 insertions(+) create mode 100644 addons/apcupsd/addon.go create mode 100644 addons/apcupsd/config.go diff --git a/addons/apcupsd/addon.go b/addons/apcupsd/addon.go new file mode 100644 index 000000000..c1aa1f231 --- /dev/null +++ b/addons/apcupsd/addon.go @@ -0,0 +1,54 @@ +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" + ApcupsdContainerName string = "addon_apcupsd" +) + +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 +} diff --git a/addons/apcupsd/config.go b/addons/apcupsd/config.go new file mode 100644 index 000000000..373985629 --- /dev/null +++ b/addons/apcupsd/config.go @@ -0,0 +1,48 @@ +package apcupsd + +import ( + "github.com/rocket-pool/smartnode/shared/types/config" +) + +// Constants +const ( + containerTag string = "rocketpool/graffiti-wall-addon:v1.0.1" +) + +// Configuration for the Graffiti Wall Writer +type ApcupsdConfig struct { + Title string `yaml:"-"` + + Enabled config.Parameter `yaml:"enabled,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, config.ContainerID_Validator}, + EnvironmentVariables: []string{"ADDON_APCUPSD_ENABLED"}, + CanBeBlank: false, + OverwriteOnUpgrade: false, + }, + } +} + +// Get the parameters for this config +func (cfg *ApcupsdConfig) GetParameters() []*config.Parameter { + return []*config.Parameter{ + &cfg.Enabled, + } +} + +// The the title for the config +func (cfg *ApcupsdConfig) GetConfigTitle() string { + return cfg.Title +} diff --git a/shared/services/config/rocket-pool-config.go b/shared/services/config/rocket-pool-config.go index 50596c4e0..c1d546a7b 100644 --- a/shared/services/config/rocket-pool-config.go +++ b/shared/services/config/rocket-pool-config.go @@ -123,6 +123,7 @@ type RocketPoolConfig struct { // Addons GraffitiWallWriter addontypes.SmartnodeAddon `yaml:"addon-gww,omitempty"` + Apcupsd addontypes.SmartnodeAddon `yaml:"addon-apcupsd,omitempty"` } // Load configuration settings from a file diff --git a/shared/services/rocketpool/client.go b/shared/services/rocketpool/client.go index 37ad4b93e..9c04b3d55 100644 --- a/shared/services/rocketpool/client.go +++ b/shared/services/rocketpool/client.go @@ -25,6 +25,7 @@ import ( "github.com/blang/semver/v4" externalip "github.com/glendc/go-external-ip" "github.com/mitchellh/go-homedir" + "github.com/rocket-pool/smartnode/addons/apcupsd" "github.com/rocket-pool/smartnode/addons/graffiti_wall_writer" "github.com/rocket-pool/smartnode/shared/services/config" cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" @@ -1635,6 +1636,31 @@ func (c *Client) composeAddons(cfg *config.RocketPoolConfig, rocketpoolDir strin deployedContainers = append(deployedContainers, filepath.Join(overrideFolder, graffiti_wall_writer.GraffitiWallWriterContainerName+composeFileSuffix)) } + // APCUPSD + if cfg.Apcupsd.GetEnabledParameter().Value == true { + runtimeFolder := filepath.Join(rocketpoolDir, runtimeDir, "addons", "apcupsd") + templatesFolder := filepath.Join(rocketpoolDir, templatesDir, "addons", "apcupsd") + overrideFolder := filepath.Join(rocketpoolDir, overrideDir, "addons", "apcupsd") + + // Make the addon folder + err := os.MkdirAll(runtimeFolder, 0775) + if err != nil { + return []string{}, fmt.Errorf("error creating addon runtime folder (%s): %w", runtimeFolder, err) + } + + contents, err := envsubst.ReadFile(filepath.Join(templatesFolder, apcupsd.ApcupsdContainerName+templateSuffix)) + if err != nil { + return []string{}, fmt.Errorf("error reading and substituting APCUPSD addon container template: %w", err) + } + composePath := filepath.Join(runtimeFolder, apcupsd.ApcupsdContainerName+composeFileSuffix) + err = os.WriteFile(composePath, contents, 0664) + if err != nil { + return []string{}, fmt.Errorf("could not write APCUPSD addon container file to %s: %w", composePath, err) + } + deployedContainers = append(deployedContainers, composePath) + deployedContainers = append(deployedContainers, filepath.Join(overrideFolder, apcupsd.ApcupsdContainerName+composeFileSuffix)) + } + return deployedContainers, nil } From 43f18e664c7437cb3da92acf9145188ddbbf50b5 Mon Sep 17 00:00:00 2001 From: Dan Imbrogno Date: Thu, 8 Jun 2023 08:58:32 -0400 Subject: [PATCH 2/7] Adds apcupsd page to tui --- addons/constructors.go | 5 + .../service/config/addon-apcupsd.go | 125 ++++++++++++++++++ rocketpool-cli/service/config/addons.go | 3 + rocketpool-cli/update.sh | 5 + shared/services/config/rocket-pool-config.go | 3 + shared/version.go | 2 +- 6 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 rocketpool-cli/service/config/addon-apcupsd.go create mode 100755 rocketpool-cli/update.sh diff --git a/addons/constructors.go b/addons/constructors.go index e1009cbb8..02518d4ca 100644 --- a/addons/constructors.go +++ b/addons/constructors.go @@ -1,6 +1,7 @@ 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" ) @@ -8,3 +9,7 @@ import ( func NewGraffitiWallWriter() addons.SmartnodeAddon { return graffiti_wall_writer.NewGraffitiWallWriter() } + +func NewApcupsd() addons.SmartnodeAddon { + return apcupsd.NewApcupsd() +} diff --git a/rocketpool-cli/service/config/addon-apcupsd.go b/rocketpool-cli/service/config/addon-apcupsd.go new file mode 100644 index 000000000..3e4e11b2d --- /dev/null +++ b/rocketpool-cli/service/config/addon-apcupsd.go @@ -0,0 +1,125 @@ +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" + cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" +) + +// The page wrapper for the Graffiti Wall Writer addon config +type AddonApcupsdPage struct { + addonsPage *AddonsPage + page *page + layout *standardLayout + masterConfig *config.RocketPoolConfig + addon addons.SmartnodeAddon + enabledBox *parameterizedFormItem + otherParams []*parameterizedFormItem +} + +// Creates a new page for the Graffiti Wall Writer 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 GWW 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() + otherParams := []*cfgtypes.Parameter{} + + for _, param := range configPage.addon.GetConfig().GetParameters() { + if param.ID != enabledParam.ID { + otherParams = append(otherParams, param) + } + } + + // Set up the form items + configPage.enabledBox = createParameterizedCheckbox(enabledParam) + configPage.otherParams = createParameterizedFormItems(otherParams, configPage.layout.descriptionBox) + + // Map the parameters to the form items in the layout + configPage.layout.mapParameterizedFormItems(configPage.enabledBox) + configPage.layout.mapParameterizedFormItems(configPage.otherParams...) + + // Set up the setting callbacks + configPage.enabledBox.item.(*tview.Checkbox).SetChangedFunc(func(checked bool) { + if enabledParam.Value == checked { + return + } + enabledParam.Value = checked + configPage.handleEnableChanged() + }) + + // Do the initial draw + configPage.handleEnableChanged() + +} + +// Handle all of the form changes when the Use Fallback EC box has changed +func (configPage *AddonApcupsdPage) handleEnableChanged() { + configPage.layout.form.Clear(true) + configPage.layout.form.AddFormItem(configPage.enabledBox.item) + + // Only add the supporting stuff if external clients are enabled + if configPage.addon.GetEnabledParameter().Value == false { + return + } + configPage.layout.addFormItems(configPage.otherParams) + configPage.layout.refresh() +} + +// Handle a bulk redraw request +func (configPage *AddonApcupsdPage) handleLayoutChanged() { + configPage.handleEnableChanged() +} diff --git a/rocketpool-cli/service/config/addons.go b/rocketpool-cli/service/config/addons.go index 0272cc20c..0cbb0d072 100644 --- a/rocketpool-cli/service/config/addons.go +++ b/rocketpool-cli/service/config/addons.go @@ -17,6 +17,7 @@ type AddonsPage struct { masterConfig *config.RocketPoolConfig gwwPage *AddonGwwPage gwwButton *parameterizedFormItem + apcupsdPage *AddonApcupsdPage categoryList *tview.List addonSubpages []settingsPage content tview.Primitive @@ -39,8 +40,10 @@ func NewAddonsPage(home *settingsHome) *AddonsPage { // Create the addon subpages addonsPage.gwwPage = NewAddonGwwPage(addonsPage, home.md.Config.GraffitiWallWriter) + addonsPage.apcupsdPage = NewAddonApcupsdPage(addonsPage, home.md.Config.Apcupsd) addonSubpages := []settingsPage{ addonsPage.gwwPage, + addonsPage.apcupsdPage, } addonsPage.addonSubpages = addonSubpages diff --git a/rocketpool-cli/update.sh b/rocketpool-cli/update.sh new file mode 100755 index 000000000..0410f1567 --- /dev/null +++ b/rocketpool-cli/update.sh @@ -0,0 +1,5 @@ +#!/bin/bash +pwd +go build -buildvcs=false +mv rocketpool-cli ~/bin/rocketpool +chmod u+x ~/bin/rocketpool diff --git a/shared/services/config/rocket-pool-config.go b/shared/services/config/rocket-pool-config.go index c1d546a7b..b4c5f62de 100644 --- a/shared/services/config/rocket-pool-config.go +++ b/shared/services/config/rocket-pool-config.go @@ -470,6 +470,7 @@ func NewRocketPoolConfig(rpDir string, isNativeMode bool) *RocketPoolConfig { // Addons cfg.GraffitiWallWriter = addons.NewGraffitiWallWriter() + cfg.Apcupsd = addons.NewApcupsd() // Apply the default values for mainnet cfg.Smartnode.Network.Value = cfg.Smartnode.Network.Options[0].Value @@ -571,6 +572,7 @@ func (cfg *RocketPoolConfig) GetSubconfigs() map[string]config.Config { "native": cfg.Native, "mevBoost": cfg.MevBoost, "addons-gww": cfg.GraffitiWallWriter.GetConfig(), + "addons-apcupsd": cfg.Apcupsd.GetConfig(), } } @@ -1010,6 +1012,7 @@ func (cfg *RocketPoolConfig) GenerateEnvironmentVariables() map[string]string { // Addons cfg.GraffitiWallWriter.UpdateEnvVars(envVars) + cfg.Apcupsd.UpdateEnvVars(envVars) return envVars diff --git a/shared/version.go b/shared/version.go index a4c28611e..e7ee1172f 100644 --- a/shared/version.go +++ b/shared/version.go @@ -1,6 +1,6 @@ package shared -const RocketPoolVersion string = "1.9.6-dev" +const RocketPoolVersion string = "1.9.6-apcupsd-dev" const Logo string = `______ _ _ ______ _ | ___ \ | | | | | ___ \ | | From 420322f665d4748d5868ec05ff409ab267975da3 Mon Sep 17 00:00:00 2001 From: Dan Imbrogno Date: Thu, 8 Jun 2023 08:59:17 -0400 Subject: [PATCH 3/7] rm temporary update script --- rocketpool-cli/update.sh | 5 ----- 1 file changed, 5 deletions(-) delete mode 100755 rocketpool-cli/update.sh diff --git a/rocketpool-cli/update.sh b/rocketpool-cli/update.sh deleted file mode 100755 index 0410f1567..000000000 --- a/rocketpool-cli/update.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -pwd -go build -buildvcs=false -mv rocketpool-cli ~/bin/rocketpool -chmod u+x ~/bin/rocketpool From 8dc5d319e4441e5d8370431672322dc66959daa4 Mon Sep 17 00:00:00 2001 From: Dan Imbrogno Date: Thu, 8 Jun 2023 10:30:41 -0400 Subject: [PATCH 4/7] update config --- addons/apcupsd/config.go | 58 ++++++++++++++++++- .../service/config/addon-apcupsd.go | 6 +- shared/version.go | 2 +- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/addons/apcupsd/config.go b/addons/apcupsd/config.go index 373985629..1f828937a 100644 --- a/addons/apcupsd/config.go +++ b/addons/apcupsd/config.go @@ -13,7 +13,11 @@ const ( type ApcupsdConfig struct { Title string `yaml:"-"` - Enabled config.Parameter `yaml:"enabled,omitempty"` + Enabled config.Parameter `yaml:"enabled,omitempty"` + MountPoint config.Parameter `yaml:"mountPoint,omitempty"` + Debug config.Parameter `yaml:"debug,omitempty"` + PollCron config.Parameter `yaml:"pollCron,omitempty"` + Timeout config.Parameter `yaml:"timeout,omitempty"` } // Creates a new configuration instance @@ -27,11 +31,57 @@ func NewConfig() *ApcupsdConfig { Description: "Enable APCUPSD monitoring", Type: config.ParameterType_Bool, Default: map[config.Network]interface{}{config.Network_All: false}, - AffectsContainers: []config.ContainerID{ContainerID_Apcupsd, config.ContainerID_Validator}, + AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, EnvironmentVariables: []string{"ADDON_APCUPSD_ENABLED"}, 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*`\n This 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: ""}, + Regex: "[^\\0]+", + AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, + EnvironmentVariables: []string{"ADDON_APCUPSD_MOUNT_POINT"}, + CanBeBlank: true, + OverwriteOnUpgrade: false, + }, + Debug: config.Parameter{ + ID: "debug", + Name: "Debug", + Description: "Output debug logs for APCUPSD monitoring", + Type: config.ParameterType_Bool, + Default: map[config.Network]interface{}{config.Network_All: false}, + AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, + EnvironmentVariables: []string{"ADDON_APCUPSD_DEBUG"}, + CanBeBlank: true, + OverwriteOnUpgrade: false, + }, + Timeout: config.Parameter{ + ID: "timeout", + Name: "Timeout", + Description: "How long to wait for a connection to the UPS (ms) before timing out. Defaults to \"30000ms\".", + Type: config.ParameterType_Int, + Default: map[config.Network]interface{}{config.Network_All: 0}, + AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, + EnvironmentVariables: []string{"ADDON_APCUPSD_TIMEOUT"}, + CanBeBlank: true, + OverwriteOnUpgrade: false, + }, + PollCron: config.Parameter{ + ID: "pollCron", + Name: "Update Interval", + Description: "CRON interval to poll stats from the UPC. Uses node-cron format, see https://www.npmjs.com/package/node-cron for details. Defaults to \"* * * * *\"", + Type: config.ParameterType_String, + Default: map[config.Network]interface{}{config.Network_All: ""}, + Regex: "^?:\\d+|\\*|\\*\\/\\d+$", + AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, + EnvironmentVariables: []string{"ADDON_APCUPSD_POLL_INTERVAL"}, + CanBeBlank: true, + OverwriteOnUpgrade: false, + }, } } @@ -39,6 +89,10 @@ func NewConfig() *ApcupsdConfig { func (cfg *ApcupsdConfig) GetParameters() []*config.Parameter { return []*config.Parameter{ &cfg.Enabled, + &cfg.MountPoint, + &cfg.PollCron, + &cfg.Debug, + // &cfg.Timeout, } } diff --git a/rocketpool-cli/service/config/addon-apcupsd.go b/rocketpool-cli/service/config/addon-apcupsd.go index 3e4e11b2d..5a834b573 100644 --- a/rocketpool-cli/service/config/addon-apcupsd.go +++ b/rocketpool-cli/service/config/addon-apcupsd.go @@ -10,7 +10,7 @@ import ( cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" ) -// The page wrapper for the Graffiti Wall Writer addon config +// The page wrapper for the APCUPSD addon config type AddonApcupsdPage struct { addonsPage *AddonsPage page *page @@ -21,7 +21,7 @@ type AddonApcupsdPage struct { otherParams []*parameterizedFormItem } -// Creates a new page for the Graffiti Wall Writer addon settings +// Creates a new page for the APCUPSD addon settings func NewAddonApcupsdPage(addonsPage *AddonsPage, addon addons.SmartnodeAddon) *AddonApcupsdPage { configPage := &AddonApcupsdPage{ @@ -48,7 +48,7 @@ func (configPage *AddonApcupsdPage) getPage() *page { return configPage.page } -// Creates the content for the GWW settings page +// Creates the content for the APCUPSD settings page func (configPage *AddonApcupsdPage) createContent() { // Create the layout diff --git a/shared/version.go b/shared/version.go index e7ee1172f..246c4ca38 100644 --- a/shared/version.go +++ b/shared/version.go @@ -1,6 +1,6 @@ package shared -const RocketPoolVersion string = "1.9.6-apcupsd-dev" +const RocketPoolVersion string = "1.9.6-apcupsd-dev.1" const Logo string = `______ _ _ ______ _ | ___ \ | | | | | ___ \ | | From 8e608547e9d18842726ee19975aa9d9d5e49cfac Mon Sep 17 00:00:00 2001 From: Dan Imbrogno Date: Thu, 8 Jun 2023 16:31:36 -0400 Subject: [PATCH 5/7] update config and reset version number to original --- addons/apcupsd/config.go | 48 +++++++++++++++++++++++++++++++--------- shared/version.go | 2 +- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/addons/apcupsd/config.go b/addons/apcupsd/config.go index 1f828937a..c045fe778 100644 --- a/addons/apcupsd/config.go +++ b/addons/apcupsd/config.go @@ -6,18 +6,20 @@ import ( // Constants const ( - containerTag string = "rocketpool/graffiti-wall-addon:v1.0.1" + containerTag string = "threevl/apcupsd-prometheus:0.1.0" ) // Configuration for the Graffiti Wall Writer type ApcupsdConfig struct { Title string `yaml:"-"` - Enabled config.Parameter `yaml:"enabled,omitempty"` - MountPoint config.Parameter `yaml:"mountPoint,omitempty"` - Debug config.Parameter `yaml:"debug,omitempty"` - PollCron config.Parameter `yaml:"pollCron,omitempty"` - Timeout config.Parameter `yaml:"timeout,omitempty"` + Enabled config.Parameter `yaml:"enabled,omitempty"` + ContainerTag config.Parameter `yaml:"containerTag,omitempty"` + MountPoint config.Parameter `yaml:"mountPoint,omitempty"` + Debug config.Parameter `yaml:"debug,omitempty"` + PollCron config.Parameter `yaml:"pollCron,omitempty"` + Timeout config.Parameter `yaml:"timeout,omitempty"` + OutputFilepath config.Parameter `yaml:"outputFilepath,omitempty"` } // Creates a new configuration instance @@ -36,6 +38,17 @@ func NewConfig() *ApcupsdConfig { CanBeBlank: false, OverwriteOnUpgrade: false, }, + ContainerTag: config.Parameter{ + ID: "containerTag", + Name: "APCUPSD Container Tag", + Description: "The tag name of the APCUPSD container you want to use on Docker Hub.", + Type: config.ParameterType_String, + Default: map[config.Network]interface{}{config.Network_All: containerTag}, + AffectsContainers: []config.ContainerID{config.ContainerID_Exporter}, + EnvironmentVariables: []string{"ADDON_APCUPSD_CONTAINER_TAG"}, + CanBeBlank: false, + OverwriteOnUpgrade: true, + }, MountPoint: config.Parameter{ ID: "mountPoint", Name: "APC USB Mount Location", @@ -63,8 +76,8 @@ func NewConfig() *ApcupsdConfig { ID: "timeout", Name: "Timeout", Description: "How long to wait for a connection to the UPS (ms) before timing out. Defaults to \"30000ms\".", - Type: config.ParameterType_Int, - Default: map[config.Network]interface{}{config.Network_All: 0}, + Type: config.ParameterType_Uint, + Default: map[config.Network]interface{}{config.Network_All: uint64(30000)}, AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, EnvironmentVariables: []string{"ADDON_APCUPSD_TIMEOUT"}, CanBeBlank: true, @@ -73,15 +86,26 @@ func NewConfig() *ApcupsdConfig { PollCron: config.Parameter{ ID: "pollCron", Name: "Update Interval", - Description: "CRON interval to poll stats from the UPC. Uses node-cron format, see https://www.npmjs.com/package/node-cron for details. Defaults to \"* * * * *\"", + Description: "Cron interval to poll stats from the UPC. Uses node-cron format, see https://www.npmjs.com/package/node-cron for details. Defaults to \"* * * * *\"", Type: config.ParameterType_String, Default: map[config.Network]interface{}{config.Network_All: ""}, Regex: "^?:\\d+|\\*|\\*\\/\\d+$", AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, - EnvironmentVariables: []string{"ADDON_APCUPSD_POLL_INTERVAL"}, + EnvironmentVariables: []string{"ADDON_APCUPSD_POLL_CRON"}, CanBeBlank: true, OverwriteOnUpgrade: false, }, + OutputFilepath: config.Parameter{ + ID: "outputFilepath", + Name: "Prometheus file name", + Description: "The filename to write ups data to within the node exporter textcollector directory", + Type: config.ParameterType_String, + Default: map[config.Network]interface{}{config.Network_All: "apcupsd.prom"}, + AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, + EnvironmentVariables: []string{"ADDON_APCUPSD_OUTPUT_FILEPATH"}, + CanBeBlank: false, + OverwriteOnUpgrade: false, + }, } } @@ -89,10 +113,12 @@ func NewConfig() *ApcupsdConfig { func (cfg *ApcupsdConfig) GetParameters() []*config.Parameter { return []*config.Parameter{ &cfg.Enabled, + &cfg.ContainerTag, &cfg.MountPoint, &cfg.PollCron, + &cfg.Timeout, + &cfg.OutputFilepath, &cfg.Debug, - // &cfg.Timeout, } } diff --git a/shared/version.go b/shared/version.go index 246c4ca38..251bae425 100644 --- a/shared/version.go +++ b/shared/version.go @@ -1,6 +1,6 @@ package shared -const RocketPoolVersion string = "1.9.6-apcupsd-dev.1" +const RocketPoolVersion string = "1.9.5" const Logo string = `______ _ _ ______ _ | ___ \ | | | | | ___ \ | | From ee84afbc632b72904a74c4a64438fa2a39a54249 Mon Sep 17 00:00:00 2001 From: Dan Imbrogno Date: Fri, 9 Jun 2023 13:52:12 -0400 Subject: [PATCH 6/7] Implements generating and mounting apcupsd.conf --- addons/apcupsd/addon.go | 6 +++-- addons/apcupsd/config.go | 36 +++++++++++++++++-------- rocketpool-cli/service/config/addons.go | 1 + shared/services/rocketpool/client.go | 28 ++++++++++++++----- 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/addons/apcupsd/addon.go b/addons/apcupsd/addon.go index c1aa1f231..9330a4c6b 100644 --- a/addons/apcupsd/addon.go +++ b/addons/apcupsd/addon.go @@ -8,8 +8,10 @@ import ( ) const ( - ContainerID_Apcupsd cfgtypes.ContainerID = "apcupsd" - ApcupsdContainerName string = "addon_apcupsd" + ContainerID_Apcupsd cfgtypes.ContainerID = "apcupsd" + ApcupsdContainerName string = "addon_apcupsd" + ApcupsdConfigTemplateName string = "addon_apcupsd_config" + ApcupsdConfigName string = "addon_apcupsd.conf" ) type Apcupsd struct { diff --git a/addons/apcupsd/config.go b/addons/apcupsd/config.go index c045fe778..9465ae005 100644 --- a/addons/apcupsd/config.go +++ b/addons/apcupsd/config.go @@ -6,20 +6,22 @@ import ( // Constants const ( - containerTag string = "threevl/apcupsd-prometheus:0.1.0" + containerTag string = "gersilex/apcupsd:v1.0.0" + exporterContainerTag string = "threevl/apcupsd-prometheus:0.2.0" ) // Configuration for the Graffiti Wall Writer type ApcupsdConfig struct { Title string `yaml:"-"` - Enabled config.Parameter `yaml:"enabled,omitempty"` - ContainerTag config.Parameter `yaml:"containerTag,omitempty"` - MountPoint config.Parameter `yaml:"mountPoint,omitempty"` - Debug config.Parameter `yaml:"debug,omitempty"` - PollCron config.Parameter `yaml:"pollCron,omitempty"` - Timeout config.Parameter `yaml:"timeout,omitempty"` - OutputFilepath config.Parameter `yaml:"outputFilepath,omitempty"` + Enabled config.Parameter `yaml:"enabled,omitempty"` + ApcupsdContainerTag config.Parameter `yaml:"apcupsdContainerTag,omitempty"` + ApcupsdExporterContainerTag config.Parameter `yaml:"apcupsdExporterContainerTag,omitempty"` + MountPoint config.Parameter `yaml:"mountPoint,omitempty"` + Debug config.Parameter `yaml:"debug,omitempty"` + PollCron config.Parameter `yaml:"pollCron,omitempty"` + Timeout config.Parameter `yaml:"timeout,omitempty"` + OutputFilepath config.Parameter `yaml:"outputFilepath,omitempty"` } // Creates a new configuration instance @@ -38,10 +40,10 @@ func NewConfig() *ApcupsdConfig { CanBeBlank: false, OverwriteOnUpgrade: false, }, - ContainerTag: config.Parameter{ + ApcupsdContainerTag: config.Parameter{ ID: "containerTag", Name: "APCUPSD Container Tag", - Description: "The tag name of the APCUPSD container you want to use on Docker Hub.", + 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{config.ContainerID_Exporter}, @@ -49,6 +51,17 @@ func NewConfig() *ApcupsdConfig { 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{config.ContainerID_Exporter}, + EnvironmentVariables: []string{"ADDON_APCUPSD_EXPORTER_CONTAINER_TAG"}, + CanBeBlank: false, + OverwriteOnUpgrade: true, + }, MountPoint: config.Parameter{ ID: "mountPoint", Name: "APC USB Mount Location", @@ -113,7 +126,8 @@ func NewConfig() *ApcupsdConfig { func (cfg *ApcupsdConfig) GetParameters() []*config.Parameter { return []*config.Parameter{ &cfg.Enabled, - &cfg.ContainerTag, + &cfg.ApcupsdContainerTag, + &cfg.ApcupsdExporterContainerTag, &cfg.MountPoint, &cfg.PollCron, &cfg.Timeout, diff --git a/rocketpool-cli/service/config/addons.go b/rocketpool-cli/service/config/addons.go index 0cbb0d072..2c785b3b7 100644 --- a/rocketpool-cli/service/config/addons.go +++ b/rocketpool-cli/service/config/addons.go @@ -40,6 +40,7 @@ func NewAddonsPage(home *settingsHome) *AddonsPage { // Create the addon subpages addonsPage.gwwPage = NewAddonGwwPage(addonsPage, home.md.Config.GraffitiWallWriter) + // TODO: Conditionally add the subpage only if metrics are enabled addonsPage.apcupsdPage = NewAddonApcupsdPage(addonsPage, home.md.Config.Apcupsd) addonSubpages := []settingsPage{ addonsPage.gwwPage, diff --git a/shared/services/rocketpool/client.go b/shared/services/rocketpool/client.go index 9c04b3d55..60e59a178 100644 --- a/shared/services/rocketpool/client.go +++ b/shared/services/rocketpool/client.go @@ -1637,7 +1637,7 @@ func (c *Client) composeAddons(cfg *config.RocketPoolConfig, rocketpoolDir strin } // APCUPSD - if cfg.Apcupsd.GetEnabledParameter().Value == true { + if cfg.EnableMetrics.Value == true && cfg.Apcupsd.GetEnabledParameter().Value == true { runtimeFolder := filepath.Join(rocketpoolDir, runtimeDir, "addons", "apcupsd") templatesFolder := filepath.Join(rocketpoolDir, templatesDir, "addons", "apcupsd") overrideFolder := filepath.Join(rocketpoolDir, overrideDir, "addons", "apcupsd") @@ -1648,17 +1648,33 @@ func (c *Client) composeAddons(cfg *config.RocketPoolConfig, rocketpoolDir strin return []string{}, fmt.Errorf("error creating addon runtime folder (%s): %w", runtimeFolder, err) } - contents, err := envsubst.ReadFile(filepath.Join(templatesFolder, apcupsd.ApcupsdContainerName+templateSuffix)) + // Write container file + containerContents, err := envsubst.ReadFile(filepath.Join(templatesFolder, apcupsd.ApcupsdContainerName+templateSuffix)) if err != nil { return []string{}, fmt.Errorf("error reading and substituting APCUPSD addon container template: %w", err) } - composePath := filepath.Join(runtimeFolder, apcupsd.ApcupsdContainerName+composeFileSuffix) - err = os.WriteFile(composePath, contents, 0664) + containerPath := filepath.Join(runtimeFolder, apcupsd.ApcupsdContainerName+composeFileSuffix) + err = os.WriteFile(containerPath, containerContents, 0664) if err != nil { - return []string{}, fmt.Errorf("could not write APCUPSD addon container file to %s: %w", composePath, err) + return []string{}, fmt.Errorf("could not write APCUPSD addon container file to %s: %w", containerPath, err) } - deployedContainers = append(deployedContainers, composePath) + + deployedContainers = append(deployedContainers, containerPath) deployedContainers = append(deployedContainers, filepath.Join(overrideFolder, apcupsd.ApcupsdContainerName+composeFileSuffix)) + + // TODO: Confirm this is a good location for a generic config file (i.e. not a container file) + // Write config file + + configContents, err := envsubst.ReadFile(filepath.Join(templatesFolder, apcupsd.ApcupsdConfigTemplateName+templateSuffix)) + if err != nil { + return []string{}, fmt.Errorf("error reading and substituting APCUPSD addon config template: %w", err) + } + configPath := filepath.Join(rocketpoolDir, apcupsd.ApcupsdConfigName) + err = os.WriteFile(configPath, configContents, 0664) + if err != nil { + return []string{}, fmt.Errorf("could not write APCUPSD addon config file to %s: %w", configPath, err) + } + } return deployedContainers, nil From 2fd09061f11abc5ab607a1e41ffc767a5368b82c Mon Sep 17 00:00:00 2001 From: Dan Imbrogno Date: Tue, 13 Jun 2023 15:19:14 -0400 Subject: [PATCH 7/7] Implements container / network modes --- addons/apcupsd/addon.go | 11 +- addons/apcupsd/config.go | 120 +++++++++--------- .../service/config/addon-apcupsd.go | 91 ++++++++++--- rocketpool-cli/service/config/addons.go | 11 +- shared/services/rocketpool/client.go | 44 +++++-- 5 files changed, 172 insertions(+), 105 deletions(-) diff --git a/addons/apcupsd/addon.go b/addons/apcupsd/addon.go index 9330a4c6b..68f80f792 100644 --- a/addons/apcupsd/addon.go +++ b/addons/apcupsd/addon.go @@ -8,10 +8,13 @@ import ( ) const ( - ContainerID_Apcupsd cfgtypes.ContainerID = "apcupsd" - ApcupsdContainerName string = "addon_apcupsd" - ApcupsdConfigTemplateName string = "addon_apcupsd_config" - ApcupsdConfigName string = "addon_apcupsd.conf" + 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 { diff --git a/addons/apcupsd/config.go b/addons/apcupsd/config.go index 9465ae005..01847bc43 100644 --- a/addons/apcupsd/config.go +++ b/addons/apcupsd/config.go @@ -7,7 +7,14 @@ import ( // Constants const ( containerTag string = "gersilex/apcupsd:v1.0.0" - exporterContainerTag string = "threevl/apcupsd-prometheus:0.2.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 @@ -17,11 +24,10 @@ type ApcupsdConfig struct { 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"` - Debug config.Parameter `yaml:"debug,omitempty"` - PollCron config.Parameter `yaml:"pollCron,omitempty"` - Timeout config.Parameter `yaml:"timeout,omitempty"` - OutputFilepath config.Parameter `yaml:"outputFilepath,omitempty"` + Mode config.Parameter `yaml:"mode,omitempty"` + NetworkAddress config.Parameter `yaml:"NetworkAddress,omitempty"` } // Creates a new configuration instance @@ -35,18 +41,38 @@ func NewConfig() *ApcupsdConfig { Description: "Enable APCUPSD monitoring", Type: config.ParameterType_Bool, Default: map[config.Network]interface{}{config.Network_All: false}, - AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, + 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{config.ContainerID_Exporter}, + AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, EnvironmentVariables: []string{"ADDON_APCUPSD_CONTAINER_TAG"}, CanBeBlank: false, OverwriteOnUpgrade: true, @@ -57,65 +83,41 @@ func NewConfig() *ApcupsdConfig { 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{config.ContainerID_Exporter}, + 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*`\n This is the value to enter in the field below. NOTE: If you reconnect your UPC this value may need to be updated.", + 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: ""}, - Regex: "[^\\0]+", + Default: map[config.Network]interface{}{config.Network_All: "/dev/usb/hiddev0"}, AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, EnvironmentVariables: []string{"ADDON_APCUPSD_MOUNT_POINT"}, - CanBeBlank: true, - OverwriteOnUpgrade: false, - }, - Debug: config.Parameter{ - ID: "debug", - Name: "Debug", - Description: "Output debug logs for APCUPSD monitoring", - Type: config.ParameterType_Bool, - Default: map[config.Network]interface{}{config.Network_All: false}, - AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, - EnvironmentVariables: []string{"ADDON_APCUPSD_DEBUG"}, - CanBeBlank: true, - OverwriteOnUpgrade: false, - }, - Timeout: config.Parameter{ - ID: "timeout", - Name: "Timeout", - Description: "How long to wait for a connection to the UPS (ms) before timing out. Defaults to \"30000ms\".", - Type: config.ParameterType_Uint, - Default: map[config.Network]interface{}{config.Network_All: uint64(30000)}, - AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, - EnvironmentVariables: []string{"ADDON_APCUPSD_TIMEOUT"}, - CanBeBlank: true, - OverwriteOnUpgrade: false, - }, - PollCron: config.Parameter{ - ID: "pollCron", - Name: "Update Interval", - Description: "Cron interval to poll stats from the UPC. Uses node-cron format, see https://www.npmjs.com/package/node-cron for details. Defaults to \"* * * * *\"", - Type: config.ParameterType_String, - Default: map[config.Network]interface{}{config.Network_All: ""}, - Regex: "^?:\\d+|\\*|\\*\\/\\d+$", - AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, - EnvironmentVariables: []string{"ADDON_APCUPSD_POLL_CRON"}, - CanBeBlank: true, + CanBeBlank: false, OverwriteOnUpgrade: false, }, - OutputFilepath: config.Parameter{ - ID: "outputFilepath", - Name: "Prometheus file name", - Description: "The filename to write ups data to within the node exporter textcollector directory", + 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: "apcupsd.prom"}, - AffectsContainers: []config.ContainerID{ContainerID_Apcupsd}, - EnvironmentVariables: []string{"ADDON_APCUPSD_OUTPUT_FILEPATH"}, + 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, }, @@ -124,16 +126,8 @@ func NewConfig() *ApcupsdConfig { // Get the parameters for this config func (cfg *ApcupsdConfig) GetParameters() []*config.Parameter { - return []*config.Parameter{ - &cfg.Enabled, - &cfg.ApcupsdContainerTag, - &cfg.ApcupsdExporterContainerTag, - &cfg.MountPoint, - &cfg.PollCron, - &cfg.Timeout, - &cfg.OutputFilepath, - &cfg.Debug, - } + return []*config.Parameter{&cfg.Enabled, &cfg.Mode, &cfg.ApcupsdExporterContainerTag, &cfg.ApcupsdContainerTag, &cfg.MetricsPort, &cfg.MountPoint, &cfg.NetworkAddress} + } // The the title for the config diff --git a/rocketpool-cli/service/config/addon-apcupsd.go b/rocketpool-cli/service/config/addon-apcupsd.go index 5a834b573..29a873157 100644 --- a/rocketpool-cli/service/config/addon-apcupsd.go +++ b/rocketpool-cli/service/config/addon-apcupsd.go @@ -7,18 +7,22 @@ import ( "github.com/rivo/tview" "github.com/rocket-pool/smartnode/shared/services/config" "github.com/rocket-pool/smartnode/shared/types/addons" - cfgtypes "github.com/rocket-pool/smartnode/shared/types/config" ) // 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 - otherParams []*parameterizedFormItem + 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 @@ -76,21 +80,31 @@ func (configPage *AddonApcupsdPage) createContent() { // Get the parameters enabledParam := configPage.addon.GetEnabledParameter() - otherParams := []*cfgtypes.Parameter{} - - for _, param := range configPage.addon.GetConfig().GetParameters() { - if param.ID != enabledParam.ID { - otherParams = append(otherParams, param) - } - } + // TODO: Don't like how I reference these by index here. Is there a better way? + 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.otherParams = createParameterizedFormItems(otherParams, configPage.layout.descriptionBox) + 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.otherParams...) + 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) { @@ -100,25 +114,60 @@ func (configPage *AddonApcupsdPage) createContent() { 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.handleEnableChanged() + configPage.handleDraw() } -// Handle all of the form changes when the Use Fallback EC box has changed +// 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() { configPage.layout.form.Clear(true) configPage.layout.form.AddFormItem(configPage.enabledBox.item) - // Only add the supporting stuff if external clients are enabled + // Only add the supporting stuff if addon is enabled if configPage.addon.GetEnabledParameter().Value == false { return } - configPage.layout.addFormItems(configPage.otherParams) + 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() diff --git a/rocketpool-cli/service/config/addons.go b/rocketpool-cli/service/config/addons.go index 2c785b3b7..f26dae995 100644 --- a/rocketpool-cli/service/config/addons.go +++ b/rocketpool-cli/service/config/addons.go @@ -40,12 +40,17 @@ func NewAddonsPage(home *settingsHome) *AddonsPage { // Create the addon subpages addonsPage.gwwPage = NewAddonGwwPage(addonsPage, home.md.Config.GraffitiWallWriter) - // TODO: Conditionally add the subpage only if metrics are enabled - addonsPage.apcupsdPage = NewAddonApcupsdPage(addonsPage, home.md.Config.Apcupsd) + addonSubpages := []settingsPage{ addonsPage.gwwPage, - addonsPage.apcupsdPage, } + + // TODO: Make this respond to uncommitted config changes + if home.md.Config.EnableMetrics.Value == true { + addonsPage.apcupsdPage = NewAddonApcupsdPage(addonsPage, home.md.Config.Apcupsd) + addonSubpages = append(addonSubpages, addonsPage.apcupsdPage) + } + addonsPage.addonSubpages = addonSubpages // Add the subpages to the main display diff --git a/shared/services/rocketpool/client.go b/shared/services/rocketpool/client.go index 60e59a178..d980e6bd4 100644 --- a/shared/services/rocketpool/client.go +++ b/shared/services/rocketpool/client.go @@ -1649,10 +1649,26 @@ func (c *Client) composeAddons(cfg *config.RocketPoolConfig, rocketpoolDir strin } // Write container file - containerContents, err := envsubst.ReadFile(filepath.Join(templatesFolder, apcupsd.ApcupsdContainerName+templateSuffix)) - if err != nil { - return []string{}, fmt.Errorf("error reading and substituting APCUPSD addon container template: %w", err) + apcupsdMode := cfg.Apcupsd.GetConfig().GetParameters()[1] + + var containerContents []byte + + if apcupsdMode.Value == apcupsd.Mode_Network { + + containerContents, err = envsubst.ReadFile(filepath.Join(templatesFolder, apcupsd.ApcupsdNetworkComposeTemplateName+templateSuffix)) + if err != nil { + return []string{}, fmt.Errorf("error reading and substituting APCUPSD addon container template: %w", err) + } + + } else if apcupsdMode.Value == apcupsd.Mode_Container { + + containerContents, err = envsubst.ReadFile(filepath.Join(templatesFolder, apcupsd.ApcupsdContainerComposeTemplateName+templateSuffix)) + if err != nil { + return []string{}, fmt.Errorf("error reading and substituting APCUPSD addon container template: %w", err) + } + } + containerPath := filepath.Join(runtimeFolder, apcupsd.ApcupsdContainerName+composeFileSuffix) err = os.WriteFile(containerPath, containerContents, 0664) if err != nil { @@ -1662,19 +1678,19 @@ func (c *Client) composeAddons(cfg *config.RocketPoolConfig, rocketpoolDir strin deployedContainers = append(deployedContainers, containerPath) deployedContainers = append(deployedContainers, filepath.Join(overrideFolder, apcupsd.ApcupsdContainerName+composeFileSuffix)) - // TODO: Confirm this is a good location for a generic config file (i.e. not a container file) // Write config file - - configContents, err := envsubst.ReadFile(filepath.Join(templatesFolder, apcupsd.ApcupsdConfigTemplateName+templateSuffix)) - if err != nil { - return []string{}, fmt.Errorf("error reading and substituting APCUPSD addon config template: %w", err) - } - configPath := filepath.Join(rocketpoolDir, apcupsd.ApcupsdConfigName) - err = os.WriteFile(configPath, configContents, 0664) - if err != nil { - return []string{}, fmt.Errorf("could not write APCUPSD addon config file to %s: %w", configPath, err) + if apcupsdMode.Value == apcupsd.Mode_Container { + // TODO: Confirm this is a good location for a generic config file (i.e. not a container file) + configContents, err := envsubst.ReadFile(filepath.Join(templatesFolder, apcupsd.ApcupsdConfigTemplateName+templateSuffix)) + if err != nil { + return []string{}, fmt.Errorf("error reading and substituting APCUPSD addon config template: %w", err) + } + configPath := filepath.Join(rocketpoolDir, apcupsd.ApcupsdConfigName) + err = os.WriteFile(configPath, configContents, 0664) + if err != nil { + return []string{}, fmt.Errorf("could not write APCUPSD addon config file to %s: %w", configPath, err) + } } - } return deployedContainers, nil