Merge branch 'feature/config' of Safing/portbase into develop

This commit is contained in:
Daniel 2018-08-13 15:23:56 +00:00 committed by Gitea
commit b890583a17
10 changed files with 327 additions and 539 deletions

57
config/get.go Normal file
View file

@ -0,0 +1,57 @@
package config
import (
"sync"
"github.com/tevino/abool"
)
var (
validityFlag = abool.NewBool(true)
validityFlagLock sync.RWMutex
tableLock sync.RWMutex
stringTable map[string]string
intTable map[string]int
boolTable map[string]bool
)
func getValidityFlag() *abool.AtomicBool {
validityFlagLock.RLock()
defer validityFlagLock.RUnlock()
return validityFlag
}
func resetValidityFlag() {
validityFlagLock.Lock()
defer validityFlagLock.Unlock()
validityFlag.SetTo(false)
validityFlag = abool.NewBool(true)
}
// GetAsString returns a function that returns the wanted string with high performance.
func GetAsString(name string, fallback string) func() string {
valid := getValidityFlag()
value := findStringValue(name, fallback)
return func() string {
if !valid.IsSet() {
valid = getValidityFlag()
value = findStringValue(name, fallback)
}
return value
}
}
// GetAsInt returns a function that returns the wanted int with high performance.
func GetAsInt(name string, fallback int64) func() int64 {
valid := getValidityFlag()
value := findIntValue(name, fallback)
return func() int64 {
if !valid.IsSet() {
valid = getValidityFlag()
value = findIntValue(name, fallback)
}
return value
}
}

137
config/get_test.go Normal file
View file

@ -0,0 +1,137 @@
package config
import (
"testing"
)
func TestGet(t *testing.T) {
err := SetConfig(`
{
"monkey": "1",
"elephant": 2
}
`)
if err != nil {
t.Fatal(err)
}
err = SetDefaultConfig(`
{
"monkey": "0",
"snake": "0",
"elephant": 0
}
`)
if err != nil {
t.Fatal(err)
}
monkey := GetAsString("monkey", "none")
elephant := GetAsInt("elephant", -1)
if monkey() != "1" {
t.Fatalf("monkey should be 1, is %s", monkey())
}
if elephant() != 2 {
t.Fatalf("elephant should be 2, is %d", elephant())
}
err = SetConfig(`
{
"monkey": "3"
}
`)
if err != nil {
t.Fatal(err)
}
if monkey() != "3" {
t.Fatalf("monkey should be 0, is %s", monkey())
}
if elephant() != 0 {
t.Fatalf("elephant should be 0, is %d", elephant())
}
}
func BenchmarkGetAsStringCached(b *testing.B) {
// Setup
err := SetConfig(`
{
"monkey": "banana"
}
`)
if err != nil {
b.Fatal(err)
}
monkey := GetAsString("monkey", "no banana")
// Reset timer for precise results
b.ResetTimer()
// Start benchmark
for i := 0; i < b.N; i++ {
monkey()
}
}
func BenchmarkGetAsStringRefetch(b *testing.B) {
// Setup
err := SetConfig(`
{
"monkey": "banana"
}
`)
if err != nil {
b.Fatal(err)
}
// Reset timer for precise results
b.ResetTimer()
// Start benchmark
for i := 0; i < b.N; i++ {
findStringValue("monkey", "no banana")
}
}
func BenchmarkGetAsIntCached(b *testing.B) {
// Setup
err := SetConfig(`
{
"monkey": 1
}
`)
if err != nil {
b.Fatal(err)
}
monkey := GetAsInt("monkey", -1)
// Reset timer for precise results
b.ResetTimer()
// Start benchmark
for i := 0; i < b.N; i++ {
monkey()
}
}
func BenchmarkGetAsIntRefetch(b *testing.B) {
// Setup
err := SetConfig(`
{
"monkey": 1
}
`)
if err != nil {
b.Fatal(err)
}
// Reset timer for precise results
b.ResetTimer()
// Start benchmark
for i := 0; i < b.N; i++ {
findIntValue("monkey", 1)
}
}

82
config/layers.go Normal file
View file

