Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
packages:
github.com/fortnoxab/gitmachinecontroller/pkg/agent/command:
interfaces:
# select the interfaces you want mocked
Commander:
config:
outpkg: mocks
dir: ../../mocks
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.PHONY: test imports
SHELL := /bin/bash

test: imports
go test -v ./...

imports: SHELL:=/bin/bash
imports:
go install golang.org/x/tools/cmd/goimports@latest
ASD=$$(goimports -l . 2>&1); test -z "$$ASD" || (echo "Code is not formatted correctly according to goimports! $$ASD" && exit 1)

2 changes: 2 additions & 0 deletions cmd/gmc/main.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:generate go install github.com/vektra/mockery/v2@v2.45.1
//go:generate mockery
package main

import (
Expand Down
269 changes: 269 additions & 0 deletions e2e/e2e_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
package e2e_test

import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"

"github.com/fortnoxab/gitmachinecontroller/pkg/admin"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

func TestMasterAgentAccept(t *testing.T) {

ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
c, closer := initMasterAgent(t, ctx)
defer closer()

resp, err := c.client.Get("/machines")
assert.NoError(t, err)
body := getBody(t, resp.Body)
assert.Contains(t, body, "acceptHost('mycooltestagent')")
assert.Contains(t, body, ">Accept<")

_, err = c.client.Post("/api/machines/accept-v1", bytes.NewBufferString(`{"host":"mycooltestagent"}`))
assert.NoError(t, err)

time.Sleep(200 * time.Millisecond)
resp, err = c.client.Get("/machines")
assert.NoError(t, err)
body = getBody(t, resp.Body)
assert.NotContains(t, body, ">Accept<")

resp, err = c.client.Get("/api/machines-v1")
assert.NoError(t, err)
body = getBody(t, resp.Body)
assert.Contains(t, body, `"name":"mycooltestagent"`)
assert.Contains(t, body, `"ip":"127.0.0.1"`)
assert.Contains(t, body, `"Online":true`)
assert.Contains(t, body, `"Accepted":true`)
assert.Contains(t, body, `"Git":false`)

cancel()
c.wg.Wait()
}
func TestMasterAgentGitOps(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "the file content")
}))
defer ts.Close()
machineYaml := fmt.Sprintf(`apiVersion: gitmachinecontroller.io/v1beta1
metadata:
annotations:
feature: ihavecoolfeature
labels:
os: rocky9
type: server
name: mycooltestagent
spec:
commands: []
files:
- checksum: 9b76e7ea790545334ea524f3ca33db8eb6c4541a9b476911e5abf850a566b41c
path: /tmp/testfromurl
url: %s
- content: |
[Unit]
Description=Mimir Service
After=network.target

[Service]
Type=simple
User=root
Group=root
ExecStart=/usr/local/sbin/mimir -config.file=/etc/mimir.yml
SuccessExitStatus=0
TimeoutSec=30
SyslogIdentifier=mimir
Restart=on-failure
RestartSec=3
LimitNOFILE=1048576

[Install]
WantedBy=multi-user.target
path: /tmp/test.systemd
systemd:
action: restart
name: exporter_exporter
daemonreload: true
ip: 10.81.22.150
lines: []
packages:
- name: vim-enhanced
version: '*'
- name: mycoolpackage
version: '*'`, ts.URL)

err := os.WriteFile("./gitrepo/mycooltestagent.yml", []byte(machineYaml), 0666)
assert.NoError(t, err)
defer os.Remove("./gitrepo/mycooltestagent.yml")
defer os.Remove("/tmp/test.systemd")
defer os.Remove("/tmp/testfromurl")

ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
c, closer := initMasterAgent(t, ctx)
defer closer()

c.commander.Mock.On("Run", "systemctl restart exporter_exporter").Return("", "", nil).Once()
c.commander.Mock.On("Run", "systemctl daemon reload").Return("", "", nil).Once()

// return 0 means its already installed
c.commander.Mock.On("RunExpectCodes", "rpm -q vim-enhanced", 0, 1).Return("", 0, nil).Twice()

// return 1 means its not installed yet and we'll try to install it
c.commander.Mock.On("RunExpectCodes", "rpm -q mycoolpackage", 0, 1).Return("", 1, nil).Twice()
c.commander.Mock.On("Run", "rpm -q --whatprovides mycoolpackage").Return("mycoolpackage", "", nil).Twice()
c.commander.Mock.On("Run", "yum install -y mycoolpackage").Return("", "", nil).Twice()

_, err = c.client.Post("/api/machines/accept-v1", bytes.NewBufferString(`{"host":"mycooltestagent"}`))
assert.NoError(t, err)

time.Sleep(2 * time.Second)

// make sure we fetched file from http server with sha256 hash
content, err := os.ReadFile("/tmp/testfromurl")
assert.NoError(t, err)
assert.EqualValues(t, "the file content", content)

cancel()
c.wg.Wait()
}

