Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08aba6cd51 | ||
|
|
d3805cd77a | ||
|
|
dd70136e6c | ||
|
|
a94c480c22 | ||
|
|
10fd4ecd6b |
@@ -1,6 +1,6 @@
|
||||
storage:
|
||||
type: postgres
|
||||
file: "postgres://username:password@postgres:5432/gatus?sslmode=disable"
|
||||
path: "postgres://username:password@postgres:5432/gatus?sslmode=disable"
|
||||
|
||||
endpoints:
|
||||
- name: back-end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
storage:
|
||||
type: sqlite
|
||||
file: /data/data.db
|
||||
path: /data/data.db
|
||||
|
||||
endpoints:
|
||||
- name: back-end
|
||||
|
||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.17
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
- name: Build binary to make sure it works
|
||||
@@ -25,9 +25,9 @@ jobs:
|
||||
- name: Test
|
||||
# We're using "sudo" because one of the tests leverages ping, which requires super-user privileges.
|
||||
# As for the 'env "PATH=$PATH" "GOROOT=$GOROOT"', we need it to use the same "go" executable that
|
||||
# was configured by the "Set up Go 1.15" step (otherwise, it'd use sudo's "go" executable)
|
||||
# was configured by the "Set up Go" step (otherwise, it'd use sudo's "go" executable)
|
||||
run: sudo env "PATH=$PATH" "GOROOT=$GOROOT" go test -mod vendor ./... -race -coverprofile=coverage.txt -covermode=atomic
|
||||
- name: Codecov
|
||||
uses: codecov/codecov-action@v1.5.2
|
||||
uses: codecov/codecov-action@v2.1.0
|
||||
with:
|
||||
file: ./coverage.txt
|
||||
files: ./coverage.txt
|
||||
|
||||
25
README.md
25
README.md
@@ -235,24 +235,29 @@ Here are some examples of conditions you can use:
|
||||
| Parameter | Description | Default |
|
||||
|:------------------ |:-------------------------------------------------------------------------------------- |:-------------- |
|
||||
| `storage` | Storage configuration | `{}` |
|
||||
| `storage.file` | Path to persist the data in. If the type is `memory`, data is persisted on interval. | `""` |
|
||||
| `storage.type` | Type of storage. Valid types: `memory`, `sqlite`, `postgres` (ALPHA). | `"memory"` |
|
||||
| `storage.path` | Path to persist the data in. Only supported for types `sqlite` and `postgres`. | `""` |
|
||||
| `storage.type` | Type of storage. Valid types: `memory`, `sqlite`, `postgres`. | `"memory"` |
|
||||
|
||||
- If `storage.type` is `memory` (default) and `storage.file` is set to a non-blank value.
|
||||
Furthermore, the data is periodically persisted, but everything remains in memory.
|
||||
- If `storage.type` is `sqlite`, `storage.file` must not be blank:
|
||||
- If `storage.type` is `memory` (default):
|
||||
```yaml
|
||||
# Note that this is the default value, and you can omit the storage configuration altogether to achieve the same result.
|
||||
# Because the data is stored in memory, the data will not survive a restart.
|
||||
storage:
|
||||
type: memory
|
||||
```
|
||||
- If `storage.type` is `sqlite`, `storage.path` must not be blank:
|
||||
```yaml
|
||||
storage:
|
||||
type: sqlite
|
||||
file: data.db
|
||||
path: data.db
|
||||
```
|
||||
See [examples/docker-compose-sqlite-storage](.examples/docker-compose-sqlite-storage) for an example.
|
||||
|
||||
- If `storage.type` is `postgres`, `storage.file` must be the connection URL:
|
||||
- If `storage.type` is `postgres`, `storage.path` must be the connection URL:
|
||||
```yaml
|
||||
storage:
|
||||
type: postgres
|
||||
file: "postgres://user:password@127.0.0.1:5432/gatus?sslmode=disable"
|
||||
path: "postgres://user:password@127.0.0.1:5432/gatus?sslmode=disable"
|
||||
```
|
||||
See [examples/docker-compose-postgres-storage](.examples/docker-compose-postgres-storage) for an example.
|
||||
|
||||
@@ -937,8 +942,8 @@ endpoints:
|
||||
- "[CONNECTED] == true"
|
||||
```
|
||||
|
||||
Placeholders `[STATUS]` and `[BODY]` as well as the fields `endpoints[].body`, `endpoints[].insecure`,
|
||||
`endpoints[].headers`, `endpoints[].method` and `endpoints[].graphql` are not supported for TCP endpoints.
|
||||
Placeholders `[STATUS]` and `[BODY]` as well as the fields `endpoints[].body`, `endpoints[].headers`,
|
||||
`endpoints[].method` and `endpoints[].graphql` are not supported for TCP endpoints.
|
||||
|
||||
**NOTE**: `[CONNECTED] == true` does not guarantee that the endpoint itself is healthy - it only guarantees that there's
|
||||
something at the given address listening to the given port, and that a connection to that address was successfully
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/TwiN/gatus/v3/config/ui"
|
||||
"github.com/TwiN/gatus/v3/config/web"
|
||||
"github.com/TwiN/gatus/v3/core"
|
||||
"github.com/TwiN/gatus/v3/storage"
|
||||
)
|
||||
|
||||
func TestLoadFileThatDoesNotExist(t *testing.T) {
|
||||
@@ -44,7 +45,8 @@ func TestParseAndValidateConfigBytes(t *testing.T) {
|
||||
}()
|
||||
config, err := parseAndValidateConfigBytes([]byte(fmt.Sprintf(`
|
||||
storage:
|
||||
file: %s
|
||||
type: sqlite
|
||||
path: %s
|
||||
maintenance:
|
||||
enabled: true
|
||||
start: 00:00
|
||||
@@ -83,6 +85,9 @@ endpoints:
|
||||
if config == nil {
|
||||
t.Fatal("Config shouldn't have been nil")
|
||||
}
|
||||
if config.Storage == nil || config.Storage.Path != file || config.Storage.Type != storage.TypeSQLite {
|
||||
t.Error("expected storage to be set to sqlite, got", config.Storage)
|
||||
}
|
||||
if config.UI == nil || config.UI.Title != "Test" {
|
||||
t.Error("Expected Config.UI.Title to be Test")
|
||||
}
|
||||
@@ -1297,3 +1302,53 @@ endpoints:
|
||||
t.Error("services should've been merged in endpoints")
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Remove this in v4.0.0
|
||||
func TestParseAndValidateConfigBytes_backwardCompatibleWithStorageFile(t *testing.T) {
|
||||
file := t.TempDir() + "/test.db"
|
||||
config, err := parseAndValidateConfigBytes([]byte(fmt.Sprintf(`
|
||||
storage:
|
||||
type: sqlite
|
||||
file: %s
|
||||
|
||||
endpoints:
|
||||
- name: website
|
||||
url: https://twin.sh/actuator/health
|
||||
conditions:
|
||||
- "[STATUS] == 200"
|
||||
`, file)))
|
||||
if err != nil {
|
||||
t.Error("expected no error, got", err.Error())
|
||||
}
|
||||
if config == nil {
|
||||
t.Fatal("Config shouldn't have been nil")
|
||||
}
|
||||
if config.Storage == nil || config.Storage.Path != file || config.Storage.Type != storage.TypeSQLite {
|
||||
t.Error("expected storage to be set to sqlite, got", config.Storage)
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Remove this in v4.0.0
|
||||
func TestParseAndValidateConfigBytes_backwardCompatibleWithStorageTypeMemoryAndFile(t *testing.T) {
|
||||
file := t.TempDir() + "/test.db"
|
||||
config, err := parseAndValidateConfigBytes([]byte(fmt.Sprintf(`
|
||||
storage:
|
||||
type: memory
|
||||
file: %s
|
||||
|
||||
endpoints:
|
||||
- name: website
|
||||
url: https://twin.sh/actuator/health
|
||||
conditions:
|
||||
- "[STATUS] == 200"
|
||||
`, file)))
|
||||
if err != nil {
|
||||
t.Error("expected no error, got", err.Error())
|
||||
}
|
||||
if config == nil {
|
||||
t.Fatal("Config shouldn't have been nil")
|
||||
}
|
||||
if config.Storage == nil || config.Storage.Path != file || config.Storage.Type != storage.TypeMemory {
|
||||
t.Error("expected storage to be set to memory, got", config.Storage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,37 +175,37 @@ func TestEndpointStatuses(t *testing.T) {
|
||||
Name: "no-pagination",
|
||||
Path: "/api/v1/endpoints/statuses",
|
||||
ExpectedCode: http.StatusOK,
|
||||
ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":150000000,"errors":null,"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":true},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":true}],"success":true,"timestamp":"0001-01-01T00:00:00Z"},{"status":200,"hostname":"example.org","duration":750000000,"errors":["error-1","error-2"],"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":false},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":false}],"success":false,"timestamp":"0001-01-01T00:00:00Z"}],"events":[]}]`,
|
||||
ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":150000000,"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":true},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":true}],"success":true,"timestamp":"0001-01-01T00:00:00Z"},{"status":200,"hostname":"example.org","duration":750000000,"errors":["error-1","error-2"],"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":false},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":false}],"success":false,"timestamp":"0001-01-01T00:00:00Z"}]}]`,
|
||||
},
|
||||
{
|
||||
Name: "pagination-first-result",
|
||||
Path: "/api/v1/endpoints/statuses?page=1&pageSize=1",
|
||||
ExpectedCode: http.StatusOK,
|
||||
ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":750000000,"errors":["error-1","error-2"],"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":false},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":false}],"success":false,"timestamp":"0001-01-01T00:00:00Z"}],"events":[]}]`,
|
||||
ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":750000000,"errors":["error-1","error-2"],"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":false},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":false}],"success":false,"timestamp":"0001-01-01T00:00:00Z"}]}]`,
|
||||
},
|
||||
{
|
||||
Name: "pagination-second-result",
|
||||
Path: "/api/v1/endpoints/statuses?page=2&pageSize=1",
|
||||
ExpectedCode: http.StatusOK,
|
||||
ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":150000000,"errors":null,"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":true},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":true}],"success":true,"timestamp":"0001-01-01T00:00:00Z"}],"events":[]}]`,
|
||||
ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":150000000,"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":true},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":true}],"success":true,"timestamp":"0001-01-01T00:00:00Z"}]}]`,
|
||||
},
|
||||
{
|
||||
Name: "pagination-no-results",
|
||||
Path: "/api/v1/endpoints/statuses?page=5&pageSize=20",
|
||||
ExpectedCode: http.StatusOK,
|
||||
ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[],"events":[]}]`,
|
||||
ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[]}]`,
|
||||
},
|
||||
{
|
||||
Name: "invalid-pagination-should-fall-back-to-default",
|
||||
Path: "/api/v1/endpoints/statuses?page=INVALID&pageSize=INVALID",
|
||||
ExpectedCode: http.StatusOK,
|
||||
ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":150000000,"errors":null,"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":true},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":true}],"success":true,"timestamp":"0001-01-01T00:00:00Z"},{"status":200,"hostname":"example.org","duration":750000000,"errors":["error-1","error-2"],"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":false},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":false}],"success":false,"timestamp":"0001-01-01T00:00:00Z"}],"events":[]}]`,
|
||||
ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":150000000,"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":true},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":true}],"success":true,"timestamp":"0001-01-01T00:00:00Z"},{"status":200,"hostname":"example.org","duration":750000000,"errors":["error-1","error-2"],"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":false},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":false}],"success":false,"timestamp":"0001-01-01T00:00:00Z"}]}]`,
|
||||
},
|
||||
{ // XXX: Remove this in v4.0.0
|
||||
Name: "backward-compatible-service-status",
|
||||
Path: "/api/v1/services/statuses",
|
||||
ExpectedCode: http.StatusOK,
|
||||
ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":150000000,"errors":null,"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":true},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":true}],"success":true,"timestamp":"0001-01-01T00:00:00Z"},{"status":200,"hostname":"example.org","duration":750000000,"errors":["error-1","error-2"],"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":false},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":false}],"success":false,"timestamp":"0001-01-01T00:00:00Z"}],"events":[]}]`,
|
||||
ExpectedBody: `[{"name":"name","group":"group","key":"group_name","results":[{"status":200,"hostname":"example.org","duration":150000000,"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":true},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":true}],"success":true,"timestamp":"0001-01-01T00:00:00Z"},{"status":200,"hostname":"example.org","duration":750000000,"errors":["error-1","error-2"],"conditionResults":[{"condition":"[STATUS] == 200","success":true},{"condition":"[RESPONSE_TIME] \u003c 500","success":false},{"condition":"[CERTIFICATE_EXPIRATION] \u003c 72h","success":false}],"success":false,"timestamp":"0001-01-01T00:00:00Z"}]}]`,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ const (
|
||||
maximumLengthBeforeTruncatingWhenComparedWithPattern = 25
|
||||
)
|
||||
|
||||
// Condition is a condition that needs to be met in order for a Endpoint to be considered healthy.
|
||||
// Condition is a condition that needs to be met in order for an Endpoint to be considered healthy.
|
||||
type Condition string
|
||||
|
||||
// evaluate the Condition with the Result of the health check
|
||||
@@ -283,7 +283,7 @@ func prettifyNumericalParameters(parameters []string, resolvedParameters []int64
|
||||
return prettify(parameters, []string{strconv.Itoa(int(resolvedParameters[0])), strconv.Itoa(int(resolvedParameters[1]))}, operator)
|
||||
}
|
||||
|
||||
// XXX: make this configurable? i.e. show-resolved-conditions-on-failure
|
||||
// prettify returns a string representation of a condition with its parameters resolved between parentheses
|
||||
func prettify(parameters []string, resolvedParameters []string, operator string) string {
|
||||
// Since, in the event of an invalid path, the resolvedParameters also contain the condition itself,
|
||||
// we'll return the resolvedParameters as-is.
|
||||
|
||||
@@ -17,7 +17,7 @@ type EndpointStatus struct {
|
||||
Results []*Result `json:"results"`
|
||||
|
||||
// Events is a list of events
|
||||
Events []*Event `json:"events"`
|
||||
Events []*Event `json:"events,omitempty"`
|
||||
|
||||
// Uptime information on the endpoint's uptime
|
||||
//
|
||||
|
||||
@@ -13,7 +13,7 @@ type Result struct {
|
||||
DNSRCode string `json:"-"`
|
||||
|
||||
// Hostname extracted from Endpoint.URL
|
||||
Hostname string `json:"hostname"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
|
||||
// IP resolved from the Endpoint URL
|
||||
IP string `json:"-"`
|
||||
@@ -25,7 +25,7 @@ type Result struct {
|
||||
Duration time.Duration `json:"duration"`
|
||||
|
||||
// Errors encountered during the evaluation of the Endpoint's health
|
||||
Errors []string `json:"errors"`
|
||||
Errors []string `json:"errors,omitempty"`
|
||||
|
||||
// ConditionResults results of the Endpoint's conditions
|
||||
ConditionResults []*ConditionResult `json:"conditionResults"`
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
package storage
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSQLStorageRequiresFile = errors.New("sql storage requires a non-empty file to be defined")
|
||||
ErrMemoryStorageDoesNotSupportFile = errors.New("memory storage does not support persistence, use sqlite if you want persistence on file")
|
||||
ErrCannotSetBothFileAndPath = errors.New("file has been deprecated in favor of path: you cannot set both of them")
|
||||
)
|
||||
|
||||
// Config is the configuration for storage
|
||||
type Config struct {
|
||||
// Path is the path used by the store to achieve persistence
|
||||
// If blank, persistence is disabled.
|
||||
// Note that not all Type support persistence
|
||||
//
|
||||
// XXX: Rename to path for v4.0.0
|
||||
Path string `yaml:"path"`
|
||||
|
||||
// File is the path of the file to use for persistence
|
||||
// If blank, persistence is disabled
|
||||
//
|
||||
// XXX: Rename to path for v4.0.0
|
||||
// Deprecated
|
||||
File string `yaml:"file"`
|
||||
|
||||
// Type of store
|
||||
@@ -21,8 +33,27 @@ type Config struct {
|
||||
|
||||
// ValidateAndSetDefaults validates the configuration and sets the default values (if applicable)
|
||||
func (c *Config) ValidateAndSetDefaults() error {
|
||||
if (c.Type == TypePostgres || c.Type == TypeSQLite) && len(c.File) == 0 {
|
||||
if len(c.File) > 0 && len(c.Path) > 0 { // XXX: Remove for v4.0.0
|
||||
return ErrCannotSetBothFileAndPath
|
||||
} else if len(c.File) > 0 { // XXX: Remove for v4.0.0
|
||||
log.Println("WARNING: Your configuration is using 'storage.file', which is deprecated in favor of 'storage.path'")
|
||||
log.Println("WARNING: storage.file will be completely removed in v4.0.0, so please update your configuration")
|
||||
log.Println("WARNING: See https://github.com/TwiN/gatus/issues/197")
|
||||
c.Path = c.File
|
||||
}
|
||||
if c.Type == "" {
|
||||
c.Type = TypeMemory
|
||||
}
|
||||
if (c.Type == TypePostgres || c.Type == TypeSQLite) && len(c.Path) == 0 {
|
||||
return ErrSQLStorageRequiresFile
|
||||
}
|
||||
if c.Type == TypeMemory && len(c.Path) > 0 {
|
||||
log.Println("WARNING: Your configuration is using a storage of type memory with persistence, which has been deprecated")
|
||||
log.Println("WARNING: As of v4.0.0, the default storage type (memory) will not support persistence.")
|
||||
log.Println("WARNING: If you want persistence, use 'storage.type: sqlite' instead of 'storage.type: memory'")
|
||||
log.Println("WARNING: See https://github.com/TwiN/gatus/issues/198")
|
||||
// XXX: Uncomment the following line for v4.0.0
|
||||
//return ErrMemoryStorageDoesNotSupportFile
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ func init() {
|
||||
// Store that leverages gocache
|
||||
type Store struct {
|
||||
sync.RWMutex
|
||||
// Deprecated
|
||||
//
|
||||
// File persistence will no longer be supported as of v4.0.0
|
||||
// XXX: Remove me in v4.0.0
|
||||
file string
|
||||
cache *gocache.Cache
|
||||
}
|
||||
@@ -41,6 +45,8 @@ func NewStore(file string) (*Store, error) {
|
||||
file: file,
|
||||
cache: gocache.NewCache().WithMaxSize(gocache.NoMaxSize),
|
||||
}
|
||||
// XXX: Remove the block below in v4.0.0 because persistence with the memory store will no longer be supported
|
||||
// XXX: Make sure to also update gocache to v2.0.0
|
||||
if len(file) > 0 {
|
||||
_, err := store.cache.ReadFromFile(file)
|
||||
if err != nil {
|
||||
@@ -57,7 +63,6 @@ func NewStore(file string) (*Store, error) {
|
||||
return store, nil
|
||||
}
|
||||
}
|
||||
// XXX: Remove the block above in v4.0.0
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrFilePathNotSpecified is the error returned when path parameter passed in NewStore is blank
|
||||
ErrFilePathNotSpecified = errors.New("file path cannot be empty")
|
||||
// ErrPathNotSpecified is the error returned when the path parameter passed in NewStore is blank
|
||||
ErrPathNotSpecified = errors.New("path cannot be empty")
|
||||
|
||||
// ErrDatabaseDriverNotSpecified is the error returned when the driver parameter passed in NewStore is blank
|
||||
ErrDatabaseDriverNotSpecified = errors.New("database driver cannot be empty")
|
||||
@@ -45,20 +45,20 @@ var (
|
||||
|
||||
// Store that leverages a database
|
||||
type Store struct {
|
||||
driver, file string
|
||||
driver, path string
|
||||
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewStore initializes the database and creates the schema if it doesn't already exist in the file specified
|
||||
// NewStore initializes the database and creates the schema if it doesn't already exist in the path specified
|
||||
func NewStore(driver, path string) (*Store, error) {
|
||||
if len(driver) == 0 {
|
||||
return nil, ErrDatabaseDriverNotSpecified
|
||||
}
|
||||
if len(path) == 0 {
|
||||
return nil, ErrFilePathNotSpecified
|
||||
return nil, ErrPathNotSpecified
|
||||
}
|
||||
store := &Store{driver: driver, file: path}
|
||||
store := &Store{driver: driver, path: path}
|
||||
var err error
|
||||
if store.db, err = sql.Open(driver, path); err != nil {
|
||||
return nil, err
|
||||
@@ -342,7 +342,7 @@ func (s *Store) DeleteAllEndpointStatusesNotInKeys(keys []string) int {
|
||||
query += fmt.Sprintf("$%d,", i+1)
|
||||
args = append(args, keys[i])
|
||||
}
|
||||
query = query[:len(query)-1] + ")" // Remove the last comma and close the parenthesis
|
||||
query = query[:len(query)-1] + ")" // Remove the last comma and add the closing parenthesis
|
||||
result, err = s.db.Exec(query, args...)
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -84,7 +84,7 @@ func TestNewStore(t *testing.T) {
|
||||
if _, err := NewStore("", "TestNewStore.db"); err != ErrDatabaseDriverNotSpecified {
|
||||
t.Error("expected error due to blank driver parameter")
|
||||
}
|
||||
if _, err := NewStore("sqlite", ""); err != ErrFilePathNotSpecified {
|
||||
if _, err := NewStore("sqlite", ""); err != ErrPathNotSpecified {
|
||||
t.Error("expected error due to blank path parameter")
|
||||
}
|
||||
if store, err := NewStore("sqlite", t.TempDir()+"/TestNewStore.db"); err != nil {
|
||||
@@ -169,8 +169,8 @@ func TestStore_InsertCleansUpEventsAndResultsProperly(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStore_Persistence(t *testing.T) {
|
||||
file := t.TempDir() + "/TestStore_Persistence.db"
|
||||
store, _ := NewStore("sqlite", file)
|
||||
path := t.TempDir() + "/TestStore_Persistence.db"
|
||||
store, _ := NewStore("sqlite", path)
|
||||
store.Insert(&testEndpoint, &testSuccessfulResult)
|
||||
store.Insert(&testEndpoint, &testUnsuccessfulResult)
|
||||
if uptime, _ := store.GetUptimeByKey(testEndpoint.Key(), time.Now().Add(-time.Hour), time.Now()); uptime != 0.5 {
|
||||
@@ -188,7 +188,7 @@ func TestStore_Persistence(t *testing.T) {
|
||||
t.Fatal("sanity check failed")
|
||||
}
|
||||
store.Close()
|
||||
store, _ = NewStore("sqlite", file)
|
||||
store, _ = NewStore("sqlite", path)
|
||||
defer store.Close()
|
||||
ssFromNewStore, _ := store.GetEndpointStatus(testEndpoint.Group, testEndpoint.Name, paging.NewEndpointStatusParams().WithResults(1, common.MaximumNumberOfResults).WithEvents(1, common.MaximumNumberOfEvents))
|
||||
if ssFromNewStore == nil || ssFromNewStore.Group != "group" || ssFromNewStore.Name != "name" || len(ssFromNewStore.Events) != 3 || len(ssFromNewStore.Results) != 2 {
|
||||
|
||||
@@ -94,23 +94,23 @@ func Initialize(cfg *storage.Config) error {
|
||||
if cfg == nil {
|
||||
cfg = &storage.Config{}
|
||||
}
|
||||
if len(cfg.File) == 0 && cfg.Type != storage.TypePostgres {
|
||||
log.Printf("[store][Initialize] Creating storage provider with type=%s and file=%s", cfg.Type, cfg.File)
|
||||
if len(cfg.Path) == 0 && cfg.Type != storage.TypePostgres {
|
||||
log.Printf("[store][Initialize] Creating storage provider with type=%s and file=%s", cfg.Type, cfg.Path)
|
||||
} else {
|
||||
log.Printf("[store][Initialize] Creating storage provider with type=%s", cfg.Type)
|
||||
}
|
||||
ctx, cancelFunc = context.WithCancel(context.Background())
|
||||
switch cfg.Type {
|
||||
case storage.TypeSQLite, storage.TypePostgres:
|
||||
store, err = sql.NewStore(string(cfg.Type), cfg.File)
|
||||
store, err = sql.NewStore(string(cfg.Type), cfg.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case storage.TypeMemory:
|
||||
fallthrough
|
||||
default:
|
||||
if len(cfg.File) > 0 {
|
||||
store, err = memory.NewStore(cfg.File)
|
||||
if len(cfg.Path) > 0 {
|
||||
store, err = memory.NewStore(cfg.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -547,23 +547,23 @@ func TestInitialize(t *testing.T) {
|
||||
ExpectedErr: nil,
|
||||
},
|
||||
{
|
||||
Name: "memory-no-file",
|
||||
Name: "memory-no-path",
|
||||
Cfg: &storage.Config{Type: storage.TypeMemory},
|
||||
ExpectedErr: nil,
|
||||
},
|
||||
{
|
||||
Name: "memory-with-file",
|
||||
Cfg: &storage.Config{Type: storage.TypeMemory, File: t.TempDir() + "/TestInitialize_memory-with-file.db"},
|
||||
{ // XXX: Remove for v4.0.0. See https://github.com/TwiN/gatus/issues/198
|
||||
Name: "memory-with-path",
|
||||
Cfg: &storage.Config{Type: storage.TypeMemory, Path: t.TempDir() + "/TestInitialize_memory-with-path.db"},
|
||||
ExpectedErr: nil,
|
||||
},
|
||||
{
|
||||
Name: "sqlite-no-file",
|
||||
Name: "sqlite-no-path",
|
||||
Cfg: &storage.Config{Type: storage.TypeSQLite},
|
||||
ExpectedErr: sql.ErrFilePathNotSpecified,
|
||||
ExpectedErr: sql.ErrPathNotSpecified,
|
||||
},
|
||||
{
|
||||
Name: "sqlite-with-file",
|
||||
Cfg: &storage.Config{Type: storage.TypeSQLite, File: t.TempDir() + "/TestInitialize_sqlite-with-file.db"},
|
||||
Name: "sqlite-with-path",
|
||||
Cfg: &storage.Config{Type: storage.TypeSQLite, Path: t.TempDir() + "/TestInitialize_sqlite-with-path.db"},
|
||||
ExpectedErr: nil,
|
||||
},
|
||||
}
|
||||
@@ -599,7 +599,7 @@ func TestInitialize(t *testing.T) {
|
||||
|
||||
func TestAutoSave(t *testing.T) {
|
||||
file := t.TempDir() + "/TestAutoSave.db"
|
||||
if err := Initialize(&storage.Config{File: file}); err != nil {
|
||||
if err := Initialize(&storage.Config{Path: file}); err != nil {
|
||||
t.Fatal("shouldn't have returned an error")
|
||||
}
|
||||
go autoSave(ctx, store, 3*time.Millisecond)
|
||||
|
||||
Reference in New Issue
Block a user