feat(pkger): add support for variable resource kind to pkger
parent
591f2870d8
commit
d252b20ecc
122
pkger/models.go
122
pkger/models.go
|
@ -13,17 +13,27 @@ const (
|
|||
kindDashboard kind = "dashboard"
|
||||
kindLabel kind = "label"
|
||||
kindPackage kind = "package"
|
||||
kindVariable kind = "variable"
|
||||
)
|
||||
|
||||
var kinds = map[kind]bool{
|
||||
kindBucket: true,
|
||||
kindDashboard: true,
|
||||
kindLabel: true,
|
||||
kindPackage: true,
|
||||
kindVariable: true,
|
||||
}
|
||||
|
||||
type kind string
|
||||
|
||||
func (k kind) String() string {
|
||||
switch k {
|
||||
case kindBucket, kindLabel, kindDashboard, kindPackage:
|
||||
if kinds[k] {
|
||||
return string(k)
|
||||
default:
|
||||
}
|
||||
if k == kindUnknown {
|
||||
return "unknown"
|
||||
}
|
||||
return string(k)
|
||||
}
|
||||
|
||||
// SafeID is an equivalent influxdb.ID that encodes safely with
|
||||
|
@ -160,6 +170,7 @@ type Summary struct {
|
|||
Dashboards []SummaryDashboard `json:"dashboards"`
|
||||
Labels []SummaryLabel `json:"labels"`
|
||||
LabelMappings []SummaryLabelMapping `json:"labelMappings"`
|
||||
Variables []SummaryVariable `json:"variables"`
|
||||
}
|
||||
|
||||
// SummaryBucket provides a summary of a pkg bucket.
|
||||
|
@ -226,6 +237,11 @@ type SummaryLabelMapping struct {
|
|||
influxdb.LabelMapping
|
||||
}
|
||||
|
||||
// SummaryVariable provides a summary of a pkg variable.
|
||||
type SummaryVariable struct {
|
||||
influxdb.Variable
|
||||
}
|
||||
|
||||
type bucket struct {
|
||||
id influxdb.ID
|
||||
OrgID influxdb.ID
|
||||
|
@ -275,17 +291,17 @@ func (b *bucket) shouldApply() bool {
|
|||
b.RetentionPeriod != b.existing.RetentionPeriod
|
||||
}
|
||||
|
||||
type labelMapKey struct {
|
||||
type assocMapKey struct {
|
||||
resType influxdb.ResourceType
|
||||
name string
|
||||
}
|
||||
|
||||
type labelMapVal struct {
|
||||
type assocMapVal struct {
|
||||
exists bool
|
||||
v interface{}
|
||||
}
|
||||
|
||||
func (l labelMapVal) bucket() (*bucket, bool) {
|
||||
func (l assocMapVal) bucket() (*bucket, bool) {
|
||||
if l.v == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
@ -293,7 +309,7 @@ func (l labelMapVal) bucket() (*bucket, bool) {
|
|||
return b, ok
|
||||
}
|
||||
|
||||
func (l labelMapVal) dashboard() (*dashboard, bool) {
|
||||
func (l assocMapVal) dashboard() (*dashboard, bool) {
|
||||
if l.v == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
@ -308,7 +324,7 @@ type label struct {
|
|||
Color string
|
||||
Description string
|
||||
|
||||
mappings map[labelMapKey]labelMapVal
|
||||
mappings map[assocMapKey]assocMapVal
|
||||
|
||||
// exists provides context for a resource that already
|
||||
// exists in the platform. If a resource already exists(exists=true)
|
||||
|
@ -359,7 +375,7 @@ func (l *label) mappingSummary() []SummaryLabelMapping {
|
|||
return mappings
|
||||
}
|
||||
|
||||
func (l *label) getMappedResourceID(k labelMapKey) influxdb.ID {
|
||||
func (l *label) getMappedResourceID(k assocMapKey) influxdb.ID {
|
||||
switch k.resType {
|
||||
case influxdb.BucketsResourceType:
|
||||
b, ok := l.mappings[k].bucket()
|
||||
|
@ -380,14 +396,14 @@ func (l *label) setBucketMapping(b *bucket, exists bool) {
|
|||
return
|
||||
}
|
||||
if l.mappings == nil {
|
||||
l.mappings = make(map[labelMapKey]labelMapVal)
|
||||
l.mappings = make(map[assocMapKey]assocMapVal)
|
||||
}
|
||||
|
||||
key := labelMapKey{
|
||||
key := assocMapKey{
|
||||
resType: influxdb.BucketsResourceType,
|
||||
name: b.Name,
|
||||
}
|
||||
l.mappings[key] = labelMapVal{
|
||||
l.mappings[key] = assocMapVal{
|
||||
exists: exists,
|
||||
v: b,
|
||||
}
|
||||
|
@ -398,14 +414,14 @@ func (l *label) setDashboardMapping(d *dashboard) {
|
|||
return
|
||||
}
|
||||
if l.mappings == nil {
|
||||
l.mappings = make(map[labelMapKey]labelMapVal)
|
||||
l.mappings = make(map[assocMapKey]assocMapVal)
|
||||
}
|
||||
|
||||
key := labelMapKey{
|
||||
key := assocMapKey{
|
||||
resType: d.ResourceType(),
|
||||
name: d.Name,
|
||||
}
|
||||
l.mappings[key] = labelMapVal{v: d}
|
||||
l.mappings[key] = assocMapVal{v: d}
|
||||
}
|
||||
|
||||
func (l *label) properties() map[string]string {
|
||||
|
@ -428,6 +444,82 @@ func toInfluxLabels(labels ...*label) []influxdb.Label {
|
|||
return iLabels
|
||||
}
|
||||
|
||||
type variable struct {
|
||||
id influxdb.ID
|
||||
OrgID influxdb.ID
|
||||
Name string
|
||||
Description string
|
||||
Type string
|
||||
Query string
|
||||
Language string
|
||||
ConstValues []string
|
||||
MapValues map[string]string
|
||||
|
||||
mappings map[assocMapKey]assocMapVal
|
||||
}
|
||||
|
||||
func (v *variable) summarize() SummaryVariable {
|
||||
args := &influxdb.VariableArguments{
|
||||
Type: v.Type,
|
||||
}
|
||||
switch args.Type {
|
||||
case "query":
|
||||
args.Values = influxdb.VariableQueryValues{
|
||||
Query: v.Query,
|
||||
Language: v.Language,
|
||||
}
|
||||
case "constant":
|
||||
args.Values = influxdb.VariableConstantValues(v.ConstValues)
|
||||
case "map":
|
||||
args.Values = influxdb.VariableMapValues(v.MapValues)
|
||||
}
|
||||
|
||||
return SummaryVariable{
|
||||
Variable: influxdb.Variable{
|
||||
ID: v.id,
|
||||
OrganizationID: v.OrgID,
|
||||
Name: v.Name,
|
||||
Description: v.Description,
|
||||
Arguments: args,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v *variable) valid() []failure {
|
||||
var failures []failure
|
||||
switch v.Type {
|
||||
case "map":
|
||||
if len(v.MapValues) == 0 {
|
||||
failures = append(failures, failure{
|
||||
Field: "values",
|
||||
Msg: "map variable must have at least 1 key/val pair",
|
||||
})
|
||||
}
|
||||
case "constant":
|
||||
if len(v.ConstValues) == 0 {
|
||||
failures = append(failures, failure{
|
||||
Field: "values",
|
||||
Msg: "constant variable must have a least 1 value provided",
|
||||
})
|
||||
}
|
||||
case "query":
|
||||
if v.Query == "" {
|
||||
failures = append(failures, failure{
|
||||
Field: "query",
|
||||
Msg: "query variable must provide a query string",
|
||||
})
|
||||
}
|
||||
if v.Language != "influxql" && v.Language != "flux" {
|
||||
const msgFmt = "query variable language must be either %q or %q; got %q"
|
||||
failures = append(failures, failure{
|
||||
Field: "language",
|
||||
Msg: fmt.Sprintf(msgFmt, "influxql", "flux", v.Language),
|
||||
})
|
||||
}
|
||||
}
|
||||
return failures
|
||||
}
|
||||
|
||||
type dashboard struct {
|
||||
id influxdb.ID
|
||||
OrgID influxdb.ID
|
||||
|
|
|
@ -94,8 +94,8 @@ func TestPkg(t *testing.T) {
|
|||
Name: "name2",
|
||||
Description: "desc2",
|
||||
Color: "blurple",
|
||||
mappings: map[labelMapKey]labelMapVal{
|
||||
labelMapKey{
|
||||
mappings: map[assocMapKey]assocMapVal{
|
||||
assocMapKey{
|
||||
resType: influxdb.BucketsResourceType,
|
||||
name: bucket1.Name,
|
||||
}: {
|
||||
|
|
155
pkger/parser.go
155
pkger/parser.go
|
@ -135,6 +135,7 @@ type Pkg struct {
|
|||
mLabels map[string]*label
|
||||
mBuckets map[string]*bucket
|
||||
mDashboards map[string]*dashboard
|
||||
mVariables map[string]*variable
|
||||
|
||||
isVerified bool // dry run has verified pkg resources with existing resources
|
||||
isParsed bool // indicates the pkg has been parsed and all resources graphed accordingly
|
||||
|
@ -146,10 +147,6 @@ type Pkg struct {
|
|||
func (p *Pkg) Summary() Summary {
|
||||
var sum Summary
|
||||
|
||||
for _, l := range p.labels() {
|
||||
sum.Labels = append(sum.Labels, l.summarize())
|
||||
}
|
||||
|
||||
for _, b := range p.buckets() {
|
||||
sum.Buckets = append(sum.Buckets, b.summarize())
|
||||
}
|
||||
|
@ -158,6 +155,10 @@ func (p *Pkg) Summary() Summary {
|
|||
sum.Dashboards = append(sum.Dashboards, d.summarize())
|
||||
}
|
||||
|
||||
for _, l := range p.labels() {
|
||||
sum.Labels = append(sum.Labels, l.summarize())
|
||||
}
|
||||
|
||||
for _, m := range p.labelMappings() {
|
||||
sum.LabelMappings = append(sum.LabelMappings, SummaryLabelMapping{
|
||||
ResourceName: m.ResourceName,
|
||||
|
@ -166,6 +167,10 @@ func (p *Pkg) Summary() Summary {
|
|||
})
|
||||
}
|
||||
|
||||
for _, v := range p.variables() {
|
||||
sum.Variables = append(sum.Variables, v.summarize())
|
||||
}
|
||||
|
||||
return sum
|
||||
}
|
||||
|
||||
|
@ -226,6 +231,19 @@ func (p *Pkg) dashboards() []*dashboard {
|
|||
return dashes
|
||||
}
|
||||
|
||||
func (p *Pkg) variables() []*variable {
|
||||
vars := make([]*variable, 0, len(p.mVariables))
|
||||
for _, v := range p.mVariables {
|
||||
vars = append(vars, v)
|
||||
}
|
||||
|
||||
sort.Slice(vars, func(i, j int) bool {
|
||||
return vars[i].Name < vars[j].Name
|
||||
})
|
||||
|
||||
return vars
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -293,7 +311,7 @@ func (p *Pkg) validMetadata() error {
|
|||
}
|
||||
|
||||
res := errResource{
|
||||
Kind: "Package",
|
||||
Kind: kindPackage.String(),
|
||||
Idx: -1,
|
||||
}
|
||||
for _, f := range failures {
|
||||
|
@ -332,6 +350,7 @@ func (p *Pkg) graphResources() error {
|
|||
graphFns := []func() error{
|
||||
// labels are first to validate associations with other resources
|
||||
p.graphLabels,
|
||||
p.graphVariables,
|
||||
p.graphBuckets,
|
||||
p.graphDashboards,
|
||||
}
|
||||
|
@ -467,6 +486,47 @@ func (p *Pkg) graphDashboards() error {
|
|||
})
|
||||
}
|
||||
|
||||
func (p *Pkg) graphVariables() error {
|
||||
p.mVariables = make(map[string]*variable)
|
||||
return p.eachResource(kindVariable, func(r Resource) []failure {
|
||||
if r.Name() == "" {
|
||||
return []failure{{
|
||||
Field: "name",
|
||||
Msg: "must be provided",
|
||||
}}
|
||||
}
|
||||
|
||||
if _, ok := p.mVariables[r.Name()]; ok {
|
||||
return []failure{{
|
||||
Field: "name",
|
||||
Msg: "duplicate name: " + r.Name(),
|
||||
}}
|
||||
}
|
||||
|
||||
newVar := &variable{
|
||||
Name: r.Name(),
|
||||
Description: r.stringShort("description"),
|
||||
Type: strings.ToLower(r.stringShort("type")),
|
||||
Query: strings.TrimSpace(r.stringShort("query")),
|
||||
Language: strings.ToLower(strings.TrimSpace(r.stringShort("language"))),
|
||||
ConstValues: r.slcStr("values"),
|
||||
MapValues: r.mapStrStr("values"),
|
||||
}
|
||||
|
||||
p.mVariables[r.Name()] = newVar
|
||||
|
||||
// here we set the var on the var map and return fails
|
||||
// reaons for this is we could end up providing bad
|
||||
// errors to the user if we dont' set it b/c of a bad
|
||||
// query or something, and its being referenced by a
|
||||
// dashboard or something. The var exists, its just
|
||||
// invalid. So the mapping is correct. So we keep this
|
||||
// to validate that mapping is correct, and return fails
|
||||
// to indicate fails from the var.
|
||||
return newVar.valid()
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Pkg) eachResource(resourceKind kind, fn func(r Resource) []failure) error {
|
||||
var parseErr ParseErr
|
||||
for i, r := range p.Spec.Resources {
|
||||
|
@ -602,7 +662,7 @@ func parseChart(r Resource) (chart, []failure) {
|
|||
Geom: r.stringShort("geom"),
|
||||
}
|
||||
|
||||
if leg, ok := ifaceMapToResource(r["legend"]); ok {
|
||||
if leg, ok := ifaceToResource(r["legend"]); ok {
|
||||
c.Legend.Type = leg.stringShort("type")
|
||||
c.Legend.Orientation = leg.stringShort("orientation")
|
||||
}
|
||||
|
@ -669,6 +729,9 @@ func (r Resource) kind() (kind, error) {
|
|||
if newKind == kindUnknown {
|
||||
return kindUnknown, errors.New("invalid kind")
|
||||
}
|
||||
if !kinds[newKind] {
|
||||
return newKind, errors.New("unsupported kind provided")
|
||||
}
|
||||
|
||||
return newKind, nil
|
||||
}
|
||||
|
@ -695,7 +758,7 @@ func (r Resource) nestedAssociations() []Resource {
|
|||
|
||||
var resources []Resource
|
||||
for _, iface := range ifaces {
|
||||
newRes, ok := ifaceMapToResource(iface)
|
||||
newRes, ok := ifaceToResource(iface)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
@ -757,15 +820,7 @@ func (r Resource) intShort(key string) int {
|
|||
}
|
||||
|
||||
func (r Resource) string(key string) (string, bool) {
|
||||
if s, ok := r[key].(string); ok {
|
||||
return s, true
|
||||
}
|
||||
|
||||
if i, ok := r[key].(int); ok {
|
||||
return strconv.Itoa(i), true
|
||||
}
|
||||
|
||||
return "", false
|
||||
return ifaceToStr(r[key])
|
||||
}
|
||||
|
||||
func (r Resource) stringShort(key string) string {
|
||||
|
@ -786,7 +841,7 @@ func (r Resource) slcResource(key string) []Resource {
|
|||
|
||||
var newResources []Resource
|
||||
for _, iFace := range iFaceSlc {
|
||||
r, ok := ifaceMapToResource(iFace)
|
||||
r, ok := ifaceToResource(iFace)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
@ -796,7 +851,51 @@ func (r Resource) slcResource(key string) []Resource {
|
|||
return newResources
|
||||
}
|
||||
|
||||
func ifaceMapToResource(i interface{}) (Resource, bool) {
|
||||
func (r Resource) slcStr(key string) []string {
|
||||
v, ok := r[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
res, ok := ifaceToResource(r[key])
|
||||
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
|
||||
}
|
||||
|
||||
res, ok := i.(Resource)
|
||||
if ok {
|
||||
return res, true
|
||||
|
@ -822,6 +921,26 @@ func ifaceMapToResource(i interface{}) (Resource, bool) {
|
|||
return newRes, true
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ParseErr is a error from parsing the given package. The ParseErr
|
||||
// provides a list of resources that failed and all validations
|
||||
// that failed for that resource. A resource can multiple errors,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"apiVersion": "0.1.0",
|
||||
"kind": "Package",
|
||||
"meta": {
|
||||
"pkgName": "pkg_name",
|
||||
"pkgVersion": "1",
|
||||
"description": "pack description"
|
||||
},
|
||||
"spec": {
|
||||
"resources": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "var_query_1",
|
||||
"description": "var_query_1 desc",
|
||||
"type": "query",
|
||||
"query": "buckets() |> filter(fn: (r) => r.name !~ /^_/) |> rename(columns: {name: \"_value\"}) |> keep(columns: [\"_value\"])",
|
||||
"language": "flux"
|
||||
},
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "var_query_2",
|
||||
"description": "var_query_2 desc",
|
||||
"type": "query",
|
||||
"query": "an influxql query of sorts",
|
||||
"language": "influxql"
|
||||
},
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "var_const",
|
||||
"description": "var_const desc",
|
||||
"type": "constant",
|
||||
"values": ["first val"]
|
||||
},
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "var_map",
|
||||
"description": "var_map desc",
|
||||
"type": "map",
|
||||
"values": {
|
||||
"k1": "v1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
apiVersion: 0.1.0
|
||||
kind: Package
|
||||
meta:
|
||||
pkgName: pkg_name
|
||||
pkgVersion: 1
|
||||
description: pack description
|
||||
spec:
|
||||
resources:
|
||||
- kind: Variable
|
||||
name: var_query_1
|
||||
description: var_query_1 desc
|
||||
type: query
|
||||
language: flux
|
||||
query: |
|
||||
buckets() |> filter(fn: (r) => r.name !~ /^_/) |> rename(columns: {name: "_value"}) |> keep(columns: ["_value"])
|
||||
- kind: Variable
|
||||
name: var_query_2
|
||||
description: var_query_2 desc
|
||||
type: query
|
||||
query: an influxql query of sorts
|
||||
language: influxql
|
||||
- kind: Variable
|
||||
name: var_const
|
||||
description: var_const desc
|
||||
type: constant
|
||||
values:
|
||||
- first val
|
||||
- kind: Variable
|
||||
name: var_map
|
||||
description: var_map desc
|
||||
type: map
|
||||
values:
|
||||
k1: v1
|
|
@ -123,7 +123,7 @@ func (m *Variable) Valid() error {
|
|||
"query": true,
|
||||
}
|
||||
|
||||
if _, prs := validTypes[m.Arguments.Type]; !prs {
|
||||
if !validTypes[m.Arguments.Type] {
|
||||
return fmt.Errorf("invalid arguments type")
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue