mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Merge pull request #2837 from yousong/feature/yousong-cloudnet
Feature/yousong cloudnet
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
region = 'Yunion'
|
||||
address = '10.168.222.136'
|
||||
port = 8891
|
||||
auth_uri = 'http://10.168.222.136:35357/v3'
|
||||
admin_user = 'cloudnetadmin'
|
||||
admin_password = 'xxxxxxxxxxxxxxxx'
|
||||
admin_tenant_name = 'system'
|
||||
sql_connection = 'mysql+pymysql://yunionansible:9YuQL9KEZn1mppaK@localhost:3306/yunionansible?charset=utf8'
|
||||
|
||||
auto_sync_table = True
|
||||
@@ -0,0 +1 @@
|
||||
DESCRIPTION="Yunion Cloudnet"
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
|
||||
"yunion.io/x/onecloud/cmd/climc/promputils"
|
||||
"yunion.io/x/onecloud/cmd/climc/shell"
|
||||
_ "yunion.io/x/onecloud/cmd/climc/shell/cloudnet"
|
||||
_ "yunion.io/x/onecloud/cmd/climc/shell/etcd"
|
||||
_ "yunion.io/x/onecloud/cmd/climc/shell/k8s"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 shell
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
)
|
||||
|
||||
func init() {
|
||||
type AnsiblePlaybookV2IdOptions struct {
|
||||
ID string `help:"name/id of the playbook"`
|
||||
}
|
||||
|
||||
type AnsiblePlaybookV2ListOptions struct {
|
||||
options.BaseListOptions
|
||||
}
|
||||
|
||||
R(&AnsiblePlaybookV2IdOptions{}, "ansibleplaybookv2-show", "Show ansible playbook", func(s *mcclient.ClientSession, opts *AnsiblePlaybookV2IdOptions) error {
|
||||
apb, err := modules.AnsiblePlaybooksV2.Get(s, opts.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(apb)
|
||||
return nil
|
||||
})
|
||||
R(&AnsiblePlaybookV2ListOptions{}, "ansibleplaybookv2-list", "List ansible playbooks", func(s *mcclient.ClientSession, opts *AnsiblePlaybookV2ListOptions) error {
|
||||
params, err := opts.Params()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apbs, err := modules.AnsiblePlaybooksV2.List(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printList(apbs, modules.AnsiblePlaybooksV2.GetColumns(s))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cloudnet
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/cmd/climc/shell"
|
||||
"yunion.io/x/onecloud/pkg/util/printutils"
|
||||
)
|
||||
|
||||
var (
|
||||
R = shell.R
|
||||
printList = printutils.PrintJSONList
|
||||
printObject = printutils.PrintJSONObject
|
||||
printBatchResults = printutils.PrintJSONBatchResults
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 cloudnet
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
modules "yunion.io/x/onecloud/pkg/mcclient/modules/cloudnet"
|
||||
base_options "yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
options "yunion.io/x/onecloud/pkg/mcclient/options/cloudnet"
|
||||
)
|
||||
|
||||
func init() {
|
||||
R(&options.MeshNetworkCreateOptions{}, "meshnetwork-create", "Create mesh network", func(s *mcclient.ClientSession, opts *options.MeshNetworkCreateOptions) error {
|
||||
params, err := base_options.StructToParams(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mn, err := modules.MeshNetworks.Create(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(mn)
|
||||
return nil
|
||||
})
|
||||
R(&options.MeshNetworkGetOptions{}, "meshnetwork-show", "Show mesh network", func(s *mcclient.ClientSession, opts *options.MeshNetworkGetOptions) error {
|
||||
mn, err := modules.MeshNetworks.Get(s, opts.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(mn)
|
||||
return nil
|
||||
})
|
||||
R(&options.MeshNetworkListOptions{}, "meshnetwork-list", "List mesh networks", func(s *mcclient.ClientSession, opts *options.MeshNetworkListOptions) error {
|
||||
params, err := base_options.ListStructToParams(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := modules.MeshNetworks.List(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printList(result, modules.MeshNetworks.GetColumns(s))
|
||||
return nil
|
||||
})
|
||||
R(&options.MeshNetworkUpdateOptions{}, "meshnetwork-update", "Update mesh network", func(s *mcclient.ClientSession, opts *options.MeshNetworkUpdateOptions) error {
|
||||
params, err := base_options.StructToParams(opts)
|
||||
mn, err := modules.MeshNetworks.Update(s, opts.ID, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(mn)
|
||||
return nil
|
||||
})
|
||||
R(&options.MeshNetworkDeleteOptions{}, "meshnetwork-delete", "Delete mesh network", func(s *mcclient.ClientSession, opts *options.MeshNetworkDeleteOptions) error {
|
||||
mn, err := modules.MeshNetworks.Delete(s, opts.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(mn)
|
||||
return nil
|
||||
})
|
||||
R(&options.MeshNetworkActionRealizeOptions{}, "meshnetwork-realize", "Realize mesh network", func(s *mcclient.ClientSession, opts *options.MeshNetworkActionRealizeOptions) error {
|
||||
mn, err := modules.MeshNetworks.PerformAction(s, opts.ID, "realize", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(mn)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 cloudnet
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
modules "yunion.io/x/onecloud/pkg/mcclient/modules/cloudnet"
|
||||
base_options "yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
options "yunion.io/x/onecloud/pkg/mcclient/options/cloudnet"
|
||||
)
|
||||
|
||||
func init() {
|
||||
R(&options.RouterCreateOptions{}, "router-create", "Create router", func(s *mcclient.ClientSession, opts *options.RouterCreateOptions) error {
|
||||
params, err := opts.Params()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
router, err := modules.Routers.Create(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
R(&options.RouterGetOptions{}, "router-show", "Show router", func(s *mcclient.ClientSession, opts *options.RouterGetOptions) error {
|
||||
router, err := modules.Routers.Get(s, opts.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
R(&options.RouterListOptions{}, "router-list", "List routers", func(s *mcclient.ClientSession, opts *options.RouterListOptions) error {
|
||||
params, err := base_options.ListStructToParams(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := modules.Routers.List(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printList(result, modules.Routers.GetColumns(s))
|
||||
return nil
|
||||
})
|
||||
R(&options.RouterUpdateOptions{}, "router-update", "Update router", func(s *mcclient.ClientSession, opts *options.RouterUpdateOptions) error {
|
||||
params, err := base_options.StructToParams(opts)
|
||||
router, err := modules.Routers.Update(s, opts.ID, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
R(&options.RouterDeleteOptions{}, "router-delete", "Delete router", func(s *mcclient.ClientSession, opts *options.RouterDeleteOptions) error {
|
||||
router, err := modules.Routers.Delete(s, opts.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
R(&options.RouterActionJoinMeshNetworkOptions{}, "router-join-meshnetwork", "Router join meshnetwork", func(s *mcclient.ClientSession, opts *options.RouterActionJoinMeshNetworkOptions) error {
|
||||
params, err := base_options.StructToParams(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
router, err := modules.Routers.PerformAction(s, opts.ID, "join-mesh-network", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
R(&options.RouterActionLeaveMeshNetworkOptions{}, "router-leave-meshnetwork", "Router leave meshnetwork", func(s *mcclient.ClientSession, opts *options.RouterActionLeaveMeshNetworkOptions) error {
|
||||
params, err := base_options.StructToParams(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
router, err := modules.Routers.PerformAction(s, opts.ID, "leave-mesh-network", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
R(&options.RouterActionRegisterIfnameOptions{}, "router-register-ifname", "Router register new ifname", func(s *mcclient.ClientSession, opts *options.RouterActionRegisterIfnameOptions) error {
|
||||
params, err := base_options.StructToParams(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
router, err := modules.Routers.PerformAction(s, opts.ID, "register-ifname", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
R(&options.RouterActionUnregisterIfnameOptions{}, "router-unregister-ifname", "Router unregister ifname", func(s *mcclient.ClientSession, opts *options.RouterActionUnregisterIfnameOptions) error {
|
||||
params, err := base_options.StructToParams(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
router, err := modules.Routers.PerformAction(s, opts.ID, "unregister-ifname", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
R(&options.RouterActionRealizeOptions{}, "router-realize", "Router realize", func(s *mcclient.ClientSession, opts *options.RouterActionRealizeOptions) error {
|
||||
params, err := base_options.StructToParams(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
router, err := modules.Routers.PerformAction(s, opts.ID, "realize", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 cloudnet
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
modules "yunion.io/x/onecloud/pkg/mcclient/modules/cloudnet"
|
||||
base_options "yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
options "yunion.io/x/onecloud/pkg/mcclient/options/cloudnet"
|
||||
)
|
||||
|
||||
func init() {
|
||||
R(&options.RouteCreateOptions{}, "router-route-create", "Create router", func(s *mcclient.ClientSession, opts *options.RouteCreateOptions) error {
|
||||
params, err := base_options.StructToParams(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
router, err := modules.Routes.Create(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
R(&options.RouteGetOptions{}, "router-route-show", "Show router", func(s *mcclient.ClientSession, opts *options.RouteGetOptions) error {
|
||||
router, err := modules.Routes.Get(s, opts.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
R(&options.RouteListOptions{}, "router-route-list", "List routers", func(s *mcclient.ClientSession, opts *options.RouteListOptions) error {
|
||||
params, err := base_options.ListStructToParams(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := modules.Routes.List(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printList(result, modules.Routes.GetColumns(s))
|
||||
return nil
|
||||
})
|
||||
R(&options.RouteUpdateOptions{}, "router-route-update", "Update router", func(s *mcclient.ClientSession, opts *options.RouteUpdateOptions) error {
|
||||
params, err := base_options.StructToParams(opts)
|
||||
router, err := modules.Routes.Update(s, opts.ID, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
R(&options.RouteDeleteOptions{}, "router-route-delete", "Delete router", func(s *mcclient.ClientSession, opts *options.RouteDeleteOptions) error {
|
||||
router, err := modules.Routes.Delete(s, opts.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 cloudnet
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
modules "yunion.io/x/onecloud/pkg/mcclient/modules/cloudnet"
|
||||
base_options "yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
options "yunion.io/x/onecloud/pkg/mcclient/options/cloudnet"
|
||||
)
|
||||
|
||||
func init() {
|
||||
R(&options.RuleCreateOptions{}, "router-rule-create", "Create router rule", func(s *mcclient.ClientSession, opts *options.RuleCreateOptions) error {
|
||||
params, err := base_options.StructToParams(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
router, err := modules.Rules.Create(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
R(&options.RuleGetOptions{}, "router-rule-show", "Show router rule", func(s *mcclient.ClientSession, opts *options.RuleGetOptions) error {
|
||||
router, err := modules.Rules.Get(s, opts.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
R(&options.RuleListOptions{}, "router-rule-list", "List router rules", func(s *mcclient.ClientSession, opts *options.RuleListOptions) error {
|
||||
params, err := base_options.ListStructToParams(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := modules.Rules.List(s, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printList(result, modules.Rules.GetColumns(s))
|
||||
return nil
|
||||
})
|
||||
R(&options.RuleUpdateOptions{}, "router-rule-update", "Update router rule", func(s *mcclient.ClientSession, opts *options.RuleUpdateOptions) error {
|
||||
params, err := base_options.StructToParams(opts)
|
||||
router, err := modules.Rules.Update(s, opts.ID, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
R(&options.RuleDeleteOptions{}, "router-rule-delete", "Delete router rule", func(s *mcclient.ClientSession, opts *options.RuleDeleteOptions) error {
|
||||
router, err := modules.Rules.Delete(s, opts.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printObject(router)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/cloudnet/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
service.StartService()
|
||||
}
|
||||
@@ -51,6 +51,7 @@ require (
|
||||
github.com/go-logfmt/logfmt v0.4.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.2 // indirect
|
||||
github.com/go-sql-driver/mysql v1.4.1
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
github.com/gofrs/uuid v3.2.0+incompatible // indirect
|
||||
github.com/golang-plus/errors v1.0.0
|
||||
github.com/golang-plus/testing v1.0.0 // indirect
|
||||
@@ -131,9 +132,10 @@ require (
|
||||
go.uber.org/atomic v1.4.0 // indirect
|
||||
go.uber.org/multierr v1.1.0 // indirect
|
||||
go.uber.org/zap v1.10.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65
|
||||
golang.org/x/sys v0.0.0-20190620070143-6f217b454f45
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20191008142428-8d021180e987
|
||||
google.golang.org/grpc v1.19.0
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||
|
||||
@@ -164,6 +164,8 @@ github.com/go-ole/go-ole v1.2.2/go.mod h1:pnvuG7BrDMZ8ifMurTQmxwhQM/odqm9sSqNe5B
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@@ -194,6 +196,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
|
||||
@@ -255,6 +259,7 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5i
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
@@ -300,6 +305,10 @@ github.com/mdlayher/arp v0.0.0-20190313224443-98a83c8a2717/go.mod h1:eOj1DDj3NAZ
|
||||
github.com/mdlayher/ethernet v0.0.0-20190313224307-5b5fc417d966/go.mod h1:5s5p/sMJ6sNsFl6uCh85lkFGV8kLuIYJCRJLavVJwvg=
|
||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE=
|
||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
|
||||
github.com/mdlayher/genetlink v0.0.0-20191004171646-5cf585d3b847/go.mod h1:LNhNWFVJapYK8zEjVHUIle4gy+Oahfc3UtcaqZ8Dz98=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
github.com/mdlayher/netlink v0.0.0-20191004170026-3c8695cb0643/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v0.0.0-20191008140946-2a17fd90af51/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/raw v0.0.0-20190313224157-43dbcdd7739d/go.mod h1:r1fbeITl2xL/zLbVnNHFyOzQJTgr/3fpf1lJX/cjzR8=
|
||||
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/mdlayher/raw v0.0.0-20190606144222-a54781e5f38f h1:Xjvm7UTnKTwrlhbs+8qA6I0v1iX77rY3QxftSgvOVRk=
|
||||
@@ -310,6 +319,7 @@ github.com/mholt/caddy v0.10.11 h1:s8X+R8DuBbrrMuUTcWSxlDe567B0s5EDmiDBKSYsioY=
|
||||
github.com/mholt/caddy v0.10.11/go.mod h1:Wb1PlT4DAYSqOEd03MsqkdkXnTxA8v9pKjdpxbqM1kY=
|
||||
github.com/miekg/dns v1.1.1 h1:DVkblRdiScEnEr0LR9nTnEQqHYycjkXW9bOjd+2EL2o=
|
||||
github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||
github.com/minio/cli v1.20.0/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
|
||||
github.com/minio/minio-go v6.0.14+incompatible h1:fnV+GD28LeqdN6vT2XdGKW8Qe/IfjJDswNVuni6km9o=
|
||||
github.com/minio/minio-go v6.0.14+incompatible/go.mod h1:7guKYtitv8dktvNUGrhzmNlA5wrAABTQXCoesZdFQO8=
|
||||
@@ -464,6 +474,9 @@ golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@@ -484,6 +497,10 @@ golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954 h1:JGZucVF/L/TotR719NbujzadOZ2AgnYlqphQGHDCKaU=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -505,24 +522,35 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190620070143-6f217b454f45 h1:Dl2hc890lrizvUppGbRWhnIh2f8jOTCQpY5IKWRS0oM=
|
||||
golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190830023255-19e00faab6ad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.zx2c4.com/wireguard v0.0.20190908 h1:SUoXDdwSMtomLdvke+zz83/u9tNvl4hHmcTIWp38tow=
|
||||
golang.zx2c4.com/wireguard v0.0.20190908/go.mod h1:LhfXh5z6bLC2lW2ve6BzYZFwnnsXK3OQjySR0Yh2dO8=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20191008142428-8d021180e987 h1:26OAgqBTufVr8WKonCEhhjO1oKsYhHv0iM5Dg92G1TM=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20191008142428-8d021180e987/go.mod h1:7hq1rEDsx7/FWl8IEEnfH2Xhs6M2MNnjUfN0PeI8Rm0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
|
||||
@@ -29,21 +29,9 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
mcclient_models "yunion.io/x/onecloud/pkg/mcclient/models"
|
||||
mcclient_modules "yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
"yunion.io/x/onecloud/pkg/util/ansible"
|
||||
)
|
||||
|
||||
const (
|
||||
AnsiblePlaybookStatusInit = "init"
|
||||
AnsiblePlaybookStatusRunning = "running"
|
||||
AnsiblePlaybookStatusSucceeded = "succeeded"
|
||||
AnsiblePlaybookStatusFailed = "failed"
|
||||
AnsiblePlaybookStatusCanceled = "canceled"
|
||||
AnsiblePlaybookStatusUnknown = "unknown"
|
||||
)
|
||||
|
||||
// low priority
|
||||
//
|
||||
// - retry times and interval
|
||||
@@ -59,11 +47,6 @@ type SAnsiblePlaybook struct {
|
||||
EndTime time.Time `list:"user"`
|
||||
}
|
||||
|
||||
const (
|
||||
OutputMaxBytes = 64*1024*1024 - 1
|
||||
PlaybookMaxBytes = 64*1024 - 1
|
||||
)
|
||||
|
||||
type SAnsiblePlaybookManager struct {
|
||||
db.SVirtualResourceBaseManager
|
||||
|
||||
@@ -180,34 +163,6 @@ func (apb *SAnsiblePlaybook) PerformStop(ctx context.Context, userCred mcclient.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (apb *SAnsiblePlaybook) fetchPrivateKey(ctx context.Context, userCred mcclient.TokenCredential) (string, error) {
|
||||
s := auth.GetSession(ctx, userCred, "", "")
|
||||
jd := jsonutils.NewDict()
|
||||
var jr jsonutils.JSONObject
|
||||
if userCred.HasSystemAdminPrivilege() {
|
||||
jd.Set("admin", jsonutils.JSONTrue)
|
||||
r, err := mcclient_modules.Sshkeypairs.List(s, jd)
|
||||
if err != nil {
|
||||
return "", errors.WithMessage(err, "get admin ssh key")
|
||||
}
|
||||
jr = r.Data[0]
|
||||
} else {
|
||||
r, err := mcclient_modules.Sshkeypairs.GetById(s, userCred.GetProjectId(), jd)
|
||||
if err != nil {
|
||||
return "", errors.WithMessage(err, "get project ssh key")
|
||||
}
|
||||
jr = r
|
||||
}
|
||||
kp := &mcclient_models.SshKeypair{}
|
||||
if err := jr.Unmarshal(kp); err != nil {
|
||||
return "", errors.WithMessage(err, "unmarshal ssh key")
|
||||
}
|
||||
if kp.PrivateKey == "" {
|
||||
return "", errors.New("empty ssh key")
|
||||
}
|
||||
return kp.PrivateKey, nil
|
||||
}
|
||||
|
||||
func (apb *SAnsiblePlaybook) runPlaybook(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
man := AnsiblePlaybookManager
|
||||
man.sessionsMux.Lock()
|
||||
@@ -218,12 +173,12 @@ func (apb *SAnsiblePlaybook) runPlaybook(ctx context.Context, userCred mcclient.
|
||||
|
||||
// init private key
|
||||
pb := apb.Playbook.Copy()
|
||||
if k, err := apb.fetchPrivateKey(ctx, userCred); err != nil {
|
||||
if k, err := fetchPrivateKey(ctx, userCred); err != nil {
|
||||
return err
|
||||
} else {
|
||||
pb.PrivateKey = []byte(k)
|
||||
}
|
||||
pb.OutputWriter(&ansiblePlaybookOutputRecorder{apb})
|
||||
pb.OutputWriter(&ansiblePlaybookOutputWriter{apb})
|
||||
|
||||
_, err := db.Update(apb, func() error {
|
||||
apb.StartTime = time.Now()
|
||||
@@ -237,6 +192,7 @@ func (apb *SAnsiblePlaybook) runPlaybook(ctx context.Context, userCred mcclient.
|
||||
}
|
||||
|
||||
man.sessions.Add(apb.Id, pb)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
man.sessionsMux.Lock()
|
||||
@@ -286,26 +242,14 @@ func (apb *SAnsiblePlaybook) stopPlaybook(ctx context.Context, userCred mcclient
|
||||
return nil
|
||||
}
|
||||
|
||||
type ansiblePlaybookOutputRecorder struct {
|
||||
apb *SAnsiblePlaybook
|
||||
func (apb *SAnsiblePlaybook) getMaxOutputLength() int {
|
||||
return OutputMaxBytes
|
||||
}
|
||||
|
||||
func (w *ansiblePlaybookOutputRecorder) Write(p []byte) (n int, err error) {
|
||||
apb := w.apb
|
||||
_, err = db.Update(apb, func() error {
|
||||
cur := apb.Output
|
||||
i := len(p) + len(cur) - OutputMaxBytes
|
||||
if i > 0 {
|
||||
// truncate to preserve the tail
|
||||
apb.Output = cur[i:] + string(p)
|
||||
} else {
|
||||
apb.Output += string(p)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("ansibleplaybook %s(%s): record output: %v", apb.Name, apb.Id, err)
|
||||
return 0, err
|
||||
}
|
||||
return len(p), nil
|
||||
func (apb *SAnsiblePlaybook) getOutput() string {
|
||||
return apb.Output
|
||||
}
|
||||
|
||||
func (apb *SAnsiblePlaybook) setOutput(s string) {
|
||||
apb.Output = s
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
mcclient_models "yunion.io/x/onecloud/pkg/mcclient/models"
|
||||
mcclient_modules "yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
)
|
||||
|
||||
func fetchPrivateKey(ctx context.Context, userCred mcclient.TokenCredential) (string, error) {
|
||||
s := auth.GetSession(ctx, userCred, "", "")
|
||||
jd := jsonutils.NewDict()
|
||||
var jr jsonutils.JSONObject
|
||||
if userCred.HasSystemAdminPrivilege() {
|
||||
jd.Set("admin", jsonutils.JSONTrue)
|
||||
r, err := mcclient_modules.Sshkeypairs.List(s, jd)
|
||||
if err != nil {
|
||||
return "", errors.WithMessage(err, "get admin ssh key")
|
||||
}
|
||||
jr = r.Data[0]
|
||||
} else {
|
||||
r, err := mcclient_modules.Sshkeypairs.GetById(s, userCred.GetProjectId(), jd)
|
||||
if err != nil {
|
||||
return "", errors.WithMessage(err, "get project ssh key")
|
||||
}
|
||||
jr = r
|
||||
}
|
||||
kp := &mcclient_models.SshKeypair{}
|
||||
if err := jr.Unmarshal(kp); err != nil {
|
||||
return "", errors.WithMessage(err, "unmarshal ssh key")
|
||||
}
|
||||
if kp.PrivateKey == "" {
|
||||
return "", errors.New("empty ssh key")
|
||||
}
|
||||
return kp.PrivateKey, nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"yunion.io/x/log"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
)
|
||||
|
||||
const (
|
||||
OutputMaxBytes = 64*1024*1024 - 1
|
||||
PlaybookMaxBytes = 64*1024 - 1
|
||||
)
|
||||
|
||||
type ansiblePlaybookOutputRecorder interface {
|
||||
db.IModel
|
||||
getMaxOutputLength() int
|
||||
getOutput() string
|
||||
setOutput(string)
|
||||
}
|
||||
|
||||
type ansiblePlaybookOutputWriter struct {
|
||||
rec ansiblePlaybookOutputRecorder
|
||||
}
|
||||
|
||||
func (w *ansiblePlaybookOutputWriter) Write(p []byte) (n int, err error) {
|
||||
rec := w.rec
|
||||
_, err = db.Update(rec, func() error {
|
||||
cur := rec.getOutput()
|
||||
i := len(p) + len(cur) - rec.getMaxOutputLength()
|
||||
if i > 0 {
|
||||
// truncate to preserve the tail
|
||||
rec.setOutput(cur[i:] + string(p))
|
||||
} else {
|
||||
rec.setOutput(cur + string(p))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("%s %s(%s): record output: %v", rec.Keyword(), rec.GetName(), rec.GetId(), err)
|
||||
return 0, err
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/util/ansible"
|
||||
"yunion.io/x/onecloud/pkg/util/ansiblev2"
|
||||
)
|
||||
|
||||
// This is at the moment for internal use only. Update is not allowed
|
||||
type SAnsiblePlaybookV2 struct {
|
||||
db.SVirtualResourceBase
|
||||
|
||||
Playbook string `length:"text" nullable:"false" create:"required" get:"user"`
|
||||
Inventory string `length:"text" nullable:"false" create:"required" get:"user"`
|
||||
Files string `length:"text" nullable:"false" create:"optional" get:"user"`
|
||||
Output string `length:"medium" get:"user"`
|
||||
StartTime time.Time `list:"user"`
|
||||
EndTime time.Time `list:"user"`
|
||||
|
||||
CreatorMark string `length:"32" nullable:"false" create:"optional" get:"user"`
|
||||
}
|
||||
|
||||
type SAnsiblePlaybookV2Manager struct {
|
||||
db.SVirtualResourceBaseManager
|
||||
|
||||
sessions ansible.SessionManager
|
||||
sessionsMux *sync.Mutex
|
||||
}
|
||||
|
||||
var AnsiblePlaybookV2Manager *SAnsiblePlaybookV2Manager
|
||||
|
||||
func init() {
|
||||
AnsiblePlaybookV2Manager = &SAnsiblePlaybookV2Manager{
|
||||
SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
|
||||
SAnsiblePlaybookV2{},
|
||||
"ansibleplaybooks_v2_tbl",
|
||||
"ansibleplaybook_v2",
|
||||
"ansibleplaybooks_v2",
|
||||
),
|
||||
sessions: ansible.SessionManager{},
|
||||
sessionsMux: &sync.Mutex{},
|
||||
}
|
||||
AnsiblePlaybookV2Manager.SetVirtualObject(AnsiblePlaybookV2Manager)
|
||||
}
|
||||
|
||||
func (man *SAnsiblePlaybookV2Manager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
|
||||
data.Set("status", jsonutils.NewString(AnsiblePlaybookStatusInit))
|
||||
return man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data)
|
||||
}
|
||||
|
||||
func (apb *SAnsiblePlaybookV2) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
apb.SVirtualResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
|
||||
err := apb.runPlaybook(ctx, userCred)
|
||||
if err != nil {
|
||||
log.Errorf("postCreate: runPlaybook: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (man *SAnsiblePlaybookV2Manager) InitializeData() error {
|
||||
pbs := []SAnsiblePlaybookV2{}
|
||||
q := AnsiblePlaybookV2Manager.Query()
|
||||
q = q.Filter(sqlchemy.Equals(q.Field("status"), AnsiblePlaybookStatusRunning))
|
||||
if err := db.FetchModelObjects(AnsiblePlaybookV2Manager, q, &pbs); err != nil {
|
||||
return errors.WithMessage(err, "fetch running playbooks")
|
||||
}
|
||||
for i := 0; i < len(pbs); i++ {
|
||||
pb := &pbs[i]
|
||||
_, err := db.Update(pb, func() error {
|
||||
pb.Status = AnsiblePlaybookStatusUnknown
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("set playbook %s(%s) to unknown state: %v", pb.Name, pb.Id, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (apb *SAnsiblePlaybookV2) ValidateDeleteCondition(ctx context.Context) error {
|
||||
if apb.Status == AnsiblePlaybookStatusRunning {
|
||||
return httperrors.NewConflictError("playbook is in running state")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (apb *SAnsiblePlaybookV2) AllowPerformRun(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return apb.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, apb, "run")
|
||||
}
|
||||
|
||||
func (apb *SAnsiblePlaybookV2) PerformRun(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
err := apb.runPlaybook(ctx, userCred)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewConflictError("%s", err.Error())
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (apb *SAnsiblePlaybookV2) AllowPerformStop(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return apb.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, apb, "stop")
|
||||
}
|
||||
|
||||
func (apb *SAnsiblePlaybookV2) PerformStop(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
err := apb.stopPlaybook(ctx, userCred)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewConflictError("%s", err.Error())
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (apb *SAnsiblePlaybookV2) runPlaybook(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
man := AnsiblePlaybookV2Manager
|
||||
man.sessionsMux.Lock()
|
||||
defer man.sessionsMux.Unlock()
|
||||
if man.sessions.Has(apb.Id) {
|
||||
return fmt.Errorf("playbook is already running")
|
||||
}
|
||||
|
||||
var (
|
||||
privateKey string
|
||||
err error
|
||||
files = map[string][]byte{}
|
||||
)
|
||||
if apb.Files != "" {
|
||||
obj, err := jsonutils.ParseString(apb.Files)
|
||||
if err != nil {
|
||||
return fmt.Errorf("playbook files json: parse: %v", err)
|
||||
}
|
||||
filesJ, err := obj.GetMap()
|
||||
if err != nil {
|
||||
return fmt.Errorf("playbook files json: get map: %v", err)
|
||||
}
|
||||
for name, obj := range filesJ {
|
||||
content, err := obj.GetString()
|
||||
if err != nil {
|
||||
return fmt.Errorf("playbook files json: get content %s: %v", name, err)
|
||||
}
|
||||
files[name] = []byte(content)
|
||||
}
|
||||
}
|
||||
// init private key
|
||||
if privateKey, err = fetchPrivateKey(ctx, userCred); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Update(apb, func() error {
|
||||
apb.StartTime = time.Now()
|
||||
apb.EndTime = time.Time{}
|
||||
apb.Output = ""
|
||||
apb.Status = AnsiblePlaybookStatusRunning
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("run playbook: update db failed before run: %v", err)
|
||||
}
|
||||
|
||||
sess := ansiblev2.NewSession().
|
||||
Inventory(apb.Inventory).
|
||||
Playbook(apb.Playbook).
|
||||
PrivateKey(privateKey).
|
||||
Files(files).
|
||||
OutputWriter(&ansiblePlaybookOutputWriter{apb})
|
||||
man.sessions.Add(apb.Id, sess)
|
||||
|
||||
// NOTE host state check? run only on online hosts and running guests, skip others
|
||||
go func() {
|
||||
defer func() {
|
||||
man.sessionsMux.Lock()
|
||||
defer man.sessionsMux.Unlock()
|
||||
man.sessions.Remove(apb.Id)
|
||||
}()
|
||||
runErr := man.sessions.Run(apb.Id)
|
||||
|
||||
_, err := db.Update(apb, func() error {
|
||||
err := man.sessions.Err(apb.Id)
|
||||
if err != nil {
|
||||
apb.Status = AnsiblePlaybookStatusCanceled
|
||||
} else if runErr != nil {
|
||||
log.Warningf("playbook %s(%s) failed: %v", apb.Name, apb.Id, runErr)
|
||||
apb.Status = AnsiblePlaybookStatusFailed
|
||||
} else {
|
||||
apb.Status = AnsiblePlaybookStatusSucceeded
|
||||
}
|
||||
apb.EndTime = time.Now()
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("updating ansible playbook failed: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (apb *SAnsiblePlaybookV2) stopPlaybook(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
man := AnsiblePlaybookV2Manager
|
||||
man.sessionsMux.Lock()
|
||||
defer man.sessionsMux.Unlock()
|
||||
if !man.sessions.Has(apb.Id) {
|
||||
return fmt.Errorf("playbook is not running")
|
||||
}
|
||||
// the playbook will be removed from session map in runPlaybook() on return from run
|
||||
man.sessions.Stop(apb.Id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (apb *SAnsiblePlaybookV2) getMaxOutputLength() int {
|
||||
return OutputMaxBytes
|
||||
}
|
||||
|
||||
func (apb *SAnsiblePlaybookV2) getOutput() string {
|
||||
return apb.Output
|
||||
}
|
||||
|
||||
func (apb *SAnsiblePlaybookV2) setOutput(s string) {
|
||||
apb.Output = s
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
const (
|
||||
AnsiblePlaybookStatusInit = "init"
|
||||
AnsiblePlaybookStatusRunning = "running"
|
||||
AnsiblePlaybookStatusSucceeded = "succeeded"
|
||||
AnsiblePlaybookStatusFailed = "failed"
|
||||
AnsiblePlaybookStatusCanceled = "canceled"
|
||||
AnsiblePlaybookStatusUnknown = "unknown"
|
||||
)
|
||||
@@ -43,6 +43,7 @@ func InitHandlers(app *appsrv.Application) {
|
||||
db.RegisterModelManager(db.TenantCacheManager)
|
||||
for _, manager := range []db.IModelManager{
|
||||
models.AnsiblePlaybookManager,
|
||||
models.AnsiblePlaybookV2Manager,
|
||||
} {
|
||||
db.RegisterModelManager(manager)
|
||||
handler := db.NewModelHandler(manager)
|
||||
|
||||
@@ -33,6 +33,7 @@ const (
|
||||
ERR_MISSING_KEY
|
||||
ERR_INVALID_TYPE
|
||||
ERR_INVALID_CHOICE
|
||||
ERR_INVALID_LENGTH
|
||||
ERR_NOT_IN_RANGE
|
||||
ERR_INVALID_VALUE
|
||||
ERR_MODEL_MANAGER
|
||||
@@ -45,6 +46,7 @@ var errTypeToString = map[ErrType]string{
|
||||
ERR_MISSING_KEY: "Missing key error",
|
||||
ERR_INVALID_TYPE: "Invalid type error",
|
||||
ERR_INVALID_CHOICE: "Invalid choice error",
|
||||
ERR_INVALID_LENGTH: "Invalid length error",
|
||||
ERR_NOT_IN_RANGE: "Not in range error",
|
||||
ERR_INVALID_VALUE: "Invalid value error",
|
||||
ERR_MODEL_MANAGER: "Model manager error",
|
||||
@@ -85,6 +87,14 @@ func newInvalidChoiceError(key string, choices choices.Choices, choice string) e
|
||||
return newError(ERR_INVALID_CHOICE, "invalid %q, want %s, got %s", key, choices, choice)
|
||||
}
|
||||
|
||||
func newStringTooShortError(key string, got, want int) error {
|
||||
return newError(ERR_INVALID_LENGTH, "%q too short, got %d, min %s", key, got, want)
|
||||
}
|
||||
|
||||
func newStringTooLongError(key string, got, want int) error {
|
||||
return newError(ERR_INVALID_LENGTH, "%q too long, got %d, max %s", key, got, want)
|
||||
}
|
||||
|
||||
func newNotInRangeError(key string, value, lower, upper int64) error {
|
||||
return newError(ERR_NOT_IN_RANGE, "invalid %q: %d, want [%d,%d]", key, value, lower, upper)
|
||||
}
|
||||
@@ -93,6 +103,10 @@ func newInvalidValueError(key string, value string) error {
|
||||
return newError(ERR_INVALID_VALUE, "invalid %q: %s", key, value)
|
||||
}
|
||||
|
||||
func newInvalidValueErrorEx(key string, err error) error {
|
||||
return newError(ERR_INVALID_VALUE, "invalid %q: %v", key, err)
|
||||
}
|
||||
|
||||
func newInvalidStructError(key string, err error) error {
|
||||
errFmt := "invalid %q: "
|
||||
params := []interface{}{key}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 validators
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/util/netutils"
|
||||
)
|
||||
|
||||
type IValidateActor interface {
|
||||
Validate(val interface{}) (bool, error)
|
||||
Set(data *jsonutils.JSONDict, key string, val interface{})
|
||||
getValue() interface{}
|
||||
}
|
||||
|
||||
type actorSetString struct{}
|
||||
|
||||
func (actor *actorSetString) Set(data *jsonutils.JSONDict, key string, val interface{}) {
|
||||
s := val.(string)
|
||||
data.Set(key, jsonutils.NewString(s))
|
||||
}
|
||||
|
||||
type ActorIPv4Prefix struct {
|
||||
actorSetString
|
||||
|
||||
val *netutils.IPV4Prefix
|
||||
}
|
||||
|
||||
func NewActorIPv4Prefix() *ActorIPv4Prefix {
|
||||
return &ActorIPv4Prefix{}
|
||||
}
|
||||
|
||||
func (actor *ActorIPv4Prefix) Validate(val interface{}) (bool, error) {
|
||||
s, ok := val.(string)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("invalid type, want string, got %t", val)
|
||||
}
|
||||
p, err := netutils.NewIPV4Prefix(s)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
actor.val = &p
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (actor *ActorIPv4Prefix) getValue() interface{} {
|
||||
return actor.val
|
||||
}
|
||||
|
||||
type ActorJoinedBy struct {
|
||||
sep string
|
||||
trimSpace bool
|
||||
ignoreEmpty bool
|
||||
actor IValidateActor
|
||||
|
||||
sanitized string
|
||||
vals []interface{}
|
||||
}
|
||||
|
||||
func NewActorJoinedBy(sep string, subActor IValidateActor) *ActorJoinedBy {
|
||||
return &ActorJoinedBy{
|
||||
sep: sep,
|
||||
trimSpace: true,
|
||||
ignoreEmpty: true,
|
||||
actor: subActor,
|
||||
}
|
||||
}
|
||||
|
||||
func (actor *ActorJoinedBy) TrimSpace(b bool) *ActorJoinedBy {
|
||||
actor.trimSpace = b
|
||||
return actor
|
||||
}
|
||||
|
||||
func (actor *ActorJoinedBy) IgnoreEmpty(b bool) *ActorJoinedBy {
|
||||
actor.ignoreEmpty = b
|
||||
return actor
|
||||
}
|
||||
|
||||
func (actor *ActorJoinedBy) Validate(val interface{}) (bool, error) {
|
||||
s, ok := val.(string)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("invalid type, want string, got %t", val)
|
||||
}
|
||||
parts := strings.Split(s, actor.sep)
|
||||
sanitizedParts := make([]string, 0, len(parts))
|
||||
vals := make([]interface{}, 0, len(parts))
|
||||
for i, part := range parts {
|
||||
if actor.trimSpace {
|
||||
part = strings.TrimSpace(part)
|
||||
}
|
||||
if actor.ignoreEmpty && part == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := actor.actor.Validate(part); err != nil {
|
||||
return false, fmt.Errorf("bad value at index %d: %v", i, err)
|
||||
}
|
||||
sanitizedParts = append(sanitizedParts, part)
|
||||
vals = append(vals, actor.actor.getValue())
|
||||
}
|
||||
actor.sanitized = strings.Join(sanitizedParts, actor.sep)
|
||||
actor.vals = vals
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (actor *ActorJoinedBy) Set(data *jsonutils.JSONDict, key string, val interface{}) {
|
||||
data.Set(key, jsonutils.NewString(actor.sanitized))
|
||||
}
|
||||
|
||||
func (actor *ActorJoinedBy) getValue() interface{} {
|
||||
return actor.vals
|
||||
}
|
||||
|
||||
type ValidatorByActor struct {
|
||||
Validator
|
||||
Value interface{}
|
||||
|
||||
actor IValidateActor
|
||||
}
|
||||
|
||||
func NewValidatorByActor(key string, actor IValidateActor) *ValidatorByActor {
|
||||
v := &ValidatorByActor{
|
||||
Validator: Validator{Key: key},
|
||||
actor: actor,
|
||||
}
|
||||
v.SetParent(v)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *ValidatorByActor) Default(val interface{}) IValidator {
|
||||
if ok, err := v.actor.Validate(val); !ok {
|
||||
panic(fmt.Sprintf("invalid default value: %v", err))
|
||||
}
|
||||
return v.Validator.Default(val)
|
||||
}
|
||||
|
||||
func (v *ValidatorByActor) getValue() interface{} {
|
||||
return v.Value
|
||||
}
|
||||
|
||||
func (v *ValidatorByActor) Validate(data *jsonutils.JSONDict) error {
|
||||
if err, isSet := v.Validator.validateEx(data); err != nil || !isSet {
|
||||
return err
|
||||
}
|
||||
val := v.value.Interface()
|
||||
if ok, err := v.actor.Validate(val); !ok {
|
||||
return newInvalidValueErrorEx(v.Key, err)
|
||||
}
|
||||
v.actor.Set(data, v.Key, val)
|
||||
v.Value = v.actor.getValue()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 validators
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"yunion.io/x/pkg/util/netutils"
|
||||
)
|
||||
|
||||
type actorJoinedByCase struct {
|
||||
sep string
|
||||
trimSpace bool
|
||||
ignoreEmpty bool
|
||||
C
|
||||
}
|
||||
|
||||
func (ac *actorJoinedByCase) Validator() *ValidatorByActor {
|
||||
actor := NewActorJoinedBy(ac.sep,
|
||||
NewActorIPv4Prefix(),
|
||||
).TrimSpace(ac.trimSpace).IgnoreEmpty(ac.ignoreEmpty)
|
||||
return NewValidatorByActor("s", actor)
|
||||
}
|
||||
|
||||
func TestJoinedByActor(t *testing.T) {
|
||||
var valsWant []interface{}
|
||||
for _, n := range []string{"10.0.0.0/8", "192.168.0.0/16"} {
|
||||
p, _ := netutils.NewIPV4Prefix(n)
|
||||
valsWant = append(valsWant, &p)
|
||||
}
|
||||
cases := []*actorJoinedByCase{
|
||||
{
|
||||
C: C{
|
||||
Name: "missing non-optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Optional: false,
|
||||
Err: ERR_MISSING_KEY,
|
||||
ValueWant: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
C: C{
|
||||
Name: "missing optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Optional: true,
|
||||
ValueWant: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
sep: ",",
|
||||
C: C{
|
||||
Name: "missing with default",
|
||||
In: `{}`,
|
||||
Out: `{s: "10.0.0.0/8,192.168.0.0/16"}`,
|
||||
Default: "10.0.0.0/8,192.168.0.0/16",
|
||||
ValueWant: valsWant,
|
||||
},
|
||||
},
|
||||
{
|
||||
sep: ",",
|
||||
C: C{
|
||||
Name: "good in",
|
||||
In: `{"s": "10.0.0.0/8,192.168.0.0/16"}`,
|
||||
Out: `{"s": "10.0.0.0/8,192.168.0.0/16"}`,
|
||||
ValueWant: valsWant,
|
||||
},
|
||||
},
|
||||
{
|
||||
sep: ",",
|
||||
ignoreEmpty: true,
|
||||
C: C{
|
||||
Name: "good in (nothing)",
|
||||
In: `{"s": ""}`,
|
||||
Out: `{"s": ""}`,
|
||||
ValueWant: []interface{}{},
|
||||
},
|
||||
},
|
||||
{
|
||||
sep: ",",
|
||||
C: C{
|
||||
Name: "good in (0.0.0.0/32)",
|
||||
In: `{"s": ""}`,
|
||||
Out: `{"s": ""}`,
|
||||
ValueWant: []interface{}{func() *netutils.IPV4Prefix {
|
||||
p, _ := netutils.NewIPV4Prefix("0.0.0.0/32")
|
||||
return &p
|
||||
}()},
|
||||
},
|
||||
},
|
||||
{
|
||||
sep: ",",
|
||||
trimSpace: true,
|
||||
ignoreEmpty: true,
|
||||
C: C{
|
||||
Name: "good in (ignore empty, trim space)",
|
||||
In: `{"s": ",,, 10.0.0.0/8 , 192.168.0.0/16, ,,"}`,
|
||||
Out: `{"s": "10.0.0.0/8,192.168.0.0/16"}`,
|
||||
ValueWant: valsWant,
|
||||
},
|
||||
},
|
||||
{
|
||||
sep: ",",
|
||||
ignoreEmpty: false,
|
||||
C: C{
|
||||
Name: "bad in (empty)",
|
||||
In: `{"s": ",,, 10.0.0.0/8 , 192.168.0.0/16, ,,"}`,
|
||||
Out: `{"s": ",,, 10.0.0.0/8 , 192.168.0.0/16, ,,"}`,
|
||||
Err: ERR_INVALID_VALUE,
|
||||
ValueWant: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
sep: ",",
|
||||
trimSpace: false,
|
||||
C: C{
|
||||
Name: "bad in (space)",
|
||||
In: `{"s": ",,, 10.0.0.0/8 , 192.168.0.0/16, ,,"}`,
|
||||
Out: `{"s": ",,, 10.0.0.0/8 , 192.168.0.0/16, ,,"}`,
|
||||
Err: ERR_INVALID_VALUE,
|
||||
ValueWant: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
C: C{
|
||||
Name: "bad in (bad value)",
|
||||
In: `{"s": "10.0.0.259/32"}`,
|
||||
Out: `{"s": "10.0.0.259/32"}`,
|
||||
Err: ERR_INVALID_VALUE,
|
||||
ValueWant: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.C.Name, func(t *testing.T) {
|
||||
v := c.Validator()
|
||||
if c.Default != nil {
|
||||
s := c.Default.(string)
|
||||
v.Default(s)
|
||||
}
|
||||
if c.Optional {
|
||||
v.Optional(true)
|
||||
}
|
||||
testS(t, v, &c.C)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 validators
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
)
|
||||
|
||||
type ValidatorSSHKey struct {
|
||||
Validator
|
||||
Value string
|
||||
Signer ssh.Signer
|
||||
}
|
||||
|
||||
func NewSSHKeyValidator(key string) *ValidatorSSHKey {
|
||||
v := &ValidatorSSHKey{
|
||||
Validator: Validator{Key: key},
|
||||
}
|
||||
v.SetParent(v)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *ValidatorSSHKey) parseKey(s string) (ssh.Signer, error) {
|
||||
signer, err := ssh.ParsePrivateKey([]byte(s))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
func (v *ValidatorSSHKey) Default(s string) IValidator {
|
||||
_, err := v.parseKey(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
v.Validator.Default(s)
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *ValidatorSSHKey) getValue() interface{} {
|
||||
return v.Value
|
||||
}
|
||||
|
||||
func (v *ValidatorSSHKey) Validate(data *jsonutils.JSONDict) error {
|
||||
if err, isSet := v.Validator.validateEx(data); err != nil || !isSet {
|
||||
return err
|
||||
}
|
||||
s, err := v.value.GetString()
|
||||
if err != nil {
|
||||
return newGeneralError(v.Key, err)
|
||||
}
|
||||
if signer, err := v.parseKey(s); err != nil {
|
||||
return newInvalidValueError(v.Key, s)
|
||||
} else {
|
||||
v.Signer = signer
|
||||
}
|
||||
data.Set(v.Key, jsonutils.NewString(s))
|
||||
v.Value = s
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 validators
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSSHKeyValidator(t *testing.T) {
|
||||
aKey := `
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAvWB7GIQ5nuffEtS5L5fPvkBP8MjOLuuIkn+G+BS8HDhWkXr4
|
||||
jYokpPz/mXwdk2VmJONQw28XmAvJTLyT6xZPNOehBENm6VNakY7PJ4fXAoHFNMaF
|
||||
crkj3KmwjQXQAEX6Ul7vOVl8wRBMWQ3iQiR2OKvnIkrWZF7Y4lEQVJkHTPzp7GLv
|
||||
LjmiOEnOZdtrM6YtrRtIdfEk/vGDJL86xOAWmM7vMFrH9obuhyXwqtomGn+4II3C
|
||||
j4ShMWKQkmHoOOmd14At+fhHlKkvtAOtDOlCXB/svKSvqBfzjbYORHCtXXc8WvID
|
||||
yz2OCPgh9VgxAIcgfEt4cKvtZjz3hlcCgIY0qQIDAQABAoIBAQCPNCQhd+typHhl
|
||||
bwLSYIQxo8RPmimABY/y6AiSFGvjEx8zR8Aol+v5728BC3/589V304VBJAK9cTw5
|
||||
kOhx/x7KLNXvuWBa1DNKmqk/hVMrjCIqNGy5QhNCS/c7zMdrTX9rRmqz/V1/SOnS
|
||||
9dLAnX3ggO15WwogQDDVguNMdaO1rMWtO/DZS53rVbhSxfM5JPq2oxAucDKxlhjT
|
||||
MGdF/iK3/NyRft36QEbBB9qG+97YvXUmXFF2UVFAro9k+nWzR7BLGhkcDRRc5FsK
|
||||
R/Vixe3G8pR9/XBs6MyWK/HlGBEvzdlehyEw1duGxEUNdBALcVtZXTZ37L6pn8G9
|
||||
yHSvzzcBAoGBAOPUGPC77CBR9d0YP+YydkTjayV+p55k1vhrW/uvlSKZO/LwZ5b5
|
||||
T/2qcmWt2SiXE6A6YUOwq2NJvBPeq+6WJBVqQoPsiVg7mc0j5/nP0TtH2GGgfPBm
|
||||
JKCG+bdrh5OcVkql5fdX35zLSDkKNnxASS4bEAISdViBelmPN/QH7ayxAoGBANTL
|
||||
MtnwhKj9Eas7yNfV6H1Va6AUuB3nx+ZDcFq/mPNzPE15Ddv3gqoQqi9doXe61oGx
|
||||
X4XfMiKBlo5Jzh2u5LcO6fAmV1itCi2pyWcKk8Nc+Aarc36HsOLao4sw1zczCVyt
|
||||
BF0TlxKeRpqBT/uQfmEThwbN8LUyoBKGl2oBWiV5AoGAHpW0m2y+8D/Qf9PnkCGq
|
||||
Gulk0u3D1tG2wja3bHxPywtDLwPzBCOIB4fAP8Is6vQNIG916z5mY7fcVdaIwkjJ
|
||||
o05Wi5tPfNbTeOSfGbw6XHjypXiEDUnJFPvJvkPjOX+9XdwTmTbkwAnSMkYatmdy
|
||||
64uahIyx0CXhpPBDFLGTyKECgYAO4XLZ6MbuJlxg9BpUdaH/ecS/+hLyDG5fPOIT
|
||||
hoiEpc9Wv5tngYSCrg2oqEyNWeR8R1Idw4D3BsbnhmPCkaNu5b0YTSYYjmlCzjfG
|
||||
W+f/ZnX1yXGXLJgDFTUQm8bBFnGWKIdAlwkehTD8xwQ33F/qG/p6UFZ/5V1qTj0y
|
||||
bYvHSQKBgBwzshyuDA/QSxSVDn2HI3hK1202eAN2PERsBGP2VSAEpIwav2KvBVva
|
||||
p30+rx5gwUquGpB24gyHlZ0l3eVbONop84wOS8eoA4wUyXBlkgzvZvlFvJCpm8x1
|
||||
qtvUqlXM7TheLX3gGucB76fmc+wLs06QPHd0sxAlTGcwwBVOUPvH
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
||||
cases := []*C{
|
||||
{
|
||||
Name: "missing non-optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Optional: false,
|
||||
Err: ERR_MISSING_KEY,
|
||||
ValueWant: "",
|
||||
},
|
||||
{
|
||||
Name: "missing optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Optional: true,
|
||||
ValueWant: "",
|
||||
},
|
||||
{
|
||||
Name: "missing with default",
|
||||
In: `{}`,
|
||||
Out: `{s: "` + aKey + `"}`,
|
||||
Default: aKey,
|
||||
ValueWant: aKey,
|
||||
},
|
||||
{
|
||||
Name: "good in",
|
||||
In: `{"s": "` + aKey + `"}`,
|
||||
Out: `{"s": "` + aKey + `"}`,
|
||||
ValueWant: aKey,
|
||||
},
|
||||
{
|
||||
Name: "bad in",
|
||||
In: `{"s": "0"}`,
|
||||
Out: `{"s": "0"}`,
|
||||
Err: ERR_INVALID_VALUE,
|
||||
ValueWant: "",
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
v := NewSSHKeyValidator("s")
|
||||
if c.Default != nil {
|
||||
s := c.Default.(string)
|
||||
v.Default(s)
|
||||
}
|
||||
if c.Optional {
|
||||
v.Optional(true)
|
||||
}
|
||||
testS(t, v, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 validators
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
)
|
||||
|
||||
type ValidatorStringLen struct {
|
||||
Validator
|
||||
Value string
|
||||
minLen int
|
||||
maxLen int
|
||||
}
|
||||
|
||||
func NewStringLenRangeValidator(key string, minLen, maxLen int) *ValidatorStringLen {
|
||||
v := &ValidatorStringLen{
|
||||
Validator: Validator{Key: key},
|
||||
minLen: minLen,
|
||||
maxLen: maxLen,
|
||||
}
|
||||
v.SetParent(v)
|
||||
return v
|
||||
}
|
||||
|
||||
func NewMinStringLenValidator(key string, minLen int) *ValidatorStringLen {
|
||||
return NewStringLenRangeValidator(key, minLen, -1)
|
||||
}
|
||||
|
||||
func NewMaxStringLenValidator(key string, maxLen int) *ValidatorStringLen {
|
||||
return NewStringLenRangeValidator(key, -1, maxLen)
|
||||
}
|
||||
|
||||
func NewStringNonEmptyValidator(key string) *ValidatorStringLen {
|
||||
return NewMinStringLenValidator(key, 1)
|
||||
}
|
||||
|
||||
func (v *ValidatorStringLen) Default(s string) IValidator {
|
||||
if v.minLen >= 0 && len(s) < v.minLen {
|
||||
panic("invalid default string: shorter than validator requirement")
|
||||
}
|
||||
if v.maxLen >= 0 && len(s) > v.maxLen {
|
||||
panic("invalid default string: shorter than validator requirement")
|
||||
}
|
||||
return v.Validator.Default(s)
|
||||
}
|
||||
|
||||
func (v *ValidatorStringLen) getValue() interface{} {
|
||||
return v.Value
|
||||
}
|
||||
|
||||
func (v *ValidatorStringLen) Validate(data *jsonutils.JSONDict) error {
|
||||
if err, isSet := v.Validator.validateEx(data); err != nil || !isSet {
|
||||
return err
|
||||
}
|
||||
s, err := v.value.GetString()
|
||||
if err != nil {
|
||||
return newGeneralError(v.Key, err)
|
||||
}
|
||||
if v.minLen >= 0 && len(s) < v.minLen {
|
||||
return newStringTooShortError(v.Key, len(s), v.minLen)
|
||||
}
|
||||
if v.maxLen >= 0 && len(s) > v.maxLen {
|
||||
return newStringTooLongError(v.Key, len(s), v.maxLen)
|
||||
}
|
||||
data.Set(v.Key, jsonutils.NewString(s))
|
||||
v.Value = s
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 validators
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringLenValidator(t *testing.T) {
|
||||
cases := []*C{
|
||||
{
|
||||
Name: "missing non-optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Optional: false,
|
||||
Err: ERR_MISSING_KEY,
|
||||
ValueWant: "",
|
||||
},
|
||||
{
|
||||
Name: "missing optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Optional: true,
|
||||
ValueWant: "",
|
||||
},
|
||||
{
|
||||
Name: "missing with default",
|
||||
In: `{}`,
|
||||
Out: `{s: "12345"}`,
|
||||
Default: "12345",
|
||||
ValueWant: "12345",
|
||||
},
|
||||
{
|
||||
Name: "stringified",
|
||||
In: `{"s": 100}`,
|
||||
Out: `{s: "100"}`,
|
||||
ValueWant: "100",
|
||||
},
|
||||
{
|
||||
Name: "stringified too long",
|
||||
In: `{"s": 9876543210}`,
|
||||
Out: `{"s": 9876543210}`,
|
||||
Err: ERR_INVALID_LENGTH,
|
||||
ValueWant: "",
|
||||
},
|
||||
{
|
||||
Name: "stringified too short",
|
||||
In: `{"s": 0}`,
|
||||
Out: `{"s": 0}`,
|
||||
Err: ERR_INVALID_LENGTH,
|
||||
ValueWant: "",
|
||||
},
|
||||
{
|
||||
Name: "good length",
|
||||
In: `{"s": "abcde"}`,
|
||||
Out: `{"s": "abcde"}`,
|
||||
ValueWant: "abcde",
|
||||
},
|
||||
{
|
||||
Name: "bad length (too short)",
|
||||
In: `{"s": "0"}`,
|
||||
Out: `{"s": "0"}`,
|
||||
Err: ERR_INVALID_LENGTH,
|
||||
ValueWant: "",
|
||||
},
|
||||
{
|
||||
Name: "bad length (too long)",
|
||||
In: `{"s": "9876543210"}`,
|
||||
Out: `{"s": "9876543210"}`,
|
||||
Err: ERR_INVALID_LENGTH,
|
||||
ValueWant: "",
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
v := NewStringLenRangeValidator("s", 2, 5)
|
||||
if c.Default != nil {
|
||||
s := c.Default.(string)
|
||||
v.Default(s)
|
||||
}
|
||||
if c.Optional {
|
||||
v.Optional(true)
|
||||
}
|
||||
testS(t, v, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models // import "yunion.io/x/onecloud/pkg/cloudnet/models"
|
||||
@@ -0,0 +1,212 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/log"
|
||||
yerrors "yunion.io/x/pkg/util/errors"
|
||||
"yunion.io/x/pkg/util/netutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
type SIfacePeer struct {
|
||||
db.SStandaloneResourceBase
|
||||
|
||||
RouterId string
|
||||
IfaceId string
|
||||
|
||||
PeerIfaceId string
|
||||
PeerRouterId string
|
||||
|
||||
PublicKey string
|
||||
AllowedIPs string
|
||||
Endpoint string
|
||||
PersistentKeepalive int
|
||||
}
|
||||
|
||||
type SIfacePeerManager struct {
|
||||
db.SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var IfacePeerManager *SIfacePeerManager
|
||||
|
||||
func init() {
|
||||
IfacePeerManager = &SIfacePeerManager{
|
||||
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
|
||||
SIfacePeer{},
|
||||
"ifacepeers_tbl",
|
||||
"ifacepeer",
|
||||
"ifacepeers",
|
||||
),
|
||||
}
|
||||
IfacePeerManager.SetVirtualObject(IfacePeerManager)
|
||||
}
|
||||
|
||||
func (ifacePeer *SIfacePeer) subnetsStrList() []string {
|
||||
return strings.Split(ifacePeer.AllowedIPs, ",")
|
||||
}
|
||||
|
||||
func (ifacePeer *SIfacePeer) subnetsParsed() Subnets {
|
||||
parts := ifacePeer.subnetsStrList()
|
||||
r := make([]*netutils.IPV4Prefix, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
p, err := netutils.NewIPV4Prefix(part)
|
||||
if err != nil {
|
||||
log.Errorf("%s: invalid subnet sneaked in: %s", ifacePeer.Id, part)
|
||||
return nil
|
||||
}
|
||||
r = append(r, &p)
|
||||
}
|
||||
return Subnets(r)
|
||||
}
|
||||
|
||||
func (man *SIfacePeerManager) removeByPeerIface(ctx context.Context, userCred mcclient.TokenCredential, iface *SIface) error {
|
||||
peers := []SIfacePeer{}
|
||||
q := man.Query().Equals("peer_iface_id", iface.Id)
|
||||
if err := db.FetchModelObjects(IfacePeerManager, q, &peers); err != nil {
|
||||
return err
|
||||
}
|
||||
var errs []error
|
||||
for j := range peers {
|
||||
if err := peers[j].Delete(ctx, userCred); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return yerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (man *SIfacePeerManager) removeByIface(ctx context.Context, userCred mcclient.TokenCredential, iface *SIface) error {
|
||||
peers := []SIfacePeer{}
|
||||
q := man.Query().Equals("iface_id", iface.Id)
|
||||
if err := db.FetchModelObjects(IfacePeerManager, q, &peers); err != nil {
|
||||
return err
|
||||
}
|
||||
var errs []error
|
||||
for j := range peers {
|
||||
if err := peers[j].Delete(ctx, userCred); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return yerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (man *SIfacePeerManager) getByFilter(filter map[string]string) ([]SIfacePeer, error) {
|
||||
ifacePeers := []SIfacePeer{}
|
||||
q := man.Query()
|
||||
for key, val := range filter {
|
||||
q = q.Equals(key, val)
|
||||
}
|
||||
if err := db.FetchModelObjects(IfacePeerManager, q, &ifacePeers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ifacePeers, nil
|
||||
}
|
||||
|
||||
func (man *SIfacePeerManager) getOneByFilter(filter map[string]string) (*SIfacePeer, error) {
|
||||
ifacePeers, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ifacePeers) == 0 {
|
||||
return nil, errNotFound(fmt.Errorf("cannot find iface peer: %#v", filter))
|
||||
}
|
||||
if len(ifacePeers) > 1 {
|
||||
return nil, errMoreThanOne(fmt.Errorf("found more than 1 iface peers: %#v", filter))
|
||||
}
|
||||
return &ifacePeers[0], nil
|
||||
}
|
||||
|
||||
func (man *SIfacePeerManager) getByIface(iface *SIface) ([]SIfacePeer, error) {
|
||||
filter := map[string]string{
|
||||
"router_id": iface.RouterId,
|
||||
"iface_id": iface.Id,
|
||||
}
|
||||
ifacePeers, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ifacePeers, nil
|
||||
}
|
||||
|
||||
func (man *SIfacePeerManager) getByIfacePublicKey(iface *SIface, pubkey string) (*SIfacePeer, error) {
|
||||
filter := map[string]string{
|
||||
"router_id": iface.RouterId,
|
||||
"iface_id": iface.Id,
|
||||
"public_key": pubkey,
|
||||
}
|
||||
ifacePeer, err := man.getOneByFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ifacePeer, nil
|
||||
}
|
||||
|
||||
func (man *SIfacePeerManager) checkAllowedIPs(iface *SIface, oldPeer *SIfacePeer, allowedNets Subnets) error {
|
||||
ifacePeers, err := man.getByIface(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range ifacePeers {
|
||||
ifacePeer := &ifacePeers[i]
|
||||
if oldPeer != nil && oldPeer.Id == ifacePeer.Id {
|
||||
continue
|
||||
}
|
||||
existingNets := ifacePeer.subnetsParsed()
|
||||
if _, net := existingNets.ContainsAnyEx(allowedNets); net != nil {
|
||||
return fmt.Errorf("subnet %s is already occupied by peer %s(%s)",
|
||||
net, ifacePeer.Name, ifacePeer.Id)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (man *SIfacePeerManager) updateEndpointIPByPeerRouter(ctx context.Context, endpointIP string, router *SRouter) error {
|
||||
filter := map[string]string{
|
||||
"peer_router_id": router.Id,
|
||||
}
|
||||
ifacePeers, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for i := range ifacePeers {
|
||||
ifacePeer := &ifacePeers[i]
|
||||
host, port, err := net.SplitHostPort(ifacePeer.Endpoint)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
if host == endpointIP {
|
||||
continue
|
||||
}
|
||||
_, err = db.Update(ifacePeer, func() error {
|
||||
endpoint := net.JoinHostPort(endpointIP, port)
|
||||
ifacePeer.Endpoint = endpoint
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return yerrors.NewAggregate(errs)
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
yerrors "yunion.io/x/pkg/util/errors"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
|
||||
cnutils "yunion.io/x/onecloud/pkg/cloudnet/utils"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/pkg/util/netutils"
|
||||
)
|
||||
|
||||
var regexpIfname = regexp.MustCompile(`[A-Za-z][A-Za-z0-9]{0,14}`)
|
||||
|
||||
type SIface struct {
|
||||
db.SStandaloneResourceBase
|
||||
|
||||
RouterId string `length:"32" nullable:"false"`
|
||||
NetworkId string `length:"32" nullable:"false"`
|
||||
|
||||
Ifname string `length:"32" nullable:"false"`
|
||||
|
||||
PrivateKey string
|
||||
PublicKey string
|
||||
ListenPort int `nullable:"false"`
|
||||
|
||||
IsSystem bool `nullable:"false"`
|
||||
}
|
||||
|
||||
type SIfaceManager struct {
|
||||
db.SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var IfaceManager *SIfaceManager
|
||||
|
||||
func init() {
|
||||
IfaceManager = &SIfaceManager{
|
||||
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
|
||||
SIface{},
|
||||
"ifaces_tbl",
|
||||
"iface",
|
||||
"ifaces",
|
||||
),
|
||||
}
|
||||
IfaceManager.SetVirtualObject(IfaceManager)
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
|
||||
// router existence
|
||||
// PrivateKey validation , or generation
|
||||
// ListenPort validation, uniqueness
|
||||
// ListenPort generation
|
||||
return nil, errors.New("manually adding interface is currently not supported")
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
|
||||
q, err := man.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := query.(*jsonutils.JSONDict)
|
||||
q, err = validators.ApplyModelFilters(q, data, []*validators.ModelFilterOptions{
|
||||
{Key: "router", ModelKeyword: "router", OwnerId: userCred},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (iface *SIface) ValidateUpdateCondition(ctx context.Context) error {
|
||||
// same as create but no generation
|
||||
// if privatekey updated
|
||||
// update peers whose peerifaceid == self.id
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iface *SIface) ValidateDeleteCondition(ctx context.Context) error {
|
||||
// if networkid != "" {
|
||||
// return errors.New("part of network, remove it by remove network memeber")
|
||||
// }
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iface *SIface) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
||||
// remove ifacepeer whose peerifaceId is self.id
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iface *SIface) addOrUpdatePeer(ctx context.Context, userCred mcclient.TokenCredential,
|
||||
peerIface *SIface, allowedNets Subnets, peerRouter *SRouter) error {
|
||||
// XXX lock
|
||||
endpoint := ""
|
||||
endpointIP := peerRouter.endpointIP()
|
||||
if endpointIP != "" && peerIface.ListenPort > 0 {
|
||||
endpoint = fmt.Sprintf("%s:%d", endpointIP, peerIface.ListenPort)
|
||||
}
|
||||
persistentKeepalive := 0
|
||||
if endpointIP != "" {
|
||||
// persistent keepalive from private addr to exit addr
|
||||
router, err := iface.getRouter()
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "get iface router %s", iface.RouterId)
|
||||
}
|
||||
myIP := router.endpointIP()
|
||||
if myIP != "" {
|
||||
myIPAddr, err := netutils.NewIPV4Addr(myIP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if netutils.IsPrivate(myIPAddr) {
|
||||
peerIPAddr, err := netutils.NewIPV4Addr(endpointIP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if netutils.IsExitAddress(peerIPAddr) {
|
||||
persistentKeepalive = 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ifacePeer, err := IfacePeerManager.getByIfacePublicKey(iface, peerIface.PublicKey)
|
||||
if err != nil && !IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
if err := IfacePeerManager.checkAllowedIPs(iface, ifacePeer, allowedNets); err != nil {
|
||||
return err
|
||||
}
|
||||
if ifacePeer == nil {
|
||||
ifacePeer := &SIfacePeer{
|
||||
RouterId: iface.RouterId,
|
||||
IfaceId: iface.Id,
|
||||
|
||||
PeerIfaceId: peerIface.Id,
|
||||
PeerRouterId: peerIface.RouterId,
|
||||
PublicKey: peerIface.PublicKey,
|
||||
AllowedIPs: allowedNets.String(),
|
||||
Endpoint: endpoint,
|
||||
PersistentKeepalive: persistentKeepalive,
|
||||
}
|
||||
ifacePeer.Name = fmt.Sprintf("%s-%s", iface.Name, peerIface.Name)
|
||||
err := IfacePeerManager.TableSpec().Insert(ifacePeer)
|
||||
return err
|
||||
}
|
||||
_, err = db.Update(ifacePeer, func() error {
|
||||
ifacePeer.PeerIfaceId = peerIface.Id
|
||||
ifacePeer.PeerRouterId = peerIface.RouterId
|
||||
ifacePeer.Endpoint = endpoint
|
||||
ifacePeer.AllowedIPs = allowedNets.String()
|
||||
ifacePeer.PersistentKeepalive = persistentKeepalive
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (iface *SIface) clearPeers(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
return IfacePeerManager.removeByIface(ctx, userCred, iface)
|
||||
}
|
||||
|
||||
func (iface *SIface) clearPeerRefs(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
return IfacePeerManager.removeByPeerIface(ctx, userCred, iface)
|
||||
}
|
||||
|
||||
func (iface *SIface) clearRoutes(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
return RouteManager.removeByIface(ctx, userCred, iface)
|
||||
}
|
||||
|
||||
func (iface *SIface) remove(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
var errs []error
|
||||
if err := iface.clearRoutes(ctx, userCred); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := iface.clearPeers(ctx, userCred); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := iface.clearPeerRefs(ctx, userCred); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
if err := iface.Delete(ctx, userCred); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return yerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (iface *SIface) isTypeWireguard() bool {
|
||||
r := iface.ListenPort > 0 && iface.PrivateKey != "" && iface.PublicKey != ""
|
||||
return r
|
||||
}
|
||||
|
||||
func (iface *SIface) getRouter() (*SRouter, error) {
|
||||
obj, err := db.FetchById(RouterManager, iface.RouterId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
router := obj.(*SRouter)
|
||||
return router, nil
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) removeByFilter(ctx context.Context, userCred mcclient.TokenCredential, filter map[string]string) error {
|
||||
ifaces, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errs []error
|
||||
for i := range ifaces {
|
||||
iface := &ifaces[i]
|
||||
if err := iface.remove(ctx, userCred); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return yerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) removeByRouter(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
|
||||
err := man.removeByFilter(ctx, userCred, map[string]string{
|
||||
"router_id": router.Id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) removeByMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, mn *SMeshNetwork) error {
|
||||
err := man.removeByFilter(ctx, userCred, map[string]string{
|
||||
"network_id": mn.Id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) removeByMeshNetworkRouter(ctx context.Context, userCred mcclient.TokenCredential, mn *SMeshNetwork, router *SRouter) error {
|
||||
err := man.removeByFilter(ctx, userCred, map[string]string{
|
||||
"network_id": mn.Id,
|
||||
"router_id": router.Id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
func (man *SIfaceManager) getByRouter(router *SRouter) ([]SIface, error) {
|
||||
return man.getByFilter(map[string]string{
|
||||
"router_id": router.Id,
|
||||
})
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) getByRouterIfname(router *SRouter, ifname string) (*SIface, error) {
|
||||
return man.getOneByFilter(map[string]string{
|
||||
"router_id": router.Id,
|
||||
"ifname": ifname,
|
||||
})
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) getByFilter(filter map[string]string) ([]SIface, error) {
|
||||
ifaces := []SIface{}
|
||||
q := man.Query()
|
||||
for key, val := range filter {
|
||||
q = q.Equals(key, val)
|
||||
}
|
||||
if err := db.FetchModelObjects(IfaceManager, q, &ifaces); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ifaces, nil
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) getOneByFilter(filter map[string]string) (*SIface, error) {
|
||||
ifaces, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ifaces) == 0 {
|
||||
return nil, fmt.Errorf("cannot find iface for condition: %#v", filter)
|
||||
}
|
||||
if len(ifaces) > 1 {
|
||||
return nil, fmt.Errorf("found more than 1 ifaces for condition: %#v", filter)
|
||||
}
|
||||
return &ifaces[0], nil
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) checkExistenceByFilter(filter map[string]string) error {
|
||||
ifaces, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ifaces) > 0 {
|
||||
return fmt.Errorf("iface exist: %s(%s)", ifaces[0].Name, ifaces[0].Id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) getByRouterNetwork(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter, mn *SMeshNetwork) ([]SIface, error) {
|
||||
ifaces, err := man.getByFilter(map[string]string{
|
||||
"router_id": router.Id,
|
||||
"network_id": mn.Id,
|
||||
})
|
||||
return ifaces, err
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) getOneByRouterNetwork(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter, mn *SMeshNetwork) (*SIface, error) {
|
||||
iface, err := man.getOneByFilter(map[string]string{
|
||||
"router_id": router.Id,
|
||||
"network_id": mn.Id,
|
||||
})
|
||||
return iface, err
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) getByMeshNetworkMember(member *SMeshNetworkMember) (*SIface, error) {
|
||||
iface, err := man.getOneByFilter(map[string]string{
|
||||
"router_id": member.RouterId,
|
||||
"network_id": member.MeshNetworkId,
|
||||
})
|
||||
return iface, err
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) getNextName(filter map[string]string, base string) (string, error) {
|
||||
ifaces, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
occupied := map[int]struct{}{}
|
||||
for i := range ifaces {
|
||||
iface := &ifaces[i]
|
||||
if strings.HasPrefix(iface.Ifname, base) {
|
||||
istr := iface.Ifname[len(base):]
|
||||
i, err := strconv.ParseUint(istr, 10, 16)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
occupied[int(i)] = struct{}{}
|
||||
}
|
||||
}
|
||||
for i := 0; i < 65536; i++ {
|
||||
if _, ok := occupied[i]; !ok {
|
||||
return fmt.Sprintf("%s%d", base, i), nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("all names occupied")
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) addWireguardIface(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter, mn *SMeshNetwork) (*SIface, error) {
|
||||
k := cnutils.MustNewKey()
|
||||
port := router.mustFindFreePort(ctx)
|
||||
iface := &SIface{
|
||||
RouterId: router.Id,
|
||||
PrivateKey: k.String(),
|
||||
PublicKey: k.PublicKey().String(),
|
||||
ListenPort: port,
|
||||
}
|
||||
iface.IsSystem = true
|
||||
|
||||
{ // ifname
|
||||
if name, err := man.getNextName(map[string]string{
|
||||
"router_id": router.Id,
|
||||
}, "wg"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
iface.Ifname = name
|
||||
}
|
||||
}
|
||||
|
||||
{ // obj name
|
||||
name := router.Name + "-"
|
||||
if mn != nil {
|
||||
iface.NetworkId = mn.Id
|
||||
name += mn.Name + "-"
|
||||
}
|
||||
name += fmt.Sprintf("%d", port)
|
||||
iface.Name = name
|
||||
}
|
||||
|
||||
iface.SetModelManager(man, iface)
|
||||
err := man.TableSpec().Insert(iface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := RuleManager.addWireguardIfaceRules(ctx, userCred, iface); err != nil {
|
||||
iface.Delete(ctx, userCred)
|
||||
return nil, err
|
||||
}
|
||||
return iface, nil
|
||||
}
|
||||
|
||||
func (man *SIfaceManager) addIface(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter, ifname string) (*SIface, error) {
|
||||
if err := man.checkExistenceByFilter(map[string]string{
|
||||
"router_id": router.Id,
|
||||
"ifname": ifname,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iface := &SIface{
|
||||
RouterId: router.Id,
|
||||
Ifname: ifname,
|
||||
}
|
||||
iface.SetModelManager(man, iface)
|
||||
err := man.TableSpec().Insert(iface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iface, nil
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
func InitDB() error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/log"
|
||||
yerrors "yunion.io/x/pkg/util/errors"
|
||||
"yunion.io/x/pkg/util/netutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
type SMeshNetworkMember struct {
|
||||
db.SStandaloneResourceBase
|
||||
|
||||
MeshNetworkId string
|
||||
RouterId string
|
||||
AdvertiseSubnets string
|
||||
}
|
||||
|
||||
type SMeshNetworkMemberManager struct {
|
||||
db.SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var MeshNetworkMemberManager *SMeshNetworkMemberManager
|
||||
|
||||
func init() {
|
||||
MeshNetworkMemberManager = &SMeshNetworkMemberManager{
|
||||
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
|
||||
SMeshNetworkMember{},
|
||||
"meshnetwork_members_tbl",
|
||||
"meshnetwork_member",
|
||||
"meshnetwork_members",
|
||||
),
|
||||
}
|
||||
MeshNetworkMemberManager.SetVirtualObject(MeshNetworkMemberManager)
|
||||
}
|
||||
|
||||
func (member *SMeshNetworkMember) subnetsStrList() []string {
|
||||
return strings.Split(member.AdvertiseSubnets, ",")
|
||||
}
|
||||
|
||||
func (member *SMeshNetworkMember) subnetsParsed() Subnets {
|
||||
parts := member.subnetsStrList()
|
||||
r := make([]*netutils.IPV4Prefix, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
p, err := netutils.NewIPV4Prefix(part)
|
||||
if err != nil {
|
||||
log.Errorf("%s: invalid subnet sneaked in: %s", member.Id, part)
|
||||
return nil
|
||||
}
|
||||
r = append(r, &p)
|
||||
}
|
||||
return Subnets(r)
|
||||
}
|
||||
|
||||
func (member *SMeshNetworkMember) getRouter() (*SRouter, error) {
|
||||
obj, err := db.FetchById(RouterManager, member.RouterId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
router := obj.(*SRouter)
|
||||
return router, nil
|
||||
}
|
||||
|
||||
func (man *SMeshNetworkMemberManager) getByFilter(filter map[string]string) ([]SMeshNetworkMember, error) {
|
||||
members := []SMeshNetworkMember{}
|
||||
q := man.Query()
|
||||
for key, val := range filter {
|
||||
q = q.Equals(key, val)
|
||||
}
|
||||
if err := db.FetchModelObjects(man, q, &members); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return members, nil
|
||||
}
|
||||
|
||||
func (man *SMeshNetworkMemberManager) removeByFilter(ctx context.Context, userCred mcclient.TokenCredential, filter map[string]string) error {
|
||||
members, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errs []error
|
||||
for i := range members {
|
||||
if err := members[i].Delete(ctx, userCred); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return yerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (man *SMeshNetworkMemberManager) removeByRouter(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
|
||||
err := man.removeByFilter(ctx, userCred, map[string]string{
|
||||
"router_id": router.Id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (man *SMeshNetworkMemberManager) removeByMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, mn *SMeshNetwork) error {
|
||||
err := man.removeByFilter(ctx, userCred, map[string]string{
|
||||
"mesh_network_id": mn.Id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (man *SMeshNetworkMemberManager) removeByMeshNetworkRouter(ctx context.Context, userCred mcclient.TokenCredential,
|
||||
mn *SMeshNetwork, router *SRouter) error {
|
||||
err := man.removeByFilter(ctx, userCred, map[string]string{
|
||||
"mesh_network_id": mn.Id,
|
||||
"router_id": router.Id,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (man *SMeshNetworkMemberManager) getMemebersByMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, mn *SMeshNetwork) ([]SMeshNetworkMember, error) {
|
||||
members := []SMeshNetworkMember{}
|
||||
q := man.Query().Equals("mesh_network_id", mn.Id)
|
||||
err := db.FetchModelObjects(man, q, &members)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return members, nil
|
||||
}
|
||||
|
||||
func (man *SMeshNetworkMemberManager) addMember(ctx context.Context, userCred mcclient.TokenCredential, mn *SMeshNetwork, router *SRouter, nets Subnets) (*SMeshNetworkMember, error) {
|
||||
member := &SMeshNetworkMember{
|
||||
MeshNetworkId: mn.Id,
|
||||
RouterId: router.Id,
|
||||
AdvertiseSubnets: nets.String(),
|
||||
}
|
||||
member.SetModelManager(man, member)
|
||||
member.Name = fmt.Sprintf("%s-%s", mn.Name, router.Name)
|
||||
man.TableSpec().Insert(member)
|
||||
return member, nil
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
yerrors "yunion.io/x/pkg/util/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
type SMeshNetwork struct {
|
||||
db.SStandaloneResourceBase
|
||||
}
|
||||
|
||||
type SMeshNetworkManager struct {
|
||||
db.SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var MeshNetworkManager *SMeshNetworkManager
|
||||
|
||||
func init() {
|
||||
MeshNetworkManager = &SMeshNetworkManager{
|
||||
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
|
||||
SMeshNetwork{},
|
||||
"meshnetworks_tbl",
|
||||
"meshnetwork",
|
||||
"meshnetworks",
|
||||
),
|
||||
}
|
||||
MeshNetworkManager.SetVirtualObject(MeshNetworkManager)
|
||||
}
|
||||
|
||||
func (mn *SMeshNetwork) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
||||
var errs []error
|
||||
if err := MeshNetworkMemberManager.removeByMeshNetwork(ctx, userCred, mn); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := IfaceManager.removeByMeshNetwork(ctx, userCred, mn); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := mn.SStandaloneResourceBase.CustomizeDelete(ctx, userCred, query, data); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return yerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (mn *SMeshNetwork) AllowPerformRealize(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowPerform(userCred, mn, "realize")
|
||||
}
|
||||
|
||||
func (mn *SMeshNetwork) PerformRealize(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
members, err := MeshNetworkMemberManager.getMemebersByMeshNetwork(ctx, userCred, mn)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewBadRequestError("fetch members: %v", err)
|
||||
}
|
||||
var errs []error
|
||||
for i := range members {
|
||||
member := &members[i]
|
||||
router, err := member.getRouter()
|
||||
if err != nil {
|
||||
errs = append(errs, errors.WithMessagef(err, "get router %s", member.RouterId))
|
||||
continue
|
||||
}
|
||||
if err := router.realize(ctx, userCred); err != nil {
|
||||
errs = append(errs, errors.WithMessagef(err, "realize router %s", router.Name))
|
||||
}
|
||||
}
|
||||
err = yerrors.NewAggregate(errs)
|
||||
if err != nil {
|
||||
err = httperrors.NewBadRequestError("some router realization failed: %s", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (mn *SMeshNetwork) addRouter(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter, nets Subnets) error {
|
||||
// XXX lock
|
||||
members, err := MeshNetworkMemberManager.getMemebersByMeshNetwork(ctx, userCred, mn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range members {
|
||||
member := &members[i]
|
||||
if member.RouterId == router.Id {
|
||||
return fmt.Errorf("router %s is already a member of %s",
|
||||
router.Name, mn.Name)
|
||||
}
|
||||
memberSubnets := member.subnetsParsed()
|
||||
if _, p := memberSubnets.ContainsAnyEx(nets); p != nil {
|
||||
return fmt.Errorf("router %s subnet %s already advertised by member %s(%s)",
|
||||
router.Name, p.String(), member.Name, member.Id)
|
||||
}
|
||||
}
|
||||
_, err = MeshNetworkMemberManager.addMember(ctx, userCred, mn, router, nets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newIface, err := IfaceManager.addWireguardIface(ctx, userCred, router, mn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// XXX allocate an iface and populate iface peers
|
||||
var errs []error
|
||||
for i := range members {
|
||||
member := &members[i]
|
||||
memberIface, err := IfaceManager.getByMeshNetworkMember(member)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
if err := memberIface.addOrUpdatePeer(ctx, userCred, newIface, nets, router); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if memberHost, err := RouterManager.getById(member.RouterId); err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
if err := newIface.addOrUpdatePeer(ctx, userCred, memberIface, member.subnetsParsed(), memberHost); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return yerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (mn *SMeshNetwork) removeRouter(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
|
||||
if err := MeshNetworkMemberManager.removeByMeshNetworkRouter(ctx, userCred, mn, router); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := IfaceManager.removeByMeshNetworkRouter(ctx, userCred, mn, router); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (man *SMeshNetworkManager) removeRouter(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
|
||||
err := MeshNetworkMemberManager.removeByRouter(ctx, userCred, router)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/gotypes"
|
||||
yerrors "yunion.io/x/pkg/util/errors"
|
||||
"yunion.io/x/pkg/util/netutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
// Add revision?
|
||||
type SRouter struct {
|
||||
db.SStandaloneResourceBase
|
||||
|
||||
User string `nullable:"false" list:"user" update:"user" create:"optional"`
|
||||
Host string `nullable:"false" list:"user" update:"user" create:"required"`
|
||||
Port int `nullable:"false" list:"user" update:"user" create:"optional"`
|
||||
PrivateKey string `nullable:"true" update:"user" create:"optional"` // do not allow get, list
|
||||
|
||||
RealizeWgIfaces bool `width:"16" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"`
|
||||
RealizeRoutes bool `width:"16" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"`
|
||||
RealizeRules bool `width:"16" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"`
|
||||
}
|
||||
|
||||
type SRouterManager struct {
|
||||
db.SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var RouterManager *SRouterManager
|
||||
|
||||
func init() {
|
||||
RouterManager = &SRouterManager{
|
||||
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
|
||||
SRouter{},
|
||||
"routers_tbl",
|
||||
"router",
|
||||
"routers",
|
||||
),
|
||||
}
|
||||
RouterManager.SetVirtualObject(RouterManager)
|
||||
}
|
||||
|
||||
func (man *SRouterManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
|
||||
if _, err := man.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vs := []validators.IValidator{
|
||||
validators.NewStringNonEmptyValidator("user").Default("cloudroot"),
|
||||
validators.NewStringNonEmptyValidator("host"),
|
||||
validators.NewPortValidator("port").Default(22),
|
||||
validators.NewSSHKeyValidator("private_key").Optional(true),
|
||||
validators.NewBoolValidator("realize_wg_ifaces").Default(true),
|
||||
validators.NewBoolValidator("realize_routes").Default(true),
|
||||
validators.NewBoolValidator("realize_rules").Default(true),
|
||||
}
|
||||
for _, v := range vs {
|
||||
if err := v.Validate(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// populate ssh credential through "cloudhost"
|
||||
//
|
||||
// if ! skip validation {
|
||||
// ssh credential validation
|
||||
// }
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (router *SRouter) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
err := RuleManager.addRouterRules(ctx, userCred, router)
|
||||
if err != nil {
|
||||
log.Errorf("add router rule: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (man *SRouterManager) getById(id string) (*SRouter, error) {
|
||||
m, err := db.FetchById(man, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
router := m.(*SRouter)
|
||||
return router, err
|
||||
}
|
||||
|
||||
func (router *SRouter) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
|
||||
if _, err := router.SStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vs := []validators.IValidator{
|
||||
validators.NewStringNonEmptyValidator("user"),
|
||||
validators.NewStringNonEmptyValidator("host"),
|
||||
validators.NewPortValidator("port"),
|
||||
validators.NewSSHKeyValidator("private_key").Optional(true),
|
||||
validators.NewBoolValidator("realize_wg_ifaces"),
|
||||
validators.NewBoolValidator("realize_routes"),
|
||||
validators.NewBoolValidator("realize_rules"),
|
||||
}
|
||||
for _, v := range vs {
|
||||
v.Optional(true)
|
||||
if err := v.Validate(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
data.Set("_old_endpoint", jsonutils.NewString(router.endpointIP()))
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (router *SRouter) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
endpointOld, _ := data.GetString("_old_endpoint")
|
||||
endpoint := router.endpointIP()
|
||||
if endpoint != endpointOld {
|
||||
err := IfacePeerManager.updateEndpointIPByPeerRouter(ctx, endpoint, router)
|
||||
if err != nil {
|
||||
log.Errorf("updating peer endpoint failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (router *SRouter) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
||||
var errs []error
|
||||
if err := MeshNetworkManager.removeRouter(ctx, userCred, router); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := IfaceManager.removeByRouter(ctx, userCred, router); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := RuleManager.removeByRouter(ctx, userCred, router); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if err := router.SStandaloneResourceBase.CustomizeDelete(ctx, userCred, query, data); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return yerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (router *SRouter) AllowPerformJoinMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowPerform(userCred, router, "join-mesh-network")
|
||||
}
|
||||
|
||||
func (router *SRouter) PerformJoinMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
mnV := validators.NewModelIdOrNameValidator("mesh_network", "meshnetwork", userCred)
|
||||
advSubnetsV := validators.NewValidatorByActor("advertise_subnets",
|
||||
validators.NewActorJoinedBy(",", validators.NewActorIPv4Prefix()))
|
||||
{
|
||||
vs := []validators.IValidator{
|
||||
mnV,
|
||||
advSubnetsV,
|
||||
}
|
||||
jd, ok := data.(*jsonutils.JSONDict)
|
||||
if !ok {
|
||||
return nil, httperrors.NewBadRequestError("expecting json dict")
|
||||
}
|
||||
for _, v := range vs {
|
||||
if err := v.Validate(jd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO dedup
|
||||
nets := gotypes.ConvertSliceElemType(advSubnetsV.Value, (**netutils.IPV4Prefix)(nil)).([]*netutils.IPV4Prefix)
|
||||
if len(nets) == 0 {
|
||||
return nil, httperrors.NewBadRequestError("advertise_subnets must not be empty")
|
||||
}
|
||||
mn := mnV.Model.(*SMeshNetwork)
|
||||
if err := mn.addRouter(ctx, userCred, router, nets); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (router *SRouter) AllowPerformLeaveMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowPerform(userCred, router, "leave-mesh-network")
|
||||
}
|
||||
|
||||
func (router *SRouter) PerformLeaveMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
jd, ok := data.(*jsonutils.JSONDict)
|
||||
if !ok {
|
||||
return nil, httperrors.NewBadRequestError("expecting json dict")
|
||||
}
|
||||
mnV := validators.NewModelIdOrNameValidator("mesh_network", "meshnetwork", userCred)
|
||||
if err := mnV.Validate(jd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mn := mnV.Model.(*SMeshNetwork)
|
||||
if err := mn.removeRouter(ctx, userCred, router); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (router *SRouter) AllowPerformRegisterIfname(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowPerform(userCred, router, "register-ifname")
|
||||
}
|
||||
|
||||
func (router *SRouter) PerformRegisterIfname(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
jd, ok := data.(*jsonutils.JSONDict)
|
||||
if !ok {
|
||||
return nil, httperrors.NewBadRequestError("expecting json dict")
|
||||
}
|
||||
ifnameV := validators.NewRegexpValidator("ifname", regexpIfname)
|
||||
if err := ifnameV.Validate(jd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err := IfaceManager.addIface(ctx, userCred, router, ifnameV.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (router *SRouter) AllowPerformUnregisterIfname(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowPerform(userCred, router, "unregister-ifname")
|
||||
}
|
||||
|
||||
func (router *SRouter) PerformUnregisterIfname(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
jd, ok := data.(*jsonutils.JSONDict)
|
||||
if !ok {
|
||||
return nil, httperrors.NewBadRequestError("expecting json dict")
|
||||
}
|
||||
ifname, err := jd.GetString("ifname")
|
||||
if err != nil {
|
||||
return nil, httperrors.NewBadRequestError("get request ifname field: %v", err)
|
||||
}
|
||||
iface, err := IfaceManager.getByRouterIfname(router, ifname)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewBadRequestError("get iface: %s", err)
|
||||
}
|
||||
if iface.NetworkId != "" {
|
||||
// XXX can_unregister
|
||||
return nil, httperrors.NewBadRequestError("please use leave network to unregister")
|
||||
}
|
||||
if err := iface.remove(ctx, userCred); err != nil {
|
||||
return nil, httperrors.NewBadRequestError("remove iface: %v", err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (router *SRouter) mustFindFreePort(ctx context.Context) int {
|
||||
// loop through ifaces listen port
|
||||
ifaces, err := IfaceManager.getByRouter(router)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for sport := 20000; sport < 65536; sport++ {
|
||||
notfound := true
|
||||
for i := range ifaces {
|
||||
if ifaces[i].ListenPort == sport {
|
||||
notfound = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if notfound {
|
||||
return sport
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("cannot find free port for host %s(%s)", router.Name, router.Id))
|
||||
}
|
||||
|
||||
func (router *SRouter) endpointIP() string {
|
||||
return router.Host
|
||||
}
|
||||
|
||||
func (router *SRouter) AllowPerformDeploy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowPerform(userCred, router, "deploy")
|
||||
}
|
||||
|
||||
func (router *SRouter) PerformDeploy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
if err := RouterDeploymentManager.requestDeployment(ctx, userCred, router); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/util/ansiblev2"
|
||||
)
|
||||
|
||||
func (router *SRouter) ansibleHost() (*ansiblev2.Host, error) {
|
||||
vars := map[string]interface{}{
|
||||
"ansible_user": router.User,
|
||||
"ansible_host": router.Host,
|
||||
}
|
||||
if router.User != "root" {
|
||||
vars["ansible_become"] = "yes"
|
||||
}
|
||||
if router.PrivateKey != "" {
|
||||
vars["ansible_ssh_private_key_file"] = ".id_rsa"
|
||||
}
|
||||
if router.RealizeWgIfaces {
|
||||
if err := router.inventoryWireguardVars(vars); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
h := ansiblev2.NewHost()
|
||||
h.Vars = vars
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (router *SRouter) playFiles() map[string]string {
|
||||
r := map[string]string{
|
||||
"wgX.conf.j2": wgX_conf_j2,
|
||||
}
|
||||
if router.PrivateKey != "" {
|
||||
r[".id_rsa"] = router.PrivateKey
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (router *SRouter) playFilesStr() string {
|
||||
files := router.playFiles()
|
||||
r, _ := json.Marshal(files)
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func (router *SRouter) inventoryWireguardVars(vars map[string]interface{}) error {
|
||||
type (
|
||||
WgNetworks []string
|
||||
WgInterface map[string]interface{}
|
||||
WgPeer map[string]interface{}
|
||||
WgPeers map[string]WgPeer
|
||||
)
|
||||
ifaces, err := IfaceManager.getByRouter(router)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wgnetworks := WgNetworks{}
|
||||
for i := range ifaces {
|
||||
iface := &ifaces[i]
|
||||
if iface.PrivateKey == "" {
|
||||
continue
|
||||
}
|
||||
ifacePeers, err := IfacePeerManager.getByIface(iface)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wgpeers := WgPeers{}
|
||||
for j := range ifacePeers {
|
||||
ifacePeer := &ifacePeers[j]
|
||||
if ifacePeer.PublicKey == "" {
|
||||
continue
|
||||
}
|
||||
wgpeer := WgPeer{
|
||||
"public_key": ifacePeer.PublicKey,
|
||||
"allowed_ips": ifacePeer.AllowedIPs,
|
||||
"endpoint": ifacePeer.Endpoint,
|
||||
}
|
||||
if ifacePeer.PersistentKeepalive > 0 {
|
||||
wgpeer["persistent_keepalive"] = ifacePeer.PersistentKeepalive
|
||||
}
|
||||
wgpeers[ifacePeer.Name] = wgpeer
|
||||
}
|
||||
if len(wgpeers) == 0 {
|
||||
continue
|
||||
}
|
||||
vars["wireguard_"+iface.Ifname+"_interface"] = WgInterface{
|
||||
"private_key": iface.PrivateKey,
|
||||
"listen_port": iface.ListenPort,
|
||||
}
|
||||
vars["wireguard_"+iface.Ifname+"_peers"] = wgpeers
|
||||
wgnetworks = append(wgnetworks, iface.Ifname)
|
||||
}
|
||||
vars["wireguard_networks"] = wgnetworks
|
||||
return nil
|
||||
}
|
||||
|
||||
func (router *SRouter) playInstallWireguard() *ansiblev2.Play {
|
||||
play := ansiblev2.NewPlay(
|
||||
&ansiblev2.Task{
|
||||
Name: "Enable EPEL",
|
||||
ModuleName: "package",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"name": "epel-release",
|
||||
"state": "present",
|
||||
},
|
||||
},
|
||||
&ansiblev2.Task{
|
||||
Name: "Check existence of wireguard repo file",
|
||||
ModuleName: "stat",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"path": "/etc/yum.repos.d/_copr_jdoss-wireguard.repo",
|
||||
},
|
||||
Register: "wireguard_repo",
|
||||
},
|
||||
&ansiblev2.Task{
|
||||
Name: "Enable wireguard repo from copr",
|
||||
ModuleName: "get_url",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"dest": "/etc/yum.repos.d/_copr_jdoss-wireguard.repo",
|
||||
"url": "https://copr.fedorainfracloud.org/coprs/jdoss/wireguard/repo/epel-7/jdoss-wireguard-epel-7.repo",
|
||||
},
|
||||
When: "(not wireguard_repo.stat.exists) or (wireguard_repo.stat.size < 10)",
|
||||
},
|
||||
&ansiblev2.Task{
|
||||
Name: "Install wireguard packages",
|
||||
ModuleName: "package",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"name": "{{ item }}",
|
||||
"state": "present",
|
||||
},
|
||||
WithPlugin: "items",
|
||||
WithPluginVal: []string{"wireguard-dkms", "wireguard-tools"},
|
||||
},
|
||||
&ansiblev2.Task{
|
||||
Name: "Create /etc/wireguard",
|
||||
ModuleName: "file",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"path": "/etc/wireguard",
|
||||
"state": "directory",
|
||||
"owner": "root",
|
||||
"group": "root",
|
||||
},
|
||||
},
|
||||
)
|
||||
play.Hosts = "all"
|
||||
play.Name = "Install WireGuard"
|
||||
return play
|
||||
}
|
||||
|
||||
func (router *SRouter) playDeployWireguardNetworks() *ansiblev2.Play {
|
||||
play := ansiblev2.NewPlay(
|
||||
&ansiblev2.ShellTask{
|
||||
Name: "List existing managed wireguard networks",
|
||||
Script: `grep -rnl 'Ansible managed' /etc/wireguard/ | grep '\.conf$' | cut -d/ -f4 | cut -d. -f1`,
|
||||
Register: "oldconfs",
|
||||
IgnoreErrors: true,
|
||||
},
|
||||
&ansiblev2.Task{
|
||||
Name: "Backup stale wireguard confs",
|
||||
ModuleName: "copy",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"src": "/etc/wireguard/{{ item }}.conf",
|
||||
"dest": "/etc/wireguard/{{ item }}.conf.stale",
|
||||
"remote_src": "yes",
|
||||
},
|
||||
WithPlugin: "items",
|
||||
WithPluginVal: "{{ oldconfs.stdout_lines }}",
|
||||
When: "(not oldconfs.failed) and (item not in wireguard_networks)",
|
||||
},
|
||||
&ansiblev2.Task{
|
||||
Name: "Remove stale wireguard confs",
|
||||
ModuleName: "file",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"path": "/etc/wireguard/{{ item }}.conf",
|
||||
"state": "absent",
|
||||
},
|
||||
WithPlugin: "items",
|
||||
WithPluginVal: "{{ oldconfs.stdout_lines }}",
|
||||
When: "(not oldconfs.failed) and (item not in wireguard_networks)",
|
||||
},
|
||||
&ansiblev2.Task{
|
||||
Name: "Disable stale wireguard networks",
|
||||
ModuleName: "service",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"name": "wg-quick@{{ item }}",
|
||||
"state": "stopped",
|
||||
"enabled": "no",
|
||||
},
|
||||
WithPlugin: "items",
|
||||
WithPluginVal: "{{ oldconfs.stdout_lines }}",
|
||||
When: "(not oldconfs.failed) and (item not in wireguard_networks)",
|
||||
},
|
||||
&ansiblev2.Task{
|
||||
Name: "Configure wireguard conf",
|
||||
ModuleName: "template",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"src": "wgX.conf.j2", // wgX_conf_j2
|
||||
"dest": "/etc/wireguard/{{ item }}.conf",
|
||||
"mode": 0600,
|
||||
},
|
||||
WithPlugin: "items",
|
||||
WithPluginVal: "{{ wireguard_networks }}",
|
||||
Register: "configuration",
|
||||
},
|
||||
&ansiblev2.Task{
|
||||
Name: "Enable wg-quick@xx service",
|
||||
ModuleName: "service",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"name": "wg-quick@{{ item }}",
|
||||
"enabled": "yes",
|
||||
},
|
||||
WithPlugin: "items",
|
||||
WithPluginVal: "{{ wireguard_networks }}",
|
||||
},
|
||||
&ansiblev2.Task{
|
||||
Name: "Restart wg-quick@xx service",
|
||||
ModuleName: "service",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"name": "wg-quick@{{ item.1 }}",
|
||||
"state": "restarted",
|
||||
},
|
||||
WithPlugin: "indexed_items",
|
||||
WithPluginVal: "{{ wireguard_networks }}",
|
||||
When: "configuration.results[item.0].changed",
|
||||
},
|
||||
)
|
||||
play.Hosts = "all"
|
||||
play.Name = "Configure wireguard networks"
|
||||
return play
|
||||
}
|
||||
|
||||
func (router *SRouter) playDeployRoutes() (*ansiblev2.Play, error) {
|
||||
r, err := RouteManager.routeLinesRouter(router)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tasks := []ansiblev2.ITask{}
|
||||
i := 0
|
||||
for ifname, lines := range r {
|
||||
if len(lines) == 0 {
|
||||
continue
|
||||
}
|
||||
iface, err := IfaceManager.getByRouterIfname(router, ifname)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "get iface %s", ifname)
|
||||
}
|
||||
filename := "route-" + ifname
|
||||
content := strings.Join(lines, "\n") + "\n"
|
||||
registerVar := fmt.Sprintf("var%d", i)
|
||||
i += 1
|
||||
tasks = append(tasks, &ansiblev2.Task{
|
||||
Name: "Put routes for " + ifname,
|
||||
ModuleName: "copy",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"content": content,
|
||||
"dest": "/etc/sysconfig/network-scripts/" + filename,
|
||||
"owner": "root",
|
||||
"group": "root",
|
||||
"mode": "0644",
|
||||
},
|
||||
Register: registerVar,
|
||||
})
|
||||
if !iface.isTypeWireguard() {
|
||||
tasks = append(tasks, &ansiblev2.ShellTask{
|
||||
Name: fmt.Sprintf("Apply routes (Ifup/ifdown %s)", ifname),
|
||||
Script: fmt.Sprintf("ifdown %s; ifup %s", ifname, ifname),
|
||||
IgnoreErrors: true,
|
||||
When: fmt.Sprintf("%s.changed", registerVar),
|
||||
})
|
||||
}
|
||||
// apply by diff on changed
|
||||
}
|
||||
play := ansiblev2.NewPlay(tasks...)
|
||||
play.Hosts = "all"
|
||||
play.Name = "Configure routes"
|
||||
return play, nil
|
||||
}
|
||||
|
||||
func (router *SRouter) playDeployRules() (*ansiblev2.Play, error) {
|
||||
d, err := RuleManager.firewalldDirectByRouter(router)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
directXML, err := xml.MarshalIndent(d, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
play := ansiblev2.NewPlay(
|
||||
&ansiblev2.Task{
|
||||
Name: "Install firewalld",
|
||||
ModuleName: "package",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"name": "firewalld",
|
||||
"state": "present",
|
||||
},
|
||||
},
|
||||
&ansiblev2.Task{
|
||||
Name: "Enable firewalld",
|
||||
ModuleName: "service",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"name": "firewalld",
|
||||
"enabled": "yes",
|
||||
},
|
||||
},
|
||||
&ansiblev2.Task{
|
||||
Name: "Put firewalld direct.xml",
|
||||
ModuleName: "copy",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"content": string(directXML),
|
||||
"dest": "/etc/firewalld/direct.xml",
|
||||
"owner": "root",
|
||||
"group": "root",
|
||||
"mode": "0644",
|
||||
},
|
||||
Register: "direct_xml",
|
||||
},
|
||||
&ansiblev2.Task{
|
||||
Name: "Restart firewalld",
|
||||
ModuleName: "service",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"name": "firewalld",
|
||||
"state": "restarted",
|
||||
},
|
||||
When: "direct_xml.changed",
|
||||
},
|
||||
)
|
||||
play.Hosts = "all"
|
||||
play.Name = "Configure firewall rules"
|
||||
return play, nil
|
||||
}
|
||||
|
||||
func (router *SRouter) playEssential() *ansiblev2.Play {
|
||||
play := ansiblev2.NewPlay(
|
||||
&ansiblev2.Task{
|
||||
Name: "Enable ip_forward",
|
||||
ModuleName: "sysctl",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"name": "net.ipv4.ip_forward",
|
||||
"value": "1",
|
||||
"state": "present",
|
||||
"reload": "yes",
|
||||
},
|
||||
},
|
||||
)
|
||||
play.Hosts = "all"
|
||||
play.Name = "Perform essential steps"
|
||||
return play
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
const wgX_conf_j2 = `
|
||||
{% set interface = lookup('vars', 'wireguard_' + item + '_interface') -%}
|
||||
{% set peers = lookup('vars', 'wireguard_' + item + '_peers') -%}
|
||||
|
||||
{% set interface_required_keys = { 'private_key': 'PrivateKey' } -%}
|
||||
{% set interface_optional_keys = {
|
||||
'address': 'Address',
|
||||
'listen_port': 'ListenPort',
|
||||
'fw_mark': 'FwMark',
|
||||
'dns': 'DNS',
|
||||
'mtu': 'MTU',
|
||||
'table': 'Table',
|
||||
'pre_up': 'PreUp',
|
||||
'post_up': 'PostUp',
|
||||
'pre_down': 'PreDown',
|
||||
'post_down': 'PostDown',
|
||||
'save_config': 'SaveConfig'
|
||||
} -%}
|
||||
{% set peer_required_keys = {
|
||||
'public_key': 'PublicKey',
|
||||
'allowed_ips': 'AllowedIPs'
|
||||
} -%}
|
||||
{% set peer_optional_keys = {
|
||||
'endpoint': 'EndPoint',
|
||||
'preshared_key': 'PresharedKey',
|
||||
'persistent_keepalive': 'PersistentKeepalive'
|
||||
} -%}
|
||||
{{ ansible_managed | comment }}
|
||||
|
||||
[Interface]
|
||||
{% for key, option in interface_required_keys.items() %}
|
||||
{{ option }} = {{ interface[key] }}
|
||||
{% endfor %}
|
||||
{% for key, option in interface_optional_keys.items() %}
|
||||
{% if interface[key] is defined %}
|
||||
{{ option }} = {{ interface[key] }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% for peer_name, peer in peers.items() %}
|
||||
[Peer] # {{ peer_name }}
|
||||
{% for key, option in peer_required_keys.items() %}
|
||||
{{ option }} = {{ peer[key] }}
|
||||
{% endfor %}
|
||||
{% for key, option in peer_optional_keys.items() %}
|
||||
{% if peer[key] is defined %}
|
||||
{{ option }} = {{ peer[key] }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
`
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
type SRouterDeployment struct {
|
||||
db.SStandaloneResourceBase
|
||||
|
||||
RouterId string
|
||||
AnsiblePlaybookId string
|
||||
|
||||
RouterRevision int
|
||||
}
|
||||
|
||||
type SRouterDeploymentManager struct {
|
||||
db.SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var RouterDeploymentManager *SRouterDeploymentManager
|
||||
|
||||
func init() {
|
||||
RouterDeploymentManager = &SRouterDeploymentManager{
|
||||
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
|
||||
SRouterDeployment{},
|
||||
"router_deployments_tbl",
|
||||
"router_deployment",
|
||||
"router_deployments",
|
||||
),
|
||||
}
|
||||
RouterDeploymentManager.SetVirtualObject(RouterDeploymentManager)
|
||||
}
|
||||
|
||||
func (man *SRouterDeploymentManager) requestDeployment(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
|
||||
// XXX
|
||||
// make inventory
|
||||
// make playbook
|
||||
// queue a deploy ansible task
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
mcclient_modules "yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
"yunion.io/x/onecloud/pkg/util/ansiblev2"
|
||||
"yunion.io/x/onecloud/pkg/util/rand"
|
||||
)
|
||||
|
||||
func (router *SRouter) realize(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||||
plays := []*ansiblev2.Play{
|
||||
router.playEssential(),
|
||||
}
|
||||
|
||||
host, err := router.ansibleHost()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if router.RealizeWgIfaces {
|
||||
plays = append(plays,
|
||||
router.playInstallWireguard(),
|
||||
router.playDeployWireguardNetworks(),
|
||||
)
|
||||
}
|
||||
|
||||
if router.RealizeRoutes {
|
||||
playRoutes, err := router.playDeployRoutes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plays = append(plays, playRoutes)
|
||||
}
|
||||
if router.RealizeRules {
|
||||
playRules, err := router.playDeployRules()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plays = append(plays, playRules)
|
||||
}
|
||||
|
||||
inv := ansiblev2.NewInventory()
|
||||
inv.SetHost(router.Name, host)
|
||||
pb := ansiblev2.NewPlaybook(plays...)
|
||||
files := router.playFilesStr()
|
||||
|
||||
params := jsonutils.NewDict()
|
||||
params.Set("creator_mark", jsonutils.NewString("router:"+router.Id))
|
||||
params.Set("name", jsonutils.NewString(router.Name+"-"+fmt.Sprintf("%d-", router.UpdateVersion)+rand.String(5)))
|
||||
params.Set("inventory", jsonutils.NewString(inv.String()))
|
||||
params.Set("playbook", jsonutils.NewString(pb.String()))
|
||||
params.Set("files", jsonutils.NewString(files))
|
||||
cliSess := auth.GetSession(ctx, userCred, "", "")
|
||||
if _, err := mcclient_modules.AnsiblePlaybooksV2.Create(cliSess, params); err != nil {
|
||||
return errors.WithMessagef(err, "create ansible task")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (router *SRouter) AllowPerformRealize(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||||
return db.IsAdminAllowPerform(userCred, router, "realize")
|
||||
}
|
||||
|
||||
func (router *SRouter) PerformRealize(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
err := router.realize(ctx, userCred)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewBadRequestError("%s", err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
yerrors "yunion.io/x/pkg/util/errors"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/util/rand"
|
||||
)
|
||||
|
||||
type SRoute struct {
|
||||
db.SStandaloneResourceBase
|
||||
|
||||
IfaceId string `length:"32" nullable:"false" list:"user" create:"required"`
|
||||
Ifname string `length:"32" nullable:"false" list:"user" create:"optional"`
|
||||
|
||||
Network string `length:"32" nullable:"false" list:"user" update:"user" create:"required"`
|
||||
Gateway string `length:"32" nullable:"false" list:"user" update:"user" create:"optional"`
|
||||
|
||||
RouterId string `length:"32" nullable:"false" list:"user" create:"optional"`
|
||||
}
|
||||
|
||||
type SRouteManager struct {
|
||||
db.SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var RouteManager *SRouteManager
|
||||
|
||||
func init() {
|
||||
RouteManager = &SRouteManager{
|
||||
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
|
||||
SRoute{},
|
||||
"routes_tbl",
|
||||
"route",
|
||||
"routes",
|
||||
),
|
||||
}
|
||||
RouteManager.SetVirtualObject(RouteManager)
|
||||
}
|
||||
|
||||
func (man *SRouteManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
|
||||
if _, err := man.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ifaceV := validators.NewModelIdOrNameValidator("iface", "iface", ownerId)
|
||||
networkV := validators.NewIPv4PrefixValidator("network")
|
||||
gatewayV := validators.NewIPv4AddrValidator("gateway")
|
||||
vs := []validators.IValidator{
|
||||
networkV,
|
||||
gatewayV.Optional(true),
|
||||
ifaceV,
|
||||
}
|
||||
for _, v := range vs {
|
||||
if err := v.Validate(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
iface := ifaceV.Model.(*SIface)
|
||||
network := networkV.Value.String()
|
||||
routerId := iface.RouterId
|
||||
{
|
||||
if routes, err := man.getByFilter(map[string]string{
|
||||
"router_id": routerId,
|
||||
"network": network,
|
||||
}); err != nil {
|
||||
return nil, httperrors.NewConflictError("query existing route to network %s: %v", network, err)
|
||||
} else if len(routes) > 0 {
|
||||
return nil, httperrors.NewConflictError("route to %s already exist: %s(%s)", network, routes[0].Name, routes[0].Id)
|
||||
}
|
||||
}
|
||||
|
||||
data.Set("router_id", jsonutils.NewString(routerId))
|
||||
data.Set("ifname", jsonutils.NewString(iface.Ifname))
|
||||
routerV := validators.NewModelIdOrNameValidator("router", "router", ownerId)
|
||||
if err := routerV.Validate(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !data.Contains("name") {
|
||||
router := routerV.Model.(*SRouter)
|
||||
data.Set("name", jsonutils.NewString(
|
||||
router.Name+"-"+iface.Name+"-"+rand.String(4)),
|
||||
)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (man *SRouteManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
|
||||
q, err := man.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := query.(*jsonutils.JSONDict)
|
||||
q, err = validators.ApplyModelFilters(q, data, []*validators.ModelFilterOptions{
|
||||
{Key: "router", ModelKeyword: "router", OwnerId: userCred},
|
||||
{Key: "iface", ModelKeyword: "iface", OwnerId: userCred},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (route *SRoute) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
|
||||
if _, err := route.SStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vs := []validators.IValidator{
|
||||
validators.NewIPv4PrefixValidator("network"),
|
||||
validators.NewIPv4AddrValidator("gateway"),
|
||||
}
|
||||
for _, v := range vs {
|
||||
v.Optional(true)
|
||||
if err := v.Validate(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (man *SRouteManager) removeByIface(ctx context.Context, userCred mcclient.TokenCredential, iface *SIface) error {
|
||||
routes := []SRoute{}
|
||||
q := man.Query().Equals("iface_id", iface.Id)
|
||||
if err := db.FetchModelObjects(RouteManager, q, &routes); err != nil {
|
||||
return err
|
||||
}
|
||||
var errs []error
|
||||
for j := range routes {
|
||||
if err := routes[j].Delete(ctx, userCred); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return yerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (man *SRouteManager) getByFilter(filter map[string]string) ([]SRoute, error) {
|
||||
routes := []SRoute{}
|
||||
q := man.Query()
|
||||
for key, val := range filter {
|
||||
q = q.Equals(key, val)
|
||||
}
|
||||
if err := db.FetchModelObjects(RouteManager, q, &routes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
func (man *SRouteManager) getOneByFilter(filter map[string]string) (*SRoute, error) {
|
||||
routes, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(routes) == 0 {
|
||||
return nil, errNotFound(fmt.Errorf("cannot find iface route: %#v", filter))
|
||||
}
|
||||
if len(routes) > 1 {
|
||||
return nil, errMoreThanOne(fmt.Errorf("found more than 1 iface routes: %#v", filter))
|
||||
}
|
||||
return &routes[0], nil
|
||||
}
|
||||
|
||||
func (man *SRouteManager) checkExistenceByFilter(filter map[string]string) error {
|
||||
ifaces, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ifaces) > 0 {
|
||||
return fmt.Errorf("iface exist: %s(%s)", ifaces[0].Name, ifaces[0].Id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (man *SRouteManager) getByIface(iface *SIface) ([]SRoute, error) {
|
||||
filter := map[string]string{
|
||||
"router_id": iface.RouterId,
|
||||
"iface_id": iface.Id,
|
||||
}
|
||||
routes, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
func (man *SRouteManager) getByRouter(router *SRouter) ([]SRoute, error) {
|
||||
filter := map[string]string{
|
||||
"router_id": router.Id,
|
||||
}
|
||||
routes, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
func (route *SRoute) routeLine() string {
|
||||
line := route.Network
|
||||
if route.Gateway != "" {
|
||||
line += " via " + route.Gateway
|
||||
}
|
||||
if route.Ifname != "" {
|
||||
line += " dev " + route.Ifname
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
func (man *SRouteManager) routeLinesByIface(iface *SIface) ([]string, error) {
|
||||
routes, err := man.getByIface(iface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := []string{}
|
||||
for i := range routes {
|
||||
route := &routes[i]
|
||||
line := route.routeLine()
|
||||
if line != "" {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func (man *SRouteManager) routeLinesRouter(router *SRouter) (map[string][]string, error) {
|
||||
routes, err := man.getByRouter(router)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := map[string][]string{}
|
||||
for i := range routes {
|
||||
route := &routes[i]
|
||||
line := route.routeLine()
|
||||
if line != "" {
|
||||
lines := r[route.Ifname]
|
||||
lines = append(lines, line)
|
||||
r[route.Ifname] = lines
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
yerrors "yunion.io/x/pkg/util/errors"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/util/choices"
|
||||
"yunion.io/x/onecloud/pkg/util/firewalld"
|
||||
"yunion.io/x/onecloud/pkg/util/rand"
|
||||
)
|
||||
|
||||
type SRule struct {
|
||||
db.SStandaloneResourceBase
|
||||
|
||||
Prio int `nullable:"false" list:"user" update:"user" create:"optional"`
|
||||
|
||||
MatchSrcNet string `length:"32" nullable:"false" list:"user" update:"user" create:"optional"`
|
||||
MatchDestNet string `length:"32" nullable:"false" list:"user" update:"user" create:"optional"`
|
||||
MatchProto string `length:"8" nullable:"false" list:"user" update:"user" create:"optional"`
|
||||
MatchSrcPort int `nullable:"false" list:"user" update:"user" create:"optional"`
|
||||
MatchDestPort int `nullable:"false" list:"user" update:"user" create:"optional"`
|
||||
MatchInIfname string `length:"32" nullable:"false" list:"user" update:"user" create:"optional"`
|
||||
MatchOutIfname string `length:"32" nullable:"false" list:"user" update:"user" create:"optional"`
|
||||
|
||||
Action string `length:"32" nullable:"false" list:"user" update:"user" create:"required"`
|
||||
ActionOptions string `length:"32" nullable:"false" list:"user" update:"user" create:"optional"`
|
||||
|
||||
RouterId string `length:"32" nullable:"false" list:"user" create:"optional"`
|
||||
|
||||
IsSystem bool `nullable:"false" list:"user" create:"optional"`
|
||||
}
|
||||
|
||||
const (
|
||||
MIN_PRIO = 0
|
||||
MAX_PRIO = 2000
|
||||
DEF_PRIO = 0
|
||||
DEF_PRIO_ROUTER_FORWARD = 1000
|
||||
DEF_PRIO_MASQUERADE = 1000
|
||||
|
||||
ACT_SNAT = "SNAT"
|
||||
ACT_DNAT = "DNAT"
|
||||
ACT_MASQUERADE = "MASQUERADE"
|
||||
ACT_TCPMSS = "TCPMSS" // FORWARD chain for now
|
||||
ACT_INPUT_ACCEPT = "INPUT_ACCEPT"
|
||||
ACT_FORWARD_ACCEPT = "FORWARD_ACCEPT"
|
||||
|
||||
PROTO_TCP = "tcp"
|
||||
PROTO_UDP = "udp"
|
||||
)
|
||||
|
||||
var (
|
||||
actionChoices = choices.NewChoices(
|
||||
ACT_SNAT,
|
||||
ACT_DNAT,
|
||||
ACT_MASQUERADE,
|
||||
ACT_TCPMSS,
|
||||
|
||||
//"DROP",
|
||||
ACT_INPUT_ACCEPT,
|
||||
ACT_FORWARD_ACCEPT,
|
||||
//"REJECT",
|
||||
)
|
||||
protoChoices = choices.NewChoices(
|
||||
PROTO_TCP,
|
||||
PROTO_UDP,
|
||||
)
|
||||
)
|
||||
|
||||
type SRuleManager struct {
|
||||
db.SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
var RuleManager *SRuleManager
|
||||
|
||||
func init() {
|
||||
RuleManager = &SRuleManager{
|
||||
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
|
||||
SRule{},
|
||||
"rules_tbl",
|
||||
"rule",
|
||||
"rules",
|
||||
),
|
||||
}
|
||||
RuleManager.SetVirtualObject(RuleManager)
|
||||
}
|
||||
|
||||
func (man *SRuleManager) validateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict, rule *SRule) error {
|
||||
isUpdate := rule != nil
|
||||
routerV := validators.NewModelIdOrNameValidator("router", "router", ownerId)
|
||||
inIfnameV := validators.NewRegexpValidator("match_in_ifname", regexpIfname)
|
||||
outIfnameV := validators.NewRegexpValidator("match_out_ifname", regexpIfname)
|
||||
protoV := validators.NewStringChoicesValidator("match_proto", protoChoices)
|
||||
srcPortV := validators.NewPortValidator("match_src_port")
|
||||
destPortV := validators.NewPortValidator("match_dest_port")
|
||||
actionV := validators.NewStringChoicesValidator("action", protoChoices)
|
||||
actionOptsV := validators.NewStringLenRangeValidator("action_options", 0, 256)
|
||||
if isUpdate {
|
||||
inIfnameV.Default(rule.MatchInIfname)
|
||||
outIfnameV.Default(rule.MatchOutIfname)
|
||||
protoV.Default(rule.MatchProto)
|
||||
srcPortV.Default(int64(rule.MatchSrcPort))
|
||||
destPortV.Default(int64(rule.MatchDestPort))
|
||||
actionV.Default(rule.Action)
|
||||
actionOptsV.Default(rule.ActionOptions)
|
||||
}
|
||||
vs := []validators.IValidator{
|
||||
inIfnameV.Optional(true),
|
||||
outIfnameV.Optional(true),
|
||||
validators.NewIPv4PrefixValidator("match_src_net").Optional(true),
|
||||
validators.NewIPv4PrefixValidator("match_dest_net").Optional(true),
|
||||
protoV.Optional(true),
|
||||
srcPortV.Optional(true),
|
||||
destPortV.Optional(true),
|
||||
actionV,
|
||||
actionOptsV.Optional(true),
|
||||
routerV,
|
||||
}
|
||||
for _, v := range vs {
|
||||
if isUpdate {
|
||||
v.Optional(true)
|
||||
}
|
||||
if err := v.Validate(data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if actionV.Value == ACT_TCPMSS {
|
||||
if protoV.Value != "" && protoV.Value != PROTO_TCP {
|
||||
return httperrors.NewBadRequestError("TCPMSS only works for proto tcp")
|
||||
}
|
||||
if protoV.Value == "" {
|
||||
data.Set("match_proto", jsonutils.NewString(PROTO_TCP))
|
||||
}
|
||||
} else if actionV.Value == ACT_DNAT {
|
||||
if outIfnameV.Value != "" {
|
||||
return httperrors.NewBadRequestError("cannot match out interface for DNAT")
|
||||
}
|
||||
} else if actionV.Value == ACT_SNAT {
|
||||
if inIfnameV.Value != "" {
|
||||
return httperrors.NewBadRequestError("cannot match in interface for SNAT")
|
||||
}
|
||||
}
|
||||
if (srcPortV.Value > 0 || destPortV.Value > 0) && protoV.Value == "" {
|
||||
return httperrors.NewBadRequestError("protocol must be specified when matching port")
|
||||
}
|
||||
|
||||
{
|
||||
prioDefault := int64(0)
|
||||
if !isUpdate && actionV.Value == ACT_MASQUERADE && !data.Contains("prio") {
|
||||
prioDefault = DEF_PRIO_MASQUERADE
|
||||
}
|
||||
prioV := validators.NewRangeValidator("prio", MIN_PRIO, MAX_PRIO)
|
||||
if !isUpdate {
|
||||
prioV.Default(prioDefault)
|
||||
}
|
||||
if err := prioV.Validate(data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// XXX validate interface against db
|
||||
// XXX validate action options
|
||||
|
||||
if !isUpdate && !data.Contains("name") {
|
||||
router := routerV.Model.(*SRouter)
|
||||
data.Set("name", jsonutils.NewString(
|
||||
router.Name+"-"+rand.String(4),
|
||||
))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (man *SRuleManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
|
||||
if _, err := man.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := man.validateData(ctx, userCred, ownerId, query, data, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (man *SRuleManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
|
||||
q, err := man.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := query.(*jsonutils.JSONDict)
|
||||
q, err = validators.ApplyModelFilters(q, data, []*validators.ModelFilterOptions{
|
||||
{Key: "router", ModelKeyword: "router", OwnerId: userCred},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (rule *SRule) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
|
||||
if _, err := rule.SStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := RuleManager.validateData(ctx, userCred, rule.GetOwnerId(), query, data, rule); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (man *SRuleManager) removeByRouter(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
|
||||
rules, err := man.getByFilter(map[string]string{
|
||||
"router_id": router.Id,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errs []error
|
||||
for j := range rules {
|
||||
if err := rules[j].Delete(ctx, userCred); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return yerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (man *SRuleManager) getByFilter(filter map[string]string) ([]SRule, error) {
|
||||
rules := []SRule{}
|
||||
q := man.Query()
|
||||
for key, val := range filter {
|
||||
q = q.Equals(key, val)
|
||||
}
|
||||
if err := db.FetchModelObjects(RuleManager, q, &rules); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func (man *SRuleManager) getOneByFilter(filter map[string]string) (*SRule, error) {
|
||||
rules, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rules) == 0 {
|
||||
return nil, errNotFound(fmt.Errorf("cannot find rule: %#v", filter))
|
||||
}
|
||||
if len(rules) > 1 {
|
||||
return nil, errMoreThanOne(fmt.Errorf("found more than 1 rules: %#v", filter))
|
||||
}
|
||||
return &rules[0], nil
|
||||
}
|
||||
|
||||
func (man *SRuleManager) checkExistenceByFilter(filter map[string]string) error {
|
||||
rules, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(rules) > 0 {
|
||||
return fmt.Errorf("rule exist: %s(%s)", rules[0].Name, rules[0].Id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (man *SRuleManager) getByRouter(router *SRouter) ([]SRule, error) {
|
||||
filter := map[string]string{
|
||||
"router_id": router.Id,
|
||||
}
|
||||
rules, err := man.getByFilter(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func (man *SRuleManager) addRouterRules(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
|
||||
r := &SRule{
|
||||
Prio: DEF_PRIO_ROUTER_FORWARD,
|
||||
RouterId: router.Id,
|
||||
Action: ACT_FORWARD_ACCEPT,
|
||||
}
|
||||
r.IsSystem = true
|
||||
r.Name = router.Name + "-allow-forward-" + rand.String(4)
|
||||
r.SetModelManager(man, r)
|
||||
|
||||
err := man.addRule(ctx, userCred, r)
|
||||
return err
|
||||
}
|
||||
|
||||
func (man *SRuleManager) addWireguardIfaceRules(ctx context.Context, userCred mcclient.TokenCredential, iface *SIface) error {
|
||||
r := &SRule{
|
||||
RouterId: iface.RouterId,
|
||||
MatchProto: PROTO_UDP,
|
||||
MatchDestPort: iface.ListenPort,
|
||||
Action: ACT_INPUT_ACCEPT,
|
||||
}
|
||||
r.IsSystem = true
|
||||
r.Name = iface.Name + "-allow-" + fmt.Sprintf("%d-", iface.ListenPort) + rand.String(4)
|
||||
r.SetModelManager(man, r)
|
||||
|
||||
rules := man.ifaceTCPMSSRules(ctx, userCred, iface)
|
||||
rules = append(rules, r)
|
||||
err := man.addRules(ctx, userCred, rules)
|
||||
return err
|
||||
}
|
||||
|
||||
func (man *SRuleManager) ifaceTCPMSSRules(ctx context.Context, userCred mcclient.TokenCredential, iface *SIface) []*SRule {
|
||||
rules := []*SRule{
|
||||
&SRule{
|
||||
RouterId: iface.RouterId,
|
||||
MatchInIfname: iface.Ifname,
|
||||
MatchProto: PROTO_TCP,
|
||||
Action: ACT_TCPMSS,
|
||||
ActionOptions: "--clamp-mss-to-pmtu",
|
||||
},
|
||||
&SRule{
|
||||
RouterId: iface.RouterId,
|
||||
MatchProto: PROTO_TCP,
|
||||
MatchOutIfname: iface.Ifname,
|
||||
Action: ACT_TCPMSS,
|
||||
ActionOptions: "--clamp-mss-to-pmtu",
|
||||
},
|
||||
}
|
||||
for _, r := range rules {
|
||||
r.IsSystem = true
|
||||
r.Name = iface.Name + "-tcpmss-" + rand.String(4)
|
||||
r.SetModelManager(man, r)
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
func (man *SRuleManager) addRules(ctx context.Context, userCred mcclient.TokenCredential, rules []*SRule) error {
|
||||
errs := []error{}
|
||||
for _, r := range rules {
|
||||
err := man.addRule(ctx, userCred, r)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return yerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (man *SRuleManager) addRule(ctx context.Context, userCred mcclient.TokenCredential, rule *SRule) error {
|
||||
return man.TableSpec().Insert(rule)
|
||||
}
|
||||
|
||||
func (rule *SRule) firewalldRule() (*firewalld.Rule, error) {
|
||||
var (
|
||||
prio int
|
||||
table string
|
||||
chain string
|
||||
action string
|
||||
body string
|
||||
matchOthers []string
|
||||
actionOthers []string
|
||||
)
|
||||
prio = rule.Prio
|
||||
switch rule.Action {
|
||||
case ACT_SNAT, ACT_DNAT, ACT_MASQUERADE:
|
||||
table = "nat"
|
||||
if rule.Action == ACT_DNAT {
|
||||
chain = "PREROUTING"
|
||||
} else {
|
||||
chain = "POSTROUTING"
|
||||
}
|
||||
action = rule.Action
|
||||
case ACT_TCPMSS:
|
||||
table = "mangle"
|
||||
chain = "FORWARD" // save INPUT, OUTPUT for future occasions
|
||||
action = rule.Action
|
||||
matchOthers = []string{"-m", "tcp", "--tcp-flags", "SYN,RST", "SYN"}
|
||||
if rule.ActionOptions == "" {
|
||||
actionOthers = []string{"--clamp-mss-to-pmtu"}
|
||||
}
|
||||
case ACT_INPUT_ACCEPT:
|
||||
table = "filter"
|
||||
chain = "INPUT"
|
||||
action = "ACCEPT"
|
||||
case ACT_FORWARD_ACCEPT:
|
||||
table = "filter"
|
||||
chain = "FORWARD"
|
||||
action = "ACCEPT"
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown rule action: %s", rule.Action)
|
||||
}
|
||||
|
||||
{
|
||||
elms := []string{}
|
||||
if rule.MatchInIfname != "" {
|
||||
elms = append(elms, "-i", rule.MatchInIfname)
|
||||
}
|
||||
if rule.MatchOutIfname != "" {
|
||||
elms = append(elms, "-o", rule.MatchOutIfname)
|
||||
}
|
||||
if rule.MatchSrcNet != "" {
|
||||
elms = append(elms, "-s", rule.MatchSrcNet)
|
||||
}
|
||||
if rule.MatchDestNet != "" {
|
||||
elms = append(elms, "-d", rule.MatchDestNet)
|
||||
}
|
||||
if rule.MatchProto != "" {
|
||||
elms = append(elms, "-p", rule.MatchProto)
|
||||
}
|
||||
if rule.MatchSrcPort > 0 {
|
||||
elms = append(elms, "--sport", fmt.Sprintf("%d", rule.MatchSrcPort))
|
||||
}
|
||||
if rule.MatchDestPort > 0 {
|
||||
elms = append(elms, "--dport", fmt.Sprintf("%d", rule.MatchDestPort))
|
||||
}
|
||||
elms = append(elms, matchOthers...) // XXX empty elm
|
||||
elms = append(elms, "-j", action)
|
||||
if rule.ActionOptions != "" {
|
||||
elms = append(elms, rule.ActionOptions)
|
||||
}
|
||||
elms = append(elms, actionOthers...)
|
||||
body = strings.Join(elms, " ")
|
||||
}
|
||||
r := firewalld.NewIP4Rule(prio, table, chain, body)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (man *SRuleManager) firewalldDirectByRouter(router *SRouter) (*firewalld.Direct, error) {
|
||||
rules, err := man.getByRouter(router)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rs := []*firewalld.Rule{}
|
||||
errs := []error{}
|
||||
for i := range rules {
|
||||
rule := &rules[i]
|
||||
r, err := rule.firewalldRule()
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
rs = append(rs, r)
|
||||
}
|
||||
return firewalld.NewDirect(rs...), yerrors.NewAggregate(errs)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
type (
|
||||
errNotFound error
|
||||
errMoreThanOne error
|
||||
)
|
||||
|
||||
func IsNotFound(err error) bool {
|
||||
_, ok := err.(errNotFound)
|
||||
return ok
|
||||
}
|
||||
|
||||
func IsMoreThanOne(err error) bool {
|
||||
_, ok := err.(errMoreThanOne)
|
||||
return ok
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/pkg/util/netutils"
|
||||
)
|
||||
|
||||
type Subnets []*netutils.IPV4Prefix
|
||||
|
||||
func (nets Subnets) StrList() []string {
|
||||
r := make([]string, 0, len(nets))
|
||||
for _, p := range nets {
|
||||
r = append(r, p.String())
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (nets Subnets) String() string {
|
||||
r := nets.StrList()
|
||||
return strings.Join(r, ",")
|
||||
}
|
||||
|
||||
func (nets Subnets) ContainsAny(nets1 Subnets) bool {
|
||||
contains, _ := nets.ContainsAnyEx(nets1)
|
||||
return contains
|
||||
}
|
||||
|
||||
func (nets Subnets) ContainsAnyEx(nets1 Subnets) (bool, *netutils.IPV4Prefix) {
|
||||
for _, p0 := range nets {
|
||||
for _, p1 := range nets1 {
|
||||
if p0.Equals(p1) {
|
||||
return true, p0
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 options // import "yunion.io/x/onecloud/pkg/cloudnet/options"
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 options
|
||||
|
||||
import common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
|
||||
|
||||
type CloudnetOptions struct {
|
||||
common_options.CommonOptions
|
||||
common_options.DBOptions
|
||||
}
|
||||
|
||||
var (
|
||||
Options CloudnetOptions
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 service // import "yunion.io/x/onecloud/pkg/cloudnet/service"
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 service
|
||||
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/appsrv/dispatcher"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudnet/models"
|
||||
)
|
||||
|
||||
func InitHandlers(app *appsrv.Application) {
|
||||
db.InitAllManagers()
|
||||
|
||||
db.RegisterModelManager(db.OpsLog)
|
||||
db.RegisterModelManager(db.TenantCacheManager)
|
||||
db.RegisterModelManager(db.UserCacheManager)
|
||||
db.RegisterModelManager(models.IfaceManager)
|
||||
db.RegisterModelManager(models.IfacePeerManager)
|
||||
db.RegisterModelManager(models.MeshNetworkMemberManager)
|
||||
for _, manager := range []db.IModelManager{
|
||||
models.RouterManager,
|
||||
models.MeshNetworkManager,
|
||||
models.RouteManager,
|
||||
models.RuleManager,
|
||||
} {
|
||||
db.RegisterModelManager(manager)
|
||||
handler := db.NewModelHandler(manager)
|
||||
dispatcher.AddModelDispatcher("", app, handler)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 service
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
"yunion.io/x/log"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon"
|
||||
common_app "yunion.io/x/onecloud/pkg/cloudcommon/app"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
|
||||
"yunion.io/x/onecloud/pkg/cloudnet/models"
|
||||
"yunion.io/x/onecloud/pkg/cloudnet/options"
|
||||
)
|
||||
|
||||
func StartService() {
|
||||
opts := &options.Options
|
||||
common_options.ParseOptions(opts, os.Args, "cloudnet.conf", "cloudnet")
|
||||
|
||||
commonOpts := &opts.CommonOptions
|
||||
common_app.InitAuth(commonOpts, func() {
|
||||
log.Infof("Auth complete")
|
||||
})
|
||||
|
||||
dbOpts := &opts.DBOptions
|
||||
baseOpts := &opts.BaseOptions
|
||||
|
||||
app := common_app.InitApp(baseOpts, false)
|
||||
InitHandlers(app)
|
||||
|
||||
db.EnsureAppInitSyncDB(app, dbOpts, models.InitDB)
|
||||
defer cloudcommon.CloseDB()
|
||||
|
||||
common_app.ServeForever(app, baseOpts)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package utils // import "yunion.io/x/onecloud/pkg/cloudnet/utils"
|
||||
@@ -0,0 +1,28 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
func MustNewKey() wgtypes.Key {
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("new key: %v", err))
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func MustNewKeyString() string {
|
||||
key := MustNewKey()
|
||||
return key.String()
|
||||
}
|
||||
|
||||
func MustParseKeyString(k string) wgtypes.Key {
|
||||
key, err := wgtypes.ParseKey(k)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("invalid key: %v", err))
|
||||
}
|
||||
return key
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
func TestKey(t *testing.T) {
|
||||
t.Run("parse", func(t *testing.T) {
|
||||
keyStr := "WJYVsrTtAae1QS9YzefV4OmVM6mkJglR+GEgxQpTs2g="
|
||||
pubkeyStr := "mOX0S5AuRqd8lQZWcqTlzOS+veo404gE7NyV4u3xVkg="
|
||||
pubkeyX, err := wgtypes.ParseKey(pubkeyStr)
|
||||
if err != nil {
|
||||
t.Fatalf("parse public key failed: %v", err)
|
||||
}
|
||||
|
||||
key, err := wgtypes.ParseKey(keyStr)
|
||||
if err != nil {
|
||||
t.Fatalf("parse private key failed: %v", err)
|
||||
}
|
||||
pubkey := key.PublicKey()
|
||||
if pubkey != pubkeyX {
|
||||
t.Errorf("derived public key does not match expected.\ngot %#v\nwant %#v", pubkey, pubkeyX)
|
||||
}
|
||||
})
|
||||
t.Run("generate", func(t *testing.T) {
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("generate private key failed: %v", err)
|
||||
}
|
||||
pubkey := key.PublicKey()
|
||||
t.Logf("private key: %s", key)
|
||||
t.Logf(" public key: %s", pubkey)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 cloudnet
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
|
||||
)
|
||||
|
||||
func NewCloudnetManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager {
|
||||
return modulebase.ResourceManager{
|
||||
BaseManager: *modulebase.NewBaseManager("cloudnet", "", "", columns, adminColumns),
|
||||
Keyword: keyword, KeywordPlural: keywordPlural}
|
||||
}
|
||||
|
||||
var (
|
||||
registerV2 = func(mod modulebase.BaseManagerInterface) {
|
||||
modulebase.Register("v2", mod)
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
package cloudnet // import "yunion.io/x/onecloud/pkg/mcclient/modules/cloudnet"
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 cloudnet
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
|
||||
)
|
||||
|
||||
type MeshNetworkManager struct {
|
||||
modulebase.ResourceManager
|
||||
}
|
||||
|
||||
var (
|
||||
MeshNetworks MeshNetworkManager
|
||||
)
|
||||
|
||||
func init() {
|
||||
MeshNetworks = MeshNetworkManager{
|
||||
NewCloudnetManager(
|
||||
"meshnetwork",
|
||||
"meshnetworks",
|
||||
[]string{
|
||||
"id",
|
||||
"name",
|
||||
},
|
||||
[]string{"tenant"},
|
||||
),
|
||||
}
|
||||
registerV2(&MeshNetworks)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 cloudnet
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
|
||||
)
|
||||
|
||||
type RouterManager struct {
|
||||
modulebase.ResourceManager
|
||||
}
|
||||
|
||||
var (
|
||||
Routers RouterManager
|
||||
)
|
||||
|
||||
func init() {
|
||||
Routers = RouterManager{
|
||||
NewCloudnetManager(
|
||||
"router",
|
||||
"routers",
|
||||
[]string{
|
||||
"id",
|
||||
"name",
|
||||
"user",
|
||||
"host",
|
||||
"port",
|
||||
},
|
||||
[]string{"tenant"},
|
||||
),
|
||||
}
|
||||
registerV2(&Routers)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 cloudnet
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
|
||||
)
|
||||
|
||||
type RouteManager struct {
|
||||
modulebase.ResourceManager
|
||||
}
|
||||
|
||||
var (
|
||||
Routes RouteManager
|
||||
)
|
||||
|
||||
func init() {
|
||||
Routes = RouteManager{
|
||||
NewCloudnetManager(
|
||||
"route",
|
||||
"routes",
|
||||
[]string{
|
||||
"id",
|
||||
"name",
|
||||
"iface_id",
|
||||
"ifname",
|
||||
"network",
|
||||
"gateway",
|
||||
},
|
||||
[]string{"tenant"},
|
||||
),
|
||||
}
|
||||
registerV2(&Routes)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 cloudnet
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
|
||||
)
|
||||
|
||||
type RuleManager struct {
|
||||
modulebase.ResourceManager
|
||||
}
|
||||
|
||||
var (
|
||||
Rules RuleManager
|
||||
)
|
||||
|
||||
func init() {
|
||||
Rules = RuleManager{
|
||||
NewCloudnetManager(
|
||||
"rule",
|
||||
"rules",
|
||||
[]string{
|
||||
"id",
|
||||
"name",
|
||||
"router_id",
|
||||
"match_src_net",
|
||||
"match_dest_net",
|
||||
"match_proto",
|
||||
"match_port",
|
||||
"match_in_ifname",
|
||||
"match_out_ifname",
|
||||
"action",
|
||||
"action_options",
|
||||
},
|
||||
[]string{"tenant"},
|
||||
),
|
||||
}
|
||||
registerV2(&Rules)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 modules
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/mcclient/modulebase"
|
||||
|
||||
type AnsiblePlaybookV2Manager struct {
|
||||
modulebase.ResourceManager
|
||||
}
|
||||
|
||||
var (
|
||||
AnsiblePlaybooksV2 AnsiblePlaybookV2Manager
|
||||
)
|
||||
|
||||
func init() {
|
||||
AnsiblePlaybooksV2 = AnsiblePlaybookV2Manager{
|
||||
NewAnsibleManager(
|
||||
"ansibleplaybook_v2",
|
||||
"ansibleplaybooks_v2",
|
||||
[]string{
|
||||
"id",
|
||||
"name",
|
||||
"status",
|
||||
"start_time",
|
||||
"end_time",
|
||||
},
|
||||
[]string{},
|
||||
),
|
||||
}
|
||||
registerV2(&AnsiblePlaybooksV2)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package cloudnet // import "yunion.io/x/onecloud/pkg/mcclient/options/cloudnet"
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 cloudnet
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
)
|
||||
|
||||
type MeshNetworkCreateOptions struct {
|
||||
NAME string
|
||||
}
|
||||
|
||||
type MeshNetworkGetOptions struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
type MeshNetworkUpdateOptions struct {
|
||||
ID string `json:"-"`
|
||||
Name string
|
||||
}
|
||||
|
||||
type MeshNetworkDeleteOptions struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
type MeshNetworkListOptions struct {
|
||||
options.BaseListOptions
|
||||
}
|
||||
|
||||
type MeshNetworkActionRealizeOptions struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 cloudnet
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
)
|
||||
|
||||
type RouterCreateOptions struct {
|
||||
NAME string
|
||||
User string
|
||||
Host string
|
||||
Port int
|
||||
PrivateKey string
|
||||
|
||||
RealizeWgIfaces string `choices:"on|off" default:"on" help:"apply wg ifaces config on realization"`
|
||||
RealizeRoutes string `choices:"on|off" default:"on" help:"apply routes config on realization"`
|
||||
RealizeRules string `choices:"on|off" default:"on" help:"apply firewall rules on realization"`
|
||||
}
|
||||
|
||||
func (opts *RouterCreateOptions) Params() (jsonutils.JSONObject, error) {
|
||||
params, err := options.StructToParams(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.PrivateKey != "" && opts.PrivateKey[0] == '@' {
|
||||
data, err := ioutil.ReadFile(opts.PrivateKey[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params.Set("private_key", jsonutils.NewString(string(data)))
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
type RouterGetOptions struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
type RouterUpdateOptions struct {
|
||||
ID string `json:"-"`
|
||||
Name string
|
||||
|
||||
User string
|
||||
Host string
|
||||
Port int `json:",omitzero"`
|
||||
PrivateKey string
|
||||
|
||||
RealizeWgIfaces string `json:",omitzero" choices:"on|off" help:"apply wg ifaces config on realization"`
|
||||
RealizeRoutes string `json:",omitzero" choices:"on|off" help:"apply routes config on realization"`
|
||||
RealizeRules string `json:",omitzero" choices:"on|off" help:"apply firewall rules on realization"`
|
||||
}
|
||||
|
||||
func (opts *RouterUpdateOptions) Params() (jsonutils.JSONObject, error) {
|
||||
params, err := options.StructToParams(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.PrivateKey != "" && opts.PrivateKey[0] == '@' {
|
||||
data, err := ioutil.ReadFile(opts.PrivateKey[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params.Set("private_key", jsonutils.NewString(string(data)))
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
type RouterDeleteOptions struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
type RouterListOptions struct {
|
||||
options.BaseListOptions
|
||||
}
|
||||
|
||||
type RouterActionJoinMeshNetworkOptions struct {
|
||||
ID string `json:"-"`
|
||||
|
||||
MeshNetwork string
|
||||
AdvertiseSubnets string `help:"cidr concatenated by comma"`
|
||||
}
|
||||
|
||||
type RouterActionLeaveMeshNetworkOptions struct {
|
||||
ID string `json:"-"`
|
||||
|
||||
MeshNetwork string
|
||||
}
|
||||
|
||||
type RouterActionRegisterIfnameOptions struct {
|
||||
ID string `json:"-"`
|
||||
|
||||
Ifname string
|
||||
}
|
||||
|
||||
type RouterActionUnregisterIfnameOptions struct {
|
||||
ID string `json:"-"`
|
||||
|
||||
Ifname string
|
||||
}
|
||||
|
||||
type RouterActionRealizeOptions struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 cloudnet
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
)
|
||||
|
||||
type RouteCreateOptions struct {
|
||||
NAME string
|
||||
|
||||
Iface string `required:"true"`
|
||||
Network string
|
||||
Gateway string
|
||||
}
|
||||
|
||||
type RouteGetOptions struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
type RouteUpdateOptions struct {
|
||||
ID string `json:"-"`
|
||||
Name string
|
||||
|
||||
Network string
|
||||
Gateway string
|
||||
}
|
||||
|
||||
type RouteDeleteOptions struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
type RouteListOptions struct {
|
||||
options.BaseListOptions
|
||||
|
||||
Iface string
|
||||
Network string
|
||||
Gateway string
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// 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 cloudnet
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
)
|
||||
|
||||
type RuleCreateOptions struct {
|
||||
NAME string
|
||||
|
||||
Router string `required:"true"`
|
||||
|
||||
MatchSrcNet string
|
||||
MatchDestNet string
|
||||
MatchProto string
|
||||
MatchSrcPort int
|
||||
MatchDestPort int
|
||||
MatchInIfname string
|
||||
MatchOutIfname string
|
||||
|
||||
Action string
|
||||
ActionOptions string
|
||||
}
|
||||
|
||||
type RuleGetOptions struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
type RuleUpdateOptions struct {
|
||||
ID string `json:"-"`
|
||||
Name string
|
||||
|
||||
MatchSrcNet string
|
||||
MatchDestNet string
|
||||
MatchProto string
|
||||
MatchSrcPort int `json:",omitzero"`
|
||||
MatchDestPort int `json:",omitzero"`
|
||||
MatchInIfname string
|
||||
MatchOutIfname string
|
||||
|
||||
Action string
|
||||
ActionOptions string
|
||||
}
|
||||
|
||||
type RuleDeleteOptions struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
type RuleListOptions struct {
|
||||
options.BaseListOptions
|
||||
|
||||
Router string
|
||||
|
||||
MatchSrcNet string
|
||||
MatchDestNet string
|
||||
MatchProto string
|
||||
MatchSrcPort int `json:",omitzero"`
|
||||
MatchDestPort int `json:",omitzero"`
|
||||
MatchInIfname string
|
||||
MatchOutIfname string
|
||||
|
||||
Action string
|
||||
ActionOptions string
|
||||
}
|
||||
@@ -18,12 +18,16 @@ import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type Runnable interface {
|
||||
Run(context.Context) error
|
||||
}
|
||||
|
||||
// Session is a container for execution of playbook
|
||||
type Session struct {
|
||||
// Ctx is the context under which the playbook will run
|
||||
Ctx context.Context
|
||||
// Playbook is the ansible playbook to be run
|
||||
Playbook *Playbook
|
||||
// Runnable is the task to be run
|
||||
Runnable Runnable
|
||||
|
||||
// cancelFunc can be called to cancel the running playbook
|
||||
cancelFunc context.CancelFunc
|
||||
@@ -38,11 +42,11 @@ func (sm SessionManager) Has(id string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// Add adds a Playbook to the manager keyed with the specified id
|
||||
func (sm SessionManager) Add(id string, pb *Playbook) *Session {
|
||||
// Add adds a Runnable to the manager keyed with the specified id
|
||||
func (sm SessionManager) Add(id string, runnable Runnable) *Session {
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
session := &Session{
|
||||
Playbook: pb,
|
||||
Runnable: runnable,
|
||||
Ctx: ctx,
|
||||
cancelFunc: cancelFunc,
|
||||
}
|
||||
@@ -63,7 +67,7 @@ func (sm SessionManager) Run(id string) error {
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return s.Playbook.Run(s.Ctx)
|
||||
return s.Runnable.Run(s.Ctx)
|
||||
}
|
||||
|
||||
// Stop stops the playbook keyed with specified id
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package ansiblev2 // import "yunion.io/x/onecloud/pkg/util/ansiblev2"
|
||||
@@ -0,0 +1,108 @@
|
||||
package ansiblev2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-yaml/yaml"
|
||||
)
|
||||
|
||||
func newVars(args ...interface{}) map[string]interface{} {
|
||||
if len(args)&1 != 0 {
|
||||
panic("odd number of args for key/value pairs!")
|
||||
}
|
||||
vars := map[string]interface{}{}
|
||||
for i := 0; i < len(args); i += 2 {
|
||||
if k, ok := args[i].(string); !ok {
|
||||
panic(fmt.Sprintf("the %drd key is not string type: %#v", i, args[i]))
|
||||
} else {
|
||||
vars[k] = args[i+1]
|
||||
}
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
type Host struct {
|
||||
Vars map[string]interface{}
|
||||
}
|
||||
|
||||
func NewHost(args ...interface{}) *Host {
|
||||
return &Host{
|
||||
Vars: newVars(args...),
|
||||
}
|
||||
}
|
||||
|
||||
type HostGroup struct {
|
||||
Hosts map[string]*Host
|
||||
Children map[string]*HostGroup
|
||||
Vars map[string]interface{}
|
||||
}
|
||||
|
||||
func NewHostGroup(args ...interface{}) *HostGroup {
|
||||
return &HostGroup{
|
||||
Vars: newVars(args...),
|
||||
}
|
||||
}
|
||||
|
||||
func (hg *HostGroup) SetHost(name string, host *Host) *HostGroup {
|
||||
if hg.Hosts == nil {
|
||||
hg.Hosts = map[string]*Host{}
|
||||
}
|
||||
hg.Hosts[name] = host
|
||||
return hg
|
||||
}
|
||||
|
||||
func (hg *HostGroup) SetChild(name string, child *HostGroup) *HostGroup {
|
||||
if hg.Children == nil {
|
||||
hg.Children = map[string]*HostGroup{}
|
||||
}
|
||||
hg.Children[name] = child
|
||||
return hg
|
||||
}
|
||||
|
||||
func (hg *HostGroup) MarshalYAML() (interface{}, error) {
|
||||
hosts := map[string]interface{}{}
|
||||
for name := range hg.Hosts {
|
||||
hosts[name] = hg.Hosts[name].Vars
|
||||
}
|
||||
children := map[string]interface{}{}
|
||||
for name := range hg.Children {
|
||||
children[name], _ = hg.Children[name].MarshalYAML()
|
||||
}
|
||||
|
||||
r := map[string]interface{}{}
|
||||
if len(hosts) > 0 {
|
||||
r["hosts"] = hosts
|
||||
}
|
||||
if len(children) > 0 {
|
||||
r["children"] = children
|
||||
}
|
||||
if len(hg.Vars) > 0 {
|
||||
r["vars"] = hg.Vars
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type Inventory struct {
|
||||
HostGroup
|
||||
}
|
||||
|
||||
func NewInventory(args ...interface{}) *Inventory {
|
||||
hg := NewHostGroup(args...)
|
||||
inv := &Inventory{
|
||||
HostGroup: *hg,
|
||||
}
|
||||
return inv
|
||||
}
|
||||
|
||||
func (inv *Inventory) MarshalYAML() (interface{}, error) {
|
||||
all, _ := inv.HostGroup.MarshalYAML()
|
||||
r := map[string]interface{}{
|
||||
"all": all,
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (inv *Inventory) String() string {
|
||||
b, _ := yaml.Marshal(inv)
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package ansiblev2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInventory(t *testing.T) {
|
||||
inv := NewInventory(
|
||||
"ansible_user", "root",
|
||||
"ansible_become", "yes",
|
||||
"redis_bind_address", "0.0.0.0",
|
||||
"redis_bind_port", 9736,
|
||||
)
|
||||
inv.SetHost("redis", NewHost(
|
||||
"ansible_host", "192.168.0.248",
|
||||
"ansible_port", 2222,
|
||||
))
|
||||
inv.SetHost("nameonly", NewHost())
|
||||
child := NewHostGroup()
|
||||
child.SetHost("aws-bj01", NewHost(
|
||||
"ansible_user", "ec2-user",
|
||||
"ansible_host", "1.2.2.3",
|
||||
))
|
||||
child.SetHost("ali-hk01", NewHost(
|
||||
"ansible_user", "cloudroot",
|
||||
"ansible_host", "1.2.2.4",
|
||||
))
|
||||
inv.SetChild("netmon-agents", child)
|
||||
t.Logf("\n%s", inv.String())
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package ansiblev2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-yaml/yaml"
|
||||
)
|
||||
|
||||
type ITask interface {
|
||||
MarshalYAML() (interface{}, error)
|
||||
}
|
||||
|
||||
type Task struct {
|
||||
Name string
|
||||
WithPlugin string
|
||||
WithPluginVal interface{}
|
||||
When string
|
||||
Register string
|
||||
IgnoreErrors bool
|
||||
Vars map[string]interface{}
|
||||
|
||||
ModuleName string
|
||||
ModuleArgs map[string]interface{}
|
||||
}
|
||||
|
||||
func (t *Task) MarshalYAML() (interface{}, error) {
|
||||
r := map[string]interface{}{
|
||||
t.ModuleName: t.ModuleArgs,
|
||||
}
|
||||
if t.Name != "" {
|
||||
r["name"] = t.Name
|
||||
}
|
||||
if t.WithPlugin != "" && t.WithPluginVal != nil {
|
||||
r["with_"+t.WithPlugin] = t.WithPluginVal
|
||||
}
|
||||
if t.When != "" {
|
||||
r["when"] = t.When
|
||||
}
|
||||
if t.Register != "" {
|
||||
r["register"] = t.Register
|
||||
}
|
||||
if t.IgnoreErrors {
|
||||
r["ignore_errors"] = "yes"
|
||||
}
|
||||
if len(t.Vars) > 0 {
|
||||
r["vars"] = t.Vars
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type ShellTask struct {
|
||||
Name string
|
||||
WithPlugin string
|
||||
WithPluginVal interface{}
|
||||
When string
|
||||
Register string
|
||||
IgnoreErrors bool
|
||||
Vars map[string]interface{}
|
||||
|
||||
Script string
|
||||
ModuleArgs map[string]interface{}
|
||||
}
|
||||
|
||||
func (t *ShellTask) MarshalYAML() (interface{}, error) {
|
||||
r := map[string]interface{}{
|
||||
"shell": t.Script,
|
||||
}
|
||||
if len(t.ModuleArgs) > 0 {
|
||||
r["args"] = t.ModuleArgs
|
||||
}
|
||||
|
||||
if t.Name != "" {
|
||||
r["name"] = t.Name
|
||||
}
|
||||
if t.WithPlugin != "" && t.WithPluginVal != nil {
|
||||
r["with_"+t.WithPlugin] = t.WithPluginVal
|
||||
}
|
||||
if t.When != "" {
|
||||
r["when"] = t.When
|
||||
}
|
||||
if t.Register != "" {
|
||||
r["register"] = t.Register
|
||||
}
|
||||
if t.IgnoreErrors {
|
||||
r["ignore_errors"] = "yes"
|
||||
}
|
||||
if len(t.Vars) > 0 {
|
||||
r["vars"] = t.Vars
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type Block struct {
|
||||
Name string
|
||||
WithPlugin string
|
||||
WithPluginVal interface{}
|
||||
When string
|
||||
Register string
|
||||
IgnoreErrors bool
|
||||
Vars map[string]interface{}
|
||||
|
||||
Tasks []ITask
|
||||
}
|
||||
|
||||
func NewBlock(tasks ...ITask) *Block {
|
||||
b := &Block{
|
||||
Tasks: tasks,
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Block) MarshalYAML() (interface{}, error) {
|
||||
r := map[string]interface{}{}
|
||||
if len(b.Tasks) > 0 {
|
||||
tasks := make([]interface{}, len(b.Tasks))
|
||||
for i := range tasks {
|
||||
var err error
|
||||
tasks[i], err = b.Tasks[i].MarshalYAML()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
r["block"] = tasks
|
||||
}
|
||||
|
||||
if b.Name != "" {
|
||||
r["name"] = b.Name
|
||||
}
|
||||
if b.WithPlugin != "" && b.WithPluginVal != nil {
|
||||
r["with_"+b.WithPlugin] = b.WithPluginVal
|
||||
}
|
||||
if b.When != "" {
|
||||
r["when"] = b.When
|
||||
}
|
||||
if b.Register != "" {
|
||||
r["register"] = b.Register
|
||||
}
|
||||
if b.IgnoreErrors {
|
||||
r["ignore_errors"] = "yes"
|
||||
}
|
||||
if len(b.Vars) > 0 {
|
||||
r["vars"] = b.Vars
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type Play struct {
|
||||
Name string
|
||||
RemoteUser string
|
||||
Vars map[string]interface{}
|
||||
IgnoreErrors bool
|
||||
|
||||
Hosts string
|
||||
Tasks []ITask
|
||||
}
|
||||
|
||||
func NewPlay(tasks ...ITask) *Play {
|
||||
play := &Play{
|
||||
Tasks: tasks,
|
||||
}
|
||||
return play
|
||||
}
|
||||
|
||||
func (play *Play) MarshalYAML() (interface{}, error) {
|
||||
if play.Hosts == "" {
|
||||
return nil, fmt.Errorf("hosts is required but not set")
|
||||
}
|
||||
|
||||
r := map[string]interface{}{
|
||||
"hosts": play.Hosts,
|
||||
}
|
||||
if len(play.Tasks) > 0 {
|
||||
tasks := make([]interface{}, len(play.Tasks))
|
||||
for i := range tasks {
|
||||
var err error
|
||||
tasks[i], err = play.Tasks[i].MarshalYAML()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
r["tasks"] = tasks
|
||||
}
|
||||
|
||||
if play.Name != "" {
|
||||
r["name"] = play.Name
|
||||
}
|
||||
if play.RemoteUser != "" {
|
||||
r["remote_user"] = play.RemoteUser
|
||||
}
|
||||
if len(play.Vars) > 0 {
|
||||
r["vars"] = play.Vars
|
||||
}
|
||||
if play.IgnoreErrors {
|
||||
r["ignore_errors"] = "yes"
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type Playbook struct {
|
||||
Plays []*Play
|
||||
}
|
||||
|
||||
func NewPlaybook(plays ...*Play) *Playbook {
|
||||
pb := &Playbook{
|
||||
Plays: plays,
|
||||
}
|
||||
return pb
|
||||
|
||||
}
|
||||
func (pb *Playbook) MarshalYAML() (interface{}, error) {
|
||||
r := make([]interface{}, len(pb.Plays))
|
||||
for i := range pb.Plays {
|
||||
var err error
|
||||
r[i], err = pb.Plays[i].MarshalYAML()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (pb *Playbook) String() string {
|
||||
b, err := yaml.Marshal(pb)
|
||||
if err != nil {
|
||||
// panic early
|
||||
panic(err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package ansiblev2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPlaybookString(t *testing.T) {
|
||||
play := NewPlay(
|
||||
&Task{
|
||||
Name: "Enable ip_forward",
|
||||
ModuleName: "sysctl",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"name": "net.ipv4.ip_forward",
|
||||
"value": "1",
|
||||
"state": "present",
|
||||
"reload": "yes",
|
||||
},
|
||||
},
|
||||
&Task{
|
||||
Name: "Enable EPEL",
|
||||
ModuleName: "package",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"name": "epel-release",
|
||||
"state": "present",
|
||||
},
|
||||
When: `ansible_distribution != "Fedora"`,
|
||||
},
|
||||
&Task{
|
||||
Name: "Install wireguard packages",
|
||||
ModuleName: "package",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"name": "{{ item }}",
|
||||
"state": "present",
|
||||
},
|
||||
WithPlugin: "items",
|
||||
WithPluginVal: []interface{}{"wireguard-dkms", "wireguard-tools"},
|
||||
},
|
||||
&Task{
|
||||
Name: "Create /etc/wireguard",
|
||||
ModuleName: "file",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"path": "/etc/wireguard",
|
||||
"staet": "directory",
|
||||
"owner": "root",
|
||||
"group": "root",
|
||||
},
|
||||
},
|
||||
)
|
||||
play.Hosts = "all"
|
||||
configureBlock := NewBlock(
|
||||
&Task{
|
||||
Name: "Configure {{ item }}",
|
||||
ModuleName: "template",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"src": "wgX.conf.j2", //XXX
|
||||
"dest": "/etc/wireguard/{{ item }}.conf",
|
||||
"mode": 0600,
|
||||
},
|
||||
Register: "configuration",
|
||||
},
|
||||
&Task{
|
||||
Name: "Enable wg-quick@{{ item }} service",
|
||||
ModuleName: "service",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"name": "wg-quick@{{ item }}",
|
||||
"state": "started",
|
||||
"enabled": "yes",
|
||||
},
|
||||
},
|
||||
&Task{
|
||||
Name: "Restart wg-quick@{{ item }} service",
|
||||
ModuleName: "service",
|
||||
ModuleArgs: map[string]interface{}{
|
||||
"name": "wg-quick@{{ item }}",
|
||||
"state": "restarted",
|
||||
},
|
||||
When: "configuration is changed",
|
||||
},
|
||||
)
|
||||
configureBlock.Name = "Configure wireguard networks"
|
||||
play.Tasks = append(play.Tasks, configureBlock)
|
||||
pb := NewPlaybook(play)
|
||||
t.Logf("\n%s", pb.String())
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package ansiblev2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
yerrors "yunion.io/x/pkg/util/errors"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
privateKey string
|
||||
playbook string
|
||||
inventory string
|
||||
files map[string][]byte
|
||||
|
||||
outputWriter io.Writer
|
||||
stateMux *sync.Mutex
|
||||
isRunning bool
|
||||
keepTmpdir bool
|
||||
}
|
||||
|
||||
func NewSession() *Session {
|
||||
sess := &Session{
|
||||
stateMux: &sync.Mutex{},
|
||||
files: map[string][]byte{},
|
||||
}
|
||||
return sess
|
||||
}
|
||||
|
||||
func (sess *Session) PrivateKey(s string) *Session {
|
||||
sess.privateKey = s
|
||||
return sess
|
||||
}
|
||||
|
||||
func (sess *Session) Playbook(s string) *Session {
|
||||
sess.playbook = s
|
||||
return sess
|
||||
}
|
||||
|
||||
func (sess *Session) Inventory(s string) *Session {
|
||||
sess.inventory = s
|
||||
return sess
|
||||
}
|
||||
|
||||
func (sess *Session) AddFile(path string, data []byte) *Session {
|
||||
sess.files[path] = data
|
||||
return sess
|
||||
}
|
||||
|
||||
func (sess *Session) RemoveFile(path string) []byte {
|
||||
data := sess.files[path]
|
||||
delete(sess.files, path)
|
||||
return data
|
||||
}
|
||||
|
||||
func (sess *Session) Files(files map[string][]byte) *Session {
|
||||
sess.files = files
|
||||
return sess
|
||||
}
|
||||
|
||||
func (sess *Session) OutputWriter(w io.Writer) *Session {
|
||||
sess.outputWriter = w
|
||||
return sess
|
||||
}
|
||||
|
||||
func (sess *Session) Run(ctx context.Context) (err error) {
|
||||
var (
|
||||
tmpdir string
|
||||
)
|
||||
|
||||
sess.stateMux.Lock()
|
||||
if sess.isRunning {
|
||||
return errors.Errorf("playbook is already running")
|
||||
}
|
||||
sess.isRunning = true
|
||||
sess.stateMux.Unlock()
|
||||
defer func() {
|
||||
sess.stateMux.Lock()
|
||||
sess.isRunning = false
|
||||
sess.stateMux.Unlock()
|
||||
}()
|
||||
|
||||
// make tmpdir
|
||||
tmpdir, err = ioutil.TempDir("", "onecloud-ansiblev2")
|
||||
if err != nil {
|
||||
err = errors.WithMessage(err, "making tmp dir")
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if sess.keepTmpdir {
|
||||
return
|
||||
}
|
||||
if err1 := os.RemoveAll(tmpdir); err1 != nil {
|
||||
err = errors.WithMessagef(err1, "removing %q", tmpdir)
|
||||
}
|
||||
}()
|
||||
|
||||
// write out inventory
|
||||
inventory := filepath.Join(tmpdir, "inventory")
|
||||
err = ioutil.WriteFile(inventory, []byte(sess.inventory), os.FileMode(0600))
|
||||
if err != nil {
|
||||
err = errors.WithMessagef(err, "writing inventory %s", inventory)
|
||||
return
|
||||
}
|
||||
|
||||
// write out playbook
|
||||
playbook := filepath.Join(tmpdir, "playbook")
|
||||
err = ioutil.WriteFile(playbook, []byte(sess.playbook), os.FileMode(0600))
|
||||
if err != nil {
|
||||
err = errors.WithMessagef(err, "writing playbook %s", playbook)
|
||||
return
|
||||
}
|
||||
|
||||
// write out private key
|
||||
var privateKey string
|
||||
if len(sess.privateKey) > 0 {
|
||||
privateKey = filepath.Join(tmpdir, "private_key")
|
||||
err = ioutil.WriteFile(privateKey, []byte(sess.privateKey), os.FileMode(0600))
|
||||
if err != nil {
|
||||
err = errors.WithMessagef(err, "writing private key %s", privateKey)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// write out files
|
||||
for name, content := range sess.files {
|
||||
path := filepath.Join(tmpdir, name)
|
||||
dir := filepath.Dir(path)
|
||||
err = os.MkdirAll(dir, os.FileMode(0700))
|
||||
if err != nil {
|
||||
err = errors.WithMessagef(err, "mkdir -p %s", dir)
|
||||
return
|
||||
}
|
||||
err = ioutil.WriteFile(path, content, os.FileMode(0600))
|
||||
if err != nil {
|
||||
err = errors.WithMessagef(err, "writing file %s", name)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// run modules one by one
|
||||
var errs []error
|
||||
defer func() {
|
||||
if len(errs) > 0 {
|
||||
err = yerrors.NewAggregate(errs)
|
||||
}
|
||||
}()
|
||||
|
||||
{
|
||||
args := []string{
|
||||
"--inventory", inventory,
|
||||
}
|
||||
if privateKey != "" {
|
||||
args = append(args, "--private-key", privateKey)
|
||||
}
|
||||
args = append(args, playbook)
|
||||
cmd := exec.CommandContext(ctx, "ansible-playbook", args...)
|
||||
cmd.Dir = tmpdir
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Env = append(cmd.Env, "ANSIBLE_HOST_KEY_CHECKING=False")
|
||||
stdout, _ := cmd.StdoutPipe()
|
||||
stderr, _ := cmd.StderrPipe()
|
||||
if err1 := cmd.Start(); err1 != nil {
|
||||
errs = append(errs, errors.WithMessagef(err1, "start playbook %s", playbook))
|
||||
return
|
||||
}
|
||||
// Mix stdout, stderr
|
||||
if sess.outputWriter != nil {
|
||||
go io.Copy(sess.outputWriter, stdout)
|
||||
go io.Copy(sess.outputWriter, stderr)
|
||||
}
|
||||
if err1 := cmd.Wait(); err1 != nil {
|
||||
errs = append(errs, errors.WithMessagef(err1, "wait playbook %s", playbook))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package firewalld
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
type Direct struct {
|
||||
Rules []*Rule
|
||||
|
||||
XMLName struct{} `xml:"direct"`
|
||||
}
|
||||
|
||||
func (d *Direct) String() string {
|
||||
data, _ := xml.MarshalIndent(d, "", " ")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
type Rule struct {
|
||||
// required, smaller the number more front the rule in chain
|
||||
Priority int `xml:"priority,attr"`
|
||||
// required, netfilter table: "nat", "mangle", etc.
|
||||
Table string `xml:"table,attr"`
|
||||
// required, ip family: "ipv4", "ipv6", "eb"
|
||||
IPv string `xml:"ipv,attr"`
|
||||
// required, netfilter chain: "FORWARD", custom chain names
|
||||
Chain string `xml:"chain,attr"`
|
||||
|
||||
// match and action command line options for {ip,ip6,eb}tables
|
||||
Body string `xml:",chardata"`
|
||||
|
||||
XMLName struct{} `xml:"rule"`
|
||||
}
|
||||
|
||||
func NewIP4Rule(prio int, table, chain, body string) *Rule {
|
||||
r := &Rule{
|
||||
Priority: prio,
|
||||
IPv: "ipv4",
|
||||
Table: table,
|
||||
Chain: chain,
|
||||
Body: body,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Rule) String() string {
|
||||
data, _ := xml.MarshalIndent(r, "", " ")
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func NewDirect(rules ...*Rule) *Direct {
|
||||
d := &Direct{
|
||||
Rules: rules,
|
||||
}
|
||||
return d
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package firewalld
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDirect(t *testing.T) {
|
||||
d := NewDirect(
|
||||
NewIP4Rule(0, "nat", "PREROUTING", "-j DNAT --to-destination 10.168.222.99:1099"),
|
||||
NewIP4Rule(0, "nat", "POSTROUTING", "-j SNAT --to-source :80"),
|
||||
NewIP4Rule(100, "nat", "POSTROUTING", "-j wg0 -j MASQUERADE"),
|
||||
)
|
||||
ruleWant := []string{
|
||||
`<rule priority="0" table="nat" ipv="ipv4" chain="PREROUTING">-j DNAT --to-destination 10.168.222.99:1099</rule>`,
|
||||
`<rule priority="0" table="nat" ipv="ipv4" chain="POSTROUTING">-j SNAT --to-source :80</rule>`,
|
||||
`<rule priority="100" table="nat" ipv="ipv4" chain="POSTROUTING">-j wg0 -j MASQUERADE</rule>`,
|
||||
}
|
||||
for i, r := range d.Rules {
|
||||
got := r.String()
|
||||
want := ruleWant[i]
|
||||
if got != want {
|
||||
t.Errorf("rule %d\n got\n %s\n want\n %s", i, got, want)
|
||||
}
|
||||
}
|
||||
want := `<direct>
|
||||
<rule priority="0" table="nat" ipv="ipv4" chain="PREROUTING">-j DNAT --to-destination 10.168.222.99:1099</rule>
|
||||
<rule priority="0" table="nat" ipv="ipv4" chain="POSTROUTING">-j SNAT --to-source :80</rule>
|
||||
<rule priority="100" table="nat" ipv="ipv4" chain="POSTROUTING">-j wg0 -j MASQUERADE</rule>
|
||||
</direct>`
|
||||
got := d.String()
|
||||
if got != want {
|
||||
t.Errorf("direct:\n got:\n %s\n want:\n %s", got, want)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package firewalld // import "yunion.io/x/onecloud/pkg/util/firewalld"
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// +godefs map struct_in_addr [4]byte /* in_addr */
|
||||
// +godefs map struct_in6_addr [16]byte /* in6_addr */
|
||||
|
||||
package socket
|
||||
|
||||
/*
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
sysAF_UNSPEC = C.AF_UNSPEC
|
||||
sysAF_INET = C.AF_INET
|
||||
sysAF_INET6 = C.AF_INET6
|
||||
|
||||
sysSOCK_RAW = C.SOCK_RAW
|
||||
)
|
||||
|
||||
type iovec C.struct_iovec
|
||||
|
||||
type msghdr C.struct_msghdr
|
||||
|
||||
type cmsghdr C.struct_cmsghdr
|
||||
|
||||
type sockaddrInet C.struct_sockaddr_in
|
||||
|
||||
type sockaddrInet6 C.struct_sockaddr_in6
|
||||
|
||||
const (
|
||||
sizeofIovec = C.sizeof_struct_iovec
|
||||
sizeofMsghdr = C.sizeof_struct_msghdr
|
||||
sizeofCmsghdr = C.sizeof_struct_cmsghdr
|
||||
|
||||
sizeofSockaddrInet = C.sizeof_struct_sockaddr_in
|
||||
sizeofSockaddrInet6 = C.sizeof_struct_sockaddr_in6
|
||||
)
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// +godefs map struct_in_addr [4]byte /* in_addr */
|
||||
// +godefs map struct_in6_addr [16]byte /* in6_addr */
|
||||
|
||||
package socket
|
||||
|
||||
/*
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
sysAF_UNSPEC = C.AF_UNSPEC
|
||||
sysAF_INET = C.AF_INET
|
||||
sysAF_INET6 = C.AF_INET6
|
||||
|
||||
sysSOCK_RAW = C.SOCK_RAW
|
||||
)
|
||||
|
||||
type iovec C.struct_iovec
|
||||
|
||||
type msghdr C.struct_msghdr
|
||||
|
||||
type cmsghdr C.struct_cmsghdr
|
||||
|
||||
type sockaddrInet C.struct_sockaddr_in
|
||||
|
||||
type sockaddrInet6 C.struct_sockaddr_in6
|
||||
|
||||
const (
|
||||
sizeofIovec = C.sizeof_struct_iovec
|
||||
sizeofMsghdr = C.sizeof_struct_msghdr
|
||||
sizeofCmsghdr = C.sizeof_struct_cmsghdr
|
||||
|
||||
sizeofSockaddrInet = C.sizeof_struct_sockaddr_in
|
||||
sizeofSockaddrInet6 = C.sizeof_struct_sockaddr_in6
|
||||
)
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// +godefs map struct_in_addr [4]byte /* in_addr */
|
||||
// +godefs map struct_in6_addr [16]byte /* in6_addr */
|
||||
|
||||
package socket
|
||||
|
||||
/*
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
sysAF_UNSPEC = C.AF_UNSPEC
|
||||
sysAF_INET = C.AF_INET
|
||||
sysAF_INET6 = C.AF_INET6
|
||||
|
||||
sysSOCK_RAW = C.SOCK_RAW
|
||||
)
|
||||
|
||||
type iovec C.struct_iovec
|
||||
|
||||
type msghdr C.struct_msghdr
|
||||
|
||||
type cmsghdr C.struct_cmsghdr
|
||||
|
||||
type sockaddrInet C.struct_sockaddr_in
|
||||
|
||||
type sockaddrInet6 C.struct_sockaddr_in6
|
||||
|
||||
const (
|
||||
sizeofIovec = C.sizeof_struct_iovec
|
||||
sizeofMsghdr = C.sizeof_struct_msghdr
|
||||
sizeofCmsghdr = C.sizeof_struct_cmsghdr
|
||||
|
||||
sizeofSockaddrInet = C.sizeof_struct_sockaddr_in
|
||||
sizeofSockaddrInet6 = C.sizeof_struct_sockaddr_in6
|
||||
)
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// +godefs map struct_in_addr [4]byte /* in_addr */
|
||||
// +godefs map struct_in6_addr [16]byte /* in6_addr */
|
||||
|
||||
package socket
|
||||
|
||||
/*
|
||||
#include <linux/in.h>
|
||||
#include <linux/in6.h>
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/socket.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
sysAF_UNSPEC = C.AF_UNSPEC
|
||||
sysAF_INET = C.AF_INET
|
||||
sysAF_INET6 = C.AF_INET6
|
||||
|
||||
sysSOCK_RAW = C.SOCK_RAW
|
||||
)
|
||||
|
||||
type iovec C.struct_iovec
|
||||
|
||||
type msghdr C.struct_msghdr
|
||||
|
||||
type mmsghdr C.struct_mmsghdr
|
||||
|
||||
type cmsghdr C.struct_cmsghdr
|
||||
|
||||
type sockaddrInet C.struct_sockaddr_in
|
||||
|
||||
type sockaddrInet6 C.struct_sockaddr_in6
|
||||
|
||||
const (
|
||||
sizeofIovec = C.sizeof_struct_iovec
|
||||
sizeofMsghdr = C.sizeof_struct_msghdr
|
||||
sizeofMmsghdr = C.sizeof_struct_mmsghdr
|
||||
sizeofCmsghdr = C.sizeof_struct_cmsghdr
|
||||
|
||||
sizeofSockaddrInet = C.sizeof_struct_sockaddr_in
|
||||
sizeofSockaddrInet6 = C.sizeof_struct_sockaddr_in6
|
||||
)
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// +godefs map struct_in_addr [4]byte /* in_addr */
|
||||
// +godefs map struct_in6_addr [16]byte /* in6_addr */
|
||||
|
||||
package socket
|
||||
|
||||
/*
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
sysAF_UNSPEC = C.AF_UNSPEC
|
||||
sysAF_INET = C.AF_INET
|
||||
sysAF_INET6 = C.AF_INET6
|
||||
|
||||
sysSOCK_RAW = C.SOCK_RAW
|
||||
)
|
||||
|
||||
type iovec C.struct_iovec
|
||||
|
||||
type msghdr C.struct_msghdr
|
||||
|
||||
type mmsghdr C.struct_mmsghdr
|
||||
|
||||
type cmsghdr C.struct_cmsghdr
|
||||
|
||||
type sockaddrInet C.struct_sockaddr_in
|
||||
|
||||
type sockaddrInet6 C.struct_sockaddr_in6
|
||||
|
||||
const (
|
||||
sizeofIovec = C.sizeof_struct_iovec
|
||||
sizeofMsghdr = C.sizeof_struct_msghdr
|
||||
sizeofMmsghdr = C.sizeof_struct_mmsghdr
|
||||
sizeofCmsghdr = C.sizeof_struct_cmsghdr
|
||||
|
||||
sizeofSockaddrInet = C.sizeof_struct_sockaddr_in
|
||||
sizeofSockaddrInet6 = C.sizeof_struct_sockaddr_in6
|
||||
)
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// +godefs map struct_in_addr [4]byte /* in_addr */
|
||||
// +godefs map struct_in6_addr [16]byte /* in6_addr */
|
||||
|
||||
package socket
|
||||
|
||||
/*
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
sysAF_UNSPEC = C.AF_UNSPEC
|
||||
sysAF_INET = C.AF_INET
|
||||
sysAF_INET6 = C.AF_INET6
|
||||
|
||||
sysSOCK_RAW = C.SOCK_RAW
|
||||
)
|
||||
|
||||
type iovec C.struct_iovec
|
||||
|
||||
type msghdr C.struct_msghdr
|
||||
|
||||
type cmsghdr C.struct_cmsghdr
|
||||
|
||||
type sockaddrInet C.struct_sockaddr_in
|
||||
|
||||
type sockaddrInet6 C.struct_sockaddr_in6
|
||||
|
||||
const (
|
||||
sizeofIovec = C.sizeof_struct_iovec
|
||||
sizeofMsghdr = C.sizeof_struct_msghdr
|
||||
sizeofCmsghdr = C.sizeof_struct_cmsghdr
|
||||
|
||||
sizeofSockaddrInet = C.sizeof_struct_sockaddr_in
|
||||
sizeofSockaddrInet6 = C.sizeof_struct_sockaddr_in6
|
||||
)
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// +godefs map struct_in_addr [4]byte /* in_addr */
|
||||
// +godefs map struct_in6_addr [16]byte /* in6_addr */
|
||||
|
||||
package socket
|
||||
|
||||
/*
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
sysAF_UNSPEC = C.AF_UNSPEC
|
||||
sysAF_INET = C.AF_INET
|
||||
sysAF_INET6 = C.AF_INET6
|
||||
|
||||
sysSOCK_RAW = C.SOCK_RAW
|
||||
)
|
||||
|
||||
type iovec C.struct_iovec
|
||||
|
||||
type msghdr C.struct_msghdr
|
||||
|
||||
type cmsghdr C.struct_cmsghdr
|
||||
|
||||
type sockaddrInet C.struct_sockaddr_in
|
||||
|
||||
type sockaddrInet6 C.struct_sockaddr_in6
|
||||
|
||||
const (
|
||||
sizeofIovec = C.sizeof_struct_iovec
|
||||
sizeofMsghdr = C.sizeof_struct_msghdr
|
||||
sizeofCmsghdr = C.sizeof_struct_cmsghdr
|
||||
|
||||
sizeofSockaddrInet = C.sizeof_struct_sockaddr_in
|
||||
sizeofSockaddrInet6 = C.sizeof_struct_sockaddr_in6
|
||||
)
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
||||
- 1.9
|
||||
- tip
|
||||
|
||||
go_import_path: gopkg.in/yaml.v2
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
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.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
The following files were ported to Go from C files of libyaml, and thus
|
||||
are still covered by their original copyright and license:
|
||||
|
||||
apic.go
|
||||
emitterc.go
|
||||
parserc.go
|
||||
readerc.go
|
||||
scannerc.go
|
||||
writerc.go
|
||||
yamlh.go
|
||||
yamlprivateh.go
|
||||
|
||||
Copyright (c) 2006 Kirill Simonov
|
||||
|
||||
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.
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
Copyright 2011-2016 Canonical Ltd.
|
||||
|
||||
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.
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
# YAML support for the Go language
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
The yaml package enables Go programs to comfortably encode and decode YAML
|
||||
values. It was developed within [Canonical](https://www.canonical.com) as
|
||||
part of the [juju](https://juju.ubuntu.com) project, and is based on a
|
||||
pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML)
|
||||
C library to parse and generate YAML data quickly and reliably.
|
||||
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
The yaml package supports most of YAML 1.1 and 1.2, including support for
|
||||
anchors, tags, map merging, etc. Multi-document unmarshalling is not yet
|
||||
implemented, and base-60 floats from YAML 1.1 are purposefully not
|
||||
supported since they're a poor design and are gone in YAML 1.2.
|
||||
|
||||
Installation and usage
|
||||
----------------------
|
||||
|
||||
The import path for the package is *gopkg.in/yaml.v2*.
|
||||
|
||||
To install it, run:
|
||||
|
||||
go get gopkg.in/yaml.v2
|
||||
|
||||
API documentation
|
||||
-----------------
|
||||
|
||||
If opened in a browser, the import path itself leads to the API documentation:
|
||||
|
||||
* [https://gopkg.in/yaml.v2](https://gopkg.in/yaml.v2)
|
||||
|
||||
API stability
|
||||
-------------
|
||||
|
||||
The package API for yaml v2 will remain stable as described in [gopkg.in](https://gopkg.in).
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The yaml package is licensed under the Apache License 2.0. Please see the LICENSE file for details.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
Some more examples can be found in the "examples" folder.
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var data = `
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d: [3, 4]
|
||||
`
|
||||
|
||||
// Note: struct fields must be public in order for unmarshal to
|
||||
// correctly populate the data.
|
||||
type T struct {
|
||||
A string
|
||||
B struct {
|
||||
RenamedC int `yaml:"c"`
|
||||
D []int `yaml:",flow"`
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := T{}
|
||||
|
||||
err := yaml.Unmarshal([]byte(data), &t)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- t:\n%v\n\n", t)
|
||||
|
||||
d, err := yaml.Marshal(&t)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- t dump:\n%s\n\n", string(d))
|
||||
|
||||
m := make(map[interface{}]interface{})
|
||||
|
||||
err = yaml.Unmarshal([]byte(data), &m)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- m:\n%v\n\n", m)
|
||||
|
||||
d, err = yaml.Marshal(&m)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- m dump:\n%s\n\n", string(d))
|
||||
}
|
||||
```
|
||||
|
||||
This example will generate the following output:
|
||||
|
||||
```
|
||||
--- t:
|
||||
{Easy! {2 [3 4]}}
|
||||
|
||||
--- t dump:
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d: [3, 4]
|
||||
|
||||
|
||||
--- m:
|
||||
map[a:Easy! b:map[c:2 d:[3 4]]]
|
||||
|
||||
--- m dump:
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d:
|
||||
- 3
|
||||
- 4
|
||||
```
|
||||
|
||||
+739
@@ -0,0 +1,739 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) {
|
||||
//fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens))
|
||||
|
||||
// Check if we can move the queue at the beginning of the buffer.
|
||||
if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) {
|
||||
if parser.tokens_head != len(parser.tokens) {
|
||||
copy(parser.tokens, parser.tokens[parser.tokens_head:])
|
||||
}
|
||||
parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head]
|
||||
parser.tokens_head = 0
|
||||
}
|
||||
parser.tokens = append(parser.tokens, *token)
|
||||
if pos < 0 {
|
||||
return
|
||||
}
|
||||
copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:])
|
||||
parser.tokens[parser.tokens_head+pos] = *token
|
||||
}
|
||||
|
||||
// Create a new parser object.
|
||||
func yaml_parser_initialize(parser *yaml_parser_t) bool {
|
||||
*parser = yaml_parser_t{
|
||||
raw_buffer: make([]byte, 0, input_raw_buffer_size),
|
||||
buffer: make([]byte, 0, input_buffer_size),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Destroy a parser object.
|
||||
func yaml_parser_delete(parser *yaml_parser_t) {
|
||||
*parser = yaml_parser_t{}
|
||||
}
|
||||
|
||||
// String read handler.
|
||||
func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
|
||||
if parser.input_pos == len(parser.input) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(buffer, parser.input[parser.input_pos:])
|
||||
parser.input_pos += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Reader read handler.
|
||||
func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
|
||||
return parser.input_reader.Read(buffer)
|
||||
}
|
||||
|
||||
// Set a string input.
|
||||
func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) {
|
||||
if parser.read_handler != nil {
|
||||
panic("must set the input source only once")
|
||||
}
|
||||
parser.read_handler = yaml_string_read_handler
|
||||
parser.input = input
|
||||
parser.input_pos = 0
|
||||
}
|
||||
|
||||
// Set a file input.
|
||||
func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) {
|
||||
if parser.read_handler != nil {
|
||||
panic("must set the input source only once")
|
||||
}
|
||||
parser.read_handler = yaml_reader_read_handler
|
||||
parser.input_reader = r
|
||||
}
|
||||
|
||||
// Set the source encoding.
|
||||
func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) {
|
||||
if parser.encoding != yaml_ANY_ENCODING {
|
||||
panic("must set the encoding only once")
|
||||
}
|
||||
parser.encoding = encoding
|
||||
}
|
||||
|
||||
// Create a new emitter object.
|
||||
func yaml_emitter_initialize(emitter *yaml_emitter_t) {
|
||||
*emitter = yaml_emitter_t{
|
||||
buffer: make([]byte, output_buffer_size),
|
||||
raw_buffer: make([]byte, 0, output_raw_buffer_size),
|
||||
states: make([]yaml_emitter_state_t, 0, initial_stack_size),
|
||||
events: make([]yaml_event_t, 0, initial_queue_size),
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy an emitter object.
|
||||
func yaml_emitter_delete(emitter *yaml_emitter_t) {
|
||||
*emitter = yaml_emitter_t{}
|
||||
}
|
||||
|
||||
// String write handler.
|
||||
func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
|
||||
*emitter.output_buffer = append(*emitter.output_buffer, buffer...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// yaml_writer_write_handler uses emitter.output_writer to write the
|
||||
// emitted text.
|
||||
func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
|
||||
_, err := emitter.output_writer.Write(buffer)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set a string output.
|
||||
func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) {
|
||||
if emitter.write_handler != nil {
|
||||
panic("must set the output target only once")
|
||||
}
|
||||
emitter.write_handler = yaml_string_write_handler
|
||||
emitter.output_buffer = output_buffer
|
||||
}
|
||||
|
||||
// Set a file output.
|
||||
func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) {
|
||||
if emitter.write_handler != nil {
|
||||
panic("must set the output target only once")
|
||||
}
|
||||
emitter.write_handler = yaml_writer_write_handler
|
||||
emitter.output_writer = w
|
||||
}
|
||||
|
||||
// Set the output encoding.
|
||||
func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) {
|
||||
if emitter.encoding != yaml_ANY_ENCODING {
|
||||
panic("must set the output encoding only once")
|
||||
}
|
||||
emitter.encoding = encoding
|
||||
}
|
||||
|
||||
// Set the canonical output style.
|
||||
func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) {
|
||||
emitter.canonical = canonical
|
||||
}
|
||||
|
||||
//// Set the indentation increment.
|
||||
func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) {
|
||||
if indent < 2 || indent > 9 {
|
||||
indent = 2
|
||||
}
|
||||
emitter.best_indent = indent
|
||||
}
|
||||
|
||||
// Set the preferred line width.
|
||||
func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) {
|
||||
if width < 0 {
|
||||
width = -1
|
||||
}
|
||||
emitter.best_width = width
|
||||
}
|
||||
|
||||
// Set if unescaped non-ASCII characters are allowed.
|
||||
func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) {
|
||||
emitter.unicode = unicode
|
||||
}
|
||||
|
||||
// Set the preferred line break character.
|
||||
func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) {
|
||||
emitter.line_break = line_break
|
||||
}
|
||||
|
||||
///*
|
||||
// * Destroy a token object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(void)
|
||||
//yaml_token_delete(yaml_token_t *token)
|
||||
//{
|
||||
// assert(token); // Non-NULL token object expected.
|
||||
//
|
||||
// switch (token.type)
|
||||
// {
|
||||
// case YAML_TAG_DIRECTIVE_TOKEN:
|
||||
// yaml_free(token.data.tag_directive.handle);
|
||||
// yaml_free(token.data.tag_directive.prefix);
|
||||
// break;
|
||||
//
|
||||
// case YAML_ALIAS_TOKEN:
|
||||
// yaml_free(token.data.alias.value);
|
||||
// break;
|
||||
//
|
||||
// case YAML_ANCHOR_TOKEN:
|
||||
// yaml_free(token.data.anchor.value);
|
||||
// break;
|
||||
//
|
||||
// case YAML_TAG_TOKEN:
|
||||
// yaml_free(token.data.tag.handle);
|
||||
// yaml_free(token.data.tag.suffix);
|
||||
// break;
|
||||
//
|
||||
// case YAML_SCALAR_TOKEN:
|
||||
// yaml_free(token.data.scalar.value);
|
||||
// break;
|
||||
//
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// memset(token, 0, sizeof(yaml_token_t));
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Check if a string is a valid UTF-8 sequence.
|
||||
// *
|
||||
// * Check 'reader.c' for more details on UTF-8 encoding.
|
||||
// */
|
||||
//
|
||||
//static int
|
||||
//yaml_check_utf8(yaml_char_t *start, size_t length)
|
||||
//{
|
||||
// yaml_char_t *end = start+length;
|
||||
// yaml_char_t *pointer = start;
|
||||
//
|
||||
// while (pointer < end) {
|
||||
// unsigned char octet;
|
||||
// unsigned int width;
|
||||
// unsigned int value;
|
||||
// size_t k;
|
||||
//
|
||||
// octet = pointer[0];
|
||||
// width = (octet & 0x80) == 0x00 ? 1 :
|
||||
// (octet & 0xE0) == 0xC0 ? 2 :
|
||||
// (octet & 0xF0) == 0xE0 ? 3 :
|
||||
// (octet & 0xF8) == 0xF0 ? 4 : 0;
|
||||
// value = (octet & 0x80) == 0x00 ? octet & 0x7F :
|
||||
// (octet & 0xE0) == 0xC0 ? octet & 0x1F :
|
||||
// (octet & 0xF0) == 0xE0 ? octet & 0x0F :
|
||||
// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0;
|
||||
// if (!width) return 0;
|
||||
// if (pointer+width > end) return 0;
|
||||
// for (k = 1; k < width; k ++) {
|
||||
// octet = pointer[k];
|
||||
// if ((octet & 0xC0) != 0x80) return 0;
|
||||
// value = (value << 6) + (octet & 0x3F);
|
||||
// }
|
||||
// if (!((width == 1) ||
|
||||
// (width == 2 && value >= 0x80) ||
|
||||
// (width == 3 && value >= 0x800) ||
|
||||
// (width == 4 && value >= 0x10000))) return 0;
|
||||
//
|
||||
// pointer += width;
|
||||
// }
|
||||
//
|
||||
// return 1;
|
||||
//}
|
||||
//
|
||||
|
||||
// Create STREAM-START.
|
||||
func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_STREAM_START_EVENT,
|
||||
encoding: encoding,
|
||||
}
|
||||
}
|
||||
|
||||
// Create STREAM-END.
|
||||
func yaml_stream_end_event_initialize(event *yaml_event_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_STREAM_END_EVENT,
|
||||
}
|
||||
}
|
||||
|
||||
// Create DOCUMENT-START.
|
||||
func yaml_document_start_event_initialize(
|
||||
event *yaml_event_t,
|
||||
version_directive *yaml_version_directive_t,
|
||||
tag_directives []yaml_tag_directive_t,
|
||||
implicit bool,
|
||||
) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_DOCUMENT_START_EVENT,
|
||||
version_directive: version_directive,
|
||||
tag_directives: tag_directives,
|
||||
implicit: implicit,
|
||||
}
|
||||
}
|
||||
|
||||
// Create DOCUMENT-END.
|
||||
func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_DOCUMENT_END_EVENT,
|
||||
implicit: implicit,
|
||||
}
|
||||
}
|
||||
|
||||
///*
|
||||
// * Create ALIAS.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_alias_event_initialize(event *yaml_event_t, anchor *yaml_char_t)
|
||||
//{
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// anchor_copy *yaml_char_t = NULL
|
||||
//
|
||||
// assert(event) // Non-NULL event object is expected.
|
||||
// assert(anchor) // Non-NULL anchor is expected.
|
||||
//
|
||||
// if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0
|
||||
//
|
||||
// anchor_copy = yaml_strdup(anchor)
|
||||
// if (!anchor_copy)
|
||||
// return 0
|
||||
//
|
||||
// ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark)
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
|
||||
// Create SCALAR.
|
||||
func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SCALAR_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
value: value,
|
||||
implicit: plain_implicit,
|
||||
quoted_implicit: quoted_implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create SEQUENCE-START.
|
||||
func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SEQUENCE_START_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
implicit: implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create SEQUENCE-END.
|
||||
func yaml_sequence_end_event_initialize(event *yaml_event_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SEQUENCE_END_EVENT,
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create MAPPING-START.
|
||||
func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_MAPPING_START_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
implicit: implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
}
|
||||
|
||||
// Create MAPPING-END.
|
||||
func yaml_mapping_end_event_initialize(event *yaml_event_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_MAPPING_END_EVENT,
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy an event object.
|
||||
func yaml_event_delete(event *yaml_event_t) {
|
||||
*event = yaml_event_t{}
|
||||
}
|
||||
|
||||
///*
|
||||
// * Create a document object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_initialize(document *yaml_document_t,
|
||||
// version_directive *yaml_version_directive_t,
|
||||
// tag_directives_start *yaml_tag_directive_t,
|
||||
// tag_directives_end *yaml_tag_directive_t,
|
||||
// start_implicit int, end_implicit int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// struct {
|
||||
// start *yaml_node_t
|
||||
// end *yaml_node_t
|
||||
// top *yaml_node_t
|
||||
// } nodes = { NULL, NULL, NULL }
|
||||
// version_directive_copy *yaml_version_directive_t = NULL
|
||||
// struct {
|
||||
// start *yaml_tag_directive_t
|
||||
// end *yaml_tag_directive_t
|
||||
// top *yaml_tag_directive_t
|
||||
// } tag_directives_copy = { NULL, NULL, NULL }
|
||||
// value yaml_tag_directive_t = { NULL, NULL }
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
// assert((tag_directives_start && tag_directives_end) ||
|
||||
// (tag_directives_start == tag_directives_end))
|
||||
// // Valid tag directives are expected.
|
||||
//
|
||||
// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// if (version_directive) {
|
||||
// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t))
|
||||
// if (!version_directive_copy) goto error
|
||||
// version_directive_copy.major = version_directive.major
|
||||
// version_directive_copy.minor = version_directive.minor
|
||||
// }
|
||||
//
|
||||
// if (tag_directives_start != tag_directives_end) {
|
||||
// tag_directive *yaml_tag_directive_t
|
||||
// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE))
|
||||
// goto error
|
||||
// for (tag_directive = tag_directives_start
|
||||
// tag_directive != tag_directives_end; tag_directive ++) {
|
||||
// assert(tag_directive.handle)
|
||||
// assert(tag_directive.prefix)
|
||||
// if (!yaml_check_utf8(tag_directive.handle,
|
||||
// strlen((char *)tag_directive.handle)))
|
||||
// goto error
|
||||
// if (!yaml_check_utf8(tag_directive.prefix,
|
||||
// strlen((char *)tag_directive.prefix)))
|
||||
// goto error
|
||||
// value.handle = yaml_strdup(tag_directive.handle)
|
||||
// value.prefix = yaml_strdup(tag_directive.prefix)
|
||||
// if (!value.handle || !value.prefix) goto error
|
||||
// if (!PUSH(&context, tag_directives_copy, value))
|
||||
// goto error
|
||||
// value.handle = NULL
|
||||
// value.prefix = NULL
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy,
|
||||
// tag_directives_copy.start, tag_directives_copy.top,
|
||||
// start_implicit, end_implicit, mark, mark)
|
||||
//
|
||||
// return 1
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, nodes)
|
||||
// yaml_free(version_directive_copy)
|
||||
// while (!STACK_EMPTY(&context, tag_directives_copy)) {
|
||||
// value yaml_tag_directive_t = POP(&context, tag_directives_copy)
|
||||
// yaml_free(value.handle)
|
||||
// yaml_free(value.prefix)
|
||||
// }
|
||||
// STACK_DEL(&context, tag_directives_copy)
|
||||
// yaml_free(value.handle)
|
||||
// yaml_free(value.prefix)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Destroy a document object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(void)
|
||||
//yaml_document_delete(document *yaml_document_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// tag_directive *yaml_tag_directive_t
|
||||
//
|
||||
// context.error = YAML_NO_ERROR // Eliminate a compliler warning.
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// while (!STACK_EMPTY(&context, document.nodes)) {
|
||||
// node yaml_node_t = POP(&context, document.nodes)
|
||||
// yaml_free(node.tag)
|
||||
// switch (node.type) {
|
||||
// case YAML_SCALAR_NODE:
|
||||
// yaml_free(node.data.scalar.value)
|
||||
// break
|
||||
// case YAML_SEQUENCE_NODE:
|
||||
// STACK_DEL(&context, node.data.sequence.items)
|
||||
// break
|
||||
// case YAML_MAPPING_NODE:
|
||||
// STACK_DEL(&context, node.data.mapping.pairs)
|
||||
// break
|
||||
// default:
|
||||
// assert(0) // Should not happen.
|
||||
// }
|
||||
// }
|
||||
// STACK_DEL(&context, document.nodes)
|
||||
//
|
||||
// yaml_free(document.version_directive)
|
||||
// for (tag_directive = document.tag_directives.start
|
||||
// tag_directive != document.tag_directives.end
|
||||
// tag_directive++) {
|
||||
// yaml_free(tag_directive.handle)
|
||||
// yaml_free(tag_directive.prefix)
|
||||
// }
|
||||
// yaml_free(document.tag_directives.start)
|
||||
//
|
||||
// memset(document, 0, sizeof(yaml_document_t))
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Get a document node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(yaml_node_t *)
|
||||
//yaml_document_get_node(document *yaml_document_t, index int)
|
||||
//{
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (index > 0 && document.nodes.start + index <= document.nodes.top) {
|
||||
// return document.nodes.start + index - 1
|
||||
// }
|
||||
// return NULL
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Get the root object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(yaml_node_t *)
|
||||
//yaml_document_get_root_node(document *yaml_document_t)
|
||||
//{
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (document.nodes.top != document.nodes.start) {
|
||||
// return document.nodes.start
|
||||
// }
|
||||
// return NULL
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a scalar node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_scalar(document *yaml_document_t,
|
||||
// tag *yaml_char_t, value *yaml_char_t, length int,
|
||||
// style yaml_scalar_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// value_copy *yaml_char_t = NULL
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
// assert(value) // Non-NULL value is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (length < 0) {
|
||||
// length = strlen((char *)value)
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(value, length)) goto error
|
||||
// value_copy = yaml_malloc(length+1)
|
||||
// if (!value_copy) goto error
|
||||
// memcpy(value_copy, value, length)
|
||||
// value_copy[length] = '\0'
|
||||
//
|
||||
// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// yaml_free(tag_copy)
|
||||
// yaml_free(value_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a sequence node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_sequence(document *yaml_document_t,
|
||||
// tag *yaml_char_t, style yaml_sequence_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// struct {
|
||||
// start *yaml_node_item_t
|
||||
// end *yaml_node_item_t
|
||||
// top *yaml_node_item_t
|
||||
// } items = { NULL, NULL, NULL }
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end,
|
||||
// style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, items)
|
||||
// yaml_free(tag_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a mapping node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_mapping(document *yaml_document_t,
|
||||
// tag *yaml_char_t, style yaml_mapping_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// struct {
|
||||
// start *yaml_node_pair_t
|
||||
// end *yaml_node_pair_t
|
||||
// top *yaml_node_pair_t
|
||||
// } pairs = { NULL, NULL, NULL }
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end,
|
||||
// style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, pairs)
|
||||
// yaml_free(tag_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Append an item to a sequence node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_append_sequence_item(document *yaml_document_t,
|
||||
// sequence int, item int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
//
|
||||
// assert(document) // Non-NULL document is required.
|
||||
// assert(sequence > 0
|
||||
// && document.nodes.start + sequence <= document.nodes.top)
|
||||
// // Valid sequence id is required.
|
||||
// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE)
|
||||
// // A sequence node is required.
|
||||
// assert(item > 0 && document.nodes.start + item <= document.nodes.top)
|
||||
// // Valid item id is required.
|
||||
//
|
||||
// if (!PUSH(&context,
|
||||
// document.nodes.start[sequence-1].data.sequence.items, item))
|
||||
// return 0
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Append a pair of a key and a value to a mapping node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_append_mapping_pair(document *yaml_document_t,
|
||||
// mapping int, key int, value int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
//
|
||||
// pair yaml_node_pair_t
|
||||
//
|
||||
// assert(document) // Non-NULL document is required.
|
||||
// assert(mapping > 0
|
||||
// && document.nodes.start + mapping <= document.nodes.top)
|
||||
// // Valid mapping id is required.
|
||||
// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE)
|
||||
// // A mapping node is required.
|
||||
// assert(key > 0 && document.nodes.start + key <= document.nodes.top)
|
||||
// // Valid key id is required.
|
||||
// assert(value > 0 && document.nodes.start + value <= document.nodes.top)
|
||||
// // Valid value id is required.
|
||||
//
|
||||
// pair.key = key
|
||||
// pair.value = value
|
||||
//
|
||||
// if (!PUSH(&context,
|
||||
// document.nodes.start[mapping-1].data.mapping.pairs, pair))
|
||||
// return 0
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
//
|
||||
//
|
||||
+764
@@ -0,0 +1,764 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
documentNode = 1 << iota
|
||||
mappingNode
|
||||
sequenceNode
|
||||
scalarNode
|
||||
aliasNode
|
||||
)
|
||||
|
||||
type node struct {
|
||||
kind int
|
||||
line, column int
|
||||
tag string
|
||||
// For an alias node, alias holds the resolved alias.
|
||||
alias *node
|
||||
value string
|
||||
implicit bool
|
||||
children []*node
|
||||
anchors map[string]*node
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Parser, produces a node tree out of a libyaml event stream.
|
||||
|
||||
type parser struct {
|
||||
parser yaml_parser_t
|
||||
event yaml_event_t
|
||||
doc *node
|
||||
doneInit bool
|
||||
}
|
||||
|
||||
func newParser(b []byte) *parser {
|
||||
p := parser{}
|
||||
if !yaml_parser_initialize(&p.parser) {
|
||||
panic("failed to initialize YAML emitter")
|
||||
}
|
||||
if len(b) == 0 {
|
||||
b = []byte{'\n'}
|
||||
}
|
||||
yaml_parser_set_input_string(&p.parser, b)
|
||||
return &p
|
||||
}
|
||||
|
||||
func newParserFromReader(r io.Reader) *parser {
|
||||
p := parser{}
|
||||
if !yaml_parser_initialize(&p.parser) {
|
||||
panic("failed to initialize YAML emitter")
|
||||
}
|
||||
yaml_parser_set_input_reader(&p.parser, r)
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *parser) init() {
|
||||
if p.doneInit {
|
||||
return
|
||||
}
|
||||
p.expect(yaml_STREAM_START_EVENT)
|
||||
p.doneInit = true
|
||||
}
|
||||
|
||||
func (p *parser) destroy() {
|
||||
if p.event.typ != yaml_NO_EVENT {
|
||||
yaml_event_delete(&p.event)
|
||||
}
|
||||
yaml_parser_delete(&p.parser)
|
||||
}
|
||||
|
||||
// expect consumes an event from the event stream and
|
||||
// checks that it's of the expected type.
|
||||
func (p *parser) expect(e yaml_event_type_t) {
|
||||
if p.event.typ == yaml_NO_EVENT {
|
||||
if !yaml_parser_parse(&p.parser, &p.event) {
|
||||
p.fail()
|
||||
}
|
||||
}
|
||||
if p.event.typ == yaml_STREAM_END_EVENT {
|
||||
failf("attempted to go past the end of stream; corrupted value?")
|
||||
}
|
||||
if p.event.typ != e {
|
||||
p.parser.problem = fmt.Sprintf("expected %s event but got %s", e, p.event.typ)
|
||||
p.fail()
|
||||
}
|
||||
yaml_event_delete(&p.event)
|
||||
p.event.typ = yaml_NO_EVENT
|
||||
}
|
||||
|
||||
// peek peeks at the next event in the event stream,
|
||||
// puts the results into p.event and returns the event type.
|
||||
func (p *parser) peek() yaml_event_type_t {
|
||||
if p.event.typ != yaml_NO_EVENT {
|
||||
return p.event.typ
|
||||
}
|
||||
if !yaml_parser_parse(&p.parser, &p.event) {
|
||||
p.fail()
|
||||
}
|
||||
return p.event.typ
|
||||
}
|
||||
|
||||
func (p *parser) fail() {
|
||||
var where string
|
||||
var line int
|
||||
if p.parser.problem_mark.line != 0 {
|
||||
line = p.parser.problem_mark.line
|
||||
} else if p.parser.context_mark.line != 0 {
|
||||
line = p.parser.context_mark.line
|
||||
}
|
||||
if line != 0 {
|
||||
where = "line " + strconv.Itoa(line) + ": "
|
||||
}
|
||||
var msg string
|
||||
if len(p.parser.problem) > 0 {
|
||||
msg = p.parser.problem
|
||||
} else {
|
||||
msg = "unknown problem parsing YAML content"
|
||||
}
|
||||
failf("%s%s", where, msg)
|
||||
}
|
||||
|
||||
func (p *parser) anchor(n *node, anchor []byte) {
|
||||
if anchor != nil {
|
||||
p.doc.anchors[string(anchor)] = n
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parse() *node {
|
||||
p.init()
|
||||
switch p.peek() {
|
||||
case yaml_SCALAR_EVENT:
|
||||
return p.scalar()
|
||||
case yaml_ALIAS_EVENT:
|
||||
return p.alias()
|
||||
case yaml_MAPPING_START_EVENT:
|
||||
return p.mapping()
|
||||
case yaml_SEQUENCE_START_EVENT:
|
||||
return p.sequence()
|
||||
case yaml_DOCUMENT_START_EVENT:
|
||||
return p.document()
|
||||
case yaml_STREAM_END_EVENT:
|
||||
// Happens when attempting to decode an empty buffer.
|
||||
return nil
|
||||
default:
|
||||
panic("attempted to parse unknown event: " + p.event.typ.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) node(kind int) *node {
|
||||
return &node{
|
||||
kind: kind,
|
||||
line: p.event.start_mark.line,
|
||||
column: p.event.start_mark.column,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) document() *node {
|
||||
n := p.node(documentNode)
|
||||
n.anchors = make(map[string]*node)
|
||||
p.doc = n
|
||||
p.expect(yaml_DOCUMENT_START_EVENT)
|
||||
n.children = append(n.children, p.parse())
|
||||
p.expect(yaml_DOCUMENT_END_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) alias() *node {
|
||||
n := p.node(aliasNode)
|
||||
n.value = string(p.event.anchor)
|
||||
n.alias = p.doc.anchors[n.value]
|
||||
if n.alias == nil {
|
||||
failf("unknown anchor '%s' referenced", n.value)
|
||||
}
|
||||
p.expect(yaml_ALIAS_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) scalar() *node {
|
||||
n := p.node(scalarNode)
|
||||
n.value = string(p.event.value)
|
||||
n.tag = string(p.event.tag)
|
||||
n.implicit = p.event.implicit
|
||||
p.anchor(n, p.event.anchor)
|
||||
p.expect(yaml_SCALAR_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) sequence() *node {
|
||||
n := p.node(sequenceNode)
|
||||
p.anchor(n, p.event.anchor)
|
||||
p.expect(yaml_SEQUENCE_START_EVENT)
|
||||
for p.peek() != yaml_SEQUENCE_END_EVENT {
|
||||
n.children = append(n.children, p.parse())
|
||||
}
|
||||
p.expect(yaml_SEQUENCE_END_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) mapping() *node {
|
||||
n := p.node(mappingNode)
|
||||
p.anchor(n, p.event.anchor)
|
||||
p.expect(yaml_MAPPING_START_EVENT)
|
||||
for p.peek() != yaml_MAPPING_END_EVENT {
|
||||
n.children = append(n.children, p.parse(), p.parse())
|
||||
}
|
||||
p.expect(yaml_MAPPING_END_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Decoder, unmarshals a node into a provided value.
|
||||
|
||||
type decoder struct {
|
||||
doc *node
|
||||
aliases map[*node]bool
|
||||
mapType reflect.Type
|
||||
terrors []string
|
||||
strict bool
|
||||
}
|
||||
|
||||
var (
|
||||
mapItemType = reflect.TypeOf(MapItem{})
|
||||
durationType = reflect.TypeOf(time.Duration(0))
|
||||
defaultMapType = reflect.TypeOf(map[interface{}]interface{}{})
|
||||
ifaceType = defaultMapType.Elem()
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
ptrTimeType = reflect.TypeOf(&time.Time{})
|
||||
)
|
||||
|
||||
func newDecoder(strict bool) *decoder {
|
||||
d := &decoder{mapType: defaultMapType, strict: strict}
|
||||
d.aliases = make(map[*node]bool)
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decoder) terror(n *node, tag string, out reflect.Value) {
|
||||
if n.tag != "" {
|
||||
tag = n.tag
|
||||
}
|
||||
value := n.value
|
||||
if tag != yaml_SEQ_TAG && tag != yaml_MAP_TAG {
|
||||
if len(value) > 10 {
|
||||
value = " `" + value[:7] + "...`"
|
||||
} else {
|
||||
value = " `" + value + "`"
|
||||
}
|
||||
}
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.line+1, shortTag(tag), value, out.Type()))
|
||||
}
|
||||
|
||||
func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) {
|
||||
terrlen := len(d.terrors)
|
||||
err := u.UnmarshalYAML(func(v interface{}) (err error) {
|
||||
defer handleErr(&err)
|
||||
d.unmarshal(n, reflect.ValueOf(v))
|
||||
if len(d.terrors) > terrlen {
|
||||
issues := d.terrors[terrlen:]
|
||||
d.terrors = d.terrors[:terrlen]
|
||||
return &TypeError{issues}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if e, ok := err.(*TypeError); ok {
|
||||
d.terrors = append(d.terrors, e.Errors...)
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// d.prepare initializes and dereferences pointers and calls UnmarshalYAML
|
||||
// if a value is found to implement it.
|
||||
// It returns the initialized and dereferenced out value, whether
|
||||
// unmarshalling was already done by UnmarshalYAML, and if so whether
|
||||
// its types unmarshalled appropriately.
|
||||
//
|
||||
// If n holds a null value, prepare returns before doing anything.
|
||||
func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) {
|
||||
if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "~" || n.value == "" && n.implicit) {
|
||||
return out, false, false
|
||||
}
|
||||
again := true
|
||||
for again {
|
||||
again = false
|
||||
if out.Kind() == reflect.Ptr {
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.New(out.Type().Elem()))
|
||||
}
|
||||
out = out.Elem()
|
||||
again = true
|
||||
}
|
||||
if out.CanAddr() {
|
||||
if u, ok := out.Addr().Interface().(Unmarshaler); ok {
|
||||
good = d.callUnmarshaler(n, u)
|
||||
return out, true, good
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, false, false
|
||||
}
|
||||
|
||||
func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) {
|
||||
switch n.kind {
|
||||
case documentNode:
|
||||
return d.document(n, out)
|
||||
case aliasNode:
|
||||
return d.alias(n, out)
|
||||
}
|
||||
out, unmarshaled, good := d.prepare(n, out)
|
||||
if unmarshaled {
|
||||
return good
|
||||
}
|
||||
switch n.kind {
|
||||
case scalarNode:
|
||||
good = d.scalar(n, out)
|
||||
case mappingNode:
|
||||
good = d.mapping(n, out)
|
||||
case sequenceNode:
|
||||
good = d.sequence(n, out)
|
||||
default:
|
||||
panic("internal error: unknown node kind: " + strconv.Itoa(n.kind))
|
||||
}
|
||||
return good
|
||||
}
|
||||
|
||||
func (d *decoder) document(n *node, out reflect.Value) (good bool) {
|
||||
if len(n.children) == 1 {
|
||||
d.doc = n
|
||||
d.unmarshal(n.children[0], out)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
|
||||
if d.aliases[n] {
|
||||
// TODO this could actually be allowed in some circumstances.
|
||||
failf("anchor '%s' value contains itself", n.value)
|
||||
}
|
||||
d.aliases[n] = true
|
||||
good = d.unmarshal(n.alias, out)
|
||||
delete(d.aliases, n)
|
||||
return good
|
||||
}
|
||||
|
||||
var zeroValue reflect.Value
|
||||
|
||||
func resetMap(out reflect.Value) {
|
||||
for _, k := range out.MapKeys() {
|
||||
out.SetMapIndex(k, zeroValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) scalar(n *node, out reflect.Value) bool {
|
||||
var tag string
|
||||
var resolved interface{}
|
||||
if n.tag == "" && !n.implicit {
|
||||
tag = yaml_STR_TAG
|
||||
resolved = n.value
|
||||
} else {
|
||||
tag, resolved = resolve(n.tag, n.value)
|
||||
if tag == yaml_BINARY_TAG {
|
||||
data, err := base64.StdEncoding.DecodeString(resolved.(string))
|
||||
if err != nil {
|
||||
failf("!!binary value contains invalid base64 data")
|
||||
}
|
||||
resolved = string(data)
|
||||
}
|
||||
}
|
||||
if resolved == nil {
|
||||
if out.Kind() == reflect.Map && !out.CanAddr() {
|
||||
resetMap(out)
|
||||
} else {
|
||||
out.Set(reflect.Zero(out.Type()))
|
||||
}
|
||||
return true
|
||||
}
|
||||
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
|
||||
// We've resolved to exactly the type we want, so use that.
|
||||
out.Set(resolvedv)
|
||||
return true
|
||||
}
|
||||
// Perhaps we can use the value as a TextUnmarshaler to
|
||||
// set its value.
|
||||
if out.CanAddr() {
|
||||
u, ok := out.Addr().Interface().(encoding.TextUnmarshaler)
|
||||
if ok {
|
||||
var text []byte
|
||||
if tag == yaml_BINARY_TAG {
|
||||
text = []byte(resolved.(string))
|
||||
} else {
|
||||
// We let any value be unmarshaled into TextUnmarshaler.
|
||||
// That might be more lax than we'd like, but the
|
||||
// TextUnmarshaler itself should bowl out any dubious values.
|
||||
text = []byte(n.value)
|
||||
}
|
||||
err := u.UnmarshalText(text)
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
switch out.Kind() {
|
||||
case reflect.String:
|
||||
if tag == yaml_BINARY_TAG {
|
||||
out.SetString(resolved.(string))
|
||||
return true
|
||||
}
|
||||
if resolved != nil {
|
||||
out.SetString(n.value)
|
||||
return true
|
||||
}
|
||||
case reflect.Interface:
|
||||
if resolved == nil {
|
||||
out.Set(reflect.Zero(out.Type()))
|
||||
} else if tag == yaml_TIMESTAMP_TAG {
|
||||
// It looks like a timestamp but for backward compatibility
|
||||
// reasons we set it as a string, so that code that unmarshals
|
||||
// timestamp-like values into interface{} will continue to
|
||||
// see a string and not a time.Time.
|
||||
out.Set(reflect.ValueOf(n.value))
|
||||
} else {
|
||||
out.Set(reflect.ValueOf(resolved))
|
||||
}
|
||||
return true
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
switch resolved := resolved.(type) {
|
||||
case int:
|
||||
if !out.OverflowInt(int64(resolved)) {
|
||||
out.SetInt(int64(resolved))
|
||||
return true
|
||||
}
|
||||
case int64:
|
||||
if !out.OverflowInt(resolved) {
|
||||
out.SetInt(resolved)
|
||||
return true
|
||||
}
|
||||
case uint64:
|
||||
if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
|
||||
out.SetInt(int64(resolved))
|
||||
return true
|
||||
}
|
||||
case float64:
|
||||
if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
|
||||
out.SetInt(int64(resolved))
|
||||
return true
|
||||
}
|
||||
case string:
|
||||
if out.Type() == durationType {
|
||||
d, err := time.ParseDuration(resolved)
|
||||
if err == nil {
|
||||
out.SetInt(int64(d))
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
switch resolved := resolved.(type) {
|
||||
case int:
|
||||
if resolved >= 0 && !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
case int64:
|
||||
if resolved >= 0 && !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
case uint64:
|
||||
if !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
case float64:
|
||||
if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
}
|
||||
case reflect.Bool:
|
||||
switch resolved := resolved.(type) {
|
||||
case bool:
|
||||
out.SetBool(resolved)
|
||||
return true
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
switch resolved := resolved.(type) {
|
||||
case int:
|
||||
out.SetFloat(float64(resolved))
|
||||
return true
|
||||
case int64:
|
||||
out.SetFloat(float64(resolved))
|
||||
return true
|
||||
case uint64:
|
||||
out.SetFloat(float64(resolved))
|
||||
return true
|
||||
case float64:
|
||||
out.SetFloat(resolved)
|
||||
return true
|
||||
}
|
||||
case reflect.Struct:
|
||||
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
|
||||
out.Set(resolvedv)
|
||||
return true
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if out.Type().Elem() == reflect.TypeOf(resolved) {
|
||||
// TODO DOes this make sense? When is out a Ptr except when decoding a nil value?
|
||||
elem := reflect.New(out.Type().Elem())
|
||||
elem.Elem().Set(reflect.ValueOf(resolved))
|
||||
out.Set(elem)
|
||||
return true
|
||||
}
|
||||
}
|
||||
d.terror(n, tag, out)
|
||||
return false
|
||||
}
|
||||
|
||||
func settableValueOf(i interface{}) reflect.Value {
|
||||
v := reflect.ValueOf(i)
|
||||
sv := reflect.New(v.Type()).Elem()
|
||||
sv.Set(v)
|
||||
return sv
|
||||
}
|
||||
|
||||
func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
|
||||
l := len(n.children)
|
||||
|
||||
var iface reflect.Value
|
||||
switch out.Kind() {
|
||||
case reflect.Slice:
|
||||
out.Set(reflect.MakeSlice(out.Type(), l, l))
|
||||
case reflect.Interface:
|
||||
// No type hints. Will have to use a generic sequence.
|
||||
iface = out
|
||||
out = settableValueOf(make([]interface{}, l))
|
||||
default:
|
||||
d.terror(n, yaml_SEQ_TAG, out)
|
||||
return false
|
||||
}
|
||||
et := out.Type().Elem()
|
||||
|
||||
j := 0
|
||||
for i := 0; i < l; i++ {
|
||||
e := reflect.New(et).Elem()
|
||||
if ok := d.unmarshal(n.children[i], e); ok {
|
||||
out.Index(j).Set(e)
|
||||
j++
|
||||
}
|
||||
}
|
||||
out.Set(out.Slice(0, j))
|
||||
if iface.IsValid() {
|
||||
iface.Set(out)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
|
||||
switch out.Kind() {
|
||||
case reflect.Struct:
|
||||
return d.mappingStruct(n, out)
|
||||
case reflect.Slice:
|
||||
return d.mappingSlice(n, out)
|
||||
case reflect.Map:
|
||||
// okay
|
||||
case reflect.Interface:
|
||||
if d.mapType.Kind() == reflect.Map {
|
||||
iface := out
|
||||
out = reflect.MakeMap(d.mapType)
|
||||
iface.Set(out)
|
||||
} else {
|
||||
slicev := reflect.New(d.mapType).Elem()
|
||||
if !d.mappingSlice(n, slicev) {
|
||||
return false
|
||||
}
|
||||
out.Set(slicev)
|
||||
return true
|
||||
}
|
||||
default:
|
||||
d.terror(n, yaml_MAP_TAG, out)
|
||||
return false
|
||||
}
|
||||
outt := out.Type()
|
||||
kt := outt.Key()
|
||||
et := outt.Elem()
|
||||
|
||||
mapType := d.mapType
|
||||
if outt.Key() == ifaceType && outt.Elem() == ifaceType {
|
||||
d.mapType = outt
|
||||
}
|
||||
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.MakeMap(outt))
|
||||
}
|
||||
l := len(n.children)
|
||||
for i := 0; i < l; i += 2 {
|
||||
if isMerge(n.children[i]) {
|
||||
d.merge(n.children[i+1], out)
|
||||
continue
|
||||
}
|
||||
k := reflect.New(kt).Elem()
|
||||
if d.unmarshal(n.children[i], k) {
|
||||
kkind := k.Kind()
|
||||
if kkind == reflect.Interface {
|
||||
kkind = k.Elem().Kind()
|
||||
}
|
||||
if kkind == reflect.Map || kkind == reflect.Slice {
|
||||
failf("invalid map key: %#v", k.Interface())
|
||||
}
|
||||
e := reflect.New(et).Elem()
|
||||
if d.unmarshal(n.children[i+1], e) {
|
||||
d.setMapIndex(n.children[i+1], out, k, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.mapType = mapType
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *decoder) setMapIndex(n *node, out, k, v reflect.Value) {
|
||||
if d.strict && out.MapIndex(k) != zeroValue {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: key %#v already set in map", n.line+1, k.Interface()))
|
||||
return
|
||||
}
|
||||
out.SetMapIndex(k, v)
|
||||
}
|
||||
|
||||
func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) {
|
||||
outt := out.Type()
|
||||
if outt.Elem() != mapItemType {
|
||||
d.terror(n, yaml_MAP_TAG, out)
|
||||
return false
|
||||
}
|
||||
|
||||
mapType := d.mapType
|
||||
d.mapType = outt
|
||||
|
||||
var slice []MapItem
|
||||
var l = len(n.children)
|
||||
for i := 0; i < l; i += 2 {
|
||||
if isMerge(n.children[i]) {
|
||||
d.merge(n.children[i+1], out)
|
||||
continue
|
||||
}
|
||||
item := MapItem{}
|
||||
k := reflect.ValueOf(&item.Key).Elem()
|
||||
if d.unmarshal(n.children[i], k) {
|
||||
v := reflect.ValueOf(&item.Value).Elem()
|
||||
if d.unmarshal(n.children[i+1], v) {
|
||||
slice = append(slice, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
out.Set(reflect.ValueOf(slice))
|
||||
d.mapType = mapType
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
|
||||
sinfo, err := getStructInfo(out.Type())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
name := settableValueOf("")
|
||||
l := len(n.children)
|
||||
|
||||
var inlineMap reflect.Value
|
||||
var elemType reflect.Type
|
||||
if sinfo.InlineMap != -1 {
|
||||
inlineMap = out.Field(sinfo.InlineMap)
|
||||
inlineMap.Set(reflect.New(inlineMap.Type()).Elem())
|
||||
elemType = inlineMap.Type().Elem()
|
||||
}
|
||||
|
||||
var doneFields []bool
|
||||
if d.strict {
|
||||
doneFields = make([]bool, len(sinfo.FieldsList))
|
||||
}
|
||||
for i := 0; i < l; i += 2 {
|
||||
ni := n.children[i]
|
||||
if isMerge(ni) {
|
||||
d.merge(n.children[i+1], out)
|
||||
continue
|
||||
}
|
||||
if !d.unmarshal(ni, name) {
|
||||
continue
|
||||
}
|
||||
if info, ok := sinfo.FieldsMap[name.String()]; ok {
|
||||
if d.strict {
|
||||
if doneFields[info.Id] {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.line+1, name.String(), out.Type()))
|
||||
continue
|
||||
}
|
||||
doneFields[info.Id] = true
|
||||
}
|
||||
var field reflect.Value
|
||||
if info.Inline == nil {
|
||||
field = out.Field(info.Num)
|
||||
} else {
|
||||
field = out.FieldByIndex(info.Inline)
|
||||
}
|
||||
d.unmarshal(n.children[i+1], field)
|
||||
} else if sinfo.InlineMap != -1 {
|
||||
if inlineMap.IsNil() {
|
||||
inlineMap.Set(reflect.MakeMap(inlineMap.Type()))
|
||||
}
|
||||
value := reflect.New(elemType).Elem()
|
||||
d.unmarshal(n.children[i+1], value)
|
||||
d.setMapIndex(n.children[i+1], inlineMap, name, value)
|
||||
} else if d.strict {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.line+1, name.String(), out.Type()))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func failWantMap() {
|
||||
failf("map merge requires map or sequence of maps as the value")
|
||||
}
|
||||
|
||||
func (d *decoder) merge(n *node, out reflect.Value) {
|
||||
switch n.kind {
|
||||
case mappingNode:
|
||||
d.unmarshal(n, out)
|
||||
case aliasNode:
|
||||
an, ok := d.doc.anchors[n.value]
|
||||
if ok && an.kind != mappingNode {
|
||||
failWantMap()
|
||||
}
|
||||
d.unmarshal(n, out)
|
||||
case sequenceNode:
|
||||
// Step backwards as earlier nodes take precedence.
|
||||
for i := len(n.children) - 1; i >= 0; i-- {
|
||||
ni := n.children[i]
|
||||
if ni.kind == aliasNode {
|
||||
an, ok := d.doc.anchors[ni.value]
|
||||
if ok && an.kind != mappingNode {
|
||||
failWantMap()
|
||||
}
|
||||
} else if ni.kind != mappingNode {
|
||||
failWantMap()
|
||||
}
|
||||
d.unmarshal(ni, out)
|
||||
}
|
||||
default:
|
||||
failWantMap()
|
||||
}
|
||||
}
|
||||
|
||||
func isMerge(n *node) bool {
|
||||
return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG)
|
||||
}
|
||||
+1685
File diff suppressed because it is too large
Load Diff
+358
@@ -0,0 +1,358 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type encoder struct {
|
||||
emitter yaml_emitter_t
|
||||
event yaml_event_t
|
||||
out []byte
|
||||
flow bool
|
||||
// doneInit holds whether the initial stream_start_event has been
|
||||
// emitted.
|
||||
doneInit bool
|
||||
}
|
||||
|
||||
func newEncoder() *encoder {
|
||||
e := &encoder{}
|
||||
yaml_emitter_initialize(&e.emitter)
|
||||
yaml_emitter_set_output_string(&e.emitter, &e.out)
|
||||
yaml_emitter_set_unicode(&e.emitter, true)
|
||||
return e
|
||||
}
|
||||
|
||||
func newEncoderWithWriter(w io.Writer) *encoder {
|
||||
e := &encoder{}
|
||||
yaml_emitter_initialize(&e.emitter)
|
||||
yaml_emitter_set_output_writer(&e.emitter, w)
|
||||
yaml_emitter_set_unicode(&e.emitter, true)
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *encoder) init() {
|
||||
if e.doneInit {
|
||||
return
|
||||
}
|
||||
yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
|
||||
e.emit()
|
||||
e.doneInit = true
|
||||
}
|
||||
|
||||
func (e *encoder) finish() {
|
||||
e.emitter.open_ended = false
|
||||
yaml_stream_end_event_initialize(&e.event)
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) destroy() {
|
||||
yaml_emitter_delete(&e.emitter)
|
||||
}
|
||||
|
||||
func (e *encoder) emit() {
|
||||
// This will internally delete the e.event value.
|
||||
e.must(yaml_emitter_emit(&e.emitter, &e.event))
|
||||
}
|
||||
|
||||
func (e *encoder) must(ok bool) {
|
||||
if !ok {
|
||||
msg := e.emitter.problem
|
||||
if msg == "" {
|
||||
msg = "unknown problem generating YAML content"
|
||||
}
|
||||
failf("%s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) marshalDoc(tag string, in reflect.Value) {
|
||||
e.init()
|
||||
yaml_document_start_event_initialize(&e.event, nil, nil, true)
|
||||
e.emit()
|
||||
e.marshal(tag, in)
|
||||
yaml_document_end_event_initialize(&e.event, true)
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) marshal(tag string, in reflect.Value) {
|
||||
if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() {
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
iface := in.Interface()
|
||||
switch m := iface.(type) {
|
||||
case time.Time, *time.Time:
|
||||
// Although time.Time implements TextMarshaler,
|
||||
// we don't want to treat it as a string for YAML
|
||||
// purposes because YAML has special support for
|
||||
// timestamps.
|
||||
case Marshaler:
|
||||
v, err := m.MarshalYAML()
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
if v == nil {
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
in = reflect.ValueOf(v)
|
||||
case encoding.TextMarshaler:
|
||||
text, err := m.MarshalText()
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
in = reflect.ValueOf(string(text))
|
||||
case nil:
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
switch in.Kind() {
|
||||
case reflect.Interface:
|
||||
e.marshal(tag, in.Elem())
|
||||
case reflect.Map:
|
||||
e.mapv(tag, in)
|
||||
case reflect.Ptr:
|
||||
if in.Type() == ptrTimeType {
|
||||
e.timev(tag, in.Elem())
|
||||
} else {
|
||||
e.marshal(tag, in.Elem())
|
||||
}
|
||||
case reflect.Struct:
|
||||
if in.Type() == timeType {
|
||||
e.timev(tag, in)
|
||||
} else {
|
||||
e.structv(tag, in)
|
||||
}
|
||||
case reflect.Slice:
|
||||
if in.Type().Elem() == mapItemType {
|
||||
e.itemsv(tag, in)
|
||||
} else {
|
||||
e.slicev(tag, in)
|
||||
}
|
||||
case reflect.String:
|
||||
e.stringv(tag, in)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if in.Type() == durationType {
|
||||
e.stringv(tag, reflect.ValueOf(iface.(time.Duration).String()))
|
||||
} else {
|
||||
e.intv(tag, in)
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
e.uintv(tag, in)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
e.floatv(tag, in)
|
||||
case reflect.Bool:
|
||||
e.boolv(tag, in)
|
||||
default:
|
||||
panic("cannot marshal type: " + in.Type().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) mapv(tag string, in reflect.Value) {
|
||||
e.mappingv(tag, func() {
|
||||
keys := keyList(in.MapKeys())
|
||||
sort.Sort(keys)
|
||||
for _, k := range keys {
|
||||
e.marshal("", k)
|
||||
e.marshal("", in.MapIndex(k))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) itemsv(tag string, in reflect.Value) {
|
||||
e.mappingv(tag, func() {
|
||||
slice := in.Convert(reflect.TypeOf([]MapItem{})).Interface().([]MapItem)
|
||||
for _, item := range slice {
|
||||
e.marshal("", reflect.ValueOf(item.Key))
|
||||
e.marshal("", reflect.ValueOf(item.Value))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) structv(tag string, in reflect.Value) {
|
||||
sinfo, err := getStructInfo(in.Type())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
e.mappingv(tag, func() {
|
||||
for _, info := range sinfo.FieldsList {
|
||||
var value reflect.Value
|
||||
if info.Inline == nil {
|
||||
value = in.Field(info.Num)
|
||||
} else {
|
||||
value = in.FieldByIndex(info.Inline)
|
||||
}
|
||||
if info.OmitEmpty && isZero(value) {
|
||||
continue
|
||||
}
|
||||
e.marshal("", reflect.ValueOf(info.Key))
|
||||
e.flow = info.Flow
|
||||
e.marshal("", value)
|
||||
}
|
||||
if sinfo.InlineMap >= 0 {
|
||||
m := in.Field(sinfo.InlineMap)
|
||||
if m.Len() > 0 {
|
||||
e.flow = false
|
||||
keys := keyList(m.MapKeys())
|
||||
sort.Sort(keys)
|
||||
for _, k := range keys {
|
||||
if _, found := sinfo.FieldsMap[k.String()]; found {
|
||||
panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", k.String()))
|
||||
}
|
||||
e.marshal("", k)
|
||||
e.flow = false
|
||||
e.marshal("", m.MapIndex(k))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) mappingv(tag string, f func()) {
|
||||
implicit := tag == ""
|
||||
style := yaml_BLOCK_MAPPING_STYLE
|
||||
if e.flow {
|
||||
e.flow = false
|
||||
style = yaml_FLOW_MAPPING_STYLE
|
||||
}
|
||||
yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
|
||||
e.emit()
|
||||
f()
|
||||
yaml_mapping_end_event_initialize(&e.event)
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) slicev(tag string, in reflect.Value) {
|
||||
implicit := tag == ""
|
||||
style := yaml_BLOCK_SEQUENCE_STYLE
|
||||
if e.flow {
|
||||
e.flow = false
|
||||
style = yaml_FLOW_SEQUENCE_STYLE
|
||||
}
|
||||
e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
|
||||
e.emit()
|
||||
n := in.Len()
|
||||
for i := 0; i < n; i++ {
|
||||
e.marshal("", in.Index(i))
|
||||
}
|
||||
e.must(yaml_sequence_end_event_initialize(&e.event))
|
||||
e.emit()
|
||||
}
|
||||
|
||||
// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
|
||||
//
|
||||
// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
|
||||
// in YAML 1.2 and by this package, but these should be marshalled quoted for
|
||||
// the time being for compatibility with other parsers.
|
||||
func isBase60Float(s string) (result bool) {
|
||||
// Fast path.
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
c := s[0]
|
||||
if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
|
||||
return false
|
||||
}
|
||||
// Do the full match.
|
||||
return base60float.MatchString(s)
|
||||
}
|
||||
|
||||
// From http://yaml.org/type/float.html, except the regular expression there
|
||||
// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
|
||||
var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
|
||||
|
||||
func (e *encoder) stringv(tag string, in reflect.Value) {
|
||||
var style yaml_scalar_style_t
|
||||
s := in.String()
|
||||
canUsePlain := true
|
||||
switch {
|
||||
case !utf8.ValidString(s):
|
||||
if tag == yaml_BINARY_TAG {
|
||||
failf("explicitly tagged !!binary data must be base64-encoded")
|
||||
}
|
||||
if tag != "" {
|
||||
failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
|
||||
}
|
||||
// It can't be encoded directly as YAML so use a binary tag
|
||||
// and encode it as base64.
|
||||
tag = yaml_BINARY_TAG
|
||||
s = encodeBase64(s)
|
||||
case tag == "":
|
||||
// Check to see if it would resolve to a specific
|
||||
// tag when encoded unquoted. If it doesn't,
|
||||
// there's no need to quote it.
|
||||
rtag, _ := resolve("", s)
|
||||
canUsePlain = rtag == yaml_STR_TAG && !isBase60Float(s)
|
||||
}
|
||||
// Note: it's possible for user code to emit invalid YAML
|
||||
// if they explicitly specify a tag and a string containing
|
||||
// text that's incompatible with that tag.
|
||||
switch {
|
||||
case strings.Contains(s, "\n"):
|
||||
style = yaml_LITERAL_SCALAR_STYLE
|
||||
case canUsePlain:
|
||||
style = yaml_PLAIN_SCALAR_STYLE
|
||||
default:
|
||||
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
||||
}
|
||||
e.emitScalar(s, "", tag, style)
|
||||
}
|
||||
|
||||
func (e *encoder) boolv(tag string, in reflect.Value) {
|
||||
var s string
|
||||
if in.Bool() {
|
||||
s = "true"
|
||||
} else {
|
||||
s = "false"
|
||||
}
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) intv(tag string, in reflect.Value) {
|
||||
s := strconv.FormatInt(in.Int(), 10)
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) uintv(tag string, in reflect.Value) {
|
||||
s := strconv.FormatUint(in.Uint(), 10)
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) timev(tag string, in reflect.Value) {
|
||||
t := in.Interface().(time.Time)
|
||||
if tag == "" {
|
||||
tag = yaml_TIMESTAMP_TAG
|
||||
}
|
||||
e.emitScalar(t.Format(time.RFC3339Nano), "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) floatv(tag string, in reflect.Value) {
|
||||
s := strconv.FormatFloat(in.Float(), 'g', -1, 64)
|
||||
switch s {
|
||||
case "+Inf":
|
||||
s = ".inf"
|
||||
case "-Inf":
|
||||
s = "-.inf"
|
||||
case "NaN":
|
||||
s = ".nan"
|
||||
}
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) nilv() {
|
||||
e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) {
|
||||
implicit := tag == ""
|
||||
e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
|
||||
e.emit()
|
||||
}
|
||||
+1095
File diff suppressed because it is too large
Load Diff
+394
@@ -0,0 +1,394 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Set the reader error and return 0.
|
||||
func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool {
|
||||
parser.error = yaml_READER_ERROR
|
||||
parser.problem = problem
|
||||
parser.problem_offset = offset
|
||||
parser.problem_value = value
|
||||
return false
|
||||
}
|
||||
|
||||
// Byte order marks.
|
||||
const (
|
||||
bom_UTF8 = "\xef\xbb\xbf"
|
||||
bom_UTF16LE = "\xff\xfe"
|
||||
bom_UTF16BE = "\xfe\xff"
|
||||
)
|
||||
|
||||
// Determine the input stream encoding by checking the BOM symbol. If no BOM is
|
||||
// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure.
|
||||
func yaml_parser_determine_encoding(parser *yaml_parser_t) bool {
|
||||
// Ensure that we had enough bytes in the raw buffer.
|
||||
for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 {
|
||||
if !yaml_parser_update_raw_buffer(parser) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the encoding.
|
||||
buf := parser.raw_buffer
|
||||
pos := parser.raw_buffer_pos
|
||||
avail := len(buf) - pos
|
||||
if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] {
|
||||
parser.encoding = yaml_UTF16LE_ENCODING
|
||||
parser.raw_buffer_pos += 2
|
||||
parser.offset += 2
|
||||
} else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] {
|
||||
parser.encoding = yaml_UTF16BE_ENCODING
|
||||
parser.raw_buffer_pos += 2
|
||||
parser.offset += 2
|
||||
} else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] {
|
||||
parser.encoding = yaml_UTF8_ENCODING
|
||||
parser.raw_buffer_pos += 3
|
||||
parser.offset += 3
|
||||
} else {
|
||||
parser.encoding = yaml_UTF8_ENCODING
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Update the raw buffer.
|
||||
func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool {
|
||||
size_read := 0
|
||||
|
||||
// Return if the raw buffer is full.
|
||||
if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Return on EOF.
|
||||
if parser.eof {
|
||||
return true
|
||||
}
|
||||
|
||||
// Move the remaining bytes in the raw buffer to the beginning.
|
||||
if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) {
|
||||
copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:])
|
||||
}
|
||||
parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos]
|
||||
parser.raw_buffer_pos = 0
|
||||
|
||||
// Call the read handler to fill the buffer.
|
||||
size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)])
|
||||
parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read]
|
||||
if err == io.EOF {
|
||||
parser.eof = true
|
||||
} else if err != nil {
|
||||
return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Ensure that the buffer contains at least `length` characters.
|
||||
// Return true on success, false on failure.
|
||||
//
|
||||
// The length is supposed to be significantly less that the buffer size.
|
||||
func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool {
|
||||
if parser.read_handler == nil {
|
||||
panic("read handler must be set")
|
||||
}
|
||||
|
||||
// If the EOF flag is set and the raw buffer is empty, do nothing.
|
||||
if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Return if the buffer contains enough characters.
|
||||
if parser.unread >= length {
|
||||
return true
|
||||
}
|
||||
|
||||
// Determine the input encoding if it is not known yet.
|
||||
if parser.encoding == yaml_ANY_ENCODING {
|
||||
if !yaml_parser_determine_encoding(parser) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Move the unread characters to the beginning of the buffer.
|
||||
buffer_len := len(parser.buffer)
|
||||
if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len {
|
||||
copy(parser.buffer, parser.buffer[parser.buffer_pos:])
|
||||
buffer_len -= parser.buffer_pos
|
||||
parser.buffer_pos = 0
|
||||
} else if parser.buffer_pos == buffer_len {
|
||||
buffer_len = 0
|
||||
parser.buffer_pos = 0
|
||||
}
|
||||
|
||||
// Open the whole buffer for writing, and cut it before returning.
|
||||
parser.buffer = parser.buffer[:cap(parser.buffer)]
|
||||
|
||||
// Fill the buffer until it has enough characters.
|
||||
first := true
|
||||
for parser.unread < length {
|
||||
|
||||
// Fill the raw buffer if necessary.
|
||||
if !first || parser.raw_buffer_pos == len(parser.raw_buffer) {
|
||||
if !yaml_parser_update_raw_buffer(parser) {
|
||||
parser.buffer = parser.buffer[:buffer_len]
|
||||
return false
|
||||
}
|
||||
}
|
||||
first = false
|
||||
|
||||
// Decode the raw buffer.
|
||||
inner:
|
||||
for parser.raw_buffer_pos != len(parser.raw_buffer) {
|
||||
var value rune
|
||||
var width int
|
||||
|
||||
raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos
|
||||
|
||||
// Decode the next character.
|
||||
switch parser.encoding {
|
||||
case yaml_UTF8_ENCODING:
|
||||
// Decode a UTF-8 character. Check RFC 3629
|
||||
// (http://www.ietf.org/rfc/rfc3629.txt) for more details.
|
||||
//
|
||||
// The following table (taken from the RFC) is used for
|
||||
// decoding.
|
||||
//
|
||||
// Char. number range | UTF-8 octet sequence
|
||||
// (hexadecimal) | (binary)
|
||||
// --------------------+------------------------------------
|
||||
// 0000 0000-0000 007F | 0xxxxxxx
|
||||
// 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
|
||||
// 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
|
||||
// 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
//
|
||||
// Additionally, the characters in the range 0xD800-0xDFFF
|
||||
// are prohibited as they are reserved for use with UTF-16
|
||||
// surrogate pairs.
|
||||
|
||||
// Determine the length of the UTF-8 sequence.
|
||||
octet := parser.raw_buffer[parser.raw_buffer_pos]
|
||||
switch {
|
||||
case octet&0x80 == 0x00:
|
||||
width = 1
|
||||
case octet&0xE0 == 0xC0:
|
||||
width = 2
|
||||
case octet&0xF0 == 0xE0:
|
||||
width = 3
|
||||
case octet&0xF8 == 0xF0:
|
||||
width = 4
|
||||
default:
|
||||
// The leading octet is invalid.
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid leading UTF-8 octet",
|
||||
parser.offset, int(octet))
|
||||
}
|
||||
|
||||
// Check if the raw buffer contains an incomplete character.
|
||||
if width > raw_unread {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-8 octet sequence",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Decode the leading octet.
|
||||
switch {
|
||||
case octet&0x80 == 0x00:
|
||||
value = rune(octet & 0x7F)
|
||||
case octet&0xE0 == 0xC0:
|
||||
value = rune(octet & 0x1F)
|
||||
case octet&0xF0 == 0xE0:
|
||||
value = rune(octet & 0x0F)
|
||||
case octet&0xF8 == 0xF0:
|
||||
value = rune(octet & 0x07)
|
||||
default:
|
||||
value = 0
|
||||
}
|
||||
|
||||
// Check and decode the trailing octets.
|
||||
for k := 1; k < width; k++ {
|
||||
octet = parser.raw_buffer[parser.raw_buffer_pos+k]
|
||||
|
||||
// Check if the octet is valid.
|
||||
if (octet & 0xC0) != 0x80 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid trailing UTF-8 octet",
|
||||
parser.offset+k, int(octet))
|
||||
}
|
||||
|
||||
// Decode the octet.
|
||||
value = (value << 6) + rune(octet&0x3F)
|
||||
}
|
||||
|
||||
// Check the length of the sequence against the value.
|
||||
switch {
|
||||
case width == 1:
|
||||
case width == 2 && value >= 0x80:
|
||||
case width == 3 && value >= 0x800:
|
||||
case width == 4 && value >= 0x10000:
|
||||
default:
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid length of a UTF-8 sequence",
|
||||
parser.offset, -1)
|
||||
}
|
||||
|
||||
// Check the range of the value.
|
||||
if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid Unicode character",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING:
|
||||
var low, high int
|
||||
if parser.encoding == yaml_UTF16LE_ENCODING {
|
||||
low, high = 0, 1
|
||||
} else {
|
||||
low, high = 1, 0
|
||||
}
|
||||
|
||||
// The UTF-16 encoding is not as simple as one might
|
||||
// naively think. Check RFC 2781
|
||||
// (http://www.ietf.org/rfc/rfc2781.txt).
|
||||
//
|
||||
// Normally, two subsequent bytes describe a Unicode
|
||||
// character. However a special technique (called a
|
||||
// surrogate pair) is used for specifying character
|
||||
// values larger than 0xFFFF.
|
||||
//
|
||||
// A surrogate pair consists of two pseudo-characters:
|
||||
// high surrogate area (0xD800-0xDBFF)
|
||||
// low surrogate area (0xDC00-0xDFFF)
|
||||
//
|
||||
// The following formulas are used for decoding
|
||||
// and encoding characters using surrogate pairs:
|
||||
//
|
||||
// U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF)
|
||||
// U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF)
|
||||
// W1 = 110110yyyyyyyyyy
|
||||
// W2 = 110111xxxxxxxxxx
|
||||
//
|
||||
// where U is the character value, W1 is the high surrogate
|
||||
// area, W2 is the low surrogate area.
|
||||
|
||||
// Check for incomplete UTF-16 character.
|
||||
if raw_unread < 2 {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-16 character",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Get the character.
|
||||
value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) +
|
||||
(rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8)
|
||||
|
||||
// Check for unexpected low surrogate area.
|
||||
if value&0xFC00 == 0xDC00 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"unexpected low surrogate area",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
// Check for a high surrogate area.
|
||||
if value&0xFC00 == 0xD800 {
|
||||
width = 4
|
||||
|
||||
// Check for incomplete surrogate pair.
|
||||
if raw_unread < 4 {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-16 surrogate pair",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Get the next character.
|
||||
value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) +
|
||||
(rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8)
|
||||
|
||||
// Check for a low surrogate area.
|
||||
if value2&0xFC00 != 0xDC00 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"expected low surrogate area",
|
||||
parser.offset+2, int(value2))
|
||||
}
|
||||
|
||||
// Generate the value of the surrogate pair.
|
||||
value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF)
|
||||
} else {
|
||||
width = 2
|
||||
}
|
||||
|
||||
default:
|
||||
panic("impossible")
|
||||
}
|
||||
|
||||
// Check if the character is in the allowed range:
|
||||
// #x9 | #xA | #xD | [#x20-#x7E] (8 bit)
|
||||
// | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit)
|
||||
// | [#x10000-#x10FFFF] (32 bit)
|
||||
switch {
|
||||
case value == 0x09:
|
||||
case value == 0x0A:
|
||||
case value == 0x0D:
|
||||
case value >= 0x20 && value <= 0x7E:
|
||||
case value == 0x85:
|
||||
case value >= 0xA0 && value <= 0xD7FF:
|
||||
case value >= 0xE000 && value <= 0xFFFD:
|
||||
case value >= 0x10000 && value <= 0x10FFFF:
|
||||
default:
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"control characters are not allowed",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
// Move the raw pointers.
|
||||
parser.raw_buffer_pos += width
|
||||
parser.offset += width
|
||||
|
||||
// Finally put the character into the buffer.
|
||||
if value <= 0x7F {
|
||||
// 0000 0000-0000 007F . 0xxxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(value)
|
||||
buffer_len += 1
|
||||
} else if value <= 0x7FF {
|
||||
// 0000 0080-0000 07FF . 110xxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 2
|
||||
} else if value <= 0xFFFF {
|
||||
// 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F))
|
||||
parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 3
|
||||
} else {
|
||||
// 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F))
|
||||
parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F))
|
||||
parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 4
|
||||
}
|
||||
|
||||
parser.unread++
|
||||
}
|
||||
|
||||
// On EOF, put NUL into the buffer and return.
|
||||
if parser.eof {
|
||||
parser.buffer[buffer_len] = 0
|
||||
buffer_len++
|
||||
parser.unread++
|
||||
break
|
||||
}
|
||||
}
|
||||
parser.buffer = parser.buffer[:buffer_len]
|
||||
return true
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type resolveMapItem struct {
|
||||
value interface{}
|
||||
tag string
|
||||
}
|
||||
|
||||
var resolveTable = make([]byte, 256)
|
||||
var resolveMap = make(map[string]resolveMapItem)
|
||||
|
||||
func init() {
|
||||
t := resolveTable
|
||||
t[int('+')] = 'S' // Sign
|
||||
t[int('-')] = 'S'
|
||||
for _, c := range "0123456789" {
|
||||
t[int(c)] = 'D' // Digit
|
||||
}
|
||||
for _, c := range "yYnNtTfFoO~" {
|
||||
t[int(c)] = 'M' // In map
|
||||
}
|
||||
t[int('.')] = '.' // Float (potentially in map)
|
||||
|
||||
var resolveMapList = []struct {
|
||||
v interface{}
|
||||
tag string
|
||||
l []string
|
||||
}{
|
||||
{true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}},
|
||||
{true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}},
|
||||
{true, yaml_BOOL_TAG, []string{"on", "On", "ON"}},
|
||||
{false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}},
|
||||
{false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}},
|
||||
{false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}},
|
||||
{nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}},
|
||||
{math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}},
|
||||
{math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}},
|
||||
{math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}},
|
||||
{math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}},
|
||||
{"<<", yaml_MERGE_TAG, []string{"<<"}},
|
||||
}
|
||||
|
||||
m := resolveMap
|
||||
for _, item := range resolveMapList {
|
||||
for _, s := range item.l {
|
||||
m[s] = resolveMapItem{item.v, item.tag}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const longTagPrefix = "tag:yaml.org,2002:"
|
||||
|
||||
func shortTag(tag string) string {
|
||||
// TODO This can easily be made faster and produce less garbage.
|
||||
if strings.HasPrefix(tag, longTagPrefix) {
|
||||
return "!!" + tag[len(longTagPrefix):]
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func longTag(tag string) string {
|
||||
if strings.HasPrefix(tag, "!!") {
|
||||
return longTagPrefix + tag[2:]
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func resolvableTag(tag string) bool {
|
||||
switch tag {
|
||||
case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var yamlStyleFloat = regexp.MustCompile(`^[-+]?[0-9]*\.?[0-9]+([eE][-+][0-9]+)?$`)
|
||||
|
||||
func resolve(tag string, in string) (rtag string, out interface{}) {
|
||||
if !resolvableTag(tag) {
|
||||
return tag, in
|
||||
}
|
||||
|
||||
defer func() {
|
||||
switch tag {
|
||||
case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG:
|
||||
return
|
||||
}
|
||||
failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag))
|
||||
}()
|
||||
|
||||
// Any data is accepted as a !!str or !!binary.
|
||||
// Otherwise, the prefix is enough of a hint about what it might be.
|
||||
hint := byte('N')
|
||||
if in != "" {
|
||||
hint = resolveTable[in[0]]
|
||||
}
|
||||
if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG {
|
||||
// Handle things we can lookup in a map.
|
||||
if item, ok := resolveMap[in]; ok {
|
||||
return item.tag, item.value
|
||||
}
|
||||
|
||||
// Base 60 floats are a bad idea, were dropped in YAML 1.2, and
|
||||
// are purposefully unsupported here. They're still quoted on
|
||||
// the way out for compatibility with other parser, though.
|
||||
|
||||
switch hint {
|
||||
case 'M':
|
||||
// We've already checked the map above.
|
||||
|
||||
case '.':
|
||||
// Not in the map, so maybe a normal float.
|
||||
floatv, err := strconv.ParseFloat(in, 64)
|
||||
if err == nil {
|
||||
return yaml_FLOAT_TAG, floatv
|
||||
}
|
||||
|
||||
case 'D', 'S':
|
||||
// Int, float, or timestamp.
|
||||
// Only try values as a timestamp if the value is unquoted or there's an explicit
|
||||
// !!timestamp tag.
|
||||
if tag == "" || tag == yaml_TIMESTAMP_TAG {
|
||||
t, ok := parseTimestamp(in)
|
||||
if ok {
|
||||
return yaml_TIMESTAMP_TAG, t
|
||||
}
|
||||
}
|
||||
|
||||
plain := strings.Replace(in, "_", "", -1)
|
||||
intv, err := strconv.ParseInt(plain, 0, 64)
|
||||
if err == nil {
|
||||
if intv == int64(int(intv)) {
|
||||
return yaml_INT_TAG, int(intv)
|
||||
} else {
|
||||
return yaml_INT_TAG, intv
|
||||
}
|
||||
}
|
||||
uintv, err := strconv.ParseUint(plain, 0, 64)
|
||||
if err == nil {
|
||||
return yaml_INT_TAG, uintv
|
||||
}
|
||||
if yamlStyleFloat.MatchString(plain) {
|
||||
floatv, err := strconv.ParseFloat(plain, 64)
|
||||
if err == nil {
|
||||
return yaml_FLOAT_TAG, floatv
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(plain, "0b") {
|
||||
intv, err := strconv.ParseInt(plain[2:], 2, 64)
|
||||
if err == nil {
|
||||
if intv == int64(int(intv)) {
|
||||
return yaml_INT_TAG, int(intv)
|
||||
} else {
|
||||
return yaml_INT_TAG, intv
|
||||
}
|
||||
}
|
||||
uintv, err := strconv.ParseUint(plain[2:], 2, 64)
|
||||
if err == nil {
|
||||
return yaml_INT_TAG, uintv
|
||||
}
|
||||
} else if strings.HasPrefix(plain, "-0b") {
|
||||
intv, err := strconv.ParseInt(plain[3:], 2, 64)
|
||||
if err == nil {
|
||||
if intv == int64(int(intv)) {
|
||||
return yaml_INT_TAG, -int(intv)
|
||||
} else {
|
||||
return yaml_INT_TAG, -intv
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")")
|
||||
}
|
||||
}
|
||||
return yaml_STR_TAG, in
|
||||
}
|
||||
|
||||
// encodeBase64 encodes s as base64 that is broken up into multiple lines
|
||||
// as appropriate for the resulting length.
|
||||
func encodeBase64(s string) string {
|
||||
const lineLen = 70
|
||||
encLen := base64.StdEncoding.EncodedLen(len(s))
|
||||
lines := encLen/lineLen + 1
|
||||
buf := make([]byte, encLen*2+lines)
|
||||
in := buf[0:encLen]
|
||||
out := buf[encLen:]
|
||||
base64.StdEncoding.Encode(in, []byte(s))
|
||||
k := 0
|
||||
for i := 0; i < len(in); i += lineLen {
|
||||
j := i + lineLen
|
||||
if j > len(in) {
|
||||
j = len(in)
|
||||
}
|
||||
k += copy(out[k:], in[i:j])
|
||||
if lines > 1 {
|
||||
out[k] = '\n'
|
||||
k++
|
||||
}
|
||||
}
|
||||
return string(out[:k])
|
||||
}
|
||||
|
||||
// This is a subset of the formats allowed by the regular expression
|
||||
// defined at http://yaml.org/type/timestamp.html.
|
||||
var allowedTimestampFormats = []string{
|
||||
"2006-1-2T15:4:5Z07:00",
|
||||
"2006-1-2t15:4:5Z07:00", // RFC3339 with lower-case "t".
|
||||
"2006-1-2 15:4:5", // space separated with no time zone
|
||||
"2006-1-2", // date only
|
||||
// Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5"
|
||||
// from the set of examples.
|
||||
}
|
||||
|
||||
// parseTimestamp parses s as a timestamp string and
|
||||
// returns the timestamp and reports whether it succeeded.
|
||||
// Timestamp formats are defined at http://yaml.org/type/timestamp.html
|
||||
func parseTimestamp(s string) (time.Time, bool) {
|
||||
// TODO write code to check all the formats supported by
|
||||
// http://yaml.org/type/timestamp.html instead of using time.Parse.
|
||||
|
||||
// Quick check: all date formats start with YYYY-.
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if c := s[i]; c < '0' || c > '9' {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i != 4 || i == len(s) || s[i] != '-' {
|
||||
return time.Time{}, false
|
||||
}
|
||||
for _, format := range allowedTimestampFormats {
|
||||
if t, err := time.Parse(format, s); err == nil {
|
||||
return t, true
|
||||
}
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
||||
+2702
File diff suppressed because it is too large
Load Diff
+104
@@ -0,0 +1,104 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type keyList []reflect.Value
|
||||
|
||||
func (l keyList) Len() int { return len(l) }
|
||||
func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||
func (l keyList) Less(i, j int) bool {
|
||||
a := l[i]
|
||||
b := l[j]
|
||||
ak := a.Kind()
|
||||
bk := b.Kind()
|
||||
for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() {
|
||||
a = a.Elem()
|
||||
ak = a.Kind()
|
||||
}
|
||||
for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() {
|
||||
b = b.Elem()
|
||||
bk = b.Kind()
|
||||
}
|
||||
af, aok := keyFloat(a)
|
||||
bf, bok := keyFloat(b)
|
||||
if aok && bok {
|
||||
if af != bf {
|
||||
return af < bf
|
||||
}
|
||||
if ak != bk {
|
||||
return ak < bk
|
||||
}
|
||||
return numLess(a, b)
|
||||
}
|
||||
if ak != reflect.String || bk != reflect.String {
|
||||
return ak < bk
|
||||
}
|
||||
ar, br := []rune(a.String()), []rune(b.String())
|
||||
for i := 0; i < len(ar) && i < len(br); i++ {
|
||||
if ar[i] == br[i] {
|
||||
continue
|
||||
}
|
||||
al := unicode.IsLetter(ar[i])
|
||||
bl := unicode.IsLetter(br[i])
|
||||
if al && bl {
|
||||
return ar[i] < br[i]
|
||||
}
|
||||
if al || bl {
|
||||
return bl
|
||||
}
|
||||
var ai, bi int
|
||||
var an, bn int64
|
||||
for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ {
|
||||
an = an*10 + int64(ar[ai]-'0')
|
||||
}
|
||||
for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ {
|
||||
bn = bn*10 + int64(br[bi]-'0')
|
||||
}
|
||||
if an != bn {
|
||||
return an < bn
|
||||
}
|
||||
if ai != bi {
|
||||
return ai < bi
|
||||
}
|
||||
return ar[i] < br[i]
|
||||
}
|
||||
return len(ar) < len(br)
|
||||
}
|
||||
|
||||
// keyFloat returns a float value for v if it is a number/bool
|
||||
// and whether it is a number/bool or not.
|
||||
func keyFloat(v reflect.Value) (f float64, ok bool) {
|
||||
switch v.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return float64(v.Int()), true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float(), true
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return float64(v.Uint()), true
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
return 1, true
|
||||
}
|
||||
return 0, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// numLess returns whether a < b.
|
||||
// a and b must necessarily have the same kind.
|
||||
func numLess(a, b reflect.Value) bool {
|
||||
switch a.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return a.Int() < b.Int()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return a.Float() < b.Float()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Bool:
|
||||
return !a.Bool() && b.Bool()
|
||||
}
|
||||
panic("not a number")
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package yaml
|
||||
|
||||
// Set the writer error and return false.
|
||||
func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool {
|
||||
emitter.error = yaml_WRITER_ERROR
|
||||
emitter.problem = problem
|
||||
return false
|
||||
}
|
||||
|
||||
// Flush the output buffer.
|
||||
func yaml_emitter_flush(emitter *yaml_emitter_t) bool {
|
||||
if emitter.write_handler == nil {
|
||||
panic("write handler not set")
|
||||
}
|
||||
|
||||
// Check if the buffer is empty.
|
||||
if emitter.buffer_pos == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil {
|
||||
return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error())
|
||||
}
|
||||
emitter.buffer_pos = 0
|
||||
return true
|
||||
}
|
||||
+466
@@ -0,0 +1,466 @@
|
||||
// Package yaml implements YAML support for the Go language.
|
||||
//
|
||||
// Source code and other details for the project are available at GitHub:
|
||||
//
|
||||
// https://github.com/go-yaml/yaml
|
||||
//
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MapSlice encodes and decodes as a YAML map.
|
||||
// The order of keys is preserved when encoding and decoding.
|
||||
type MapSlice []MapItem
|
||||
|
||||
// MapItem is an item in a MapSlice.
|
||||
type MapItem struct {
|
||||
Key, Value interface{}
|
||||
}
|
||||
|
||||
// The Unmarshaler interface may be implemented by types to customize their
|
||||
// behavior when being unmarshaled from a YAML document. The UnmarshalYAML
|
||||
// method receives a function that may be called to unmarshal the original
|
||||
// YAML value into a field or variable. It is safe to call the unmarshal
|
||||
// function parameter more than once if necessary.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalYAML(unmarshal func(interface{}) error) error
|
||||
}
|
||||
|
||||
// The Marshaler interface may be implemented by types to customize their
|
||||
// behavior when being marshaled into a YAML document. The returned value
|
||||
// is marshaled in place of the original value implementing Marshaler.
|
||||
//
|
||||
// If an error is returned by MarshalYAML, the marshaling procedure stops
|
||||
// and returns with the provided error.
|
||||
type Marshaler interface {
|
||||
MarshalYAML() (interface{}, error)
|
||||
}
|
||||
|
||||
// Unmarshal decodes the first document found within the in byte slice
|
||||
// and assigns decoded values into the out value.
|
||||
//
|
||||
// Maps and pointers (to a struct, string, int, etc) are accepted as out
|
||||
// values. If an internal pointer within a struct is not initialized,
|
||||
// the yaml package will initialize it if necessary for unmarshalling
|
||||
// the provided data. The out parameter must not be nil.
|
||||
//
|
||||
// The type of the decoded values should be compatible with the respective
|
||||
// values in out. If one or more values cannot be decoded due to a type
|
||||
// mismatches, decoding continues partially until the end of the YAML
|
||||
// content, and a *yaml.TypeError is returned with details for all
|
||||
// missed values.
|
||||
//
|
||||
// Struct fields are only unmarshalled if they are exported (have an
|
||||
// upper case first letter), and are unmarshalled using the field name
|
||||
// lowercased as the default key. Custom keys may be defined via the
|
||||
// "yaml" name in the field tag: the content preceding the first comma
|
||||
// is used as the key, and the following comma-separated options are
|
||||
// used to tweak the marshalling process (see Marshal).
|
||||
// Conflicting names result in a runtime error.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// type T struct {
|
||||
// F int `yaml:"a,omitempty"`
|
||||
// B int
|
||||
// }
|
||||
// var t T
|
||||
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
|
||||
//
|
||||
// See the documentation of Marshal for the format of tags and a list of
|
||||
// supported tag options.
|
||||
//
|
||||
func Unmarshal(in []byte, out interface{}) (err error) {
|
||||
return unmarshal(in, out, false)
|
||||
}
|
||||
|
||||
// UnmarshalStrict is like Unmarshal except that any fields that are found
|
||||
// in the data that do not have corresponding struct members, or mapping
|
||||
// keys that are duplicates, will result in
|
||||
// an error.
|
||||
func UnmarshalStrict(in []byte, out interface{}) (err error) {
|
||||
return unmarshal(in, out, true)
|
||||
}
|
||||
|
||||
// A Decorder reads and decodes YAML values from an input stream.
|
||||
type Decoder struct {
|
||||
strict bool
|
||||
parser *parser
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that reads from r.
|
||||
//
|
||||
// The decoder introduces its own buffering and may read
|
||||
// data from r beyond the YAML values requested.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
return &Decoder{
|
||||
parser: newParserFromReader(r),
|
||||
}
|
||||
}
|
||||
|
||||
// SetStrict sets whether strict decoding behaviour is enabled when
|
||||
// decoding items in the data (see UnmarshalStrict). By default, decoding is not strict.
|
||||
func (dec *Decoder) SetStrict(strict bool) {
|
||||
dec.strict = strict
|
||||
}
|
||||
|
||||
// Decode reads the next YAML-encoded value from its input
|
||||
// and stores it in the value pointed to by v.
|
||||
//
|
||||
// See the documentation for Unmarshal for details about the
|
||||
// conversion of YAML into a Go value.
|
||||
func (dec *Decoder) Decode(v interface{}) (err error) {
|
||||
d := newDecoder(dec.strict)
|
||||
defer handleErr(&err)
|
||||
node := dec.parser.parse()
|
||||
if node == nil {
|
||||
return io.EOF
|
||||
}
|
||||
out := reflect.ValueOf(v)
|
||||
if out.Kind() == reflect.Ptr && !out.IsNil() {
|
||||
out = out.Elem()
|
||||
}
|
||||
d.unmarshal(node, out)
|
||||
if len(d.terrors) > 0 {
|
||||
return &TypeError{d.terrors}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshal(in []byte, out interface{}, strict bool) (err error) {
|
||||
defer handleErr(&err)
|
||||
d := newDecoder(strict)
|
||||
p := newParser(in)
|
||||
defer p.destroy()
|
||||
node := p.parse()
|
||||
if node != nil {
|
||||
v := reflect.ValueOf(out)
|
||||
if v.Kind() == reflect.Ptr && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
d.unmarshal(node, v)
|
||||
}
|
||||
if len(d.terrors) > 0 {
|
||||
return &TypeError{d.terrors}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal serializes the value provided into a YAML document. The structure
|
||||
// of the generated document will reflect the structure of the value itself.
|
||||
// Maps and pointers (to struct, string, int, etc) are accepted as the in value.
|
||||
//
|
||||
// Struct fields are only unmarshalled if they are exported (have an upper case
|
||||
// first letter), and are unmarshalled using the field name lowercased as the
|
||||
// default key. Custom keys may be defined via the "yaml" name in the field
|
||||
// tag: the content preceding the first comma is used as the key, and the
|
||||
// following comma-separated options are used to tweak the marshalling process.
|
||||
// Conflicting names result in a runtime error.
|
||||
//
|
||||
// The field tag format accepted is:
|
||||
//
|
||||
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
|
||||
//
|
||||
// The following flags are currently supported:
|
||||
//
|
||||
// omitempty Only include the field if it's not set to the zero
|
||||
// value for the type or to empty slices or maps.
|
||||
// Zero valued structs will be omitted if all their public
|
||||
// fields are zero, unless they implement an IsZero
|
||||
// method (see the IsZeroer interface type), in which
|
||||
// case the field will be included if that method returns true.
|
||||
//
|
||||
// flow Marshal using a flow style (useful for structs,
|
||||
// sequences and maps).
|
||||
//
|
||||
// inline Inline the field, which must be a struct or a map,
|
||||
// causing all of its fields or keys to be processed as if
|
||||
// they were part of the outer struct. For maps, keys must
|
||||
// not conflict with the yaml keys of other struct fields.
|
||||
//
|
||||
// In addition, if the key is "-", the field is ignored.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// type T struct {
|
||||
// F int `yaml:"a,omitempty"`
|
||||
// B int
|
||||
// }
|
||||
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
|
||||
// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n"
|
||||
//
|
||||
func Marshal(in interface{}) (out []byte, err error) {
|
||||
defer handleErr(&err)
|
||||
e := newEncoder()
|
||||
defer e.destroy()
|
||||
e.marshalDoc("", reflect.ValueOf(in))
|
||||
e.finish()
|
||||
out = e.out
|
||||
return
|
||||
}
|
||||
|
||||
// An Encoder writes YAML values to an output stream.
|
||||
type Encoder struct {
|
||||
encoder *encoder
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to w.
|
||||
// The Encoder should be closed after use to flush all data
|
||||
// to w.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{
|
||||
encoder: newEncoderWithWriter(w),
|
||||
}
|
||||
}
|
||||
|
||||
// Encode writes the YAML encoding of v to the stream.
|
||||
// If multiple items are encoded to the stream, the
|
||||
// second and subsequent document will be preceded
|
||||
// with a "---" document separator, but the first will not.
|
||||
//
|
||||
// See the documentation for Marshal for details about the conversion of Go
|
||||
// values to YAML.
|
||||
func (e *Encoder) Encode(v interface{}) (err error) {
|
||||
defer handleErr(&err)
|
||||
e.encoder.marshalDoc("", reflect.ValueOf(v))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the encoder by writing any remaining data.
|
||||
// It does not write a stream terminating string "...".
|
||||
func (e *Encoder) Close() (err error) {
|
||||
defer handleErr(&err)
|
||||
e.encoder.finish()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleErr(err *error) {
|
||||
if v := recover(); v != nil {
|
||||
if e, ok := v.(yamlError); ok {
|
||||
*err = e.err
|
||||
} else {
|
||||
panic(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type yamlError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func fail(err error) {
|
||||
panic(yamlError{err})
|
||||
}
|
||||
|
||||
func failf(format string, args ...interface{}) {
|
||||
panic(yamlError{fmt.Errorf("yaml: "+format, args...)})
|
||||
}
|
||||
|
||||
// A TypeError is returned by Unmarshal when one or more fields in
|
||||
// the YAML document cannot be properly decoded into the requested
|
||||
// types. When this error is returned, the value is still
|
||||
// unmarshaled partially.
|
||||
type TypeError struct {
|
||||
Errors []string
|
||||
}
|
||||
|
||||
func (e *TypeError) Error() string {
|
||||
return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n "))
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Maintain a mapping of keys to structure field indexes
|
||||
|
||||
// The code in this section was copied from mgo/bson.
|
||||
|
||||
// structInfo holds details for the serialization of fields of
|
||||
// a given struct.
|
||||
type structInfo struct {
|
||||
FieldsMap map[string]fieldInfo
|
||||
FieldsList []fieldInfo
|
||||
|
||||
// InlineMap is the number of the field in the struct that
|
||||
// contains an ,inline map, or -1 if there's none.
|
||||
InlineMap int
|
||||
}
|
||||
|
||||
type fieldInfo struct {
|
||||
Key string
|
||||
Num int
|
||||
OmitEmpty bool
|
||||
Flow bool
|
||||
// Id holds the unique field identifier, so we can cheaply
|
||||
// check for field duplicates without maintaining an extra map.
|
||||
Id int
|
||||
|
||||
// Inline holds the field index if the field is part of an inlined struct.
|
||||
Inline []int
|
||||
}
|
||||
|
||||
var structMap = make(map[reflect.Type]*structInfo)
|
||||
var fieldMapMutex sync.RWMutex
|
||||
|
||||
func getStructInfo(st reflect.Type) (*structInfo, error) {
|
||||
fieldMapMutex.RLock()
|
||||
sinfo, found := structMap[st]
|
||||
fieldMapMutex.RUnlock()
|
||||
if found {
|
||||
return sinfo, nil
|
||||
}
|
||||
|
||||
n := st.NumField()
|
||||
fieldsMap := make(map[string]fieldInfo)
|
||||
fieldsList := make([]fieldInfo, 0, n)
|
||||
inlineMap := -1
|
||||
for i := 0; i != n; i++ {
|
||||
field := st.Field(i)
|
||||
if field.PkgPath != "" && !field.Anonymous {
|
||||
continue // Private field
|
||||
}
|
||||
|
||||
info := fieldInfo{Num: i}
|
||||
|
||||
tag := field.Tag.Get("yaml")
|
||||
if tag == "" && strings.Index(string(field.Tag), ":") < 0 {
|
||||
tag = string(field.Tag)
|
||||
}
|
||||
if tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
inline := false
|
||||
fields := strings.Split(tag, ",")
|
||||
if len(fields) > 1 {
|
||||
for _, flag := range fields[1:] {
|
||||
switch flag {
|
||||
case "omitempty":
|
||||
info.OmitEmpty = true
|
||||
case "flow":
|
||||
info.Flow = true
|
||||
case "inline":
|
||||
inline = true
|
||||
default:
|
||||
return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st))
|
||||
}
|
||||
}
|
||||
tag = fields[0]
|
||||
}
|
||||
|
||||
if inline {
|
||||
switch field.Type.Kind() {
|
||||
case reflect.Map:
|
||||
if inlineMap >= 0 {
|
||||
return nil, errors.New("Multiple ,inline maps in struct " + st.String())
|
||||
}
|
||||
if field.Type.Key() != reflect.TypeOf("") {
|
||||
return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String())
|
||||
}
|
||||
inlineMap = info.Num
|
||||
case reflect.Struct:
|
||||
sinfo, err := getStructInfo(field.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, finfo := range sinfo.FieldsList {
|
||||
if _, found := fieldsMap[finfo.Key]; found {
|
||||
msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String()
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
if finfo.Inline == nil {
|
||||
finfo.Inline = []int{i, finfo.Num}
|
||||
} else {
|
||||
finfo.Inline = append([]int{i}, finfo.Inline...)
|
||||
}
|
||||
finfo.Id = len(fieldsList)
|
||||
fieldsMap[finfo.Key] = finfo
|
||||
fieldsList = append(fieldsList, finfo)
|
||||
}
|
||||
default:
|
||||
//return nil, errors.New("Option ,inline needs a struct value or map field")
|
||||
return nil, errors.New("Option ,inline needs a struct value field")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if tag != "" {
|
||||
info.Key = tag
|
||||
} else {
|
||||
info.Key = strings.ToLower(field.Name)
|
||||
}
|
||||
|
||||
if _, found = fieldsMap[info.Key]; found {
|
||||
msg := "Duplicated key '" + info.Key + "' in struct " + st.String()
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
|
||||
info.Id = len(fieldsList)
|
||||
fieldsList = append(fieldsList, info)
|
||||
fieldsMap[info.Key] = info
|
||||
}
|
||||
|
||||
sinfo = &structInfo{
|
||||
FieldsMap: fieldsMap,
|
||||
FieldsList: fieldsList,
|
||||
InlineMap: inlineMap,
|
||||
}
|
||||
|
||||
fieldMapMutex.Lock()
|
||||
structMap[st] = sinfo
|
||||
fieldMapMutex.Unlock()
|
||||
return sinfo, nil
|
||||
}
|
||||
|
||||
// IsZeroer is used to check whether an object is zero to
|
||||
// determine whether it should be omitted when marshaling
|
||||
// with the omitempty flag. One notable implementation
|
||||
// is time.Time.
|
||||
type IsZeroer interface {
|
||||
IsZero() bool
|
||||
}
|
||||
|
||||
func isZero(v reflect.Value) bool {
|
||||
kind := v.Kind()
|
||||
if z, ok := v.Interface().(IsZeroer); ok {
|
||||
if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() {
|
||||
return true
|
||||
}
|
||||
return z.IsZero()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.String:
|
||||
return len(v.String()) == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
case reflect.Slice:
|
||||
return v.Len() == 0
|
||||
case reflect.Map:
|
||||
return v.Len() == 0
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Struct:
|
||||
vt := v.Type()
|
||||
for i := v.NumField() - 1; i >= 0; i-- {
|
||||
if vt.Field(i).PkgPath != "" {
|
||||
continue // Private field
|
||||
}
|
||||
if !isZero(v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
+738
@@ -0,0 +1,738 @@
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// The version directive data.
|
||||
type yaml_version_directive_t struct {
|
||||
major int8 // The major version number.
|
||||
minor int8 // The minor version number.
|
||||
}
|
||||
|
||||
// The tag directive data.
|
||||
type yaml_tag_directive_t struct {
|
||||
handle []byte // The tag handle.
|
||||
prefix []byte // The tag prefix.
|
||||
}
|
||||
|
||||
type yaml_encoding_t int
|
||||
|
||||
// The stream encoding.
|
||||
const (
|
||||
// Let the parser choose the encoding.
|
||||
yaml_ANY_ENCODING yaml_encoding_t = iota
|
||||
|
||||
yaml_UTF8_ENCODING // The default UTF-8 encoding.
|
||||
yaml_UTF16LE_ENCODING // The UTF-16-LE encoding with BOM.
|
||||
yaml_UTF16BE_ENCODING // The UTF-16-BE encoding with BOM.
|
||||
)
|
||||
|
||||
type yaml_break_t int
|
||||
|
||||
// Line break types.
|
||||
const (
|
||||
// Let the parser choose the break type.
|
||||
yaml_ANY_BREAK yaml_break_t = iota
|
||||
|
||||
yaml_CR_BREAK // Use CR for line breaks (Mac style).
|
||||
yaml_LN_BREAK // Use LN for line breaks (Unix style).
|
||||
yaml_CRLN_BREAK // Use CR LN for line breaks (DOS style).
|
||||
)
|
||||
|
||||
type yaml_error_type_t int
|
||||
|
||||
// Many bad things could happen with the parser and emitter.
|
||||
const (
|
||||
// No error is produced.
|
||||
yaml_NO_ERROR yaml_error_type_t = iota
|
||||
|
||||
yaml_MEMORY_ERROR // Cannot allocate or reallocate a block of memory.
|
||||
yaml_READER_ERROR // Cannot read or decode the input stream.
|
||||
yaml_SCANNER_ERROR // Cannot scan the input stream.
|
||||
yaml_PARSER_ERROR // Cannot parse the input stream.
|
||||
yaml_COMPOSER_ERROR // Cannot compose a YAML document.
|
||||
yaml_WRITER_ERROR // Cannot write to the output stream.
|
||||
yaml_EMITTER_ERROR // Cannot emit a YAML stream.
|
||||
)
|
||||
|
||||
// The pointer position.
|
||||
type yaml_mark_t struct {
|
||||
index int // The position index.
|
||||
line int // The position line.
|
||||
column int // The position column.
|
||||
}
|
||||
|
||||
// Node Styles
|
||||
|
||||
type yaml_style_t int8
|
||||
|
||||
type yaml_scalar_style_t yaml_style_t
|
||||
|
||||
// Scalar styles.
|
||||
const (
|
||||
// Let the emitter choose the style.
|
||||
yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = iota
|
||||
|
||||
yaml_PLAIN_SCALAR_STYLE // The plain scalar style.
|
||||
yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style.
|
||||
yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style.
|
||||
yaml_LITERAL_SCALAR_STYLE // The literal scalar style.
|
||||
yaml_FOLDED_SCALAR_STYLE // The folded scalar style.
|
||||
)
|
||||
|
||||
type yaml_sequence_style_t yaml_style_t
|
||||
|
||||
// Sequence styles.
|
||||
const (
|
||||
// Let the emitter choose the style.
|
||||
yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota
|
||||
|
||||
yaml_BLOCK_SEQUENCE_STYLE // The block sequence style.
|
||||
yaml_FLOW_SEQUENCE_STYLE // The flow sequence style.
|
||||
)
|
||||
|
||||
type yaml_mapping_style_t yaml_style_t
|
||||
|
||||
// Mapping styles.
|
||||
const (
|
||||
// Let the emitter choose the style.
|
||||
yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota
|
||||
|
||||
yaml_BLOCK_MAPPING_STYLE // The block mapping style.
|
||||
yaml_FLOW_MAPPING_STYLE // The flow mapping style.
|
||||
)
|
||||
|
||||
// Tokens
|
||||
|
||||
type yaml_token_type_t int
|
||||
|
||||
// Token types.
|
||||
const (
|
||||
// An empty token.
|
||||
yaml_NO_TOKEN yaml_token_type_t = iota
|
||||
|
||||
yaml_STREAM_START_TOKEN // A STREAM-START token.
|
||||
yaml_STREAM_END_TOKEN // A STREAM-END token.
|
||||
|
||||
yaml_VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token.
|
||||
yaml_TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token.
|
||||
yaml_DOCUMENT_START_TOKEN // A DOCUMENT-START token.
|
||||
yaml_DOCUMENT_END_TOKEN // A DOCUMENT-END token.
|
||||
|
||||
yaml_BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token.
|
||||
yaml_BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token.
|
||||
yaml_BLOCK_END_TOKEN // A BLOCK-END token.
|
||||
|
||||
yaml_FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token.
|
||||
yaml_FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token.
|
||||
yaml_FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token.
|
||||
yaml_FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token.
|
||||
|
||||
yaml_BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token.
|
||||
yaml_FLOW_ENTRY_TOKEN // A FLOW-ENTRY token.
|
||||
yaml_KEY_TOKEN // A KEY token.
|
||||
yaml_VALUE_TOKEN // A VALUE token.
|
||||
|
||||
yaml_ALIAS_TOKEN // An ALIAS token.
|
||||
yaml_ANCHOR_TOKEN // An ANCHOR token.
|
||||
yaml_TAG_TOKEN // A TAG token.
|
||||
yaml_SCALAR_TOKEN // A SCALAR token.
|
||||
)
|
||||
|
||||
func (tt yaml_token_type_t) String() string {
|
||||
switch tt {
|
||||
case yaml_NO_TOKEN:
|
||||
return "yaml_NO_TOKEN"
|
||||
case yaml_STREAM_START_TOKEN:
|
||||
return "yaml_STREAM_START_TOKEN"
|
||||
case yaml_STREAM_END_TOKEN:
|
||||
return "yaml_STREAM_END_TOKEN"
|
||||
case yaml_VERSION_DIRECTIVE_TOKEN:
|
||||
return "yaml_VERSION_DIRECTIVE_TOKEN"
|
||||
case yaml_TAG_DIRECTIVE_TOKEN:
|
||||
return "yaml_TAG_DIRECTIVE_TOKEN"
|
||||
case yaml_DOCUMENT_START_TOKEN:
|
||||
return "yaml_DOCUMENT_START_TOKEN"
|
||||
case yaml_DOCUMENT_END_TOKEN:
|
||||
return "yaml_DOCUMENT_END_TOKEN"
|
||||
case yaml_BLOCK_SEQUENCE_START_TOKEN:
|
||||
return "yaml_BLOCK_SEQUENCE_START_TOKEN"
|
||||
case yaml_BLOCK_MAPPING_START_TOKEN:
|
||||
return "yaml_BLOCK_MAPPING_START_TOKEN"
|
||||
case yaml_BLOCK_END_TOKEN:
|
||||
return "yaml_BLOCK_END_TOKEN"
|
||||
case yaml_FLOW_SEQUENCE_START_TOKEN:
|
||||
return "yaml_FLOW_SEQUENCE_START_TOKEN"
|
||||
case yaml_FLOW_SEQUENCE_END_TOKEN:
|
||||
return "yaml_FLOW_SEQUENCE_END_TOKEN"
|
||||
case yaml_FLOW_MAPPING_START_TOKEN:
|
||||
return "yaml_FLOW_MAPPING_START_TOKEN"
|
||||
case yaml_FLOW_MAPPING_END_TOKEN:
|
||||
return "yaml_FLOW_MAPPING_END_TOKEN"
|
||||
case yaml_BLOCK_ENTRY_TOKEN:
|
||||
return "yaml_BLOCK_ENTRY_TOKEN"
|
||||
case yaml_FLOW_ENTRY_TOKEN:
|
||||
return "yaml_FLOW_ENTRY_TOKEN"
|
||||
case yaml_KEY_TOKEN:
|
||||
return "yaml_KEY_TOKEN"
|
||||
case yaml_VALUE_TOKEN:
|
||||
return "yaml_VALUE_TOKEN"
|
||||
case yaml_ALIAS_TOKEN:
|
||||
return "yaml_ALIAS_TOKEN"
|
||||
case yaml_ANCHOR_TOKEN:
|
||||
return "yaml_ANCHOR_TOKEN"
|
||||
case yaml_TAG_TOKEN:
|
||||
return "yaml_TAG_TOKEN"
|
||||
case yaml_SCALAR_TOKEN:
|
||||
return "yaml_SCALAR_TOKEN"
|
||||
}
|
||||
return "<unknown token>"
|
||||
}
|
||||
|
||||
// The token structure.
|
||||
type yaml_token_t struct {
|
||||
// The token type.
|
||||
typ yaml_token_type_t
|
||||
|
||||
// The start/end of the token.
|
||||
start_mark, end_mark yaml_mark_t
|
||||
|
||||
// The stream encoding (for yaml_STREAM_START_TOKEN).
|
||||
encoding yaml_encoding_t
|
||||
|
||||
// The alias/anchor/scalar value or tag/tag directive handle
|
||||
// (for yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN, yaml_TAG_TOKEN, yaml_TAG_DIRECTIVE_TOKEN).
|
||||
value []byte
|
||||
|
||||
// The tag suffix (for yaml_TAG_TOKEN).
|
||||
suffix []byte
|
||||
|
||||
// The tag directive prefix (for yaml_TAG_DIRECTIVE_TOKEN).
|
||||
prefix []byte
|
||||
|
||||
// The scalar style (for yaml_SCALAR_TOKEN).
|
||||
style yaml_scalar_style_t
|
||||
|
||||
// The version directive major/minor (for yaml_VERSION_DIRECTIVE_TOKEN).
|
||||
major, minor int8
|
||||
}
|
||||
|
||||
// Events
|
||||
|
||||
type yaml_event_type_t int8
|
||||
|
||||
// Event types.
|
||||
const (
|
||||
// An empty event.
|
||||
yaml_NO_EVENT yaml_event_type_t = iota
|
||||
|
||||
yaml_STREAM_START_EVENT // A STREAM-START event.
|
||||
yaml_STREAM_END_EVENT // A STREAM-END event.
|
||||
yaml_DOCUMENT_START_EVENT // A DOCUMENT-START event.
|
||||
yaml_DOCUMENT_END_EVENT // A DOCUMENT-END event.
|
||||
yaml_ALIAS_EVENT // An ALIAS event.
|
||||
yaml_SCALAR_EVENT // A SCALAR event.
|
||||
yaml_SEQUENCE_START_EVENT // A SEQUENCE-START event.
|
||||
yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event.
|
||||
yaml_MAPPING_START_EVENT // A MAPPING-START event.
|
||||
yaml_MAPPING_END_EVENT // A MAPPING-END event.
|
||||
)
|
||||
|
||||
var eventStrings = []string{
|
||||
yaml_NO_EVENT: "none",
|
||||
yaml_STREAM_START_EVENT: "stream start",
|
||||
yaml_STREAM_END_EVENT: "stream end",
|
||||
yaml_DOCUMENT_START_EVENT: "document start",
|
||||
yaml_DOCUMENT_END_EVENT: "document end",
|
||||
yaml_ALIAS_EVENT: "alias",
|
||||
yaml_SCALAR_EVENT: "scalar",
|
||||
yaml_SEQUENCE_START_EVENT: "sequence start",
|
||||
yaml_SEQUENCE_END_EVENT: "sequence end",
|
||||
yaml_MAPPING_START_EVENT: "mapping start",
|
||||
yaml_MAPPING_END_EVENT: "mapping end",
|
||||
}
|
||||
|
||||
func (e yaml_event_type_t) String() string {
|
||||
if e < 0 || int(e) >= len(eventStrings) {
|
||||
return fmt.Sprintf("unknown event %d", e)
|
||||
}
|
||||
return eventStrings[e]
|
||||
}
|
||||
|
||||
// The event structure.
|
||||
type yaml_event_t struct {
|
||||
|
||||
// The event type.
|
||||
typ yaml_event_type_t
|
||||
|
||||
// The start and end of the event.
|
||||
start_mark, end_mark yaml_mark_t
|
||||
|
||||
// The document encoding (for yaml_STREAM_START_EVENT).
|
||||
encoding yaml_encoding_t
|
||||
|
||||
// The version directive (for yaml_DOCUMENT_START_EVENT).
|
||||
version_directive *yaml_version_directive_t
|
||||
|
||||
// The list of tag directives (for yaml_DOCUMENT_START_EVENT).
|
||||
tag_directives []yaml_tag_directive_t
|
||||
|
||||
// The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT).
|
||||
anchor []byte
|
||||
|
||||
// The tag (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT).
|
||||
tag []byte
|
||||
|
||||
// The scalar value (for yaml_SCALAR_EVENT).
|
||||
value []byte
|
||||
|
||||
// Is the document start/end indicator implicit, or the tag optional?
|
||||
// (for yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_SCALAR_EVENT).
|
||||
implicit bool
|
||||
|
||||
// Is the tag optional for any non-plain style? (for yaml_SCALAR_EVENT).
|
||||
quoted_implicit bool
|
||||
|
||||
// The style (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT).
|
||||
style yaml_style_t
|
||||
}
|
||||
|
||||
func (e *yaml_event_t) scalar_style() yaml_scalar_style_t { return yaml_scalar_style_t(e.style) }
|
||||
func (e *yaml_event_t) sequence_style() yaml_sequence_style_t { return yaml_sequence_style_t(e.style) }
|
||||
func (e *yaml_event_t) mapping_style() yaml_mapping_style_t { return yaml_mapping_style_t(e.style) }
|
||||
|
||||
// Nodes
|
||||
|
||||
const (
|
||||
yaml_NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null.
|
||||
yaml_BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false.
|
||||
yaml_STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values.
|
||||
yaml_INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values.
|
||||
yaml_FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values.
|
||||
yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values.
|
||||
|
||||
yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences.
|
||||
yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping.
|
||||
|
||||
// Not in original libyaml.
|
||||
yaml_BINARY_TAG = "tag:yaml.org,2002:binary"
|
||||
yaml_MERGE_TAG = "tag:yaml.org,2002:merge"
|
||||
|
||||
yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str.
|
||||
yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq.
|
||||
yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map.
|
||||
)
|
||||
|
||||
type yaml_node_type_t int
|
||||
|
||||
// Node types.
|
||||
const (
|
||||
// An empty node.
|
||||
yaml_NO_NODE yaml_node_type_t = iota
|
||||
|
||||
yaml_SCALAR_NODE // A scalar node.
|
||||
yaml_SEQUENCE_NODE // A sequence node.
|
||||
yaml_MAPPING_NODE // A mapping node.
|
||||
)
|
||||
|
||||
// An element of a sequence node.
|
||||
type yaml_node_item_t int
|
||||
|
||||
// An element of a mapping node.
|
||||
type yaml_node_pair_t struct {
|
||||
key int // The key of the element.
|
||||
value int // The value of the element.
|
||||
}
|
||||
|
||||
// The node structure.
|
||||
type yaml_node_t struct {
|
||||
typ yaml_node_type_t // The node type.
|
||||
tag []byte // The node tag.
|
||||
|
||||
// The node data.
|
||||
|
||||
// The scalar parameters (for yaml_SCALAR_NODE).
|
||||
scalar struct {
|
||||
value []byte // The scalar value.
|
||||
length int // The length of the scalar value.
|
||||
style yaml_scalar_style_t // The scalar style.
|
||||
}
|
||||
|
||||
// The sequence parameters (for YAML_SEQUENCE_NODE).
|
||||
sequence struct {
|
||||
items_data []yaml_node_item_t // The stack of sequence items.
|
||||
style yaml_sequence_style_t // The sequence style.
|
||||
}
|
||||
|
||||
// The mapping parameters (for yaml_MAPPING_NODE).
|
||||
mapping struct {
|
||||
pairs_data []yaml_node_pair_t // The stack of mapping pairs (key, value).
|
||||
pairs_start *yaml_node_pair_t // The beginning of the stack.
|
||||
pairs_end *yaml_node_pair_t // The end of the stack.
|
||||
pairs_top *yaml_node_pair_t // The top of the stack.
|
||||
style yaml_mapping_style_t // The mapping style.
|
||||
}
|
||||
|
||||
start_mark yaml_mark_t // The beginning of the node.
|
||||
end_mark yaml_mark_t // The end of the node.
|
||||
|
||||
}
|
||||
|
||||
// The document structure.
|
||||
type yaml_document_t struct {
|
||||
|
||||
// The document nodes.
|
||||
nodes []yaml_node_t
|
||||
|
||||
// The version directive.
|
||||
version_directive *yaml_version_directive_t
|
||||
|
||||
// The list of tag directives.
|
||||
tag_directives_data []yaml_tag_directive_t
|
||||
tag_directives_start int // The beginning of the tag directives list.
|
||||
tag_directives_end int // The end of the tag directives list.
|
||||
|
||||
start_implicit int // Is the document start indicator implicit?
|
||||
end_implicit int // Is the document end indicator implicit?
|
||||
|
||||
// The start/end of the document.
|
||||
start_mark, end_mark yaml_mark_t
|
||||
}
|
||||
|
||||
// The prototype of a read handler.
|
||||
//
|
||||
// The read handler is called when the parser needs to read more bytes from the
|
||||
// source. The handler should write not more than size bytes to the buffer.
|
||||
// The number of written bytes should be set to the size_read variable.
|
||||
//
|
||||
// [in,out] data A pointer to an application data specified by
|
||||
// yaml_parser_set_input().
|
||||
// [out] buffer The buffer to write the data from the source.
|
||||
// [in] size The size of the buffer.
|
||||
// [out] size_read The actual number of bytes read from the source.
|
||||
//
|
||||
// On success, the handler should return 1. If the handler failed,
|
||||
// the returned value should be 0. On EOF, the handler should set the
|
||||
// size_read to 0 and return 1.
|
||||
type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error)
|
||||
|
||||
// This structure holds information about a potential simple key.
|
||||
type yaml_simple_key_t struct {
|
||||
possible bool // Is a simple key possible?
|
||||
required bool // Is a simple key required?
|
||||
token_number int // The number of the token.
|
||||
mark yaml_mark_t // The position mark.
|
||||
}
|
||||
|
||||
// The states of the parser.
|
||||
type yaml_parser_state_t int
|
||||
|
||||
const (
|
||||
yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota
|
||||
|
||||
yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document.
|
||||
yaml_PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START.
|
||||
yaml_PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document.
|
||||
yaml_PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END.
|
||||
yaml_PARSE_BLOCK_NODE_STATE // Expect a block node.
|
||||
yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE // Expect a block node or indentless sequence.
|
||||
yaml_PARSE_FLOW_NODE_STATE // Expect a flow node.
|
||||
yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence.
|
||||
yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence.
|
||||
yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence.
|
||||
yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping.
|
||||
yaml_PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key.
|
||||
yaml_PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value.
|
||||
yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry.
|
||||
yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping.
|
||||
yaml_PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping.
|
||||
yaml_PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping.
|
||||
yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping.
|
||||
yaml_PARSE_END_STATE // Expect nothing.
|
||||
)
|
||||
|
||||
func (ps yaml_parser_state_t) String() string {
|
||||
switch ps {
|
||||
case yaml_PARSE_STREAM_START_STATE:
|
||||
return "yaml_PARSE_STREAM_START_STATE"
|
||||
case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE:
|
||||
return "yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE"
|
||||
case yaml_PARSE_DOCUMENT_START_STATE:
|
||||
return "yaml_PARSE_DOCUMENT_START_STATE"
|
||||
case yaml_PARSE_DOCUMENT_CONTENT_STATE:
|
||||
return "yaml_PARSE_DOCUMENT_CONTENT_STATE"
|
||||
case yaml_PARSE_DOCUMENT_END_STATE:
|
||||
return "yaml_PARSE_DOCUMENT_END_STATE"
|
||||
case yaml_PARSE_BLOCK_NODE_STATE:
|
||||
return "yaml_PARSE_BLOCK_NODE_STATE"
|
||||
case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE:
|
||||
return "yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE"
|
||||
case yaml_PARSE_FLOW_NODE_STATE:
|
||||
return "yaml_PARSE_FLOW_NODE_STATE"
|
||||
case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE:
|
||||
return "yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE"
|
||||
case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE:
|
||||
return "yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE"
|
||||
case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE:
|
||||
return "yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE"
|
||||
case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE:
|
||||
return "yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE"
|
||||
case yaml_PARSE_BLOCK_MAPPING_KEY_STATE:
|
||||
return "yaml_PARSE_BLOCK_MAPPING_KEY_STATE"
|
||||
case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE:
|
||||
return "yaml_PARSE_BLOCK_MAPPING_VALUE_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_KEY_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_KEY_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_VALUE_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_VALUE_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE"
|
||||
case yaml_PARSE_END_STATE:
|
||||
return "yaml_PARSE_END_STATE"
|
||||
}
|
||||
return "<unknown parser state>"
|
||||
}
|
||||
|
||||
// This structure holds aliases data.
|
||||
type yaml_alias_data_t struct {
|
||||
anchor []byte // The anchor.
|
||||
index int // The node id.
|
||||
mark yaml_mark_t // The anchor mark.
|
||||
}
|
||||
|
||||
// The parser structure.
|
||||
//
|
||||
// All members are internal. Manage the structure using the
|
||||
// yaml_parser_ family of functions.
|
||||
type yaml_parser_t struct {
|
||||
|
||||
// Error handling
|
||||
|
||||
error yaml_error_type_t // Error type.
|
||||
|
||||
problem string // Error description.
|
||||
|
||||
// The byte about which the problem occurred.
|
||||
problem_offset int
|
||||
problem_value int
|
||||
problem_mark yaml_mark_t
|
||||
|
||||
// The error context.
|
||||
context string
|
||||
context_mark yaml_mark_t
|
||||
|
||||
// Reader stuff
|
||||
|
||||
read_handler yaml_read_handler_t // Read handler.
|
||||
|
||||
input_reader io.Reader // File input data.
|
||||
input []byte // String input data.
|
||||
input_pos int
|
||||
|
||||
eof bool // EOF flag
|
||||
|
||||
buffer []byte // The working buffer.
|
||||
buffer_pos int // The current position of the buffer.
|
||||
|
||||
unread int // The number of unread characters in the buffer.
|
||||
|
||||
raw_buffer []byte // The raw buffer.
|
||||
raw_buffer_pos int // The current position of the buffer.
|
||||
|
||||
encoding yaml_encoding_t // The input encoding.
|
||||
|
||||
offset int // The offset of the current position (in bytes).
|
||||
mark yaml_mark_t // The mark of the current position.
|
||||
|
||||
// Scanner stuff
|
||||
|
||||
stream_start_produced bool // Have we started to scan the input stream?
|
||||
stream_end_produced bool // Have we reached the end of the input stream?
|
||||
|
||||
flow_level int // The number of unclosed '[' and '{' indicators.
|
||||
|
||||
tokens []yaml_token_t // The tokens queue.
|
||||
tokens_head int // The head of the tokens queue.
|
||||
tokens_parsed int // The number of tokens fetched from the queue.
|
||||
token_available bool // Does the tokens queue contain a token ready for dequeueing.
|
||||
|
||||
indent int // The current indentation level.
|
||||
indents []int // The indentation levels stack.
|
||||
|
||||
simple_key_allowed bool // May a simple key occur at the current position?
|
||||
simple_keys []yaml_simple_key_t // The stack of simple keys.
|
||||
|
||||
// Parser stuff
|
||||
|
||||
state yaml_parser_state_t // The current parser state.
|
||||
states []yaml_parser_state_t // The parser states stack.
|
||||
marks []yaml_mark_t // The stack of marks.
|
||||
tag_directives []yaml_tag_directive_t // The list of TAG directives.
|
||||
|
||||
// Dumper stuff
|
||||
|
||||
aliases []yaml_alias_data_t // The alias data.
|
||||
|
||||
document *yaml_document_t // The currently parsed document.
|
||||
}
|
||||
|
||||
// Emitter Definitions
|
||||
|
||||
// The prototype of a write handler.
|
||||
//
|
||||
// The write handler is called when the emitter needs to flush the accumulated
|
||||
// characters to the output. The handler should write @a size bytes of the
|
||||
// @a buffer to the output.
|
||||
//
|
||||
// @param[in,out] data A pointer to an application data specified by
|
||||
// yaml_emitter_set_output().
|
||||
// @param[in] buffer The buffer with bytes to be written.
|
||||
// @param[in] size The size of the buffer.
|
||||
//
|
||||
// @returns On success, the handler should return @c 1. If the handler failed,
|
||||
// the returned value should be @c 0.
|
||||
//
|
||||
type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error
|
||||
|
||||
type yaml_emitter_state_t int
|
||||
|
||||
// The emitter states.
|
||||
const (
|
||||
// Expect STREAM-START.
|
||||
yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota
|
||||
|
||||
yaml_EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END.
|
||||
yaml_EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END.
|
||||
yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document.
|
||||
yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END.
|
||||
yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence.
|
||||
yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence.
|
||||
yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping.
|
||||
yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping.
|
||||
yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping.
|
||||
yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping.
|
||||
yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence.
|
||||
yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence.
|
||||
yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping.
|
||||
yaml_EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping.
|
||||
yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping.
|
||||
yaml_EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping.
|
||||
yaml_EMIT_END_STATE // Expect nothing.
|
||||
)
|
||||
|
||||
// The emitter structure.
|
||||
//
|
||||
// All members are internal. Manage the structure using the @c yaml_emitter_
|
||||
// family of functions.
|
||||
type yaml_emitter_t struct {
|
||||
|
||||
// Error handling
|
||||
|
||||
error yaml_error_type_t // Error type.
|
||||
problem string // Error description.
|
||||
|
||||
// Writer stuff
|
||||
|
||||
write_handler yaml_write_handler_t // Write handler.
|
||||
|
||||
output_buffer *[]byte // String output data.
|
||||
output_writer io.Writer // File output data.
|
||||
|
||||
buffer []byte // The working buffer.
|
||||
buffer_pos int // The current position of the buffer.
|
||||
|
||||
raw_buffer []byte // The raw buffer.
|
||||
raw_buffer_pos int // The current position of the buffer.
|
||||
|
||||
encoding yaml_encoding_t // The stream encoding.
|
||||
|
||||
// Emitter stuff
|
||||
|
||||
canonical bool // If the output is in the canonical style?
|
||||
best_indent int // The number of indentation spaces.
|
||||
best_width int // The preferred width of the output lines.
|
||||
unicode bool // Allow unescaped non-ASCII characters?
|
||||
line_break yaml_break_t // The preferred line break.
|
||||
|
||||
state yaml_emitter_state_t // The current emitter state.
|
||||
states []yaml_emitter_state_t // The stack of states.
|
||||
|
||||
events []yaml_event_t // The event queue.
|
||||
events_head int // The head of the event queue.
|
||||
|
||||
indents []int // The stack of indentation levels.
|
||||
|
||||
tag_directives []yaml_tag_directive_t // The list of tag directives.
|
||||
|
||||
indent int // The current indentation level.
|
||||
|
||||
flow_level int // The current flow level.
|
||||
|
||||
root_context bool // Is it the document root context?
|
||||
sequence_context bool // Is it a sequence context?
|
||||
mapping_context bool // Is it a mapping context?
|
||||
simple_key_context bool // Is it a simple mapping key context?
|
||||
|
||||
line int // The current line.
|
||||
column int // The current column.
|
||||
whitespace bool // If the last character was a whitespace?
|
||||
indention bool // If the last character was an indentation character (' ', '-', '?', ':')?
|
||||
open_ended bool // If an explicit document end is required?
|
||||
|
||||
// Anchor analysis.
|
||||
anchor_data struct {
|
||||
anchor []byte // The anchor value.
|
||||
alias bool // Is it an alias?
|
||||
}
|
||||
|
||||
// Tag analysis.
|
||||
tag_data struct {
|
||||
handle []byte // The tag handle.
|
||||
suffix []byte // The tag suffix.
|
||||
}
|
||||
|
||||
// Scalar analysis.
|
||||
scalar_data struct {
|
||||
value []byte // The scalar value.
|
||||
multiline bool // Does the scalar contain line breaks?
|
||||
flow_plain_allowed bool // Can the scalar be expessed in the flow plain style?
|
||||
block_plain_allowed bool // Can the scalar be expressed in the block plain style?
|
||||
single_quoted_allowed bool // Can the scalar be expressed in the single quoted style?
|
||||
block_allowed bool // Can the scalar be expressed in the literal or folded styles?
|
||||
style yaml_scalar_style_t // The output style.
|
||||
}
|
||||
|
||||
// Dumper stuff
|
||||
|
||||
opened bool // If the stream was already opened?
|
||||
closed bool // If the stream was already closed?
|
||||
|
||||
// The information associated with the document nodes.
|
||||
anchors *struct {
|
||||
references int // The number of references.
|
||||
anchor int // The anchor id.
|
||||
serialized bool // If the node has been emitted?
|
||||
}
|
||||
|
||||
last_anchor_id int // The last assigned anchor id.
|
||||
|
||||
document *yaml_document_t // The currently emitted document.
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
package yaml
|
||||
|
||||
const (
|
||||
// The size of the input raw buffer.
|
||||
input_raw_buffer_size = 512
|
||||
|
||||
// The size of the input buffer.
|
||||
// It should be possible to decode the whole raw buffer.
|
||||
input_buffer_size = input_raw_buffer_size * 3
|
||||
|
||||
// The size of the output buffer.
|
||||
output_buffer_size = 128
|
||||
|
||||
// The size of the output raw buffer.
|
||||
// It should be possible to encode the whole output buffer.
|
||||
output_raw_buffer_size = (output_buffer_size*2 + 2)
|
||||
|
||||
// The size of other stacks and queues.
|
||||
initial_stack_size = 16
|
||||
initial_queue_size = 16
|
||||
initial_string_size = 16
|
||||
)
|
||||
|
||||
// Check if the character at the specified position is an alphabetical
|
||||
// character, a digit, '_', or '-'.
|
||||
func is_alpha(b []byte, i int) bool {
|
||||
return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is a digit.
|
||||
func is_digit(b []byte, i int) bool {
|
||||
return b[i] >= '0' && b[i] <= '9'
|
||||
}
|
||||
|
||||
// Get the value of a digit.
|
||||
func as_digit(b []byte, i int) int {
|
||||
return int(b[i]) - '0'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is a hex-digit.
|
||||
func is_hex(b []byte, i int) bool {
|
||||
return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f'
|
||||
}
|
||||
|
||||
// Get the value of a hex-digit.
|
||||
func as_hex(b []byte, i int) int {
|
||||
bi := b[i]
|
||||
if bi >= 'A' && bi <= 'F' {
|
||||
return int(bi) - 'A' + 10
|
||||
}
|
||||
if bi >= 'a' && bi <= 'f' {
|
||||
return int(bi) - 'a' + 10
|
||||
}
|
||||
return int(bi) - '0'
|
||||
}
|
||||
|
||||
// Check if the character is ASCII.
|
||||
func is_ascii(b []byte, i int) bool {
|
||||
return b[i] <= 0x7F
|
||||
}
|
||||
|
||||
// Check if the character at the start of the buffer can be printed unescaped.
|
||||
func is_printable(b []byte, i int) bool {
|
||||
return ((b[i] == 0x0A) || // . == #x0A
|
||||
(b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E
|
||||
(b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF
|
||||
(b[i] > 0xC2 && b[i] < 0xED) ||
|
||||
(b[i] == 0xED && b[i+1] < 0xA0) ||
|
||||
(b[i] == 0xEE) ||
|
||||
(b[i] == 0xEF && // #xE000 <= . <= #xFFFD
|
||||
!(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF
|
||||
!(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF))))
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is NUL.
|
||||
func is_z(b []byte, i int) bool {
|
||||
return b[i] == 0x00
|
||||
}
|
||||
|
||||
// Check if the beginning of the buffer is a BOM.
|
||||
func is_bom(b []byte, i int) bool {
|
||||
return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is space.
|
||||
func is_space(b []byte, i int) bool {
|
||||
return b[i] == ' '
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is tab.
|
||||
func is_tab(b []byte, i int) bool {
|
||||
return b[i] == '\t'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is blank (space or tab).
|
||||
func is_blank(b []byte, i int) bool {
|
||||
//return is_space(b, i) || is_tab(b, i)
|
||||
return b[i] == ' ' || b[i] == '\t'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is a line break.
|
||||
func is_break(b []byte, i int) bool {
|
||||
return (b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029)
|
||||
}
|
||||
|
||||
func is_crlf(b []byte, i int) bool {
|
||||
return b[i] == '\r' && b[i+1] == '\n'
|
||||
}
|
||||
|
||||
// Check if the character is a line break or NUL.
|
||||
func is_breakz(b []byte, i int) bool {
|
||||
//return is_break(b, i) || is_z(b, i)
|
||||
return ( // is_break:
|
||||
b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||
// is_z:
|
||||
b[i] == 0)
|
||||
}
|
||||
|
||||
// Check if the character is a line break, space, or NUL.
|
||||
func is_spacez(b []byte, i int) bool {
|
||||
//return is_space(b, i) || is_breakz(b, i)
|
||||
return ( // is_space:
|
||||
b[i] == ' ' ||
|
||||
// is_breakz:
|
||||
b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||
b[i] == 0)
|
||||
}
|
||||
|
||||
// Check if the character is a line break, space, tab, or NUL.
|
||||
func is_blankz(b []byte, i int) bool {
|
||||
//return is_blank(b, i) || is_breakz(b, i)
|
||||
return ( // is_blank:
|
||||
b[i] == ' ' || b[i] == '\t' ||
|
||||
// is_breakz:
|
||||
b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||
b[i] == 0)
|
||||
}
|
||||
|
||||
// Determine the width of the character.
|
||||
func width(b byte) int {
|
||||
// Don't replace these by a switch without first
|
||||
// confirming that it is being inlined.
|
||||
if b&0x80 == 0x00 {
|
||||
return 1
|
||||
}
|
||||
if b&0xE0 == 0xC0 {
|
||||
return 2
|
||||
}
|
||||
if b&0xF0 == 0xE0 {
|
||||
return 3
|
||||
}
|
||||
if b&0xF8 == 0xF0 {
|
||||
return 4
|
||||
}
|
||||
return 0
|
||||
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
// Copyright 2014 Google Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// This binary compares memory usage between btree and gollrb.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/google/btree"
|
||||
"github.com/petar/GoLLRB/llrb"
|
||||
)
|
||||
|
||||
var (
|
||||
size = flag.Int("size", 1000000, "size of the tree to build")
|
||||
degree = flag.Int("degree", 8, "degree of btree")
|
||||
gollrb = flag.Bool("llrb", false, "use llrb instead of btree")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
vals := rand.Perm(*size)
|
||||
var t, v interface{}
|
||||
v = vals
|
||||
var stats runtime.MemStats
|
||||
for i := 0; i < 10; i++ {
|
||||
runtime.GC()
|
||||
}
|
||||
fmt.Println("-------- BEFORE ----------")
|
||||
runtime.ReadMemStats(&stats)
|
||||
fmt.Printf("%+v\n", stats)
|
||||
start := time.Now()
|
||||
if *gollrb {
|
||||
tr := llrb.New()
|
||||
for _, v := range vals {
|
||||
tr.ReplaceOrInsert(llrb.Int(v))
|
||||
}
|
||||
t = tr // keep it around
|
||||
} else {
|
||||
tr := btree.New(*degree)
|
||||
for _, v := range vals {
|
||||
tr.ReplaceOrInsert(btree.Int(v))
|
||||
}
|
||||
t = tr // keep it around
|
||||
}
|
||||
fmt.Printf("%v inserts in %v\n", *size, time.Since(start))
|
||||
fmt.Println("-------- AFTER ----------")
|
||||
runtime.ReadMemStats(&stats)
|
||||
fmt.Printf("%+v\n", stats)
|
||||
for i := 0; i < 10; i++ {
|
||||
runtime.GC()
|
||||
}
|
||||
fmt.Println("-------- AFTER GC ----------")
|
||||
runtime.ReadMemStats(&stats)
|
||||
fmt.Printf("%+v\n", stats)
|
||||
if t == v {
|
||||
fmt.Println("to make sure vals and tree aren't GC'd")
|
||||
}
|
||||
}
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
// Copyright 2012 Google, Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// This binary pulls known ports from IANA, and uses them to populate
|
||||
// iana_ports.go's TCPPortNames and UDPPortNames maps.
|
||||
//
|
||||
// go run gen.go | gofmt > iana_ports.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const fmtString = `// Copyright 2012 Google, Inc. All rights reserved.
|
||||
|
||||
package layers
|
||||
|
||||
// Created by gen.go, don't edit manually
|
||||
// Generated at %s
|
||||
// Fetched from %q
|
||||
|
||||
// TCPPortNames contains the port names for all TCP ports.
|
||||
var TCPPortNames = tcpPortNames
|
||||
|
||||
// UDPPortNames contains the port names for all UDP ports.
|
||||
var UDPPortNames = udpPortNames
|
||||
|
||||
// SCTPPortNames contains the port names for all SCTP ports.
|
||||
var SCTPPortNames = sctpPortNames
|
||||
|
||||
var tcpPortNames = map[TCPPort]string{
|
||||
%s}
|
||||
var udpPortNames = map[UDPPort]string{
|
||||
%s}
|
||||
var sctpPortNames = map[SCTPPort]string{
|
||||
%s}
|
||||
`
|
||||
|
||||
var url = flag.String("url", "http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml", "URL to grab port numbers from")
|
||||
|
||||
func main() {
|
||||
fmt.Fprintf(os.Stderr, "Fetching ports from %q\n", *url)
|
||||
resp, err := http.Get(*url)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "Parsing XML")
|
||||
var registry struct {
|
||||
Records []struct {
|
||||
Protocol string `xml:"protocol"`
|
||||
Number string `xml:"number"`
|
||||
Name string `xml:"name"`
|
||||
} `xml:"record"`
|
||||
}
|
||||
xml.Unmarshal(body, ®istry)
|
||||
var tcpPorts bytes.Buffer
|
||||
var udpPorts bytes.Buffer
|
||||
var sctpPorts bytes.Buffer
|
||||
done := map[string]map[int]bool{
|
||||
"tcp": map[int]bool{},
|
||||
"udp": map[int]bool{},
|
||||
"sctp": map[int]bool{},
|
||||
}
|
||||
for _, r := range registry.Records {
|
||||
port, err := strconv.Atoi(r.Number)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if r.Name == "" {
|
||||
continue
|
||||
}
|
||||
var b *bytes.Buffer
|
||||
switch r.Protocol {
|
||||
case "tcp":
|
||||
b = &tcpPorts
|
||||
case "udp":
|
||||
b = &udpPorts
|
||||
case "sctp":
|
||||
b = &sctpPorts
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if done[r.Protocol][port] {
|
||||
continue
|
||||
}
|
||||
done[r.Protocol][port] = true
|
||||
fmt.Fprintf(b, "\t%d: %q,\n", port, r.Name)
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "Writing results to stdout")
|
||||
fmt.Printf(fmtString, time.Now(), *url, tcpPorts.String(), udpPorts.String(), sctpPorts.String())
|
||||
}
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
// Copyright 2012 Google, Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// This binary handles creating string constants and function templates for enums.
|
||||
//
|
||||
// go run gen.go | gofmt > enums_generated.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
const fmtString = `// Copyright 2012 Google, Inc. All rights reserved.
|
||||
|
||||
package layers
|
||||
|
||||
// Created by gen2.go, don't edit manually
|
||||
// Generated at %s
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
)
|
||||
|
||||
`
|
||||
|
||||
var funcsTmpl = template.Must(template.New("foo").Parse(`
|
||||
// Decoder calls {{.Name}}Metadata.DecodeWith's decoder.
|
||||
func (a {{.Name}}) Decode(data []byte, p gopacket.PacketBuilder) error {
|
||||
return {{.Name}}Metadata[a].DecodeWith.Decode(data, p)
|
||||
}
|
||||
// String returns {{.Name}}Metadata.Name.
|
||||
func (a {{.Name}}) String() string {
|
||||
return {{.Name}}Metadata[a].Name
|
||||
}
|
||||
// LayerType returns {{.Name}}Metadata.LayerType.
|
||||
func (a {{.Name}}) LayerType() gopacket.LayerType {
|
||||
return {{.Name}}Metadata[a].LayerType
|
||||
}
|
||||
|
||||
type errorDecoderFor{{.Name}} int
|
||||
func (a *errorDecoderFor{{.Name}}) Decode(data []byte, p gopacket.PacketBuilder) error {
|
||||
return a
|
||||
}
|
||||
func (a *errorDecoderFor{{.Name}}) Error() string {
|
||||
return fmt.Sprintf("Unable to decode {{.Name}} %d", int(*a))
|
||||
}
|
||||
|
||||
var errorDecodersFor{{.Name}} [{{.Num}}]errorDecoderFor{{.Name}}
|
||||
var {{.Name}}Metadata [{{.Num}}]EnumMetadata
|
||||
|
||||
func initUnknownTypesFor{{.Name}}() {
|
||||
for i := 0; i < {{.Num}}; i++ {
|
||||
errorDecodersFor{{.Name}}[i] = errorDecoderFor{{.Name}}(i)
|
||||
{{.Name}}Metadata[i] = EnumMetadata{
|
||||
DecodeWith: &errorDecodersFor{{.Name}}[i],
|
||||
Name: "Unknown{{.Name}}",
|
||||
}
|
||||
}
|
||||
}
|
||||
`))
|
||||
|
||||
func main() {
|
||||
fmt.Fprintf(os.Stderr, "Writing results to stdout\n")
|
||||
fmt.Printf(fmtString, time.Now())
|
||||
types := []struct {
|
||||
Name string
|
||||
Num int
|
||||
}{
|
||||
{"LinkType", 256},
|
||||
{"EthernetType", 65536},
|
||||
{"PPPType", 65536},
|
||||
{"IPProtocol", 256},
|
||||
{"SCTPChunkType", 256},
|
||||
{"PPPoECode", 256},
|
||||
{"FDDIFrameControl", 256},
|
||||
{"EAPOLType", 256},
|
||||
{"ProtocolFamily", 256},
|
||||
{"Dot11Type", 256},
|
||||
{"USBTransportType", 256},
|
||||
}
|
||||
|
||||
fmt.Println("func init() {")
|
||||
for _, t := range types {
|
||||
fmt.Printf("initUnknownTypesFor%s()\n", t.Name)
|
||||
}
|
||||
fmt.Println("initActualTypeData()")
|
||||
fmt.Println("}")
|
||||
for _, t := range types {
|
||||
if err := funcsTmpl.Execute(os.Stdout, t); err != nil {
|
||||
log.Fatalf("Failed to execute template %s: %v", t.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
package pty
|
||||
|
||||
import "C"
|
||||
|
||||
type (
|
||||
_C_int C.int
|
||||
_C_uint C.uint
|
||||
)
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
// +build ignore
|
||||
|
||||
package pty
|
||||
|
||||
/*
|
||||
#define _KERNEL
|
||||
#include <sys/conf.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/filio.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
_C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */
|
||||
)
|
||||
|
||||
type fiodgnameArg C.struct_fiodname_args
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user