diff --git a/pkg/diagnostic/prom/version/check.go b/pkg/diagnostic/prom/version/check.go new file mode 100644 index 0000000..eb2310e --- /dev/null +++ b/pkg/diagnostic/prom/version/check.go @@ -0,0 +1,108 @@ +package version + +import ( + "bytes" + "context" + "fmt" + net "net/http" + "os" + "os/exec" + + "github.com/cloudzero/cloudzero-agent-validator/pkg/config" + "github.com/cloudzero/cloudzero-agent-validator/pkg/diagnostic" + "github.com/cloudzero/cloudzero-agent-validator/pkg/logging" + "github.com/cloudzero/cloudzero-agent-validator/pkg/status" + "github.com/sirupsen/logrus" +) + +const DiagnosticPrometheusVersion = config.DiagnosticPrometheusVersion + +type checker struct { + cfg *config.Settings + logger *logrus.Entry +} + +func NewProvider(ctx context.Context, cfg *config.Settings) diagnostic.Provider { + return &checker{ + cfg: cfg, + logger: logging.NewLogger(). + WithContext(ctx).WithField(logging.OpField, "prom"), + } +} + +func (c *checker) Check(ctx context.Context, _ *net.Client, accessor status.Accessor) error { + if len(c.cfg.Prometheus.Executable) == 0 { + accessor.AddCheck(&status.StatusCheck{ + Name: DiagnosticPrometheusVersion, + Error: "no prometheus binary available at configured location", + }) + return nil + } + + versionData, err := c.GetVersion(ctx) + if err != nil { + accessor.AddCheck( + &status.StatusCheck{ + Name: DiagnosticPrometheusVersion, + Error: err.Error(), + }) + return nil + } + + accessor.WriteToReport(func(s *status.ClusterStatus) { + s.AgentVersion = string(versionData) + s.Checks = append(s.Checks, &status.StatusCheck{Name: DiagnosticPrometheusVersion, Passing: true}) + }) + return nil +} + +func (c *checker) GetVersion(ctx context.Context) ([]byte, error) { + executable := c.cfg.Prometheus.Executable + if len(executable) == 0 { + return nil, fmt.Errorf("no prometheus binary available at configured location") + } + + fi, err := os.Stat(executable) + if os.IsNotExist(err) { + return nil, fmt.Errorf("prometheus executable not found: %w", err) + } + if fi.Mode()&0111 == 0 { + return nil, fmt.Errorf("prometheus executable is not executable: %s", executable) + } + + // create the raw output fle + rawOutput, err := os.CreateTemp(os.TempDir(), ".promver.*") + if err != nil { + return nil, fmt.Errorf("failed to create raw prometheus version output file: %w", err) + } + defer func() { + _ = rawOutput.Close() + _ = os.Remove(rawOutput.Name()) + }() + + // Build the command and Exec the scanner + cmd := exec.CommandContext(ctx, executable, "--version") + + // capture the output + var stderr bytes.Buffer + cmd.Stderr = &stderr + cmd.Stdout = rawOutput + + // Now run the app + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("failed to run prometheus: %w", err) + } + + // make sure all bytes are written from the standard output + if err := rawOutput.Sync(); err != nil { + return nil, fmt.Errorf("failed to sync raw prometheus output file: %w", err) + } + + // seek to the beginning of the file for reading. + if _, err := rawOutput.Seek(0, 0); err != nil { + return nil, fmt.Errorf("failed to seek to the beginning of the raw prometheus output file: %w", err) + } + + // read the results into a byte slice + return os.ReadFile(rawOutput.Name()) +} diff --git a/pkg/diagnostic/prom/version/check_test.go b/pkg/diagnostic/prom/version/check_test.go new file mode 100644 index 0000000..765b933 --- /dev/null +++ b/pkg/diagnostic/prom/version/check_test.go @@ -0,0 +1,74 @@ +package version_test + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/cloudzero/cloudzero-agent-validator/pkg/config" + "github.com/cloudzero/cloudzero-agent-validator/pkg/diagnostic/prom/version" + "github.com/cloudzero/cloudzero-agent-validator/pkg/status" +) + +func makeReport() status.Accessor { + return status.NewAccessor(&status.ClusterStatus{}) +} + +func TestChecker_GetVersion(t *testing.T) { + tests := []struct { + name string + executable string + expected bool + }{ + { + name: "ExecutableNotFound", + executable: "/path/to/nonexistent/prometheus", + expected: false, + }, + { + name: "ExecutableEmpty", + executable: "", + expected: false, + }, + { + name: "Success", + executable: getPromExecutablePath(), + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &config.Settings{ + Prometheus: config.Prometheus{ + Executable: tt.executable, + }, + } + provider := version.NewProvider(context.Background(), cfg) + accessor := makeReport() + + err := provider.Check(context.Background(), nil, accessor) + assert.NoError(t, err) + + accessor.ReadFromReport(func(s *status.ClusterStatus) { + assert.Len(t, s.Checks, 1) + for _, c := range s.Checks { + assert.Equal(t, tt.expected, c.Passing) + } + if tt.expected { + assert.NotEmpty(t, s.AgentVersion) + } + }) + }) + } +} + +func getPromExecutablePath() string { + wd, err := os.Getwd() + if err != nil { + panic(err) + } + return wd + "/testdata/prometheus" +} diff --git a/pkg/diagnostic/prom/version/testdata/prometheus b/pkg/diagnostic/prom/version/testdata/prometheus new file mode 100755 index 0000000..83608d9 --- /dev/null +++ b/pkg/diagnostic/prom/version/testdata/prometheus @@ -0,0 +1,8 @@ +#!/bin/sh + +echo " prometheus, version 2.50.1 (branch: HEAD, revision: 8c9b0285360a0b6288d76214a75ce3025bce4050)" +echo " build user: root@6213bb3ee580" +echo " build date: 20240226-11:38:47" +echo " go version: go1.21.7" +echo " platform: linux/arm64" +echo " tags: netgo,builtinassets,stringlabels" \ No newline at end of file