diff --git a/pkg/ansibleserver/models/ansibleplaybooks_v2.go b/pkg/ansibleserver/models/ansibleplaybooks_v2.go index 75fb44f0d8..fdfd0053b3 100644 --- a/pkg/ansibleserver/models/ansibleplaybooks_v2.go +++ b/pkg/ansibleserver/models/ansibleplaybooks_v2.go @@ -39,12 +39,13 @@ import ( 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"` + Playbook string `length:"text" nullable:"false" create:"required" get:"user"` + Inventory string `length:"text" nullable:"false" create:"required" get:"user"` + Requirements string `length:"test" 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"` } @@ -196,6 +197,7 @@ func (apb *SAnsiblePlaybookV2) runPlaybook(ctx context.Context, userCred mcclien Inventory(apb.Inventory). Playbook(apb.Playbook). PrivateKey(privateKey). + Requirements(apb.Requirements). Files(files). OutputWriter(&ansiblePlaybookOutputWriter{apb}) man.sessions.Add(apb.Id, sess) diff --git a/pkg/util/ansiblev2/requirements.go b/pkg/util/ansiblev2/requirements.go new file mode 100644 index 0000000000..5a9398316d --- /dev/null +++ b/pkg/util/ansiblev2/requirements.go @@ -0,0 +1,70 @@ +// 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 "gopkg.in/yaml.v2" + +type RoleSource struct { + Name string + Src string + Version string +} + +func (rs *RoleSource) MarshalYAML() (interface{}, error) { + r := map[string]string{ + "src": rs.Src, + } + if len(rs.Name) > 0 { + r["name"] = rs.Name + } + if len(rs.Version) > 0 { + r["version"] = rs.Version + } + return r, nil +} + +type Requirements struct { + RoleSources []RoleSource +} + +func NewRequirements(rss ...RoleSource) *Requirements { + return &Requirements{ + RoleSources: rss, + } +} + +func (rm *Requirements) AddRoleSource(rss ...RoleSource) { + rm.RoleSources = append(rm.RoleSources, rss...) +} + +func (rm *Requirements) MarshalYAML() (interface{}, error) { + var err error + r := make([]interface{}, len(rm.RoleSources)) + for i := range rm.RoleSources { + r[i], err = rm.RoleSources[i].MarshalYAML() + if err != nil { + return nil, err + } + } + return r, nil +} + +func (rm *Requirements) String() string { + r, err := yaml.Marshal(rm) + if err != nil { + panic(err) + } + return string(r) +} diff --git a/pkg/util/ansiblev2/requirements_test.go b/pkg/util/ansiblev2/requirements_test.go new file mode 100644 index 0000000000..33db1f3896 --- /dev/null +++ b/pkg/util/ansiblev2/requirements_test.go @@ -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 ansiblev2 + +import "testing" + +func TestRequirementsString(t *testing.T) { + rss := []RoleSource{ + { + Src: "zaiste.nginx", + }, + { + Src: "geerlingguy.gitlab", + Version: "1.1.0", + }, + { + Name: "essentials", + Src: "https://github.com/zaiste/ansible-essentials", + }, + { + Name: "java", + Src: "https://github.com/geerlingguy/ansible-role-java", + Version: "origin/master", + }, + { + Name: "jenkins", + Src: "https://github.com/geerlingguy/ansible-role-jenkins", + Version: "4.2.1", + }, + } + rm := NewRequirements(rss...) + t.Logf("\n%s", rm.String()) +} diff --git a/pkg/util/ansiblev2/session.go b/pkg/util/ansiblev2/session.go index b4f494536f..ff60fda5be 100644 --- a/pkg/util/ansiblev2/session.go +++ b/pkg/util/ansiblev2/session.go @@ -29,10 +29,11 @@ import ( ) type Session struct { - privateKey string - playbook string - inventory string - files map[string][]byte + privateKey string + playbook string + inventory string + requirements string + files map[string][]byte outputWriter io.Writer stateMux *sync.Mutex @@ -63,6 +64,11 @@ func (sess *Session) Inventory(s string) *Session { return sess } +func (sess *Session) Requirements(s string) *Session { + sess.requirements = s + return sess +} + func (sess *Session) AddFile(path string, data []byte) *Session { sess.files[path] = data return sess @@ -143,6 +149,17 @@ func (sess *Session) Run(ctx context.Context) (err error) { } } + // 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) @@ -167,6 +184,29 @@ func (sess *Session) Run(ctx context.Context) (err error) { } }() + // install required roles + if len(requirements) > 0 { + args := []string{ + "install", "-r", requirements, + } + 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,