mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Merge pull request #1210 from yousong/feature/yousong-ansibleserver
ansibleserver: 添加文件支持
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user