Create a --set flag for generate (#2787)

This commit is contained in:
Julien Corioland 2018-05-03 01:09:55 +02:00 коммит произвёл Jack Francis
Родитель 914172d3cd
Коммит ca9b3f1cad
12 изменённых файлов: 2113 добавлений и 6 удалений

Просмотреть файл

@ -30,6 +30,7 @@ type generateCmd struct {
classicMode bool
noPrettyPrint bool
parametersOnly bool
set []string
// derived
containerService *api.ContainerService
@ -48,6 +49,15 @@ func newGenerateCmd() *cobra.Command {
if err := gc.validate(cmd, args); err != nil {
log.Fatalf(fmt.Sprintf("error validating generateCmd: %s", err.Error()))
}
if err := gc.mergeAPIModel(); err != nil {
log.Fatalf(fmt.Sprintf("error merging API model in generateCmd: %s", err.Error()))
}
if err := gc.loadAPIModel(cmd, args); err != nil {
log.Fatalf(fmt.Sprintf("error loading API model in generateCmd: %s", err.Error()))
}
return gc.run()
},
}
@ -57,6 +67,7 @@ func newGenerateCmd() *cobra.Command {
f.StringVar(&gc.outputDirectory, "output-directory", "", "output directory (derived from FQDN if absent)")
f.StringVar(&gc.caCertificatePath, "ca-certificate-path", "", "path to the CA certificate to use for Kubernetes PKI assets")
f.StringVar(&gc.caPrivateKeyPath, "ca-private-key-path", "", "path to the CA private key to use for Kubernetes PKI assets")
f.StringArrayVar(&gc.set, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.BoolVar(&gc.classicMode, "classic-mode", false, "enable classic parameters and outputs")
f.BoolVar(&gc.noPrettyPrint, "no-pretty-print", false, "skip pretty printing the output")
f.BoolVar(&gc.parametersOnly, "parameters-only", false, "only output parameters files")
@ -65,8 +76,6 @@ func newGenerateCmd() *cobra.Command {
}
func (gc *generateCmd) validate(cmd *cobra.Command, args []string) error {
var caCertificateBytes []byte
var caKeyBytes []byte
var err error
gc.locale, err = i18n.LoadTranslations()
@ -90,6 +99,34 @@ func (gc *generateCmd) validate(cmd *cobra.Command, args []string) error {
return fmt.Errorf(fmt.Sprintf("specified api model does not exist (%s)", gc.apimodelPath))
}
return nil
}
func (gc *generateCmd) mergeAPIModel() error {
var err error
// if --set flag has been used
if gc.set != nil && len(gc.set) > 0 {
m := make(map[string]transform.APIModelValue)
transform.MapValues(m, gc.set)
// overrides the api model and generates a new file
gc.apimodelPath, err = transform.MergeValuesWithAPIModel(gc.apimodelPath, m)
if err != nil {
return fmt.Errorf(fmt.Sprintf("error merging --set values with the api model: %s", err.Error()))
}
log.Infoln(fmt.Sprintf("new api model file has been generated during merge: %s", gc.apimodelPath))
}
return nil
}
func (gc *generateCmd) loadAPIModel(cmd *cobra.Command, args []string) error {
var caCertificateBytes []byte
var caKeyBytes []byte
var err error
apiloader := &api.Apiloader{
Translator: &i18n.Translator{
Locale: gc.locale,
@ -128,6 +165,7 @@ func (gc *generateCmd) validate(cmd *cobra.Command, args []string) error {
prop.CertificateProfile.CaCertificate = string(caCertificateBytes)
prop.CertificateProfile.CaPrivateKey = string(caKeyBytes)
}
return nil
}

Просмотреть файл

@ -7,9 +7,7 @@ import (
)
func TestGenerateCmdValidate(t *testing.T) {
g := &generateCmd{}
r := &cobra.Command{}
// validate cmd with 1 arg
@ -37,3 +35,51 @@ func TestGenerateCmdValidate(t *testing.T) {
}
}
func TestGenerateCmdMergeAPIModel(t *testing.T) {
g := &generateCmd{}
g.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
err := g.mergeAPIModel()
if err != nil {
t.Fatalf("unexpected error calling mergeAPIModel with no --set flag defined: %s", err.Error())
}
g = &generateCmd{}
g.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
g.set = []string{"masterProfile.count=3,linuxProfile.adminUsername=testuser"}
err = g.mergeAPIModel()
if err != nil {
t.Fatalf("unexpected error calling mergeAPIModel with one --set flag: %s", err.Error())
}
g = &generateCmd{}
g.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
g.set = []string{"masterProfile.count=3", "linuxProfile.adminUsername=testuser"}
err = g.mergeAPIModel()
if err != nil {
t.Fatalf("unexpected error calling mergeAPIModel with multiple --set flags: %s", err.Error())
}
g = &generateCmd{}
g.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
g.set = []string{"agentPoolProfiles[0].count=1"}
err = g.mergeAPIModel()
if err != nil {
t.Fatalf("unexpected error calling mergeAPIModel with one --set flag to override an array property: %s", err.Error())
}
}
func TestGenerateCmdMLoadAPIModel(t *testing.T) {
g := &generateCmd{}
r := &cobra.Command{}
g.apimodelPath = "../pkg/acsengine/testdata/simple/kubernetes.json"
g.set = []string{"agentPoolProfiles[0].count=1"}
g.validate(r, []string{"../pkg/acsengine/testdata/simple/kubernetes.json"})
g.mergeAPIModel()
err := g.loadAPIModel(r, []string{"../pkg/acsengine/testdata/simple/kubernetes.json"})
if err != nil {
t.Fatalf("unexpected error loading api model: %s", err.Error())
}
}

Просмотреть файл

@ -98,12 +98,26 @@ Edit the [simple Kubernetes cluster definition](/examples/kubernetes.json) and f
Optional: attach to an existing virtual network (VNET). Details [here](features.md#feat-custom-vnet)
Note: you can then use the `--set` option of the generate command to override values from the cluster definition file directly in the command line (cf. [Step 4](deploy.md#step-4-generate-the-templates))
### Step 4: Generate the Templates
The generate command takes a cluster definition and outputs a number of templates which describe your Kubernetes cluster. By default, `generate` will create a new directory named after your cluster nested in the `_output` directory. If my dnsPrefix was `larry` my cluster templates would be found in `_output/larry-`.
Run `acs-engine generate examples/kubernetes.json`
The generate command lets you override values from the cluster definition file without having to update the file. You can use the `--set` flag to do that:
```bash
acs-engine generate --set linuxProfile.adminUsername=myNewUsername,masterProfile.count=3 clusterdefinition.json
```
The `--set` flag only supports JSON properties under `properties`. You can also work with array, like the following:
```bash
acs-engine generate --set agentPoolProfiles[0].count=5,agentPoolProfiles[1].name=myPoolName clusterdefinition.json
```
### Step 5: Submit your Templates to Azure Resource Manager (ARM)
[Deploy the output azuredeploy.json and azuredeploy.parameters.json](../acsengine.md#deployment-usage)

8
glide.lock сгенерированный
Просмотреть файл

@ -1,5 +1,5 @@
hash: 6ccf7505e5ada9ca2cbbfc77ca540b6ec59e0bd40d9392c79e29942386446685
updated: 2018-04-06T09:25:41.745057-07:00
hash: d3817832375df23ed63ab732dabbb73bd3b59896118b8965a1cfbf09815d4c27
updated: 2018-04-25T16:03:39.306816-07:00
imports:
- name: github.com/alexcesaro/statsd
version: 7fea3f0d2fab1ad973e641e51dba45443a311a90
@ -82,6 +82,8 @@ imports:
- client/v2
- models
- pkg/escape
- name: github.com/Jeffail/gabs
version: 2a3aa15961d5fee6047b8151b67ac2f08ba2c48c
- name: github.com/JiangtianLi/gettext
version: a8983c062be4b565d723c478922d7736e04fdba4
- name: github.com/juju/ratelimit
@ -165,6 +167,8 @@ imports:
- name: golang.org/x/crypto
version: 122d919ec1efcfb58483215da23f815853e24b81
subpackages:
- bcrypt
- blowfish
- curve25519
- ed25519
- ed25519/internal/edwards25519

Просмотреть файл

@ -56,6 +56,8 @@ import:
version: 366b072768b4e6d93c7de236464c0abe85d0b7c6
- package: k8s.io/client-go
version: ~4.0.0
- package: github.com/Jeffail/gabs
version: 1.0
testImport:
# glide isn't able to mutually reconcile pinned versions of these deps
- package: github.com/onsi/gomega

Просмотреть файл

@ -0,0 +1,119 @@
package transform
import (
"fmt"
"io/ioutil"
"os"
"regexp"
"strconv"
"strings"
"github.com/Jeffail/gabs"
log "github.com/sirupsen/logrus"
)
// APIModelValue represents a value in the APIModel JSON file
type APIModelValue struct {
stringValue string
intValue int64
arrayValue bool
arrayIndex int
arrayProperty string
arrayName string
}
// MapValues converts an arraw of rwa ApiModel values (like ["masterProfile.count=4","linuxProfile.adminUsername=admin"]) to a map
func MapValues(m map[string]APIModelValue, values []string) {
if values == nil || len(values) == 0 {
return
}
for _, value := range values {
splittedValues := strings.Split(value, ",")
if len(splittedValues) > 1 {
MapValues(m, splittedValues)
} else {
keyValueSplitted := strings.Split(value, "=")
key := keyValueSplitted[0]
stringValue := keyValueSplitted[1]
flagValue := APIModelValue{}
if asInteger, err := strconv.ParseInt(stringValue, 10, 64); err == nil {
flagValue.intValue = asInteger
} else {
flagValue.stringValue = stringValue
}
// use regex to find array[index].property pattern in the key
re := regexp.MustCompile(`(.*?)\[(.*?)\]\.(.*?)$`)
match := re.FindStringSubmatch(key)
// it's an array
if len(match) != 0 {
i, err := strconv.ParseInt(match[2], 10, 32)
if err != nil {
log.Warnln(fmt.Sprintf("array index is not specified for property %s", key))
} else {
arrayIndex := int(i)
flagValue.arrayValue = true
flagValue.arrayName = match[1]
flagValue.arrayIndex = arrayIndex
flagValue.arrayProperty = match[3]
m[key] = flagValue
}
} else {
m[key] = flagValue
}
}
}
}
// MergeValuesWithAPIModel takes the path to an ApiModel JSON file, loads it and merges it with the values in the map to another temp file
func MergeValuesWithAPIModel(apiModelPath string, m map[string]APIModelValue) (string, error) {
// load the apiModel file from path
fileContent, err := ioutil.ReadFile(apiModelPath)
if err != nil {
return "", err
}
// parse the json from file content
jsonObj, err := gabs.ParseJSON(fileContent)
if err != nil {
return "", err
}
// update api model definition with each value in the map
for key, flagValue := range m {
// working on an array
if flagValue.arrayValue {
log.Infoln(fmt.Sprintf("--set flag array value detected. Name: %s, Index: %b, PropertyName: %s", flagValue.arrayName, flagValue.arrayIndex, flagValue.arrayProperty))
arrayValue := jsonObj.Path(fmt.Sprint("properties.", flagValue.arrayName))
if flagValue.stringValue != "" {
arrayValue.Index(flagValue.arrayIndex).SetP(flagValue.stringValue, flagValue.arrayProperty)
} else {
arrayValue.Index(flagValue.arrayIndex).SetP(flagValue.intValue, flagValue.arrayProperty)
}
} else {
if flagValue.stringValue != "" {
jsonObj.SetP(flagValue.stringValue, fmt.Sprint("properties.", key))
} else {
jsonObj.SetP(flagValue.intValue, fmt.Sprint("properties.", key))
}
}
}
// generate a new file
tmpFile, err := ioutil.TempFile("", "mergedApiModel")
if err != nil {
return "", err
}
tmpFileName := tmpFile.Name()
err = ioutil.WriteFile(tmpFileName, []byte(jsonObj.String()), os.ModeAppend)
if err != nil {
return "", err
}
return tmpFileName, nil
}

Просмотреть файл

@ -0,0 +1,50 @@
package transform
import (
"io/ioutil"
"testing"
"github.com/Jeffail/gabs"
. "github.com/onsi/gomega"
)
func TestAPIModelMergerMapValues(t *testing.T) {
RegisterTestingT(t)
m := make(map[string]APIModelValue)
values := []string{"masterProfile.count=5", "agentPoolProfiles[0].name=agentpool1", "linuxProfile.adminUsername=admin"}
MapValues(m, values)
Expect(m["masterProfile.count"].intValue).To(BeIdenticalTo(int64(5)))
Expect(m["agentPoolProfiles[0].name"].arrayValue).To(BeTrue())
Expect(m["agentPoolProfiles[0].name"].arrayIndex).To(BeIdenticalTo(0))
Expect(m["agentPoolProfiles[0].name"].arrayProperty).To(BeIdenticalTo("name"))
Expect(m["agentPoolProfiles[0].name"].arrayName).To(BeIdenticalTo("agentPoolProfiles"))
Expect(m["agentPoolProfiles[0].name"].stringValue).To(BeIdenticalTo("agentpool1"))
Expect(m["linuxProfile.adminUsername"].stringValue).To(BeIdenticalTo("admin"))
}
func TestMergeValuesWithAPIModel(t *testing.T) {
RegisterTestingT(t)
m := make(map[string]APIModelValue)
values := []string{"masterProfile.count=5", "agentPoolProfiles[0].name=agentpool1", "linuxProfile.adminUsername=admin"}
MapValues(m, values)
tmpFile, _ := MergeValuesWithAPIModel("../testdata/simple/kubernetes.json", m)
jsonFileContent, err := ioutil.ReadFile(tmpFile)
Expect(err).To(BeNil())
jsonAPIModel, err := gabs.ParseJSON(jsonFileContent)
Expect(err).To(BeNil())
masterProfileCount := jsonAPIModel.Path("properties.masterProfile.count").Data()
Expect(masterProfileCount).To(BeIdenticalTo(float64(5)))
adminUsername := jsonAPIModel.Path("properties.linuxProfile.adminUsername").Data()
Expect(adminUsername).To(BeIdenticalTo("admin"))
agentPoolProfileName := jsonAPIModel.Path("properties.agentPoolProfiles").Index(0).Path("name").Data().(string)
Expect(agentPoolProfileName).To(BeIdenticalTo("agentpool1"))
}

19
vendor/github.com/Jeffail/gabs/LICENSE сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,19 @@
Copyright (c) 2014 Ashley Jeffs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

284
vendor/github.com/Jeffail/gabs/README.md сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,284 @@
![Gabs](gabs_logo.png "Gabs")
Gabs is a small utility for dealing with dynamic or unknown JSON structures in golang. It's pretty much just a helpful wrapper around the golang json.Marshal/json.Unmarshal behaviour and map[string]interface{} objects. It does nothing spectacular except for being fabulous.
https://godoc.org/github.com/Jeffail/gabs
##How to install:
```bash
go get github.com/Jeffail/gabs
```
##How to use
###Parsing and searching JSON
```go
...
import "github.com/Jeffail/gabs"
jsonParsed, err := gabs.ParseJSON([]byte(`{
"outter":{
"inner":{
"value1":10,
"value2":22
},
"alsoInner":{
"value1":20
}
}
}`))
var value float64
var ok bool
value, ok = jsonParsed.Path("outter.inner.value1").Data().(float64)
// value == 10.0, ok == true
value, ok = jsonParsed.Search("outter", "inner", "value1").Data().(float64)
// value == 10.0, ok == true
value, ok = jsonParsed.Path("does.not.exist").Data().(float64)
// value == 0.0, ok == false
exists := jsonParsed.Exists("outter", "inner", "value1")
// exists == true
exists := jsonParsed.Exists("does", "not", "exist")
// exists == false
exists := jsonParsed.ExistsP("does.not.exist")
// exists == false
...
```
###Iterating objects
```go
...
jsonParsed, _ := gabs.ParseJSON([]byte(`{"object":{ "first": 1, "second": 2, "third": 3 }}`))
// S is shorthand for Search
children, _ := jsonParsed.S("object").ChildrenMap()
for key, child := range children {
fmt.Printf("key: %v, value: %v\n", key, child.Data().(string))
}
...
```
###Iterating arrays
```go
...
jsonParsed, _ := gabs.ParseJSON([]byte(`{"array":[ "first", "second", "third" ]}`))
// S is shorthand for Search
children, _ := jsonParsed.S("array").Children()
for _, child := range children {
fmt.Println(child.Data().(string))
}
...
```
Will print:
```
first
second
third
```
Children() will return all children of an array in order. This also works on objects, however, the children will be returned in a random order.
###Searching through arrays
If your JSON structure contains arrays you can still search the fields of the objects within the array, this returns a JSON array containing the results for each element.
```go
...
jsonParsed, _ := gabs.ParseJSON([]byte(`{"array":[ {"value":1}, {"value":2}, {"value":3} ]}`))
fmt.Println(jsonParsed.Path("array.value").String())
...
```
Will print:
```
[1,2,3]
```
###Generating JSON
```go
...
jsonObj := gabs.New()
// or gabs.Consume(jsonObject) to work on an existing map[string]interface{}
jsonObj.Set(10, "outter", "inner", "value")
jsonObj.SetP(20, "outter.inner.value2")
jsonObj.Set(30, "outter", "inner2", "value3")
fmt.Println(jsonObj.String())
...
```
Will print:
```
{"outter":{"inner":{"value":10,"value2":20},"inner2":{"value3":30}}}
```
To pretty-print:
```go
...
fmt.Println(jsonObj.StringIndent("", " "))
...
```
Will print:
```
{
"outter": {
"inner": {
"value": 10,
"value2": 20
},
"inner2": {
"value3": 30
}
}
}
```
###Generating Arrays
```go
...
jsonObj := gabs.New()
jsonObj.Array("foo", "array")
// Or .ArrayP("foo.array")
jsonObj.ArrayAppend(10, "foo", "array")
jsonObj.ArrayAppend(20, "foo", "array")
jsonObj.ArrayAppend(30, "foo", "array")
fmt.Println(jsonObj.String())
...
```
Will print:
```
{"foo":{"array":[10,20,30]}}
```
Working with arrays by index:
```go
...
jsonObj := gabs.New()
// Create an array with the length of 3
jsonObj.ArrayOfSize(3, "foo")
jsonObj.S("foo").SetIndex("test1", 0)
jsonObj.S("foo").SetIndex("test2", 1)
// Create an embedded array with the length of 3
jsonObj.S("foo").ArrayOfSizeI(3, 2)
jsonObj.S("foo").Index(2).SetIndex(1, 0)
jsonObj.S("foo").Index(2).SetIndex(2, 1)
jsonObj.S("foo").Index(2).SetIndex(3, 2)
fmt.Println(jsonObj.String())
...
```
Will print:
```
{"foo":["test1","test2",[1,2,3]]}
```
###Converting back to JSON
This is the easiest part:
```go
...
jsonParsedObj := gabs.ParseJSON([]byte(`{
"outter":{
"values":{
"first":10,
"second":11
}
},
"outter2":"hello world"
}`))
jsonOutput := jsonParsedObj.String()
// Becomes `{"outter":{"values":{"first":10,"second":11}},"outter2":"hello world"}`
...
```
And to serialize a specific segment is as simple as:
```go
...
jsonParsedObj := gabs.ParseJSON([]byte(`{
"outter":{
"values":{
"first":10,
"second":11
}
},
"outter2":"hello world"
}`))
jsonOutput := jsonParsedObj.Search("outter").String()
// Becomes `{"values":{"first":10,"second":11}}`
...
```
### Parsing Numbers
Gabs uses the `json` package under the bonnet, which by default will parse all number values into `float64`. If you need to parse `Int` values then you should use a `json.Decoder` (https://golang.org/pkg/encoding/json/#Decoder):
```go
sample := []byte(`{"test":{"int":10, "float":6.66}}`)
dec := json.NewDecoder(bytes.NewReader(sample))
dec.UseNumber()
val, err := gabs.ParseJSONDecoder(dec)
if err != nil {
t.Errorf("Failed to parse: %v", err)
return
}
intValue, err := val.Path("test.int").Data().(json.Number).Int64()
```

475
vendor/github.com/Jeffail/gabs/gabs.go сгенерированный поставляемый Normal file
Просмотреть файл

@ -0,0 +1,475 @@
/*
Copyright (c) 2014 Ashley Jeffs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Package gabs implements a simplified wrapper around creating and parsing JSON.
package gabs
import (
"encoding/json"
"errors"
"io"
"io/ioutil"
"strings"
)
//--------------------------------------------------------------------------------------------------
var (
// ErrOutOfBounds - Index out of bounds.
ErrOutOfBounds = errors.New("out of bounds")
// ErrNotObjOrArray - The target is not an object or array type.
ErrNotObjOrArray = errors.New("not an object or array")
// ErrNotObj - The target is not an object type.
ErrNotObj = errors.New("not an object")
// ErrNotArray - The target is not an array type.
ErrNotArray = errors.New("not an array")
// ErrPathCollision - Creating a path failed because an element collided with an existing value.
ErrPathCollision = errors.New("encountered value collision whilst building path")
// ErrInvalidInputObj - The input value was not a map[string]interface{}.
ErrInvalidInputObj = errors.New("invalid input object")
// ErrInvalidInputText - The input data could not be parsed.
ErrInvalidInputText = errors.New("input text could not be parsed")
// ErrInvalidPath - The filepath was not valid.
ErrInvalidPath = errors.New("invalid file path")
// ErrInvalidBuffer - The input buffer contained an invalid JSON string
ErrInvalidBuffer = errors.New("input buffer contained invalid JSON")
)
//--------------------------------------------------------------------------------------------------
// Container - an internal structure that holds a reference to the core interface map of the parsed
// json. Use this container to move context.
type Container struct {
object interface{}
}
// Data - Return the contained data as an interface{}.
func (g *Container) Data() interface{} {
return g.object
}
//--------------------------------------------------------------------------------------------------
// Path - Search for a value using dot notation.
func (g *Container) Path(path string) *Container {
return g.Search(strings.Split(path, ".")...)
}
// Search - Attempt to find and return an object within the JSON structure by specifying the
// hierarchy of field names to locate the target. If the search encounters an array and has not
// reached the end target then it will iterate each object of the array for the target and return
// all of the results in a JSON array.
func (g *Container) Search(hierarchy ...string) *Container {
var object interface{}
object = g.object
for target := 0; target < len(hierarchy); target++ {
if mmap, ok := object.(map[string]interface{}); ok {
object = mmap[hierarchy[target]]
} else if marray, ok := object.([]interface{}); ok {
tmpArray := []interface{}{}
for _, val := range marray {
tmpGabs := &Container{val}
res := tmpGabs.Search(hierarchy[target:]...).Data()
if res != nil {
tmpArray = append(tmpArray, res)
}
}
if len(tmpArray) == 0 {
return &Container{nil}
}
return &Container{tmpArray}
} else {
return &Container{nil}
}
}
return &Container{object}
}
// S - Shorthand method, does the same thing as Search.
func (g *Container) S(hierarchy ...string) *Container {
return g.Search(hierarchy...)
}
// Exists - Checks whether a path exists.
func (g *Container) Exists(hierarchy ...string) bool {
return g.Search(hierarchy...).Data() != nil
}
// ExistsP - Checks whether a dot notation path exists.
func (g *Container) ExistsP(path string) bool {
return g.Exists(strings.Split(path, ".")...)
}
// Index - Attempt to find and return an object within a JSON array by index.
func (g *Container) Index(index int) *Container {
if array, ok := g.Data().([]interface{}); ok {
if index >= len(array) {
return &Container{nil}
}
return &Container{array[index]}
}
return &Container{nil}
}
// Children - Return a slice of all the children of the array. This also works for objects, however,
// the children returned for an object will NOT be in order and you lose the names of the returned
// objects this way.
func (g *Container) Children() ([]*Container, error) {
if array, ok := g.Data().([]interface{}); ok {
children := make([]*Container, len(array))
for i := 0; i < len(array); i++ {
children[i] = &Container{array[i]}
}
return children, nil
}
if mmap, ok := g.Data().(map[string]interface{}); ok {
children := []*Container{}
for _, obj := range mmap {
children = append(children, &Container{obj})
}
return children, nil
}
return nil, ErrNotObjOrArray
}
// ChildrenMap - Return a map of all the children of an object.
func (g *Container) ChildrenMap() (map[string]*Container, error) {
if mmap, ok := g.Data().(map[string]interface{}); ok {
children := map[string]*Container{}
for name, obj := range mmap {
children[name] = &Container{obj}
}
return children, nil
}
return nil, ErrNotObj
}
//--------------------------------------------------------------------------------------------------
// Set - Set the value of a field at a JSON path, any parts of the path that do not exist will be
// constructed, and if a collision occurs with a non object type whilst iterating the path an error
// is returned.
func (g *Container) Set(value interface{}, path ...string) (*Container, error) {
if len(path) == 0 {
g.object = value
return g, nil
}
var object interface{}
if g.object == nil {
g.object = map[string]interface{}{}
}
object = g.object
for target := 0; target < len(path); target++ {
if mmap, ok := object.(map[string]interface{}); ok {
if target == len(path)-1 {
mmap[path[target]] = value
} else if mmap[path[target]] == nil {
mmap[path[target]] = map[string]interface{}{}
}
object = mmap[path[target]]
} else {
return &Container{nil}, ErrPathCollision
}
}
return &Container{object}, nil
}
// SetP - Does the same as Set, but using a dot notation JSON path.
func (g *Container) SetP(value interface{}, path string) (*Container, error) {
return g.Set(value, strings.Split(path, ".")...)
}
// SetIndex - Set a value of an array element based on the index.
func (g *Container) SetIndex(value interface{}, index int) (*Container, error) {
if array, ok := g.Data().([]interface{}); ok {
if index >= len(array) {
return &Container{nil}, ErrOutOfBounds
}
array[index] = value
return &Container{array[index]}, nil
}
return &Container{nil}, ErrNotArray
}
// Object - Create a new JSON object at a path. Returns an error if the path contains a collision
// with a non object type.
func (g *Container) Object(path ...string) (*Container, error) {
return g.Set(map[string]interface{}{}, path...)
}
// ObjectP - Does the same as Object, but using a dot notation JSON path.
func (g *Container) ObjectP(path string) (*Container, error) {
return g.Object(strings.Split(path, ".")...)
}
// ObjectI - Create a new JSON object at an array index. Returns an error if the object is not an
// array or the index is out of bounds.
func (g *Container) ObjectI(index int) (*Container, error) {
return g.SetIndex(map[string]interface{}{}, index)
}
// Array - Create a new JSON array at a path. Returns an error if the path contains a collision with
// a non object type.
func (g *Container) Array(path ...string) (*Container, error) {
return g.Set([]interface{}{}, path...)
}
// ArrayP - Does the same as Array, but using a dot notation JSON path.
func (g *Container) ArrayP(path string) (*Container, error) {
return g.Array(strings.Split(path, ".")...)
}
// ArrayI - Create a new JSON array at an array index. Returns an error if the object is not an
// array or the index is out of bounds.
func (g *Container) ArrayI(index int) (*Container, error) {
return g.SetIndex([]interface{}{}, index)
}
// ArrayOfSize - Create a new JSON array of a particular size at a path. Returns an error if the
// path contains a collision with a non object type.
func (g *Container) ArrayOfSize(size int, path ...string) (*Container, error) {
a := make([]interface{}, size)
return g.Set(a, path...)
}
// ArrayOfSizeP - Does the same as ArrayOfSize, but using a dot notation JSON path.
func (g *Container) ArrayOfSizeP(size int, path string) (*Container, error) {
return g.ArrayOfSize(size, strings.Split(path, ".")...)
}
// ArrayOfSizeI - Create a new JSON array of a particular size at an array index. Returns an error
// if the object is not an array or the index is out of bounds.
func (g *Container) ArrayOfSizeI(size, index int) (*Container, error) {
a := make([]interface{}, size)
return g.SetIndex(a, index)
}
// Delete - Delete an element at a JSON path, an error is returned if the element does not exist.
func (g *Container) Delete(path ...string) error {
var object interface{}
if g.object == nil {
return ErrNotObj
}
object = g.object
for target := 0; target < len(path); target++ {
if mmap, ok := object.(map[string]interface{}); ok {
if target == len(path)-1 {
delete(mmap, path[target])
} else if mmap[path[target]] == nil {
return ErrNotObj
}
object = mmap[path[target]]
} else {
return ErrNotObj
}
}
return nil
}
// DeleteP - Does the same as Delete, but using a dot notation JSON path.
func (g *Container) DeleteP(path string) error {
return g.Delete(strings.Split(path, ".")...)
}
//--------------------------------------------------------------------------------------------------
/*
Array modification/search - Keeping these options simple right now, no need for anything more
complicated since you can just cast to []interface{}, modify and then reassign with Set.
*/
// ArrayAppend - Append a value onto a JSON array.
func (g *Container) ArrayAppend(value interface{}, path ...string) error {
array, ok := g.Search(path...).Data().([]interface{})
if !ok {
return ErrNotArray
}
array = append(array, value)
_, err := g.Set(array, path...)
return err
}
// ArrayAppendP - Append a value onto a JSON array using a dot notation JSON path.
func (g *Container) ArrayAppendP(value interface{}, path string) error {
return g.ArrayAppend(value, strings.Split(path, ".")...)
}
// ArrayRemove - Remove an element from a JSON array.
func (g *Container) ArrayRemove(index int, path ...string) error {
if index < 0 {
return ErrOutOfBounds
}
array, ok := g.Search(path...).Data().([]interface{})
if !ok {
return ErrNotArray
}
if index < len(array) {
array = append(array[:index], array[index+1:]...)
} else {
return ErrOutOfBounds
}
_, err := g.Set(array, path...)
return err
}
// ArrayRemoveP - Remove an element from a JSON array using a dot notation JSON path.
func (g *Container) ArrayRemoveP(index int, path string) error {
return g.ArrayRemove(index, strings.Split(path, ".")...)
}
// ArrayElement - Access an element from a JSON array.
func (g *Container) ArrayElement(index int, path ...string) (*Container, error) {
if index < 0 {
return &Container{nil}, ErrOutOfBounds
}
array, ok := g.Search(path...).Data().([]interface{})
if !ok {
return &Container{nil}, ErrNotArray
}
if index < len(array) {
return &Container{array[index]}, nil
}
return &Container{nil}, ErrOutOfBounds
}
// ArrayElementP - Access an element from a JSON array using a dot notation JSON path.
func (g *Container) ArrayElementP(index int, path string) (*Container, error) {
return g.ArrayElement(index, strings.Split(path, ".")...)
}
// ArrayCount - Count the number of elements in a JSON array.
func (g *Container) ArrayCount(path ...string) (int, error) {
if array, ok := g.Search(path...).Data().([]interface{}); ok {
return len(array), nil
}
return 0, ErrNotArray
}
// ArrayCountP - Count the number of elements in a JSON array using a dot notation JSON path.
func (g *Container) ArrayCountP(path string) (int, error) {
return g.ArrayCount(strings.Split(path, ".")...)
}
//--------------------------------------------------------------------------------------------------
// Bytes - Converts the contained object back to a JSON []byte blob.
func (g *Container) Bytes() []byte {
if g.object != nil {
if bytes, err := json.Marshal(g.object); err == nil {
return bytes
}
}
return []byte("{}")
}
// BytesIndent - Converts the contained object to a JSON []byte blob formatted with prefix, indent.
func (g *Container) BytesIndent(prefix string, indent string) []byte {
if g.object != nil {
if bytes, err := json.MarshalIndent(g.object, prefix, indent); err == nil {
return bytes
}
}
return []byte("{}")
}
// String - Converts the contained object to a JSON formatted string.
func (g *Container) String() string {
return string(g.Bytes())
}
// StringIndent - Converts the contained object back to a JSON formatted string with prefix, indent.
func (g *Container) StringIndent(prefix string, indent string) string {
return string(g.BytesIndent(prefix, indent))
}
// New - Create a new gabs JSON object.
func New() *Container {
return &Container{map[string]interface{}{}}
}
// Consume - Gobble up an already converted JSON object, or a fresh map[string]interface{} object.
func Consume(root interface{}) (*Container, error) {
return &Container{root}, nil
}
// ParseJSON - Convert a string into a representation of the parsed JSON.
func ParseJSON(sample []byte) (*Container, error) {
var gabs Container
if err := json.Unmarshal(sample, &gabs.object); err != nil {
return nil, err
}
return &gabs, nil
}
// ParseJSONDecoder - Convert a json.Decoder into a representation of the parsed JSON.
func ParseJSONDecoder(decoder *json.Decoder) (*Container, error) {
var gabs Container
if err := decoder.Decode(&gabs.object); err != nil {
return nil, err
}
return &gabs, nil
}
// ParseJSONFile - Read a file and convert into a representation of the parsed JSON.
func ParseJSONFile(path string) (*Container, error) {
if len(path) > 0 {
cBytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
container, err := ParseJSON(cBytes)
if err != nil {
return nil, err
}
return container, nil
}
return nil, ErrInvalidPath
}
// ParseJSONBuffer - Read the contents of a buffer into a representation of the parsed JSON.
func ParseJSONBuffer(buffer io.Reader) (*Container, error) {
var gabs Container
jsonDecoder := json.NewDecoder(buffer)
if err := jsonDecoder.Decode(&gabs.object); err != nil {
return nil, err
}
return &gabs, nil
}
//--------------------------------------------------------------------------------------------------

Двоичные данные
vendor/github.com/Jeffail/gabs/gabs_logo.png сгенерированный поставляемый Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 56 KiB

1056
vendor/github.com/Jeffail/gabs/gabs_test.go сгенерированный поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу