Merge pull request #1210 from yousong/feature/yousong-ansibleserver

ansibleserver: 添加文件支持
This commit is contained in:
yunion-ci-robot
2019-06-20 17:49:48 +08:00
committed by GitHub
6 changed files with 142 additions and 17 deletions
+3 -2
View File
@@ -82,8 +82,8 @@ func init() {
}
func (man *SAnsiblePlaybookManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
v := NewAnsiblePlaybookValidator("playbook", userCred)
if err := v.Validate(data); err != nil {
pbV := NewAnsiblePlaybookValidator("playbook", userCred)
if err := pbV.Validate(data); err != nil {
return nil, err
}
data.Set("status", jsonutils.NewString(AnsiblePlaybookStatusInit))
@@ -244,6 +244,7 @@ func (apb *SAnsiblePlaybook) runPlaybook(ctx context.Context, userCred mcclient.
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
+29
View File
@@ -0,0 +1,29 @@
// 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 ansible
import (
"yunion.io/x/onecloud/pkg/apis"
"yunion.io/x/onecloud/pkg/util/ansible"
)
type AnsiblePlaybookCreateInput struct {
apis.Meta
Name string
Playbook ansible.Playbook
}
type AnsiblePlaybookUpdateInput AnsiblePlaybookCreateInput
+16
View File
@@ -0,0 +1,16 @@
package models
import (
"time"
"yunion.io/x/onecloud/pkg/util/ansible"
)
type AnsiblePlaybook struct {
VirtualResource
Playbook *ansible.Playbook
Output string
StartTime time.Time
EndTime time.Time
}
+43 -11
View File
@@ -16,9 +16,12 @@ package options
import (
"fmt"
"io/ioutil"
"strings"
"yunion.io/x/jsonutils"
apis "yunion.io/x/onecloud/pkg/apis/ansible"
"yunion.io/x/onecloud/pkg/util/ansible"
)
@@ -33,9 +36,10 @@ type AnsiblePlaybookListOptions struct {
type AnsiblePlaybookCommonOptions struct {
Host []string `help:"name or id of server or host in format '<[server:]id|host:id>|ipaddr var=val'"`
Mod []string `help:"ansible modules and their arguments in format 'name k1=v1 k2=v2'"`
File []string `help:"files for use by modules, e.g. name=content, name=@file"`
}
func (opts *AnsiblePlaybookCommonOptions) params() (jsonutils.JSONObject, error) {
func (opts *AnsiblePlaybookCommonOptions) ToPlaybook() (*ansible.Playbook, error) {
if len(opts.Mod) == 0 {
return nil, fmt.Errorf("Requires at least one --mod argument")
}
@@ -59,8 +63,30 @@ func (opts *AnsiblePlaybookCommonOptions) params() (jsonutils.JSONObject, error)
}
pb.Modules = append(pb.Modules, module)
}
pbJson := jsonutils.Marshal(pb)
return pbJson, nil
files := map[string][]byte{}
for _, s := range opts.File {
i := strings.IndexByte(s, '=')
if i < 0 {
return nil, fmt.Errorf("missing '=' in argument for --file. Read command help")
}
name := strings.TrimSpace(s[:i])
if name == "" {
return nil, fmt.Errorf("empty file name: %s", s)
}
v := s[i+1:]
if len(v) > 0 && v[0] == '@' {
path := v[1:]
d, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read file %s: %v", path, err)
}
files[name] = d
} else {
files[name] = []byte(v)
}
}
pb.Files = files
return pb, nil
}
type AnsiblePlaybookCreateOptions struct {
@@ -69,27 +95,33 @@ type AnsiblePlaybookCreateOptions struct {
}
func (opts *AnsiblePlaybookCreateOptions) Params() (*jsonutils.JSONDict, error) {
pbJson, err := opts.AnsiblePlaybookCommonOptions.params()
pb, err := opts.AnsiblePlaybookCommonOptions.ToPlaybook()
if err != nil {
return nil, err
}
params := jsonutils.NewDict()
params.Set("playbook", pbJson)
params.Set("name", jsonutils.NewString(opts.NAME))
input := &apis.AnsiblePlaybookCreateInput{
Name: opts.NAME,
Playbook: *pb,
}
params := input.JSON(input)
return params, nil
}
type AnsiblePlaybookUpdateOptions struct {
ID string `json:"-" help:"name/id of the playbook"`
ID string `json:"-" help:"name/id of the playbook"`
Name string
AnsiblePlaybookCommonOptions
}
func (opts *AnsiblePlaybookUpdateOptions) Params() (*jsonutils.JSONDict, error) {
pbJson, err := opts.AnsiblePlaybookCommonOptions.params()
pb, err := opts.AnsiblePlaybookCommonOptions.ToPlaybook()
if err != nil {
return nil, err
}
params := jsonutils.NewDict()
params.Set("playbook", pbJson)
input := &apis.AnsiblePlaybookUpdateInput{
Name: opts.Name,
Playbook: *pb,
}
params := input.JSON(input)
return params, nil
}
+19
View File
@@ -55,6 +55,7 @@ type Playbook struct {
Inventory Inventory
Modules []Module
PrivateKey []byte
Files map[string][]byte
tmpdir string
noCleanOnExit bool
@@ -77,6 +78,7 @@ func (pb *Playbook) Copy() *Playbook {
pb1.Inventory = gotypes.DeepCopy(pb.Inventory).(Inventory)
pb1.Modules = gotypes.DeepCopy(pb.Modules).([]Module)
pb1.PrivateKey = gotypes.DeepCopy(pb.PrivateKey).([]byte)
pb1.Files = gotypes.DeepCopy(pb.Files).(map[string][]byte)
return pb1
}
@@ -160,6 +162,22 @@ func (pb *Playbook) Run(ctx context.Context) (err error) {
}
}
// write out files
for name, content := range pb.Files {
path := filepath.Join(tmpdir, "files", 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() {
@@ -185,6 +203,7 @@ func (pb *Playbook) Run(ctx context.Context) (err error) {
args = append(args, "--private-key", privateKey)
}
cmd := exec.CommandContext(ctx, "ansible", args...)
cmd.Dir = pb.tmpdir
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "ANSIBLE_HOST_KEY_CHECKING=False")
stdout, _ := cmd.StdoutPipe()
+32 -4
View File
@@ -17,6 +17,7 @@ package ansible
import (
"context"
"os/exec"
"reflect"
"testing"
)
@@ -45,10 +46,37 @@ func TestPlaybook(t *testing.T) {
{
Name: "ping",
},
{
Name: "copy",
Args: []string{
"src=afile",
"dest=/tmp/afile",
},
},
{
Name: "copy",
Args: []string{
"src=adir/afile",
"dest=/tmp/adirfile",
},
},
}
err := pb.Run(context.TODO())
if err != nil {
t.Fatalf("not expecting err: %v", err)
pb.Files = map[string][]byte{
"afile": []byte("afilecontent"),
"adir/afile": []byte("afilecontent under adir"),
}
t.Logf("%s", pb.Output())
t.Run("copy", func(t *testing.T) {
pb2 := pb.Copy()
if !reflect.DeepEqual(pb2, pb) {
t.Errorf("copy and the original should be equal")
}
})
t.Run("run", func(t *testing.T) {
err := pb.Run(context.TODO())
t.Logf("%s", pb.Output())
if err != nil {
t.Fatalf("not expecting err: %v", err)
}
})
}