@ -0,0 +1,82 @@
package config
import (
"errors"
"sync"
"github.com/tidwall/gjson"
)
var (
configLock sync.RWMutex
userConfig = ""
defaultConfig = ""
// ErrInvalidJSON is returned by SetConfig and SetDefaultConfig if they receive invalid json.
ErrInvalidJSON = errors.New("json string invalid")
)
// SetConfig sets the (prioritized) user defined config.
func SetConfig(json string) error {
if !gjson.Valid(json) {
return ErrInvalidJSON
}
configLock.Lock()
defer configLock.Unlock()
userConfig = json
resetValidityFlag()
return nil
}
// SetDefaultConfig sets the (fallback) default config.
func SetDefaultConfig(json string) error {
if !gjson.Valid(json) {
return ErrInvalidJSON
}
configLock.Lock()
defer configLock.Unlock()
defaultConfig = json
resetValidityFlag()
return nil
}
// findValue find the correct value in the user or default config
func findValue(name string) (result gjson.Result) {
configLock.RLock()
defer configLock.RUnlock()
result = gjson.Get(userConfig, name)
if !result.Exists() {
result = gjson.Get(defaultConfig, name)
}
return result
}
// findStringValue validates and return the value with the given name
func findStringValue(name string, fallback string) (value string) {
result := findValue(name)
if !result.Exists() {
return fallback
}
if result.Type != gjson.String {
return fallback
}
return result.String()
}
// findIntValue validates and return the value with the given name
func findIntValue(name string, fallback int64) (value int64) {
result := findValue(name)
if !result.Exists() {
return fallback
}
if result.Type != gjson.Number {
return fallback
}
return result.Int()
}

51
config/layers_test.go Normal file
View file

@ -0,0 +1,51 @@
package config
import "testing"
func TestLayers(t *testing.T) {
err := SetConfig("{invalid json")
if err == nil {
t.Fatal("expected error")
}
err = SetDefaultConfig("{invalid json")
if err == nil {
t.Fatal("expected error")
}
err = SetConfig(`
{
"monkey": "banana",
"elephant": 3
}
`)
if err != nil {
t.Fatal(err)
}
// Test missing values
missingString := GetAsString("missing", "fallback")
if missingString() != "fallback" {
t.Fatal("expected fallback value: fallback")
}
missingInt := GetAsInt("missing", -1)
if missingInt() != -1 {
t.Fatal("expected fallback value: -1")
}
// Test value mismatch
notString := GetAsString("elephant", "fallback")
if notString() != "fallback" {
t.Fatal("expected fallback value: fallback")
}
notInt := GetAsInt("monkey", -1)
if notInt() != -1 {
t.Fatal("expected fallback value: -1")
}
}

View file

