feat(ansibleserver): add reference and instance

Reference is a reference to a local or remote playbook without certain inventory
and its location is determined by playbookPath.
Instance is a complete playbook that can be executed directly, it is filled with inventory and config.
Instance ensures that the playbook is executed successfully as far as
possible without exceeding the maximum number of attempts
This commit is contained in:
rainzm
2021-01-09 16:54:57 +08:00
parent 4250b14754
commit f8f99365a2
20 changed files with 1145 additions and 540 deletions
@@ -0,0 +1,34 @@
// 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/cmd/climc/shell"
"yunion.io/x/onecloud/pkg/mcclient/modules"
options "yunion.io/x/onecloud/pkg/mcclient/options/ansible"
)
func init() {
cmd := shell.NewResourceCmd(&modules.AnsiblePlaybookReference).WithKeyword("ansibleplaybook-reference")
cmd.List(new(options.APRListOptions))
cmd.Show(new(options.APROptions))
cmd.Perform("run", new(options.APRRunOptions))
cmd.Perform("stop", new(options.APRStopOptions))
cmd1 := shell.NewResourceCmd(&modules.AnsiblePlaybookInstance).WithKeyword("ansibleplaybook-instance")
cmd1.List(new(options.APIListOptions))
cmd1.Show(new(options.APIOptions))
cmd1.Perform("run", new(options.APIOptions))
}
-222
View File
@@ -1,222 +0,0 @@
// 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 (
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
func paramValidator(param jsonutils.JSONObject) (bool, error) {
// TODO 移到 server 端
log.Infof("paramValidator: param: %+v", param)
interval, err := param.Int("interval")
if err != nil {
return true, nil
}
day, err := param.Int("day")
if err != nil {
return true, nil
}
if interval == 0 && day == 0 {
return false, fmt.Errorf("interval and day can not be 0 at the same time")
}
return true, nil
}
func init() {
type CronjobListOptions struct {
options.BaseListOptions
Name string `help:"cloud region ID or Name" json:"-"`
}
R(&CronjobListOptions{}, "devtoolcronjob-list", "List Devtool Cronjobs", func(s *mcclient.ClientSession, args *CronjobListOptions) error {
params, err := options.ListStructToParams(args)
if err != nil {
return err
}
var result *modulebase.ListResult
result, err = modules.DevToolCronjobs.List(s, params)
printList(result, modules.DevToolCronjobs.GetColumns(s))
return nil
})
type CronjobCreateOptions struct {
NAME string `help:"Ansible Playbook ID or Name" json:"-"`
Day int `help:"Cronjob runs at given day" default:"0"`
Hour int `help:"Cronjob runs at given hour" default:"0"`
Min int `help:"Cronjob runs at given min" default:"0"`
Sec int `help:"Cronjob runs at given sec" default:"0"`
Interval int64 `help:"Cronjob runs at given interval" default:"0"`
Start bool `help:"start job when created" default:"false"`
Enabled bool `help:"Set job status enabled" default:"false"`
}
R(
&CronjobCreateOptions{},
"devtoolcronjob-create",
"Create a cronjob repo component",
func(s *mcclient.ClientSession, args *CronjobCreateOptions) error {
result, err := modules.AnsiblePlaybooks.Get(s, args.NAME, nil)
if err != nil {
return err
}
ansiblePlaybookName, err := result.GetString("name")
if err != nil {
return err
}
ansiblePlaybookID, err := result.GetString("id")
if err != nil {
return err
}
params := jsonutils.NewDict()
params.Add(jsonutils.NewString(ansiblePlaybookName), "name")
params.Add(jsonutils.NewString(ansiblePlaybookID), "ansible_playbook_id")
if args.Start {
params.Add(jsonutils.JSONTrue, "start")
}
if args.Enabled {
params.Add(jsonutils.JSONTrue, "enabled")
} else if args.Interval > 0 {
params.Add(jsonutils.NewInt(int64(args.Interval)), "interval")
} else {
params.Add(jsonutils.NewInt(int64(0)), "interval")
params.Add(jsonutils.NewInt(int64(args.Day)), "day")
params.Add(jsonutils.NewInt(int64(args.Hour)), "hour")
params.Add(jsonutils.NewInt(int64(args.Min)), "min")
params.Add(jsonutils.NewInt(int64(args.Sec)), "sec")
}
ok, err := paramValidator(params)
if err != nil || !ok {
log.Infof("paramValidator error %s", err)
return err
}
cronjob, err := modules.DevToolCronjobs.Create(s, params)
if err != nil {
log.Errorf("modules.DevToolCronjobs.Create error %s", err)
return err
}
printObject(cronjob)
return nil
},
)
type DevToolCronjobShowOptions struct {
ID string `help:"ID or Name of the DevToolCronjob to show"`
}
R(&DevToolCronjobShowOptions{}, "devtoolcronjob-show", "Show cronjob details", func(s *mcclient.ClientSession, args *DevToolCronjobShowOptions) error {
result, err := modules.DevToolCronjobs.Get(s, args.ID, nil)
if err != nil {
return err
}
printObject(result)
return nil
})
type DevToolCronjobUpdateOptions struct {
ID string `help:"ID or Name of DevToolCronjob to update"`
Day int `help:"Cronjob runs at given day" json:"-" default:"-1"`
Hour int `help:"Cronjob runs at given hour" json:"-" default:"-1"`
Min int `help:"Cronjob runs at given min" default:"-1"`
Sec int `help:"Cronjob runs at given sec" default:"-1"`
Interval int `help:"Cronjob runs at given interval" default:"-1"`
Start bool `help:"start job when created"`
Stop bool `help:"start job when created"`
Enable bool `help:"Set job status enabled"`
Disable bool `help:"Set job status enabled"`
}
R(&DevToolCronjobUpdateOptions{}, "devtoolcronjob-update", "Update DevToolCronjob", func(s *mcclient.ClientSession, args *DevToolCronjobUpdateOptions) error {
result, err := modules.DevToolCronjobs.Get(s, args.ID, nil)
if err != nil {
return err
}
params := jsonutils.NewDict()
interval, _ := result.Int("interval")
day, _ := result.Int("day")
params.Add(jsonutils.NewString(args.ID), "id")
params.Add(jsonutils.NewInt(int64(interval)), "interval")
params.Add(jsonutils.NewInt(int64(day)), "day")
log.Infof("DevToolCronjobUpdateOptions args: %+v", args)
if args.Interval >= 0 {
params.Add(jsonutils.NewInt(int64(args.Interval)), "interval")
if args.Interval > 0 {
params.Add(jsonutils.NewInt(0), "day")
}
} else if args.Day >= 0 {
params.Add(jsonutils.NewInt(int64(args.Day)), "day")
if args.Day > 0 {
params.Add(jsonutils.NewInt(0), "interval")
}
}
if args.Hour >= 0 {
params.Add(jsonutils.NewInt(int64(args.Hour)), "hour")
}
if args.Min >= 0 {
params.Add(jsonutils.NewInt(int64(args.Min)), "min")
}
if args.Sec >= 0 {
params.Add(jsonutils.NewInt(int64(args.Sec)), "sec")
}
ok, err := paramValidator(params)
if err != nil || !ok {
return err
}
if args.Start && args.Stop {
return fmt.Errorf("can not set job start and stop at the same time")
} else if args.Start {
params.Add(jsonutils.JSONTrue, "start")
} else if args.Stop {
params.Add(jsonutils.JSONFalse, "start")
}
if args.Enable && args.Disable {
return fmt.Errorf("can not set job enabled and disabled at the same time")
} else if args.Enable {
params.Add(jsonutils.JSONTrue, "enabled")
} else if args.Disable {
params.Add(jsonutils.JSONFalse, "enabled")
}
result, err = modules.DevToolCronjobs.Update(s, args.ID, params)
if err != nil {
return err
}
printObject(result)
return nil
})
R(&DevToolCronjobShowOptions{}, "devtoolcronjob-delete", "Delete DevToolCronjob", func(s *mcclient.ClientSession, args *DevToolCronjobShowOptions) error {
result, err := modules.DevToolCronjobs.Delete(s, args.ID, nil)
if err != nil {
return err
}
printObject(result)
return nil
})
}
-154
View File
@@ -1,154 +0,0 @@
// 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/jsonutils"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
func init() {
printAnsiblePlaybookObject := func(obj jsonutils.JSONObject) {
dict := obj.(*jsonutils.JSONDict)
pbJson, err := dict.Get("playbook")
if err != nil {
printObject(obj)
return
}
pbStr := pbJson.YAMLString()
dict.Set("playbook", jsonutils.NewString(pbStr))
printObject(obj)
}
type TemplateListOptions struct {
options.BaseListOptions
Name string `help:"cloud region ID or Name" json:"-"`
}
R(&TemplateListOptions{}, "devtooltemplate-list", "List Devtool Templates", func(s *mcclient.ClientSession, args *TemplateListOptions) error {
params, err := options.ListStructToParams(args)
if err != nil {
return err
}
var result *modulebase.ListResult
result, err = modules.DevToolTemplates.List(s, params)
printList(result, modules.DevToolTemplates.GetColumns(s))
return nil
})
R(
&options.DevtoolTemplateCreateOptions{},
"devtooltemplate-create",
"Create a template repo component",
func(s *mcclient.ClientSession, opts *options.DevtoolTemplateCreateOptions) error {
params, err := opts.Params()
if err != nil {
return err
}
log.Infof("devtool playbook create opts: %+v", params)
apb, err := modules.DevToolTemplates.Create(s, params)
if err != nil {
return err
}
printAnsiblePlaybookObject(apb)
return nil
},
)
R(
&options.DevtoolTemplateIdOptions{},
"devtooltemplate-show",
"Show devtool template",
func(s *mcclient.ClientSession, opts *options.DevtoolTemplateIdOptions) error {
apb, err := modules.DevToolTemplates.Get(s, opts.ID, nil)
if err != nil {
return err
}
printAnsiblePlaybookObject(apb)
return nil
},
)
R(
&options.DevtoolTemplateBindingOptions{},
"devtooltemplate-bind",
"Binding devtool template to a host/vm",
func(s *mcclient.ClientSession, opts *options.DevtoolTemplateBindingOptions) error {
params := jsonutils.NewDict()
params.Set("server_id", jsonutils.NewString(opts.ServerID))
apb, err := modules.DevToolTemplates.PerformAction(s, opts.ID, "bind", params)
if err != nil {
return err
}
printAnsiblePlaybookObject(apb)
return nil
},
)
R(
&options.DevtoolTemplateBindingOptions{},
"devtooltemplate-unbind",
"UnBinding devtool template to a host/vm",
func(s *mcclient.ClientSession, opts *options.DevtoolTemplateBindingOptions) error {
params := jsonutils.NewDict()
params.Set("server_id", jsonutils.NewString(opts.ServerID))
apb, err := modules.DevToolTemplates.PerformAction(s, opts.ID, "unbind", params)
if err != nil {
return err
}
printAnsiblePlaybookObject(apb)
return nil
},
)
R(
&options.DevtoolTemplateIdOptions{},
"devtooltemplate-delete",
"Delete devtool template",
func(s *mcclient.ClientSession, opts *options.DevtoolTemplateIdOptions) error {
apb, err := modules.DevToolTemplates.Delete(s, opts.ID, nil)
if err != nil {
return err
}
printAnsiblePlaybookObject(apb)
return nil
},
)
R(
&options.DevtoolTemplateUpdateOptions{},
"devtooltemplate-update",
"Update ansible playbook",
func(s *mcclient.ClientSession, opts *options.DevtoolTemplateUpdateOptions) error {
params, err := opts.Params()
if err != nil {
return err
}
apb, err := modules.DevToolTemplates.Update(s, opts.ID, params)
if err != nil {
return err
}
printAnsiblePlaybookObject(apb)
return nil
},
)
}
@@ -0,0 +1,238 @@
// 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"
"sync"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/ansibleserver/options"
api "yunion.io/x/onecloud/pkg/apis/ansible"
"yunion.io/x/onecloud/pkg/appctx"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/workmanager"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/util/ansible"
"yunion.io/x/onecloud/pkg/util/ansiblev2"
)
type SAnsiblePlaybookInstance struct {
db.SStatusStandaloneResourceBase
ReferenceId string `width:"36" nullable:"false" get:"user" list:"user"`
Inventory string `length:"text" nullable:"false" get:"user" list:"user"`
Params jsonutils.JSONObject
Output string `length:"medium" get:"user" list:"user"`
StartTime time.Time `list:"user" get:"user"`
EndTime time.Time `list:"user" get:"user"`
}
type SAnsiblePlaybookInstanceManager struct {
db.SStatusStandaloneResourceBaseManager
sessions ansible.SessionManager
sessionsMux *sync.Mutex
}
var AnsiblePlaybookInstanceManager *SAnsiblePlaybookInstanceManager
func init() {
AnsiblePlaybookInstanceManager = &SAnsiblePlaybookInstanceManager{
SStatusStandaloneResourceBaseManager: db.NewStatusStandaloneResourceBaseManager(
SAnsiblePlaybookInstance{},
"ansibleplaybook_instance_tbl",
"ansibleplaybookinstance",
"ansibleplaybookinstances",
),
sessions: ansible.SessionManager{},
}
AnsiblePlaybookInstanceManager.SetVirtualObject(AnsiblePlaybookInstanceManager)
}
func (aim *SAnsiblePlaybookInstanceManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, input api.AnsiblePlaybookInstanceListInput) (*sqlchemy.SQuery, error) {
if len(input.AnsiblePlayboookReferenceId) > 0 {
q = q.Equals("reference_id", input.AnsiblePlayboookReferenceId)
}
return aim.SStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StatusStandaloneResourceListInput)
}
func (aim *SAnsiblePlaybookInstanceManager) createInstance(ctx context.Context, referenceId string, host api.AnsibleHost, params jsonutils.JSONObject) (*SAnsiblePlaybookInstance, error) {
// build inventory
inv := ansiblev2.NewInventory()
vars := map[string]interface{}{
"ansible_user": host.User,
"ansible_host": host.IP,
"ansible_port": host.Port,
}
h := ansiblev2.NewHost()
h.Vars = vars
inv.SetHost(host.Name, h)
ai := &SAnsiblePlaybookInstance{
ReferenceId: referenceId,
Params: params,
Inventory: inv.String(),
}
err := aim.TableSpec().Insert(ctx, ai)
if err != nil {
return nil, errors.Wrapf(err, "unable to create AnsiblePlaybookInstance")
}
ai.SetModelManager(aim, ai)
return ai, nil
}
func (ai *SAnsiblePlaybookInstance) AllowPerformRun(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
return true
}
func (ai *SAnsiblePlaybookInstance) PerformRun(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) {
return nil, ai.runPlaybook(ctx, userCred, nil)
}
func (ai *SAnsiblePlaybookInstance) runPlaybook(ctx context.Context, userCred mcclient.TokenCredential, ar *SAnsiblePlaybookReference) error {
man := AnsiblePlaybookInstanceManager
if man.sessions.Has(ai.Id) {
return errors.Error("playbook is already running")
}
if ar == nil {
obj, err := AnsiblePlaybookReferenceManager.FetchById(ai.ReferenceId)
if err != nil {
return errors.Wrapf(err, "unable to fetch ansibleplaybook reference %s", ai.ReferenceId)
}
ar = obj.(*SAnsiblePlaybookReference)
}
var (
privateKey string
err error
)
if privateKey, err = modules.Sshkeypairs.FetchPrivateKey(ctx, userCred); err != nil {
return err
}
_, err = db.Update(ai, func() error {
ai.StartTime = time.Now()
ai.EndTime = time.Time{}
ai.Output = ""
ai.Status = api.AnsiblePlaybookStatusRunning
return nil
})
if err != nil {
return errors.Wrap(err, "unable to update ansibleplaybookinstance")
}
convertJO := func(o jsonutils.JSONObject) map[string]interface{} {
ret := make(map[string]interface{})
o.Unmarshal(&ret)
return ret
}
// merge configs
params := make(map[string]interface{})
for k, v := range convertJO(ar.DefaultParams) {
params[k] = v
}
for k, v := range convertJO(ai.Params) {
params[k] = v
}
sess := ansiblev2.NewOfflineSession().
Inventory(ai.Inventory).
PrivateKey(privateKey).
Configs(params).
PlaybookPath(ar.PlaybookPath).
OutputWriter(&ansiblePlaybookOutputWriter{ai}).
KeepTmpdir(options.Options.KeepTmpdir)
man.sessions.Add(ai.Id, sess)
// NOTE host state check? run only on online hosts and running guests, skip others
run := func(ctx context.Context, data interface{}) (jsonutils.JSONObject, error) {
defer func() {
man.sessions.Remove(ai.Id)
}()
runErr := man.sessions.Run(ai.Id)
// TODO: try to close local forwarding?
_, err := db.Update(ai, func() error {
err := man.sessions.Err(ai.Id)
if err != nil {
ai.Status = api.AnsiblePlaybookStatusCanceled
} else if runErr != nil {
log.Warningf("playbook %s(%s) failed: %v", ai.Name, ai.Id, runErr)
ai.Status = api.AnsiblePlaybookStatusFailed
} else {
ai.Status = api.AnsiblePlaybookStatusSucceeded
}
ai.EndTime = time.Now()
return nil
})
if err != nil {
log.Errorf("updating ansible playbook failed: %v", err)
}
return nil, runErr
}
PlaybookWorker.DelayTask(ctx, run, nil)
return nil
}
func (ai *SAnsiblePlaybookInstance) stopPlaybook(ctx context.Context, userCred mcclient.TokenCredential) error {
man := AnsiblePlaybookInstanceManager
if !man.sessions.Has(ai.Id) {
return errors.Error("playbook is not running")
}
// the playbook will be removed from session map in runPlaybook() on return from run
man.sessions.Stop(ai.Id)
return nil
}
func (ai *SAnsiblePlaybookInstance) getMaxOutputLength() int {
return OutputMaxBytes
}
func (ai *SAnsiblePlaybookInstance) getOutput() string {
return ai.Output
}
func (ai *SAnsiblePlaybookInstance) setOutput(s string) {
ai.Output = s
}
var PlaybookWorker *workmanager.SWorkManager
func taskFailed(ctx context.Context, reason string) {
if taskId := ctx.Value(appctx.APP_CONTEXT_KEY_TASK_ID); taskId != nil {
session := auth.GetAdminSessionWithInternal(ctx, "", "")
modules.TaskFailed(&modules.DevtoolTasks, session, taskId.(string), reason)
} else {
log.Warningf("Reqeuest task failed missing task id, with reason: %s", reason)
}
}
func taskCompleted(ctx context.Context, data jsonutils.JSONObject) {
if taskId := ctx.Value(appctx.APP_CONTEXT_KEY_TASK_ID); taskId != nil {
session := auth.GetAdminSessionWithInternal(ctx, "", "")
modules.TaskComplete(&modules.DevtoolTasks, session, taskId.(string), data)
} else {
log.Warningf("Reqeuest task failed missing task id, with data: %v", data)
}
}
func InitPlaybookWorker() {
PlaybookWorker = workmanager.NewWorkManger(taskFailed, taskCompleted, options.Options.PlaybookWorkerCount)
}
@@ -0,0 +1,130 @@
// 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"
"os"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/ansible"
"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/rbacutils"
)
type SAnsiblePlaybookReference struct {
db.SSharableVirtualResourceBase
PlaybookPath string `length:"text" nullable:"false" create:"required" get:"user" list:"user"`
Method string `width:"8" nullable:"false" default:"offline" get:"user" list:"user"`
DefaultParams jsonutils.JSONObject `get:"user" list:"user"`
}
type SAnsiblePlaybookReferenceManager struct {
db.SSharableVirtualResourceBaseManager
}
var AnsiblePlaybookReferenceManager *SAnsiblePlaybookReferenceManager
func init() {
AnsiblePlaybookReferenceManager = &SAnsiblePlaybookReferenceManager{
SSharableVirtualResourceBaseManager: db.NewSharableVirtualResourceBaseManager(
SAnsiblePlaybookReference{},
"ansibleplaybook_reference_tbl",
"ansibleplaybookreference",
"ansibleplaybookreferences",
),
}
AnsiblePlaybookReferenceManager.SetVirtualObject(AnsiblePlaybookReferenceManager)
}
func (arm *SAnsiblePlaybookReferenceManager) ResourceScope() rbacutils.TRbacScope {
return rbacutils.ScopeSystem
}
var (
monitorAgent = "monitor agent"
monitorAgentId = "monitoragent"
)
func (arm *SAnsiblePlaybookReferenceManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.AnsiblePlaybookReferenceCreateInput) (api.AnsiblePlaybookReferenceCreateInput, error) {
if input.Method != api.APReferenceMethodOffline {
return input, httperrors.NewInputParameterError("unkown Method %q", input.Method)
}
if !arm.checkOfflinePath(input.PlaybookPath) {
return input, httperrors.NewInputParameterError("non-existent path: %q", input.PlaybookPath)
}
return input, nil
}
func (arm *SAnsiblePlaybookReferenceManager) checkOfflinePath(path string) bool {
_, err := os.Stat(path)
if err != nil {
if os.IsExist(err) {
return true
}
return false
}
return true
}
func (ar *SAnsiblePlaybookReference) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
if data.Contains("playbook_params") {
params, _ := data.Get("playbook_params")
ar.DefaultParams = params
}
ar.Status = api.APReferenceStatusReady
return nil
}
func (ar *SAnsiblePlaybookReference) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.AnsiblePlaybookReferenceUpdateInput) (api.AnsiblePlaybookReferenceUpdateInput, error) {
return input, httperrors.NewForbiddenError("prohibited operation")
}
func (ar *SAnsiblePlaybookReference) AllowPerformRun(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
return ar.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, ar, "run")
}
func (ar *SAnsiblePlaybookReference) PerformRun(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.AnsiblePlaybookReferenceRunInput) (api.AnsiblePlaybookReferenceRunOutput, error) {
output := api.AnsiblePlaybookReferenceRunOutput{}
ai, err := AnsiblePlaybookInstanceManager.createInstance(ctx, ar.Id, input.Host, input.Args)
if err != nil {
return output, errors.Wrap(err, "unable to create instance")
}
output.AnsiblePlaybookInstanceId = ai.Id
err = ai.runPlaybook(ctx, userCred, ar)
if err != nil {
return output, errors.Wrap(err, "unable to runPlaybook")
}
return output, nil
}
func (ar *SAnsiblePlaybookReference) AllowPerformStop(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
return ar.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, ar, "stop")
}
func (ar *SAnsiblePlaybookReference) PerformStop(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.AnsiblePlaybookReferenceStopInput) (jsonutils.JSONObject, error) {
obj, err := AnsiblePlaybookInstanceManager.FetchById(input.AnsiblePlaybookInstanceId)
if err != nil {
return nil, errors.Wrap(err, "unable to fetch ansibleplaybookinstance")
}
ai := obj.(*SAnsiblePlaybookInstance)
return nil, ai.stopPlaybook(ctx, userCred)
}
+2 -1
View File
@@ -19,7 +19,8 @@ import common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
type AnsibleServerOptions struct {
common_options.CommonOptions
common_options.DBOptions
KeepTmpdir bool `help:"Whether to save the tmp directory" json:"keep_tmpdir"`
KeepTmpdir bool `help:"Whether to save the tmp directory" json:"keep_tmpdir"`
PlaybookWorkerCount int `help:"count of worker to run playbook" default:"5" json:"playbook_worker_count"`
}
var (
+3
View File
@@ -42,9 +42,12 @@ func InitHandlers(app *appsrv.Application) {
db.RegisterModelManager(db.Metadata)
db.RegisterModelManager(db.UserCacheManager)
db.RegisterModelManager(db.TenantCacheManager)
db.RegisterModelManager(db.SharedResourceManager)
for _, manager := range []db.IModelManager{
models.AnsiblePlaybookManager,
models.AnsiblePlaybookV2Manager,
models.AnsiblePlaybookReferenceManager,
models.AnsiblePlaybookInstanceManager,
} {
db.RegisterModelManager(manager)
handler := db.NewModelHandler(manager)
+1
View File
@@ -41,6 +41,7 @@ func StartService() {
dbOpts := &opts.DBOptions
baseOpts := &opts.BaseOptions
models.InitPlaybookWorker()
app := common_app.InitApp(baseOpts, false)
InitHandlers(app)
+37
View File
@@ -15,6 +15,8 @@
package ansible
import (
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/apis"
"yunion.io/x/onecloud/pkg/util/ansible"
)
@@ -27,3 +29,38 @@ type AnsiblePlaybookCreateInput struct {
}
type AnsiblePlaybookUpdateInput AnsiblePlaybookCreateInput
type AnsibleHost struct {
User string `json:"user"`
IP string `json:"ip"`
Port int `json:"port"`
Name string `json:"name"`
}
type AnsiblePlaybookReferenceCreateInput struct {
apis.SharableVirtualResourceCreateInput
SAnsiblePlaybookReference
PlaybookParams map[string]interface{} `json:"playbook_params"`
}
type AnsiblePlaybookReferenceUpdateInput struct {
}
type AnsiblePlaybookReferenceRunInput struct {
Host AnsibleHost
Args jsonutils.JSONObject
}
type AnsiblePlaybookReferenceRunOutput struct {
AnsiblePlaybookInstanceId string
}
type AnsiblePlaybookReferenceStopInput struct {
AnsiblePlaybookInstanceId string
}
type AnsiblePlaybookInstanceListInput struct {
apis.StatusStandaloneResourceListInput
AnsiblePlayboookReferenceId string
}
+22
View File
@@ -0,0 +1,22 @@
// 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
const (
APReferenceMethodOffline = "offline"
APReferenceMethodOnline = "online"
APReferenceStatusReady = "ready"
)
+63
View File
@@ -0,0 +1,63 @@
// 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.
// Code generated by model-api-gen. DO NOT EDIT.
package ansible
import (
time "time"
"yunion.io/x/onecloud/pkg/apis"
ansible "yunion.io/x/onecloud/pkg/util/ansible"
)
// SAnsiblePlaybook is an autogenerated struct via yunion.io/x/onecloud/pkg/ansibleserver/models.SAnsiblePlaybook.
type SAnsiblePlaybook struct {
apis.SVirtualResourceBase
Playbook *ansible.Playbook `json:"playbook"`
Output string `json:"output"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
}
// SAnsiblePlaybookInstance is an autogenerated struct via yunion.io/x/onecloud/pkg/ansibleserver/models.SAnsiblePlaybookInstance.
type SAnsiblePlaybookInstance struct {
apis.SStatusStandaloneResourceBase
ReferenceId string `json:"reference_id"`
ProxyEndpoingId string `json:"proxy_endpoing_id"`
LocalForwardId string `json:"local_forward_id"`
Proxy string `json:"proxy"`
Output string `json:"output"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
}
// SAnsiblePlaybookReference is an autogenerated struct via yunion.io/x/onecloud/pkg/ansibleserver/models.SAnsiblePlaybookReference.
type SAnsiblePlaybookReference struct {
apis.SVirtualResourceBase
PlaybookPath string `json:"playbook_path"`
Method string `json:"method"`
}
// SAnsiblePlaybookV2 is an autogenerated struct via yunion.io/x/onecloud/pkg/ansibleserver/models.SAnsiblePlaybookV2.
type SAnsiblePlaybookV2 struct {
apis.SVirtualResourceBase
Playbook string `json:"playbook"`
Inventory string `json:"inventory"`
Requirements string `json:"requirements"`
Files string `json:"files"`
Output string `json:"output"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
CreatorMark string `json:"creator_mark"`
}
+13
View File
@@ -0,0 +1,13 @@
// 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 ansibleserver // import "yunion.io/x/onecloud/pkg/apis/ansibleserver"
@@ -0,0 +1,61 @@
// 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.
// Code generated by model-api-gen. DO NOT EDIT.
package ansibleserver
import (
time "time"
"yunion.io/x/onecloud/pkg/apis"
ansible "yunion.io/x/onecloud/pkg/util/ansible"
)
// SAnsiblePlaybook is an autogenerated struct via yunion.io/x/onecloud/pkg/ansibleserver/models.SAnsiblePlaybook.
type SAnsiblePlaybook struct {
apis.SVirtualResourceBase
Playbook *ansible.Playbook `json:"playbook"`
Output string `json:"output"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
}
// SAnsiblePlaybookInstance is an autogenerated struct via yunion.io/x/onecloud/pkg/ansibleserver/models.SAnsiblePlaybookInstance.
type SAnsiblePlaybookInstance struct {
apis.SStatusStandaloneResourceBase
ReferenceId string `json:"reference_id"`
Inventory string `json:"inventory"`
Output string `json:"output"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
}
// SAnsiblePlaybookReference is an autogenerated struct via yunion.io/x/onecloud/pkg/ansibleserver/models.SAnsiblePlaybookReference.
type SAnsiblePlaybookReference struct {
apis.SSharableVirtualResourceBase
PlaybookPath string `json:"playbook_path"`
Method string `json:"method"`
}
// SAnsiblePlaybookV2 is an autogenerated struct via yunion.io/x/onecloud/pkg/ansibleserver/models.SAnsiblePlaybookV2.
type SAnsiblePlaybookV2 struct {
apis.SVirtualResourceBase
Playbook string `json:"playbook"`
Inventory string `json:"inventory"`
Requirements string `json:"requirements"`
Files string `json:"files"`
Output string `json:"output"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
CreatorMark string `json:"creator_mark"`
}
@@ -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 modules
import "yunion.io/x/onecloud/pkg/mcclient/modulebase"
var (
AnsiblePlaybookReference modulebase.ResourceManager
AnsiblePlaybookInstance modulebase.ResourceManager
)
func init() {
AnsiblePlaybookReference = NewAnsibleManager(
"ansibleplaybookreference",
"ansibleplaybookreferences",
[]string{
"Id",
"Name",
"Playbook_Path",
"Default_Params",
"Method",
},
[]string{},
)
AnsiblePlaybookInstance = NewAnsibleManager(
"ansibleplaybookinstance",
"ansibleplaybookinstances",
[]string{
"Id",
"Status",
"Start_Time",
"End_Time",
"Output",
},
[]string{},
)
registerV2(&AnsiblePlaybookReference)
registerV2(&AnsiblePlaybookInstance)
}
+6
View File
@@ -28,6 +28,7 @@ var (
Tasks modulebase.ResourceManager
ComputeTasks ComputeTasksManager
DevtoolTasks modulebase.ResourceManager
)
type ComputeTasksManager struct {
@@ -45,6 +46,11 @@ func init() {
[]string{"Id", "Obj_name", "Obj_Id", "Task_name", "Stage", "Created_at"}),
}
registerCompute(&ComputeTasks)
DevtoolTasks = NewDevtoolManager("task", "tasks",
[]string{},
[]string{"Id", "Obj_name", "Obj_Id", "Task_name", "Stage", "Created_at"},
)
}
type ITaskResourceManager interface {
@@ -0,0 +1,92 @@
// 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/jsonutils"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
type APRListOptions struct {
options.BaseListOptions
}
func (ao *APRListOptions) Params() (jsonutils.JSONObject, error) {
return options.ListStructToParams(ao)
}
type APROptions struct {
ID string `help:"id or name of ansible playbook reference"`
}
func (ao *APROptions) GetId() string {
return ao.ID
}
func (ao *APROptions) Params() (jsonutils.JSONObject, error) {
return nil, nil
}
type aprRunOptions struct {
ServerName string
ServerIp string
ServerUser string
Args map[string]interface{}
ProxyEndpoingId string
}
type APRRunOptions struct {
APROptions
aprRunOptions
}
func (ao *APRRunOptions) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(ao.aprRunOptions), nil
}
type aprStopOptions struct {
AnsiblePlaybookInstanceId string
}
type APRStopOptions struct {
APROptions
aprStopOptions
}
func (ao *APRStopOptions) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(ao.aprStopOptions), nil
}
type APIListOptions struct {
options.BaseListOptions
AnsiblePlayboookReferenceId string
}
func (ao *APIListOptions) Params() (jsonutils.JSONObject, error) {
return options.ListStructToParams(ao)
}
type APIOptions struct {
ID string
}
func (ao *APIOptions) GetId() string {
return ao.ID
}
func (ao *APIOptions) Params() (jsonutils.JSONObject, error) {
return nil, nil
}
+15
View File
@@ -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 ansible // import "yunion.io/x/onecloud/pkg/mcclient/options/ansible"
+81
View File
@@ -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 ansiblev2
import (
"context"
"io"
)
type OfflineSession struct {
PlaybookSessionBase
playbookPath string
proxy string
user string
hostIp string
hostName string
configs map[string]interface{}
}
func NewOfflineSession() *OfflineSession {
sess := &OfflineSession{
PlaybookSessionBase: NewPlaybookSessionBase(),
configs: map[string]interface{}{},
}
return sess
}
func (sess *OfflineSession) PrivateKey(s string) *OfflineSession {
sess.privateKey = s
return sess
}
func (sess *OfflineSession) PlaybookPath(s string) *OfflineSession {
sess.playbookPath = s
return sess
}
func (sess *OfflineSession) Inventory(s string) *OfflineSession {
sess.inventory = s
return sess
}
func (sess *OfflineSession) OutputWriter(w io.Writer) *OfflineSession {
sess.outputWriter = w
return sess
}
func (sess *OfflineSession) KeepTmpdir(keep bool) *OfflineSession {
sess.keepTmpdir = keep
return sess
}
func (sess *OfflineSession) Configs(configs map[string]interface{}) *OfflineSession {
sess.configs = configs
return sess
}
func (sess *OfflineSession) GetPlaybookPath() string {
return sess.playbookPath
}
func (sess *OfflineSession) GetConfigs() map[string]interface{} {
return sess.configs
}
func (sess *OfflineSession) Run(ctx context.Context) (err error) {
return runnable{sess}.Run(ctx)
}
+278
View File
@@ -0,0 +1,278 @@
// 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 ansiblev2
import (
"context"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sync"
"github.com/go-yaml/yaml"
"yunion.io/x/pkg/errors"
yerrors "yunion.io/x/pkg/util/errors"
)
type IPlaybookSession interface {
GetPrivateKey() string
GetPlaybook() string
GetPlaybookPath() string
GetInventory() string
IsKeepTmpdir() bool
GetConfigs() map[string]interface{}
GetRequirements() string
GetFiles() map[string][]byte
GetOutputWriter() io.Writer
CheckAndSetRunning() bool
SetStopped()
}
type runnable struct {
IPlaybookSession
}
func (r runnable) Run(ctx context.Context) (err error) {
var (
tmpdir string
)
has := r.CheckAndSetRunning()
if has {
return errors.Errorf("playbook is already running")
}
defer r.SetStopped()
// make tmpdir
tmpdir, err = ioutil.TempDir("", "onecloud-ansiblev2")
if err != nil {
err = errors.Wrap(err, "making tmp dir")
return
}
defer func() {
if r.IsKeepTmpdir() {
return
}
if err1 := os.RemoveAll(tmpdir); err1 != nil {
err = errors.Wrapf(err1, "removing %q", tmpdir)
}
}()
// write out inventory
inventory := filepath.Join(tmpdir, "inventory")
err = ioutil.WriteFile(inventory, []byte(r.GetInventory()), os.FileMode(0600))
if err != nil {
err = errors.Wrapf(err, "writing inventory %s", inventory)
return
}
// write out playbook
playbook := r.GetPlaybookPath()
if len(playbook) == 0 {
playbook := filepath.Join(tmpdir, "playbook")
err = ioutil.WriteFile(playbook, []byte(r.GetPlaybook()), os.FileMode(0600))
if err != nil {
err = errors.Wrapf(err, "writing playbook %s", playbook)
return
}
}
// write out private key
var privateKey string
if len(r.GetPrivateKey()) > 0 {
privateKey = filepath.Join(tmpdir, "private_key")
err = ioutil.WriteFile(privateKey, []byte(r.GetPrivateKey()), os.FileMode(0600))
if err != nil {
err = errors.Wrapf(err, "writing private key %s", privateKey)
return
}
}
// write out requirements
var requirements string
if len(r.GetRequirements()) > 0 {
requirements = filepath.Join(tmpdir, "requirements.yml")
err = ioutil.WriteFile(requirements, []byte(r.GetRequirements()), os.FileMode(0600))
if err != nil {
err = errors.Wrapf(err, "writing requirements %s", requirements)
return
}
}
// write out files
for name, content := range r.GetFiles() {
path := filepath.Join(tmpdir, name)
dir := filepath.Dir(path)
err = os.MkdirAll(dir, os.FileMode(0700))
if err != nil {
err = errors.Wrapf(err, "mkdir -p %s", dir)
return
}
err = ioutil.WriteFile(path, content, os.FileMode(0600))
if err != nil {
err = errors.Wrapf(err, "writing file %s", name)
return
}
}
// write out configs
var config string
if r.GetConfigs() != nil {
yml, err := yaml.Marshal(r.GetConfigs())
if err != nil {
return errors.Wrap(err, "unable to marshal map to yaml")
}
config = filepath.Join(tmpdir, "config")
err = ioutil.WriteFile(config, yml, os.FileMode(0600))
if err != nil {
return errors.Wrapf(err, "unable to write config to file %s", config)
}
}
// run modules one by one
var errs []error
defer func() {
if len(errs) > 0 {
err = yerrors.NewAggregate(errs)
}
}()
// install required roles
if len(requirements) > 0 {
args := []string{
"install", "-r", requirements, "-p", tmpdir,
}
cmd := exec.CommandContext(ctx, "ansible-galaxy", args...)
stdout, _ := cmd.StdoutPipe()
stderr, _ := cmd.StderrPipe()
if err1 := cmd.Start(); err1 != nil {
errs = append(errs, errors.Wrap(err1, "start ansible-galaxy install roles"))
return
}
// Mix stdout, stderr
if writer := r.GetOutputWriter(); writer != nil {
go io.Copy(writer, stdout)
go io.Copy(writer, stderr)
}
if err1 := cmd.Wait(); err1 != nil {
errs = append(errs, errors.Wrap(err1, "wait ansible-galaxy install roles"))
}
}
// run playbook
{
args := []string{
"--inventory", inventory,
}
if config != "" {
args = append(args, "-e", "@"+config)
}
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.Wrapf(err1, "start playbook %s", playbook))
return
}
// Mix stdout, stderr
if writer := r.GetOutputWriter(); writer != nil {
go io.Copy(writer, stdout)
go io.Copy(writer, stderr)
}
if err1 := cmd.Wait(); err1 != nil {
errs = append(errs, errors.Wrapf(err1, "wait playbook %s", playbook))
}
}
return nil
}
type PlaybookSessionBase struct {
privateKey string
inventory string
outputWriter io.Writer
stateMux *sync.Mutex
isRunning bool
keepTmpdir bool
}
func NewPlaybookSessionBase() PlaybookSessionBase {
return PlaybookSessionBase{
stateMux: &sync.Mutex{},
}
}
func (pb *PlaybookSessionBase) GetPrivateKey() string {
return pb.privateKey
}
func (pb *PlaybookSessionBase) IsKeepTmpdir() bool {
return pb.keepTmpdir
}
func (pb *PlaybookSessionBase) GetOutputWriter() io.Writer {
return pb.outputWriter
}
func (pb *PlaybookSessionBase) CheckAndSetRunning() bool {
pb.stateMux.Lock()
if pb.isRunning {
return true
}
pb.isRunning = true
pb.stateMux.Unlock()
return false
}
func (pb *PlaybookSessionBase) SetStopped() {
pb.stateMux.Lock()
pb.isRunning = false
pb.stateMux.Unlock()
}
func (pb *PlaybookSessionBase) GetPlaybook() string {
return ""
}
func (pb *PlaybookSessionBase) GetPlaybookPath() string {
return ""
}
func (pb *PlaybookSessionBase) GetInventory() string {
return pb.inventory
}
func (pb *PlaybookSessionBase) GetConfigs() map[string]interface{} {
return nil
}
func (pb *PlaybookSessionBase) GetRequirements() string {
return ""
}
func (pb *PlaybookSessionBase) GetFiles() map[string][]byte {
return nil
}
+18 -163
View File
@@ -17,34 +17,20 @@ 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
PlaybookSessionBase
playbook string
inventory string
requirements 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{},
PlaybookSessionBase: NewPlaybookSessionBase(),
files: map[string][]byte{},
}
return sess
}
@@ -95,149 +81,18 @@ func (sess *Session) KeepTmpdir(keep bool) *Session {
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 requirements
var requirements string
if len(sess.requirements) > 0 {
requirements = filepath.Join(tmpdir, "requirements.yml")
err = ioutil.WriteFile(requirements, []byte(sess.requirements), os.FileMode(0600))
if err != nil {
err = errors.WithMessagef(err, "writing requirements %s", requirements)
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)
}
}()
// install required roles
if len(requirements) > 0 {
args := []string{
"install", "-r", requirements, "-p", tmpdir,
}
cmd := exec.CommandContext(ctx, "ansible-galaxy", args...)
stdout, _ := cmd.StdoutPipe()
stderr, _ := cmd.StderrPipe()
if err1 := cmd.Start(); err1 != nil {
errs = append(errs, errors.WithMessage(err1, "start ansible-galaxy install roles"))
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.WithMessage(err1, "wait ansible-galaxy install roles"))
}
}
// run playbook
{
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
func (sess *Session) GetPlaybook() string {
return sess.playbook
}
func (sess *Session) GetRequirements() string {
return sess.requirements
}
func (sess *Session) GetFile() map[string][]byte {
return sess.files
}
func (sess *Session) Run(ctx context.Context) (err error) {
return runnable{sess}.Run(ctx)
}