You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
cd~/go/src/sabey.co/patrol/unittest/testapp
go build -a -v
cd~/go/src/sabey.co/patrol/unittest/testserver
go build -a -v
cd~/go/src/sabey.co/patrol/
clear && go vet && go test -race
API
HTTP API Endpoint
GET /status/
# returns API_Status Object
GET /api/?group=(app||service)&id=testapp&toggle=STATE&history=true&secret=SECRET&cas=CAS
# returns API_Response Object
POST /api/
# requires API_Request Object# returns API_Response Object
UDP API Endpoint
127.0.0.1:1248
# requires API_Request Object# returns API_Response Object to the dialing IP address if no error occurred
// Apps/Services must contain a unique non empty key: ( 0-9 A-Z a-z - )// ID MUST be usable as a valid hostname label, ie: len <= 63 AND no starting/ending -// Keys are NOT our binary name// Keys are only used as unique identifiers for our API and Keep AliveAppsmap[string]*ConfigApp`json:"apps,omitempty"`Servicesmap[string]*ConfigService`json:"services,omitempty"`// TickEvery is an integer value in seconds of how often we will check the state of our Apps and Services// Value of 0 Defaults to 15 secondsTickEveryint`json:"tick-every,omitempty"`// History is the maximum amount of instance history we should hold// Value of 0 Defaults to 100Historyint`json:"history,omitempty"`// Timestamp Layout is used by the JSON API and HTTP GUI templates//// Timestamp Layout can be found here:// https://golang.org/pkg/time/#pkg-constants// https://golang.org/pkg/time/#example_Time_Format//// The recommended value is RFC1123Z: "Mon, 02 Jan 2006 15:04:05 -0700"//// An empty value will default to time.String()// https://golang.org/pkg/time/#Time.String// This default is: "2006-01-02 15:04:05.999999999 -0700 MST"// This default will also include our monotonic clock as a suffix: "m=±<value>"Timestampstring`json:"json-timestamp,omitempty"`// PingTimeout is an integer value in seconds of how often we require a Ping to be sent// This only applies to App KeepAlives: APP_KEEPALIVE_HTTP and APP_KEEPALIVE_UDPPingTimeoutint`json:"ping-timeout,omitempty"`// ListenHTTP/ListenUDP is our list of listeners// These values are passed as Environment Variables to our executed Apps as JSON Arrays//// Example Environment Variables:// PATROL_HTTP=["127.0.0.1:8421"]// PATROL_UDP=["127.0.0.1:1248"]//// When using APP_KEEPALIVE_HTTP and APP_KEEPALIVE_UDP, these are the addresses we MUST pingListenHTTP []string`json:"listen-http,omitempty"`ListenUDP []string`json:"listen-udp,omitempty"`// HTTP/UDP currently only support the attribute `listen`// This will allow us to overwrite our default listeners for HTTP and UDP// In the future this will include additional options.HTTP*ConfigHTTP`json:"http,omitempty"`UDP*ConfigUDP`json:"udp,omitempty"`// Triggers are only available when you extend Patrol as a library// These values will NOT be able to be set from `config.json` - They must be set manually// TriggerStart is called on CreatePatrol// This will only be called ONCE// If an error is returned a Patrol object will NOT be returned!TriggerStartfunc(
patrol*Patrol,
) error`json:"-"`// TriggerShutdown is called when we call Patrol.Shutdown()// This will only be called ONCE// Once Patrol.Shutdown() is called our Patrol object will no longer be usableTriggerShutdownfunc(
patrol*Patrol,
) `json:"-"`// TriggerStarted is called every time we call Patrol.Start()TriggerStartedfunc(
patrol*Patrol,
) `json:"-"`// TriggerTick is called every time we Patrol.tick() and BEFORE we check our App and Service StatesTriggerTickfunc(
patrol*Patrol,
) `json:"-"`// TriggerStopped is called every time we call Patrol.Stop()TriggerStoppedfunc(
patrol*Patrol,
) `json:"-"`// Extra Unstructured Data
X json.RawMessage`json:"x,omitempty"`
type ConfigApp struct {
// KeepAlive Method//// APP_KEEPALIVE_PID_PATROL = 1// APP_KEEPALIVE_PID_APP = 2// APP_KEEPALIVE_HTTP = 3// APP_KEEPALIVE_UDP = 4//// PID_PATROL: Patrol will watch the execution of the Application. Apps will not be able to fork.// PID_APP: The Application is required to write its CURRENT PID to our `pid-path`. Patrol will `kill -0 PID` to verify that the App is running. This option should be used for forking processes.// HTTP: The Application must send a Ping request to our HTTP API.// UDP: The Application must send a Ping request to our UDP API.KeepAliveint`json:"keepalive,omitempty"`// Name is used as our Display Name in our HTTP GUI.// Name can contain any characters but must be less than 255 bytes in length.Namestring`json:"name,omitempty"`// Binary is the relative path to the executableBinarystring`json:"binary,omitempty"`// Working Directory is the ABSOLUTE Path to our Application Directory.// Binary, LogDirectory, and PidPath are RELATIVE to this Path!//// The only time WorkingDirectory is allowed to be relative is if we're prefixed with ~/// If prefixed with ~/, we will then replace it with our current users home directory.WorkingDirectorystring`json:"working-directory,omitempty"`// Log Directory is the relative path to our log directory.// STDErr and STDOut Logs are held in a `YEAR/MONTH/DAY` sub folder.LogDirectorystring`json:"log-directory,omitempty"`// Path is the relative path to our PID file.// PID is optional, it is only required when using the KeepAlive method: APP_KEEPALIVE_PID_APP// Our PID file must ONLY contain the integer of our current PIDPIDPathstring`json:"pid-path,omitempty"`// PIDVerify - Should we verify that our PID belongs to Binary?// PIDVerify is optional, it is only supported when using the KeepAlive method: APP_KEEPALIVE_PID_APP// This currently is NOT supported.// By default when we execute an App - `ps aux` will report our FULL PATH and BINARY as our first Arg.// If our process should fork, we're unsure of how this will change. We may have to compare that PID contains at the very least Binary in the first Arg.PIDVerifybool`json:"pid-verify,omitempty"`// If Disabled is true our App won't be executed until enabled.// The only way to enable an App once Patrol is started is to use the API or restart Patrol// If we are Disabled and we discover an App that is running, we will signal it to stop.Disabledbool`json:"disabled,omitempty"`// KeyValue - prexisting values to populate objects with on initKeyValuemap[string]interface{} `json:"keyvalue,omitempty"`// KeyValueClear if true will cause our App KeyValue to be cleared once a new instance of our App is started.KeyValueClearbool`json:"keyvalue-clear,omitempty"`// If Secret is set, we will require a secret to be passed when pinging and modifying the state of our App from our HTTP and UDP API.// We are not going to throttle comparing our secret. Choose a secret with enough bits of uniqueness and don't make your Patrol instance public!// If you are worried about your secret being public, use TLS and HTTP, DO NOT USE UDP!!!Secretstring`json:"secret,omitempty"`////////////// os.Cmd //////////////// ExecuteTimeout is an optional value in seconds of how long we will run our App for.// A Value of 0 will disable this.ExecuteTimeoutint`json:"execute-timeout,omitempty"`// Args holds command line arguments, including the command as Args[0].// If the Args field is empty or nil, Run uses {Path}.//// In typical use, both Path and Args are set by calling Command.Args []string`json:"args,omitempty"`// os.Cmd.Env specifies the environment of the process.// Each entry is of the form "key=value".// If os.Cmd.Env is nil, the new process uses the current process's environment.// If os.Cmd.Env contains duplicate environment keys, only the last value in the slice for each duplicate key is used.//// We're going to include our own Patrol related environment variables, so EnvParent is required if we wish to include parent values.Env []string`json:"env,omitempty"`// If EnvParent is true, we will prepend all of our Patrol environment variables to the execution of our process.EnvParentbool`json:"env-parent,omitempty"`// These options are only available when you extend Patrol as a library// These values will NOT be able to be set from `config.json` - They must be set manually// ExtraArgs is an optional set of values that will be appended to Args.ExtraArgsfunc(
idstring,
) []string`json:"-"`// ExtraEnv is an optional set of values that will be appended to Env.ExtraEnvfunc(
idstring,
) []string`json:"-"`// Stdin specifies the process's standard input.//// If Stdin is nil, the process reads from the null device (os.DevNull).//// If Stdin is an *os.File, the process's standard input is connected// directly to that file.//// Otherwise, during the execution of the command a separate// goroutine reads from Stdin and delivers that data to the command// over a pipe. In this case, Wait does not complete until the goroutine// stops copying, either because it has reached the end of Stdin// (EOF or a read error) or because writing to the pipe returned an error.
Stdin io.Reader`json:"-"`// Stdout and Stderr specify the process's standard output and error.//// If either is nil, Run connects the corresponding file descriptor// to the null device (os.DevNull).//// If either is an *os.File, the corresponding output from the process// is connected directly to that file.//// Otherwise, during the execution of the command a separate goroutine// reads from the process over a pipe and delivers that data to the// corresponding Writer. In this case, Wait does not complete until the// goroutine reaches EOF or encounters an error.//// If Stdout and Stderr are the same writer, and have a type that can// be compared with ==, at most one goroutine at a time will call Write.//// If this value is nil, Patrol will create its own file located in our Log Directory.// If this value is nil, this file will also be able to be read from the HTTP GUI.
Stdout io.Writer`json:"-"`
Stderr io.Writer`json:"-"`// Merge Stdout and Stderr into a single file?StdMergebool`json:"std-merge,omitempty"`// ExtraFiles specifies additional open files to be inherited by the// new process. It does not include standard input, standard output, or// standard error. If non-nil, entry i becomes file descriptor 3+i.ExtraFilesfunc(
idstring,
) []*os.File`json:"-"`// TriggerStart is called from tick in runApps() before we attempt to execute an App.TriggerStartfunc(
app*App,
) `json:"-"`// TriggerStarted is called from tick in runApps() and isAppRunning()// This is called after we either execute a new App or we discover a newly running App.TriggerStartedfunc(
app*App,
) `json:"-"`// TriggerStartedPinged is called from App.apiRequest() when we discover a newly running App from a Ping request.TriggerStartedPingedfunc(
app*App,
) `json:"-"`// TriggerStartFailed is called from tick in runApps() when we fail to execute a new App.TriggerStartFailedfunc(
app*App,
) `json:"-"`// TriggerRunning is called from tick() when we discover an App is running.TriggerRunningfunc(
app*App,
) `json:"-"`// TriggerDisabled is called from tick() when we discover an App that is disabled.TriggerDisabledfunc(
app*App,
) `json:"-"`// TriggerClosed is called from App.close() when we discover a previous instance of an App is closed.TriggerClosedfunc(
app*App,
history*History,
) `json:"-"`// TriggerPinged is from App.apiRequest() when we discover an App is running from a Ping request.TriggerPingedfunc(
app*App,
) `json:"-"`// TriggerShutdown is called when we call Patrol.Shutdown()// This will only be called ONCE// This is called regardless if our App is running or disabled!TriggerShutdownfunc(
app*App,
) `json:"-"`// Extra Unstructured Data
X json.RawMessage`json:"x,omitempty"`
type ConfigService struct {
// Management Method//// SERVICE_MANAGEMENT_SERVICE = 1// SERVICE_MANAGEMENT_INITD = 2//// SERVICE_MANAGEMENT_SERVICE: Patrol will use the command `service *`// SERVICE_MANAGEMENT_INITD: Patrol will use the command `/etc/init.d/*`//// If Management is set it will ignore all of the Management Start/Status/Stop/Restart values// If Management is 0, Start/Status/Stop/Restart must each be individually set!// If for whatever reason is necessary, we could choose to user `service` for `status` and `/etc/init.d/` for start or stop!Managementint`json:"management,omitempty"`ManagementStartint`json:"management-start,omitempty"`ManagementStatusint`json:"management-status,omitempty"`ManagementStopint`json:"management-stop,omitempty"`ManagementRestartint`json:"management-restart,omitempty"`// Optionally we may override our service parameters.// For example, instead of `restart` we may choose to use `force-reload`ManagementStartParameterstring`json:"management-start-parameter,omitempty"`ManagementStatusParameterstring`json:"management-status-parameter,omitempty"`ManagementStopParameterstring`json:"management-stop-parameter,omitempty"`ManagementRestartParameterstring`json:"management-restart-parameter,omitempty"`// Name is used as our Display Name in our HTTP GUI.// Name can contain any characters but must be less than 255 bytes in length.Namestring`json:"name,omitempty"`// Service is the parameter of our service.// This is the equivalent of BinaryServicestring`json:"service,omitempty"`// These are a list of valid exit codes to ignore when returned from Start/Status/Stop/Restart// By Default 0 is always ignored, it is assumed to mean that the command was successful!IgnoreExitCodesStart []uint8`json:"ignore-exit-codes-start,omitempty"`IgnoreExitCodesStatus []uint8`json:"ignore-exit-codes-status,omitempty"`IgnoreExitCodesStop []uint8`json:"ignore-exit-codes-stop,omitempty"`IgnoreExitCodesRestart []uint8`json:"ignore-exit-codes-restart,omitempty"`// If Disabled is true our Service won't be executed until enabled.// The only way to enable an Service once Patrol is started is to use the API or restart Patrol// If we are Disabled and we discover an Service that is running, we will signal it to stop.Disabledbool`json:"disabled,omitempty"`// KeyValue - prexisting values to populate objects with on initKeyValuemap[string]interface{} `json:"keyvalue,omitempty"`// KeyValueClear if true will cause our Service KeyValue to be cleared once a new instance of our Service is started.KeyValueClearbool`json:"keyvalue-clear,omitempty"`// If Secret is set, we will require a secret to be passed when pinging and modifying the state of our Service from our HTTP and UDP API.// We are not going to throttle comparing our secret. Choose a secret with enough bits of uniqueness and don't make your Patrol instance public!// If you are worried about your secret being public, use TLS and HTTP, DO NOT USE UDP!!!Secretstring`json:"secret,omitempty"`// Triggers are only available when you extend Patrol as a library// These values will NOT be able to be set from `config.json` - They must be set manually// TriggerStart is called from tick in runServices() before we attempt to execute an Service.TriggerStartfunc(
service*Service,
) `json:"-"`// TriggerStarted is called from tick in runServices() and isServiceRunning()// This is called after we either execute a new Service or we discover a newly running Service.TriggerStartedfunc(
service*Service,
) `json:"-"`// TriggerStartFailed is called from tick in runServices() when we fail to execute a new Service.TriggerStartFailedfunc(
service*Service,
) `json:"-"`// TriggerRunning is called from tick() when we discover an Service is running.TriggerRunningfunc(
service*Service,
) `json:"-"`// TriggerDisabled is called from tick() when we discover an Service that is disabled.TriggerDisabledfunc(
service*Service,
) `json:"-"`// TriggerPinged is from Service.apiRequest() when we discover an Service is running from a Ping request.TriggerClosedfunc(
service*Service,
history*History,
) `json:"-"`// TriggerShutdown is called when we call Patrol.Shutdown()// This will only be called ONCE// This is called regardless if our Service is running or disabled!TriggerShutdownfunc(
service*Service,
) `json:"-"`// Extra Unstructured Data
X json.RawMessage`json:"x,omitempty"`
type API_Status struct {
// Instance ID - UUIDv4InstanceIDstring`json:"instance-id,omitempty"`Appsmap[string]*API_Response`json:"apps,omitempty"`Servicesmap[string]*API_Response`json:"service,omitempty"`// Timestamp Patrol started atStartedstring`json:"started,omitempty"`// Is Patrol in a Shutdown state?Shutdownbool`json:"shutdown,omitempty"`
type API_Request struct {
// Requests by Default are STATELESS - If no values are set then nothing is modified!// The reason we're stateless by default is so that our UDP endpoint can make requests as if it were a HTTP GET Request// UDP has the downside that if an error occurs a response will not be sent in return// Unique IdentifierIDstring`json:"id,omitempty"`// Group: `app` or `service`Groupstring`json:"group,omitempty"`// Ping?// Only supported by either APP_KEEPALIVE_HTTP or APP_KEEPALIVE_UDP// If APP_KEEPALIVE_HTTP is used, the HTTP endpoint MUST be used// If APP_KEEPALIVE_UDP is used, the UDP endpoint MUST be usedPingbool`json:"ping,omitempty"`// App Process ID// Ping MUST be true if we wish to send a PIDPIDuint32`json:"pid,omitempty"`// Toggle State//// API_TOGGLE_STATE_ENABLE = 1// API_TOGGLE_STATE_DISABLE = 2// API_TOGGLE_STATE_RESTART = 3// API_TOGGLE_STATE_RUNONCE_ENABLE = 4// API_TOGGLE_STATE_RUNONCE_DISABLE = 5// API_TOGGLE_STATE_ENABLE_RUNONCE_ENABLE = 6// API_TOGGLE_STATE_ENABLE_RUNONCE_DISABLE = 7//// API_TOGGLE_STATE_ENABLE: Enable App or Service// API_TOGGLE_STATE_DISABLE: Disable App or Service// API_TOGGLE_STATE_RESTART: Restart App or Service, Enable App or Service if Disabled// API_TOGGLE_STATE_RUNONCE_ENABLE: Enable RunOnce for App or Service// API_TOGGLE_STATE_RUNONCE_DISABLE: Disable RunOnce for App or Service// API_TOGGLE_STATE_ENABLE_RUNONCE_ENABLE: Enable App or Service and Enable RunOnce// API_TOGGLE_STATE_ENABLE_RUNONCE_DISABLE: Enable App or Service and Disable RunOnceToggleuint8`json:"toggle,omitempty"`// Return History?Historybool`json:"history,omitempty"`// KeyValueKeyValuemap[string]interface{} `json:"keyvalue,omitempty"`// If KeyValueReplace is true, previous KeyValue will be replaced with KeyValueKeyValueReplacebool`json:"keyvalue-replace,omitempty"`// Secret is required to access the /api GET and POST endpointsSecretstring`json:"secret,omitempty"`// CAS IS OPTIONAL// if CAS is NOT set: we will ignore it and we will override all of our values and state!!!// if CAS IS SET: we will only override values if our CAS is correct!// HOWEVER, we will ALWAYS update our PING/LastSeen value REGARDLESS OF CAS!!!// updating `Ping, LastSeen, or PID` will cause our CAS to be incremented!!!CASuint64`json:"cas,omitempty"`
type API_Response struct {
// An API Response references our STATE at the time of Request// If any values change or CAS is incremented, they will STILL reference the premodification state!//// When using UDP, we won't be able to respond with all of our data, we're going to have to limit our response size// We're going to limit our response to: `id, group, pid, started, lastseen, disabled, restart, run-once, shutdown`// We'll have to ignore `history, keyvalue, and errors`, if they're needed the HTTP endpoint should be used instead// Unique IdentifierIDstring`json:"id,omitempty"`// Instance ID - UUIDv4 - Only exists IF we're running!InstanceIDstring`json:"instance-id,omitempty"`// Group: `app` or `service`Groupstring`json:"group,omitempty"`// Display NameNamestring`json:"name,omitempty"`// App Process IDPIDuint32`json:"pid,omitempty"`// Timestamp App or Service started atStartedstring`json:"started,omitempty"`// Timestamp App or Service was last seenLastSeenstring`json:"lastseen,omitempty"`// Is our App or Service Disabled?Disabledbool`json:"disabled,omitempty"`// Is our App or Service in a Restart state?Restartbool`json:"restart,omitempty"`// Is our App or Service set to RunOnce?RunOncebool`json:"run-once,omitempty"`// Is Patrol in a Shutdown state?Shutdownbool`json:"shutdown,omitempty"`// History of previous App or Service states at the time of close()History []*History`json:"history,omitempty"`// Current state's KeyValueKeyValuemap[string]interface{} `json:"keyvalue,omitempty"`// Does this App or Service require a Secret to modify?Secretbool`json:"secret,omitempty"`// Did any Errors occur?Errors []string`json:"errors,omitempty"`// like all of our other values, CAS is a snapshot of our PREVIOUS state// we are NEVER going to return our current CAS after modifying our current state or values// the reason for this is that if a modification request is successful, we know our CAS is CAS + 1// if we were to take a snapshot, update our object, then get our CAS ---// we could never actually verify what our current state or values are!!!// the reason for this has to do with triggers, we NEVER KNOW when we're going to unlock and/or execute triggers!!!// there are going to be very many scenarios where an API request is made and our CAS is updated more than once!!!// we're never in a scenario where we take a snapshot, update, and get our CAS WITHOUT UNLOCKING!!!// if we want to make a clean CAS, we should do a REQUEST without modifying anything(no ping), then do a secondary request without incrementing CAS!CASuint64`json:"cas,omitempty"`// CASInvalid is the only exception to data that references our previous snapshot// We need to know if our CAS was successful or not!// I prefer to have this as invalid and not valid as most requests without a CAS will be valid!CASInvalidbool`json:"cas-invalid,omitempty"`