github.imxd.top/openshift/source-to-image@v1.2.0/pkg/scripts/install.go (about) 1 package scripts 2 3 import ( 4 "fmt" 5 "net/url" 6 "path/filepath" 7 "strings" 8 9 "github.com/openshift/source-to-image/pkg/api" 10 "github.com/openshift/source-to-image/pkg/api/constants" 11 "github.com/openshift/source-to-image/pkg/docker" 12 s2ierr "github.com/openshift/source-to-image/pkg/errors" 13 "github.com/openshift/source-to-image/pkg/util/fs" 14 ) 15 16 // Installer interface is responsible for installing scripts needed to run the 17 // build. 18 type Installer interface { 19 InstallRequired(scripts []string, dstDir string) ([]api.InstallResult, error) 20 InstallOptional(scripts []string, dstDir string) []api.InstallResult 21 } 22 23 // ScriptHandler provides an interface for various scripts source handlers. 24 type ScriptHandler interface { 25 Get(script string) *api.InstallResult 26 Install(*api.InstallResult) error 27 SetDestinationDir(string) 28 String() string 29 } 30 31 // URLScriptHandler handles script download using URL. 32 type URLScriptHandler struct { 33 URL string 34 DestinationDir string 35 Download Downloader 36 FS fs.FileSystem 37 Name string 38 } 39 40 const ( 41 sourcesRootAbbrev = "<source-dir>" 42 43 // ScriptURLHandler is the name of the script URL handler 44 ScriptURLHandler = "script URL handler" 45 46 // ImageURLHandler is the name of the image URL handler 47 ImageURLHandler = "image URL handler" 48 49 // SourceHandler is the name of the source script handler 50 SourceHandler = "source handler" 51 ) 52 53 var ( 54 // RequiredScripts must be present to do an s2i build 55 RequiredScripts = []string{constants.Assemble, constants.Run} 56 57 // OptionalScripts may be provided when doing an s2i build 58 OptionalScripts = []string{constants.SaveArtifacts} 59 ) 60 61 // SetDestinationDir sets the destination where the scripts should be 62 // downloaded. 63 func (s *URLScriptHandler) SetDestinationDir(baseDir string) { 64 s.DestinationDir = baseDir 65 } 66 67 // String implements the String() function. 68 func (s *URLScriptHandler) String() string { 69 return s.Name 70 } 71 72 // Get parses the provided URL and the script name. 73 func (s *URLScriptHandler) Get(script string) *api.InstallResult { 74 if len(s.URL) == 0 { 75 return nil 76 } 77 scriptURL, err := url.ParseRequestURI(s.URL + "/" + script) 78 if err != nil { 79 log.Infof("invalid script url %q: %v", s.URL, err) 80 return nil 81 } 82 return &api.InstallResult{ 83 Script: script, 84 URL: scriptURL.String(), 85 } 86 } 87 88 // Install downloads the script and fix its permissions. 89 func (s *URLScriptHandler) Install(r *api.InstallResult) error { 90 downloadURL, err := url.Parse(r.URL) 91 if err != nil { 92 return err 93 } 94 dst := filepath.Join(s.DestinationDir, constants.UploadScripts, r.Script) 95 if _, err := s.Download.Download(downloadURL, dst); err != nil { 96 if e, ok := err.(s2ierr.Error); ok { 97 if e.ErrorCode == s2ierr.ScriptsInsideImageError { 98 r.Installed = true 99 return nil 100 } 101 } 102 return err 103 } 104 if err := s.FS.Chmod(dst, 0755); err != nil { 105 return err 106 } 107 r.Installed = true 108 r.Downloaded = true 109 return nil 110 } 111 112 // SourceScriptHandler handles the case when the scripts are contained in the 113 // source code directory. 114 type SourceScriptHandler struct { 115 DestinationDir string 116 fs fs.FileSystem 117 } 118 119 // Get verifies if the script is present in the source directory and get the 120 // installation result. 121 func (s *SourceScriptHandler) Get(script string) *api.InstallResult { 122 location := filepath.Join(s.DestinationDir, constants.SourceScripts, script) 123 if s.fs.Exists(location) { 124 return &api.InstallResult{Script: script, URL: location} 125 } 126 // TODO: The '.sti/bin' path inside the source code directory is deprecated 127 // and this should (and will) be removed soon. 128 location = filepath.FromSlash(strings.Replace(filepath.ToSlash(location), "s2i/bin", "sti/bin", 1)) 129 if s.fs.Exists(location) { 130 log.Info("DEPRECATED: Use .s2i/bin instead of .sti/bin") 131 return &api.InstallResult{Script: script, URL: location} 132 } 133 return nil 134 } 135 136 // String implements the String() function. 137 func (s *SourceScriptHandler) String() string { 138 return SourceHandler 139 } 140 141 // Install copies the script into upload directory and fix its permissions. 142 func (s *SourceScriptHandler) Install(r *api.InstallResult) error { 143 dst := filepath.Join(s.DestinationDir, constants.UploadScripts, r.Script) 144 if err := s.fs.Rename(r.URL, dst); err != nil { 145 return err 146 } 147 if err := s.fs.Chmod(dst, 0755); err != nil { 148 return err 149 } 150 // Make the path to scripts nicer in logs 151 parts := strings.Split(filepath.ToSlash(r.URL), "/") 152 if len(parts) > 3 { 153 r.URL = filepath.FromSlash(sourcesRootAbbrev + "/" + strings.Join(parts[len(parts)-3:], "/")) 154 } 155 r.Installed = true 156 r.Downloaded = true 157 return nil 158 } 159 160 // SetDestinationDir sets the directory where the scripts should be uploaded. 161 // In case of SourceScriptHandler this is a source directory root. 162 func (s *SourceScriptHandler) SetDestinationDir(baseDir string) { 163 s.DestinationDir = baseDir 164 } 165 166 // ScriptSourceManager manages various script handlers. 167 type ScriptSourceManager interface { 168 Add(ScriptHandler) 169 SetDownloader(Downloader) 170 Installer 171 } 172 173 // DefaultScriptSourceManager manages the default script lookup and installation 174 // for source-to-image. 175 type DefaultScriptSourceManager struct { 176 Image string 177 ScriptsURL string 178 download Downloader 179 docker docker.Docker 180 dockerAuth api.AuthConfig 181 sources []ScriptHandler 182 fs fs.FileSystem 183 } 184 185 // Add registers a new script source handler. 186 func (m *DefaultScriptSourceManager) Add(s ScriptHandler) { 187 if len(m.sources) == 0 { 188 m.sources = []ScriptHandler{} 189 } 190 m.sources = append(m.sources, s) 191 } 192 193 // NewInstaller returns a new instance of the default Installer implementation 194 func NewInstaller(image string, scriptsURL string, proxyConfig *api.ProxyConfig, docker docker.Docker, auth api.AuthConfig, fs fs.FileSystem) Installer { 195 m := DefaultScriptSourceManager{ 196 Image: image, 197 ScriptsURL: scriptsURL, 198 dockerAuth: auth, 199 docker: docker, 200 fs: fs, 201 download: NewDownloader(proxyConfig), 202 } 203 // Order is important here, first we try to get the scripts from provided URL, 204 // then we look into sources and check for .s2i/bin scripts. 205 if len(m.ScriptsURL) > 0 { 206 m.Add(&URLScriptHandler{URL: m.ScriptsURL, Download: m.download, FS: m.fs, Name: ScriptURLHandler}) 207 } 208 209 m.Add(&SourceScriptHandler{fs: m.fs}) 210 211 if m.docker != nil { 212 // If the detection handlers above fail, try to get the script url from the 213 // docker image itself. 214 defaultURL, err := m.docker.GetScriptsURL(m.Image) 215 if err == nil && defaultURL != "" { 216 m.Add(&URLScriptHandler{URL: defaultURL, Download: m.download, FS: m.fs, Name: ImageURLHandler}) 217 } 218 } 219 return &m 220 } 221 222 // InstallRequired Downloads and installs required scripts into dstDir, the result is a 223 // map of scripts with detailed information about each of the scripts install process 224 // with error if installing some of them failed 225 func (m *DefaultScriptSourceManager) InstallRequired(scripts []string, dstDir string) ([]api.InstallResult, error) { 226 result := m.InstallOptional(scripts, dstDir) 227 failedScripts := []string{} 228 var err error 229 for _, r := range result { 230 if r.Error != nil { 231 failedScripts = append(failedScripts, r.Script) 232 } 233 } 234 if len(failedScripts) > 0 { 235 err = s2ierr.NewInstallRequiredError(failedScripts, constants.ScriptsURLLabel) 236 } 237 return result, err 238 } 239 240 // InstallOptional downloads and installs a set of scripts into dstDir, the result is a 241 // map of scripts with detailed information about each of the scripts install process 242 func (m *DefaultScriptSourceManager) InstallOptional(scripts []string, dstDir string) []api.InstallResult { 243 result := []api.InstallResult{} 244 for _, script := range scripts { 245 installed := false 246 failedSources := []string{} 247 for _, e := range m.sources { 248 detected := false 249 h := e.(ScriptHandler) 250 h.SetDestinationDir(dstDir) 251 if r := h.Get(script); r != nil { 252 if err := h.Install(r); err != nil { 253 failedSources = append(failedSources, h.String()) 254 // all this means is this source didn't have this particular script 255 log.V(4).Infof("script %q found by the %s, but failed to install: %v", script, h, err) 256 } else { 257 r.FailedSources = failedSources 258 result = append(result, *r) 259 installed = true 260 detected = true 261 log.V(4).Infof("Using %q installed from %q", script, r.URL) 262 } 263 } 264 if detected { 265 break 266 } 267 } 268 if !installed { 269 result = append(result, api.InstallResult{ 270 FailedSources: failedSources, 271 Script: script, 272 Error: fmt.Errorf("script %q not installed", script), 273 }) 274 } 275 } 276 return result 277 }