@ -1,96 +0,0 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package configuration
import (
"sync/atomic"
"github.com/Safing/safing-core/database"
datastore "github.com/ipfs/go-datastore"
)
type SecurityLevelBoolean int8
func (slb SecurityLevelBoolean) IsSet() bool {
return int8(atomic.LoadInt32(securityLevel)) >= int8(slb)
}
func (slb SecurityLevelBoolean) IsSetWithLevel(customSecurityLevel int8) bool {
return customSecurityLevel >= int8(slb) || int8(atomic.LoadInt32(securityLevel)) >= int8(slb)
}
func (slb SecurityLevelBoolean) Level() int8 {
return int8(slb)
}
type Configuration struct {
database.Base
// Security Config
EnforceCT SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Hardfail on Certificate Transparency
EnforceRevocation SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Hardfail on Certificate Revokation
DenyInsecureTLS SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Block TLS connections, that use insecure TLS versions, cipher suites, ...
DenyTLSWithoutSNI SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Block TLS connections that do not use SNI, connections without SNI cannot be verified as well as connections with SNI.
DoNotUseAssignedDNS SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Do not use DNS Servers assigned by DHCP
DoNotUseMDNS SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Do not use mDNS
DoNotForwardSpecialDomains SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Do not resolve special domains with assigned DNS Servers
AlwaysPromptAtNewProfile SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Always prompt user to review new profiles
DenyNetworkUntilProfileApproved SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Deny network communication until a new profile is actively approved by the user
// Generic Config
CompetenceLevel int8 `json:",omitempty bson:",omitempty"` // Select CompetenceLevel
Beta bool `json:",omitempty bson:",omitempty"` // Take part in Beta
PermanentVerdicts bool `json:",omitempty bson:",omitempty"` // As soon as work on a link is finished, leave it to the system for performance and stability
DNSServers []string `json:",omitempty bson:",omitempty"` // DNS Servers to use for name resolution. Please refer to the user guide for further help.
// regex: ^(DoH|DNS|TDNS)\|[A-Za-z0-9\.:\[\]]+(\|[A-Za-z0-9\.:]+)?$
DNSServerRetryRate int64 `json:",omitempty bson:",omitempty"` // Amount of seconds to wait until failing DNS Servers may be retried.
CountryBlacklist []string `json:",omitempty bson:",omitempty"` // Do not connect to servers in these countries
ASBlacklist []uint32 `json:",omitempty bson:",omitempty"` // Do not connect to server in these AS
LocalPort17Node bool `json:",omitempty bson:",omitempty"` // Serve as local Port17 Node
PublicPort17Node bool `json:",omitempty bson:",omitempty"` // Serve as public Port17 Node
}
var (
configurationModel *Configuration // only use this as parameter for database.EnsureModel-like functions
configurationInstanceName = "config"
defaultConfigurationInstanceName = "default"
)
func initConfigurationModel() {
database.RegisterModel(configurationModel, func() database.Model { return new(Configuration) })
}
// Create saves Configuration with the provided name in the default namespace.
func (m *Configuration) Create(name string) error {
return m.CreateObject(&database.Me, name, m)
}
// CreateInNamespace saves Configuration with the provided name in the provided namespace.
func (m *Configuration) CreateInNamespace(namespace *datastore.Key, name string) error {
return m.CreateObject(namespace, name, m)
}
// Save saves Configuration.
func (m *Configuration) Save() error {
return m.SaveObject(m)
}
// GetConfiguration fetches Configuration with the provided name in the default namespace.
func GetConfiguration(name string) (*Configuration, error) {
return GetConfigurationFromNamespace(&database.Me, name)
}
// GetConfigurationFromNamespace fetches Configuration with the provided name in the provided namespace.
func GetConfigurationFromNamespace(namespace *datastore.Key, name string) (*Configuration, error) {
object, err := database.GetAndEnsureModel(namespace, name, configurationModel)
if err != nil {
return nil, err
}
model, ok := object.(*Configuration)
if !ok {
return nil, database.NewMismatchError(object, configurationModel)
}
return model, nil
}

View file

