diff --git a/Makefile b/Makefile index ad55dc5..ae3baf4 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ endef compile: daemon-compile-currentenv daemon-compile-currentenv: - go build -o OliveTin github.com/OliveTin/OliveTin/cmd/OliveTin + go build github.com/OliveTin/OliveTin/cmd/OliveTin daemon-compile-armhf: go env -w GOARCH=arm GOARM=6 @@ -31,7 +31,8 @@ daemon-codestyle: gocritic check ./... daemon-unittests: - mkdir -p reports + $(call delete-files,reports) + mkdir reports go test ./... -coverprofile reports/unittests.out go tool cover -html=reports/unittests.out -o reports/unittests.html diff --git a/integration-tests/Makefile b/integration-tests/Makefile index 46132c0..a0a6622 100644 --- a/integration-tests/Makefile +++ b/integration-tests/Makefile @@ -4,7 +4,7 @@ test-install: npm install --no-fund test-run: - ./node_modules/.bin/mocha -t 10000 + npx mocha -t 10000 find-flakey-tests: echo "Running test-run infinately" diff --git a/integration-tests/configs/general/config.yaml b/integration-tests/configs/general/config.yaml index 8c864f8..d70c0bc 100644 --- a/integration-tests/configs/general/config.yaml +++ b/integration-tests/configs/general/config.yaml @@ -24,12 +24,12 @@ actions: shell: sleep 5 icon: "😪" -- title: date-popup - shell: date +- title: dir-popup + shell: dir popupOnStart: execution-dialog-stdout-only -- title: date-passive - shell: date +- title: cd-passive + shell: cd - title: "Run Ansible Playbook" icon: "🇦" diff --git a/integration-tests/configs/prometheus/config.yaml b/integration-tests/configs/prometheus/config.yaml new file mode 100644 index 0000000..a9c8dea --- /dev/null +++ b/integration-tests/configs/prometheus/config.yaml @@ -0,0 +1,16 @@ +# +# Integration Test Config: General +# + +listenAddressSingleHTTPFrontend: 0.0.0.0:1337 + +logLevel: "DEBUG" +checkForUpdates: false + +prometheus: + enabled: true + defaultGoMetrics: false + +actions: + - title: Hello OliveTin + shell: echo "Hello OliveTin" diff --git a/integration-tests/runner.mjs b/integration-tests/runner.mjs index e880a44..be5b75b 100644 --- a/integration-tests/runner.mjs +++ b/integration-tests/runner.mjs @@ -25,6 +25,10 @@ class OliveTinTestRunner { baseUrl() { return this.BASE_URL } + + metricsUrl() { + return new URL('metrics', this.baseUrl()); + } } class OliveTinTestRunnerStartLocalProcess extends OliveTinTestRunner { diff --git a/integration-tests/test/general.mjs b/integration-tests/test/general.mjs index b547e60..2c648ee 100644 --- a/integration-tests/test/general.mjs +++ b/integration-tests/test/general.mjs @@ -44,42 +44,42 @@ describe('config: general', function () { expect(buttons).to.have.length(8) }) - it('Start date action (popup)', async function() { + it('Start dir action (popup)', async function () { await getRootAndWait() - const buttons = await webdriver.findElements(By.css('[title="date-popup"]')) + const buttons = await webdriver.findElements(By.css('[title="dir-popup"]')) expect(buttons).to.have.length(1) - const buttonDate = buttons[0] + const buttonCMD = buttons[0] - expect(buttonDate).to.not.be.null + expect(buttonCMD).to.not.be.null - buttonDate.click() + buttonCMD.click() const dialog = await webdriver.findElement(By.id('execution-results-popup')) expect(await dialog.isDisplayed()).to.be.true const title = await webdriver.findElement(By.id('execution-dialog-title')) - expect(await title.getAttribute('innerText')).to.be.equal('date-popup') + expect(await webdriver.wait(until.elementTextIs(title, 'dir-popup'), 2000)) const dialogErr = await webdriver.findElement(By.id('big-error')) expect(dialogErr).to.not.be.null expect(await dialogErr.isDisplayed()).to.be.false }) - it('Start date action (passive)', async function() { + it('Start cd action (passive)', async function () { await getRootAndWait() - const buttons = await webdriver.findElements(By.css('[title="date-passive"]')) + const buttons = await webdriver.findElements(By.css('[title="cd-passive"]')) expect(buttons).to.have.length(1) - const buttonDate = buttons[0] + const buttonCMD = buttons[0] - expect(buttonDate).to.not.be.null + expect(buttonCMD).to.not.be.null - buttonDate.click() + buttonCMD.click() const dialog = await webdriver.findElement(By.id('execution-results-popup')) expect(await dialog.isDisplayed()).to.be.false diff --git a/integration-tests/test/prometheus.mjs b/integration-tests/test/prometheus.mjs new file mode 100644 index 0000000..0c25e50 --- /dev/null +++ b/integration-tests/test/prometheus.mjs @@ -0,0 +1,33 @@ +import { describe, it, before, after } from 'mocha' +import { expect } from 'chai' + +import { By } from 'selenium-webdriver' + +let metrics = [ + {'name': 'olivetin_actions_requested_count', 'type': 'counter', 'desc': 'The actions requested count'}, + {'name': 'olivetin_config_action_count', 'type': 'gauge', 'desc': 'The number of actions in the config file'}, + {'name': 'olivetin_config_reloaded_count', 'type': 'counter', 'desc': 'The number of times the config has been reloaded'}, + {'name': 'olivetin_sv_count', 'type': 'gauge', 'desc': 'The number entries in the sv map'}, +] + +describe('config: prometheus', function () { + before(async function () { + await runner.start('prometheus') + }) + + after(async () => { + await runner.stop() + }) + + it('Metrics are available with correct types', async () => { + webdriver.get(runner.metricsUrl()) + const prometheusOutput = await webdriver.findElement(By.tagName('pre')).getText() + + expect(prometheusOutput).to.not.be.null + metrics.forEach(({name, type, desc}) => { + const metaLines = `# HELP ${name} ${desc}\n` + + `# TYPE ${name} ${type}\n` + expect(prometheusOutput).to.match(new RegExp(metaLines)) + }) + }) +}) diff --git a/internal/config/config_reloader.go b/internal/config/config_reloader.go index 5042d12..0a34add 100644 --- a/internal/config/config_reloader.go +++ b/internal/config/config_reloader.go @@ -4,9 +4,10 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - log "github.com/sirupsen/logrus" "os" - "path" + "path/filepath" + + log "github.com/sirupsen/logrus" "github.com/spf13/viper" ) @@ -14,7 +15,7 @@ import ( var ( metricConfigActionCount = promauto.NewGauge(prometheus.GaugeOpts{ Name: "olivetin_config_action_count", - Help: "Then number of actions in the config file", + Help: "The number of actions in the config file", }) metricConfigReloadedCount = promauto.NewCounter(prometheus.CounterOpts{ @@ -38,7 +39,7 @@ func Reload(cfg *Config) { metricConfigReloadedCount.Inc() metricConfigActionCount.Set(float64(len(cfg.Actions))) - cfg.SetDir(path.Dir(viper.ConfigFileUsed())) + cfg.SetDir(filepath.Dir(viper.ConfigFileUsed())) cfg.Sanitize() for _, l := range listeners { diff --git a/internal/executor/executor.go b/internal/executor/executor.go index 4e54947..7aeed52 100644 --- a/internal/executor/executor.go +++ b/internal/executor/executor.go @@ -22,7 +22,7 @@ import ( ) var ( - metricActionsRequested = promauto.NewGauge(prometheus.GaugeOpts{ + metricActionsRequested = promauto.NewCounter(prometheus.CounterOpts{ Name: "olivetin_actions_requested_count", Help: "The actions requested count", }) @@ -135,11 +135,6 @@ func (e *Executor) AddListener(m listener) { // ExecRequest processes an ExecutionRequest func (e *Executor) ExecRequest(req *ExecutionRequest) (*sync.WaitGroup, string) { req.executor = e - - // req.UUID is now set by the client, so that they can track the request - // from start to finish. This means that a malicious client could send - // duplicate UUIDs (or just random strings), but this is the only way. - req.logEntry = &InternalLogEntry{ DatetimeStarted: time.Now(), ExecutionTrackingID: req.TrackingID, @@ -152,12 +147,14 @@ func (e *Executor) ExecRequest(req *ExecutionRequest) (*sync.WaitGroup, string) ActionIcon: "💩", } - _, foundLog := e.Logs[req.TrackingID] + _, isDuplicate := e.Logs[req.TrackingID] - if foundLog || req.TrackingID == "" { + if isDuplicate || req.TrackingID == "" { req.TrackingID = uuid.NewString() } + log.Tracef("executor.ExecRequest(): %v", req) + e.Logs[req.TrackingID] = req.logEntry wg := new(sync.WaitGroup)