github.imxd.top/operator-framework/operator-sdk@v0.8.2/pkg/ansible/runner/internal/inputdir/inputdir.go (about) 1 // Copyright 2018 The Operator-SDK Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package inputdir 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "strings" 24 25 "github.com/operator-framework/operator-sdk/internal/util/fileutil" 26 "github.com/spf13/afero" 27 28 logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" 29 ) 30 31 var log = logf.Log.WithName("inputdir") 32 33 // InputDir represents an input directory for ansible-runner. 34 type InputDir struct { 35 Path string 36 PlaybookPath string 37 Parameters map[string]interface{} 38 EnvVars map[string]string 39 Settings map[string]string 40 } 41 42 // makeDirs creates the required directory structure. 43 func (i *InputDir) makeDirs() error { 44 for _, path := range []string{"env", "project", "inventory"} { 45 fullPath := filepath.Join(i.Path, path) 46 err := os.MkdirAll(fullPath, os.ModePerm) 47 if err != nil { 48 log.Error(err, "Unable to create directory", "Path", fullPath) 49 return err 50 } 51 } 52 return nil 53 } 54 55 // addFile adds a file to the given relative path within the input directory. 56 func (i *InputDir) addFile(path string, content []byte) error { 57 fullPath := filepath.Join(i.Path, path) 58 err := ioutil.WriteFile(fullPath, content, 0644) 59 if err != nil { 60 log.Error(err, "Unable to write file", "Path", fullPath) 61 } 62 return err 63 } 64 65 // copyInventory copies a file or directory from src to dst 66 func (i *InputDir) copyInventory(src string, dst string) error { 67 fs := afero.NewOsFs() 68 return afero.Walk(fs, src, 69 func(path string, info os.FileInfo, err error) error { 70 if err != nil { 71 return err 72 } 73 fullDst := strings.Replace(path, src, dst, 1) 74 if info.IsDir() { 75 if err = fs.MkdirAll(fullDst, info.Mode()); err != nil { 76 return err 77 } 78 } else { 79 f, err := fs.Open(path) 80 if err != nil { 81 return err 82 } 83 if err = afero.WriteReader(fs, fullDst, f); err != nil { 84 return err 85 } 86 if err = fs.Chmod(fullDst, info.Mode()); err != nil { 87 return err 88 } 89 } 90 return nil 91 }) 92 } 93 94 // Stdout reads the stdout from the ansible artifact that corresponds to the 95 // given ident and returns it as a string. 96 func (i *InputDir) Stdout(ident string) (string, error) { 97 errorPath := filepath.Join(i.Path, "artifacts", ident, "stdout") 98 errorText, err := ioutil.ReadFile(errorPath) 99 return string(errorText), err 100 } 101 102 // Write commits the object's state to the filesystem at i.Path. 103 func (i *InputDir) Write() error { 104 paramBytes, err := json.Marshal(i.Parameters) 105 if err != nil { 106 return err 107 } 108 envVarBytes, err := json.Marshal(i.EnvVars) 109 if err != nil { 110 return err 111 } 112 settingsBytes, err := json.Marshal(i.Settings) 113 if err != nil { 114 return err 115 } 116 117 err = i.makeDirs() 118 if err != nil { 119 return err 120 } 121 122 err = i.addFile("env/envvars", envVarBytes) 123 if err != nil { 124 return err 125 } 126 err = i.addFile("env/extravars", paramBytes) 127 if err != nil { 128 return err 129 } 130 err = i.addFile("env/settings", settingsBytes) 131 if err != nil { 132 return err 133 } 134 135 // ANSIBLE_INVENTORY takes precedence over our generated hosts file 136 // so if the envvar is set we don't bother making it, we just copy 137 // the inventory into our runner directory 138 ansible_inventory := os.Getenv("ANSIBLE_INVENTORY") 139 if ansible_inventory == "" { 140 // If ansible-runner is running in a python virtual environment, propagate 141 // that to ansible. 142 venv := os.Getenv("VIRTUAL_ENV") 143 hosts := "localhost ansible_connection=local" 144 if venv != "" { 145 hosts = fmt.Sprintf("%s ansible_python_interpreter=%s", hosts, filepath.Join(venv, "bin/python")) 146 } 147 err = i.addFile("inventory/hosts", []byte(hosts)) 148 if err != nil { 149 return err 150 } 151 } else { 152 fi, err := os.Stat(ansible_inventory) 153 if err != nil { 154 return err 155 } 156 switch mode := fi.Mode(); { 157 case mode.IsDir(): 158 err = i.copyInventory(ansible_inventory, filepath.Join(i.Path, "inventory")) 159 if err != nil { 160 return err 161 } 162 case mode.IsRegular(): 163 err = i.copyInventory(ansible_inventory, filepath.Join(i.Path, "inventory/hosts")) 164 if err != nil { 165 return err 166 } 167 } 168 } 169 170 if i.PlaybookPath != "" { 171 f, err := os.Open(i.PlaybookPath) 172 if err != nil { 173 log.Error(err, "Failed to open playbook file", "Path", i.PlaybookPath) 174 return err 175 } 176 defer func() { 177 if err := f.Close(); err != nil && !fileutil.IsClosedError(err) { 178 log.Error(err, "Failed to close playbook file") 179 } 180 }() 181 182 playbookBytes, err := ioutil.ReadAll(f) 183 if err != nil { 184 return err 185 } 186 187 err = i.addFile("project/playbook.yaml", playbookBytes) 188 if err != nil { 189 return err 190 } 191 } 192 return nil 193 }