@ -1,238 +0,0 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package configuration
import (
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/Safing/safing-core/database"
"github.com/Safing/safing-core/log"
"github.com/Safing/safing-core/modules"
)
// think about:
// config changes validation (e.g. if on in secure mode, must be on in fortress mode)
// config switches
// small codebase
// nice api
// be static as much as possible
const (
SecurityLevelOff int8 = 0
SecurityLevelDynamic int8 = 1
SecurityLevelSecure int8 = 2
SecurityLevelFortress int8 = 3
CompetenceLevelNone int8 = 0
CompetenceLevelBasic int8 = 1
CompetenceLevelPowerUser int8 = 2
CompetenceLevelExpert int8 = 3
StatusOk int8 = 0
StatusWarning int8 = 1
StatusError int8 = 2
)
var (
configurationModule *modules.Module
lastChange *int64
securityLevel *int32
lock sync.RWMutex
status *SystemStatus
currentConfig *Configuration
)
func init() {
configurationModule = modules.Register("Configuration", 128)
initDefaultConfig()
initSystemStatusModel()
initConfigurationModel()
lastChangeValue := time.Now().Unix()
lastChange = &lastChangeValue
var securityLevelValue int32
securityLevel = &securityLevelValue
var err error
config, err := GetConfiguration(configurationInstanceName)
if err != nil {
log.Warningf("configuration: could not load configuration: %s", err)
loadedConfig := defaultConfig
config = &loadedConfig
err = config.Create(configurationInstanceName)
if err != nil {
log.Warningf("configuration: could not save new configuration: %s", err)
}
}
status, err = GetSystemStatus()
if err != nil {
log.Warningf("configuration: could not load status: %s", err)
status = &SystemStatus{
CurrentSecurityLevel: 1,
SelectedSecurityLevel: 1,
}
err = status.Create()
if err != nil {
log.Warningf("configuration: could not save new status: %s", err)
}
}
log.Infof("configuration: initial security level is [%s]", status.FmtSecurityLevel())
// atomic.StoreInt32(securityLevel, int32(status.CurrentSecurityLevel))
updateConfig(config)
go configChangeListener()
go statusChangeListener()
}
func configChangeListener() {
sub := database.NewSubscription()
sub.Subscribe(fmt.Sprintf("%s/Configuration:%s", database.Me.String(), configurationInstanceName))
for {
var receivedModel database.Model
select {
case <-configurationModule.Stop:
configurationModule.StopComplete()
return
case receivedModel = <-sub.Updated:
case receivedModel = <-sub.Created:
}
config, ok := database.SilentEnsureModel(receivedModel, configurationModel).(*Configuration)
if !ok {
log.Warning("configuration: received config update, but was not of type *Configuration")
continue
}
updateConfig(config)
}
}
func updateConfig(update *Configuration) {
new := &Configuration{}
if update.EnforceCT > 0 && update.EnforceCT < 4 {
new.EnforceCT = update.EnforceCT
} else {
new.EnforceCT = defaultConfig.EnforceCT
}
if update.EnforceRevocation > 0 && update.EnforceRevocation < 4 {
new.EnforceRevocation = update.EnforceRevocation
} else {
new.EnforceRevocation = defaultConfig.EnforceRevocation
}
if update.DenyInsecureTLS > 0 && update.DenyInsecureTLS < 4 {
new.DenyInsecureTLS = update.DenyInsecureTLS
} else {
new.DenyInsecureTLS = defaultConfig.DenyInsecureTLS
}
if update.DenyTLSWithoutSNI > 0 && update.DenyTLSWithoutSNI < 4 {
new.DenyTLSWithoutSNI = update.DenyTLSWithoutSNI
} else {
new.DenyTLSWithoutSNI = defaultConfig.DenyTLSWithoutSNI
}
if update.DoNotUseAssignedDNS > 0 && update.DoNotUseAssignedDNS < 4 {
new.DoNotUseAssignedDNS = update.DoNotUseAssignedDNS
} else {
new.DoNotUseAssignedDNS = defaultConfig.DoNotUseAssignedDNS
}
if update.DoNotUseMDNS > 0 && update.DoNotUseMDNS < 4 {
new.DoNotUseMDNS = update.DoNotUseMDNS
} else {
new.DoNotUseMDNS = defaultConfig.DoNotUseMDNS
}
if update.DoNotForwardSpecialDomains > 0 && update.DoNotForwardSpecialDomains < 4 {
new.DoNotForwardSpecialDomains = update.DoNotForwardSpecialDomains
} else {
new.DoNotForwardSpecialDomains = defaultConfig.DoNotForwardSpecialDomains
}
if update.AlwaysPromptAtNewProfile > 0 && update.AlwaysPromptAtNewProfile < 4 {
new.AlwaysPromptAtNewProfile = update.AlwaysPromptAtNewProfile
} else {
new.AlwaysPromptAtNewProfile = defaultConfig.AlwaysPromptAtNewProfile
}
if update.DenyNetworkUntilProfileApproved > 0 && update.DenyNetworkUntilProfileApproved < 4 {
new.DenyNetworkUntilProfileApproved = update.DenyNetworkUntilProfileApproved
} else {
new.DenyNetworkUntilProfileApproved = defaultConfig.DenyNetworkUntilProfileApproved
}
// generic configuration
if update.CompetenceLevel >= 0 && update.CompetenceLevel <= 3 {
new.CompetenceLevel = update.CompetenceLevel
} else {
new.CompetenceLevel = 3
// TODO: maybe notify user?
}
if len(update.DNSServers) != 0 {
new.DNSServers = update.DNSServers
} else {
new.DNSServers = defaultConfig.DNSServers
}
if update.DNSServerRetryRate != 0 {
new.DNSServerRetryRate = update.DNSServerRetryRate
} else {
new.DNSServerRetryRate = defaultConfig.DNSServerRetryRate
}
if len(update.CountryBlacklist) != 0 {
new.CountryBlacklist = update.CountryBlacklist
} else {
new.CountryBlacklist = defaultConfig.CountryBlacklist
}
if len(update.ASBlacklist) != 0 {
new.ASBlacklist = update.ASBlacklist
} else {
new.ASBlacklist = defaultConfig.ASBlacklist
}
lock.Lock()
defer lock.Unlock()
// set new config and update timestamp
currentConfig = new
atomic.StoreInt64(lastChange, time.Now().UnixNano())
// update status with new values
// status.CurrentSecurityLevel = currentConfig.SecurityLevel
// status.Save()
// update atomic securityLevel
// atomic.StoreInt32(securityLevel, int32(currentConfig.SecurityLevel))
}
func statusChangeListener() {
sub := database.NewSubscription()
sub.Subscribe(fmt.Sprintf("%s/SystemStatus:%s", database.Me.String(), systemStatusInstanceName))
for {
var receivedModel database.Model
select {
case <-configurationModule.Stop:
configurationModule.StopComplete()
return
case receivedModel = <-sub.Updated:
case receivedModel = <-sub.Created:
}
status, ok := database.SilentEnsureModel(receivedModel, systemStatusModel).(*SystemStatus)
if !ok {
log.Warning("configuration: received system status update, but was not of type *SystemStatus")
continue
}
atomic.StoreInt32(securityLevel, int32(status.CurrentSecurityLevel))
}
}

