mirror of
https://github.com/openshift/source-to-image.git
synced 2026-02-06 15:45:00 +01:00
286 lines
8.7 KiB
Go
Executable File
286 lines
8.7 KiB
Go
Executable File
package scripts
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/openshift/source-to-image/pkg/api"
|
|
"github.com/openshift/source-to-image/pkg/api/constants"
|
|
"github.com/openshift/source-to-image/pkg/docker"
|
|
s2ierr "github.com/openshift/source-to-image/pkg/errors"
|
|
"github.com/openshift/source-to-image/pkg/util"
|
|
"github.com/openshift/source-to-image/pkg/util/fs"
|
|
)
|
|
|
|
// Installer interface is responsible for installing scripts needed to run the
|
|
// build.
|
|
type Installer interface {
|
|
InstallRequired(scripts []string, dstDir string) ([]api.InstallResult, error)
|
|
InstallOptional(scripts []string, dstDir string) []api.InstallResult
|
|
}
|
|
|
|
// ScriptHandler provides an interface for various scripts source handlers.
|
|
type ScriptHandler interface {
|
|
Get(script string) *api.InstallResult
|
|
Install(*api.InstallResult) error
|
|
SetDestinationDir(string)
|
|
String() string
|
|
}
|
|
|
|
// URLScriptHandler handles script download using URL.
|
|
type URLScriptHandler struct {
|
|
URL string
|
|
DestinationDir string
|
|
Download Downloader
|
|
FS fs.FileSystem
|
|
Name string
|
|
}
|
|
|
|
const (
|
|
sourcesRootAbbrev = "<source-dir>"
|
|
|
|
// ScriptURLHandler is the name of the script URL handler
|
|
ScriptURLHandler = "script URL handler"
|
|
|
|
// ImageURLHandler is the name of the image URL handler
|
|
ImageURLHandler = "image URL handler"
|
|
|
|
// SourceHandler is the name of the source script handler
|
|
SourceHandler = "source handler"
|
|
)
|
|
|
|
var (
|
|
// RequiredScripts must be present to do an s2i build
|
|
RequiredScripts = []string{constants.Assemble, constants.Run}
|
|
|
|
// OptionalScripts may be provided when doing an s2i build
|
|
OptionalScripts = []string{constants.SaveArtifacts}
|
|
)
|
|
|
|
// SetDestinationDir sets the destination where the scripts should be
|
|
// downloaded.
|
|
func (s *URLScriptHandler) SetDestinationDir(baseDir string) {
|
|
s.DestinationDir = baseDir
|
|
}
|
|
|
|
// String implements the String() function.
|
|
func (s *URLScriptHandler) String() string {
|
|
return s.Name
|
|
}
|
|
|
|
// Get parses the provided URL and the script name.
|
|
func (s *URLScriptHandler) Get(script string) *api.InstallResult {
|
|
if len(s.URL) == 0 {
|
|
return nil
|
|
}
|
|
scriptURL, err := url.ParseRequestURI(s.URL + "/" + script)
|
|
if err != nil {
|
|
log.Infof("invalid script url %q: %v", s.URL, err)
|
|
return nil
|
|
}
|
|
return &api.InstallResult{
|
|
Script: script,
|
|
URL: scriptURL.String(),
|
|
}
|
|
}
|
|
|
|
// Install downloads the script and fix its permissions.
|
|
func (s *URLScriptHandler) Install(r *api.InstallResult) error {
|
|
downloadURL, err := url.Parse(r.URL)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dst := filepath.Join(s.DestinationDir, constants.UploadScripts, r.Script)
|
|
if _, err := s.Download.Download(downloadURL, dst); err != nil {
|
|
if e, ok := err.(s2ierr.Error); ok {
|
|
if e.ErrorCode == s2ierr.ScriptsInsideImageError {
|
|
r.Installed = true
|
|
return nil
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
if err := s.FS.Chmod(dst, 0755); err != nil {
|
|
return err
|
|
}
|
|
r.Installed = true
|
|
r.Downloaded = true
|
|
return nil
|
|
}
|
|
|
|
// SourceScriptHandler handles the case when the scripts are contained in the
|
|
// source code directory.
|
|
type SourceScriptHandler struct {
|
|
DestinationDir string
|
|
fs fs.FileSystem
|
|
}
|
|
|
|
// Get verifies if the script is present in the source directory and get the
|
|
// installation result.
|
|
func (s *SourceScriptHandler) Get(script string) *api.InstallResult {
|
|
location := filepath.Join(s.DestinationDir, constants.SourceScripts, script)
|
|
if s.fs.Exists(location) {
|
|
return &api.InstallResult{Script: script, URL: location}
|
|
}
|
|
// TODO: The '.sti/bin' path inside the source code directory is deprecated
|
|
// and this should (and will) be removed soon.
|
|
location = filepath.FromSlash(strings.Replace(filepath.ToSlash(location), "s2i/bin", "sti/bin", 1))
|
|
if s.fs.Exists(location) {
|
|
log.Info("DEPRECATED: Use .s2i/bin instead of .sti/bin")
|
|
return &api.InstallResult{Script: script, URL: location}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// String implements the String() function.
|
|
func (s *SourceScriptHandler) String() string {
|
|
return SourceHandler
|
|
}
|
|
|
|
// Install copies the script into upload directory and fix its permissions.
|
|
func (s *SourceScriptHandler) Install(r *api.InstallResult) error {
|
|
dst := filepath.Join(s.DestinationDir, constants.UploadScripts, r.Script)
|
|
if err := s.fs.Rename(r.URL, dst); err != nil {
|
|
return err
|
|
}
|
|
if err := s.fs.Chmod(dst, 0755); err != nil {
|
|
return err
|
|
}
|
|
// Make the path to scripts nicer in logs
|
|
parts := strings.Split(filepath.ToSlash(r.URL), "/")
|
|
if len(parts) > 3 {
|
|
r.URL = filepath.FromSlash(sourcesRootAbbrev + "/" + strings.Join(parts[len(parts)-3:], "/"))
|
|
}
|
|
r.Installed = true
|
|
r.Downloaded = true
|
|
return nil
|
|
}
|
|
|
|
// SetDestinationDir sets the directory where the scripts should be uploaded.
|
|
// In case of SourceScriptHandler this is a source directory root.
|
|
func (s *SourceScriptHandler) SetDestinationDir(baseDir string) {
|
|
s.DestinationDir = baseDir
|
|
}
|
|
|
|
// ScriptSourceManager manages various script handlers.
|
|
type ScriptSourceManager interface {
|
|
Add(ScriptHandler)
|
|
SetDownloader(Downloader)
|
|
Installer
|
|
}
|
|
|
|
// DefaultScriptSourceManager manages the default script lookup and installation
|
|
// for source-to-image.
|
|
type DefaultScriptSourceManager struct {
|
|
Image string
|
|
ScriptsURL string
|
|
download Downloader
|
|
docker docker.Docker
|
|
dockerAuth api.AuthConfig
|
|
sources []ScriptHandler
|
|
fs fs.FileSystem
|
|
}
|
|
|
|
// Add registers a new script source handler.
|
|
func (m *DefaultScriptSourceManager) Add(s ScriptHandler) {
|
|
if len(m.sources) == 0 {
|
|
m.sources = []ScriptHandler{}
|
|
}
|
|
m.sources = append(m.sources, s)
|
|
}
|
|
|
|
// NewInstaller returns a new instance of the default Installer implementation
|
|
func NewInstaller(image string, scriptsURL string, proxyConfig *api.ProxyConfig, docker docker.Docker, auth api.AuthConfig, fs fs.FileSystem, config *api.Config) Installer {
|
|
m := DefaultScriptSourceManager{
|
|
Image: image,
|
|
ScriptsURL: scriptsURL,
|
|
dockerAuth: auth,
|
|
docker: docker,
|
|
fs: fs,
|
|
download: NewDownloader(proxyConfig),
|
|
}
|
|
// Order is important here, first we try to get the scripts from provided URL,
|
|
// then we look into sources and check for .s2i/bin scripts.
|
|
if len(m.ScriptsURL) > 0 {
|
|
m.Add(&URLScriptHandler{URL: m.ScriptsURL, Download: m.download, FS: m.fs, Name: ScriptURLHandler})
|
|
}
|
|
|
|
m.Add(&SourceScriptHandler{fs: m.fs})
|
|
|
|
if m.docker != nil {
|
|
// If the detection handlers above fail, try to get the script url from the
|
|
// docker image itself.
|
|
defaultURL, err := m.docker.GetScriptsURL(m.Image)
|
|
if err == nil && defaultURL != "" {
|
|
m.Add(&URLScriptHandler{URL: defaultURL, Download: m.download, FS: m.fs, Name: ImageURLHandler})
|
|
}
|
|
} else {
|
|
// this means we are doing a s2i build with --as-dockerfile
|
|
// so lets see if we found builder image labels
|
|
scriptsURL, _ := util.AdjustConfigWithImageLabels(config)
|
|
if len(scriptsURL) > 0 {
|
|
m.Add(&URLScriptHandler{URL: scriptsURL, Download: m.download, FS: m.fs, Name: ScriptURLHandler})
|
|
}
|
|
}
|
|
return &m
|
|
}
|
|
|
|
// InstallRequired Downloads and installs required scripts into dstDir, the result is a
|
|
// map of scripts with detailed information about each of the scripts install process
|
|
// with error if installing some of them failed
|
|
func (m *DefaultScriptSourceManager) InstallRequired(scripts []string, dstDir string) ([]api.InstallResult, error) {
|
|
result := m.InstallOptional(scripts, dstDir)
|
|
failedScripts := []string{}
|
|
var err error
|
|
for _, r := range result {
|
|
if r.Error != nil {
|
|
failedScripts = append(failedScripts, r.Script)
|
|
}
|
|
}
|
|
if len(failedScripts) > 0 {
|
|
err = s2ierr.NewInstallRequiredError(failedScripts, constants.ScriptsURLLabel)
|
|
}
|
|
return result, err
|
|
}
|
|
|
|
// InstallOptional downloads and installs a set of scripts into dstDir, the result is a
|
|
// map of scripts with detailed information about each of the scripts install process
|
|
func (m *DefaultScriptSourceManager) InstallOptional(scripts []string, dstDir string) []api.InstallResult {
|
|
result := []api.InstallResult{}
|
|
for _, script := range scripts {
|
|
installed := false
|
|
failedSources := []string{}
|
|
for _, e := range m.sources {
|
|
detected := false
|
|
h := e.(ScriptHandler)
|
|
h.SetDestinationDir(dstDir)
|
|
if r := h.Get(script); r != nil {
|
|
if err := h.Install(r); err != nil {
|
|
failedSources = append(failedSources, h.String())
|
|
// all this means is this source didn't have this particular script
|
|
log.V(4).Infof("script %q found by the %s, but failed to install: %v", script, h, err)
|
|
} else {
|
|
r.FailedSources = failedSources
|
|
result = append(result, *r)
|
|
installed = true
|
|
detected = true
|
|
log.V(4).Infof("Using %q installed from %q", script, r.URL)
|
|
}
|
|
}
|
|
if detected {
|
|
break
|
|
}
|
|
}
|
|
if !installed {
|
|
result = append(result, api.InstallResult{
|
|
FailedSources: failedSources,
|
|
Script: script,
|
|
Error: fmt.Errorf("script %q not installed", script),
|
|
})
|
|
}
|
|
}
|
|
return result
|
|
}
|