encoding/yaml: eliminate use of ghodss yaml pkg
This implementation translates a CUE AST to yaml.Node
for the yaml.v3 package.
The new builtins now also verify the concreteness of
the value. Closes #288.
Also, the extra roundtrip by ghodss translates CUE to
a map, which then causes the fields to be ordered
lexically, which is undesirable.
The new encoder leaves fields in CUE ordering
which is closer to the original ordering of the
fields. This causes large (desirable) changes in the
Kubernetes' demo validation data.
Change-Id: Iedd32c46b9738d9c367628087c918c4c70310e1d
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/5087
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin.go b/cue/builtin.go
index 3f8c7b9..99e8fff 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -14,6 +14,7 @@
//go:generate go run gen.go
//go:generate goimports -w -local cuelang.org/go builtins.go
+//go:generate gofmt -s -w builtins.go
package cue
diff --git a/cue/builtins.go b/cue/builtins.go
index 7b2e4e1..d350402 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -31,13 +31,13 @@
"unicode/utf8"
"github.com/cockroachdb/apd/v2"
- goyaml "github.com/ghodss/yaml"
"golang.org/x/net/idna"
"cuelang.org/go/cue/errors"
"cuelang.org/go/cue/literal"
"cuelang.org/go/cue/parser"
"cuelang.org/go/internal"
+ cueyaml "cuelang.org/go/internal/encoding/yaml"
"cuelang.org/go/internal/third_party/yaml"
)
@@ -147,21 +147,6 @@
return true, nil
}
-var boolValues = map[string]bool{
- "1": true,
- "0": false,
- "t": true,
- "f": false,
- "T": true,
- "F": false,
- "true": true,
- "false": false,
- "TRUE": true,
- "FALSE": false,
- "True": true,
- "False": false,
-}
-
var builtinPackages = map[string]*builtinPkg{
"": {
native: []*builtin{{}},
@@ -652,7 +637,14 @@
v := c.value(0)
if c.do() {
c.ret, c.err = func() (interface{}, error) {
- b, err := goyaml.Marshal(v)
+ if err := v.Validate(Concrete(true)); err != nil {
+ if err := v.Validate(); err != nil {
+ return "", err
+ }
+ return "", internal.ErrIncomplete
+ }
+ n := v.Syntax(Final())
+ b, err := cueyaml.Encode(n)
return string(b), err
}()
}
@@ -675,7 +667,15 @@
if i > 0 {
buf.WriteString("---\n")
}
- b, err := goyaml.Marshal(iter.Value())
+ v := iter.Value()
+ if err := v.Validate(Concrete(true)); err != nil {
+ if err := v.Validate(); err != nil {
+ return "", err
+ }
+ return "", internal.ErrIncomplete
+ }
+ n := v.Syntax(Final())
+ b, err := cueyaml.Encode(n)
if err != nil {
return "", err
}
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index 548bd2e..9f1e555 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -2900,6 +2900,50 @@
`a: <1>.Marshal (<2>{a: string}), ` +
`foo: <3>{a: 3, b: <4>.foo.c}, ` +
`b: <1>.Marshal (<4>.foo)}`,
+ }, {
+ desc: "detectIncompleteYAML",
+ in: `
+ package foobar
+
+ import yaml "encoding/yaml"
+
+ Spec :: {
+ _vars: {something: string}
+ data: {
+ foo :: {
+ use: _vars.something
+ }
+ baz: yaml.Marshal(_vars.something)
+ foobar: yaml.Marshal(foo)
+ }
+ }
+ Val: Spec & {
+ _vars: something: "var-string"
+ }
+ `,
+ out: `<0>{Spec :: <1>C{_vars: <2>C{something: string}, data: <3>C{foo :: <4>C{use: string}, baz: <5>.Marshal (<6>._vars.something), foobar: <5>.Marshal (<7>.foo)}}, Val: <8>C{_vars: <9>C{something: "var-string"}, data: <10>C{foo :: <11>C{use: "var-string"}, baz: "var-string\n", foobar: "use: var-string\n"}}}`,
+ }, {
+ desc: "detectIncompleteJSON",
+ in: `
+ package foobar
+
+ import "encoding/json"
+
+ Spec :: {
+ _vars: {something: string}
+ data: {
+ foo :: {
+ use: _vars.something
+ }
+ baz: json.Marshal(_vars.something)
+ foobar: json.Marshal(foo)
+ }
+ }
+ Val: Spec & {
+ _vars: something: "var-string"
+ }
+ `,
+ out: `<0>{Spec :: <1>C{_vars: <2>C{something: string}, data: <3>C{foo :: <4>C{use: string}, baz: <5>.Marshal (<6>._vars.something), foobar: <5>.Marshal (<7>.foo)}}, Val: <8>C{_vars: <9>C{something: "var-string"}, data: <10>C{foo :: <11>C{use: "var-string"}, baz: "\"var-string\"", foobar: "{\"use\":\"var-string\"}"}}}`,
}}
rewriteHelper(t, testCases, evalFull)
}
diff --git a/doc/tutorial/kubernetes/testdata/manual.out b/doc/tutorial/kubernetes/testdata/manual.out
index 8eaae10..831fb1c 100644
--- a/doc/tutorial/kubernetes/testdata/manual.out
+++ b/doc/tutorial/kubernetes/testdata/manual.out
@@ -5496,18 +5496,18 @@
data: {
"alerts.yaml": """
receivers:
- - name: pager
- slack_configs:
- - channel: '#cloudmon'
- send_resolved: true
- text: |-
- {{ range .Alerts }}{{ .Annotations.description }}
- {{ end }}
+ - name: pager
+ slack_configs:
+ - text: |-
+ {{ range .Alerts }}{{ .Annotations.description }}
+ {{ end }}
+ channel: '#cloudmon'
+ send_resolved: true
route:
- group_by:
- - alertname
- - cluster
- receiver: pager
+ receiver: pager
+ group_by:
+ - alertname
+ - cluster
"""
}
@@ -5607,18 +5607,18 @@
alertmanager: {
"alerts.yaml": """
receivers:
- - name: pager
- slack_configs:
- - channel: '#cloudmon'
- send_resolved: true
- text: |-
- {{ range .Alerts }}{{ .Annotations.description }}
- {{ end }}
+ - name: pager
+ slack_configs:
+ - text: |-
+ {{ range .Alerts }}{{ .Annotations.description }}
+ {{ end }}
+ channel: '#cloudmon'
+ send_resolved: true
route:
- group_by:
- - alertname
- - cluster
- receiver: pager
+ receiver: pager
+ group_by:
+ - alertname
+ - cluster
"""
}
@@ -6341,229 +6341,229 @@
data: {
"alert.rules": """
groups:
- - name: rules.yaml
- rules:
- - alert: InstanceDown
- annotations:
- description: '{{$labels.app}} of job {{ $labels.job }} has been down for more
- than 30 seconds.'
- summary: Instance {{$labels.app}} down
- expr: up == 0
- for: 30s
- labels:
- severity: page
- - alert: InsufficientPeers
- annotations:
- description: If one more etcd peer goes down the cluster will be unavailable
- summary: etcd cluster small
- expr: count(up{job=\"etcd\"} == 0) > (count(up{job=\"etcd\"}) / 2 - 1)
- for: 3m
- labels:
- severity: page
- - alert: EtcdNoMaster
- annotations:
- summary: No ETCD master elected.
- expr: sum(etcd_server_has_leader{app=\"etcd\"}) == 0
- for: 1s
- labels:
- severity: page
- - alert: PodRestart
- annotations:
- description: '{{$labels.app}} {{ $labels.container }} resturted {{ $value }}
- times in 5m.'
- summary: Pod for {{$labels.container}} restarts too often
- expr: (max_over_time(pod_container_status_restarts_total[5m]) - min_over_time(pod_container_status_restarts_total[5m]))
- > 2
- for: 1m
- labels:
- severity: page
+ - name: rules.yaml
+ rules:
+ - labels:
+ severity: page
+ annotations:
+ description: '{{$labels.app}} of job {{ $labels.job }} has been down for
+ more than 30 seconds.'
+ summary: Instance {{$labels.app}} down
+ alert: InstanceDown
+ expr: up == 0
+ for: 30s
+ - labels:
+ severity: page
+ annotations:
+ description: If one more etcd peer goes down the cluster will be unavailable
+ summary: etcd cluster small
+ alert: InsufficientPeers
+ expr: count(up{job=\"etcd\"} == 0) > (count(up{job=\"etcd\"}) / 2 - 1)
+ for: 3m
+ - labels:
+ severity: page
+ annotations:
+ summary: No ETCD master elected.
+ alert: EtcdNoMaster
+ expr: sum(etcd_server_has_leader{app=\"etcd\"}) == 0
+ for: 1s
+ - labels:
+ severity: page
+ annotations:
+ description: '{{$labels.app}} {{ $labels.container }} resturted {{ $value
+ }} times in 5m.'
+ summary: Pod for {{$labels.container}} restarts too often
+ alert: PodRestart
+ expr: (max_over_time(pod_container_status_restarts_total[5m]) - min_over_time(pod_container_status_restarts_total[5m]))
+ > 2
+ for: 1m
"""
"prometheus.yml": """
- alerting:
- alertmanagers:
- - scheme: http
- static_configs:
- - targets:
- - alertmanager:9093
global:
- scrape_interval: 15s
+ scrape_interval: 15s
rule_files:
- - /etc/prometheus/alert.rules
+ - /etc/prometheus/alert.rules
+ alerting:
+ alertmanagers:
+ - scheme: http
+ static_configs:
+ - targets:
+ - alertmanager:9093
scrape_configs:
- - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
- job_name: kubernetes-apiservers
- kubernetes_sd_configs:
- - role: endpoints
- relabel_configs:
- - action: keep
- regex: default;kubernetes;https
- source_labels:
- - __meta_kubernetes_namespace
- - __meta_kubernetes_service_name
- - __meta_kubernetes_endpoint_port_name
- scheme: https
- tls_config:
- ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
- job_name: kubernetes-nodes
- kubernetes_sd_configs:
- - role: node
- relabel_configs:
- - action: labelmap
- regex: __meta_kubernetes_node_label_(.+)
- - replacement: kubernetes.default.svc:443
- target_label: __address__
- - regex: (.+)
- replacement: /api/v1/nodes/${1}/proxy/metrics
- source_labels:
- - __meta_kubernetes_node_name
- target_label: __metrics_path__
- scheme: https
- tls_config:
- ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
- job_name: kubernetes-cadvisor
- kubernetes_sd_configs:
- - role: node
- relabel_configs:
- - action: labelmap
- regex: __meta_kubernetes_node_label_(.+)
- - replacement: kubernetes.default.svc:443
- target_label: __address__
- - regex: (.+)
- replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
- source_labels:
- - __meta_kubernetes_node_name
- target_label: __metrics_path__
- scheme: https
- tls_config:
- ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- - job_name: kubernetes-service-endpoints
- kubernetes_sd_configs:
- - role: endpoints
- relabel_configs:
- - action: keep
- regex: true
- source_labels:
- - __meta_kubernetes_service_annotation_prometheus_io_scrape
- - action: replace
- regex: (https?)
- source_labels:
- - __meta_kubernetes_service_annotation_prometheus_io_scheme
- target_label: __scheme__
- - action: replace
- regex: (.+)
- source_labels:
- - __meta_kubernetes_service_annotation_prometheus_io_path
- target_label: __metrics_path__
- - action: replace
- regex: ([^:]+)(?::\\d+)?;(\\d+)
- replacement: $1:$2
- source_labels:
- - __address__
- - __meta_kubernetes_service_annotation_prometheus_io_port
- target_label: __address__
- - action: labelmap
- regex: __meta_kubernetes_service_label_(.+)
- - action: replace
- source_labels:
- - __meta_kubernetes_namespace
- target_label: kubernetes_namespace
- - action: replace
- source_labels:
- - __meta_kubernetes_service_name
- target_label: kubernetes_name
- - job_name: kubernetes-services
- kubernetes_sd_configs:
- - role: service
- metrics_path: /probe
- params:
- module:
- - http_2xx
- relabel_configs:
- - action: keep
- regex: true
- source_labels:
- - __meta_kubernetes_service_annotation_prometheus_io_probe
- - source_labels:
- - __address__
- target_label: __param_target
- - replacement: blackbox-exporter.example.com:9115
- target_label: __address__
- - source_labels:
- - __param_target
- target_label: app
- - action: labelmap
- regex: __meta_kubernetes_service_label_(.+)
- - source_labels:
- - __meta_kubernetes_namespace
- target_label: kubernetes_namespace
- - source_labels:
- - __meta_kubernetes_service_name
- target_label: kubernetes_name
- - job_name: kubernetes-ingresses
- kubernetes_sd_configs:
- - role: ingress
- metrics_path: /probe
- params:
- module:
- - http_2xx
- relabel_configs:
- - action: keep
- regex: true
- source_labels:
- - __meta_kubernetes_ingress_annotation_prometheus_io_probe
- - regex: (.+);(.+);(.+)
- replacement: ${1}://${2}${3}
- source_labels:
- - __meta_kubernetes_ingress_scheme
- - __address__
- - __meta_kubernetes_ingress_path
- target_label: __param_target
- - replacement: blackbox-exporter.example.com:9115
- target_label: __address__
- - source_labels:
- - __param_target
- target_label: app
- - action: labelmap
- regex: __meta_kubernetes_ingress_label_(.+)
- - source_labels:
- - __meta_kubernetes_namespace
- target_label: kubernetes_namespace
- - source_labels:
- - __meta_kubernetes_ingress_name
- target_label: kubernetes_name
- - job_name: kubernetes-pods
- kubernetes_sd_configs:
- - role: pod
- relabel_configs:
- - action: keep
- regex: true
- source_labels:
- - __meta_kubernetes_pod_annotation_prometheus_io_scrape
- - action: replace
- regex: (.+)
- source_labels:
- - __meta_kubernetes_pod_annotation_prometheus_io_path
- target_label: __metrics_path__
- - action: replace
- regex: ([^:]+)(?::\\d+)?;(\\d+)
- replacement: $1:$2
- source_labels:
- - __address__
- - __meta_kubernetes_pod_annotation_prometheus_io_port
- target_label: __address__
- - action: labelmap
- regex: __meta_kubernetes_pod_label_(.+)
- - action: replace
- source_labels:
- - __meta_kubernetes_namespace
- target_label: kubernetes_namespace
- - action: replace
- source_labels:
- - __meta_kubernetes_pod_name
- target_label: kubernetes_pod_name
+ - scheme: https
+ job_name: kubernetes-apiservers
+ kubernetes_sd_configs:
+ - role: endpoints
+ tls_config:
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ relabel_configs:
+ - source_labels:
+ - __meta_kubernetes_namespace
+ - __meta_kubernetes_service_name
+ - __meta_kubernetes_endpoint_port_name
+ action: keep
+ regex: default;kubernetes;https
+ - scheme: https
+ job_name: kubernetes-nodes
+ kubernetes_sd_configs:
+ - role: node
+ tls_config:
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ relabel_configs:
+ - action: labelmap
+ regex: __meta_kubernetes_node_label_(.+)
+ - target_label: __address__
+ replacement: kubernetes.default.svc:443
+ - source_labels:
+ - __meta_kubernetes_node_name
+ regex: (.+)
+ target_label: __metrics_path__
+ replacement: /api/v1/nodes/${1}/proxy/metrics
+ - scheme: https
+ job_name: kubernetes-cadvisor
+ kubernetes_sd_configs:
+ - role: node
+ tls_config:
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ relabel_configs:
+ - action: labelmap
+ regex: __meta_kubernetes_node_label_(.+)
+ - target_label: __address__
+ replacement: kubernetes.default.svc:443
+ - source_labels:
+ - __meta_kubernetes_node_name
+ regex: (.+)
+ target_label: __metrics_path__
+ replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
+ - job_name: kubernetes-service-endpoints
+ kubernetes_sd_configs:
+ - role: endpoints
+ relabel_configs:
+ - source_labels:
+ - __meta_kubernetes_service_annotation_prometheus_io_scrape
+ action: keep
+ regex: true
+ - source_labels:
+ - __meta_kubernetes_service_annotation_prometheus_io_scheme
+ action: replace
+ regex: (https?)
+ target_label: __scheme__
+ - source_labels:
+ - __meta_kubernetes_service_annotation_prometheus_io_path
+ action: replace
+ regex: (.+)
+ target_label: __metrics_path__
+ - source_labels:
+ - __address__
+ - __meta_kubernetes_service_annotation_prometheus_io_port
+ action: replace
+ regex: ([^:]+)(?::\\d+)?;(\\d+)
+ target_label: __address__
+ replacement: $1:$2
+ - action: labelmap
+ regex: __meta_kubernetes_service_label_(.+)
+ - source_labels:
+ - __meta_kubernetes_namespace
+ action: replace
+ target_label: kubernetes_namespace
+ - source_labels:
+ - __meta_kubernetes_service_name
+ action: replace
+ target_label: kubernetes_name
+ - job_name: kubernetes-services
+ kubernetes_sd_configs:
+ - role: service
+ relabel_configs:
+ - source_labels:
+ - __meta_kubernetes_service_annotation_prometheus_io_probe
+ action: keep
+ regex: true
+ - source_labels:
+ - __address__
+ target_label: __param_target
+ - target_label: __address__
+ replacement: blackbox-exporter.example.com:9115
+ - source_labels:
+ - __param_target
+ target_label: app
+ - action: labelmap
+ regex: __meta_kubernetes_service_label_(.+)
+ - source_labels:
+ - __meta_kubernetes_namespace
+ target_label: kubernetes_namespace
+ - source_labels:
+ - __meta_kubernetes_service_name
+ target_label: kubernetes_name
+ metrics_path: /probe
+ params:
+ module:
+ - http_2xx
+ - job_name: kubernetes-ingresses
+ kubernetes_sd_configs:
+ - role: ingress
+ relabel_configs:
+ - source_labels:
+ - __meta_kubernetes_ingress_annotation_prometheus_io_probe
+ action: keep
+ regex: true
+ - source_labels:
+ - __meta_kubernetes_ingress_scheme
+ - __address__
+ - __meta_kubernetes_ingress_path
+ regex: (.+);(.+);(.+)
+ target_label: __param_target
+ replacement: ${1}://${2}${3}
+ - target_label: __address__
+ replacement: blackbox-exporter.example.com:9115
+ - source_labels:
+ - __param_target
+ target_label: app
+ - action: labelmap
+ regex: __meta_kubernetes_ingress_label_(.+)
+ - source_labels:
+ - __meta_kubernetes_namespace
+ target_label: kubernetes_namespace
+ - source_labels:
+ - __meta_kubernetes_ingress_name
+ target_label: kubernetes_name
+ metrics_path: /probe
+ params:
+ module:
+ - http_2xx
+ - job_name: kubernetes-pods
+ kubernetes_sd_configs:
+ - role: pod
+ relabel_configs:
+ - source_labels:
+ - __meta_kubernetes_pod_annotation_prometheus_io_scrape
+ action: keep
+ regex: true
+ - source_labels:
+ - __meta_kubernetes_pod_annotation_prometheus_io_path
+ action: replace
+ regex: (.+)
+ target_label: __metrics_path__
+ - source_labels:
+ - __address__
+ - __meta_kubernetes_pod_annotation_prometheus_io_port
+ action: replace
+ regex: ([^:]+)(?::\\d+)?;(\\d+)
+ target_label: __address__
+ replacement: $1:$2
+ - action: labelmap
+ regex: __meta_kubernetes_pod_label_(.+)
+ - source_labels:
+ - __meta_kubernetes_namespace
+ action: replace
+ target_label: kubernetes_namespace
+ - source_labels:
+ - __meta_kubernetes_pod_name
+ action: replace
+ target_label: kubernetes_pod_name
"""
}
@@ -6668,229 +6668,229 @@
prometheus: {
"alert.rules": """
groups:
- - name: rules.yaml
- rules:
- - alert: InstanceDown
- annotations:
- description: '{{$labels.app}} of job {{ $labels.job }} has been down for more
- than 30 seconds.'
- summary: Instance {{$labels.app}} down
- expr: up == 0
- for: 30s
- labels:
- severity: page
- - alert: InsufficientPeers
- annotations:
- description: If one more etcd peer goes down the cluster will be unavailable
- summary: etcd cluster small
- expr: count(up{job=\"etcd\"} == 0) > (count(up{job=\"etcd\"}) / 2 - 1)
- for: 3m
- labels:
- severity: page
- - alert: EtcdNoMaster
- annotations:
- summary: No ETCD master elected.
- expr: sum(etcd_server_has_leader{app=\"etcd\"}) == 0
- for: 1s
- labels:
- severity: page
- - alert: PodRestart
- annotations:
- description: '{{$labels.app}} {{ $labels.container }} resturted {{ $value }}
- times in 5m.'
- summary: Pod for {{$labels.container}} restarts too often
- expr: (max_over_time(pod_container_status_restarts_total[5m]) - min_over_time(pod_container_status_restarts_total[5m]))
- > 2
- for: 1m
- labels:
- severity: page
+ - name: rules.yaml
+ rules:
+ - labels:
+ severity: page
+ annotations:
+ description: '{{$labels.app}} of job {{ $labels.job }} has been down for
+ more than 30 seconds.'
+ summary: Instance {{$labels.app}} down
+ alert: InstanceDown
+ expr: up == 0
+ for: 30s
+ - labels:
+ severity: page
+ annotations:
+ description: If one more etcd peer goes down the cluster will be unavailable
+ summary: etcd cluster small
+ alert: InsufficientPeers
+ expr: count(up{job=\"etcd\"} == 0) > (count(up{job=\"etcd\"}) / 2 - 1)
+ for: 3m
+ - labels:
+ severity: page
+ annotations:
+ summary: No ETCD master elected.
+ alert: EtcdNoMaster
+ expr: sum(etcd_server_has_leader{app=\"etcd\"}) == 0
+ for: 1s
+ - labels:
+ severity: page
+ annotations:
+ description: '{{$labels.app}} {{ $labels.container }} resturted {{ $value
+ }} times in 5m.'
+ summary: Pod for {{$labels.container}} restarts too often
+ alert: PodRestart
+ expr: (max_over_time(pod_container_status_restarts_total[5m]) - min_over_time(pod_container_status_restarts_total[5m]))
+ > 2
+ for: 1m
"""
"prometheus.yml": """
- alerting:
- alertmanagers:
- - scheme: http
- static_configs:
- - targets:
- - alertmanager:9093
global:
- scrape_interval: 15s
+ scrape_interval: 15s
rule_files:
- - /etc/prometheus/alert.rules
+ - /etc/prometheus/alert.rules
+ alerting:
+ alertmanagers:
+ - scheme: http
+ static_configs:
+ - targets:
+ - alertmanager:9093
scrape_configs:
- - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
- job_name: kubernetes-apiservers
- kubernetes_sd_configs:
- - role: endpoints
- relabel_configs:
- - action: keep
- regex: default;kubernetes;https
- source_labels:
- - __meta_kubernetes_namespace
- - __meta_kubernetes_service_name
- - __meta_kubernetes_endpoint_port_name
- scheme: https
- tls_config:
- ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
- job_name: kubernetes-nodes
- kubernetes_sd_configs:
- - role: node
- relabel_configs:
- - action: labelmap
- regex: __meta_kubernetes_node_label_(.+)
- - replacement: kubernetes.default.svc:443
- target_label: __address__
- - regex: (.+)
- replacement: /api/v1/nodes/${1}/proxy/metrics
- source_labels:
- - __meta_kubernetes_node_name
- target_label: __metrics_path__
- scheme: https
- tls_config:
- ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
- job_name: kubernetes-cadvisor
- kubernetes_sd_configs:
- - role: node
- relabel_configs:
- - action: labelmap
- regex: __meta_kubernetes_node_label_(.+)
- - replacement: kubernetes.default.svc:443
- target_label: __address__
- - regex: (.+)
- replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
- source_labels:
- - __meta_kubernetes_node_name
- target_label: __metrics_path__
- scheme: https
- tls_config:
- ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- - job_name: kubernetes-service-endpoints
- kubernetes_sd_configs:
- - role: endpoints
- relabel_configs:
- - action: keep
- regex: true
- source_labels:
- - __meta_kubernetes_service_annotation_prometheus_io_scrape
- - action: replace
- regex: (https?)
- source_labels:
- - __meta_kubernetes_service_annotation_prometheus_io_scheme
- target_label: __scheme__
- - action: replace
- regex: (.+)
- source_labels:
- - __meta_kubernetes_service_annotation_prometheus_io_path
- target_label: __metrics_path__
- - action: replace
- regex: ([^:]+)(?::\\d+)?;(\\d+)
- replacement: $1:$2
- source_labels:
- - __address__
- - __meta_kubernetes_service_annotation_prometheus_io_port
- target_label: __address__
- - action: labelmap
- regex: __meta_kubernetes_service_label_(.+)
- - action: replace
- source_labels:
- - __meta_kubernetes_namespace
- target_label: kubernetes_namespace
- - action: replace
- source_labels:
- - __meta_kubernetes_service_name
- target_label: kubernetes_name
- - job_name: kubernetes-services
- kubernetes_sd_configs:
- - role: service
- metrics_path: /probe
- params:
- module:
- - http_2xx
- relabel_configs:
- - action: keep
- regex: true
- source_labels:
- - __meta_kubernetes_service_annotation_prometheus_io_probe
- - source_labels:
- - __address__
- target_label: __param_target
- - replacement: blackbox-exporter.example.com:9115
- target_label: __address__
- - source_labels:
- - __param_target
- target_label: app
- - action: labelmap
- regex: __meta_kubernetes_service_label_(.+)
- - source_labels:
- - __meta_kubernetes_namespace
- target_label: kubernetes_namespace
- - source_labels:
- - __meta_kubernetes_service_name
- target_label: kubernetes_name
- - job_name: kubernetes-ingresses
- kubernetes_sd_configs:
- - role: ingress
- metrics_path: /probe
- params:
- module:
- - http_2xx
- relabel_configs:
- - action: keep
- regex: true
- source_labels:
- - __meta_kubernetes_ingress_annotation_prometheus_io_probe
- - regex: (.+);(.+);(.+)
- replacement: ${1}://${2}${3}
- source_labels:
- - __meta_kubernetes_ingress_scheme
- - __address__
- - __meta_kubernetes_ingress_path
- target_label: __param_target
- - replacement: blackbox-exporter.example.com:9115
- target_label: __address__
- - source_labels:
- - __param_target
- target_label: app
- - action: labelmap
- regex: __meta_kubernetes_ingress_label_(.+)
- - source_labels:
- - __meta_kubernetes_namespace
- target_label: kubernetes_namespace
- - source_labels:
- - __meta_kubernetes_ingress_name
- target_label: kubernetes_name
- - job_name: kubernetes-pods
- kubernetes_sd_configs:
- - role: pod
- relabel_configs:
- - action: keep
- regex: true
- source_labels:
- - __meta_kubernetes_pod_annotation_prometheus_io_scrape
- - action: replace
- regex: (.+)
- source_labels:
- - __meta_kubernetes_pod_annotation_prometheus_io_path
- target_label: __metrics_path__
- - action: replace
- regex: ([^:]+)(?::\\d+)?;(\\d+)
- replacement: $1:$2
- source_labels:
- - __address__
- - __meta_kubernetes_pod_annotation_prometheus_io_port
- target_label: __address__
- - action: labelmap
- regex: __meta_kubernetes_pod_label_(.+)
- - action: replace
- source_labels:
- - __meta_kubernetes_namespace
- target_label: kubernetes_namespace
- - action: replace
- source_labels:
- - __meta_kubernetes_pod_name
- target_label: kubernetes_pod_name
+ - scheme: https
+ job_name: kubernetes-apiservers
+ kubernetes_sd_configs:
+ - role: endpoints
+ tls_config:
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ relabel_configs:
+ - source_labels:
+ - __meta_kubernetes_namespace
+ - __meta_kubernetes_service_name
+ - __meta_kubernetes_endpoint_port_name
+ action: keep
+ regex: default;kubernetes;https
+ - scheme: https
+ job_name: kubernetes-nodes
+ kubernetes_sd_configs:
+ - role: node
+ tls_config:
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ relabel_configs:
+ - action: labelmap
+ regex: __meta_kubernetes_node_label_(.+)
+ - target_label: __address__
+ replacement: kubernetes.default.svc:443
+ - source_labels:
+ - __meta_kubernetes_node_name
+ regex: (.+)
+ target_label: __metrics_path__
+ replacement: /api/v1/nodes/${1}/proxy/metrics
+ - scheme: https
+ job_name: kubernetes-cadvisor
+ kubernetes_sd_configs:
+ - role: node
+ tls_config:
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ relabel_configs:
+ - action: labelmap
+ regex: __meta_kubernetes_node_label_(.+)
+ - target_label: __address__
+ replacement: kubernetes.default.svc:443
+ - source_labels:
+ - __meta_kubernetes_node_name
+ regex: (.+)
+ target_label: __metrics_path__
+ replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
+ - job_name: kubernetes-service-endpoints
+ kubernetes_sd_configs:
+ - role: endpoints
+ relabel_configs:
+ - source_labels:
+ - __meta_kubernetes_service_annotation_prometheus_io_scrape
+ action: keep
+ regex: true
+ - source_labels:
+ - __meta_kubernetes_service_annotation_prometheus_io_scheme
+ action: replace
+ regex: (https?)
+ target_label: __scheme__
+ - source_labels:
+ - __meta_kubernetes_service_annotation_prometheus_io_path
+ action: replace
+ regex: (.+)
+ target_label: __metrics_path__
+ - source_labels:
+ - __address__
+ - __meta_kubernetes_service_annotation_prometheus_io_port
+ action: replace
+ regex: ([^:]+)(?::\\d+)?;(\\d+)
+ target_label: __address__
+ replacement: $1:$2
+ - action: labelmap
+ regex: __meta_kubernetes_service_label_(.+)
+ - source_labels:
+ - __meta_kubernetes_namespace
+ action: replace
+ target_label: kubernetes_namespace
+ - source_labels:
+ - __meta_kubernetes_service_name
+ action: replace
+ target_label: kubernetes_name
+ - job_name: kubernetes-services
+ kubernetes_sd_configs:
+ - role: service
+ relabel_configs:
+ - source_labels:
+ - __meta_kubernetes_service_annotation_prometheus_io_probe
+ action: keep
+ regex: true
+ - source_labels:
+ - __address__
+ target_label: __param_target
+ - target_label: __address__
+ replacement: blackbox-exporter.example.com:9115
+ - source_labels:
+ - __param_target
+ target_label: app
+ - action: labelmap
+ regex: __meta_kubernetes_service_label_(.+)
+ - source_labels:
+ - __meta_kubernetes_namespace
+ target_label: kubernetes_namespace
+ - source_labels:
+ - __meta_kubernetes_service_name
+ target_label: kubernetes_name
+ metrics_path: /probe
+ params:
+ module:
+ - http_2xx
+ - job_name: kubernetes-ingresses
+ kubernetes_sd_configs:
+ - role: ingress
+ relabel_configs:
+ - source_labels:
+ - __meta_kubernetes_ingress_annotation_prometheus_io_probe
+ action: keep
+ regex: true
+ - source_labels:
+ - __meta_kubernetes_ingress_scheme
+ - __address__
+ - __meta_kubernetes_ingress_path
+ regex: (.+);(.+);(.+)
+ target_label: __param_target
+ replacement: ${1}://${2}${3}
+ - target_label: __address__
+ replacement: blackbox-exporter.example.com:9115
+ - source_labels:
+ - __param_target
+ target_label: app
+ - action: labelmap
+ regex: __meta_kubernetes_ingress_label_(.+)
+ - source_labels:
+ - __meta_kubernetes_namespace
+ target_label: kubernetes_namespace
+ - source_labels:
+ - __meta_kubernetes_ingress_name
+ target_label: kubernetes_name
+ metrics_path: /probe
+ params:
+ module:
+ - http_2xx
+ - job_name: kubernetes-pods
+ kubernetes_sd_configs:
+ - role: pod
+ relabel_configs:
+ - source_labels:
+ - __meta_kubernetes_pod_annotation_prometheus_io_scrape
+ action: keep
+ regex: true
+ - source_labels:
+ - __meta_kubernetes_pod_annotation_prometheus_io_path
+ action: replace
+ regex: (.+)
+ target_label: __metrics_path__
+ - source_labels:
+ - __address__
+ - __meta_kubernetes_pod_annotation_prometheus_io_port
+ action: replace
+ regex: ([^:]+)(?::\\d+)?;(\\d+)
+ target_label: __address__
+ replacement: $1:$2
+ - action: labelmap
+ regex: __meta_kubernetes_pod_label_(.+)
+ - source_labels:
+ - __meta_kubernetes_namespace
+ action: replace
+ target_label: kubernetes_namespace
+ - source_labels:
+ - __meta_kubernetes_pod_name
+ action: replace
+ target_label: kubernetes_pod_name
"""
}
diff --git a/doc/tutorial/kubernetes/testdata/quick.out b/doc/tutorial/kubernetes/testdata/quick.out
index 6d4fa10..1da9042 100644
--- a/doc/tutorial/kubernetes/testdata/quick.out
+++ b/doc/tutorial/kubernetes/testdata/quick.out
@@ -2656,18 +2656,18 @@
data: {
"alerts.yaml": """
receivers:
- - name: pager
- slack_configs:
- - channel: '#cloudmon'
- send_resolved: true
- text: |-
- {{ range .Alerts }}{{ .Annotations.description }}
- {{ end }}
+ - name: pager
+ slack_configs:
+ - text: |-
+ {{ range .Alerts }}{{ .Annotations.description }}
+ {{ end }}
+ channel: '#cloudmon'
+ send_resolved: true
route:
- group_by:
- - alertname
- - cluster
- receiver: pager
+ receiver: pager
+ group_by:
+ - alertname
+ - cluster
"""
}
@@ -3088,229 +3088,229 @@
data: {
"alert.rules": """
groups:
- - name: rules.yaml
- rules:
- - alert: InstanceDown
- annotations:
- description: '{{$labels.app}} of job {{ $labels.job }} has been down for more
- than 30 seconds.'
- summary: Instance {{$labels.app}} down
- expr: up == 0
- for: 30s
- labels:
- severity: page
- - alert: InsufficientPeers
- annotations:
- description: If one more etcd peer goes down the cluster will be unavailable
- summary: etcd cluster small
- expr: count(up{job=\"etcd\"} == 0) > (count(up{job=\"etcd\"}) / 2 - 1)
- for: 3m
- labels:
- severity: page
- - alert: EtcdNoMaster
- annotations:
- summary: No ETCD master elected.
- expr: sum(etcd_server_has_leader{app=\"etcd\"}) == 0
- for: 1s
- labels:
- severity: page
- - alert: PodRestart
- annotations:
- description: '{{$labels.app}} {{ $labels.container }} resturted {{ $value }}
- times in 5m.'
- summary: Pod for {{$labels.container}} restarts too often
- expr: (max_over_time(pod_container_status_restarts_total[5m]) - min_over_time(pod_container_status_restarts_total[5m]))
- > 2
- for: 1m
- labels:
- severity: page
+ - name: rules.yaml
+ rules:
+ - labels:
+ severity: page
+ annotations:
+ description: '{{$labels.app}} of job {{ $labels.job }} has been down for
+ more than 30 seconds.'
+ summary: Instance {{$labels.app}} down
+ alert: InstanceDown
+ expr: up == 0
+ for: 30s
+ - labels:
+ severity: page
+ annotations:
+ description: If one more etcd peer goes down the cluster will be unavailable
+ summary: etcd cluster small
+ alert: InsufficientPeers
+ expr: count(up{job=\"etcd\"} == 0) > (count(up{job=\"etcd\"}) / 2 - 1)
+ for: 3m
+ - labels:
+ severity: page
+ annotations:
+ summary: No ETCD master elected.
+ alert: EtcdNoMaster
+ expr: sum(etcd_server_has_leader{app=\"etcd\"}) == 0
+ for: 1s
+ - labels:
+ severity: page
+ annotations:
+ description: '{{$labels.app}} {{ $labels.container }} resturted {{ $value
+ }} times in 5m.'
+ summary: Pod for {{$labels.container}} restarts too often
+ alert: PodRestart
+ expr: (max_over_time(pod_container_status_restarts_total[5m]) - min_over_time(pod_container_status_restarts_total[5m]))
+ > 2
+ for: 1m
"""
"prometheus.yml": """
- alerting:
- alertmanagers:
- - scheme: http
- static_configs:
- - targets:
- - alertmanager:9093
global:
- scrape_interval: 15s
+ scrape_interval: 15s
rule_files:
- - /etc/prometheus/alert.rules
+ - /etc/prometheus/alert.rules
+ alerting:
+ alertmanagers:
+ - scheme: http
+ static_configs:
+ - targets:
+ - alertmanager:9093
scrape_configs:
- - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
- job_name: kubernetes-apiservers
- kubernetes_sd_configs:
- - role: endpoints
- relabel_configs:
- - action: keep
- regex: default;kubernetes;https
- source_labels:
- - __meta_kubernetes_namespace
- - __meta_kubernetes_service_name
- - __meta_kubernetes_endpoint_port_name
- scheme: https
- tls_config:
- ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
- job_name: kubernetes-nodes
- kubernetes_sd_configs:
- - role: node
- relabel_configs:
- - action: labelmap
- regex: __meta_kubernetes_node_label_(.+)
- - replacement: kubernetes.default.svc:443
- target_label: __address__
- - regex: (.+)
- replacement: /api/v1/nodes/${1}/proxy/metrics
- source_labels:
- - __meta_kubernetes_node_name
- target_label: __metrics_path__
- scheme: https
- tls_config:
- ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- - bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
- job_name: kubernetes-cadvisor
- kubernetes_sd_configs:
- - role: node
- relabel_configs:
- - action: labelmap
- regex: __meta_kubernetes_node_label_(.+)
- - replacement: kubernetes.default.svc:443
- target_label: __address__
- - regex: (.+)
- replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
- source_labels:
- - __meta_kubernetes_node_name
- target_label: __metrics_path__
- scheme: https
- tls_config:
- ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
- - job_name: kubernetes-service-endpoints
- kubernetes_sd_configs:
- - role: endpoints
- relabel_configs:
- - action: keep
- regex: true
- source_labels:
- - __meta_kubernetes_service_annotation_prometheus_io_scrape
- - action: replace
- regex: (https?)
- source_labels:
- - __meta_kubernetes_service_annotation_prometheus_io_scheme
- target_label: __scheme__
- - action: replace
- regex: (.+)
- source_labels:
- - __meta_kubernetes_service_annotation_prometheus_io_path
- target_label: __metrics_path__
- - action: replace
- regex: ([^:]+)(?::\\d+)?;(\\d+)
- replacement: $1:$2
- source_labels:
- - __address__
- - __meta_kubernetes_service_annotation_prometheus_io_port
- target_label: __address__
- - action: labelmap
- regex: __meta_kubernetes_service_label_(.+)
- - action: replace
- source_labels:
- - __meta_kubernetes_namespace
- target_label: kubernetes_namespace
- - action: replace
- source_labels:
- - __meta_kubernetes_service_name
- target_label: kubernetes_name
- - job_name: kubernetes-services
- kubernetes_sd_configs:
- - role: service
- metrics_path: /probe
- params:
- module:
- - http_2xx
- relabel_configs:
- - action: keep
- regex: true
- source_labels:
- - __meta_kubernetes_service_annotation_prometheus_io_probe
- - source_labels:
- - __address__
- target_label: __param_target
- - replacement: blackbox-exporter.example.com:9115
- target_label: __address__
- - source_labels:
- - __param_target
- target_label: app
- - action: labelmap
- regex: __meta_kubernetes_service_label_(.+)
- - source_labels:
- - __meta_kubernetes_namespace
- target_label: kubernetes_namespace
- - source_labels:
- - __meta_kubernetes_service_name
- target_label: kubernetes_name
- - job_name: kubernetes-ingresses
- kubernetes_sd_configs:
- - role: ingress
- metrics_path: /probe
- params:
- module:
- - http_2xx
- relabel_configs:
- - action: keep
- regex: true
- source_labels:
- - __meta_kubernetes_ingress_annotation_prometheus_io_probe
- - regex: (.+);(.+);(.+)
- replacement: ${1}://${2}${3}
- source_labels:
- - __meta_kubernetes_ingress_scheme
- - __address__
- - __meta_kubernetes_ingress_path
- target_label: __param_target
- - replacement: blackbox-exporter.example.com:9115
- target_label: __address__
- - source_labels:
- - __param_target
- target_label: app
- - action: labelmap
- regex: __meta_kubernetes_ingress_label_(.+)
- - source_labels:
- - __meta_kubernetes_namespace
- target_label: kubernetes_namespace
- - source_labels:
- - __meta_kubernetes_ingress_name
- target_label: kubernetes_name
- - job_name: kubernetes-pods
- kubernetes_sd_configs:
- - role: pod
- relabel_configs:
- - action: keep
- regex: true
- source_labels:
- - __meta_kubernetes_pod_annotation_prometheus_io_scrape
- - action: replace
- regex: (.+)
- source_labels:
- - __meta_kubernetes_pod_annotation_prometheus_io_path
- target_label: __metrics_path__
- - action: replace
- regex: ([^:]+)(?::\\d+)?;(\\d+)
- replacement: $1:$2
- source_labels:
- - __address__
- - __meta_kubernetes_pod_annotation_prometheus_io_port
- target_label: __address__
- - action: labelmap
- regex: __meta_kubernetes_pod_label_(.+)
- - action: replace
- source_labels:
- - __meta_kubernetes_namespace
- target_label: kubernetes_namespace
- - action: replace
- source_labels:
- - __meta_kubernetes_pod_name
- target_label: kubernetes_pod_name
+ - scheme: https
+ job_name: kubernetes-apiservers
+ kubernetes_sd_configs:
+ - role: endpoints
+ tls_config:
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ relabel_configs:
+ - action: keep
+ source_labels:
+ - __meta_kubernetes_namespace
+ - __meta_kubernetes_service_name
+ - __meta_kubernetes_endpoint_port_name
+ regex: default;kubernetes;https
+ - scheme: https
+ job_name: kubernetes-nodes
+ kubernetes_sd_configs:
+ - role: node
+ tls_config:
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ relabel_configs:
+ - action: labelmap
+ regex: __meta_kubernetes_node_label_(.+)
+ - target_label: __address__
+ replacement: kubernetes.default.svc:443
+ - source_labels:
+ - __meta_kubernetes_node_name
+ regex: (.+)
+ target_label: __metrics_path__
+ replacement: /api/v1/nodes/${1}/proxy/metrics
+ - scheme: https
+ job_name: kubernetes-cadvisor
+ kubernetes_sd_configs:
+ - role: node
+ tls_config:
+ ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ relabel_configs:
+ - action: labelmap
+ regex: __meta_kubernetes_node_label_(.+)
+ - target_label: __address__
+ replacement: kubernetes.default.svc:443
+ - source_labels:
+ - __meta_kubernetes_node_name
+ regex: (.+)
+ target_label: __metrics_path__
+ replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
+ - job_name: kubernetes-service-endpoints
+ kubernetes_sd_configs:
+ - role: endpoints
+ relabel_configs:
+ - action: keep
+ source_labels:
+ - __meta_kubernetes_service_annotation_prometheus_io_scrape
+ regex: true
+ - action: replace
+ source_labels:
+ - __meta_kubernetes_service_annotation_prometheus_io_scheme
+ regex: (https?)
+ target_label: __scheme__
+ - action: replace
+ source_labels:
+ - __meta_kubernetes_service_annotation_prometheus_io_path
+ regex: (.+)
+ target_label: __metrics_path__
+ - action: replace
+ source_labels:
+ - __address__
+ - __meta_kubernetes_service_annotation_prometheus_io_port
+ regex: ([^:]+)(?::\\d+)?;(\\d+)
+ target_label: __address__
+ replacement: $1:$2
+ - action: labelmap
+ regex: __meta_kubernetes_service_label_(.+)
+ - action: replace
+ source_labels:
+ - __meta_kubernetes_namespace
+ target_label: kubernetes_namespace
+ - action: replace
+ source_labels:
+ - __meta_kubernetes_service_name
+ target_label: kubernetes_name
+ - job_name: kubernetes-services
+ kubernetes_sd_configs:
+ - role: service
+ relabel_configs:
+ - action: keep
+ source_labels:
+ - __meta_kubernetes_service_annotation_prometheus_io_probe
+ regex: true
+ - source_labels:
+ - __address__
+ target_label: __param_target
+ - target_label: __address__
+ replacement: blackbox-exporter.example.com:9115
+ - source_labels:
+ - __param_target
+ target_label: app
+ - action: labelmap
+ regex: __meta_kubernetes_service_label_(.+)
+ - source_labels:
+ - __meta_kubernetes_namespace
+ target_label: kubernetes_namespace
+ - source_labels:
+ - __meta_kubernetes_service_name
+ target_label: kubernetes_name
+ metrics_path: /probe
+ params:
+ module:
+ - http_2xx
+ - job_name: kubernetes-ingresses
+ kubernetes_sd_configs:
+ - role: ingress
+ relabel_configs:
+ - action: keep
+ source_labels:
+ - __meta_kubernetes_ingress_annotation_prometheus_io_probe
+ regex: true
+ - source_labels:
+ - __meta_kubernetes_ingress_scheme
+ - __address__
+ - __meta_kubernetes_ingress_path
+ regex: (.+);(.+);(.+)
+ target_label: __param_target
+ replacement: ${1}://${2}${3}
+ - target_label: __address__
+ replacement: blackbox-exporter.example.com:9115
+ - source_labels:
+ - __param_target
+ target_label: app
+ - action: labelmap
+ regex: __meta_kubernetes_ingress_label_(.+)
+ - source_labels:
+ - __meta_kubernetes_namespace
+ target_label: kubernetes_namespace
+ - source_labels:
+ - __meta_kubernetes_ingress_name
+ target_label: kubernetes_name
+ metrics_path: /probe
+ params:
+ module:
+ - http_2xx
+ - job_name: kubernetes-pods
+ kubernetes_sd_configs:
+ - role: pod
+ relabel_configs:
+ - action: keep
+ source_labels:
+ - __meta_kubernetes_pod_annotation_prometheus_io_scrape
+ regex: true
+ - action: replace
+ source_labels:
+ - __meta_kubernetes_pod_annotation_prometheus_io_path
+ regex: (.+)
+ target_label: __metrics_path__
+ - action: replace
+ source_labels:
+ - __address__
+ - __meta_kubernetes_pod_annotation_prometheus_io_port
+ regex: ([^:]+)(?::\\d+)?;(\\d+)
+ target_label: __address__
+ replacement: $1:$2
+ - action: labelmap
+ regex: __meta_kubernetes_pod_label_(.+)
+ - action: replace
+ source_labels:
+ - __meta_kubernetes_namespace
+ target_label: kubernetes_namespace
+ - action: replace
+ source_labels:
+ - __meta_kubernetes_pod_name
+ target_label: kubernetes_pod_name
"""
}
diff --git a/encoding/yaml/yaml.go b/encoding/yaml/yaml.go
index f972bd2..80c19b9 100644
--- a/encoding/yaml/yaml.go
+++ b/encoding/yaml/yaml.go
@@ -20,19 +20,13 @@
"bytes"
"io"
- goyaml "github.com/ghodss/yaml"
-
"cuelang.org/go/cue"
"cuelang.org/go/cue/ast"
+ cueyaml "cuelang.org/go/internal/encoding/yaml"
"cuelang.org/go/internal/third_party/yaml"
pkgyaml "cuelang.org/go/pkg/encoding/yaml"
)
-// TODO: replace the ghodss YAML encoder. It has a few major issues:
-// - it does not expose the underlying error, which means we lose valuable
-// information.
-// - comments and other meta data are lost.
-
// Extract parses the YAML to a CUE expression. Streams are returned as a list
// of the streamed values.
func Extract(filename string, src interface{}) (*ast.File, error) {
@@ -79,7 +73,8 @@
// Encode returns the YAML encoding of v.
func Encode(v cue.Value) ([]byte, error) {
- b, err := goyaml.Marshal(v)
+ n := v.Syntax(cue.Final())
+ b, err := cueyaml.Encode(n)
return b, err
}
@@ -92,7 +87,8 @@
if i > 0 {
buf.WriteString("---\n")
}
- b, err := goyaml.Marshal(iter.Value())
+ n := iter.Value().Syntax(cue.Final())
+ b, err := cueyaml.Encode(n)
if err != nil {
return nil, err
}
diff --git a/go.mod b/go.mod
index e96c5af..d2f461b 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,6 @@
github.com/cockroachdb/apd/v2 v2.0.1
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/emicklei/proto v1.6.15
- github.com/ghodss/yaml v1.0.0
github.com/google/go-cmp v0.4.0
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/kr/pretty v0.1.0
@@ -23,7 +22,7 @@
golang.org/x/text v0.3.2
golang.org/x/tools v0.0.0-20200113154838-30cae5f2fb06
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
- gopkg.in/yaml.v2 v2.2.2 // indirect
+ gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71
)
go 1.12
diff --git a/go.sum b/go.sum
index aa4e3c4..5537cf7 100644
--- a/go.sum
+++ b/go.sum
@@ -6,8 +6,6 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/proto v1.6.15 h1:XbpwxmuOPrdES97FrSfpyy67SSCV/wBIKXqgJzh6hNw=
github.com/emicklei/proto v1.6.15/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
-github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@@ -61,5 +59,5 @@
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo=
+gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/encoding/yaml/encode.go b/internal/encoding/yaml/encode.go
new file mode 100644
index 0000000..b0a247d
--- /dev/null
+++ b/internal/encoding/yaml/encode.go
@@ -0,0 +1,293 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package yaml
+
+import (
+ "math/big"
+ "strings"
+
+ "gopkg.in/yaml.v3"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/literal"
+ "cuelang.org/go/cue/token"
+ "cuelang.org/go/internal"
+)
+
+// Encode converts a CUE AST to YAML.
+//
+// The given file must only contain values that can be directly supported by
+// YAML:
+// Type Restrictions
+// BasicLit
+// File no imports, aliases, or definitions
+// StructLit no embeddings, aliases, or definitions
+// List
+// Field must be regular; label must be a BasicLit or Ident
+// CommentGroup
+//
+// TODO: support anchors through Ident.
+func Encode(n ast.Node) (b []byte, err error) {
+ y, err := encode(n)
+ if err != nil {
+ return nil, err
+ }
+ return yaml.Marshal(y)
+}
+
+func encode(n ast.Node) (y *yaml.Node, err error) {
+ switch x := n.(type) {
+ case *ast.BasicLit:
+ y, err = encodeScalar(x)
+
+ case *ast.ListLit:
+ y, err = encodeExprs(x.Elts)
+ line := x.Lbrack.Line()
+ if err == nil && line > 0 && line == x.Rbrack.Line() {
+ y.Style = yaml.FlowStyle
+ }
+
+ case *ast.StructLit:
+ y, err = encodeDecls(x.Elts)
+ line := x.Lbrace.Line()
+ if err == nil && line > 0 && line == x.Rbrace.Line() {
+ y.Style = yaml.FlowStyle
+ }
+
+ case *ast.File:
+ y, err = encodeDecls(x.Decls)
+
+ case *ast.UnaryExpr:
+ b, ok := x.X.(*ast.BasicLit)
+ if ok && x.Op == token.SUB && (b.Kind == token.INT || b.Kind == token.FLOAT) {
+ y, err = encodeScalar(b)
+ if !strings.HasPrefix(y.Value, "-") {
+ y.Value = "-" + y.Value
+ break
+ }
+ }
+ return nil, errors.Newf(x.Pos(), "yaml: unsupported node %s (%T)", internal.DebugStr(x), x)
+ default:
+ return nil, errors.Newf(x.Pos(), "yaml: unsupported node %s (%T)", internal.DebugStr(x), x)
+ }
+ if err != nil {
+ return nil, err
+ }
+ addDocs(n, y, y)
+ return y, nil
+}
+
+func encodeScalar(b *ast.BasicLit) (n *yaml.Node, err error) {
+ n = &yaml.Node{Kind: yaml.ScalarNode}
+
+ switch b.Kind {
+ case token.INT:
+ var x big.Int
+ if err := setNum(n, b.Value, &x); err != nil {
+ return nil, err
+ }
+
+ case token.FLOAT:
+ var x big.Float
+ if err := setNum(n, b.Value, &x); err != nil {
+ return nil, err
+ }
+
+ case token.TRUE, token.FALSE, token.NULL:
+ n.Value = b.Value
+
+ case token.STRING:
+ str, err := literal.Unquote(b.Value)
+ if err != nil {
+ return nil, err
+ }
+ n.SetString(str)
+
+ default:
+ return nil, errors.Newf(b.Pos(), "unknown literal type %v", b.Kind)
+ }
+ return n, nil
+}
+
+func setNum(n *yaml.Node, s string, x interface{}) error {
+ if yaml.Unmarshal([]byte(s), x) == nil {
+ n.Value = s
+ return nil
+ }
+
+ var ni literal.NumInfo
+ if err := literal.ParseNum(s, &ni); err != nil {
+ return err
+ }
+ n.Value = ni.String()
+ return nil
+}
+
+func encodeExprs(exprs []ast.Expr) (n *yaml.Node, err error) {
+ n = &yaml.Node{Kind: yaml.SequenceNode}
+
+ for _, elem := range exprs {
+ e, err := encode(elem)
+ if err != nil {
+ return nil, err
+ }
+ n.Content = append(n.Content, e)
+ }
+ return n, nil
+}
+
+// encodeDecls converts a sequence of declarations to a value. If it encounters
+// an embedded value, it will return this expression. This is more relaxed for
+// structs than is currently allowed for CUE, but the expectation is that this
+// will be allowed at some point. The input would still be illegal CUE.
+func encodeDecls(decls []ast.Decl) (n *yaml.Node, err error) {
+ n = &yaml.Node{Kind: yaml.MappingNode}
+
+ docForNext := strings.Builder{}
+ var lastHead, lastFoot *yaml.Node
+ hasEmbed := false
+ for _, d := range decls {
+ switch x := d.(type) {
+ default:
+ return nil, errors.Newf(x.Pos(), "yaml: unsupported node %s (%T)", internal.DebugStr(x), x)
+
+ case *ast.Package:
+ if len(n.Content) > 0 {
+ return nil, errors.Newf(x.Pos(), "invalid package clause")
+ }
+ continue
+
+ case *ast.CommentGroup:
+ docForNext.WriteString(docToYAML(x))
+ docForNext.WriteString("\n\n")
+ continue
+
+ case *ast.Field:
+ if x.Token == token.ISA {
+ return nil, errors.Newf(x.TokenPos, "yaml: definition not allowed")
+ }
+ if x.Optional != token.NoPos {
+ return nil, errors.Newf(x.Optional, "yaml: optional fields not allowed")
+ }
+ if hasEmbed {
+ return nil, errors.Newf(x.TokenPos, "yaml: embedding mixed with fields")
+ }
+ name, _, err := ast.LabelName(x.Label)
+ if err != nil {
+ return nil, errors.Newf(x.Label.Pos(), "yaml: only literal labels allowed")
+ }
+
+ label := &yaml.Node{}
+ addDocs(x.Label, label, label)
+ label.SetString(name)
+
+ value, err := encode(x.Value)
+ if err != nil {
+ return nil, err
+ }
+ lastHead = label
+ lastFoot = value
+ addDocs(x, label, value)
+ n.Content = append(n.Content, label)
+ n.Content = append(n.Content, value)
+
+ case *ast.EmbedDecl:
+ if hasEmbed {
+ return nil, errors.Newf(x.Pos(), "yaml: multiple embedded values")
+ }
+ hasEmbed = true
+ e, err := encode(x.Expr)
+ if err != nil {
+ return nil, err
+ }
+ addDocs(x, e, e)
+ lastHead = e
+ lastFoot = e
+ n.Content = append(n.Content, e)
+ }
+ if docForNext.Len() > 0 {
+ docForNext.WriteString(lastHead.HeadComment)
+ lastHead.HeadComment = docForNext.String()
+ docForNext.Reset()
+ }
+ }
+
+ if docForNext.Len() > 0 && lastFoot != nil {
+ if !strings.HasSuffix(lastFoot.FootComment, "\n") {
+ lastFoot.FootComment += "\n"
+ }
+ n := docForNext.Len()
+ lastFoot.FootComment += docForNext.String()[:n-1]
+ }
+
+ if hasEmbed {
+ return n.Content[0], nil
+ }
+
+ return n, nil
+}
+
+// addDocs prefixes head, replaces line and appends foot comments.
+func addDocs(n ast.Node, h, f *yaml.Node) {
+ head := ""
+ isDoc := false
+ for _, c := range ast.Comments(n) {
+ switch {
+ case c.Line:
+ f.LineComment = docToYAML(c)
+
+ case c.Position > 0:
+ if f.FootComment != "" {
+ f.FootComment += "\n\n"
+ } else if relPos := c.Pos().RelPos(); relPos == token.NewSection {
+ f.FootComment += "\n"
+ }
+ f.FootComment += docToYAML(c)
+
+ default:
+ if head != "" {
+ head += "\n\n"
+ }
+ head += docToYAML(c)
+ isDoc = isDoc || c.Doc
+ }
+ }
+
+ if head != "" {
+ if h.HeadComment != "" || !isDoc {
+ head += "\n\n"
+ }
+ h.HeadComment = head + h.HeadComment
+ }
+}
+
+// docToYAML converts a CUE CommentGroup to a YAML comment string. This ensures
+// that comments with empty lines get properly converted.
+func docToYAML(c *ast.CommentGroup) string {
+ s := c.Text()
+ if strings.HasSuffix(s, "\n") { // always true
+ s = s[:len(s)-1]
+ }
+ lines := strings.Split(s, "\n")
+ for i, l := range lines {
+ if l == "" {
+ lines[i] = "#"
+ } else {
+ lines[i] = "# " + l
+ }
+ }
+ return strings.Join(lines, "\n")
+}
diff --git a/internal/encoding/yaml/encode_test.go b/internal/encoding/yaml/encode_test.go
new file mode 100644
index 0000000..c73a2cc
--- /dev/null
+++ b/internal/encoding/yaml/encode_test.go
@@ -0,0 +1,343 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package yaml
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/kr/pretty"
+ "gopkg.in/yaml.v3"
+
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/parser"
+)
+
+func TestEncodeFile(t *testing.T) {
+ testCases := []struct {
+ name string
+ in string
+ out string
+ }{{
+ name: "foo",
+ in: `
+ package test
+
+ seq: [
+ 1, 2, 3, {
+ a: 1
+ b: 2
+ }
+ ]
+ a: b: c: 3
+ b: {
+ x: 0
+ y: 1
+ z: 2
+ }
+ `,
+ out: `
+seq:
+ - 1
+ - 2
+ - 3
+ - a: 1
+ b: 2
+a:
+ b:
+ c: 3
+b:
+ x: 0
+ y: 1
+ z: 2
+ `,
+ }, {
+ name: "oneLineFields",
+ in: `
+ seq: [1, 2, 3]
+ map: {a: 3}
+ str: "str"
+ int: 1K
+ bin: 0b11
+ hex: 0x11
+ oct: 0o11
+ dec: .3
+ dat: '\x80'
+ nil: null
+ yes: true
+ non: false
+ `,
+ out: `
+seq: [1, 2, 3]
+map: {a: 3}
+str: str
+int: 1000
+bin: 0b11
+hex: 0x11
+oct: 0o11
+dec: .3
+dat: !!binary gA==
+nil: null
+yes: true
+non: false
+`,
+ }, {
+ name: "comments",
+ in: `
+// Document
+
+// head 1
+f1: 1
+// foot 1
+
+// head 2
+f2: 2 // line 2
+
+// intermezzo f2
+//
+// with multiline
+
+// head 3
+f3:
+ // struct doc
+ {
+ a: 1
+ }
+
+f4: {
+} // line 4
+
+// Trailing
+`,
+ out: `
+# Document
+
+# head 1
+f1: 1
+# foot 1
+
+# head 2
+f2: 2 # line 2
+
+# intermezzo f2
+#
+# with multiline
+
+# head 3
+f3:
+ # struct doc
+ a: 1
+f4: {} # line 4
+
+# Trailing
+`,
+ }, {
+ // TODO: support this at some point
+ name: "embed",
+ in: `
+ // octal
+ 0o755 // line
+ // trail
+ `,
+ out: `
+# octal
+0o755 # line
+# trail
+`,
+ }, {
+ // TODO: support this at some point
+ name: "anchors",
+ in: `
+ a: b
+ b: 3
+ `,
+ out: "yaml: unsupported node b (*ast.Ident)",
+ }, {
+ name: "errors",
+ in: `
+ m: {
+ a: 1
+ b: 3
+ }
+ c: [1, [ x for x in m ]]
+ `,
+ out: "yaml: unsupported node [x for x in m ] (*ast.ListComprehension)",
+ }, {
+ name: "disallowMultipleEmbeddings",
+ in: `
+ 1
+ 1
+ `,
+ out: "yaml: multiple embedded values",
+ }, {
+ name: "disallowDefinitions",
+ in: `a :: 2 `,
+ out: "yaml: definition not allowed",
+ }, {
+ name: "disallowOptionals",
+ in: `a?: 2`,
+ out: "yaml: optional fields not allowed",
+ }, {
+ name: "disallowBulkOptionals",
+ in: `[string]: 2`,
+ out: "yaml: only literal labels allowed",
+ }, {
+ name: "noImports",
+ in: `
+ import "foo"
+
+ a: 1
+ `,
+ out: `yaml: unsupported node import "foo" (*ast.ImportDecl)`,
+ }, {
+ name: "disallowMultipleEmbeddings",
+ in: `
+ 1
+ a: 2
+ `,
+ out: "yaml: embedding mixed with fields",
+ }, {
+ name: "prometheus",
+ in: `
+ {
+ receivers: [{
+ name: "pager"
+ slack_configs: [{
+ text: """
+ {{ range .Alerts }}{{ .Annotations.description }}
+ {{ end }}
+ """
+ channel: "#cloudmon"
+ send_resolved: true
+ }]
+ }]
+ route: {
+ receiver: "pager"
+ group_by: ["alertname", "cluster"]
+ }
+ }`,
+ out: `
+receivers:
+ - name: pager
+ slack_configs:
+ - text: |-
+ {{ range .Alerts }}{{ .Annotations.description }}
+ {{ end }}
+ channel: '#cloudmon'
+ send_resolved: true
+route:
+ receiver: pager
+ group_by: [alertname, cluster]
+ `,
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ f, err := parser.ParseFile(tc.name, tc.in, parser.ParseComments)
+ if err != nil {
+ t.Fatal(err)
+ }
+ b, err := Encode(f)
+ var got string
+ if err != nil {
+ got = err.Error()
+ } else {
+ got = strings.TrimSpace(string(b))
+ }
+ want := strings.TrimSpace(tc.out)
+ if got != want {
+ t.Error(cmp.Diff(got, want))
+ }
+ })
+ }
+}
+
+func TestEncodeAST(t *testing.T) {
+ comment := func(s string) *ast.CommentGroup {
+ return &ast.CommentGroup{List: []*ast.Comment{
+ &ast.Comment{Text: "// " + s},
+ }}
+ }
+ testCases := []struct {
+ name string
+ in ast.Expr
+ out string
+ }{{
+ in: ast.NewStruct(
+ comment("foo"),
+ comment("bar"),
+ "field", ast.NewString("value"),
+ "field2", ast.NewString("value"),
+ comment("trail1"),
+ comment("trail2"),
+ ),
+ out: `
+# foo
+
+# bar
+
+field: value
+field2: value
+
+# trail1
+
+# trail2
+ `,
+ }, {
+ in: &ast.StructLit{Elts: []ast.Decl{
+ comment("bar"),
+ &ast.EmbedDecl{Expr: ast.NewBool(true)},
+ }},
+ out: `
+# bar
+
+true
+ `,
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ n, err := encode(tc.in)
+ if err != nil {
+ t.Fatal(err)
+ }
+ b, err := yaml.Marshal(n)
+ if err != nil {
+ t.Fatal(err)
+ }
+ got := strings.TrimSpace(string(b))
+ want := strings.TrimSpace(tc.out)
+ if got != want {
+ t.Error(cmp.Diff(got, want))
+ }
+ })
+ }
+}
+
+// TestX is for experimentation with the YAML package to figure out the
+// semantics of the Node type.
+func TestX(t *testing.T) {
+ t.Skip()
+ var n yaml.Node
+ yaml.Unmarshal([]byte(`
+# map
+map: {
+ # doc
+} # line 1
+
+`), &n)
+
+ pretty.Print(n)
+ t.Fail()
+}
diff --git a/pkg/encoding/yaml/manual.go b/pkg/encoding/yaml/manual.go
index 053ab6b..4697982 100644
--- a/pkg/encoding/yaml/manual.go
+++ b/pkg/encoding/yaml/manual.go
@@ -18,17 +18,23 @@
"bytes"
"io"
- goyaml "github.com/ghodss/yaml"
-
"cuelang.org/go/cue"
"cuelang.org/go/cue/ast"
"cuelang.org/go/internal"
+ cueyaml "cuelang.org/go/internal/encoding/yaml"
"cuelang.org/go/internal/third_party/yaml"
)
// Marshal returns the YAML encoding of v.
func Marshal(v cue.Value) (string, error) {
- b, err := goyaml.Marshal(v)
+ if err := v.Validate(cue.Concrete(true)); err != nil {
+ if err := v.Validate(); err != nil {
+ return "", err
+ }
+ return "", internal.ErrIncomplete
+ }
+ n := v.Syntax(cue.Final())
+ b, err := cueyaml.Encode(n)
return string(b), err
}
@@ -44,7 +50,15 @@
if i > 0 {
buf.WriteString("---\n")
}
- b, err := goyaml.Marshal(iter.Value())
+ v := iter.Value()
+ if err := v.Validate(cue.Concrete(true)); err != nil {
+ if err := v.Validate(); err != nil {
+ return "", err
+ }
+ return "", internal.ErrIncomplete
+ }
+ n := v.Syntax(cue.Final())
+ b, err := cueyaml.Encode(n)
if err != nil {
return "", err
}