View file

@ -1,23 +0,0 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package configuration
import (
"fmt"
"testing"
"time"
)
func TestConfiguration(t *testing.T) {
config1 := Get()
fmt.Printf("%v", config1)
time.Sleep(1 * time.Millisecond)
config1.Changed()
time.Sleep(1 * time.Millisecond)
config1.Save()
time.Sleep(1 * time.Millisecond)
config1.Changed()
time.Sleep(1 * time.Millisecond)
}

View file

@ -1,46 +0,0 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package configuration
import (
"github.com/Safing/safing-core/log"
)
var (
defaultConfig Configuration
)
func initDefaultConfig() {
defaultConfig = Configuration{
// based on security level
EnforceCT: 3,
EnforceRevocation: 3,
DenyInsecureTLS: 2,
DenyTLSWithoutSNI: 2,
DoNotUseAssignedDNS: 3,
DoNotUseMDNS: 2,
DoNotForwardSpecialDomains: 2,
AlwaysPromptAtNewProfile: 3,
DenyNetworkUntilProfileApproved: 3,
// generic configuration
CompetenceLevel: 0,
PermanentVerdicts: true,
// Possible values: DNS, DoH (DNS over HTTPS - using Google's syntax: https://developers.google.com/speed/public-dns/docs/dns-over-https)
// DNSServers: []string{"DoH|dns.google.com:443|df:www.google.com"},
DNSServers: []string{"DNS|1.1.1.1:53", "DNS|1.0.0.1:53", "DNS|[2606:4700:4700::1111]:53", "DNS|[2606:4700:4700::1001]:53", "DNS|8.8.8.8:53", "DNS|8.8.4.4:53", "DNS|[2001:4860:4860::8888]:53", "DNS|[2001:4860:4860::8844]:53", "DNS|208.67.222.222:53", "DNS|208.67.220.220:53"},
// DNSServers: []string{"DNS|[2001:4860:4860::8888]:53", "DNS|[2001:4860:4860::8844]:53"},
// DNSServers: []string{"DoH|dns.google.com:443|df:www.google.com", "DNS|8.8.8.8:53", "DNS|8.8.4.4:53", "DNS|172.30.30.1:53", "DNS|172.20.30.2:53"},
// DNSServers: []string{"DNS|208.67.222.222:53", "DNS|208.67.220.220:53", "DNS|8.8.8.8:53", "DNS|8.8.4.4:53"},
// Amount of seconds to wait until failing DNS Servers may be retried.
DNSServerRetryRate: 120,
// CountryBlacklist []string
// ASBlacklist []uint32
LocalPort17Node: false,
PublicPort17Node: true,
}
err := defaultConfig.Create(defaultConfigurationInstanceName)
if err != nil {
log.Warningf("configuration: could not save default configuration: %s", err)
}
}

