2019-10-23 17:09:04 +00:00
package pkger
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
feat: add --hardening-enabled option to limit flux/pkger HTTP requests (#23207)
Flux HTTP and template fetching requests do not perform IP address
checks for local addresses. This behavior on the one hand allows SSRF
(Server Side Request Forgery) attacks via authenticated requests but on
the other hand is useful for scenarios that have legitimate requirements
to fetch from private addresses (eg, hosting templates internally or
performing flux queries to local resources during development).
To not break existing installations, the default behavior will remain
the same but a new --hardening-enabled option is added to influxd to
turn on IP address verification and limit both flux and template
fetching HTTP requests to non-private addresses. We plan to enable new
security features that aren't suitable for the default install with this
option. Put another way, this new option is intended to be used to make
it easy to turn on all security options when running in production
environments. The 'Manage security and authorization' section of the
docs will also be updated for this option.
Specifically for flux, when --hardening-enabled is specified, we now
pass in PrivateIPValidator{} to the flux dependency configuration. The
flux url validator will then tap into the http.Client 'Control'
mechanism to validate the IP address since it is called after DNS lookup
but before the connection starts.
For pkger (template fetching), when --hardening-enabled is specified,
the template parser's HTTP client will be configured to also use
PrivateIPValidator{}. Note that /api/v2/stacks POST ('init', aka create)
and PATCH ('update') only store the new url to be applied later with
/api/v2/templates/apply. While it is possible to have InitStack() and
UpdateStack() mimic net.DialContext() to setup a go routine to perform a
DNS lookup and then loop through the returned addresses to verify none
are for a private IP before storing the url, this would add considerable
complexity to the stacks implementation. Since the stack's urls are
fetched when it is applied and the IP address is verified as part of
apply (see above), for now we'll keep this simple and not validate the
IPs of the stack's urls during init or update.
Lastly, update pkger/http_server_template_test.go's Templates() test for
disabled jsonnet to also check the contents of the 422 error (since the
flux validator also returns a 422 with different message). Also, fix the
URL in one of these tests to use a valid path.
2022-03-18 14:25:31 +00:00
"net"
2020-01-12 02:25:19 +00:00
"net/http"
2020-06-15 21:13:38 +00:00
"net/url"
2022-04-13 20:24:27 +00:00
"os"
2020-06-16 19:24:11 +00:00
"path"
2020-05-03 17:34:24 +00:00
"regexp"
2019-10-23 17:09:04 +00:00
"sort"
"strconv"
"strings"
feat: add --hardening-enabled option to limit flux/pkger HTTP requests (#23207)
Flux HTTP and template fetching requests do not perform IP address
checks for local addresses. This behavior on the one hand allows SSRF
(Server Side Request Forgery) attacks via authenticated requests but on
the other hand is useful for scenarios that have legitimate requirements
to fetch from private addresses (eg, hosting templates internally or
performing flux queries to local resources during development).
To not break existing installations, the default behavior will remain
the same but a new --hardening-enabled option is added to influxd to
turn on IP address verification and limit both flux and template
fetching HTTP requests to non-private addresses. We plan to enable new
security features that aren't suitable for the default install with this
option. Put another way, this new option is intended to be used to make
it easy to turn on all security options when running in production
environments. The 'Manage security and authorization' section of the
docs will also be updated for this option.
Specifically for flux, when --hardening-enabled is specified, we now
pass in PrivateIPValidator{} to the flux dependency configuration. The
flux url validator will then tap into the http.Client 'Control'
mechanism to validate the IP address since it is called after DNS lookup
but before the connection starts.
For pkger (template fetching), when --hardening-enabled is specified,
the template parser's HTTP client will be configured to also use
PrivateIPValidator{}. Note that /api/v2/stacks POST ('init', aka create)
and PATCH ('update') only store the new url to be applied later with
/api/v2/templates/apply. While it is possible to have InitStack() and
UpdateStack() mimic net.DialContext() to setup a go routine to perform a
DNS lookup and then loop through the returned addresses to verify none
are for a private IP before storing the url, this would add considerable
complexity to the stacks implementation. Since the stack's urls are
fetched when it is applied and the IP address is verified as part of
apply (see above), for now we'll keep this simple and not validate the
IPs of the stack's urls during init or update.
Lastly, update pkger/http_server_template_test.go's Templates() test for
disabled jsonnet to also check the contents of the 422 error (since the
flux validator also returns a 422 with different message). Also, fix the
URL in one of these tests to use a valid path.
2022-03-18 14:25:31 +00:00
"syscall"
2019-10-23 17:09:04 +00:00
"time"
2020-07-30 18:26:17 +00:00
"github.com/influxdata/flux/ast"
2020-09-17 00:59:15 +00:00
"github.com/influxdata/flux/ast/edit"
feat: add --hardening-enabled option to limit flux/pkger HTTP requests (#23207)
Flux HTTP and template fetching requests do not perform IP address
checks for local addresses. This behavior on the one hand allows SSRF
(Server Side Request Forgery) attacks via authenticated requests but on
the other hand is useful for scenarios that have legitimate requirements
to fetch from private addresses (eg, hosting templates internally or
performing flux queries to local resources during development).
To not break existing installations, the default behavior will remain
the same but a new --hardening-enabled option is added to influxd to
turn on IP address verification and limit both flux and template
fetching HTTP requests to non-private addresses. We plan to enable new
security features that aren't suitable for the default install with this
option. Put another way, this new option is intended to be used to make
it easy to turn on all security options when running in production
environments. The 'Manage security and authorization' section of the
docs will also be updated for this option.
Specifically for flux, when --hardening-enabled is specified, we now
pass in PrivateIPValidator{} to the flux dependency configuration. The
flux url validator will then tap into the http.Client 'Control'
mechanism to validate the IP address since it is called after DNS lookup
but before the connection starts.
For pkger (template fetching), when --hardening-enabled is specified,
the template parser's HTTP client will be configured to also use
PrivateIPValidator{}. Note that /api/v2/stacks POST ('init', aka create)
and PATCH ('update') only store the new url to be applied later with
/api/v2/templates/apply. While it is possible to have InitStack() and
UpdateStack() mimic net.DialContext() to setup a go routine to perform a
DNS lookup and then loop through the returned addresses to verify none
are for a private IP before storing the url, this would add considerable
complexity to the stacks implementation. Since the stack's urls are
fetched when it is applied and the IP address is verified as part of
apply (see above), for now we'll keep this simple and not validate the
IPs of the stack's urls during init or update.
Lastly, update pkger/http_server_template_test.go's Templates() test for
disabled jsonnet to also check the contents of the 422 error (since the
flux validator also returns a 422 with different message). Also, fix the
URL in one of these tests to use a valid path.
2022-03-18 14:25:31 +00:00
fluxurl "github.com/influxdata/flux/dependencies/url"
2020-07-30 18:26:17 +00:00
"github.com/influxdata/flux/parser"
2021-09-13 19:12:35 +00:00
errors2 "github.com/influxdata/influxdb/v2/kit/platform/errors"
2020-04-03 17:39:20 +00:00
"github.com/influxdata/influxdb/v2/pkg/jsonnet"
2020-10-09 20:17:04 +00:00
"github.com/influxdata/influxdb/v2/task/options"
2019-10-23 17:09:04 +00:00
"gopkg.in/yaml.v3"
)
2020-01-13 19:13:37 +00:00
type (
// ReaderFn is used for functional inputs to abstract the individual
// entrypoints for the reader itself.
2020-06-15 20:24:35 +00:00
ReaderFn func ( ) ( r io . Reader , source string , err error )
2019-10-23 17:09:04 +00:00
2020-01-13 19:13:37 +00:00
// Encoder is an encodes a type.
Encoder interface {
Encode ( v interface { } ) error
}
// Encoding describes the encoding for the raw package data. The
// encoding determines how the raw data is parsed.
Encoding int
)
2019-10-23 17:09:04 +00:00
// encoding types
const (
2019-11-05 01:40:42 +00:00
EncodingUnknown Encoding = iota
2019-10-23 17:09:04 +00:00
EncodingJSON
2020-01-12 02:49:55 +00:00
EncodingJsonnet
2020-01-12 02:25:19 +00:00
EncodingSource // EncodingSource draws the encoding type by inferring it from the source.
EncodingYAML
2019-10-23 17:09:04 +00:00
)
2019-11-05 01:40:42 +00:00
// String provides the string representation of the encoding.
func ( e Encoding ) String ( ) string {
switch e {
case EncodingJSON :
return "json"
2020-01-12 02:49:55 +00:00
case EncodingJsonnet :
return "jsonnet"
2020-01-12 02:25:19 +00:00
case EncodingSource :
return "source"
2019-11-05 01:40:42 +00:00
case EncodingYAML :
return "yaml"
default :
return "unknown"
}
}
2019-10-30 17:55:13 +00:00
// ErrInvalidEncoding indicates the encoding is invalid type for the parser.
var ErrInvalidEncoding = errors . New ( "invalid encoding provided" )
2019-10-23 17:09:04 +00:00
// Parse parses a pkg defined by the encoding and readerFns. As of writing this
2020-06-30 21:54:00 +00:00
// we can parse both a YAML, JSON, and Jsonnet formats of the Template model.
func Parse ( encoding Encoding , readerFn ReaderFn , opts ... ValidateOptFn ) ( * Template , error ) {
2020-06-15 20:24:35 +00:00
r , source , err := readerFn ( )
2019-10-23 17:09:04 +00:00
if err != nil {
return nil , err
}
2020-06-30 21:54:00 +00:00
var pkgFn func ( io . Reader , ... ValidateOptFn ) ( * Template , error )
2019-10-23 17:09:04 +00:00
switch encoding {
case EncodingJSON :
2020-06-15 20:24:35 +00:00
pkgFn = parseJSON
2020-01-12 02:49:55 +00:00
case EncodingJsonnet :
2020-06-15 20:24:35 +00:00
pkgFn = parseJsonnet
2020-01-12 02:25:19 +00:00
case EncodingSource :
2020-06-15 20:24:35 +00:00
pkgFn = parseSource
2020-01-12 02:25:19 +00:00
case EncodingYAML :
2020-06-15 20:24:35 +00:00
pkgFn = parseYAML
2019-10-23 17:09:04 +00:00
default :
2019-10-30 17:55:13 +00:00
return nil , ErrInvalidEncoding
2019-10-23 17:09:04 +00:00
}
2020-06-15 20:24:35 +00:00
pkg , err := pkgFn ( r , opts ... )
if err != nil {
return nil , err
}
pkg . sources = [ ] string { source }
return pkg , nil
2019-10-23 17:09:04 +00:00
}
// FromFile reads a file from disk and provides a reader from it.
func FromFile ( filePath string ) ReaderFn {
2020-06-15 20:24:35 +00:00
return func ( ) ( io . Reader , string , error ) {
2020-06-15 21:13:38 +00:00
u , err := url . Parse ( filePath )
if err != nil {
2021-03-30 18:10:02 +00:00
return nil , filePath , & errors2 . Error {
Code : errors2 . EInvalid ,
2020-06-15 21:13:38 +00:00
Msg : "invalid filepath provided" ,
Err : err ,
}
}
if u . Scheme == "" {
u . Scheme = "file"
}
2019-10-23 17:09:04 +00:00
// not using os.Open to avoid having to deal with closing the file in here
2022-04-13 20:24:27 +00:00
b , err := os . ReadFile ( u . Path )
2019-10-23 17:09:04 +00:00
if err != nil {
2020-06-15 20:24:35 +00:00
return nil , filePath , err
2019-10-23 17:09:04 +00:00
}
2020-06-15 21:13:38 +00:00
return bytes . NewBuffer ( b ) , u . String ( ) , nil
2019-10-23 17:09:04 +00:00
}
}
// FromReader simply passes the reader along. Useful when consuming
// this from an HTTP request body. There are a number of other useful
// places for this functional input.
2020-06-15 21:13:38 +00:00
func FromReader ( r io . Reader , sources ... string ) ReaderFn {
2020-06-15 20:24:35 +00:00
return func ( ) ( io . Reader , string , error ) {
2020-06-15 21:13:38 +00:00
source := "byte stream"
if len ( sources ) > 0 {
source = formatSources ( sources )
}
return r , source , nil
2019-10-23 17:09:04 +00:00
}
}
// FromString parses a pkg from a raw string value. This is very useful
// in tests.
func FromString ( s string ) ReaderFn {
2020-06-15 20:24:35 +00:00
return func ( ) ( io . Reader , string , error ) {
return strings . NewReader ( s ) , "string" , nil
2019-10-23 17:09:04 +00:00
}
}
feat: add --hardening-enabled option to limit flux/pkger HTTP requests (#23207)
Flux HTTP and template fetching requests do not perform IP address
checks for local addresses. This behavior on the one hand allows SSRF
(Server Side Request Forgery) attacks via authenticated requests but on
the other hand is useful for scenarios that have legitimate requirements
to fetch from private addresses (eg, hosting templates internally or
performing flux queries to local resources during development).
To not break existing installations, the default behavior will remain
the same but a new --hardening-enabled option is added to influxd to
turn on IP address verification and limit both flux and template
fetching HTTP requests to non-private addresses. We plan to enable new
security features that aren't suitable for the default install with this
option. Put another way, this new option is intended to be used to make
it easy to turn on all security options when running in production
environments. The 'Manage security and authorization' section of the
docs will also be updated for this option.
Specifically for flux, when --hardening-enabled is specified, we now
pass in PrivateIPValidator{} to the flux dependency configuration. The
flux url validator will then tap into the http.Client 'Control'
mechanism to validate the IP address since it is called after DNS lookup
but before the connection starts.
For pkger (template fetching), when --hardening-enabled is specified,
the template parser's HTTP client will be configured to also use
PrivateIPValidator{}. Note that /api/v2/stacks POST ('init', aka create)
and PATCH ('update') only store the new url to be applied later with
/api/v2/templates/apply. While it is possible to have InitStack() and
UpdateStack() mimic net.DialContext() to setup a go routine to perform a
DNS lookup and then loop through the returned addresses to verify none
are for a private IP before storing the url, this would add considerable
complexity to the stacks implementation. Since the stack's urls are
fetched when it is applied and the IP address is verified as part of
apply (see above), for now we'll keep this simple and not validate the
IPs of the stack's urls during init or update.
Lastly, update pkger/http_server_template_test.go's Templates() test for
disabled jsonnet to also check the contents of the 422 error (since the
flux validator also returns a 422 with different message). Also, fix the
URL in one of these tests to use a valid path.
2022-03-18 14:25:31 +00:00
// NewDefaultHTTPClient creates a client with the specified flux IP validator.
// This is copied from flux/dependencies/http/http.go
func NewDefaultHTTPClient ( urlValidator fluxurl . Validator ) * http . Client {
// Control is called after DNS lookup, but before the network
// connection is initiated.
control := func ( network , address string , c syscall . RawConn ) error {
host , _ , err := net . SplitHostPort ( address )
if err != nil {
return err
}
ip := net . ParseIP ( host )
return urlValidator . ValidateIP ( ip )
}
dialer := & net . Dialer {
Timeout : time . Minute ,
Control : control ,
// DualStack is deprecated
}
return & http . Client {
Transport : & http . Transport {
DialContext : dialer . DialContext ,
} ,
}
2020-06-16 19:24:11 +00:00
}
2020-01-12 02:25:19 +00:00
// FromHTTPRequest parses a pkg from the request body of a HTTP request. This is
// very useful when using packages that are hosted..
feat: add --hardening-enabled option to limit flux/pkger HTTP requests (#23207)
Flux HTTP and template fetching requests do not perform IP address
checks for local addresses. This behavior on the one hand allows SSRF
(Server Side Request Forgery) attacks via authenticated requests but on
the other hand is useful for scenarios that have legitimate requirements
to fetch from private addresses (eg, hosting templates internally or
performing flux queries to local resources during development).
To not break existing installations, the default behavior will remain
the same but a new --hardening-enabled option is added to influxd to
turn on IP address verification and limit both flux and template
fetching HTTP requests to non-private addresses. We plan to enable new
security features that aren't suitable for the default install with this
option. Put another way, this new option is intended to be used to make
it easy to turn on all security options when running in production
environments. The 'Manage security and authorization' section of the
docs will also be updated for this option.
Specifically for flux, when --hardening-enabled is specified, we now
pass in PrivateIPValidator{} to the flux dependency configuration. The
flux url validator will then tap into the http.Client 'Control'
mechanism to validate the IP address since it is called after DNS lookup
but before the connection starts.
For pkger (template fetching), when --hardening-enabled is specified,
the template parser's HTTP client will be configured to also use
PrivateIPValidator{}. Note that /api/v2/stacks POST ('init', aka create)
and PATCH ('update') only store the new url to be applied later with
/api/v2/templates/apply. While it is possible to have InitStack() and
UpdateStack() mimic net.DialContext() to setup a go routine to perform a
DNS lookup and then loop through the returned addresses to verify none
are for a private IP before storing the url, this would add considerable
complexity to the stacks implementation. Since the stack's urls are
fetched when it is applied and the IP address is verified as part of
apply (see above), for now we'll keep this simple and not validate the
IPs of the stack's urls during init or update.
Lastly, update pkger/http_server_template_test.go's Templates() test for
disabled jsonnet to also check the contents of the 422 error (since the
flux validator also returns a 422 with different message). Also, fix the
URL in one of these tests to use a valid path.
2022-03-18 14:25:31 +00:00
func FromHTTPRequest ( addr string , client * http . Client ) ReaderFn {
2020-06-15 20:24:35 +00:00
return func ( ) ( io . Reader , string , error ) {
feat: add --hardening-enabled option to limit flux/pkger HTTP requests (#23207)
Flux HTTP and template fetching requests do not perform IP address
checks for local addresses. This behavior on the one hand allows SSRF
(Server Side Request Forgery) attacks via authenticated requests but on
the other hand is useful for scenarios that have legitimate requirements
to fetch from private addresses (eg, hosting templates internally or
performing flux queries to local resources during development).
To not break existing installations, the default behavior will remain
the same but a new --hardening-enabled option is added to influxd to
turn on IP address verification and limit both flux and template
fetching HTTP requests to non-private addresses. We plan to enable new
security features that aren't suitable for the default install with this
option. Put another way, this new option is intended to be used to make
it easy to turn on all security options when running in production
environments. The 'Manage security and authorization' section of the
docs will also be updated for this option.
Specifically for flux, when --hardening-enabled is specified, we now
pass in PrivateIPValidator{} to the flux dependency configuration. The
flux url validator will then tap into the http.Client 'Control'
mechanism to validate the IP address since it is called after DNS lookup
but before the connection starts.
For pkger (template fetching), when --hardening-enabled is specified,
the template parser's HTTP client will be configured to also use
PrivateIPValidator{}. Note that /api/v2/stacks POST ('init', aka create)
and PATCH ('update') only store the new url to be applied later with
/api/v2/templates/apply. While it is possible to have InitStack() and
UpdateStack() mimic net.DialContext() to setup a go routine to perform a
DNS lookup and then loop through the returned addresses to verify none
are for a private IP before storing the url, this would add considerable
complexity to the stacks implementation. Since the stack's urls are
fetched when it is applied and the IP address is verified as part of
apply (see above), for now we'll keep this simple and not validate the
IPs of the stack's urls during init or update.
Lastly, update pkger/http_server_template_test.go's Templates() test for
disabled jsonnet to also check the contents of the 422 error (since the
flux validator also returns a 422 with different message). Also, fix the
URL in one of these tests to use a valid path.
2022-03-18 14:25:31 +00:00
resp , err := client . Get ( normalizeGithubURLToContent ( addr ) )
2020-01-12 02:25:19 +00:00
if err != nil {
2020-06-15 20:24:35 +00:00
return nil , addr , err
2020-01-12 02:25:19 +00:00
}
defer resp . Body . Close ( )
var buf bytes . Buffer
if _ , err := io . Copy ( & buf , resp . Body ) ; err != nil {
2020-06-15 20:24:35 +00:00
return nil , addr , err
2020-01-12 02:25:19 +00:00
}
2020-03-16 23:04:44 +00:00
if resp . StatusCode / 100 != 2 {
2020-06-15 20:24:35 +00:00
return nil , addr , fmt . Errorf (
"bad response: address=%s status_code=%d body=%q" ,
addr , resp . StatusCode , strings . TrimSpace ( buf . String ( ) ) ,
)
2020-03-16 23:04:44 +00:00
}
2020-06-15 20:24:35 +00:00
return & buf , addr , nil
2020-01-12 02:25:19 +00:00
}
2019-10-23 17:09:04 +00:00
}
2020-06-16 19:24:11 +00:00
const (
githubRawContentHost = "raw.githubusercontent.com"
githubHost = "github.com"
)
func normalizeGithubURLToContent ( addr string ) string {
u , err := url . Parse ( addr )
if err != nil {
return addr
}
if u . Host == githubHost {
switch path . Ext ( u . Path ) {
case ".yaml" , ".yml" , ".json" , ".jsonnet" :
default :
return u . String ( )
}
parts := strings . Split ( u . Path , "/" )
if len ( parts ) < 4 {
return u . String ( )
}
u . Host = githubRawContentHost
u . Path = path . Join ( append ( parts [ : 3 ] , parts [ 4 : ] ... ) ... )
}
return u . String ( )
}
2020-06-30 21:54:00 +00:00
func parseJSON ( r io . Reader , opts ... ValidateOptFn ) ( * Template , error ) {
2019-11-18 18:50:45 +00:00
return parse ( json . NewDecoder ( r ) , opts ... )
2019-10-23 17:09:04 +00:00
}
2020-06-30 21:54:00 +00:00
func parseJsonnet ( r io . Reader , opts ... ValidateOptFn ) ( * Template , error ) {
2021-12-30 17:55:45 +00:00
opt := & validateOpt { }
for _ , o := range opts {
o ( opt )
}
// For security, we'll default to disabling parsing jsonnet but allow callers to override the behavior via
// EnableJsonnet(). Enabling jsonnet might be useful for client code where parsing jsonnet could be acceptable.
if opt . enableJsonnet {
return parse ( jsonnet . NewDecoder ( r ) , opts ... )
}
return nil , fmt . Errorf ( "%s: jsonnet" , ErrInvalidEncoding )
2020-01-12 02:49:55 +00:00
}
2020-06-30 21:54:00 +00:00
func parseSource ( r io . Reader , opts ... ValidateOptFn ) ( * Template , error ) {
2020-01-12 02:25:19 +00:00
var b [ ] byte
if byter , ok := r . ( interface { Bytes ( ) [ ] byte } ) ; ok {
b = byter . Bytes ( )
} else {
2022-04-13 20:24:27 +00:00
bb , err := io . ReadAll ( r )
2020-01-12 02:25:19 +00:00
if err != nil {
return nil , fmt . Errorf ( "failed to decode pkg source: %s" , err )
}
b = bb
}
2020-01-13 19:13:37 +00:00
contentType := http . DetectContentType ( b [ : 512 ] )
2020-01-12 02:25:19 +00:00
switch {
2020-01-12 02:49:55 +00:00
case strings . Contains ( contentType , "jsonnet" ) :
// highly unlikely to fall in here with supported content type detection as is
return parseJsonnet ( bytes . NewReader ( b ) , opts ... )
2020-01-12 02:25:19 +00:00
case strings . Contains ( contentType , "json" ) :
return parseJSON ( bytes . NewReader ( b ) , opts ... )
2020-01-12 02:49:55 +00:00
case strings . Contains ( contentType , "yaml" ) ,
strings . Contains ( contentType , "yml" ) :
2020-01-12 02:25:19 +00:00
return parseYAML ( bytes . NewReader ( b ) , opts ... )
2020-01-12 02:49:55 +00:00
default :
2020-01-14 22:23:47 +00:00
return parseYAML ( bytes . NewReader ( b ) , opts ... )
2020-01-12 02:25:19 +00:00
}
}
2020-06-30 21:54:00 +00:00
func parseYAML ( r io . Reader , opts ... ValidateOptFn ) ( * Template , error ) {
2020-01-13 19:13:37 +00:00
dec := yaml . NewDecoder ( r )
2020-06-30 21:54:00 +00:00
var pkg Template
2020-01-13 19:13:37 +00:00
for {
// forced to use this for loop b/c the yaml dependency does not
// decode multi documents.
var k Object
err := dec . Decode ( & k )
if err == io . EOF {
break
}
if err != nil {
return nil , err
}
pkg . Objects = append ( pkg . Objects , k )
}
if err := pkg . Validate ( opts ... ) ; err != nil {
return nil , err
}
return & pkg , nil
2020-01-12 02:25:19 +00:00
}
2019-10-23 17:09:04 +00:00
type decoder interface {
Decode ( interface { } ) error
}
2020-06-30 21:54:00 +00:00
func parse ( dec decoder , opts ... ValidateOptFn ) ( * Template , error ) {
var pkg Template
2020-01-13 19:13:37 +00:00
if err := dec . Decode ( & pkg . Objects ) ; err != nil {
2019-10-23 17:09:04 +00:00
return nil , err
}
2019-11-18 18:50:45 +00:00
if err := pkg . Validate ( opts ... ) ; err != nil {
2019-11-05 01:40:42 +00:00
return nil , err
2019-10-23 17:09:04 +00:00
}
return & pkg , nil
}
2020-01-13 19:13:37 +00:00
// Object describes the metadata and raw spec for an entity of a package kind.
type Object struct {
APIVersion string ` json:"apiVersion" yaml:"apiVersion" `
2020-03-18 18:47:13 +00:00
Kind Kind ` json:"kind" yaml:"kind" `
2020-02-05 00:15:20 +00:00
Metadata Resource ` json:"metadata" yaml:"metadata" `
2020-01-13 19:13:37 +00:00
Spec Resource ` json:"spec" yaml:"spec" `
}
// Name returns the name of the kind.
func ( k Object ) Name ( ) string {
2020-03-16 18:25:39 +00:00
return k . Metadata . references ( fieldName ) . String ( )
2020-01-13 19:13:37 +00:00
}
2020-04-24 17:59:58 +00:00
// ObjectAssociation is an association for an object. The supported types
// at this time are KindLabel.
type ObjectAssociation struct {
2020-06-24 18:27:03 +00:00
Kind Kind
MetaName string
2020-04-24 17:59:58 +00:00
}
// AddAssociations adds an association to the object.
func ( k Object ) AddAssociations ( associations ... ObjectAssociation ) {
2020-06-26 03:17:11 +00:00
if len ( associations ) == 0 {
return
}
2020-04-24 17:59:58 +00:00
if k . Spec == nil {
k . Spec = make ( Resource )
}
existingAss := k . Spec . slcResource ( fieldAssociations )
for _ , ass := range associations {
existingAss = append ( existingAss , Resource {
fieldKind : ass . Kind ,
2020-06-24 18:27:03 +00:00
fieldName : ass . MetaName ,
2020-04-24 17:59:58 +00:00
} )
}
sort . Slice ( existingAss , func ( i , j int ) bool {
iPkgName , jPkgName := existingAss [ i ] . Name ( ) , existingAss [ j ] . Name ( )
return iPkgName < jPkgName
} )
if existingAss == nil {
return
}
k . Spec [ fieldAssociations ] = existingAss
}
2020-04-01 23:44:17 +00:00
// SetMetadataName sets the metadata.name field.
func ( k Object ) SetMetadataName ( name string ) {
if k . Metadata == nil {
k . Metadata = make ( Resource )
}
k . Metadata [ fieldName ] = name
}
2020-06-30 21:54:00 +00:00
// Template is the model for a package. The resources are more generic that one might
2019-10-23 17:09:04 +00:00
// expect at first glance. This was done on purpose. The way json/yaml/toml or
// w/e scripting you want to use, can have very different ways of parsing. The
// different parsers are limited for the parsers that do not come from the std
// lib (looking at you yaml/v2). This allows us to parse it and leave the matching
// to another power, the graphing of the package is handled within itself.
2020-06-30 21:54:00 +00:00
type Template struct {
2020-01-13 19:13:37 +00:00
Objects [ ] Object ` json:"-" yaml:"-" `
2020-06-15 20:24:35 +00:00
sources [ ] string
2019-10-23 17:09:04 +00:00
2019-12-06 07:05:32 +00:00
mLabels map [ string ] * label
mBuckets map [ string ] * bucket
2019-12-18 01:57:44 +00:00
mChecks map [ string ] * check
2020-03-18 22:54:02 +00:00
mDashboards map [ string ] * dashboard
2019-12-06 07:05:32 +00:00
mNotificationEndpoints map [ string ] * notificationEndpoint
2020-03-19 00:05:29 +00:00
mNotificationRules map [ string ] * notificationRule
2020-03-19 16:36:54 +00:00
mTasks map [ string ] * task
2020-03-17 21:59:37 +00:00
mTelegrafs map [ string ] * telegraf
2019-12-06 07:05:32 +00:00
mVariables map [ string ] * variable
2019-10-28 22:23:40 +00:00
2020-02-06 17:28:04 +00:00
mEnv map [ string ] bool
2020-07-28 18:27:52 +00:00
mEnvVals map [ string ] interface { }
2019-12-27 19:22:05 +00:00
mSecrets map [ string ] bool
2019-12-16 17:39:55 +00:00
2020-04-11 04:51:13 +00:00
isParsed bool // indicates the pkg has been parsed and all resources graphed accordingly
2019-10-23 17:09:04 +00:00
}
2020-01-13 19:13:37 +00:00
// Encode is a helper for encoding the pkg correctly.
2020-06-30 21:54:00 +00:00
func ( p * Template ) Encode ( encoding Encoding ) ( [ ] byte , error ) {
2020-04-29 22:24:19 +00:00
if p == nil {
2020-06-30 21:54:00 +00:00
panic ( "attempted to encode a nil Template" )
2020-04-29 22:24:19 +00:00
}
2020-01-13 19:13:37 +00:00
var (
buf bytes . Buffer
err error
)
switch encoding {
case EncodingJSON , EncodingJsonnet :
enc := json . NewEncoder ( & buf )
enc . SetIndent ( "" , "\t" )
err = enc . Encode ( p . Objects )
case EncodingYAML :
enc := yaml . NewEncoder ( & buf )
for _ , k := range p . Objects {
if err = enc . Encode ( k ) ; err != nil {
break
}
}
default :
return nil , ErrInvalidEncoding
}
if err != nil {
return nil , err
}
return buf . Bytes ( ) , nil
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) Sources ( ) [ ] string {
2020-06-15 21:13:38 +00:00
// note: we prevent the internal field from being changed by enabling access
// to the sources via the exported method here.
return p . sources
}
2019-10-30 21:13:42 +00:00
// Summary returns a package Summary that describes all the resources and
2019-10-23 17:09:04 +00:00
// associations the pkg contains. It is very useful for informing users of
// the changes that will take place when this pkg would be applied.
2020-06-30 21:54:00 +00:00
func ( p * Template ) Summary ( ) Summary {
2020-01-12 02:25:19 +00:00
// ensure zero values for arrays aren't returned, but instead
// we always returning an initialized slice.
sum := Summary {
Buckets : [ ] SummaryBucket { } ,
Checks : [ ] SummaryCheck { } ,
Dashboards : [ ] SummaryDashboard { } ,
NotificationEndpoints : [ ] SummaryNotificationEndpoint { } ,
NotificationRules : [ ] SummaryNotificationRule { } ,
Labels : [ ] SummaryLabel { } ,
2020-02-05 00:15:20 +00:00
MissingEnvs : p . missingEnvRefs ( ) ,
2020-04-11 04:51:13 +00:00
MissingSecrets : p . missingSecrets ( ) ,
2020-01-12 02:25:19 +00:00
Tasks : [ ] SummaryTask { } ,
TelegrafConfigs : [ ] SummaryTelegraf { } ,
Variables : [ ] SummaryVariable { } ,
}
2019-10-23 17:09:04 +00:00
2020-04-02 22:28:11 +00:00
for _ , b := range p . buckets ( ) {
2019-10-28 22:23:40 +00:00
sum . Buckets = append ( sum . Buckets , b . summarize ( ) )
2019-10-23 17:09:04 +00:00
}
2019-10-30 21:13:42 +00:00
2019-12-18 01:57:44 +00:00
for _ , c := range p . checks ( ) {
sum . Checks = append ( sum . Checks , c . summarize ( ) )
}
2019-10-30 21:13:42 +00:00
for _ , d := range p . dashboards ( ) {
sum . Dashboards = append ( sum . Dashboards , d . summarize ( ) )
}
2019-10-23 17:09:04 +00:00
2020-04-02 22:28:11 +00:00
for _ , l := range p . labels ( ) {
2019-11-06 22:41:06 +00:00
sum . Labels = append ( sum . Labels , l . summarize ( ) )
}
2019-12-12 19:09:32 +00:00
sum . LabelMappings = p . labelMappings ( )
2019-10-28 22:23:40 +00:00
2019-12-06 07:05:32 +00:00
for _ , n := range p . notificationEndpoints ( ) {
sum . NotificationEndpoints = append ( sum . NotificationEndpoints , n . summarize ( ) )
}
2019-12-19 19:56:03 +00:00
for _ , r := range p . notificationRules ( ) {
sum . NotificationRules = append ( sum . NotificationRules , r . summarize ( ) )
}
2019-12-23 08:22:48 +00:00
for _ , t := range p . tasks ( ) {
sum . Tasks = append ( sum . Tasks , t . summarize ( ) )
}
2019-12-03 18:22:59 +00:00
for _ , t := range p . telegrafs ( ) {
sum . TelegrafConfigs = append ( sum . TelegrafConfigs , t . summarize ( ) )
}
2019-11-06 22:41:06 +00:00
for _ , v := range p . variables ( ) {
sum . Variables = append ( sum . Variables , v . summarize ( ) )
}
2019-10-23 17:09:04 +00:00
return sum
}
2020-07-28 18:27:52 +00:00
func ( p * Template ) applyEnvRefs ( envRefs map [ string ] interface { } ) error {
2020-02-06 17:28:04 +00:00
if len ( envRefs ) == 0 {
return nil
}
2020-02-06 05:42:01 +00:00
if p . mEnvVals == nil {
2020-07-28 18:27:52 +00:00
p . mEnvVals = make ( map [ string ] interface { } )
2020-02-06 05:42:01 +00:00
}
2020-02-05 00:15:20 +00:00
for k , v := range envRefs {
2020-02-06 05:42:01 +00:00
p . mEnvVals [ k ] = v
2020-02-05 00:15:20 +00:00
}
2020-02-06 17:28:04 +00:00
return p . Validate ( )
2020-02-05 00:15:20 +00:00
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) applySecrets ( secrets map [ string ] string ) {
2019-12-27 19:22:05 +00:00
for k := range secrets {
p . mSecrets [ k ] = true
}
}
2020-04-01 00:01:45 +00:00
// Contains identifies if a pkg contains a given object identified
2020-06-24 18:27:03 +00:00
// by its kind and metadata.Name (MetaName) field.
2020-06-30 21:54:00 +00:00
func ( p * Template ) Contains ( k Kind , pkgName string ) bool {
2020-04-01 00:01:45 +00:00
switch k {
2020-04-17 02:27:58 +00:00
case KindBucket :
_ , ok := p . mBuckets [ pkgName ]
return ok
case KindCheck , KindCheckDeadman , KindCheckThreshold :
_ , ok := p . mChecks [ pkgName ]
return ok
2020-04-01 00:01:45 +00:00
case KindLabel :
2020-04-17 02:27:58 +00:00
_ , ok := p . mLabels [ pkgName ]
return ok
case KindNotificationEndpoint ,
KindNotificationEndpointHTTP ,
KindNotificationEndpointPagerDuty ,
KindNotificationEndpointSlack :
_ , ok := p . mNotificationEndpoints [ pkgName ]
return ok
2020-04-01 00:01:45 +00:00
case KindNotificationRule :
2020-04-17 02:27:58 +00:00
_ , ok := p . mNotificationRules [ pkgName ]
return ok
case KindTask :
_ , ok := p . mTasks [ pkgName ]
return ok
case KindTelegraf :
_ , ok := p . mTelegrafs [ pkgName ]
return ok
case KindVariable :
_ , ok := p . mVariables [ pkgName ]
return ok
2020-04-01 00:01:45 +00:00
}
2020-04-17 02:27:58 +00:00
return false
2020-04-01 00:01:45 +00:00
}
2020-02-06 20:26:10 +00:00
// Combine combines pkgs together. Is useful when you want to take multiple disparate pkgs
// and compile them into one to take advantage of the parser and service guarantees.
2020-06-30 21:54:00 +00:00
func Combine ( pkgs [ ] * Template , validationOpts ... ValidateOptFn ) ( * Template , error ) {
newPkg := new ( Template )
2020-02-06 20:26:10 +00:00
for _ , p := range pkgs {
2020-06-15 20:24:35 +00:00
if len ( p . Objects ) == 0 {
continue
}
newPkg . sources = append ( newPkg . sources , p . sources ... )
2020-02-06 20:26:10 +00:00
newPkg . Objects = append ( newPkg . Objects , p . Objects ... )
}
2020-03-25 17:34:41 +00:00
return newPkg , newPkg . Validate ( validationOpts ... )
2020-02-06 20:26:10 +00:00
}
2019-11-09 02:12:48 +00:00
type (
validateOpt struct {
2021-12-30 17:55:45 +00:00
minResources bool
skipValidate bool
enableJsonnet bool
2019-11-09 02:12:48 +00:00
}
// ValidateOptFn provides a means to disable desired validation checks.
ValidateOptFn func ( * validateOpt )
)
2021-12-30 17:55:45 +00:00
// Jsonnet parsing is disabled by default. EnableJsonnet turns it back on.
func EnableJsonnet ( ) ValidateOptFn {
return func ( opt * validateOpt ) {
opt . enableJsonnet = true
}
}
2019-11-09 02:12:48 +00:00
// ValidWithoutResources ignores the validation check for minimum number
// of resources. This is useful for the service Create to ignore this and
// allow the creation of a pkg without resources.
func ValidWithoutResources ( ) ValidateOptFn {
return func ( opt * validateOpt ) {
opt . minResources = false
}
}
2019-12-23 22:31:56 +00:00
// ValidSkipParseError ignores the validation check from the of resources. This
// is useful for the service Create to ignore this and allow the creation of a
// pkg without resources.
func ValidSkipParseError ( ) ValidateOptFn {
return func ( opt * validateOpt ) {
opt . skipValidate = true
}
}
2019-11-05 01:40:42 +00:00
// Validate will graph all resources and validate every thing is in a useful form.
2020-06-30 21:54:00 +00:00
func ( p * Template ) Validate ( opts ... ValidateOptFn ) error {
2019-11-09 02:12:48 +00:00
opt := & validateOpt { minResources : true }
for _ , o := range opts {
o ( opt )
}
2020-01-13 19:13:37 +00:00
var setupFns [ ] func ( ) error
2019-11-09 02:12:48 +00:00
if opt . minResources {
setupFns = append ( setupFns , p . validResources )
}
setupFns = append ( setupFns , p . graphResources )
2019-11-05 01:40:42 +00:00
2019-11-22 01:07:12 +00:00
var pErr parseErr
2019-11-05 01:40:42 +00:00
for _ , fn := range setupFns {
if err := fn ( ) ; err != nil {
2019-11-22 01:07:12 +00:00
if IsParseErr ( err ) {
pErr . append ( err . ( * parseErr ) . Resources ... )
continue
}
2019-11-05 01:40:42 +00:00
return err
}
}
2019-11-06 18:02:45 +00:00
2019-12-23 22:31:56 +00:00
if len ( pErr . Resources ) > 0 && ! opt . skipValidate {
2019-11-22 01:07:12 +00:00
return & pErr
}
2019-11-06 18:02:45 +00:00
p . isParsed = true
2019-11-05 01:40:42 +00:00
return nil
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) buckets ( ) [ ] * bucket {
2019-10-26 02:11:47 +00:00
buckets := make ( [ ] * bucket , 0 , len ( p . mBuckets ) )
for _ , b := range p . mBuckets {
2019-10-23 17:09:04 +00:00
buckets = append ( buckets , b )
}
2020-06-30 21:54:00 +00:00
sort . Slice ( buckets , func ( i , j int ) bool { return buckets [ i ] . MetaName ( ) < buckets [ j ] . MetaName ( ) } )
2019-10-23 17:09:04 +00:00
return buckets
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) checks ( ) [ ] * check {
2019-12-18 01:57:44 +00:00
checks := make ( [ ] * check , 0 , len ( p . mChecks ) )
for _ , c := range p . mChecks {
checks = append ( checks , c )
}
2020-06-30 21:54:00 +00:00
sort . Slice ( checks , func ( i , j int ) bool { return checks [ i ] . MetaName ( ) < checks [ j ] . MetaName ( ) } )
2019-12-18 01:57:44 +00:00
return checks
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) labels ( ) [ ] * label {
2019-12-06 00:53:00 +00:00
labels := make ( sortedLabels , 0 , len ( p . mLabels ) )
2020-04-01 23:44:17 +00:00
for _ , l := range p . mLabels {
labels = append ( labels , l )
2019-10-24 23:59:01 +00:00
}
2019-12-03 02:05:10 +00:00
sort . Sort ( labels )
2019-10-24 23:59:01 +00:00
return labels
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) dashboards ( ) [ ] * dashboard {
2020-03-18 22:54:02 +00:00
dashes := make ( [ ] * dashboard , 0 , len ( p . mDashboards ) )
for _ , d := range p . mDashboards {
dashes = append ( dashes , d )
}
2020-06-30 21:54:00 +00:00
sort . Slice ( dashes , func ( i , j int ) bool { return dashes [ i ] . MetaName ( ) < dashes [ j ] . MetaName ( ) } )
2019-10-30 21:13:42 +00:00
return dashes
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) notificationEndpoints ( ) [ ] * notificationEndpoint {
2019-12-06 07:05:32 +00:00
endpoints := make ( [ ] * notificationEndpoint , 0 , len ( p . mNotificationEndpoints ) )
for _ , e := range p . mNotificationEndpoints {
endpoints = append ( endpoints , e )
}
sort . Slice ( endpoints , func ( i , j int ) bool {
ei , ej := endpoints [ i ] , endpoints [ j ]
if ei . kind == ej . kind {
2020-06-30 21:54:00 +00:00
return ei . MetaName ( ) < ej . MetaName ( )
2019-12-06 07:05:32 +00:00
}
return ei . kind < ej . kind
} )
return endpoints
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) notificationRules ( ) [ ] * notificationRule {
2020-03-19 00:05:29 +00:00
rules := make ( [ ] * notificationRule , 0 , len ( p . mNotificationRules ) )
for _ , r := range p . mNotificationRules {
rules = append ( rules , r )
}
2020-06-30 21:54:00 +00:00
sort . Slice ( rules , func ( i , j int ) bool { return rules [ i ] . MetaName ( ) < rules [ j ] . MetaName ( ) } )
2019-12-19 19:56:03 +00:00
return rules
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) missingEnvRefs ( ) [ ] string {
2020-02-05 00:15:20 +00:00
envRefs := make ( [ ] string , 0 )
2020-02-06 17:28:04 +00:00
for envRef , matching := range p . mEnv {
if ! matching {
2020-02-05 00:15:20 +00:00
envRefs = append ( envRefs , envRef )
}
}
2020-02-05 01:23:28 +00:00
sort . Strings ( envRefs )
2020-02-05 00:15:20 +00:00
return envRefs
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) missingSecrets ( ) [ ] string {
2019-12-27 19:22:05 +00:00
secrets := make ( [ ] string , 0 , len ( p . mSecrets ) )
for secret , foundInPlatform := range p . mSecrets {
if foundInPlatform {
continue
}
secrets = append ( secrets , secret )
2019-12-16 17:39:55 +00:00
}
return secrets
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) tasks ( ) [ ] * task {
2020-03-19 16:36:54 +00:00
tasks := make ( [ ] * task , 0 , len ( p . mTasks ) )
for _ , t := range p . mTasks {
tasks = append ( tasks , t )
}
2019-12-23 08:22:48 +00:00
2020-06-30 21:54:00 +00:00
sort . Slice ( tasks , func ( i , j int ) bool { return tasks [ i ] . MetaName ( ) < tasks [ j ] . MetaName ( ) } )
2019-12-23 08:22:48 +00:00
return tasks
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) telegrafs ( ) [ ] * telegraf {
2020-02-05 01:23:28 +00:00
teles := make ( [ ] * telegraf , 0 , len ( p . mTelegrafs ) )
for _ , t := range p . mTelegrafs {
t . config . Name = t . Name ( )
teles = append ( teles , t )
}
2020-03-19 16:36:54 +00:00
2020-06-30 21:54:00 +00:00
sort . Slice ( teles , func ( i , j int ) bool { return teles [ i ] . MetaName ( ) < teles [ j ] . MetaName ( ) } )
2020-03-19 16:36:54 +00:00
2019-12-03 18:22:59 +00:00
return teles
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) variables ( ) [ ] * variable {
2019-11-06 22:41:06 +00:00
vars := make ( [ ] * variable , 0 , len ( p . mVariables ) )
for _ , v := range p . mVariables {
vars = append ( vars , v )
}
2020-06-30 21:54:00 +00:00
sort . Slice ( vars , func ( i , j int ) bool { return vars [ i ] . MetaName ( ) < vars [ j ] . MetaName ( ) } )
2019-11-06 22:41:06 +00:00
return vars
}
2019-10-26 02:11:47 +00:00
// labelMappings returns the mappings that will be created for
// valid pairs of labels and resources of which all have IDs.
// If a resource does not exist yet, a label mapping will not
// be returned for it.
2020-06-30 21:54:00 +00:00
func ( p * Template ) labelMappings ( ) [ ] SummaryLabelMapping {
2020-01-12 02:25:19 +00:00
labels := p . mLabels
mappings := make ( [ ] SummaryLabelMapping , 0 , len ( labels ) )
for _ , l := range labels {
2019-10-28 22:23:40 +00:00
mappings = append ( mappings , l . mappingSummary ( ) ... )
2019-10-26 02:11:47 +00:00
}
2019-10-30 21:13:42 +00:00
// sort by res type ASC, then res name ASC, then label name ASC
sort . Slice ( mappings , func ( i , j int ) bool {
n , m := mappings [ i ] , mappings [ j ]
if n . ResourceType < m . ResourceType {
return true
}
if n . ResourceType > m . ResourceType {
return false
}
if n . ResourceName < m . ResourceName {
return true
}
if n . ResourceName > m . ResourceName {
return false
}
return n . LabelName < m . LabelName
} )
2019-10-26 02:11:47 +00:00
return mappings
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) validResources ( ) error {
2020-01-13 19:13:37 +00:00
if len ( p . Objects ) > 0 {
2019-10-30 17:55:13 +00:00
return nil
}
2019-11-22 01:07:12 +00:00
res := resourceErr {
2020-01-13 19:13:37 +00:00
Kind : KindPackage . String ( ) ,
2019-11-22 01:07:12 +00:00
RootErrs : [ ] validationErr { {
Field : "resources" ,
2020-01-13 19:13:37 +00:00
Msg : "at least 1 kind must be provided" ,
2019-11-22 01:07:12 +00:00
} } ,
2019-10-30 17:55:13 +00:00
}
2019-11-22 01:07:12 +00:00
var err parseErr
2019-10-30 17:55:13 +00:00
err . append ( res )
return & err
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) graphResources ( ) error {
2020-02-06 17:28:04 +00:00
p . mEnv = make ( map [ string ] bool )
2019-12-27 19:22:05 +00:00
p . mSecrets = make ( map [ string ] bool )
2019-12-16 17:39:55 +00:00
2019-12-06 07:05:32 +00:00
graphFns := [ ] func ( ) * parseErr {
// labels are first, this is to validate associations with other resources
2019-10-26 02:11:47 +00:00
p . graphLabels ,
2019-11-06 22:41:06 +00:00
p . graphVariables ,
2019-10-26 02:11:47 +00:00
p . graphBuckets ,
2019-12-18 01:57:44 +00:00
p . graphChecks ,
2019-10-30 21:13:42 +00:00
p . graphDashboards ,
2019-12-06 07:05:32 +00:00
p . graphNotificationEndpoints ,
2019-12-19 19:56:03 +00:00
p . graphNotificationRules ,
2019-12-23 08:22:48 +00:00
p . graphTasks ,
2019-12-03 18:22:59 +00:00
p . graphTelegrafs ,
2019-10-23 17:09:04 +00:00
}
2019-11-22 01:07:12 +00:00
var pErr parseErr
2019-10-23 17:09:04 +00:00
for _ , fn := range graphFns {
if err := fn ( ) ; err != nil {
2019-12-06 07:05:32 +00:00
pErr . append ( err . Resources ... )
2019-10-23 17:09:04 +00:00
}
}
2019-11-22 01:07:12 +00:00
if len ( pErr . Resources ) > 0 {
sort . Slice ( pErr . Resources , func ( i , j int ) bool {
ir , jr := pErr . Resources [ i ] , pErr . Resources [ j ]
return * ir . Idx < * jr . Idx
2019-11-14 00:24:05 +00:00
} )
2019-11-22 01:07:12 +00:00
return & pErr
2019-11-14 00:24:05 +00:00
}
2019-10-23 17:09:04 +00:00
return nil
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) graphBuckets ( ) * parseErr {
2019-10-26 02:11:47 +00:00
p . mBuckets = make ( map [ string ] * bucket )
2020-03-19 17:00:25 +00:00
tracker := p . trackNames ( true )
2020-05-03 17:34:24 +00:00
return p . eachResource ( KindBucket , func ( o Object ) [ ] validationErr {
2020-03-19 17:00:25 +00:00
ident , errs := tracker ( o )
if len ( errs ) > 0 {
return errs
2020-03-16 18:25:39 +00:00
}
2019-10-26 02:11:47 +00:00
bkt := & bucket {
2020-03-19 17:00:25 +00:00
identity : ident ,
2020-02-06 05:42:01 +00:00
Description : o . Spec . stringShort ( fieldDescription ) ,
2021-05-31 00:01:29 +00:00
SchemaType : o . Spec . stringShort ( fieldBucketSchemaType ) ,
2019-11-22 18:41:08 +00:00
}
2020-02-06 05:42:01 +00:00
if rules , ok := o . Spec [ fieldBucketRetentionRules ] . ( retentionRules ) ; ok {
2019-11-22 18:41:08 +00:00
bkt . RetentionRules = rules
} else {
2020-02-06 05:42:01 +00:00
for _ , r := range o . Spec . slcResource ( fieldBucketRetentionRules ) {
2019-11-22 18:41:08 +00:00
bkt . RetentionRules = append ( bkt . RetentionRules , retentionRule {
Type : r . stringShort ( fieldType ) ,
Seconds : r . intShort ( fieldRetentionRulesEverySeconds ) ,
} )
}
2019-10-23 17:09:04 +00:00
}
2021-05-31 00:01:29 +00:00
if schemas , ok := o . Spec [ fieldMeasurementSchemas ] . ( measurementSchemas ) ; ok {
bkt . MeasurementSchemas = schemas
} else {
for _ , sr := range o . Spec . slcResource ( fieldMeasurementSchemas ) {
ms := measurementSchema { Name : sr . stringShort ( fieldMeasurementSchemaName ) }
for _ , scr := range sr . slcResource ( fieldMeasurementSchemaColumns ) {
ms . Columns = append ( ms . Columns , measurementColumn {
Name : scr . stringShort ( fieldMeasurementColumnName ) ,
Type : scr . stringShort ( fieldMeasurementColumnType ) ,
DataType : scr . stringShort ( fieldMeasurementColumnDataType ) ,
} )
}
bkt . MeasurementSchemas = append ( bkt . MeasurementSchemas , ms )
}
}
2020-03-16 18:25:39 +00:00
p . setRefs ( bkt . name , bkt . displayName )
2019-10-23 17:09:04 +00:00
2020-02-06 05:42:01 +00:00
failures := p . parseNestedLabels ( o . Spec , func ( l * label ) error {
2019-10-30 21:13:42 +00:00
bkt . labels = append ( bkt . labels , l )
2020-06-30 21:54:00 +00:00
p . mLabels [ l . MetaName ( ) ] . setMapping ( bkt , false )
2019-10-30 21:13:42 +00:00
return nil
} )
2019-12-03 02:05:10 +00:00
sort . Sort ( bkt . labels )
2019-10-26 02:11:47 +00:00
2020-06-30 21:54:00 +00:00
p . mBuckets [ bkt . MetaName ( ) ] = bkt
2019-10-26 02:11:47 +00:00
2019-11-22 18:41:08 +00:00
return append ( failures , bkt . valid ( ) ... )
2019-10-23 17:09:04 +00:00
} )
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) graphLabels ( ) * parseErr {
2019-10-26 02:11:47 +00:00
p . mLabels = make ( map [ string ] * label )
2020-03-19 17:00:25 +00:00
tracker := p . trackNames ( true )
2020-05-03 17:34:24 +00:00
return p . eachResource ( KindLabel , func ( o Object ) [ ] validationErr {
2020-03-19 17:00:25 +00:00
ident , errs := tracker ( o )
if len ( errs ) > 0 {
return errs
2019-10-24 23:59:01 +00:00
}
2020-02-05 01:23:28 +00:00
l := & label {
2020-03-19 17:00:25 +00:00
identity : ident ,
2020-02-06 05:42:01 +00:00
Color : o . Spec . stringShort ( fieldLabelColor ) ,
Description : o . Spec . stringShort ( fieldDescription ) ,
2019-10-24 23:59:01 +00:00
}
2020-06-30 21:54:00 +00:00
p . mLabels [ l . MetaName ( ) ] = l
2020-03-19 17:00:25 +00:00
p . setRefs ( l . name , l . displayName )
2019-10-24 23:59:01 +00:00
2020-03-16 22:17:24 +00:00
return l . valid ( )
2019-10-24 23:59:01 +00:00
} )
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) graphChecks ( ) * parseErr {
2019-12-18 01:57:44 +00:00
p . mChecks = make ( map [ string ] * check )
2020-09-03 23:56:02 +00:00
// todo: what is the business goal wrt having unique names? (currently duplicates are allowed)
tracker := p . trackNames ( false )
2019-12-18 01:57:44 +00:00
checkKinds := [ ] struct {
kind Kind
checkKind checkKind
} {
{ kind : KindCheckThreshold , checkKind : checkKindThreshold } ,
{ kind : KindCheckDeadman , checkKind : checkKindDeadman } ,
}
var pErr parseErr
2020-01-13 19:13:37 +00:00
for _ , checkKind := range checkKinds {
2020-05-03 17:34:24 +00:00
err := p . eachResource ( checkKind . kind , func ( o Object ) [ ] validationErr {
2020-03-19 17:00:25 +00:00
ident , errs := tracker ( o )
if len ( errs ) > 0 {
return errs
2020-03-16 23:45:25 +00:00
}
2019-12-18 01:57:44 +00:00
ch := & check {
2020-01-13 19:13:37 +00:00
kind : checkKind . checkKind ,
2020-03-19 17:00:25 +00:00
identity : ident ,
2020-02-06 05:42:01 +00:00
description : o . Spec . stringShort ( fieldDescription ) ,
every : o . Spec . durationShort ( fieldEvery ) ,
level : o . Spec . stringShort ( fieldLevel ) ,
offset : o . Spec . durationShort ( fieldOffset ) ,
query : strings . TrimSpace ( o . Spec . stringShort ( fieldQuery ) ) ,
reportZero : o . Spec . boolShort ( fieldCheckReportZero ) ,
staleTime : o . Spec . durationShort ( fieldCheckStaleTime ) ,
status : normStr ( o . Spec . stringShort ( fieldStatus ) ) ,
statusMessage : o . Spec . stringShort ( fieldCheckStatusMessageTemplate ) ,
timeSince : o . Spec . durationShort ( fieldCheckTimeSince ) ,
2019-12-18 01:57:44 +00:00
}
2020-02-06 05:42:01 +00:00
for _ , tagRes := range o . Spec . slcResource ( fieldCheckTags ) {
2019-12-18 01:57:44 +00:00
ch . tags = append ( ch . tags , struct { k , v string } {
k : tagRes . stringShort ( fieldKey ) ,
v : tagRes . stringShort ( fieldValue ) ,
} )
}
2020-02-06 05:42:01 +00:00
for _ , th := range o . Spec . slcResource ( fieldCheckThresholds ) {
2019-12-18 01:57:44 +00:00
ch . thresholds = append ( ch . thresholds , threshold {
threshType : thresholdType ( normStr ( th . stringShort ( fieldType ) ) ) ,
allVals : th . boolShort ( fieldCheckAllValues ) ,
2019-12-19 19:56:03 +00:00
level : strings . TrimSpace ( strings . ToUpper ( th . stringShort ( fieldLevel ) ) ) ,
2019-12-18 01:57:44 +00:00
max : th . float64Short ( fieldMax ) ,
min : th . float64Short ( fieldMin ) ,
val : th . float64Short ( fieldValue ) ,
} )
}
2020-02-06 05:42:01 +00:00
failures := p . parseNestedLabels ( o . Spec , func ( l * label ) error {
2019-12-18 01:57:44 +00:00
ch . labels = append ( ch . labels , l )
2020-06-30 21:54:00 +00:00
p . mLabels [ l . MetaName ( ) ] . setMapping ( ch , false )
2019-12-18 01:57:44 +00:00
return nil
} )
sort . Sort ( ch . labels )
2020-06-30 21:54:00 +00:00
p . mChecks [ ch . MetaName ( ) ] = ch
2020-03-19 17:00:25 +00:00
p . setRefs ( ch . name , ch . displayName )
2019-12-18 01:57:44 +00:00
return append ( failures , ch . valid ( ) ... )
} )
if err != nil {
pErr . append ( err . Resources ... )
}
}
if len ( pErr . Resources ) > 0 {
return & pErr
}
return nil
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) graphDashboards ( ) * parseErr {
2020-03-18 22:54:02 +00:00
p . mDashboards = make ( map [ string ] * dashboard )
2020-03-19 17:00:25 +00:00
tracker := p . trackNames ( false )
2020-05-03 17:34:24 +00:00
return p . eachResource ( KindDashboard , func ( o Object ) [ ] validationErr {
2020-03-19 17:00:25 +00:00
ident , errs := tracker ( o )
if len ( errs ) > 0 {
return errs
2020-03-18 22:54:02 +00:00
}
2020-03-19 16:36:54 +00:00
2019-10-30 21:13:42 +00:00
dash := & dashboard {
2020-03-19 17:00:25 +00:00
identity : ident ,
2020-02-06 05:42:01 +00:00
Description : o . Spec . stringShort ( fieldDescription ) ,
2019-10-30 21:13:42 +00:00
}
2020-02-06 05:42:01 +00:00
failures := p . parseNestedLabels ( o . Spec , func ( l * label ) error {
2019-10-30 21:13:42 +00:00
dash . labels = append ( dash . labels , l )
2020-06-30 21:54:00 +00:00
p . mLabels [ l . MetaName ( ) ] . setMapping ( dash , false )
2019-10-30 21:13:42 +00:00
return nil
} )
2019-12-03 02:05:10 +00:00
sort . Sort ( dash . labels )
2019-10-30 21:13:42 +00:00
2020-02-06 05:42:01 +00:00
for i , cr := range o . Spec . slcResource ( fieldDashCharts ) {
2020-07-30 18:26:17 +00:00
ch , fails := p . parseChart ( dash . MetaName ( ) , i , cr )
2019-11-01 18:11:42 +00:00
if fails != nil {
2020-03-18 22:54:02 +00:00
failures = append ( failures ,
objectValidationErr ( fieldSpec , validationErr {
Field : fieldDashCharts ,
Index : intPtr ( i ) ,
Nested : fails ,
} ) ,
)
2019-11-01 18:11:42 +00:00
continue
}
dash . Charts = append ( dash . Charts , ch )
}
2020-06-30 21:54:00 +00:00
p . mDashboards [ dash . MetaName ( ) ] = dash
2020-07-30 18:26:17 +00:00
p . setRefs ( dash . refs ( ) ... )
2019-10-30 21:13:42 +00:00
2020-03-18 22:54:02 +00:00
return append ( failures , dash . valid ( ) ... )
2019-10-30 21:13:42 +00:00
} )
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) graphNotificationEndpoints ( ) * parseErr {
2019-12-06 07:05:32 +00:00
p . mNotificationEndpoints = make ( map [ string ] * notificationEndpoint )
2020-09-10 23:49:36 +00:00
tracker := p . trackNames ( true )
2019-12-06 07:05:32 +00:00
notificationKinds := [ ] struct {
kind Kind
2020-04-15 19:46:17 +00:00
notificationKind notificationEndpointKind
2019-12-06 07:05:32 +00:00
} {
{
kind : KindNotificationEndpointHTTP ,
notificationKind : notificationKindHTTP ,
} ,
{
kind : KindNotificationEndpointPagerDuty ,
notificationKind : notificationKindPagerDuty ,
} ,
{
kind : KindNotificationEndpointSlack ,
notificationKind : notificationKindSlack ,
} ,
}
var pErr parseErr
for _ , nk := range notificationKinds {
2020-05-03 17:34:24 +00:00
err := p . eachResource ( nk . kind , func ( o Object ) [ ] validationErr {
2020-03-19 17:00:25 +00:00
ident , errs := tracker ( o )
if len ( errs ) > 0 {
return errs
2020-03-17 17:38:29 +00:00
}
2019-12-06 07:05:32 +00:00
endpoint := & notificationEndpoint {
kind : nk . notificationKind ,
2020-03-19 17:00:25 +00:00
identity : ident ,
2020-02-06 05:42:01 +00:00
description : o . Spec . stringShort ( fieldDescription ) ,
method : strings . TrimSpace ( strings . ToUpper ( o . Spec . stringShort ( fieldNotificationEndpointHTTPMethod ) ) ) ,
httpType : normStr ( o . Spec . stringShort ( fieldType ) ) ,
password : o . Spec . references ( fieldNotificationEndpointPassword ) ,
routingKey : o . Spec . references ( fieldNotificationEndpointRoutingKey ) ,
status : normStr ( o . Spec . stringShort ( fieldStatus ) ) ,
token : o . Spec . references ( fieldNotificationEndpointToken ) ,
url : o . Spec . stringShort ( fieldNotificationEndpointURL ) ,
username : o . Spec . references ( fieldNotificationEndpointUsername ) ,
2019-12-06 07:05:32 +00:00
}
2020-02-06 05:42:01 +00:00
failures := p . parseNestedLabels ( o . Spec , func ( l * label ) error {
2019-12-06 07:05:32 +00:00
endpoint . labels = append ( endpoint . labels , l )
2020-06-30 21:54:00 +00:00
p . mLabels [ l . MetaName ( ) ] . setMapping ( endpoint , false )
2019-12-06 07:05:32 +00:00
return nil
} )
sort . Sort ( endpoint . labels )
2020-03-17 17:38:29 +00:00
p . setRefs (
2020-03-19 17:00:25 +00:00
endpoint . name ,
endpoint . displayName ,
2020-03-17 17:38:29 +00:00
endpoint . password ,
endpoint . routingKey ,
endpoint . token ,
endpoint . username ,
)
2019-12-16 17:39:55 +00:00
2020-06-30 21:54:00 +00:00
p . mNotificationEndpoints [ endpoint . MetaName ( ) ] = endpoint
2019-12-06 07:05:32 +00:00
return append ( failures , endpoint . valid ( ) ... )
} )
if err != nil {
pErr . append ( err . Resources ... )
}
}
if len ( pErr . Resources ) > 0 {
return & pErr
}
return nil
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) graphNotificationRules ( ) * parseErr {
2020-03-19 00:05:29 +00:00
p . mNotificationRules = make ( map [ string ] * notificationRule )
2020-03-19 17:00:25 +00:00
tracker := p . trackNames ( false )
2020-05-03 17:34:24 +00:00
return p . eachResource ( KindNotificationRule , func ( o Object ) [ ] validationErr {
2020-03-19 17:00:25 +00:00
ident , errs := tracker ( o )
if len ( errs ) > 0 {
return errs
2020-03-19 00:05:29 +00:00
}
2019-12-19 19:56:03 +00:00
rule := & notificationRule {
2020-03-19 17:00:25 +00:00
identity : ident ,
2020-02-06 17:28:04 +00:00
endpointName : p . getRefWithKnownEnvs ( o . Spec , fieldNotificationRuleEndpointName ) ,
2020-02-06 05:42:01 +00:00
description : o . Spec . stringShort ( fieldDescription ) ,
channel : o . Spec . stringShort ( fieldNotificationRuleChannel ) ,
every : o . Spec . durationShort ( fieldEvery ) ,
msgTemplate : o . Spec . stringShort ( fieldNotificationRuleMessageTemplate ) ,
offset : o . Spec . durationShort ( fieldOffset ) ,
status : normStr ( o . Spec . stringShort ( fieldStatus ) ) ,
2019-12-19 19:56:03 +00:00
}
2020-02-06 05:42:01 +00:00
for _ , sRule := range o . Spec . slcResource ( fieldNotificationRuleStatusRules ) {
2019-12-19 19:56:03 +00:00
rule . statusRules = append ( rule . statusRules , struct { curLvl , prevLvl string } {
curLvl : strings . TrimSpace ( strings . ToUpper ( sRule . stringShort ( fieldNotificationRuleCurrentLevel ) ) ) ,
prevLvl : strings . TrimSpace ( strings . ToUpper ( sRule . stringShort ( fieldNotificationRulePreviousLevel ) ) ) ,
} )
}
2020-02-06 05:42:01 +00:00
for _ , tRule := range o . Spec . slcResource ( fieldNotificationRuleTagRules ) {
2019-12-19 19:56:03 +00:00
rule . tagRules = append ( rule . tagRules , struct { k , v , op string } {
k : tRule . stringShort ( fieldKey ) ,
v : tRule . stringShort ( fieldValue ) ,
op : normStr ( tRule . stringShort ( fieldOperator ) ) ,
} )
}
2020-04-17 02:27:58 +00:00
rule . associatedEndpoint = p . mNotificationEndpoints [ rule . endpointName . String ( ) ]
2020-02-06 05:42:01 +00:00
failures := p . parseNestedLabels ( o . Spec , func ( l * label ) error {
2019-12-19 19:56:03 +00:00
rule . labels = append ( rule . labels , l )
2020-06-30 21:54:00 +00:00
p . mLabels [ l . MetaName ( ) ] . setMapping ( rule , false )
2019-12-19 19:56:03 +00:00
return nil
} )
sort . Sort ( rule . labels )
2020-06-30 21:54:00 +00:00
p . mNotificationRules [ rule . MetaName ( ) ] = rule
2020-03-19 00:05:29 +00:00
p . setRefs ( rule . name , rule . displayName , rule . endpointName )
2019-12-19 19:56:03 +00:00
return append ( failures , rule . valid ( ) ... )
} )
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) graphTasks ( ) * parseErr {
2020-03-19 16:36:54 +00:00
p . mTasks = make ( map [ string ] * task )
2020-03-19 17:00:25 +00:00
tracker := p . trackNames ( false )
2020-05-03 17:34:24 +00:00
return p . eachResource ( KindTask , func ( o Object ) [ ] validationErr {
2020-03-19 17:00:25 +00:00
ident , errs := tracker ( o )
if len ( errs ) > 0 {
return errs
2020-03-19 16:36:54 +00:00
}
2019-12-23 08:22:48 +00:00
t := & task {
2020-03-19 17:00:25 +00:00
identity : ident ,
2020-02-06 05:42:01 +00:00
cron : o . Spec . stringShort ( fieldTaskCron ) ,
description : o . Spec . stringShort ( fieldDescription ) ,
every : o . Spec . durationShort ( fieldEvery ) ,
offset : o . Spec . durationShort ( fieldOffset ) ,
status : normStr ( o . Spec . stringShort ( fieldStatus ) ) ,
2019-11-06 22:41:06 +00:00
}
2020-08-05 21:36:57 +00:00
prefix := fmt . Sprintf ( "tasks[%s].spec" , t . MetaName ( ) )
2020-08-26 20:44:59 +00:00
params := o . Spec . slcResource ( fieldParams )
task := o . Spec . slcResource ( "task" )
2020-08-05 21:36:57 +00:00
2020-08-26 20:44:59 +00:00
var (
err error
failures [ ] validationErr
)
t . query , err = p . parseQuery ( prefix , o . Spec . stringShort ( fieldQuery ) , params , task )
if err != nil {
failures = append ( failures , validationErr {
Field : fieldQuery ,
Msg : err . Error ( ) ,
} )
}
if o . APIVersion == APIVersion2 {
for _ , ref := range t . query . task {
switch ref . EnvRef {
case prefix + ".task.name" , prefix + ".params.name" :
t . displayName = ref
case prefix + ".task.every" :
every , ok := ref . defaultVal . ( time . Duration )
if ok {
t . every = every
} else {
failures = append ( failures , validationErr {
Field : fieldTask ,
Msg : "field every is not duration" ,
} )
}
case prefix + ".task.offset" :
offset , ok := ref . defaultVal . ( time . Duration )
if ok {
t . offset = offset
} else {
failures = append ( failures , validationErr {
Field : fieldTask ,
Msg : "field every is not duration" ,
} )
}
}
}
}
failures = append ( failures , p . parseNestedLabels ( o . Spec , func ( l * label ) error {
2019-12-23 08:22:48 +00:00
t . labels = append ( t . labels , l )
2020-06-30 21:54:00 +00:00
p . mLabels [ l . MetaName ( ) ] . setMapping ( t , false )
2019-11-07 00:45:00 +00:00
return nil
2020-08-26 20:44:59 +00:00
} ) ... )
2019-12-23 08:22:48 +00:00
sort . Sort ( t . labels )
2019-11-06 22:41:06 +00:00
2020-06-30 21:54:00 +00:00
p . mTasks [ t . MetaName ( ) ] = t
2020-08-26 20:44:59 +00:00
2020-08-05 21:36:57 +00:00
p . setRefs ( t . refs ( ) ... )
2019-12-23 08:22:48 +00:00
return append ( failures , t . valid ( ) ... )
2019-11-06 22:41:06 +00:00
} )
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) graphTelegrafs ( ) * parseErr {
2020-03-17 21:59:37 +00:00
p . mTelegrafs = make ( map [ string ] * telegraf )
2020-03-19 17:00:25 +00:00
tracker := p . trackNames ( false )
2020-05-03 17:34:24 +00:00
return p . eachResource ( KindTelegraf , func ( o Object ) [ ] validationErr {
2020-03-19 17:00:25 +00:00
ident , errs := tracker ( o )
if len ( errs ) > 0 {
return errs
2020-03-17 21:59:37 +00:00
}
2020-02-05 01:23:28 +00:00
tele := & telegraf {
2020-03-19 17:00:25 +00:00
identity : ident ,
2020-02-05 01:23:28 +00:00
}
2020-03-17 21:59:37 +00:00
tele . config . Config = o . Spec . stringShort ( fieldTelegrafConfig )
2020-02-06 05:42:01 +00:00
tele . config . Description = o . Spec . stringShort ( fieldDescription )
2019-12-04 01:00:15 +00:00
2020-02-06 05:42:01 +00:00
failures := p . parseNestedLabels ( o . Spec , func ( l * label ) error {
2019-12-03 18:22:59 +00:00
tele . labels = append ( tele . labels , l )
2020-06-30 21:54:00 +00:00
p . mLabels [ l . MetaName ( ) ] . setMapping ( tele , false )
2019-12-03 18:22:59 +00:00
return nil
} )
sort . Sort ( tele . labels )
2020-06-30 21:54:00 +00:00
p . mTelegrafs [ tele . MetaName ( ) ] = tele
2020-03-18 22:54:02 +00:00
p . setRefs ( tele . name , tele . displayName )
2019-12-03 18:22:59 +00:00
2020-03-17 21:59:37 +00:00
return append ( failures , tele . valid ( ) ... )
2019-12-03 18:22:59 +00:00
} )
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) graphVariables ( ) * parseErr {
2019-12-23 08:22:48 +00:00
p . mVariables = make ( map [ string ] * variable )
2020-03-19 17:00:25 +00:00
tracker := p . trackNames ( true )
2020-05-03 17:34:24 +00:00
return p . eachResource ( KindVariable , func ( o Object ) [ ] validationErr {
2020-03-19 17:00:25 +00:00
ident , errs := tracker ( o )
if len ( errs ) > 0 {
return errs
2019-12-23 08:22:48 +00:00
}
newVar := & variable {
2020-03-19 17:00:25 +00:00
identity : ident ,
2020-02-06 05:42:01 +00:00
Description : o . Spec . stringShort ( fieldDescription ) ,
Type : normStr ( o . Spec . stringShort ( fieldType ) ) ,
Query : strings . TrimSpace ( o . Spec . stringShort ( fieldQuery ) ) ,
Language : normStr ( o . Spec . stringShort ( fieldLanguage ) ) ,
ConstValues : o . Spec . slcStr ( fieldValues ) ,
MapValues : o . Spec . mapStrStr ( fieldValues ) ,
2019-12-23 08:22:48 +00:00
}
2020-06-22 22:44:53 +00:00
if iSelected , ok := o . Spec [ fieldVariableSelected ] . ( [ ] interface { } ) ; ok {
for _ , res := range iSelected {
newVar . selected = append ( newVar . selected , ifaceToReference ( res ) )
}
}
2020-02-06 05:42:01 +00:00
failures := p . parseNestedLabels ( o . Spec , func ( l * label ) error {
2019-12-23 08:22:48 +00:00
newVar . labels = append ( newVar . labels , l )
2020-06-30 21:54:00 +00:00
p . mLabels [ l . MetaName ( ) ] . setMapping ( newVar , false )
2019-12-23 08:22:48 +00:00
return nil
} )
sort . Sort ( newVar . labels )
2020-06-30 21:54:00 +00:00
p . mVariables [ newVar . MetaName ( ) ] = newVar
2020-03-17 19:05:15 +00:00
p . setRefs ( newVar . name , newVar . displayName )
2020-06-22 22:44:53 +00:00
p . setRefs ( newVar . selected ... )
2019-12-23 08:22:48 +00:00
return append ( failures , newVar . valid ( ) ... )
} )
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) eachResource ( resourceKind Kind , fn func ( o Object ) [ ] validationErr ) * parseErr {
2019-11-22 01:07:12 +00:00
var pErr parseErr
2020-01-13 19:13:37 +00:00
for i , k := range p . Objects {
2020-03-18 18:47:13 +00:00
if err := k . Kind . OK ( ) ; err != nil {
2019-11-22 01:07:12 +00:00
pErr . append ( resourceErr {
2020-03-18 18:47:13 +00:00
Kind : k . Kind . String ( ) ,
2019-11-22 01:07:12 +00:00
Idx : intPtr ( i ) ,
ValidationErrs : [ ] validationErr {
2019-10-23 17:09:04 +00:00
{
2020-01-14 21:49:29 +00:00
Field : fieldKind ,
2019-10-23 17:09:04 +00:00
Msg : err . Error ( ) ,
} ,
} ,
} )
continue
}
2020-03-18 18:47:13 +00:00
if ! k . Kind . is ( resourceKind ) {
2019-10-23 17:09:04 +00:00
continue
}
2020-08-26 20:44:59 +00:00
if k . APIVersion != APIVersion && k . APIVersion != APIVersion2 {
2020-01-14 21:49:29 +00:00
pErr . append ( resourceErr {
2020-03-18 18:47:13 +00:00
Kind : k . Kind . String ( ) ,
2020-01-14 21:49:29 +00:00
Idx : intPtr ( i ) ,
ValidationErrs : [ ] validationErr {
{
Field : fieldAPIVersion ,
2020-08-26 20:44:59 +00:00
Msg : fmt . Sprintf ( "invalid API version provided %q; must be 1 in [%s, %s]" , k . APIVersion , APIVersion , APIVersion2 ) ,
2020-01-14 21:49:29 +00:00
} ,
} ,
} )
continue
}
2020-05-03 17:34:24 +00:00
if errs := isDNS1123Label ( k . Name ( ) ) ; len ( errs ) > 0 {
2019-12-03 02:05:10 +00:00
pErr . append ( resourceErr {
2020-03-18 18:47:13 +00:00
Kind : k . Kind . String ( ) ,
2019-12-03 02:05:10 +00:00
Idx : intPtr ( i ) ,
ValidationErrs : [ ] validationErr {
2020-03-16 18:25:39 +00:00
objectValidationErr ( fieldMetadata , validationErr {
Field : fieldName ,
2020-05-03 17:34:24 +00:00
Msg : fmt . Sprintf ( "name %q is invalid; %s" , k . Name ( ) , strings . Join ( errs , "; " ) ) ,
2020-03-16 18:25:39 +00:00
} ) ,
2019-12-03 02:05:10 +00:00
} ,
} )
continue
}
2020-02-06 17:28:04 +00:00
if failures := fn ( k ) ; failures != nil {
2019-11-22 01:07:12 +00:00
err := resourceErr {
2019-11-01 18:11:42 +00:00
Kind : resourceKind . String ( ) ,
2019-11-22 01:07:12 +00:00
Idx : intPtr ( i ) ,
2019-10-26 02:11:47 +00:00
}
for _ , f := range failures {
2019-11-22 01:07:12 +00:00
vErr := validationErr {
2019-11-14 00:24:05 +00:00
Field : f . Field ,
Msg : f . Msg ,
Index : f . Index ,
Nested : f . Nested ,
}
if vErr . Field == "associations" {
err . AssociationErrs = append ( err . AssociationErrs , vErr )
2019-10-26 02:11:47 +00:00
continue
}
2019-11-14 00:24:05 +00:00
err . ValidationErrs = append ( err . ValidationErrs , vErr )
2019-10-26 02:11:47 +00:00
}
2019-11-22 01:07:12 +00:00
pErr . append ( err )
2019-10-23 17:09:04 +00:00
}
}
2019-11-22 01:07:12 +00:00
if len ( pErr . Resources ) > 0 {
return & pErr
2019-10-23 17:09:04 +00:00
}
return nil
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) parseNestedLabels ( r Resource , fn func ( lb * label ) error ) [ ] validationErr {
2019-10-30 21:13:42 +00:00
nestedLabels := make ( map [ string ] * label )
2019-11-22 01:07:12 +00:00
var failures [ ] validationErr
2019-11-08 19:33:41 +00:00
for i , nr := range r . slcResource ( fieldAssociations ) {
2019-11-14 00:24:05 +00:00
fail := p . parseNestedLabel ( nr , func ( l * label ) error {
2019-12-03 02:05:10 +00:00
if _ , ok := nestedLabels [ l . Name ( ) ] ; ok {
return fmt . Errorf ( "duplicate nested label: %q" , l . Name ( ) )
2019-10-30 21:13:42 +00:00
}
2019-12-03 02:05:10 +00:00
nestedLabels [ l . Name ( ) ] = l
2019-10-30 21:13:42 +00:00
return fn ( l )
} )
if fail != nil {
2019-11-14 00:24:05 +00:00
fail . Index = intPtr ( i )
2019-10-30 21:13:42 +00:00
failures = append ( failures , * fail )
}
}
return failures
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) parseNestedLabel ( nr Resource , fn func ( lb * label ) error ) * validationErr {
2019-10-26 02:11:47 +00:00
k , err := nr . kind ( )
if err != nil {
2019-11-22 01:07:12 +00:00
return & validationErr {
2019-12-12 19:09:32 +00:00
Field : fieldAssociations ,
2019-11-22 01:07:12 +00:00
Nested : [ ] validationErr {
2019-11-14 00:24:05 +00:00
{
2019-12-12 19:09:32 +00:00
Field : fieldKind ,
2019-11-14 00:24:05 +00:00
Msg : err . Error ( ) ,
} ,
} ,
2019-10-26 02:11:47 +00:00
}
}
2019-11-08 19:33:41 +00:00
if ! k . is ( KindLabel ) {
2019-10-26 02:11:47 +00:00
return nil
}
2020-02-06 17:28:04 +00:00
nameRef := p . getRefWithKnownEnvs ( nr , fieldName )
2020-02-05 17:33:45 +00:00
lb , found := p . mLabels [ nameRef . String ( ) ]
2019-10-26 02:11:47 +00:00
if ! found {
2019-11-22 01:07:12 +00:00
return & validationErr {
2019-12-12 19:09:32 +00:00
Field : fieldAssociations ,
2019-11-14 00:24:05 +00:00
Msg : fmt . Sprintf ( "label %q does not exist in pkg" , nr . Name ( ) ) ,
2019-10-26 02:11:47 +00:00
}
}
if err := fn ( lb ) ; err != nil {
2019-11-22 01:07:12 +00:00
return & validationErr {
2019-12-12 19:09:32 +00:00
Field : fieldAssociations ,
2019-11-14 00:24:05 +00:00
Msg : err . Error ( ) ,
2019-10-26 02:11:47 +00:00
}
}
return nil
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) trackNames ( resourceUniqueByName bool ) func ( Object ) ( identity , [ ] validationErr ) {
2020-03-19 17:00:25 +00:00
mPkgNames := make ( map [ string ] bool )
uniqNames := make ( map [ string ] bool )
return func ( o Object ) ( identity , [ ] validationErr ) {
nameRef := p . getRefWithKnownEnvs ( o . Metadata , fieldName )
if mPkgNames [ nameRef . String ( ) ] {
return identity { } , [ ] validationErr {
objectValidationErr ( fieldMetadata , validationErr {
Field : fieldName ,
Msg : "duplicate name: " + nameRef . String ( ) ,
} ) ,
}
}
mPkgNames [ nameRef . String ( ) ] = true
displayNameRef := p . getRefWithKnownEnvs ( o . Spec , fieldName )
identity := identity {
name : nameRef ,
displayName : displayNameRef ,
}
if ! resourceUniqueByName {
return identity , nil
}
name := identity . Name ( )
if uniqNames [ name ] {
return identity , [ ] validationErr {
objectValidationErr ( fieldSpec , validationErr {
Field : fieldName ,
Msg : "duplicate name: " + nameRef . String ( ) ,
} ) ,
}
}
uniqNames [ name ] = true
return identity , nil
}
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) getRefWithKnownEnvs ( r Resource , field string ) * references {
2020-02-06 17:28:04 +00:00
nameRef := r . references ( field )
2020-02-06 05:42:01 +00:00
if v , ok := p . mEnvVals [ nameRef . EnvRef ] ; ok {
nameRef . val = v
}
return nameRef
}
2020-06-30 21:54:00 +00:00
func ( p * Template ) setRefs ( refs ... * references ) {
2020-02-05 00:15:20 +00:00
for _ , ref := range refs {
if ref . Secret != "" {
p . mSecrets [ ref . Secret ] = false
}
if ref . EnvRef != "" {
2020-07-28 18:27:52 +00:00
p . mEnv [ ref . EnvRef ] = p . mEnvVals [ ref . EnvRef ] != nil
2020-02-05 00:15:20 +00:00
}
}
}
2020-11-25 18:14:16 +00:00
func parseAxis ( ra Resource , domain [ ] float64 ) * axis {
return & axis {
Base : ra . stringShort ( fieldAxisBase ) ,
Label : ra . stringShort ( fieldAxisLabel ) ,
Name : ra . Name ( ) ,
Prefix : ra . stringShort ( fieldPrefix ) ,
Scale : ra . stringShort ( fieldAxisScale ) ,
Suffix : ra . stringShort ( fieldSuffix ) ,
Domain : domain ,
}
}
func parseColor ( rc Resource ) * color {
return & color {
ID : rc . stringShort ( "id" ) ,
Name : rc . Name ( ) ,
Type : rc . stringShort ( fieldType ) ,
Hex : rc . stringShort ( fieldColorHex ) ,
Value : flt64Ptr ( rc . float64Short ( fieldValue ) ) ,
}
}
2020-07-30 18:26:17 +00:00
func ( p * Template ) parseChart ( dashMetaName string , chartIdx int , r Resource ) ( * chart , [ ] validationErr ) {
2019-11-01 18:11:42 +00:00
ck , err := r . chartKind ( )
if err != nil {
2020-07-30 18:26:17 +00:00
return nil , [ ] validationErr { {
2020-03-18 22:54:02 +00:00
Field : fieldKind ,
2019-11-01 18:11:42 +00:00
Msg : err . Error ( ) ,
} }
}
c := chart {
2020-09-21 18:02:51 +00:00
Kind : ck ,
Name : r . Name ( ) ,
BinSize : r . intShort ( fieldChartBinSize ) ,
BinCount : r . intShort ( fieldChartBinCount ) ,
Geom : r . stringShort ( fieldChartGeom ) ,
Height : r . intShort ( fieldChartHeight ) ,
Note : r . stringShort ( fieldChartNote ) ,
NoteOnEmpty : r . boolShort ( fieldChartNoteOnEmpty ) ,
Position : r . stringShort ( fieldChartPosition ) ,
Prefix : r . stringShort ( fieldPrefix ) ,
Shade : r . boolShort ( fieldChartShade ) ,
HoverDimension : r . stringShort ( fieldChartHoverDimension ) ,
Suffix : r . stringShort ( fieldSuffix ) ,
TickPrefix : r . stringShort ( fieldChartTickPrefix ) ,
TickSuffix : r . stringShort ( fieldChartTickSuffix ) ,
TimeFormat : r . stringShort ( fieldChartTimeFormat ) ,
Width : r . intShort ( fieldChartWidth ) ,
XCol : r . stringShort ( fieldChartXCol ) ,
2020-10-29 17:59:25 +00:00
GenerateXAxisTicks : r . slcStr ( fieldChartGenerateXAxisTicks ) ,
2020-10-27 23:20:35 +00:00
XTotalTicks : r . intShort ( fieldChartXTotalTicks ) ,
XTickStart : r . float64Short ( fieldChartXTickStart ) ,
XTickStep : r . float64Short ( fieldChartXTickStep ) ,
2020-09-21 18:02:51 +00:00
YCol : r . stringShort ( fieldChartYCol ) ,
2020-10-29 17:59:25 +00:00
GenerateYAxisTicks : r . slcStr ( fieldChartGenerateYAxisTicks ) ,
2020-10-27 23:20:35 +00:00
YTotalTicks : r . intShort ( fieldChartYTotalTicks ) ,
YTickStart : r . float64Short ( fieldChartYTickStart ) ,
YTickStep : r . float64Short ( fieldChartYTickStep ) ,
2020-09-21 18:02:51 +00:00
XPos : r . intShort ( fieldChartXPos ) ,
YPos : r . intShort ( fieldChartYPos ) ,
FillColumns : r . slcStr ( fieldChartFillColumns ) ,
2021-02-16 02:04:31 +00:00
YLabelColumnSeparator : r . stringShort ( fieldChartYLabelColumnSeparator ) ,
YLabelColumns : r . slcStr ( fieldChartYLabelColumns ) ,
2020-09-21 18:02:51 +00:00
YSeriesColumns : r . slcStr ( fieldChartYSeriesColumns ) ,
UpperColumn : r . stringShort ( fieldChartUpperColumn ) ,
MainColumn : r . stringShort ( fieldChartMainColumn ) ,
LowerColumn : r . stringShort ( fieldChartLowerColumn ) ,
2020-10-27 23:20:35 +00:00
LegendColorizeRows : r . boolShort ( fieldChartLegendColorizeRows ) ,
2021-05-25 17:31:50 +00:00
LegendHide : r . boolShort ( fieldChartLegendHide ) ,
2020-09-21 18:02:51 +00:00
LegendOpacity : r . float64Short ( fieldChartLegendOpacity ) ,
LegendOrientationThreshold : r . intShort ( fieldChartLegendOrientationThreshold ) ,
2020-11-25 18:14:16 +00:00
Zoom : r . float64Short ( fieldChartGeoZoom ) ,
Center : center { Lat : r . float64Short ( fieldChartGeoCenterLat ) , Lon : r . float64Short ( fieldChartGeoCenterLon ) } ,
MapStyle : r . stringShort ( fieldChartGeoMapStyle ) ,
AllowPanAndZoom : r . boolShort ( fieldChartGeoAllowPanAndZoom ) ,
DetectCoordinateFields : r . boolShort ( fieldChartGeoDetectCoordinateFields ) ,
2019-11-08 19:33:41 +00:00
}
2021-04-15 22:06:01 +00:00
if presStaticLeg , ok := r [ fieldChartStaticLegend ] . ( StaticLegend ) ; ok {
c . StaticLegend = presStaticLeg
2019-11-08 19:33:41 +00:00
} else {
2021-04-15 22:06:01 +00:00
if staticLeg , ok := ifaceToResource ( r [ fieldChartStaticLegend ] ) ; ok {
c . StaticLegend . ColorizeRows = staticLeg . boolShort ( fieldChartStaticLegendColorizeRows )
c . StaticLegend . HeightRatio = staticLeg . float64Short ( fieldChartStaticLegendHeightRatio )
2021-06-10 16:20:59 +00:00
c . StaticLegend . Show = staticLeg . boolShort ( fieldChartStaticLegendShow )
2021-04-15 22:06:01 +00:00
c . StaticLegend . Opacity = staticLeg . float64Short ( fieldChartStaticLegendOpacity )
c . StaticLegend . OrientationThreshold = staticLeg . intShort ( fieldChartStaticLegendOrientationThreshold )
c . StaticLegend . ValueAxis = staticLeg . stringShort ( fieldChartStaticLegendValueAxis )
c . StaticLegend . WidthRatio = staticLeg . float64Short ( fieldChartStaticLegendWidthRatio )
2019-11-08 19:33:41 +00:00
}
}
if dp , ok := r . int ( fieldChartDecimalPlaces ) ; ok {
2019-11-01 18:11:42 +00:00
c . EnforceDecimals = true
c . DecimalPlaces = dp
}
2019-11-22 01:07:12 +00:00
var failures [ ] validationErr
2019-11-08 19:33:41 +00:00
if presentQueries , ok := r [ fieldChartQueries ] . ( queries ) ; ok {
c . Queries = presentQueries
} else {
2020-07-30 18:26:17 +00:00
q , vErrs := p . parseChartQueries ( dashMetaName , chartIdx , r . slcResource ( fieldChartQueries ) )
if len ( vErrs ) > 0 {
failures = append ( failures , validationErr {
Field : "queries" ,
Nested : vErrs ,
2019-11-08 19:33:41 +00:00
} )
}
2020-07-30 18:26:17 +00:00
c . Queries = q
2019-11-01 18:11:42 +00:00
}
2019-11-08 19:33:41 +00:00
if presentColors , ok := r [ fieldChartColors ] . ( colors ) ; ok {
c . Colors = presentColors
} else {
for _ , rc := range r . slcResource ( fieldChartColors ) {
2020-11-25 18:14:16 +00:00
c . Colors = append ( c . Colors , parseColor ( rc ) )
2019-11-08 19:33:41 +00:00
}
2019-11-01 18:11:42 +00:00
}
2019-11-08 19:33:41 +00:00
if presAxes , ok := r [ fieldChartAxes ] . ( axes ) ; ok {
c . Axes = presAxes
} else {
for _ , ra := range r . slcResource ( fieldChartAxes ) {
2019-11-15 01:05:21 +00:00
domain := [ ] float64 { }
if _ , ok := ra [ fieldChartDomain ] ; ok {
for _ , str := range ra . slcStr ( fieldChartDomain ) {
val , err := strconv . ParseFloat ( str , 64 )
if err != nil {
2019-11-22 01:07:12 +00:00
failures = append ( failures , validationErr {
2019-11-15 01:05:21 +00:00
Field : "axes" ,
Msg : err . Error ( ) ,
} )
}
domain = append ( domain , val )
}
}
2020-11-25 18:14:16 +00:00
c . Axes = append ( c . Axes , * parseAxis ( ra , domain ) )
}
}
if presentGeoLayers , ok := r [ fieldChartGeoLayers ] . ( geoLayers ) ; ok {
c . GeoLayers = presentGeoLayers
} else {
parseGeoAxis := func ( r Resource , field string ) * axis {
if axis , ok := r [ field ] . ( * axis ) ; ok {
return axis
} else {
if leg , ok := ifaceToResource ( r [ field ] ) ; ok {
return parseAxis ( leg , nil )
}
}
return nil
}
for _ , rl := range r . slcResource ( fieldChartGeoLayers ) {
gl := geoLayer {
Type : rl . stringShort ( fieldChartGeoLayerType ) ,
RadiusField : rl . stringShort ( fieldChartGeoLayerRadiusField ) ,
ColorField : rl . stringShort ( fieldChartGeoLayerColorField ) ,
IntensityField : rl . stringShort ( fieldChartGeoLayerIntensityField ) ,
Radius : int32 ( rl . intShort ( fieldChartGeoLayerRadius ) ) ,
Blur : int32 ( rl . intShort ( fieldChartGeoLayerBlur ) ) ,
RadiusDimension : parseGeoAxis ( rl , fieldChartGeoLayerRadiusDimension ) ,
ColorDimension : parseGeoAxis ( rl , fieldChartGeoLayerColorDimension ) ,
IntensityDimension : parseGeoAxis ( rl , fieldChartGeoLayerIntensityDimension ) ,
InterpolateColors : rl . boolShort ( fieldChartGeoLayerInterpolateColors ) ,
TrackWidth : int32 ( rl . intShort ( fieldChartGeoLayerTrackWidth ) ) ,
Speed : int32 ( rl . intShort ( fieldChartGeoLayerSpeed ) ) ,
RandomColors : rl . boolShort ( fieldChartGeoLayerRandomColors ) ,
IsClustered : rl . boolShort ( fieldChartGeoLayerIsClustered ) ,
}
if presentColors , ok := rl [ fieldChartGeoLayerViewColors ] . ( colors ) ; ok {
gl . ViewColors = presentColors
} else {
for _ , rc := range rl . slcResource ( fieldChartGeoLayerViewColors ) {
gl . ViewColors = append ( gl . ViewColors , parseColor ( rc ) )
}
}
c . GeoLayers = append ( c . GeoLayers , & gl )
2019-11-08 19:33:41 +00:00
}
2019-11-04 19:16:32 +00:00
}
2020-03-04 19:11:55 +00:00
if tableOptsRes , ok := ifaceToResource ( r [ fieldChartTableOptions ] ) ; ok {
c . TableOptions = tableOptions {
VerticalTimeAxis : tableOptsRes . boolShort ( fieldChartTableOptionVerticalTimeAxis ) ,
SortByField : tableOptsRes . stringShort ( fieldChartTableOptionSortBy ) ,
Wrapping : tableOptsRes . stringShort ( fieldChartTableOptionWrapping ) ,
FixFirstColumn : tableOptsRes . boolShort ( fieldChartTableOptionFixFirstColumn ) ,
}
}
for _ , fieldOptRes := range r . slcResource ( fieldChartFieldOptions ) {
c . FieldOptions = append ( c . FieldOptions , fieldOption {
FieldName : fieldOptRes . stringShort ( fieldChartFieldOptionFieldName ) ,
DisplayName : fieldOptRes . stringShort ( fieldChartFieldOptionDisplayName ) ,
Visible : fieldOptRes . boolShort ( fieldChartFieldOptionVisible ) ,
} )
}
2019-11-15 17:17:31 +00:00
if failures = append ( failures , c . validProperties ( ) ... ) ; len ( failures ) > 0 {
2020-07-30 18:26:17 +00:00
return nil , failures
2019-11-01 18:11:42 +00:00
}
2020-07-30 18:26:17 +00:00
return & c , nil
}
func ( p * Template ) parseChartQueries ( dashMetaName string , chartIdx int , resources [ ] Resource ) ( queries , [ ] validationErr ) {
var (
q queries
vErrs [ ] validationErr
)
for i , rq := range resources {
source := rq . stringShort ( fieldQuery )
if source == "" {
continue
}
prefix := fmt . Sprintf ( "dashboards[%s].spec.charts[%d].queries[%d]" , dashMetaName , chartIdx , i )
2020-08-26 20:44:59 +00:00
qq , err := p . parseQuery ( prefix , source , rq . slcResource ( fieldParams ) , nil )
2020-07-30 18:26:17 +00:00
if err != nil {
vErrs = append ( vErrs , validationErr {
Field : "query" ,
Index : intPtr ( i ) ,
Msg : err . Error ( ) ,
} )
}
q = append ( q , qq )
}
return q , vErrs
}
2020-08-26 20:44:59 +00:00
func ( p * Template ) parseQuery ( prefix , source string , params , task [ ] Resource ) ( query , error ) {
2020-07-30 18:26:17 +00:00
files := parser . ParseSource ( source ) . Files
if len ( files ) != 1 {
2021-03-30 18:10:02 +00:00
return query { } , influxErr ( errors2 . EInvalid , "invalid query source" )
2020-07-30 18:26:17 +00:00
}
q := query {
Query : strings . TrimSpace ( source ) ,
}
2020-08-26 20:44:59 +00:00
mParams := make ( map [ string ] * references )
tParams := make ( map [ string ] * references )
paramsOpt , paramsErr := edit . GetOption ( files [ 0 ] , "params" )
taskOpt , taskErr := edit . GetOption ( files [ 0 ] , "task" )
if paramsErr != nil && taskErr != nil {
2020-07-30 18:26:17 +00:00
return q , nil
}
2020-08-26 20:44:59 +00:00
if paramsErr == nil {
obj , ok := paramsOpt . ( * ast . ObjectExpression )
if ok {
for _ , p := range obj . Properties {
sl , ok := p . Key . ( * ast . Identifier )
if ! ok {
continue
}
mParams [ sl . Name ] = & references {
EnvRef : sl . Name ,
defaultVal : valFromExpr ( p . Value ) ,
valType : p . Value . Type ( ) ,
}
}
2020-07-30 18:26:17 +00:00
}
2020-08-26 20:44:59 +00:00
}
if taskErr == nil {
tobj , ok := taskOpt . ( * ast . ObjectExpression )
if ok {
for _ , p := range tobj . Properties {
sl , ok := p . Key . ( * ast . Identifier )
if ! ok {
continue
}
2020-07-30 18:26:17 +00:00
2020-08-26 20:44:59 +00:00
tParams [ sl . Name ] = & references {
EnvRef : sl . Name ,
defaultVal : valFromExpr ( p . Value ) ,
valType : p . Value . Type ( ) ,
}
}
2020-07-30 18:26:17 +00:00
}
}
2020-08-26 20:44:59 +00:00
// override defaults here maybe?
2020-07-30 18:26:17 +00:00
for _ , pr := range params {
field := pr . stringShort ( fieldKey )
if field == "" {
continue
}
if _ , ok := mParams [ field ] ; ! ok {
mParams [ field ] = & references { EnvRef : field }
}
if def , ok := pr [ fieldDefault ] ; ok {
mParams [ field ] . defaultVal = def
}
if valtype , ok := pr . string ( fieldType ) ; ok {
mParams [ field ] . valType = valtype
}
}
2020-08-26 20:44:59 +00:00
var err error
for _ , pr := range task {
field := pr . stringShort ( fieldKey )
if field == "" {
continue
}
if _ , ok := tParams [ field ] ; ! ok {
tParams [ field ] = & references { EnvRef : field }
}
if valtype , ok := pr . string ( fieldType ) ; ok {
tParams [ field ] . valType = valtype
}
if def , ok := pr [ fieldDefault ] ; ok {
switch tParams [ field ] . valType {
case "duration" :
switch defDur := def . ( type ) {
case string :
tParams [ field ] . defaultVal , err = time . ParseDuration ( defDur )
if err != nil {
2021-03-30 18:10:02 +00:00
return query { } , influxErr ( errors2 . EInvalid , err . Error ( ) )
2020-08-26 20:44:59 +00:00
}
case time . Duration :
tParams [ field ] . defaultVal = defDur
}
default :
tParams [ field ] . defaultVal = def
}
}
}
2020-07-30 18:26:17 +00:00
for _ , ref := range mParams {
envRef := fmt . Sprintf ( "%s.params.%s" , prefix , ref . EnvRef )
q . params = append ( q . params , & references {
EnvRef : envRef ,
defaultVal : ref . defaultVal ,
val : p . mEnvVals [ envRef ] ,
valType : ref . valType ,
} )
}
2020-08-26 20:44:59 +00:00
for _ , ref := range tParams {
envRef := fmt . Sprintf ( "%s.task.%s" , prefix , ref . EnvRef )
q . task = append ( q . task , & references {
EnvRef : envRef ,
defaultVal : ref . defaultVal ,
val : p . mEnvVals [ envRef ] ,
valType : ref . valType ,
} )
}
2020-07-30 18:26:17 +00:00
return q , nil
}
func valFromExpr ( p ast . Expression ) interface { } {
switch literal := p . ( type ) {
case * ast . CallExpression :
sl , ok := literal . Callee . ( * ast . Identifier )
if ok && sl . Name == "now" {
return "now()"
}
return nil
case * ast . DateTimeLiteral :
2020-09-17 00:59:15 +00:00
return ast . DateTimeFromLiteral ( literal )
2020-07-30 18:26:17 +00:00
case * ast . FloatLiteral :
2020-09-17 00:59:15 +00:00
return ast . FloatFromLiteral ( literal )
2020-07-30 18:26:17 +00:00
case * ast . IntegerLiteral :
2020-09-17 00:59:15 +00:00
return ast . IntegerFromLiteral ( literal )
2020-07-30 18:26:17 +00:00
case * ast . DurationLiteral :
dur , _ := ast . DurationFrom ( literal , time . Time { } )
return dur
case * ast . StringLiteral :
2020-09-17 00:59:15 +00:00
return ast . StringFromLiteral ( literal )
2020-07-30 18:26:17 +00:00
case * ast . UnaryExpression :
// a signed duration is represented by a UnaryExpression.
// it is the only unary expression allowed.
v := valFromExpr ( literal . Argument )
if dur , ok := v . ( time . Duration ) ; ok {
switch literal . Operator {
case ast . SubtractionOperator :
return "-" + dur . String ( )
}
}
return v
default :
return nil
}
2019-11-01 18:11:42 +00:00
}
2020-05-03 17:34:24 +00:00
// dns1123LabelMaxLength is a label's max length in DNS (RFC 1123)
const dns1123LabelMaxLength int = 63
const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
const dns1123LabelErrMsg string = "a DNS-1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character"
var dns1123LabelRegexp = regexp . MustCompile ( "^" + dns1123LabelFmt + "$" )
// isDNS1123Label tests for a string that conforms to the definition of a label in
// DNS (RFC 1123).
func isDNS1123Label ( value string ) [ ] string {
var errs [ ] string
if len ( value ) > dns1123LabelMaxLength {
errs = append ( errs , fmt . Sprintf ( "must be no more than %d characters" , dns1123LabelMaxLength ) )
}
if ! dns1123LabelRegexp . MatchString ( value ) {
errs = append ( errs , regexError ( dns1123LabelErrMsg , dns1123LabelFmt , "my-name" , "123-abc" ) )
}
return errs
}
// regexError returns a string explanation of a regex validation failure.
func regexError ( msg string , fmt string , examples ... string ) string {
if len ( examples ) == 0 {
return msg + " (regex used for validation is '" + fmt + "')"
}
msg += " (e.g. "
for i := range examples {
if i > 0 {
msg += " or "
}
msg += "'" + examples [ i ] + "', "
}
msg += "regex used for validation is '" + fmt + "')"
return msg
}
2019-10-23 17:09:04 +00:00
// Resource is a pkger Resource kind. It can be one of any of
// available kinds that are supported.
type Resource map [ string ] interface { }
2019-11-08 19:33:41 +00:00
// Name returns the name of the resource.
2019-11-01 18:11:42 +00:00
func ( r Resource ) Name ( ) string {
2019-11-08 19:33:41 +00:00
return strings . TrimSpace ( r . stringShort ( fieldName ) )
2019-11-01 18:11:42 +00:00
}
2019-11-08 19:33:41 +00:00
func ( r Resource ) kind ( ) ( Kind , error ) {
2019-11-21 00:38:12 +00:00
if k , ok := r [ fieldKind ] . ( Kind ) ; ok {
return k , k . OK ( )
}
2019-11-08 19:33:41 +00:00
resKind , ok := r . string ( fieldKind )
2019-10-23 17:09:04 +00:00
if ! ok {
2019-11-08 19:33:41 +00:00
return KindUnknown , errors . New ( "no kind provided" )
2019-10-23 17:09:04 +00:00
}
2020-01-13 19:13:37 +00:00
k := Kind ( resKind )
2019-11-21 00:38:12 +00:00
return k , k . OK ( )
2019-10-23 17:09:04 +00:00
}
2019-11-05 22:08:30 +00:00
func ( r Resource ) chartKind ( ) ( chartKind , error ) {
2019-11-01 18:11:42 +00:00
ck , _ := r . kind ( )
2020-01-13 19:13:37 +00:00
chartKind := chartKind ( normStr ( string ( ck ) ) )
2019-11-01 18:11:42 +00:00
if ! chartKind . ok ( ) {
2019-11-05 22:08:30 +00:00
return chartKindUnknown , errors . New ( "invalid chart kind provided: " + string ( chartKind ) )
2019-11-01 18:11:42 +00:00
}
return chartKind , nil
2019-10-23 17:09:04 +00:00
}
2019-11-01 18:11:42 +00:00
func ( r Resource ) bool ( key string ) ( bool , bool ) {
b , ok := r [ key ] . ( bool )
return b , ok
}
func ( r Resource ) boolShort ( key string ) bool {
b , _ := r . bool ( key )
return b
}
2019-12-18 01:57:44 +00:00
func ( r Resource ) duration ( key string ) ( time . Duration , bool ) {
2020-10-09 20:17:04 +00:00
astDur , err := options . ParseSignedDuration ( r . stringShort ( key ) )
if err != nil {
return time . Duration ( 0 ) , false
}
dur , err := ast . DurationFrom ( astDur , time . Time { } )
2019-12-18 01:57:44 +00:00
return dur , err == nil
}
func ( r Resource ) durationShort ( key string ) time . Duration {
dur , _ := r . duration ( key )
return dur
}
2019-11-01 18:11:42 +00:00
func ( r Resource ) float64 ( key string ) ( float64 , bool ) {
f , ok := r [ key ] . ( float64 )
if ok {
return f , true
}
i , ok := r [ key ] . ( int )
if ok {
return float64 ( i ) , true
}
return 0 , false
}
func ( r Resource ) float64Short ( key string ) float64 {
f , _ := r . float64 ( key )
return f
}
func ( r Resource ) int ( key string ) ( int , bool ) {
i , ok := r [ key ] . ( int )
if ok {
return i , true
}
f , ok := r [ key ] . ( float64 )
if ok {
return int ( f ) , true
}
return 0 , false
}
func ( r Resource ) intShort ( key string ) int {
i , _ := r . int ( key )
return i
}
2020-02-05 00:15:20 +00:00
func ( r Resource ) references ( key string ) * references {
2019-12-16 17:39:55 +00:00
v , ok := r [ key ]
if ! ok {
2020-02-05 00:15:20 +00:00
return & references { }
2019-12-16 17:39:55 +00:00
}
2020-06-22 22:44:53 +00:00
return ifaceToReference ( v )
2019-12-16 17:39:55 +00:00
}
2019-10-23 17:09:04 +00:00
func ( r Resource ) string ( key string ) ( string , bool ) {
2019-11-06 22:41:06 +00:00
return ifaceToStr ( r [ key ] )
2019-10-23 17:09:04 +00:00
}
func ( r Resource ) stringShort ( key string ) string {
s , _ := r . string ( key )
return s
}
2019-11-01 18:11:42 +00:00
func ( r Resource ) slcResource ( key string ) [ ] Resource {
v , ok := r [ key ]
if ! ok {
return nil
}
2019-11-08 19:33:41 +00:00
if resources , ok := v . ( [ ] Resource ) ; ok {
return resources
}
2019-11-01 18:11:42 +00:00
iFaceSlc , ok := v . ( [ ] interface { } )
if ! ok {
return nil
}
var newResources [ ] Resource
for _ , iFace := range iFaceSlc {
2019-11-06 22:41:06 +00:00
r , ok := ifaceToResource ( iFace )
2019-11-01 18:11:42 +00:00
if ! ok {
continue
}
newResources = append ( newResources , r )
}
return newResources
}
2019-11-06 22:41:06 +00:00
func ( r Resource ) slcStr ( key string ) [ ] string {
v , ok := r [ key ]
if ! ok {
return nil
}
2019-11-08 19:33:41 +00:00
if strSlc , ok := v . ( [ ] string ) ; ok {
return strSlc
}
2019-11-06 22:41:06 +00:00
iFaceSlc , ok := v . ( [ ] interface { } )
if ! ok {
return nil
}
var out [ ] string
for _ , iface := range iFaceSlc {
s , ok := ifaceToStr ( iface )
if ! ok {
continue
}
out = append ( out , s )
}
return out
}
func ( r Resource ) mapStrStr ( key string ) map [ string ] string {
2019-11-08 19:33:41 +00:00
v , ok := r [ key ]
if ! ok {
return nil
}
if m , ok := v . ( map [ string ] string ) ; ok {
return m
}
res , ok := ifaceToResource ( v )
2019-11-06 22:41:06 +00:00
if ! ok {
return nil
}
m := make ( map [ string ] string )
for k , v := range res {
s , ok := ifaceToStr ( v )
if ! ok {
continue
}
m [ k ] = s
}
return m
}
func ifaceToResource ( i interface { } ) ( Resource , bool ) {
if i == nil {
return nil , false
}
2019-11-08 19:33:41 +00:00
if res , ok := i . ( Resource ) ; ok {
2019-10-26 02:11:47 +00:00
return res , true
}
if m , ok := i . ( map [ string ] interface { } ) ; ok {
return m , true
}
2019-10-23 17:09:04 +00:00
m , ok := i . ( map [ interface { } ] interface { } )
if ! ok {
return nil , false
}
newRes := make ( Resource )
for k , v := range m {
s , ok := k . ( string )
if ! ok {
continue
}
newRes [ s ] = v
}
return newRes , true
}
2020-06-22 22:44:53 +00:00
func ifaceToReference ( i interface { } ) * references {
var ref references
for _ , f := range [ ] string { fieldReferencesSecret , fieldReferencesEnv } {
resBody , ok := ifaceToResource ( i )
if ! ok {
continue
}
if keyRes , ok := ifaceToResource ( resBody [ f ] ) ; ok {
switch f {
case fieldReferencesEnv :
ref . EnvRef = keyRes . stringShort ( fieldKey )
2020-07-28 18:27:52 +00:00
ref . defaultVal = keyRes [ fieldDefault ]
2020-06-22 22:44:53 +00:00
case fieldReferencesSecret :
ref . Secret = keyRes . stringShort ( fieldKey )
}
}
}
if ref . hasValue ( ) {
return & ref
}
return & references { val : i }
}
2019-11-06 22:41:06 +00:00
func ifaceToStr ( v interface { } ) ( string , bool ) {
if v == nil {
return "" , false
}
if s , ok := v . ( string ) ; ok {
return s , true
}
if i , ok := v . ( int ) ; ok {
return strconv . Itoa ( i ) , true
}
if f , ok := v . ( float64 ) ; ok {
return strconv . FormatFloat ( f , 'f' , - 1 , 64 ) , true
}
return "" , false
}
2019-11-22 01:07:12 +00:00
// ParseError is the error from parsing the given package. The ParseError
// behavior provides a list of resources that failed and all validations
// that failed for that resource. A resource can multiple errors, and
// a parseErr can have multiple resources which themselves can have
// multiple validation failures.
type ParseError interface {
ValidationErrs ( ) [ ] ValidationErr
}
2019-12-12 19:09:32 +00:00
// NewParseError creates a new parse error from existing validation errors.
func NewParseError ( errs ... ValidationErr ) error {
if len ( errs ) == 0 {
return nil
}
return & parseErr { rawErrs : errs }
}
2019-11-14 00:24:05 +00:00
type (
2019-11-22 01:07:12 +00:00
parseErr struct {
Resources [ ] resourceErr
2019-12-12 19:09:32 +00:00
rawErrs [ ] ValidationErr
2019-11-14 00:24:05 +00:00
}
2019-11-22 01:07:12 +00:00
// resourceErr describes the error for a particular resource. In
2019-11-14 00:43:28 +00:00
// which it may have numerous validation and association errors.
2019-11-22 01:07:12 +00:00
resourceErr struct {
2019-11-01 18:11:42 +00:00
Kind string
2019-11-22 01:07:12 +00:00
Idx * int
RootErrs [ ] validationErr
AssociationErrs [ ] validationErr
ValidationErrs [ ] validationErr
2019-10-23 17:09:04 +00:00
}
2019-11-14 00:24:05 +00:00
2019-11-22 01:07:12 +00:00
validationErr struct {
2019-11-14 00:24:05 +00:00
Field string
Msg string
Index * int
2019-11-22 01:07:12 +00:00
Nested [ ] validationErr
2019-11-14 00:24:05 +00:00
}
)
2019-10-23 17:09:04 +00:00
// Error implements the error interface.
2019-11-22 01:07:12 +00:00
func ( e * parseErr ) Error ( ) string {
2020-06-29 23:38:52 +00:00
var (
errMsg [ ] string
seenErrs = make ( map [ string ] bool )
)
2019-12-12 19:09:32 +00:00
for _ , ve := range append ( e . ValidationErrs ( ) , e . rawErrs ... ) {
2020-06-29 23:38:52 +00:00
msg := ve . Error ( )
if seenErrs [ msg ] {
continue
}
seenErrs [ msg ] = true
2019-11-22 01:07:12 +00:00
errMsg = append ( errMsg , ve . Error ( ) )
}
return strings . Join ( errMsg , "\n\t" )
}
func ( e * parseErr ) ValidationErrs ( ) [ ] ValidationErr {
2019-12-12 19:09:32 +00:00
errs := e . rawErrs [ : ]
2019-10-23 17:09:04 +00:00
for _ , r := range e . Resources {
2019-11-22 01:07:12 +00:00
rootErr := ValidationErr {
Kind : r . Kind ,
2019-10-23 17:09:04 +00:00
}
2019-11-22 01:07:12 +00:00
for _ , v := range r . RootErrs {
errs = append ( errs , traverseErrs ( rootErr , v ) ... )
2019-10-26 02:11:47 +00:00
}
2019-11-22 01:07:12 +00:00
rootErr . Indexes = [ ] * int { r . Idx }
2020-01-24 19:25:03 +00:00
rootErr . Fields = [ ] string { "root" }
2019-11-22 01:07:12 +00:00
for _ , v := range append ( r . ValidationErrs , r . AssociationErrs ... ) {
errs = append ( errs , traverseErrs ( rootErr , v ) ... )
2019-10-23 17:09:04 +00:00
}
}
2019-12-21 23:57:41 +00:00
// used to provide a means to == or != in the map lookup
// to remove duplicate errors
type key struct {
kind string
fields string
indexes string
reason string
}
m := make ( map [ key ] bool )
var out [ ] ValidationErr
for _ , verr := range errs {
k := key {
kind : verr . Kind ,
fields : strings . Join ( verr . Fields , ":" ) ,
reason : verr . Reason ,
}
var indexes [ ] string
for _ , idx := range verr . Indexes {
if idx == nil {
continue
}
indexes = append ( indexes , strconv . Itoa ( * idx ) )
}
k . indexes = strings . Join ( indexes , ":" )
if m [ k ] {
continue
}
m [ k ] = true
out = append ( out , verr )
}
return out
2019-11-22 01:07:12 +00:00
}
// ValidationErr represents an error during the parsing of a package.
type ValidationErr struct {
Kind string ` json:"kind" yaml:"kind" `
Fields [ ] string ` json:"fields" yaml:"fields" `
Indexes [ ] * int ` json:"idxs" yaml:"idxs" `
Reason string ` json:"reason" yaml:"reason" `
}
2019-10-23 17:09:04 +00:00
2019-11-22 01:07:12 +00:00
func ( v ValidationErr ) Error ( ) string {
fieldPairs := make ( [ ] string , 0 , len ( v . Fields ) )
for i , idx := range v . Indexes {
field := v . Fields [ i ]
if idx == nil || * idx == - 1 {
fieldPairs = append ( fieldPairs , field )
continue
}
fieldPairs = append ( fieldPairs , fmt . Sprintf ( "%s[%d]" , field , * idx ) )
}
return fmt . Sprintf ( "kind=%s field=%s reason=%q" , v . Kind , strings . Join ( fieldPairs , "." ) , v . Reason )
}
func traverseErrs ( root ValidationErr , vErr validationErr ) [ ] ValidationErr {
root . Fields = append ( root . Fields , vErr . Field )
root . Indexes = append ( root . Indexes , vErr . Index )
if len ( vErr . Nested ) == 0 {
root . Reason = vErr . Msg
return [ ] ValidationErr { root }
}
var errs [ ] ValidationErr
for _ , n := range vErr . Nested {
errs = append ( errs , traverseErrs ( root , n ) ... )
}
return errs
2019-10-23 17:09:04 +00:00
}
2019-11-22 01:07:12 +00:00
func ( e * parseErr ) append ( errs ... resourceErr ) {
2019-11-14 00:24:05 +00:00
e . Resources = append ( e . Resources , errs ... )
2019-10-23 17:09:04 +00:00
}
// IsParseErr inspects a given error to determine if it is
2019-11-22 01:07:12 +00:00
// a parseErr. If a parseErr it is, it will return it along
// with the confirmation boolean. If the error is not a parseErr
// it will return nil values for the parseErr, making it unsafe
2019-10-23 17:09:04 +00:00
// to use.
2019-11-14 00:43:28 +00:00
func IsParseErr ( err error ) bool {
2019-12-21 23:57:41 +00:00
if _ , ok := err . ( * parseErr ) ; ok {
return true
}
2021-03-30 18:10:02 +00:00
iErr , ok := err . ( * errors2 . Error )
2019-12-21 23:57:41 +00:00
if ! ok {
return false
}
return IsParseErr ( iErr . Err )
2019-10-23 17:09:04 +00:00
}
2019-12-10 22:51:11 +00:00
2020-03-16 18:25:39 +00:00
func objectValidationErr ( field string , vErrs ... validationErr ) validationErr {
return validationErr {
Field : field ,
Nested : vErrs ,
}
}
2019-12-10 22:51:11 +00:00
func normStr ( s string ) string {
return strings . TrimSpace ( strings . ToLower ( s ) )
}