Skip to content

Commit

Permalink
feat(install): local validation for OHI installations (#1516)
Browse files Browse the repository at this point in the history
  • Loading branch information
noahmmcgivern authored Oct 3, 2023
1 parent d9cf52b commit 0f2c034
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 3 deletions.
15 changes: 12 additions & 3 deletions internal/install/recipe_installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -620,13 +620,22 @@ func (i *RecipeInstall) validateRecipeViaAllMethods(ctx context.Context, r *type
log.Debugf("no validationUrl defined, skipping")
}

// Add NRQL validation if configured
if r.ValidationNRQL != "" {
hasValidationIntegration := r.ValidationIntegration != ""
hasValidationNRQL := r.ValidationNRQL != ""

// Add either Integration Validation or NRQL Validation if configured
if hasValidationIntegration {
integrationName := r.ValidationIntegration

validationFuncs = append(validationFuncs, func() (string, error) {
return validation.ValidateIntegration(integrationName)
})
} else if hasValidationNRQL {
validationFuncs = append(validationFuncs, func() (string, error) {
return i.recipeValidator.ValidateRecipe(timeoutCtx, *m, *r, vars)
})
} else {
log.Debugf("no validationNRQL defined, skipping")
log.Debugf("no validationIntegration or validationNRQL defined, skipping")
}

if len(validationFuncs) == 0 {
Expand Down
4 changes: 4 additions & 0 deletions internal/install/types/recipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ func (r *OpenInstallationRecipe) UnmarshalYAML(unmarshal func(interface{}) error
r.ValidationURL = v.(string)
}

if v, ok := recipe["validationIntegration"]; ok {
r.ValidationIntegration = v.(string)
}

return err
}

Expand Down
2 changes: 2 additions & 0 deletions internal/install/types/types.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

101 changes: 101 additions & 0 deletions internal/install/validation/integration_validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package validation

import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"

"gopkg.in/yaml.v3"
)

type integrationType struct {
Name string `yaml:"name"`
Env map[string]string `yaml:"env"`
}

type configType struct {
Integrations []integrationType `yaml:"integrations"`
}

// Validate an integration using its <integrationName>-config.yml
// by iterating over the defined integrations and running the
// integration command with its defined environment variables.
// <integrationName>-config.yml is determined to be valid if every
// defined integration exits without error (exits with exit code 0).
// The <integrationName>-config.yml is located in the
// default configuration directory, which may vary by GOOS.
func ValidateIntegration(integrationName string) (string, error) {
configBasename := fmt.Sprintf("%s-config.yml", integrationName)

configPath := filepath.Join(ConfigurationsDirname, configBasename)

config, err := readConfig(configPath)

if err != nil {
return "", err
}

for _, integration := range config.Integrations {
integrationBasename := integration.Name

binPath := filepath.Join(IntegrationsDirname, integrationBasename)

cmd := exec.Command(binPath)

for k, v := range integration.Env {
env := fmt.Sprintf("%s=%s", k, v)

cmd.Env = append(cmd.Env, env)
}

var e strings.Builder

cmd.Stderr = &e

if err := cmd.Run(); err != nil {
stderr := e.String()

if stderr != "" {
s := strings.TrimSpace(stderr)

return "", fmt.Errorf("%w: %s", err, s)
}

return "", err
}
}

return "", nil
}

// Reads and unmarshals an <integrationName>-config.yml from the
// given configPath, returning a configType{} containing the
// defined integrations and their respective environments.
// Returns an empty configType{} and an error if:
// 1.) The file does not exist
// 2.) The file can not be read
// 3.) The file cannot be unmarshalled to a configType{}
func readConfig(configPath string) (configType, error) {
if _, err := os.Stat(configPath); errors.Is(err, os.ErrNotExist) {
return configType{}, err
}

b, err := os.ReadFile(configPath)

if err != nil {
return configType{}, err
}

config := configType{}

err = yaml.Unmarshal(b, &config)

if err != nil {
return configType{}, err
}

return config, nil
}
7 changes: 7 additions & 0 deletions internal/install/validation/integration_validator_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package validation

// There are no integrations for macOS,
// therefore there is no directory
// for integrations or configurations.
const IntegrationsDirname = "/dev/null"
const ConfigurationsDirname = "/dev/null"
4 changes: 4 additions & 0 deletions internal/install/validation/integration_validator_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package validation

const IntegrationsDirname = "/var/db/newrelic-infra/newrelic-integrations/bin/"
const ConfigurationsDirname = "/etc/newrelic-infra/integrations.d/"
4 changes: 4 additions & 0 deletions internal/install/validation/integration_validator_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package validation

const IntegrationsDirname = "C:\\Program Files\\New Relic\\newrelic-infra\\newrelic-integrations\\bin\\"
const ConfigurationsDirname = "C:\\Program Files\\New Relic\\newrelic-infra\\integrations.d\\"

0 comments on commit 0f2c034

Please sign in to comment.