View file

@ -1,50 +0,0 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package configuration
import (
"sync"
"sync/atomic"
)
type Interface struct {
*Configuration
LastChange int64
ConfigLock sync.RWMutex
}
func Get() *Interface {
lock.RLock()
defer lock.RUnlock()
return &Interface{
Configuration: currentConfig,
LastChange: atomic.LoadInt64(lastChange),
}
}
func (lc *Interface) RLock() {
lc.ConfigLock.RLock()
}
func (lc *Interface) RUnlock() {
lc.ConfigLock.RUnlock()
}
func (lc *Interface) Changed() bool {
lastGlobalChange := atomic.LoadInt64(lastChange)
if lc.LastChange != lastGlobalChange {
lc.ConfigLock.Lock()
lock.RLock()
lc.Configuration = currentConfig
lc.LastChange = lastGlobalChange
lock.RUnlock()
lc.ConfigLock.Unlock()
return true
}
return false
}
func (lc *Interface) SecurityLevel() int8 {
return int8(atomic.LoadInt32(securityLevel))
}

View file

@ -1,86 +0,0 @@
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
package configuration
import (
"github.com/Safing/safing-core/database"
datastore "github.com/ipfs/go-datastore"
)
// SystemStatus saves basic information about the current system status.
type SystemStatus struct {
database.Base
CurrentSecurityLevel int8
SelectedSecurityLevel int8
ThreatLevel int8 `json:",omitempty" bson:",omitempty"`
ThreatReason string `json:",omitempty" bson:",omitempty"`
PortmasterStatus int8 `json:",omitempty" bson:",omitempty"`
PortmasterStatusMsg string `json:",omitempty" bson:",omitempty"`
Port17Status int8 `json:",omitempty" bson:",omitempty"`
Port17StatusMsg string `json:",omitempty" bson:",omitempty"`
}
var (
systemStatusModel *SystemStatus // only use this as parameter for database.EnsureModel-like functions
systemStatusInstanceName = "status"
)
func initSystemStatusModel() {
database.RegisterModel(systemStatusModel, func() database.Model { return new(SystemStatus) })
}
// Create saves SystemStatus with the provided name in the default namespace.
func (m *SystemStatus) Create() error {
return m.CreateObject(&database.Me, systemStatusInstanceName, m)
}
// CreateInNamespace saves SystemStatus with the provided name in the provided namespace.
func (m *SystemStatus) CreateInNamespace(namespace *datastore.Key) error {
return m.CreateObject(namespace, systemStatusInstanceName, m)
}
// Save saves SystemStatus.
func (m *SystemStatus) Save() error {
return m.SaveObject(m)
}
// FmtSecurityLevel returns the current security level as a string.
func (m *SystemStatus) FmtSecurityLevel() string {
var s string
switch m.CurrentSecurityLevel {
case SecurityLevelOff:
s = "Off"
case SecurityLevelDynamic:
s = "Dynamic"
case SecurityLevelSecure:
s = "Secure"
case SecurityLevelFortress:
s = "Fortress"
}
if m.CurrentSecurityLevel != m.SelectedSecurityLevel {
s += "*"
}
return s
}
// GetSystemStatus fetches SystemStatus with the provided name in the default namespace.
func GetSystemStatus() (*SystemStatus, error) {
return GetSystemStatusFromNamespace(&database.Me)
}
// GetSystemStatusFromNamespace fetches SystemStatus with the provided name in the provided namespace.
func GetSystemStatusFromNamespace(namespace *datastore.Key) (*SystemStatus, error) {
object, err := database.GetAndEnsureModel(namespace, systemStatusInstanceName, systemStatusModel)
if err != nil {
return nil, err
}
model, ok := object.(*SystemStatus)
if !ok {
return nil, database.NewMismatchError(object, systemStatusModel)
}
return model, nil
}