Repository: NetApp/harvest
Pull request #713 (2021-11-30 13:00:42)

feat: Nightly build publish

rahulguptajss: TODO: Need better text for release
How does it impact release page?
Shall we publish docker nightly also?

Patch:
diff --git a/cmd/poller/collector/helpers.go b/cmd/poller/collector/helpers.go
index b8b7f4b2..a531277b 100644
--- a/cmd/poller/collector/helpers.go
+++ b/cmd/poller/collector/helpers.go
@@ -29,12 +29,12 @@ import (
 )
 
 // ImportTemplate retrieves the config (template) of a collector, arguments are:
-// @confDir			- path of Harvest config durectory (usually /etc/harvest)
+// @confDir			- path of Harvest config directory (usually /etc/harvest)
 // @confFn			- template filename (e.g. default.yaml or custom.yaml)
 // @collectorName	- name of the collector
 func ImportTemplate(confPath, confFn, collectorName string) (*node.Node, error) {
 	fp := path.Join(confPath, "conf/", strings.ToLower(collectorName), confFn)
-	return tree.Import("yaml", fp)
+	return tree.ImportYaml(fp)
 }
 
 // ImportSubTemplate retrieves the best matching subtemplate of a collector object.
@@ -105,7 +105,7 @@ func (c *AbstractCollector) ImportSubTemplate(model, filename string, ver [3]int
 			verS, err := version.NewVersion(verWithDots)
 			if err != nil {
 				c.Logger.Trace().Msgf("error parsing ONTAP version: %s err: %s", verWithDots, err)
-				return nil, errors.New("No best-fitting subtemplate version found")
+				return nil, errors.New("no best-fitting subtemplate version found")
 			}
 			// get closest index
 			idx := getClosestIndex(versions, verS)
@@ -115,18 +115,18 @@ func (c *AbstractCollector) ImportSubTemplate(model, filename string, ver [3]int
 		}
 
 		if selectedVersion == "" {
-			return nil, errors.New("No best-fit template found")
+			return nil, errors.New("no best-fit template found")
 		}
 
 		subTemplateFp = path.Join(pathPrefix, selectedVersion, f)
 		c.Logger.Info().Msgf("best-fit template [%s] for [%s]", subTemplateFp, verWithDots)
 		if finalTemplate == nil {
-			finalTemplate, err = tree.Import("yaml", subTemplateFp)
+			finalTemplate, err = tree.ImportYaml(subTemplateFp)
 			if err == nil {
 				finalTemplate.PreprocessTemplate()
 			}
 		} else {
-			tempTemplate, err = tree.Import("yaml", subTemplateFp)
+			tempTemplate, err = tree.ImportYaml(subTemplateFp)
 			if err == nil {
 				tempTemplate.PreprocessTemplate()
 				// merge templates
diff --git a/cmd/poller/plugin/README.md b/cmd/poller/plugin/README.md
index 897b8042..d4e2ca93 100644
--- a/cmd/poller/plugin/README.md
+++ b/cmd/poller/plugin/README.md
@@ -33,7 +33,7 @@ plugins:
 # will aggregate a new Matrix based on target label LABEL
 ```
 
-If you want to speficy which labels should be included in the new instances, you can add those space-seperated after `LABEL`:
+If you want to specify which labels should be included in the new instances, you can add those space-seperated after `LABEL`:
 
 ```yaml
     - LABEL LABEL1,LABEL2
@@ -47,7 +47,7 @@ Or include all labels:
     - LABEL ...
 # copy all labels of the original instance
 ```
-By default aggregated metrics will be prefixed with `LABEL`. For example if the object of the original Matrix is `volume` (meaning metrics are prefixed with `volume_`) and `LABEL` is `aggr`, then the metric `volume_read_ops` will become `aggr_volume_read_ops`, etc. You can override this by providing the `<>OBJ` using the following syntax:
+By default, aggregated metrics will be prefixed with `LABEL`. For example if the object of the original Matrix is `volume` (meaning metrics are prefixed with `volume_`) and `LABEL` is `aggr`, then the metric `volume_read_ops` will become `aggr_volume_read_ops`, etc. You can override this by providing the `<>OBJ` using the following syntax:
 
 ```yaml
     - LABEL<>OBJ
@@ -71,14 +71,13 @@ Examples:
 ```yaml
 plugins:
   Aggregator:
+    # will aggregate metrics of the aggregate. The labels "node" and "type" are included in the new instances
     - aggr node type
-# will aggregate metrics of Anggregate level and make sure
-# labels "node" and "type" are included in the new instances
+    # aggregate instances if label "type" has value "flexgroup"
+    # include all original labels
     - type<flexgroup> ...
-# aggregate instances if label "type" has value "flexgroup"
-# include all original labels
+    # aggregate all instances if value of "volume" ends with underscore and 4 digits
     - volume<`_\d{4}$`>
-# aggregate all instances if value of "volume" ends with underscore and 4 digits
 ```
 
 ### Aggregation rules
@@ -94,7 +93,7 @@ The plugin tries to intelligently aggregate metrics based on a few rules:
 - **Ignore** - metrics created by some plugins, such as value_to_num by LabelAgent
 
 # LabelAgent
-LabelAgent allows to manipulate instance labels based on rules. You can define multiple rules, here is an example of what you could add to the yaml file of a collector:
+LabelAgent are used to manipulate instance labels based on rules. You can define multiple rules, here is an example of what you could add to the yaml file of a collector:
 
 
 ```yaml
@@ -130,23 +129,23 @@ Rule syntax:
 
 ```yaml
 split: LABEL `SEP` LABEL1,LABEL2,LABEL3
-# source label - seperator - comma-seperated target labels
+# source label - separator - comma-seperated target labels
 ```
 
-Splits the value of a given label by seperator `SEP` and creates new labels if their number matches to the number of target labels defined in rule. To discard a subvalue, just add a redundant `,` in the names of the target labels. 
+Splits the value of a given label by separator `SEP` and creates new labels if their number matches to the number of target labels defined in rule. To discard a subvalue, just add a redundant `,` in the names of the target labels. 
 
 Example:
 
 ```yaml
 split: node `/` ,aggr,plex,disk
-# will split the value of "node" using seperator "/"
+# will split the value of "node" using separator "/"
 # will expect 4 values: first will be discarded, remaining
 # three will be stored as labels "aggr", "plex" and "disk"
 ```
 
 ## split_regex
 
-Does the same as `split_regex` but uses a regular expression instead of a seperator.
+Does the same as `split_regex` but uses a regular expression instead of a separator.
 
 Rule syntax:
 
@@ -170,7 +169,7 @@ Rule syntax:
 
 ```yaml
 split_pairs: LABEL `SEP1` `SEP2`
-# source label - pair seperator - key-value seperator
+# source label - pair separator - key-value separator
 ```
 
 Extracts key-value pairs from the value of source label `LABEL`. Note that you need to add these keys in the export options, otherwise they will not be exported.
@@ -186,13 +185,13 @@ split_pairs: comment ` ` `:`
 
 ## join
 
-Join multiple label values using seperator `SEP` and create a new label. 
+Join multiple label values using separator `SEP` and create a new label. 
 
 Rule syntax:
 
 ```yaml
 join: LABEL `SEP` LABEL1,LABEL2,LABEL3
-# target label - seperator - comma-seperated source labels
+# target label - separator - comma-seperated source labels
 ```
 
 Example:
@@ -307,7 +306,7 @@ value_mapping was deprecated in 21.11 and removed in 22.02. Use [value_to_num ma
 
 ## value_to_num
 
-Maps values of a given label to a numeric metric (of type `uint8`).
+Map values of a given label to a numeric metric (of type `uint8`).
 This rule maps values of a given label to a numeric metric (of type `unit8`). Healthy is mapped to 1 and all non-healthy values are mapped to 0.
 
 This is handy to manipulate the data in the DB or Grafana (e.g. change color based on status or create alert).
@@ -318,8 +317,8 @@ Rule syntax:
 
 ```yaml
 value_to_num: METRIC LABEL ZAPI_VALUE REST_VALUE `N`
-# maps values of LABEL to 1 if it is ZAPI_VALUE or REST_VALUE
-# otherwise value of METRIC is set to N
+# map values of LABEL to 1 if it is ZAPI_VALUE or REST_VALUE
+# otherwise, value of METRIC is set to N
 ```
 The default value `N` is optional, if no default value is given and the label value does not match any of the given values, the metric value will not be set.
 
@@ -341,6 +340,6 @@ value_to_num: status state up online `4`
 ```yaml
 value_to_num: status outage - - `0` #ok_value is empty value. 
 # metric value will be set to 1 if "outage" is empty, if it's any other value, it will be set to the default, 0
-# '-' is a special symbol in this mapping and it will be converted to blank while processing.
+# '-' is a special symbol in this mapping, and it will be converted to blank while processing.
 ```
 
diff --git a/cmd/poller/plugin/label_agent/parse_rules.go b/cmd/poller/plugin/label_agent/parse_rules.go
index 10e2ca17..d4cb8a18 100644
--- a/cmd/poller/plugin/label_agent/parse_rules.go
+++ b/cmd/poller/plugin/label_agent/parse_rules.go
@@ -55,7 +55,9 @@ func (me *LabelAgent) parseRules() int {
 			case "value_to_num":
 				me.parseValueToNumRule(rule)
 			default:
-				me.Logger.Warn().Msgf("unknown rule (%s)", name)
+				me.Logger.Warn().
+					Str("object", me.ParentParams.GetChildContentS("object")).
+					Str("name", name).Msg("Unknown rule name")
 			}
 		}
 	}
diff --git a/cmd/tools/doctor/doctor.go b/cmd/tools/doctor/doctor.go
index 8af716d3..ed20b6dd 100644
--- a/cmd/tools/doctor/doctor.go
+++ b/cmd/tools/doctor/doctor.go
@@ -49,12 +49,12 @@ func doMergeCmd(_ *cobra.Command, _ []string) {
 }
 
 func doMerge(path1 string, path2 string) {
-	template, err := tree.Import("yaml", path1)
+	template, err := tree.ImportYaml(path1)
 	if err != nil {
 		fmt.Printf("error reading template file [%s]. err=%+v\n", path1, err)
 		return
 	}
-	subTemplate, err := tree.Import("yaml", path2)
+	subTemplate, err := tree.ImportYaml(path2)
 	if err != nil {
 		fmt.Printf("error reading template file [%s] err=%+v\n", path2, err)
 		return
diff --git a/conf/rest/9.13.0/volume.yaml b/conf/rest/9.13.0/volume.yaml
index 84d273f4..e2cd94e5 100644
--- a/conf/rest/9.13.0/volume.yaml
+++ b/conf/rest/9.13.0/volume.yaml
@@ -57,7 +57,7 @@ plugins:
     replace:
       - style style `flexgroup_constituent` `flexgroup`
   Aggregator:
-    volume<style=flexgroup>volume node,svm,aggr,style
+    - volume<style=flexgroup>volume node,svm,aggr,style
 
 export_options:
   instance_keys:
diff --git a/conf/zapi/7mode/8.6.0/shelf.yaml b/conf/zapi/7mode/8.6.0/shelf.yaml
index ba6c6ef6..28d9a7fb 100644
--- a/conf/zapi/7mode/8.6.0/shelf.yaml
+++ b/conf/zapi/7mode/8.6.0/shelf.yaml
@@ -26,56 +26,56 @@ plugins:
   Shelf:
     objects:
       - cooling-element-list => fan:
-        cooling-element-info:
-          - ^^cooling-element-number => fan_id
-          - ^location  #fake
-          - rpm
-#          - ^cooling-element-is-error
-#          - ^cooling-element-is-not-installed
+          cooling-element-info:
+            - ^^cooling-element-number => fan_id
+            - ^location  #fake
+            - rpm
+  #          - ^cooling-element-is-error
+  #          - ^cooling-element-is-not-installed
       - current-sensor-list => sensor:
-        current-sensor-info:
-          - ^^current-sensor-no => sensor_id
-          - ^location #fake
-          - sensor-reading => reading
-#        - ^is-sensor-error
-#        - ^is-sensor-not-installed
-#        - ^sensor-condition
+          current-sensor-info:
+            - ^^current-sensor-no => sensor_id
+            - ^location #fake
+            - sensor-reading => reading
+  #        - ^is-sensor-error
+  #        - ^is-sensor-not-installed
+  #        - ^sensor-condition
       - temp-sensor-list => temperature:
-        temp-sensor-info:
-          - ^^temp-sensor-element-no => sensor_id
-          - temp-sensor-current-temperature => reading
-          - ^temp-sensor-hi-critical => high_critical
-          - ^temp-sensor-hi-warning => high_warning
-          - ^temp-sensor-is-ambient => temp_is_ambient
-          - ^temp-sensor-low-critical => low_critical
-          - ^temp-sensor-low-warning => low_warning
-#        - ^temp-sensor-is-error
-#        - ^temp-sensor-is-not-installed
-#        - ^temp-sensor-current-condition
+          temp-sensor-info:
+            - ^^temp-sensor-element-no => sensor_id
+            - temp-sensor-current-temperature => reading
+            - ^temp-sensor-hi-critical => high_critical
+            - ^temp-sensor-hi-warning => high_warning
+            - ^temp-sensor-is-ambient => temp_is_ambient
+            - ^temp-sensor-low-critical => low_critical
+            - ^temp-sensor-low-warning => low_warning
+  #        - ^temp-sensor-is-error
+  #        - ^temp-sensor-is-not-installed
+  #        - ^temp-sensor-current-condition
       - voltage-sensor-list => voltage:
-        voltage-sensor-info:
-          - ^^voltage-sensor-no => sensor_id
-          - sensor-reading => reading
-          - ^location #fake
-#        - ^is-sensor-error
-#        - ^is-sensor-not-installed
-#        - ^sensor-condition
+          voltage-sensor-info:
+            - ^^voltage-sensor-no => sensor_id
+            - sensor-reading => reading
+            - ^location #fake
+  #        - ^is-sensor-error
+  #        - ^is-sensor-not-installed
+  #        - ^sensor-condition
 
       - power-supply-list => psu:
-        power-supply-info:
-          - ^^power-supply-element-number => psu_id
-          - ^power-supply-part-no => part_number
-          - ^power-supply-serial-no => serial
-        #  - power-supply-swap-count => psu_swap_count
-          - ^power-supply-type => type
-        # fake counters to match CDOT
-          - ^enabled
-          - ^location
-#        - ^is-auto-power-reset-enabled
-#        - ^is-power-reset-capable
-#        - ^power-supply-firmware-revision
-#        - ^power-supply-is-error
-#        - ^power-supply-is-not-installed
+          power-supply-info:
+            - ^^power-supply-element-number => psu_id
+            - ^power-supply-part-no => part_number
+            - ^power-supply-serial-no => serial
+          #  - power-supply-swap-count => psu_swap_count
+            - ^power-supply-type => type
+          # fake counters to match CDOT
+            - ^enabled
+            - ^location
+  #        - ^is-auto-power-reset-enabled
+  #        - ^is-power-reset-capable
+  #        - ^power-supply-firmware-revision
+  #        - ^power-supply-is-error
+  #        - ^power-supply-is-not-installed
   LabelAgent:
     # metric label zapi_value rest_value `default_value`
     value_to_num:
diff --git a/conf/zapi/cdot/9.8.0/shelf.yaml b/conf/zapi/cdot/9.8.0/shelf.yaml
index 12f57419..bcc08560 100644
--- a/conf/zapi/cdot/9.8.0/shelf.yaml
+++ b/conf/zapi/cdot/9.8.0/shelf.yaml
@@ -20,47 +20,47 @@ plugins:
   Shelf:
     objects:
       - cooling-fans => fan:
-        storage-shelf-cooling-fan-info:
-          - ^^fan-id
-          - ^fan-location => location
-          - ^fan-op-status => status
-          - fan-rpm => rpm
+          storage-shelf-cooling-fan-info:
+            - ^^fan-id
+            - ^fan-location => location
+            - ^fan-op-status => status
+            - fan-rpm => rpm
       - current-sensors => sensor:
-        storage-shelf-current-sensor-info:
-          - ^^current-sensor-id    => sensor_id
-          - ^current-sensor-location => location
-          - ^current-op-status => status
-          - current-sensor-reading => reading
+          storage-shelf-current-sensor-info:
+            - ^^current-sensor-id    => sensor_id
+            - ^current-sensor-location => location
+            - ^current-op-status => status
+            - current-sensor-reading => reading
       - power-supply-units => psu:
-        storage-shelf-power-supply-unit-info:
-#          - ^psu-crest-factor => crest_factor
-#          - ^psu-fw-version => fw_version
-          - ^^psu-id
-          - ^psu-is-enabled => enabled
-          - ^psu-location => location
-          - ^psu-part-number => part_number
-          - psu-power-drawn => power_drawn
-          - psu-power-rating => power_rating
-#          - ^psu-reset-capable => reset_capable
-          - ^psu-serial-number => serial
-          - ^psu-type => type
-          - ^psu-op-status => status
+          storage-shelf-power-supply-unit-info:
+  #          - ^psu-crest-factor => crest_factor
+  #          - ^psu-fw-version => fw_version
+            - ^^psu-id
+            - ^psu-is-enabled => enabled
+            - ^psu-location => location
+            - ^psu-part-number => part_number
+            - psu-power-drawn => power_drawn
+            - psu-power-rating => power_rating
+  #          - ^psu-reset-capable => reset_capable
+            - ^psu-serial-number => serial
+            - ^psu-type => type
+            - ^psu-op-status => status
       - temperature-sensors => temperature:
-        storage-shelf-temperature-sensor-info:
-          - ^high-critical-threshold => high_critical
-          - ^high-warning-threshold => high_warning
-          - ^temp-is-ambient
-          - ^temp-low-critical-threshold => low_critical
-          - ^temp-low-warning-threshold => low_warning
-          - ^^temp-sensor-id => sensor_id
-          - ^temp-sens-op-status => status
-          - temp-sensor-reading => reading
+          storage-shelf-temperature-sensor-info:
+            - ^high-critical-threshold => high_critical
+            - ^high-warning-threshold => high_warning
+            - ^temp-is-ambient
+            - ^temp-low-critical-threshold => low_critical
+            - ^temp-low-warning-threshold => low_warning
+            - ^^temp-sensor-id => sensor_id
+            - ^temp-sens-op-status => status
+            - temp-sensor-reading => reading
       - voltage-sensors => voltage:
-        storage-shelf-voltage-sensor-info:
-          - ^^voltage-sensor-id => sensor_id
-          - ^voltage-sensor-location => location
-          - ^voltage-op-status => status
-          - voltage-sensor-reading => reading
+          storage-shelf-voltage-sensor-info:
+            - ^^voltage-sensor-id => sensor_id
+            - ^voltage-sensor-location => location
+            - ^voltage-op-status => status
+            - voltage-sensor-reading => reading
 
   LabelAgent:
     # metric label zapi_value rest_value `default_value`
diff --git a/conf/zapi/cdot/9.8.0/volume.yaml b/conf/zapi/cdot/9.8.0/volume.yaml
index cbc16c0b..fffca1bf 100644
--- a/conf/zapi/cdot/9.8.0/volume.yaml
+++ b/conf/zapi/cdot/9.8.0/volume.yaml
@@ -68,7 +68,7 @@ plugins:
     replace:
       - style style `flexgroup_constituent` `flexgroup`
   Aggregator:
-    volume<style=flexgroup>volume node,svm,aggr,style
+    - volume<style=flexgroup>volume node,svm,aggr,style
 
 export_options:
   instance_keys:
diff --git a/conf/zapiperf/cdot/9.8.0/workload.yaml b/conf/zapiperf/cdot/9.8.0/workload.yaml
index 5ed2404c..df2b12a0 100644
--- a/conf/zapiperf/cdot/9.8.0/workload.yaml
+++ b/conf/zapiperf/cdot/9.8.0/workload.yaml
@@ -15,6 +15,7 @@ schedule:
   - data: 180s
 
 counters:
+  - concurrency
   - instance_name
   - instance_uuid
   - latency
diff --git a/jenkins/artifacts/jenkinsfile b/jenkins/artifacts/jenkinsfile
index bfee5370..e0a487e5 100644
--- a/jenkins/artifacts/jenkinsfile
+++ b/jenkins/artifacts/jenkinsfile
@@ -5,7 +5,7 @@ pipeline {
         string(name: 'VERSION', defaultValue: '', description: '[Optional] RPM Version should match Harvest version with rc or nightly stripped. If not filled default is YY.mm.ddHH. See https://github.com/NetApp/harvest/wiki/Release-Checklist#version-names for details')
         string(name: 'RELEASE', defaultValue: 'nightly', description: '[Optional] Example: nightly (default) See https://github.com/NetApp/harvest-private/wiki/Release-Checklist#rpm-and-debian-names for details ')
         string(name: 'BRANCH', defaultValue: 'main', description: '[Optional] Branch name to clone. Default (main) ')
-		string(name: 'ASUP_MAKE_TARGET', defaultValue: 'build', description: 'make target for AutoSupport binary build|production')
+		string(name: 'ASUP_MAKE_TARGET', defaultValue: 'production', description: 'make target for AutoSupport binary build|production')
 		string(name: 'DOCKER_PUBLISH', defaultValue: 'false', description: 'true to publish to dockerhub')
 		string(name: 'RUN_TEST', defaultValue: 'true', description: 'false to skip test')
 		string(name: 'OVERWRITE_DOCKER_LATEST_TAG', defaultValue: 'false', description: 'true to overwrite latest tag at dockerhub. Works only if DOCKER_PUBLISH is true')
@@ -14,7 +14,7 @@ pipeline {
     environment {
         GIT_TOKEN = credentials('GIT_TOKEN')
         VERSION =sh (returnStdout: true, script: """
-        [ -n \"${params.VERSION}\" ] && echo \"${params.VERSION}\" || date +%Y.%m.%d%H | cut -c 3-
+        [ -n \"${params.VERSION}\" ] && echo \"${params.VERSION}\" || date +%Y.%m.%d | cut -c 3-
         """).trim()
         RELEASE =sh (returnStdout: true, script: """
         echo \"${params.RELEASE}\"
@@ -179,7 +179,28 @@ pipeline {
             }
         }
       }
+
+      stage('Publish Nightly Build to GitHub') {
+        when {
+            expression {
+                return params.RELEASE == 'nightly' && env.BRANCH == 'main' && params.ASUP_MAKE_TARGET == 'production'
+            }
+        }
+        steps {
+           sh '''
+           targetLocation=$targetParentLocation$VERSION-$RELEASE-$BRANCH
+           wget -q -O /opt/home/gh.tar.gz "https://github.com/cli/cli/releases/download/v2.2.0/gh_2.2.0_linux_386.tar.gz"
+           tar -C /opt/home -xzf /opt/home/gh.tar.gz
+           echo $GIT_TOKEN > mytoken.txt
+           /opt/home/gh_2.2.0_linux_386/bin/gh auth login --with-token < mytoken.txt
+           /opt/home/gh_2.2.0_linux_386/bin/gh release delete nightly -y || true
+           /opt/home/gh_2.2.0_linux_386/bin/gh release create nightly $targetLocation/*.rpm $targetLocation/*.deb $targetLocation/*.gz --notes "Harvest Nightly Release Builds" --title "Harvest Nightly Release" --prerelease
+            '''
+            }
+        }
     }
+
+
     post {
         failure {
             sendNotification("FAILED")
diff --git a/pkg/tree/tree.go b/pkg/tree/tree.go
index 9c32c28a..caf881b0 100644
--- a/pkg/tree/tree.go
+++ b/pkg/tree/tree.go
@@ -4,66 +4,52 @@
 package tree
 
 import (
-	"errors"
-	"goharvest2/pkg/tree/json"
 	"goharvest2/pkg/tree/node"
 	"goharvest2/pkg/tree/xml"
-	"goharvest2/pkg/tree/yaml"
+	y3 "gopkg.in/yaml.v3"
 	"io/ioutil"
 )
 
-func Print(n *node.Node) {
-	n.Print(0)
-}
-
-func Import(format, filepath string) (*node.Node, error) {
-
+func ImportYaml(filepath string) (*node.Node, error) {
 	data, err := ioutil.ReadFile(filepath)
 
 	if err != nil {
 		return nil, err
 	}
 
-	switch format {
-	case "yaml":
-		return yaml.Load(data)
-	case "xml":
-		return xml.Load(data)
-	case "json":
-		return json.Load(data)
+	root := y3.Node{}
+	err = y3.Unmarshal(data, &root)
+	if err != nil || len(root.Content) == 0 {
+		return nil, err
 	}
-
-	return nil, errors.New("unknown format: " + format)
+	r := node.New([]byte("Root"))
+	consume(r, "", root.Content[0])
+	return r, nil
 }
 
-func Export(n *node.Node, format, filepath string) error {
-
-	var data []byte
-	var err error
-
-	switch format {
-	case "yaml":
-		data, err = yaml.Dump(n)
-	case "xml":
-		data, err = xml.Dump(n)
-	case "json":
-		data = json.Dump(n)
-	default:
-		err = errors.New("unknown format: " + format)
+func consume(r *node.Node, key string, y *y3.Node) {
+	if y.Kind == y3.ScalarNode {
+		r.NewChildS(key, y.Value)
+	} else if y.Kind == y3.MappingNode {
+		var s = r
+		if key != "" {
+			s = r.NewChildS(key, "")
+		}
+		for i := 0; i < len(y.Content); i += 2 {
+			k := y.Content[i].Value
+			// special case to handle incorrectly indented LabelAgent
+			if k == "LabelAgent" && y.Content[i+1].Kind == y3.ScalarNode {
+				s = r.NewChildS(k, "")
+				continue
+			}
+			consume(s, k, y.Content[i+1])
+		}
+	} else { // sequence
+		s := r.NewChildS(key, "")
+		for _, child := range y.Content {
+			consume(s, "", child)
+		}
 	}
-
-	if err == nil {
-		err = ioutil.WriteFile(filepath, data, 0644)
-	}
-	return err
-}
-
-func LoadYaml(data []byte) (*node.Node, error) {
-	return yaml.Load(data)
-}
-
-func DumpYaml(n *node.Node) ([]byte, error) {
-	return yaml.Dump(n)
 }
 
 func LoadXml(data []byte) (*node.Node, error) {
diff --git a/pkg/tree/yaml/yaml.go b/pkg/tree/yaml/yaml.go
index aa084468..ef94c7d9 100644
--- a/pkg/tree/yaml/yaml.go
+++ b/pkg/tree/yaml/yaml.go
@@ -1,114 +1,14 @@
 /*
  * Copyright NetApp Inc, 2021 All rights reserved
  */
+
 package yaml
 
 import (
 	"bytes"
-	"errors"
 	"goharvest2/pkg/tree/node"
-	"strconv"
 )
 
-func Load(data []byte) (*node.Node, error) {
-	root := node.New([]byte("Root"))
-	// be tolerant against tabs, replace with 2 spaces
-	return root, parse(root, bytes.Split(bytes.ReplaceAll(data, []byte("\t"), []byte("  ")), []byte("\n")), 0, 0)
-}
-
-func parse(node *node.Node, lines [][]byte, index, depth int) error {
-	//fmt.Printf(" -> %d\n", index)
-	// Reached end of file
-	if index == len(lines) {
-		return nil
-	}
-
-	// Indentation should be 2 spaces
-	// Stop if invalid indentation
-	if depth%2 != 0 {
-		return errors.New("inconsistent indentation, line: " + strconv.Itoa(index+1))
-	}
-
-	// parse current line
-	depthNew, key, value := parseLine(lines[index])
-	//fmt.Printf("  depth=%d, depthNew=%d, key=%s, value=%s\n", depth, depthNew, string(key), string(value))
-
-	// If line is empty, jump to next line
-	if len(key) == 0 && len(value) == 0 {
-		return parse(node, lines, index+1, depth)
-	}
-
-	// Indentation is same, so parse for current node
-	if depthNew == depth {
-
-		child := node.NewChild(key, value)
-		// no key, means key was defined on previous line
-		// means value is element of list
-		if len(key) == 0 {
-			return parse(node, lines, index+1, depth)
-		} else {
-			// key with no value, means we expect a list of values on next line(s)
-			if len(value) == 0 {
-				return parse(child, lines, index+1, depth+2)
-			} else { // single-value child
-				return parse(node, lines, index+1, depth)
-			}
-		}
-		// Jump back to parent node
-	} else if depthNew < depth {
-		return parse(node.GetParent(), lines, index, depth-2)
-	} else { // Current depth can't be larger than previous
-		return errors.New("invalid indentation, line: " + strconv.Itoa(index+1))
-	}
-}
-
-func parseLine(line []byte) (int, []byte, []byte) {
-	/* variables hold indices of:
-	   start = position of first non-whitespace character, i.e. where indentation ends
-	   mid = position of key-value separator (first occurance of ":")
-	   end = end of line or position where comment starts (#....) */
-	var start, mid, end int
-	var key, value []byte
-
-	start = -1
-
-	for end = 0; end < len(line); end += 1 {
-		// stop if comment starts
-		if line[end] == '#' {
-			break
-		}
-		// first non-space character
-		if start == -1 && line[end] != ' ' {
-			start = end
-		}
-		// first seperator character
-		if mid == 0 && line[end] == ':' {
-			mid = end
-		}
-	}
-
-	if start == -1 {
-		start = 0
-	}
-
-	//fmt.Printf(" <%d> start=%d, mid=%d, end=%d\n", len(line), start, mid, end)
-
-	// no seperator, means line only has value
-	if mid == 0 {
-		value = line[start:end]
-		// seperator is last character, means only key
-	} else if mid+1 == end {
-		key = line[start:mid]
-		// both present
-	} else {
-		key = line[start:mid]
-		value = line[mid+2 : end]
-	}
-	key = bytes.TrimPrefix(key, []byte("- "))
-	value = bytes.TrimPrefix(value, []byte("- "))
-	return start, bytes.Trim(key, " "), bytes.Trim(value, " ")
-}
-
 func Dump(root *node.Node) ([]byte, error) {
 	data := make([][]byte, 0)
 	for _, n := range root.GetChildren() {


Test patch:
diff --git a/pkg/test/mergeTemplates/21.08.0_lun_merge_21.08.0_extended.yaml b/pkg/test/mergeTemplates/21.08.0_lun_merge_21.08.0_extended.yaml
index 02ee311e..b09f12b5 100644
--- a/pkg/test/mergeTemplates/21.08.0_lun_merge_21.08.0_extended.yaml
+++ b/pkg/test/mergeTemplates/21.08.0_lun_merge_21.08.0_extended.yaml
@@ -20,7 +20,6 @@ plugins:
       - path `/` ,,,lun
     new_mapping:
       - xyz
-  - nic
   Aggregator:
     - node<>node_cpu1
     - nodetest
diff --git a/pkg/test/mergeTemplates/lun_merge_21.08.0_extended.yaml b/pkg/test/mergeTemplates/lun_merge_21.08.0_extended.yaml
index e878905a..8b72813a 100644
--- a/pkg/test/mergeTemplates/lun_merge_21.08.0_extended.yaml
+++ b/pkg/test/mergeTemplates/lun_merge_21.08.0_extended.yaml
@@ -24,7 +24,6 @@ plugins:
     - aggr
     - node<>node_cpu1
     - nodetest
-  - nic
 export_options:
   instance_keys:
     - node
diff --git a/pkg/test/node_test.go b/pkg/test/node_test.go
index d1c90348..917f8670 100644
--- a/pkg/test/node_test.go
+++ b/pkg/test/node_test.go
@@ -10,8 +10,8 @@ import (
 
 // Merge default.yaml and custom.yaml
 func TestNode_Merge(t *testing.T) {
-	defaultTemplate, _ := tree.Import("yaml", "testdata/default_collector.yaml")
-	customTemplate, _ := tree.Import("yaml", "testdata/extend_collector.yaml")
+	defaultTemplate, _ := tree.ImportYaml("testdata/default_collector.yaml")
+	customTemplate, _ := tree.ImportYaml("testdata/extend_collector.yaml")
 	defaultTemplate.Merge(customTemplate, []string{"objects"})
 
 	// count number of objects post merge
@@ -53,8 +53,8 @@ func TestNode_Merge(t *testing.T) {
 // merge collector templates for 21.08.6+ versions
 // change is LabelAgent child will have list of rules instead of key-value pair
 func TestNode_MergeCollector(t *testing.T) {
-	defaultTemplate, _ := tree.Import("yaml", "testdata/lun.yaml")
-	customTemplate, _ := tree.Import("yaml", "testdata/extend_lun.yaml")
+	defaultTemplate, _ := tree.ImportYaml("testdata/lun.yaml")
+	customTemplate, _ := tree.ImportYaml("testdata/extend_lun.yaml")
 	defaultTemplate.PreprocessTemplate()
 	customTemplate.PreprocessTemplate()
 	defaultTemplate.Merge(customTemplate, nil)
@@ -206,8 +206,8 @@ func TestNode_MergeCollector(t *testing.T) {
 // Merge collector templates where custom templates are from 21.08.6 and before
 // LabelAgent child did have key-value pair of rules instead of a list
 func TestNode_MergeCollectorOld(t *testing.T) {
-	defaultTemplate, _ := tree.Import("yaml", "testdata/lun.yaml")
-	customTemplate, _ := tree.Import("yaml", "testdata/21.08.0_extend_lun.yaml")
+	defaultTemplate, _ := tree.ImportYaml("testdata/lun.yaml")
+	customTemplate, _ := tree.ImportYaml("testdata/21.08.0_extend_lun.yaml")
 	defaultTemplate.PreprocessTemplate()
 	customTemplate.PreprocessTemplate()
 	defaultTemplate.Merge(customTemplate, nil)
@@ -295,7 +295,7 @@ func TestNode_PreProcessCollector(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			template, _ := tree.Import("yaml", tt.sourceFile)
+			template, _ := tree.ImportYaml(tt.sourceFile)
 			template.PreprocessTemplate()
 			got, _ := yaml.Dump(template)
 			expected, _ := ioutil.ReadFile(tt.compareFile)
@@ -343,8 +343,8 @@ func TestNode_PreProcessMergeCollector(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			baseTemplate, _ := tree.Import("yaml", tt.baseTemplate)
-			extendTemplate, _ := tree.Import("yaml", tt.extendTemplate)
+			baseTemplate, _ := tree.ImportYaml(tt.baseTemplate)
+			extendTemplate, _ := tree.ImportYaml(tt.extendTemplate)
 			baseTemplate.PreprocessTemplate()
 			extendTemplate.PreprocessTemplate()
 			baseTemplate.Merge(extendTemplate, nil)
diff --git a/pkg/test/preProcessResultData/p_21.08.0_extend_lun.yaml b/pkg/test/preProcessResultData/p_21.08.0_extend_lun.yaml
index d975cc3d..e5441c96 100644
--- a/pkg/test/preProcessResultData/p_21.08.0_extend_lun.yaml
+++ b/pkg/test/preProcessResultData/p_21.08.0_extend_lun.yaml
@@ -1,5 +1,4 @@
 plugins:
-  - nic
   LabelAgent:
     value_to_num:
       - new_status state online online `0`
diff --git a/pkg/test/testdata/21.08.0_extend_lun.yaml b/pkg/test/testdata/21.08.0_extend_lun.yaml
index 6bfa82fc..b7cc81f3 100644
--- a/pkg/test/testdata/21.08.0_extend_lun.yaml
+++ b/pkg/test/testdata/21.08.0_extend_lun.yaml
@@ -1,5 +1,4 @@
 plugins:
-  - nic
   LabelAgent:
     value_to_num: new_status state online online `0`
     new_mapping: xyz