func TestCliCommand(t *testing.T) {
machineYaml := `apiVersion: gitmachinecontroller.io/v1beta1
metadata:
annotations:
feature: ihavecoolfeature
labels:
os: rocky9
type: server
name: mycooltestagent
spec:
ip: 127.0.0.1
lines: []`

err := os.WriteFile("./gitrepo/mycooltestagent.yml", []byte(machineYaml), 0666)
assert.NoError(t, err)
defer os.Remove("./gitrepo/mycooltestagent.yml")

ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
c, closer := initMasterAgent(t, ctx)
defer closer()

c.commander.Mock.On("RunWithCode", "uptime").Return(" 14:43:12 up 56 days, 23:56, 1 user, load average: 0,71, 0,58, 0,46", "", 0, nil).Once()

_, err = c.client.Post("/api/machines/accept-v1", bytes.NewBufferString(`{"host":"mycooltestagent"}`))
assert.NoError(t, err)

time.Sleep(2 * time.Second)

err = os.WriteFile("./adminConfig", []byte(fmt.Sprintf(`
{"masters":[{"name":"http://localhost:%s","zone":"zone1"}],
"token":"%s"}`, c.master.WsPort, c.client.Token)), 0666)
assert.NoError(t, err)
defer os.Remove("./adminConfig")

stdout := captureStdout()

a := admin.NewAdmin("./adminConfig", "", "")
err = a.Exec(context.TODO(), "uptime")
assert.NoError(t, err)

out := stdout()
assert.Contains(t, out, " 14:43:12 up 56 days, 23:56, 1 user, load average: 0,71, 0,58, 0,46")
assert.Contains(t, out, "mycooltestagent:")

cancel()
c.wg.Wait()
}

func TestCliCommandInvalidToken(t *testing.T) {
machineYaml := `apiVersion: gitmachinecontroller.io/v1beta1
metadata:
annotations:
feature: ihavecoolfeature
labels:
os: rocky9
type: server
name: mycooltestagent
spec:
ip: 127.0.0.1
lines: []`

err := os.WriteFile("./gitrepo/mycooltestagent.yml", []byte(machineYaml), 0666)
assert.NoError(t, err)
defer os.Remove("./gitrepo/mycooltestagent.yml")

ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
c, closer := initMasterAgent(t, ctx)
defer closer()

_, err = c.client.Post("/api/machines/accept-v1", bytes.NewBufferString(`{"host":"mycooltestagent"}`))
assert.NoError(t, err)

time.Sleep(2 * time.Second)

err = os.WriteFile("./adminConfig", []byte(fmt.Sprintf(`
{"masters":[{"name":"http://localhost:%s","zone":"zone1"}],
"token":"%s"}`, c.master.WsPort, "blaha")), 0666)
assert.NoError(t, err)
defer os.Remove("./adminConfig")

buf := &bytes.Buffer{}
logrus.SetOutput(buf)
a := admin.NewAdmin("./adminConfig", "", "")
err = a.Exec(context.TODO(), "uptime")
assert.Equal(t, "websocket: bad handshake", err.Error())

assert.Contains(t, buf.String(), "Error #01: auth: error validating")
cancel()
c.wg.Wait()
}

func TestCliCommandNotAdminToken(t *testing.T) {
machineYaml := `apiVersion: gitmachinecontroller.io/v1beta1
metadata:
annotations:
feature: ihavecoolfeature
labels:
os: rocky9
type: server
name: mycooltestagent
spec:
ip: 127.0.0.1
lines: []`

err := os.WriteFile("./gitrepo/mycooltestagent.yml", []byte(machineYaml), 0666)
assert.NoError(t, err)
defer os.Remove("./gitrepo/mycooltestagent.yml")

ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
c, closer := initMasterAgent(t, ctx)
defer closer()

_, err = c.client.Post("/api/machines/accept-v1", bytes.NewBufferString(`{"host":"mycooltestagent"}`))
assert.NoError(t, err)

time.Sleep(2 * time.Second)

buf := &bytes.Buffer{}
logrus.SetOutput(buf)
a := admin.NewAdmin("./agentConfig", "", "")
err = a.Exec(context.TODO(), "uptime")
assert.NoError(t, err)

assert.Contains(t, buf.String(), "run-command-request permission denied")

cancel()
c.wg.Wait()
}
67 changes: 67 additions & 0 deletions e2e/httpclient_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package e2e_test

import (
"encoding/json"
"io"
"net/http"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

type authedHttpClient struct {
Token string
baseURL string
}

func NewAuthedHttpClient(t *testing.T, baseURL string) *authedHttpClient {
type tokenStruct struct {
Jwt string
}

resp, err := http.Post(baseURL+"/api/admin-v1", "application/json", nil)
assert.NoError(t, err)
defer resp.Body.Close()

token := &tokenStruct{}
err = json.NewDecoder(resp.Body).Decode(token)
assert.NoError(t, err)

return &authedHttpClient{
Token: token.Jwt,
baseURL: baseURL,
}
}

func (ahc *authedHttpClient) Post(u string, body io.Reader) (resp *http.Response, err error) {
u = strings.TrimLeft(u, "/")

req, err := http.NewRequest(http.MethodPost, ahc.baseURL+"/"+u, body)
if err != nil {
return nil, err
}

req.Header.Add("Authorization", ahc.Token)

return http.DefaultClient.Do(req)
}
func (ahc *authedHttpClient) Get(u string) (resp *http.Response, err error) {
u = strings.TrimLeft(u, "/")

req, err := http.NewRequest(http.MethodGet, ahc.baseURL+"/"+u, nil)
if err != nil {
return nil, err
}

req.Header.Add("Authorization", ahc.Token)

return http.DefaultClient.Do(req)
}

func getBody(t *testing.T, b io.ReadCloser) string {

d, err := io.ReadAll(b)
assert.NoError(t, err)
return string(d)
}
Loading