package app
import (
"fmt"
"sort"
"strings"
"github.com/openshift/origin/pkg/generate"
"k8s.io/kubernetes/pkg/util/errors"
)
// IsComponentReference returns true if the provided string appears to be a reference to a source repository
// on disk, at a URL, a docker image name (which might be on a Docker registry or an OpenShift image stream),
// or a template.
func IsComponentReference(s string) bool {
if len(s) == 0 {
return false
}
all := strings.Split(s, "+")
_, _, _, err := componentWithSource(all[0])
return err == nil
}
// componentWithSource parses the provided string and returns an image component
// and optionally a repository on success
func componentWithSource(s string) (component, repo string, builder bool, err error) {
if strings.Contains(s, "~") {
segs := strings.SplitN(s, "~", 2)
if len(segs) == 2 {
builder = true
switch {
case len(segs[0]) == 0:
err = fmt.Errorf("when using '[image]~[code]' form for %q, you must specify a image name", s)
return
case len(segs[1]) == 0:
component = segs[0]
default:
component = segs[0]
repo = segs[1]
}
}
} else {
component = s
}
return
}
// ComponentReference defines an interface for components
type ComponentReference interface {
// Input contains the input of the component
Input() *ComponentInput
// Resolve sets the match in input
Resolve() error
// Search sets the search matches in input
Search() error
// NeedsSource indicates if the component needs source code
NeedsSource() bool
}
// ComponentReferences is a set of components
type ComponentReferences []ComponentReference
func (r ComponentReferences) filter(filterFunc func(ref ComponentReference) bool) ComponentReferences {
refs := ComponentReferences{}
for _, ref := range r {
if filterFunc(ref) {
refs = append(refs, ref)
}
}
return refs
}
// HasSource returns true if there is more than one component that has a repo associated
func (r ComponentReferences) HasSource() bool {
return len(r.filter(func(ref ComponentReference) bool { return ref.Input().Uses != nil })) > 0
}
// NeedsSource returns all the components that need source code in order to build
func (r ComponentReferences) NeedsSource() (refs ComponentReferences) {
return r.filter(func(ref ComponentReference) bool {
return ref.NeedsSource()
})
}
// UseSource returns all the components that use source repositories
func (r ComponentReferences) UseSource() (refs ComponentReferences) {
return r.filter(func(ref ComponentReference) bool {
return ref.Input().Uses != nil
})
}
// ImageComponentRefs returns the list of component references to images
func (r ComponentReferences) ImageComponentRefs() (refs ComponentReferences) {
return r.filter(func(ref ComponentReference) bool {
if ref.Input().ScratchImage {
return true
}
return ref.Input() != nil && ref.Input().ResolvedMatch != nil && ref.Input().ResolvedMatch.IsImage()
})
}
// TemplateComponentRefs returns the list of component references to templates
func (r ComponentReferences) TemplateComponentRefs() (refs ComponentReferences) {
return r.filter(func(ref ComponentReference) bool {
return ref.Input() != nil && ref.Input().ResolvedMatch != nil && ref.Input().ResolvedMatch.IsTemplate()
})
}
// InstallableComponentRefs returns the list of component references to templates
func (r ComponentReferences) InstallableComponentRefs() (refs ComponentReferences) {
return r.filter(func(ref ComponentReference) bool {
return ref.Input() != nil && ref.Input().ResolvedMatch != nil && ref.Input().ResolvedMatch.GeneratorInput.Job
})
}
func (r ComponentReferences) String() string {
return r.HumanString(",")
}
func (r ComponentReferences) HumanString(separator string) string {
components := []string{}
for _, ref := range r {
components = append(components, ref.Input().Value)
}
return strings.Join(components, separator)
}
// GroupedComponentReferences is a set of components that can be grouped
// by their group id
type GroupedComponentReferences ComponentReferences
func (m GroupedComponentReferences) Len() int { return len(m) }
func (m GroupedComponentReferences) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m GroupedComponentReferences) Less(i, j int) bool {
return m[i].Input().GroupID < m[j].Input().GroupID
}
// Group groups components based on their group ids
func (r ComponentReferences) Group() (refs []ComponentReferences) {
sorted := make(GroupedComponentReferences, len(r))
copy(sorted, r)
sort.Sort(sorted)
groupID := -1
for _, ref := range sorted {
if ref.Input().GroupID != groupID {
refs = append(refs, ComponentReferences{})
}
groupID = ref.Input().GroupID
refs[len(refs)-1] = append(refs[len(refs)-1], ref)
}
return
}
// Resolve the references to ensure they are all valid, and identify any images that don't match user input.
func (components ComponentReferences) Resolve() error {
errs := []error{}
for _, ref := range components {
if err := ref.Resolve(); err != nil {
errs = append(errs, err)
continue
}
}
return errors.NewAggregate(errs)
}
// Search searches on all references
func (components ComponentReferences) Search() error {
errs := []error{}
for _, ref := range components {
if err := ref.Search(); err != nil {
errs = append(errs, err)
continue
}
}
return errors.NewAggregate(errs)
}
// GeneratorJobReference is a reference that should be treated as a job execution,
// not a direct app creation.
type GeneratorJobReference struct {
Ref ComponentReference
Input GeneratorInput
Err error
}
// ReferenceBuilder is used for building all the necessary object references
// for an application
type ReferenceBuilder struct {
refs ComponentReferences
repos SourceRepositories
errs []error
groupID int
}
// AddComponents turns all provided component inputs into component references
func (r *ReferenceBuilder) AddComponents(inputs []string, fn func(*ComponentInput) ComponentReference) ComponentReferences {
refs := ComponentReferences{}
for _, s := range inputs {
for _, s := range strings.Split(s, "+") {
input, repo, err := NewComponentInput(s)
if err != nil {
r.errs = append(r.errs, err)
continue
}
input.GroupID = r.groupID
ref := fn(input)
if len(repo) != 0 {
repository, ok := r.AddSourceRepository(repo, generate.StrategySource)
if !ok {
continue
}
input.Use(repository)
repository.UsedBy(ref)
}
refs = append(refs, ref)
}
r.groupID++
}
r.refs = append(r.refs, refs...)
return refs
}
// AddGroups adds group ids to groups of components
func (r *ReferenceBuilder) AddGroups(inputs []string) {
for _, s := range inputs {
groups := strings.Split(s, "+")
if len(groups) == 1 {
r.errs = append(r.errs, fmt.Errorf("group %q only contains a single name", s))
continue
}
to := -1
for _, group := range groups {
var match ComponentReference
for _, ref := range r.refs {
if group == ref.Input().Value {
match = ref
break
}
}
if match == nil {
r.errs = append(r.errs, fmt.Errorf("the name %q from the group definition is not in use, and can't be used", group))
break
}
if to == -1 {
to = match.Input().GroupID
} else {
match.Input().GroupID = to
}
}
}
}
// AddSourceRepository resolves the input to an actual source repository
func (r *ReferenceBuilder) AddSourceRepository(input string, strategy generate.Strategy) (*SourceRepository, bool) {
for _, existing := range r.repos {
if input == existing.location {
return existing, true
}
}
source, err := NewSourceRepository(input, strategy)
if err != nil {
r.errs = append(r.errs, err)
return nil, false
}
r.repos = append(r.repos, source)
return source, true
}
func (r *ReferenceBuilder) AddExistingSourceRepository(source *SourceRepository) {
r.repos = append(r.repos, source)
}
// Result returns the result of the config conversion to object references
func (r *ReferenceBuilder) Result() (ComponentReferences, SourceRepositories, []error) {
return r.refs, r.repos, r.errs
}
// NewComponentInput returns a new ComponentInput by checking for image using [image]~
// (to indicate builder) or [image]~[code] (builder plus code)
func NewComponentInput(input string) (*ComponentInput, string, error) {
component, repo, builder, err := componentWithSource(input)
if err != nil {
return nil, "", err
}
return &ComponentInput{
From: input,
Argument: input,
Value: component,
ExpectToBuild: builder,
}, repo, nil
}
// ComponentInput is the necessary input for creating a component
type ComponentInput struct {
GroupID int
From string
Argument string
Value string
ExpectToBuild bool
ScratchImage bool
Uses *SourceRepository
ResolvedMatch *ComponentMatch
SearchMatches ComponentMatches
Resolver
Searcher
}
// Input returns the component input
func (i *ComponentInput) Input() *ComponentInput {
return i
}
// NeedsSource indicates if the component input needs source code
func (i *ComponentInput) NeedsSource() bool {
return i.ExpectToBuild && i.Uses == nil
}
// Resolve sets the unique match in input
func (i *ComponentInput) Resolve() error {
if i.Resolver == nil {
return ErrNoMatch{Value: i.Value, Qualifier: "no resolver defined"}
}
match, err := i.Resolver.Resolve(i.Value)
if err != nil {
return err
}
i.Value = match.Value
i.Argument = match.Argument
i.ResolvedMatch = match
return nil
}
// Search sets the search matches in input
func (i *ComponentInput) Search() error {
if i.Searcher == nil {
return ErrNoMatch{Value: i.Value, Qualifier: "no searcher defined"}
}
matches, err := i.Searcher.Search(false, i.Value)
if matches != nil {
i.SearchMatches = matches
}
return errors.NewAggregate(err)
}
func (i *ComponentInput) String() string {
return i.Value
}
// Use adds the provided source repository as the used one
// by the component input
func (i *ComponentInput) Use(repo *SourceRepository) {
i.Uses = repo
}