Browse code

Reorganize command line code and move app-gen under experimental

csrwng authored on 2015/01/28 06:56:41
Showing 31 changed files
1 1
deleted file mode 100644
... ...
@@ -1,189 +0,0 @@
1
-package main
2
-
3
-import (
4
-	"fmt"
5
-	"os"
6
-
7
-	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
8
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
9
-	"github.com/fsouza/go-dockerclient"
10
-	"github.com/spf13/cobra"
11
-
12
-	"github.com/openshift/origin/pkg/api/latest"
13
-	"github.com/openshift/origin/pkg/client"
14
-	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
15
-	dh "github.com/openshift/origin/pkg/cmd/util/docker"
16
-	config "github.com/openshift/origin/pkg/config/api"
17
-	appgen "github.com/openshift/origin/pkg/generate/app"
18
-	gen "github.com/openshift/origin/pkg/generate/generator"
19
-	"github.com/openshift/origin/pkg/generate/imageinfo"
20
-	"github.com/openshift/origin/pkg/generate/source"
21
-)
22
-
23
-type Input struct {
24
-	name,
25
-	sourceDir,
26
-	sourceURL,
27
-	dockerContext,
28
-	builderImage,
29
-	outputImage string
30
-}
31
-
32
-func main() {
33
-	cfg := clientcmd.NewConfig()
34
-	dockerHelper := dh.NewHelper()
35
-	input := Input{}
36
-	cmd := &cobra.Command{
37
-		Use:   fmt.Sprintf("%s%s", "gen-app", clientcmd.ConfigSyntax),
38
-		Short: "Generate an application configuration",
39
-		Long:  "Generate an application configuration",
40
-		Run: func(c *cobra.Command, args []string) {
41
-			_, osClient, err := cfg.Clients()
42
-			if err != nil {
43
-				osClient = nil
44
-			}
45
-			dockerClient, _, err := dockerHelper.GetClient()
46
-			if err != nil {
47
-				osClient = nil
48
-			}
49
-			GenerateApp(input, osClient, dockerClient)
50
-		},
51
-	}
52
-
53
-	flag := cmd.Flags()
54
-	flag.StringVar(&input.name, "name", "", "Set name to use for generated application artifacts")
55
-	flag.StringVar(&input.sourceDir, "source-dir", "", "Set the source directory for the application build")
56
-	flag.StringVar(&input.sourceURL, "source-url", "", "Set the source URL")
57
-	flag.StringVar(&input.dockerContext, "context", "", "Context path for Dockerfile if creating a Docker build")
58
-	flag.StringVar(&input.builderImage, "builder-image", "", "Image to use for STI build")
59
-	flag.StringVar(&input.outputImage, "output-image", "", "Image name to use for output")
60
-	cfg.Bind(flag)
61
-	dockerHelper.InstallFlags(flag)
62
-	if err := cmd.Execute(); err != nil {
63
-		fmt.Fprintf(os.Stderr, "Error: %s", err)
64
-		os.Exit(1)
65
-	}
66
-}
67
-
68
-func GenerateApp(input Input, client client.Interface, dockerClient *docker.Client) {
69
-	// Get a SourceRef
70
-	var srcRef *appgen.SourceRef
71
-	var err error
72
-	srcRefGen := gen.NewSourceRefGenerator()
73
-	if len(input.sourceURL) > 0 {
74
-		if srcRef, err = srcRefGen.FromGitURL(input.sourceURL); err != nil {
75
-			exitWithError(err)
76
-		}
77
-	} else {
78
-		if len(input.sourceDir) == 0 {
79
-			if input.sourceDir, err = os.Getwd(); err != nil {
80
-				exitWithError(err)
81
-			}
82
-		}
83
-		if srcRef, err = srcRefGen.FromDirectory(input.sourceDir); err != nil {
84
-			exitWithError(err)
85
-		}
86
-	}
87
-
88
-	// Get a BuildStrategyRef
89
-	var strategyRef *appgen.BuildStrategyRef
90
-	strategyRefGen := gen.NewBuildStrategyRefGenerator(source.DefaultDetectors)
91
-	imageRefGen := gen.NewImageRefGenerator()
92
-	if len(input.dockerContext) > 0 {
93
-		if strategyRef, err = strategyRefGen.FromSourceRefAndDockerContext(*srcRef, input.dockerContext); err != nil {
94
-			exitWithError(err)
95
-		}
96
-	} else if len(input.builderImage) > 0 {
97
-		builderRef, err := imageRefGen.FromName(input.builderImage)
98
-		if err != nil {
99
-			exitWithError(err)
100
-		}
101
-		if strategyRef, err = strategyRefGen.FromSTIBuilderImage(builderRef); err != nil {
102
-			exitWithError(err)
103
-		}
104
-	} else {
105
-		if strategyRef, err = strategyRefGen.FromSourceRef(*srcRef); err != nil {
106
-			exitWithError(err)
107
-		}
108
-	}
109
-
110
-	// Get an ImageRef for Output
111
-	outputImage := input.outputImage
112
-	if len(outputImage) == 0 {
113
-		var ok bool
114
-		if outputImage, ok = srcRef.SuggestName(); !ok {
115
-			exitWithError(fmt.Errorf("Cannot suggest a name for the output image, please specify one in the command line"))
116
-		}
117
-	}
118
-	outputRef, err := imageRefGen.FromName(outputImage)
119
-	if err != nil {
120
-		exitWithError(err)
121
-	}
122
-
123
-	// Get a BuildRef
124
-	buildRef := appgen.BuildRef{
125
-		Source:   srcRef,
126
-		Strategy: strategyRef,
127
-		Output:   outputRef,
128
-	}
129
-
130
-	// Get a DeploymentConfigRef
131
-	var imageInfoRetriever imageinfo.Retriever
132
-	if client != nil {
133
-		imageInfoRetriever = imageinfo.NewRetriever(
134
-			client.ImageRepositories(kapi.NamespaceAll),
135
-			client.Images(kapi.NamespaceAll),
136
-			dockerClient)
137
-	} else {
138
-		imageInfoRetriever = imageinfo.NewRetriever(nil, nil, dockerClient)
139
-	}
140
-	imageInfoGenerator := gen.NewImageInfoGenerator(imageInfoRetriever)
141
-	imageInfos := imageInfoGenerator.FromImageRefs([]appgen.ImageRef{*outputRef})
142
-	deployRef := appgen.DeploymentConfigRef{
143
-		Images: imageInfos,
144
-	}
145
-
146
-	// Generate OpenShift resources
147
-	config := config.Config{}
148
-	bldcfg, err := buildRef.BuildConfig()
149
-	if err != nil {
150
-		exitWithError(err)
151
-	}
152
-	imgrepo, err := outputRef.ImageRepository()
153
-	if err != nil {
154
-		exitWithError(err)
155
-	}
156
-	deploycfg, err := deployRef.DeploymentConfig()
157
-	if err != nil {
158
-		exitWithError(err)
159
-	}
160
-	addToConfig(&config, bldcfg)
161
-	addToConfig(&config, imgrepo)
162
-	addToConfig(&config, deploycfg)
163
-
164
-	if strategyRef.Base != nil {
165
-		baserepo, err := strategyRef.Base.ImageRepository()
166
-		if err == nil {
167
-			addToConfig(&config, baserepo)
168
-		}
169
-	}
170
-
171
-	result, err := latest.Codec.Encode(&config)
172
-	if err != nil {
173
-		exitWithError(err)
174
-	}
175
-	fmt.Println(string(result))
176
-}
177
-
178
-func exitWithError(err error) {
179
-	fmt.Fprintf(os.Stderr, "%v", err)
180
-	os.Exit(1)
181
-}
182
-
183
-func addToConfig(cfg *config.Config, object runtime.Object) {
184
-	json, err := latest.Codec.Encode(object)
185
-	if err != nil {
186
-		exitWithError(err)
187
-	}
188
-	cfg.Items = append(cfg.Items, runtime.RawExtension{RawJSON: json})
189
-}
... ...
@@ -11,7 +11,6 @@ import (
11 11
 	"github.com/spf13/pflag"
12 12
 
13 13
 	"github.com/openshift/origin/pkg/cmd/cli/cmd"
14
-	cmdnew "github.com/openshift/origin/pkg/cmd/cli/cmd/new"
15 14
 )
16 15
 
17 16
 const longDesc = `
... ...
@@ -47,7 +46,7 @@ func NewCommandCLI(name, fullName string) *cobra.Command {
47 47
 	clientcmd.DefaultCluster.Server = defaultClusterURL
48 48
 
49 49
 	// TODO: there should be two client configs, one for OpenShift, and one for Kubernetes
50
-	clientConfig := defaultClientConfig(cmds.PersistentFlags())
50
+	clientConfig := DefaultClientConfig(cmds.PersistentFlags())
51 51
 	f := cmd.NewFactory(clientConfig)
52 52
 	f.BindFlags(cmds.PersistentFlags())
53 53
 	out := os.Stdout
... ...
@@ -65,6 +64,7 @@ func NewCommandCLI(name, fullName string) *cobra.Command {
65 65
 	cmds.AddCommand(f.NewCmdProxy(out))
66 66
 
67 67
 	// Origin commands
68
+	// cmds.AddCommand(cmd.NewCmdNewApplication(f, out))
68 69
 	cmds.AddCommand(cmd.NewCmdProcess(f, out))
69 70
 
70 71
 	// Origin build commands
... ...
@@ -82,7 +82,7 @@ func NewCommandCLI(name, fullName string) *cobra.Command {
82 82
 func NewCmdKubectl(name string) *cobra.Command {
83 83
 	flags := pflag.NewFlagSet("", pflag.ContinueOnError)
84 84
 	clientcmd.DefaultCluster.Server = defaultClusterURL
85
-	clientConfig := defaultClientConfig(flags)
85
+	clientConfig := DefaultClientConfig(flags)
86 86
 	f := cmd.NewFactory(clientConfig)
87 87
 	cmd := f.NewKubectlCommand(os.Stdout)
88 88
 	cmd.Use = name
... ...
@@ -110,7 +110,7 @@ func applyToCreate(dst *cobra.Command) *cobra.Command {
110 110
 }
111 111
 
112 112
 // Copy of kubectl/cmd/DefaultClientConfig, using NewNonInteractiveDeferredLoadingClientConfig
113
-func defaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
113
+func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
114 114
 	loadingRules := clientcmd.NewClientConfigLoadingRules()
115 115
 	loadingRules.EnvVarPath = os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
116 116
 	flags.StringVar(&loadingRules.CommandLinePath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.")
117 117
deleted file mode 100644
... ...
@@ -1,323 +0,0 @@
1
-package new
2
-
3
-import (
4
-	"fmt"
5
-	"sort"
6
-	"strings"
7
-
8
-	"github.com/fsouza/go-dockerclient"
9
-
10
-	image "github.com/openshift/origin/pkg/image/api"
11
-	template "github.com/openshift/origin/pkg/template/api"
12
-)
13
-
14
-// isComponentReference returns true if the provided string appears to be a reference to a source repository
15
-// on disk, at a URL, a docker image name (which might be on a Docker registry or an OpenShift image stream),
16
-// or a template.
17
-func isComponentReference(s string) bool {
18
-	if len(s) == 0 {
19
-		return false
20
-	}
21
-	all := strings.Split(s, "+")
22
-	_, _, _, err := componentWithSource(all[0])
23
-	return err == nil
24
-}
25
-
26
-func componentWithSource(s string) (component, repo string, builder bool, err error) {
27
-	if strings.Contains(s, "~") {
28
-		segs := strings.SplitN(s, "~", 2)
29
-		if len(segs) == 2 {
30
-			builder = true
31
-			switch {
32
-			case len(segs[0]) == 0:
33
-				err = fmt.Errorf("when using '[image]~[code]' form for %q, you must specify a image name", s)
34
-				return
35
-			case len(segs[1]) == 0:
36
-				component = segs[0]
37
-			default:
38
-				component = segs[0]
39
-				repo = segs[1]
40
-			}
41
-		}
42
-	} else {
43
-		component = s
44
-	}
45
-	// TODO: component must be of the form compatible with a pull spec *or* <namespace>/<name>
46
-	if !image.IsPullSpec(component) {
47
-		return "", "", false, fmt.Errorf("%q is not a valid Docker pull specification", component)
48
-	}
49
-	return
50
-}
51
-
52
-type ComponentReference interface {
53
-	Input() *ComponentInput
54
-	// Sets Input.Match or returns an error
55
-	Resolve() error
56
-	NeedsSource() bool
57
-}
58
-
59
-type ComponentReferences []ComponentReference
60
-
61
-func (r ComponentReferences) NeedsSource() (refs ComponentReferences) {
62
-	for _, ref := range r {
63
-		if ref.NeedsSource() {
64
-			refs = append(refs, ref)
65
-		}
66
-	}
67
-	return
68
-}
69
-
70
-type GroupedComponentReferences ComponentReferences
71
-
72
-func (m GroupedComponentReferences) Len() int      { return len(m) }
73
-func (m GroupedComponentReferences) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
74
-func (m GroupedComponentReferences) Less(i, j int) bool {
75
-	return m[i].Input().Group < m[j].Input().Group
76
-}
77
-
78
-func (r ComponentReferences) Group() (refs []ComponentReferences) {
79
-	sorted := make(GroupedComponentReferences, len(r))
80
-	copy(sorted, r)
81
-	sort.Sort(sorted)
82
-	group := -1
83
-	for _, ref := range sorted {
84
-		if ref.Input().Group != group {
85
-			refs = append(refs, ComponentReferences{})
86
-		}
87
-		group = ref.Input().Group
88
-		refs[len(refs)-1] = append(refs[len(refs)-1], ref)
89
-	}
90
-	return
91
-}
92
-
93
-type ComponentMatch struct {
94
-	Value       string
95
-	Argument    string
96
-	Name        string
97
-	Description string
98
-	Score       float32
99
-
100
-	Builder     bool
101
-	Image       *docker.Image
102
-	ImageStream *image.ImageRepository
103
-	ImageTag    string
104
-	Template    *template.Template
105
-}
106
-
107
-func (m *ComponentMatch) String() string {
108
-	return m.Argument
109
-}
110
-
111
-type Resolver interface {
112
-	// resolvers should return ErrMultipleMatches when more than one result could
113
-	// be construed as a match. Resolvers should set the score to 0.0 if this is a
114
-	// perfect match, and to higher values the less adequate the match is.
115
-	Resolve(value string) (*ComponentMatch, error)
116
-}
117
-
118
-type ScoredComponentMatches []*ComponentMatch
119
-
120
-func (m ScoredComponentMatches) Len() int           { return len(m) }
121
-func (m ScoredComponentMatches) Swap(i, j int)      { m[i], m[j] = m[j], m[i] }
122
-func (m ScoredComponentMatches) Less(i, j int) bool { return m[i].Score < m[j].Score }
123
-
124
-type WeightedResolver struct {
125
-	Resolver
126
-	Weight float32
127
-}
128
-
129
-// PerfectMatchWeightedResolver returns only matches from resolvers that are identified as exact
130
-// (weight 0.0), and only matches from those resolvers that qualify as exact (score = 0.0). If no
131
-// perfect matches exist, an ErrMultipleMatches is returned indicating the remaining candidate(s).
132
-// Note that this metchod may resolve ErrMultipleMatches with a single match, indicating an error
133
-// (no perfect match) but with only one candidate.
134
-type PerfectMatchWeightedResolver []WeightedResolver
135
-
136
-func (r PerfectMatchWeightedResolver) Resolve(value string) (*ComponentMatch, error) {
137
-	match, err := WeightedResolvers(r).Resolve(value)
138
-	if err != nil {
139
-		if multiple, ok := err.(ErrMultipleMatches); ok {
140
-			sort.Sort(ScoredComponentMatches(multiple.matches))
141
-			if multiple.matches[0].Score == 0.0 && (len(multiple.matches) == 1 || multiple.matches[1].Score != 0.0) {
142
-				return multiple.matches[0], nil
143
-			}
144
-		}
145
-		return nil, err
146
-	}
147
-	if match.Score != 0.0 {
148
-		return nil, ErrMultipleMatches{value, []*ComponentMatch{match}}
149
-	}
150
-	return match, nil
151
-}
152
-
153
-type WeightedResolvers []WeightedResolver
154
-
155
-func (r WeightedResolvers) Resolve(value string) (*ComponentMatch, error) {
156
-	candidates := []*ComponentMatch{}
157
-	errs := []error{}
158
-	for _, resolver := range r {
159
-		match, err := resolver.Resolve(value)
160
-		if err != nil {
161
-			if multiple, ok := err.(ErrMultipleMatches); ok {
162
-				for _, match := range multiple.matches {
163
-					if resolver.Weight != 0.0 {
164
-						match.Score = match.Score * resolver.Weight
165
-					}
166
-					candidates = append(candidates, match)
167
-				}
168
-				continue
169
-			}
170
-			if _, ok := err.(ErrNoMatch); ok {
171
-				continue
172
-			}
173
-			errs = append(errs, err)
174
-			continue
175
-		}
176
-		candidates = append(candidates, match)
177
-	}
178
-	switch len(candidates) {
179
-	case 0:
180
-		return nil, ErrNoMatch{value: value}
181
-	case 1:
182
-		return candidates[0], nil
183
-	default:
184
-		return nil, ErrMultipleMatches{value, candidates}
185
-	}
186
-}
187
-
188
-type ReferenceBuilder struct {
189
-	refs  ComponentReferences
190
-	repos []*SourceRepository
191
-	errs  []error
192
-	group int
193
-}
194
-
195
-func (r *ReferenceBuilder) AddImages(inputs []string, fn func(*ComponentInput) ComponentReference) {
196
-	for _, s := range inputs {
197
-		for _, s := range strings.Split(s, "+") {
198
-			input, repo, err := NewComponentInput(s)
199
-			if err != nil {
200
-				r.errs = append(r.errs, err)
201
-				continue
202
-			}
203
-			input.Group = r.group
204
-			ref := fn(input)
205
-			if len(repo) != 0 {
206
-				repository, ok := r.AddSourceRepository(repo)
207
-				if !ok {
208
-					continue
209
-				}
210
-				input.Use(repository)
211
-				repository.UsedBy(ref)
212
-			}
213
-			r.refs = append(r.refs, ref)
214
-		}
215
-		r.group++
216
-	}
217
-}
218
-
219
-func (r *ReferenceBuilder) AddGroups(inputs []string) {
220
-	for _, s := range inputs {
221
-		groups := strings.Split(s, "+")
222
-		if len(groups) == 1 {
223
-			r.errs = append(r.errs, fmt.Errorf("group %q only contains a single name", s))
224
-			continue
225
-		}
226
-		to := -1
227
-		for _, group := range groups {
228
-			var match ComponentReference
229
-			for _, ref := range r.refs {
230
-				if group == ref.Input().Value {
231
-					match = ref
232
-					break
233
-				}
234
-			}
235
-			if match == nil {
236
-				r.errs = append(r.errs, fmt.Errorf("the name %q from the group definition is not in use, and can't be used", group))
237
-				break
238
-			}
239
-			if to == -1 {
240
-				to = match.Input().Group
241
-			} else {
242
-				match.Input().Group = to
243
-			}
244
-		}
245
-	}
246
-}
247
-
248
-func (r *ReferenceBuilder) AddSourceRepository(input string) (*SourceRepository, bool) {
249
-	for _, existing := range r.repos {
250
-		if input == existing.location {
251
-			return existing, true
252
-		}
253
-	}
254
-	source, err := NewSourceRepository(input)
255
-	if err != nil {
256
-		r.errs = append(r.errs, err)
257
-		return nil, false
258
-	}
259
-	r.repos = append(r.repos, source)
260
-	return source, true
261
-}
262
-
263
-func (r *ReferenceBuilder) Result() (ComponentReferences, []*SourceRepository, []error) {
264
-	return r.refs, r.repos, r.errs
265
-}
266
-
267
-func NewComponentInput(input string) (*ComponentInput, string, error) {
268
-	// check for image using [image]~ (to indicate builder) or [image]~[code] (builder plus code)
269
-	component, repo, builder, err := componentWithSource(input)
270
-	if err != nil {
271
-		return nil, "", err
272
-	}
273
-	return &ComponentInput{
274
-		From:          input,
275
-		Argument:      input,
276
-		Value:         component,
277
-		ExpectToBuild: builder,
278
-	}, repo, nil
279
-}
280
-
281
-type ComponentInput struct {
282
-	Group         int
283
-	From          string
284
-	Argument      string
285
-	Value         string
286
-	ExpectToBuild bool
287
-
288
-	Uses  *SourceRepository
289
-	Match *ComponentMatch
290
-
291
-	Resolver
292
-}
293
-
294
-func (i *ComponentInput) Input() *ComponentInput {
295
-	return i
296
-}
297
-
298
-func (i *ComponentInput) NeedsSource() bool {
299
-	return i.ExpectToBuild && i.Uses == nil
300
-}
301
-
302
-func (i *ComponentInput) Resolve() error {
303
-	if i.Resolver == nil {
304
-		return ErrNoMatch{value: i.Value, qualifier: "no resolver defined"}
305
-	}
306
-	match, err := i.Resolver.Resolve(i.Value)
307
-	if err != nil {
308
-		return err
309
-	}
310
-	i.Value = match.Value
311
-	i.Argument = match.Argument
312
-	i.Match = match
313
-
314
-	return nil
315
-}
316
-
317
-func (i *ComponentInput) String() string {
318
-	return i.Value
319
-}
320
-
321
-func (i *ComponentInput) Use(repo *SourceRepository) {
322
-	i.Uses = repo
323
-}
324 1
deleted file mode 100644
... ...
@@ -1,49 +0,0 @@
1
-package new
2
-
3
-import (
4
-	"bytes"
5
-	"fmt"
6
-)
7
-
8
-type ErrNoMatch struct {
9
-	value     string
10
-	qualifier string
11
-}
12
-
13
-func (e ErrNoMatch) Error() string {
14
-	if len(e.qualifier) != 0 {
15
-		return fmt.Sprintf("no image matched %q: %s", e.value, e.qualifier)
16
-	}
17
-	return fmt.Sprintf("no image matched %q", e.value)
18
-}
19
-
20
-func (e ErrNoMatch) UsageError(commandName string) string {
21
-	return fmt.Sprintf(`
22
-%[3]s - you can try to search for images or templates that may match this name with:
23
-
24
-    $ %[2]s -S %[1]q
25
-
26
-`, e.value, commandName, e.Error())
27
-}
28
-
29
-type ErrMultipleMatches struct {
30
-	image   string
31
-	matches []*ComponentMatch
32
-}
33
-
34
-func (e ErrMultipleMatches) Error() string {
35
-	return fmt.Sprintf("multiple images matched %q: %d", e.image, len(e.matches))
36
-}
37
-
38
-func (e ErrMultipleMatches) UsageError(commandName string) string {
39
-	buf := &bytes.Buffer{}
40
-	for _, match := range e.matches {
41
-		fmt.Fprintf(buf, "* %[1]s (use %[2]s)\n", match.Name, match.Argument)
42
-		fmt.Fprintf(buf, "  %s\n\n", match.Description)
43
-	}
44
-	return fmt.Sprintf(`
45
-The argument %[1]q could apply to the following images or templates:
46
-
47
-%[2]s
48
-`, e.image, buf.String())
49
-}
50 1
deleted file mode 100644
... ...
@@ -1,239 +0,0 @@
1
-package new
2
-
3
-import (
4
-	"fmt"
5
-	"strings"
6
-
7
-	"github.com/fsouza/go-dockerclient"
8
-
9
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
10
-	"github.com/golang/glog"
11
-
12
-	"github.com/openshift/origin/pkg/client"
13
-	"github.com/openshift/origin/pkg/dockerregistry"
14
-	"github.com/openshift/origin/pkg/generate/app"
15
-	image "github.com/openshift/origin/pkg/image/api"
16
-)
17
-
18
-type dockerClientResolver struct {
19
-	client *docker.Client
20
-}
21
-
22
-func (r dockerClientResolver) Resolve(value string) (*ComponentMatch, error) {
23
-	image, err := r.client.InspectImage(value)
24
-	switch {
25
-	case err == docker.ErrNoSuchImage:
26
-		return nil, ErrNoMatch{value: value}
27
-	case err != nil:
28
-		return nil, err
29
-	}
30
-	return &ComponentMatch{
31
-		Value:       value,
32
-		Argument:    fmt.Sprintf("--docker-image=%q", value),
33
-		Name:        value,
34
-		Description: fmt.Sprintf("Docker image %q by %s\n%s", value, image.Author, image.Comment),
35
-		Builder:     false,
36
-		Score:       0,
37
-	}, nil
38
-}
39
-
40
-type dockerRegistryResolver struct {
41
-	client dockerregistry.Client
42
-}
43
-
44
-func (r dockerRegistryResolver) Resolve(value string) (*ComponentMatch, error) {
45
-	registry, namespace, name, tag, err := image.SplitDockerPullSpec(value)
46
-	if err != nil {
47
-		return nil, err
48
-	}
49
-	connection, err := r.client.Connect(registry)
50
-	if err != nil {
51
-		if dockerregistry.IsRegistryNotFound(err) {
52
-			return nil, ErrNoMatch{value: value}
53
-		}
54
-		return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("can't connect to %q: %v", registry, err)}
55
-	}
56
-	image, err := connection.ImageByTag(namespace, name, tag)
57
-	if err != nil {
58
-		if dockerregistry.IsNotFound(err) {
59
-			return nil, ErrNoMatch{value: value, qualifier: err.Error()}
60
-		}
61
-		return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("can't connect to %q: %v", registry, err)}
62
-	}
63
-	if len(tag) == 0 {
64
-		tag = "latest"
65
-	}
66
-	glog.V(4).Infof("found image: %#v", image)
67
-	return &ComponentMatch{
68
-		Value:       value,
69
-		Argument:    fmt.Sprintf("--docker-image=%q", value),
70
-		Name:        value,
71
-		Description: fmt.Sprintf("Docker image %q (%q)", value, image.ID),
72
-		Builder:     app.IsBuilderImage(image),
73
-		Score:       0,
74
-
75
-		Image:    image,
76
-		ImageTag: tag,
77
-	}, nil
78
-}
79
-
80
-type imageStreamResolver struct {
81
-	client     client.ImageRepositoriesNamespacer
82
-	images     client.ImagesNamespacer
83
-	namespaces []string
84
-}
85
-
86
-func (r imageStreamResolver) Resolve(value string) (*ComponentMatch, error) {
87
-	registry, namespace, name, tag, err := image.SplitOpenShiftPullSpec(value)
88
-	if err != nil || len(registry) != 0 {
89
-		return nil, fmt.Errorf("image repositories must be of the form [<namespace>/]<name>[:<tag>]")
90
-	}
91
-	namespaces := r.namespaces
92
-	if len(namespace) != 0 {
93
-		namespaces = []string{namespace}
94
-	}
95
-	for _, namespace := range namespaces {
96
-		glog.V(4).Infof("checking image stream %s/%s with tag %q", namespace, name, tag)
97
-		repo, err := r.client.ImageRepositories(namespace).Get(name)
98
-		if err != nil {
99
-			if errors.IsNotFound(err) {
100
-				continue
101
-			}
102
-			return nil, err
103
-		}
104
-		searchTag := tag
105
-		// TODO: move to a lookup function on repo, or better yet, have the repo.Status.Tags field automatically infer latest
106
-		if len(searchTag) == 0 {
107
-			searchTag = "latest"
108
-		}
109
-		id, ok := repo.Tags[searchTag]
110
-		if !ok {
111
-			if len(tag) == 0 {
112
-				return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("the default tag %q has not been set", searchTag)}
113
-			}
114
-			return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("tag %q has not been set", tag)}
115
-		}
116
-		imageData, err := r.images.Images(namespace).Get(id)
117
-		if err != nil {
118
-			if errors.IsNotFound(err) {
119
-				return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("tag %q is set, but image %q has been removed", tag, id)}
120
-			}
121
-			return nil, err
122
-		}
123
-
124
-		spec := image.JoinDockerPullSpec("", namespace, name, tag)
125
-		return &ComponentMatch{
126
-			Value:       spec,
127
-			Argument:    fmt.Sprintf("--image=%q", spec),
128
-			Name:        name,
129
-			Description: fmt.Sprintf("Image stream %s (tag %q) in namespace %s, tracks %q", name, searchTag, namespace, repo.Status.DockerImageRepository),
130
-			Builder:     app.IsBuilderImage(&imageData.DockerImageMetadata),
131
-			Score:       0,
132
-
133
-			ImageStream: repo,
134
-			Image:       &imageData.DockerImageMetadata,
135
-			ImageTag:    searchTag,
136
-		}, nil
137
-	}
138
-	return nil, ErrNoMatch{value: value}
139
-}
140
-
141
-type mockResolver struct{}
142
-
143
-func (mockResolver) Resolve(value string) (*ComponentMatch, error) {
144
-	matches, err := mockSearcher{}.Search([]string{value})
145
-	switch {
146
-	case err != nil:
147
-		return nil, err
148
-	case len(matches) > 1:
149
-		return nil, ErrMultipleMatches{value, matches}
150
-	case len(matches) == 0:
151
-		return nil, ErrNoMatch{value: value}
152
-	default:
153
-		return matches[0], nil
154
-	}
155
-}
156
-
157
-type Searcher interface {
158
-	Search(terms []string) ([]*ComponentMatch, error)
159
-}
160
-
161
-type mockSearcher struct{}
162
-
163
-func (mockSearcher) Search(terms []string) ([]*ComponentMatch, error) {
164
-	for _, term := range terms {
165
-		term = strings.ToLower(term)
166
-		switch term {
167
-		case "redhat/mysql:5.6":
168
-			return []*ComponentMatch{
169
-				{
170
-					Value:       term,
171
-					Argument:    "redhat/mysql:5.6",
172
-					Name:        "MySQL 5.6",
173
-					Description: "The Open Source SQL database",
174
-				},
175
-			}, nil
176
-		case "mysql", "mysql5", "mysql-5", "mysql-5.x":
177
-			return []*ComponentMatch{
178
-				{
179
-					Value:       term,
180
-					Argument:    "redhat/mysql:5.6",
181
-					Name:        "MySQL 5.6",
182
-					Description: "The Open Source SQL database",
183
-				},
184
-				{
185
-					Value:       term,
186
-					Argument:    "mysql",
187
-					Name:        "MySQL 5.X",
188
-					Description: "Something out there on the Docker Hub.",
189
-				},
190
-			}, nil
191
-		case "php", "php-5", "php5", "redhat/php:5", "redhat/php-5":
192
-			return []*ComponentMatch{
193
-				{
194
-					Value:       term,
195
-					Argument:    "redhat/php:5",
196
-					Name:        "PHP 5.5",
197
-					Description: "A fast and easy to use scripting language for building websites.",
198
-					Builder:     true,
199
-				},
200
-			}, nil
201
-		case "ruby":
202
-			return []*ComponentMatch{
203
-				{
204
-					Value:       term,
205
-					Argument:    "redhat/ruby:2",
206
-					Name:        "Ruby 2.0",
207
-					Description: "A fast and easy to use scripting language for building websites.",
208
-					Builder:     true,
209
-				},
210
-			}, nil
211
-		}
212
-	}
213
-	return []*ComponentMatch{}, nil
214
-}
215
-
216
-func InputImageFromMatch(match *ComponentMatch) (*app.ImageRef, error) {
217
-	switch {
218
-	case match.ImageStream != nil:
219
-		input, err := app.ImageFromRepository(match.ImageStream, match.ImageTag)
220
-		if err != nil {
221
-			return nil, err
222
-		}
223
-		input.AsImageRepository = true
224
-		input.Info = match.Image
225
-		return input, nil
226
-
227
-	case match.Image != nil:
228
-		input, err := app.ImageFromName(match.Value, match.ImageTag)
229
-		if err != nil {
230
-			return nil, err
231
-		}
232
-		input.AsImageRepository = false
233
-		input.Info = match.Image
234
-		return input, nil
235
-
236
-	default:
237
-		return nil, fmt.Errorf("no image or image stream, can't setup a build")
238
-	}
239
-}
240 1
deleted file mode 100644
... ...
@@ -1,383 +0,0 @@
1
-package new
2
-
3
-import (
4
-	"fmt"
5
-	"io"
6
-
7
-	"github.com/golang/glog"
8
-	"github.com/spf13/cobra"
9
-
10
-	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
11
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
12
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
13
-
14
-	"github.com/openshift/origin/pkg/cmd/cli/cmd"
15
-	cmdutil "github.com/openshift/origin/pkg/cmd/util"
16
-	dockerutil "github.com/openshift/origin/pkg/cmd/util/docker"
17
-	"github.com/openshift/origin/pkg/dockerregistry"
18
-	"github.com/openshift/origin/pkg/generate/app"
19
-	"github.com/openshift/origin/pkg/generate/dockerfile"
20
-	"github.com/openshift/origin/pkg/generate/source"
21
-)
22
-
23
-type newAppConfig struct {
24
-	SourceRepositories util.StringList
25
-
26
-	Components   util.StringList
27
-	ImageStreams util.StringList
28
-	DockerImages util.StringList
29
-	Groups       util.StringList
30
-	Environment  util.StringList
31
-
32
-	TypeOfBuild string
33
-
34
-	localDockerResolver Resolver
35
-	imageStreamResolver Resolver
36
-
37
-	searcher Searcher
38
-	detector Detector
39
-}
40
-
41
-const longNewAppDescription = `
42
-Create a new application in OpenShift by specifying source code, templates, and/or images.
43
-
44
-Examples:
45
-  $ osc new-app .
46
-  <try to create an application based on the source code in the current directory>
47
-
48
-  $ osc new-app mysql
49
-  <use the public DockerHub MySQL image to create an app>
50
-
51
-  $ osc new-app myregistry.com/mycompany/mysql
52
-  <use a MySQL image in a private registry to create an app>
53
-
54
-  $ osc new-app openshift/ruby-20-centos~git@github.com/mfojtik/sinatra-app-example
55
-  <build an application using the OpenShift Ruby DockerHub image and an example repo>`
56
-
57
-func NewCmdNewApplication(f *cmd.Factory, out io.Writer) *cobra.Command {
58
-	config := newAppConfig{
59
-		searcher: &mockSearcher{},
60
-		detector: sourceRepositoryEnumerator{
61
-			detectors: source.DefaultDetectors,
62
-			tester:    dockerfile.NewTester(),
63
-		},
64
-	}
65
-	helper := dockerutil.NewHelper()
66
-
67
-	cmd := &cobra.Command{
68
-		Use:   "new-app <components> [--code=<path|url>]",
69
-		Short: "Create a new application",
70
-		Long:  longNewAppDescription,
71
-
72
-		Run: func(c *cobra.Command, args []string) {
73
-			if dockerclient, _, err := helper.GetClient(); err == nil {
74
-				config.localDockerResolver = dockerClientResolver{dockerclient}
75
-				config.localDockerResolver = dockerRegistryResolver{dockerregistry.NewClient()}
76
-			}
77
-			if osclient, _, err := f.Clients(c); err == nil {
78
-				config.imageStreamResolver = imageStreamResolver{
79
-					client:     osclient,
80
-					images:     osclient,
81
-					namespaces: []string{cmd.GetOriginNamespace(c), "default"},
82
-				}
83
-			} else {
84
-				glog.Warningf("error getting client: %v", err)
85
-			}
86
-			unknown := config.addArguments(args)
87
-			if len(unknown) != 0 {
88
-				glog.Fatalf("Did not recognize the following arguments: %v", unknown)
89
-			}
90
-			if err := config.Run(f, out, c.Help); err != nil {
91
-				if errs, ok := err.(errlist); ok {
92
-					if len(errs.Errors()) == 1 {
93
-						err = errs.Errors()[0]
94
-					}
95
-				}
96
-				if usage, ok := err.(UsageError); ok {
97
-					glog.Fatal(usage.UsageError(c.CommandPath()))
98
-				}
99
-				glog.Fatalf("Error: %v", err)
100
-			}
101
-		},
102
-	}
103
-
104
-	cmd.Flags().Var(&config.SourceRepositories, "code", "Source code to use to build this application.")
105
-	cmd.Flags().VarP(&config.ImageStreams, "image", "i", "Name of an OpenShift image repository to use in the app.")
106
-	cmd.Flags().Var(&config.DockerImages, "docker-image", "Name of a Docker image to include in the app.")
107
-	cmd.Flags().Var(&config.Groups, "group", "Indicate components that should be grouped together as <comp1>+<comp2>.")
108
-	cmd.Flags().VarP(&config.Environment, "env", "e", "Specify key value pairs of environment variables to set into each container.")
109
-	cmd.Flags().StringVar(&config.TypeOfBuild, "build", "", "Specify the type of build to use if you don't want to detect (docker|source)")
110
-	return cmd
111
-}
112
-
113
-type UsageError interface {
114
-	UsageError(commandName string) string
115
-}
116
-
117
-// TODO: replace with upstream converting [1]error to error
118
-type errlist interface {
119
-	Errors() []error
120
-}
121
-
122
-// addArguments converts command line arguments into the appropriate bucket based on what they look like
123
-func (c *newAppConfig) addArguments(args []string) []string {
124
-	unknown := []string{}
125
-	for _, s := range args {
126
-		switch {
127
-		case cmdutil.IsEnvironmentArgument(s):
128
-			c.Environment = append(c.Environment, s)
129
-		case isPossibleSourceRepository(s):
130
-			c.SourceRepositories = append(c.SourceRepositories, s)
131
-		case isComponentReference(s):
132
-			c.Components = append(c.Components, s)
133
-		default:
134
-			if len(s) == 0 {
135
-				break
136
-			}
137
-			unknown = append(unknown, s)
138
-		}
139
-	}
140
-	return unknown
141
-}
142
-
143
-// validate converts all of the arguments on the config into references to objects, or returns an error
144
-func (c *newAppConfig) validate() (ComponentReferences, []*SourceRepository, cmdutil.Environment, error) {
145
-	b := &ReferenceBuilder{}
146
-	for _, s := range c.SourceRepositories {
147
-		b.AddSourceRepository(s)
148
-	}
149
-	b.AddImages(c.DockerImages, func(input *ComponentInput) ComponentReference {
150
-		input.Argument = fmt.Sprintf("--docker-image=%q", input.From)
151
-		input.Resolver = c.localDockerResolver
152
-		return input
153
-	})
154
-	b.AddImages(c.ImageStreams, func(input *ComponentInput) ComponentReference {
155
-		input.Argument = fmt.Sprintf("--image=%q", input.From)
156
-		input.Resolver = c.imageStreamResolver
157
-		return input
158
-	})
159
-	b.AddImages(c.Components, func(input *ComponentInput) ComponentReference {
160
-		input.Resolver = PerfectMatchWeightedResolver{
161
-			WeightedResolver{Resolver: c.imageStreamResolver, Weight: 0.0},
162
-			WeightedResolver{Resolver: c.localDockerResolver, Weight: 0.0},
163
-		}
164
-		return input
165
-	})
166
-	b.AddGroups(c.Groups)
167
-	refs, repos, errs := b.Result()
168
-	if len(c.TypeOfBuild) != 0 && len(repos) == 0 {
169
-		errs = append(errs, fmt.Errorf("when --build is specified you must provide at least one source code location"))
170
-	}
171
-
172
-	env, duplicate, envErrs := cmdutil.ParseEnvironmentArguments(c.Environment)
173
-	for _, s := range duplicate {
174
-		glog.V(1).Infof("The environment variable %q was overwritten", s)
175
-	}
176
-	errs = append(errs, envErrs...)
177
-
178
-	return refs, repos, env, util.SliceToError(errs)
179
-}
180
-
181
-// resolve the references to ensure they are all valid, and identify any images that don't match user input.
182
-func (c *newAppConfig) resolve(components ComponentReferences) error {
183
-	errs := []error{}
184
-	for _, ref := range components {
185
-		if err := ref.Resolve(); err != nil {
186
-			errs = append(errs, err)
187
-			continue
188
-		}
189
-		switch input := ref.Input(); {
190
-		case !input.ExpectToBuild && input.Match.Builder:
191
-			if c.TypeOfBuild != "docker" {
192
-				glog.Infof("Image %q is a builder, so a repository will be expected unless you also specify --build=docker", input)
193
-				input.ExpectToBuild = true
194
-			}
195
-		case input.ExpectToBuild && !input.Match.Builder:
196
-			if len(c.TypeOfBuild) == 0 {
197
-				errs = append(errs, fmt.Errorf("none of the images that match %q can build source code - check whether this is the image you want to use, then use --build=source to build using source or --build=docker to treat this as a Docker base image and set up a layered Docker build", ref))
198
-				continue
199
-			}
200
-		}
201
-	}
202
-	return util.SliceToError(errs)
203
-}
204
-
205
-// ensureHasSource ensure every builder component has source code associated with it
206
-func (c *newAppConfig) ensureHasSource(components ComponentReferences, repositories []*SourceRepository) error {
207
-	requiresSource := components.NeedsSource()
208
-	if len(requiresSource) > 0 {
209
-		switch {
210
-		case len(repositories) > 1:
211
-			// TODO: harder problem - need to match repos up
212
-			if len(requiresSource) == 1 {
213
-				// TODO: print all suggestions
214
-				return fmt.Errorf("there are multiple code locations provided - use '%s~<repo>' to declare which code goes with the image", requiresSource[0])
215
-			}
216
-			// TODO: indicate which args don't match, and which repos don't match
217
-			return fmt.Errorf("there are multiple code locations provided - use '[image]~[repo]' to declare which code goes with which image")
218
-		case len(repositories) == 1:
219
-			glog.Infof("Using %q as the source for build", repositories[0])
220
-			for _, component := range requiresSource {
221
-				component.Input().Use(repositories[0])
222
-				repositories[0].UsedBy(component)
223
-			}
224
-		default:
225
-			if len(requiresSource) == 1 {
226
-				return fmt.Errorf("the image %q will build source code, so you must specify a repository via --code", requiresSource[0])
227
-			}
228
-			// TODO: array of pointers won't print correctly
229
-			return fmt.Errorf("you must provide at least one source code repository with --code for the images: %v", requiresSource)
230
-		}
231
-	}
232
-	return nil
233
-}
234
-
235
-// detectSource tries to match each source repository to an image type
236
-func (c *newAppConfig) detectSource(repositories []*SourceRepository) error {
237
-	errs := []error{}
238
-	for _, repo := range repositories {
239
-		// if the repository is being used by one of the images, we don't need to detect its type (unless we want to double check)
240
-		if repo.InUse() {
241
-			continue
242
-		}
243
-		path, err := repo.LocalPath()
244
-		if err != nil {
245
-			errs = append(errs, err)
246
-			continue
247
-		}
248
-		info, err := c.detector.Detect(path)
249
-		if err != nil {
250
-			errs = append(errs, err)
251
-			continue
252
-		}
253
-		if info.Dockerfile != nil {
254
-			// TODO: this should be using the reference builder flow, possibly by moving detectSource up before other steps
255
-			/*if from, ok := info.Dockerfile.GetDirective("FROM"); ok {
256
-				input, _, err := NewComponentInput(from[0])
257
-				if err != nil {
258
-					errs = append(errs, err)
259
-					continue
260
-				}
261
-				input.
262
-			}*/
263
-			repo.BuildWithDocker()
264
-			continue
265
-		}
266
-
267
-		terms := info.Terms()
268
-		matches, err := c.searcher.Search(terms)
269
-		if err != nil {
270
-			errs = append(errs, err)
271
-			continue
272
-		}
273
-		if len(matches) == 0 {
274
-			errs = append(errs, fmt.Errorf("we could not find any images that match the source repo %q (looked for: %v) and this repository does not have a Dockerfile - you'll need to choose a source builder image to continue", repo, terms))
275
-			continue
276
-		}
277
-		errs = append(errs, fmt.Errorf("found the following possible images to use to build this source repository: %v - to continue, you'll need to specify which image to use with %q", matches, repo))
278
-	}
279
-	return util.SliceToError(errs)
280
-}
281
-
282
-// buildPipelines converts a set of resolved, valid references into pipelines.
283
-func (c *newAppConfig) buildPipelines(components ComponentReferences, environment app.Environment) (app.PipelineGroup, error) {
284
-	pipelines := app.PipelineGroup{}
285
-	for _, group := range components.Group() {
286
-		glog.V(2).Infof("found group: %#v", group)
287
-		common := app.PipelineGroup{}
288
-		for _, ref := range group {
289
-
290
-			var pipeline *app.Pipeline
291
-			if ref.Input().ExpectToBuild {
292
-				glog.V(2).Infof("will use %q as the base image for a source build of %q", ref, ref.Input().Uses)
293
-				input, err := InputImageFromMatch(ref.Input().Match)
294
-				if err != nil {
295
-					return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
296
-				}
297
-				strategy, source, err := StrategyAndSourceForRepository(ref.Input().Uses)
298
-				if err != nil {
299
-					return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
300
-				}
301
-				if pipeline, err = app.NewBuildPipeline(ref.Input().String(), input, strategy, source); err != nil {
302
-					return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
303
-				}
304
-
305
-			} else {
306
-				glog.V(2).Infof("will include %q", ref)
307
-				input, err := InputImageFromMatch(ref.Input().Match)
308
-				if err != nil {
309
-					return nil, fmt.Errorf("can't include %q: %v", ref.Input(), err)
310
-				}
311
-				if pipeline, err = app.NewImagePipeline(ref.Input().String(), input); err != nil {
312
-					return nil, fmt.Errorf("can't include %q: %v", ref.Input(), err)
313
-				}
314
-			}
315
-
316
-			if err := pipeline.NeedsDeployment(environment); err != nil {
317
-				return nil, fmt.Errorf("can't set up a deployment for %q: %v", ref.Input(), err)
318
-			}
319
-			common = append(common, pipeline)
320
-		}
321
-
322
-		if err := common.Reduce(); err != nil {
323
-			return nil, fmt.Errorf("can't create a pipeline from %s: %v", common, err)
324
-		}
325
-		pipelines = append(pipelines, common...)
326
-	}
327
-	return pipelines, nil
328
-}
329
-
330
-// Run executes the provided config.
331
-func (c *newAppConfig) Run(f *cmd.Factory, out io.Writer, helpFn func() error) error {
332
-	components, repositories, environment, err := c.validate()
333
-	if err != nil {
334
-		return err
335
-	}
336
-
337
-	hasSource := len(repositories) != 0
338
-	hasImages := len(components) != 0
339
-	if !hasSource && !hasImages {
340
-		// display help page
341
-		// TODO: return usage error, which should trigger help display
342
-		return helpFn()
343
-	}
344
-
345
-	if err := c.resolve(components); err != nil {
346
-		return err
347
-	}
348
-
349
-	if err := c.ensureHasSource(components, repositories); err != nil {
350
-		return err
351
-	}
352
-
353
-	glog.V(4).Infof("Code %v", repositories)
354
-	glog.V(4).Infof("Images %v", components)
355
-
356
-	if err := c.detectSource(repositories); err != nil {
357
-		return err
358
-	}
359
-
360
-	pipelines, err := c.buildPipelines(components, app.Environment(environment))
361
-	if err != nil {
362
-		return err
363
-	}
364
-
365
-	objects := app.Objects{}
366
-	accept := app.NewAcceptFirst()
367
-	for _, p := range pipelines {
368
-		obj, err := p.Objects(accept)
369
-		if err != nil {
370
-			return fmt.Errorf("can't setup %q: %v", p.From, err)
371
-		}
372
-		objects = append(objects, obj...)
373
-	}
374
-
375
-	objects = app.AddServices(objects)
376
-
377
-	list := &kapi.List{Items: objects}
378
-	p, err := kubectl.GetPrinter("yaml", "", "v1beta1", kapi.Scheme, nil)
379
-	if err != nil {
380
-		return err
381
-	}
382
-	return p.PrintObj(list, out)
383
-}
384 1
deleted file mode 100644
... ...
@@ -1,185 +0,0 @@
1
-package new
2
-
3
-import (
4
-	"fmt"
5
-	"net/url"
6
-	"os"
7
-	"path/filepath"
8
-	"regexp"
9
-	"strings"
10
-
11
-	"github.com/openshift/origin/pkg/generate/app"
12
-	"github.com/openshift/origin/pkg/generate/dockerfile"
13
-	"github.com/openshift/origin/pkg/generate/source"
14
-)
15
-
16
-var (
17
-	argumentGit         = regexp.MustCompile("^(http://|https://|git@|git://).*\\.git(?:#([a-zA-Z0-9]*))?$")
18
-	argumentGitProtocol = regexp.MustCompile("^git@")
19
-	argumentPath        = regexp.MustCompile("^\\.")
20
-)
21
-
22
-func isPossibleSourceRepository(s string) bool {
23
-	return argumentGit.MatchString(s) || argumentGitProtocol.MatchString(s) || argumentPath.MatchString(s)
24
-}
25
-
26
-// SourceRepository represents an code repository that may be the target of a build.
27
-type SourceRepository struct {
28
-	location string
29
-	url      url.URL
30
-
31
-	usedBy          []ComponentReference
32
-	buildWithDocker bool
33
-}
34
-
35
-// NewSourceRepository creates a reference to a local or remote source code repository from
36
-// a URL or path.
37
-func NewSourceRepository(s string) (*SourceRepository, error) {
38
-	var location *url.URL
39
-	switch {
40
-	case strings.HasPrefix(s, "git@"):
41
-		base := "git://" + strings.TrimPrefix(s, "git@")
42
-		url, err := url.Parse(base)
43
-		if err != nil {
44
-			return nil, err
45
-		}
46
-		location = url
47
-
48
-	default:
49
-		uri, err := url.Parse(s)
50
-		if err != nil {
51
-			return nil, err
52
-		}
53
-
54
-		if uri.Scheme == "" {
55
-			path := s
56
-			ref := ""
57
-			segments := strings.SplitN(path, "#", 2)
58
-			if len(segments) == 2 {
59
-				path, ref = segments[0], segments[1]
60
-			}
61
-			path, err := filepath.Abs(path)
62
-			if err != nil {
63
-				return nil, err
64
-			}
65
-			uri = &url.URL{
66
-				Scheme:   "file",
67
-				Path:     path,
68
-				Fragment: ref,
69
-			}
70
-		}
71
-
72
-		location = uri
73
-	}
74
-	return &SourceRepository{
75
-		location: s,
76
-		url:      *location,
77
-	}, nil
78
-}
79
-
80
-func (r *SourceRepository) UsedBy(ref ComponentReference) {
81
-	r.usedBy = append(r.usedBy, ref)
82
-}
83
-
84
-func (r *SourceRepository) Remote() bool {
85
-	return r.url.Scheme != "file"
86
-}
87
-
88
-func (r *SourceRepository) InUse() bool {
89
-	return len(r.usedBy) > 0
90
-}
91
-
92
-func (r *SourceRepository) BuildWithDocker() {
93
-	r.buildWithDocker = true
94
-}
95
-
96
-func (r *SourceRepository) IsDockerBuild() bool {
97
-	return r.buildWithDocker
98
-}
99
-
100
-func (r *SourceRepository) String() string {
101
-	return r.location
102
-}
103
-
104
-func (r *SourceRepository) LocalPath() (string, error) {
105
-	switch {
106
-	case r.url.Scheme == "file":
107
-		return r.url.Path, nil
108
-		// TODO: implement other types
109
-		// TODO: lazy cache (predictably?)
110
-	default:
111
-
112
-		return "", fmt.Errorf("reading local repositories is not implemented: %q", r.location)
113
-	}
114
-}
115
-
116
-type SourceRepositoryInfo struct {
117
-	Path       string
118
-	Types      []SourceLanguageType
119
-	Dockerfile dockerfile.Dockerfile
120
-}
121
-
122
-func (info *SourceRepositoryInfo) Terms() []string {
123
-	terms := []string{}
124
-	for i := range info.Types {
125
-		terms = append(terms, info.Types[i].Platform)
126
-	}
127
-	return terms
128
-}
129
-
130
-type SourceLanguageType struct {
131
-	Platform string
132
-	Version  string
133
-}
134
-
135
-type Detector interface {
136
-	Detect(dir string) (*SourceRepositoryInfo, error)
137
-}
138
-
139
-type sourceRepositoryEnumerator struct {
140
-	detectors source.Detectors
141
-	tester    dockerfile.Tester
142
-}
143
-
144
-func (e sourceRepositoryEnumerator) Detect(dir string) (*SourceRepositoryInfo, error) {
145
-	info := &SourceRepositoryInfo{
146
-		Path: dir,
147
-	}
148
-	for _, d := range e.detectors {
149
-		if detected, ok := d(dir); ok {
150
-			info.Types = append(info.Types, SourceLanguageType{
151
-				Platform: detected.Platform,
152
-				Version:  detected.Version,
153
-			})
154
-		}
155
-	}
156
-	if path, ok, err := e.tester.Has(dir); err == nil && ok {
157
-		file, err := os.Open(path)
158
-		if err != nil {
159
-			return nil, err
160
-		}
161
-		defer file.Close()
162
-		dockerfile, err := dockerfile.NewParser().Parse(file)
163
-		if err != nil {
164
-			return nil, err
165
-		}
166
-		info.Dockerfile = dockerfile
167
-	}
168
-	return info, nil
169
-}
170
-
171
-func StrategyAndSourceForRepository(repo *SourceRepository) (*app.BuildStrategyRef, *app.SourceRef, error) {
172
-	// TODO: replace with repository origin lookup, then in the future replace with auto push repository to server
173
-	if !repo.Remote() {
174
-		return nil, nil, fmt.Errorf("the repository %q can't be used, as the CLI does not yet support pushing a local repository from your filesystem to OpenShift", repo)
175
-	}
176
-	strategy := &app.BuildStrategyRef{
177
-		IsDockerBuild: repo.IsDockerBuild(),
178
-		DockerContext: "",
179
-	}
180
-	source := &app.SourceRef{
181
-		URL: &repo.url,
182
-		Ref: repo.url.Fragment,
183
-	}
184
-	return strategy, source, nil
185
-}
186 1
new file mode 100644
... ...
@@ -0,0 +1,79 @@
0
+package cmd
1
+
2
+import (
3
+	"io"
4
+
5
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
6
+	"github.com/golang/glog"
7
+	"github.com/spf13/cobra"
8
+
9
+	dockerutil "github.com/openshift/origin/pkg/cmd/util/docker"
10
+	newcmd "github.com/openshift/origin/pkg/generate/app/cmd"
11
+)
12
+
13
+type usage interface {
14
+	UsageError(commandName string) string
15
+}
16
+
17
+const longNewAppDescription = `
18
+Create a new application in OpenShift by specifying source code, templates, and/or images.
19
+
20
+Examples:
21
+
22
+    $ osc new-app .
23
+    <try to create an application based on the source code in the current directory>
24
+
25
+    $ osc new-app mysql
26
+    <use the public DockerHub MySQL image to create an app>
27
+
28
+    $ osc new-app myregistry.com/mycompany/mysql
29
+    <use a MySQL image in a private registry to create an app>
30
+
31
+    $ osc new-app openshift/ruby-20-centos~git@github.com/mfojtik/sinatra-app-example
32
+    <build an application using the OpenShift Ruby DockerHub image and an example repo>`
33
+
34
+func NewCmdNewApplication(f *Factory, out io.Writer) *cobra.Command {
35
+	config := newcmd.NewAppConfig()
36
+	helper := dockerutil.NewHelper()
37
+	cmd := &cobra.Command{
38
+		Use:   "new-app <components> [--code=<path|url>]",
39
+		Short: "Create a new application",
40
+		Long:  longNewAppDescription,
41
+
42
+		Run: func(c *cobra.Command, args []string) {
43
+			if dockerClient, _, err := helper.GetClient(); err == nil {
44
+				config.SetDockerClient(dockerClient)
45
+			}
46
+			if osclient, _, err := f.Clients(c); err == nil {
47
+				namespace, err := f.DefaultNamespace(c)
48
+				checkErr(err)
49
+				config.SetOpenShiftClient(osclient, namespace)
50
+			} else {
51
+				glog.Warningf("error getting client: %v", err)
52
+			}
53
+			unknown := config.AddArguments(args)
54
+			if len(unknown) != 0 {
55
+				glog.Fatalf("Did not recognize the following arguments: %v", unknown)
56
+			}
57
+			if err := config.Run(out, c.Help); err != nil {
58
+				if errs, ok := err.(errors.Aggregate); ok {
59
+					if len(errs.Errors()) == 1 {
60
+						err = errs.Errors()[0]
61
+					}
62
+				}
63
+				if u, ok := err.(usage); ok {
64
+					glog.Fatal(u.UsageError(c.CommandPath()))
65
+				}
66
+				glog.Fatalf("Error: %v", err)
67
+			}
68
+		},
69
+	}
70
+
71
+	cmd.Flags().Var(&config.SourceRepositories, "code", "Source code to use to build this application.")
72
+	cmd.Flags().VarP(&config.ImageStreams, "image", "i", "Name of an OpenShift image repository to use in the app.")
73
+	cmd.Flags().Var(&config.DockerImages, "docker-image", "Name of a Docker image to include in the app.")
74
+	cmd.Flags().Var(&config.Groups, "group", "Indicate components that should be grouped together as <comp1>+<comp2>.")
75
+	cmd.Flags().VarP(&config.Environment, "env", "e", "Specify key value pairs of environment variables to set into each container.")
76
+	cmd.Flags().StringVar(&config.TypeOfBuild, "build", "", "Specify the type of build to use if you don't want to detect (docker|source)")
77
+	return cmd
78
+}
0 79
new file mode 100644
... ...
@@ -0,0 +1,299 @@
0
+package generate
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+	"os"
6
+	"strings"
7
+
8
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
9
+	kubecmd "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
10
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
11
+	"github.com/fsouza/go-dockerclient"
12
+	"github.com/golang/glog"
13
+	"github.com/spf13/cobra"
14
+
15
+	"github.com/openshift/origin/pkg/api/latest"
16
+	osclient "github.com/openshift/origin/pkg/client"
17
+	"github.com/openshift/origin/pkg/cmd/cli"
18
+	"github.com/openshift/origin/pkg/cmd/cli/cmd"
19
+	cmdutil "github.com/openshift/origin/pkg/cmd/util"
20
+	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
21
+	dh "github.com/openshift/origin/pkg/cmd/util/docker"
22
+	"github.com/openshift/origin/pkg/dockerregistry"
23
+	genapp "github.com/openshift/origin/pkg/generate/app"
24
+	gen "github.com/openshift/origin/pkg/generate/generator"
25
+	"github.com/openshift/origin/pkg/generate/source"
26
+)
27
+
28
+const longDescription = `
29
+Experimental command
30
+
31
+Generate configuration to build and deploy code in OpenShift from a source code
32
+repository.
33
+
34
+Docker builds - If a Dockerfile is present in the source code repository, then
35
+a docker build is generated.
36
+
37
+STI builds - If no builder image is specified as an argument, generate will detect
38
+the type of source repository (JEE, Ruby, NodeJS) and associate a default builder
39
+to it.
40
+
41
+Services and Exposed Ports - For Docker builds, generate looks for EXPOSE directives
42
+in the Dockerfile to determine which ports to expose. For STI builds, generate will
43
+use the exposed ports of the builder image. In either case, if a different set of
44
+ports needs to be exposed, use the --ports flag to specify them. Services will be
45
+generated using these ports as well.
46
+
47
+
48
+Usage:
49
+openshift ex generate [source]
50
+
51
+The source parameter may be a directory or a repository URL.
52
+If not specified, the current directory is used.
53
+
54
+Examples:
55
+
56
+    $ openshift ex generate
57
+    <finds a git repository in the current directory and builds artifacts based on detection>
58
+
59
+    $ openshift ex generate ./repo/dir
60
+    <specify the directory for the repository to use>
61
+
62
+    $ openshift ex generate https://github.com/openshift/ruby-hello-world.git
63
+    <use a remote git repository>
64
+
65
+    $ openshift ex generate --builder-image=openshift/ruby-20-centos
66
+    <force the application to use the specific builder-image>`
67
+
68
+type params struct {
69
+	name,
70
+	sourceDir,
71
+	sourceRef,
72
+	sourceURL,
73
+	dockerContext,
74
+	builderImage,
75
+	ports string
76
+	env cmdutil.Environment
77
+}
78
+
79
+func NewCmdGenerate(name string) *cobra.Command {
80
+	cfg := clientcmd.NewConfig()
81
+	dockerHelper := dh.NewHelper()
82
+	input := params{}
83
+	var factory *cmd.Factory
84
+
85
+	c := &cobra.Command{
86
+		Use:   fmt.Sprintf("%s%s", name, clientcmd.ConfigSyntax),
87
+		Short: "Generates an application configuration from a source repository",
88
+		Long:  longDescription,
89
+		Run: func(c *cobra.Command, args []string) {
90
+			_, osClient, err := cfg.Clients()
91
+			if err != nil {
92
+				osClient = nil
93
+			}
94
+			dockerClient, _, err := dockerHelper.GetClient()
95
+			if err != nil {
96
+				osClient = nil
97
+			}
98
+			if len(args) == 1 {
99
+				if source.IsRemoteRepository(args[0]) {
100
+					input.sourceURL = args[0]
101
+				} else {
102
+					input.sourceDir = args[0]
103
+				}
104
+			}
105
+			if len(input.sourceDir) == 0 && len(input.sourceURL) == 0 {
106
+				if input.sourceDir, err = os.Getwd(); err != nil {
107
+					exitWithError(err)
108
+				}
109
+			}
110
+			if envParam := kubecmd.GetFlagString(c, "environment"); len(envParam) > 0 {
111
+				envVars := strings.Split(envParam, ",")
112
+				env, _, errs := cmdutil.ParseEnvironmentArguments(envVars)
113
+				if len(errs) > 0 {
114
+					exitWithError(errors.NewAggregate(errs))
115
+				}
116
+				input.env = env
117
+			}
118
+			namespace, err := factory.DefaultNamespace(c)
119
+			if err != nil {
120
+				namespace = ""
121
+			}
122
+			imageResolver := newImageResolver(namespace, osClient, dockerClient)
123
+
124
+			if err = generateApp(input, imageResolver, os.Stdout); err != nil {
125
+				exitWithError(err)
126
+			}
127
+		},
128
+	}
129
+	clientConfig := cli.DefaultClientConfig(c.PersistentFlags())
130
+	factory = cmd.NewFactory(clientConfig)
131
+	factory.BindFlags(c.PersistentFlags())
132
+
133
+	flag := c.Flags()
134
+	flag.StringVar(&input.name, "name", "", "Set name to use for generated application artifacts")
135
+	flag.StringVar(&input.sourceRef, "ref", "", "Set the name of the repository branch/ref to use")
136
+	flag.StringVar(&input.sourceURL, "source-url", "", "Set the source URL")
137
+	flag.StringVar(&input.dockerContext, "docker-context", "", "Context path for Dockerfile if creating a Docker build")
138
+	flag.StringVar(&input.builderImage, "builder-image", "", "Image to use for STI build")
139
+	flag.StringVarP(&input.ports, "ports", "p", "", "Comma-separated list of ports to expose on pod deployment")
140
+	flag.StringP("environment", "e", "", "Comma-separated list of environment variables to add to the deployment. Should be in the form of var1=value1,var2=value2,...")
141
+	dockerHelper.InstallFlags(flag)
142
+	return c
143
+}
144
+
145
+func newImageResolver(namespace string, osClient osclient.Interface, dockerClient *docker.Client) genapp.Resolver {
146
+	resolver := genapp.PerfectMatchWeightedResolver{}
147
+
148
+	if dockerClient != nil {
149
+		localDockerResolver := &genapp.DockerClientResolver{dockerClient}
150
+		resolver = append(resolver, genapp.WeightedResolver{localDockerResolver, 0.0})
151
+	}
152
+
153
+	if osClient != nil {
154
+		namespaces := []string{}
155
+		if len(namespace) > 0 {
156
+			namespaces = append(namespaces, namespace)
157
+		}
158
+		namespaces = append(namespaces, "default")
159
+		imageStreamResolver := &genapp.ImageStreamResolver{
160
+			Client:     osClient,
161
+			Images:     osClient,
162
+			Namespaces: namespaces,
163
+		}
164
+		resolver = append(resolver, genapp.WeightedResolver{imageStreamResolver, 0.0})
165
+	}
166
+
167
+	dockerRegistryResolver := &genapp.DockerRegistryResolver{dockerregistry.NewClient()}
168
+	resolver = append(resolver, genapp.WeightedResolver{dockerRegistryResolver, 0.0})
169
+
170
+	return resolver
171
+}
172
+
173
+func generateSourceRef(url string, dir string, ref string, name string) (*genapp.SourceRef, error) {
174
+	srcRefGen := gen.NewSourceRefGenerator()
175
+	var result *genapp.SourceRef
176
+	var err error
177
+	if len(url) > 0 {
178
+		glog.V(3).Infof("Generating source reference from URL: %s", url)
179
+		if result, err = srcRefGen.FromGitURL(url); err != nil {
180
+			return nil, err
181
+		}
182
+	} else {
183
+		glog.V(3).Infof("Generating source reference from directory: %s", dir)
184
+		if result, err = srcRefGen.FromDirectory(dir); err != nil {
185
+			return nil, err
186
+		}
187
+	}
188
+	if len(ref) > 0 {
189
+		result.Ref = ref
190
+	}
191
+	if len(name) > 0 {
192
+		result.Name = name
193
+	}
194
+	return result, nil
195
+}
196
+
197
+func generateBuildStrategyRef(srcRef *genapp.SourceRef, dockerContext string, builderImage string) (*genapp.BuildStrategyRef, error) {
198
+	strategyRefGen := gen.NewBuildStrategyRefGenerator(source.DefaultDetectors)
199
+	imageRefGen := gen.NewImageRefGenerator()
200
+	if len(dockerContext) > 0 {
201
+		glog.V(3).Infof("Generating build strategy reference using dockerContext: %s", dockerContext)
202
+		return strategyRefGen.FromSourceRefAndDockerContext(*srcRef, dockerContext)
203
+	} else if len(builderImage) > 0 {
204
+		glog.V(3).Infof("Generating build strategy reference using builder image: %s", builderImage)
205
+		builderRef, err := imageRefGen.FromName(builderImage)
206
+		if err != nil {
207
+			return nil, err
208
+		}
209
+		return strategyRefGen.FromSTIBuilderImage(builderRef)
210
+	} else {
211
+		glog.V(3).Infof("Detecting build strategy using source reference: %#v", srcRef)
212
+		return strategyRefGen.FromSourceRef(*srcRef)
213
+	}
214
+}
215
+
216
+func generateOutputImageRef(srcRef *genapp.SourceRef, strategyRef *genapp.BuildStrategyRef, ports string) (*genapp.ImageRef, error) {
217
+	imageRefGen := gen.NewImageRefGenerator()
218
+	name, ok := srcRef.SuggestName()
219
+	if !ok {
220
+		return nil, fmt.Errorf("cannot suggest a name for the output image. Please specify one with the --output-image argument")
221
+	}
222
+	if len(ports) > 0 {
223
+		portList := strings.Split(ports, ",")
224
+		return imageRefGen.FromNameAndPorts(name, portList)
225
+	}
226
+	if strategyRef.IsDockerBuild {
227
+		return imageRefGen.FromDockerfile(name, srcRef.Dir, strategyRef.DockerContext)
228
+	} else {
229
+		return imageRefGen.FromName(name)
230
+	}
231
+}
232
+
233
+func generateApp(input params, imageResolver genapp.Resolver, out io.Writer) error {
234
+	// Get a SourceRef
235
+	srcRef, err := generateSourceRef(input.sourceURL, input.sourceDir, input.sourceRef, input.name)
236
+	if err != nil {
237
+		return err
238
+	}
239
+	glog.V(2).Infof("Source reference: %#v", srcRef)
240
+
241
+	// Get a BuildStrategyRef
242
+	strategyRef, err := generateBuildStrategyRef(srcRef, input.dockerContext, input.builderImage)
243
+	if err != nil {
244
+		return err
245
+	}
246
+	glog.V(2).Infof("Generated build strategy reference: %#v", strategyRef)
247
+
248
+	// Get an ImageRef for Output
249
+	outputRef, err := generateOutputImageRef(srcRef, strategyRef, input.ports)
250
+	if err != nil {
251
+		return err
252
+	}
253
+	glog.V(2).Infof("Generated output image reference: %#v", outputRef)
254
+
255
+	outputRefCopy := *outputRef
256
+	baseRef := &outputRefCopy
257
+	if !strategyRef.IsDockerBuild {
258
+		if baseImageMatch, err := imageResolver.Resolve(strategyRef.Base.RepoName()); err == nil {
259
+			if inputRef, err := genapp.InputImageFromMatch(baseImageMatch); err == nil {
260
+				if len(input.ports) > 0 {
261
+					inputRef.Info = baseRef.Info
262
+				}
263
+				baseRef = inputRef
264
+			}
265
+		}
266
+	}
267
+	baseRef.AsImageRepository = false
268
+
269
+	pipeline, err := genapp.NewBuildPipeline(outputRef.Name, baseRef, strategyRef, srcRef)
270
+	if err != nil {
271
+		return err
272
+	}
273
+	env := genapp.Environment{}
274
+	for k, v := range input.env {
275
+		env[k] = v
276
+	}
277
+	if err := pipeline.NeedsDeployment(env); err != nil {
278
+		return err
279
+	}
280
+
281
+	objects, err := pipeline.Objects(genapp.NewAcceptFirst())
282
+	if err != nil {
283
+		return err
284
+	}
285
+	objects = genapp.AddServices(objects)
286
+	list := &kapi.List{Items: objects}
287
+	output, err := latest.Codec.Encode(list)
288
+	if err != nil {
289
+		return err
290
+	}
291
+	_, err = out.Write(output)
292
+	return err
293
+}
294
+
295
+func exitWithError(err error) {
296
+	fmt.Fprintf(os.Stderr, "Error: %v\n", err)
297
+	os.Exit(1)
298
+}
... ...
@@ -9,6 +9,7 @@ import (
9 9
 
10 10
 	"github.com/openshift/origin/pkg/cmd/cli"
11 11
 	"github.com/openshift/origin/pkg/cmd/experimental/config"
12
+	"github.com/openshift/origin/pkg/cmd/experimental/generate"
12 13
 	"github.com/openshift/origin/pkg/cmd/experimental/policy"
13 14
 	"github.com/openshift/origin/pkg/cmd/experimental/tokens"
14 15
 	"github.com/openshift/origin/pkg/cmd/flagtypes"
... ...
@@ -112,6 +113,7 @@ func newExperimentalCommand(parentName, name string) *cobra.Command {
112 112
 	experimental.AddCommand(config.NewCmdConfig(fmt.Sprintf("%s %s", parentName, name), "config"))
113 113
 	experimental.AddCommand(tokens.NewCmdTokens("tokens"))
114 114
 	experimental.AddCommand(policy.NewCommandPolicy("policy"))
115
+	experimental.AddCommand(generate.NewCmdGenerate("generate"))
115 116
 	return experimental
116 117
 }
117 118
 
... ...
@@ -17,9 +17,9 @@ import (
17 17
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
18 18
 	"github.com/fsouza/go-dockerclient"
19 19
 
20
-	build "github.com/openshift/origin/pkg/build/api"
21
-	deploy "github.com/openshift/origin/pkg/deploy/api"
22
-	image "github.com/openshift/origin/pkg/image/api"
20
+	buildapi "github.com/openshift/origin/pkg/build/api"
21
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
22
+	imageapi "github.com/openshift/origin/pkg/image/api"
23 23
 )
24 24
 
25 25
 // NameSuggester is an object that can suggest a name for itself
... ...
@@ -92,34 +92,38 @@ func nameFromGitURL(url *url.URL) (string, bool) {
92 92
 
93 93
 // SourceRef is a reference to a build source
94 94
 type SourceRef struct {
95
-	URL *url.URL
96
-	Ref string
97
-	Dir string
95
+	URL  *url.URL
96
+	Ref  string
97
+	Dir  string
98
+	Name string
98 99
 }
99 100
 
100 101
 // SuggestName returns a name derived from the source URL
101 102
 func (r *SourceRef) SuggestName() (string, bool) {
103
+	if len(r.Name) > 0 {
104
+		return r.Name, true
105
+	}
102 106
 	return nameFromGitURL(r.URL)
103 107
 }
104 108
 
105 109
 // BuildSource returns an OpenShift BuildSource from the SourceRef
106
-func (r *SourceRef) BuildSource() (*build.BuildSource, []build.BuildTriggerPolicy) {
107
-	return &build.BuildSource{
108
-			Type: build.BuildSourceGit,
109
-			Git: &build.GitBuildSource{
110
+func (r *SourceRef) BuildSource() (*buildapi.BuildSource, []buildapi.BuildTriggerPolicy) {
111
+	return &buildapi.BuildSource{
112
+			Type: buildapi.BuildSourceGit,
113
+			Git: &buildapi.GitBuildSource{
110 114
 				URI: r.URL.String(),
111 115
 				Ref: r.Ref,
112 116
 			},
113
-		}, []build.BuildTriggerPolicy{
117
+		}, []buildapi.BuildTriggerPolicy{
114 118
 			{
115
-				Type: build.GithubWebHookType,
116
-				GithubWebHook: &build.WebHookTrigger{
119
+				Type: buildapi.GithubWebHookBuildTriggerType,
120
+				GithubWebHook: &buildapi.WebHookTrigger{
117 121
 					Secret: generateSecret(20),
118 122
 				},
119 123
 			},
120 124
 			{
121
-				Type: build.GenericWebHookType,
122
-				GenericWebHook: &build.WebHookTrigger{
125
+				Type: buildapi.GenericWebHookBuildTriggerType,
126
+				GenericWebHook: &buildapi.WebHookTrigger{
123 127
 					Secret: generateSecret(20),
124 128
 				},
125 129
 			},
... ...
@@ -134,22 +138,22 @@ type BuildStrategyRef struct {
134 134
 }
135 135
 
136 136
 // BuildStrategy builds an OpenShift BuildStrategy from a BuildStrategyRef
137
-func (s *BuildStrategyRef) BuildStrategy() (*build.BuildStrategy, []build.BuildTriggerPolicy) {
137
+func (s *BuildStrategyRef) BuildStrategy() (*buildapi.BuildStrategy, []buildapi.BuildTriggerPolicy) {
138 138
 	if s.IsDockerBuild {
139
-		strategy := &build.BuildStrategy{
140
-			Type: build.DockerBuildStrategyType,
139
+		strategy := &buildapi.BuildStrategy{
140
+			Type: buildapi.DockerBuildStrategyType,
141 141
 		}
142 142
 		if len(s.DockerContext) > 0 {
143
-			strategy.DockerStrategy = &build.DockerBuildStrategy{
143
+			strategy.DockerStrategy = &buildapi.DockerBuildStrategy{
144 144
 				ContextDir: s.DockerContext,
145 145
 			}
146 146
 		}
147 147
 		return strategy, s.Base.BuildTriggers()
148 148
 	}
149 149
 
150
-	return &build.BuildStrategy{
151
-		Type: build.STIBuildStrategyType,
152
-		STIStrategy: &build.STIBuildStrategy{
150
+	return &buildapi.BuildStrategy{
151
+		Type: buildapi.STIBuildStrategyType,
152
+		STIStrategy: &buildapi.STIBuildStrategy{
153 153
 			Image: s.Base.NameReference(),
154 154
 		},
155 155
 	}, nil
... ...
@@ -164,14 +168,14 @@ type ImageRef struct {
164 164
 
165 165
 	AsImageRepository bool
166 166
 
167
-	Repository *image.ImageRepository
168
-	Info       *docker.Image
167
+	Repository *imageapi.ImageRepository
168
+	Info       *imageapi.DockerImage
169 169
 }
170 170
 
171 171
 // pullSpec returns the string that can be passed to Docker to fetch this
172 172
 // image.
173 173
 func (r *ImageRef) pullSpec() string {
174
-	return image.JoinDockerPullSpec(r.Registry, r.Namespace, r.Name, r.Tag)
174
+	return imageapi.JoinDockerPullSpec(r.Registry, r.Namespace, r.Name, r.Tag)
175 175
 }
176 176
 
177 177
 // NameReference returns the name that other OpenShift objects may refer to this
... ...
@@ -203,22 +207,27 @@ func (r *ImageRef) SuggestName() (string, bool) {
203 203
 	return r.Name, true
204 204
 }
205 205
 
206
-func (r *ImageRef) BuildOutput() *build.BuildOutput {
206
+func (r *ImageRef) BuildOutput() (*buildapi.BuildOutput, error) {
207 207
 	if r == nil {
208
-		return &build.BuildOutput{}
208
+		return &buildapi.BuildOutput{}, nil
209 209
 	}
210
-	return &build.BuildOutput{
211
-		ImageTag: r.NameReference(),
212
-		Registry: r.Registry,
210
+	imageRepo, err := r.ImageRepository()
211
+	if err != nil {
212
+		return nil, err
213 213
 	}
214
+	return &buildapi.BuildOutput{
215
+		To: &kapi.ObjectReference{
216
+			Name: imageRepo.Name,
217
+		},
218
+	}, nil
214 219
 }
215 220
 
216
-func (r *ImageRef) BuildTriggers() []build.BuildTriggerPolicy {
221
+func (r *ImageRef) BuildTriggers() []buildapi.BuildTriggerPolicy {
217 222
 	// TODO return triggers when image build triggers are available
218
-	return []build.BuildTriggerPolicy{}
223
+	return []buildapi.BuildTriggerPolicy{}
219 224
 }
220 225
 
221
-func (r *ImageRef) ImageRepository() (*image.ImageRepository, error) {
226
+func (r *ImageRef) ImageRepository() (*imageapi.ImageRepository, error) {
222 227
 	if r.Repository != nil {
223 228
 		return r.Repository, nil
224 229
 	}
... ...
@@ -228,7 +237,7 @@ func (r *ImageRef) ImageRepository() (*image.ImageRepository, error) {
228 228
 		return nil, fmt.Errorf("unable to suggest an image repository name for %q", r.pullSpec())
229 229
 	}
230 230
 
231
-	repo := &image.ImageRepository{
231
+	repo := &imageapi.ImageRepository{
232 232
 		ObjectMeta: kapi.ObjectMeta{
233 233
 			Name: name,
234 234
 		},
... ...
@@ -240,23 +249,26 @@ func (r *ImageRef) ImageRepository() (*image.ImageRepository, error) {
240 240
 	return repo, nil
241 241
 }
242 242
 
243
-func (r *ImageRef) DeployableContainer() (container *kapi.Container, triggers []deploy.DeploymentTriggerPolicy, err error) {
244
-	if r.Info == nil {
245
-		return nil, nil, fmt.Errorf("image info for %q is required to generate a container definition", r.Name)
246
-	}
243
+func (r *ImageRef) DeployableContainer() (container *kapi.Container, triggers []deployapi.DeploymentTriggerPolicy, err error) {
247 244
 	name, ok := r.SuggestName()
248 245
 	if !ok {
249 246
 		return nil, nil, fmt.Errorf("unable to suggest a container name for the image %q", r.pullSpec())
250 247
 	}
251 248
 	if r.AsImageRepository {
252
-		triggers = []deploy.DeploymentTriggerPolicy{
249
+		tag := r.Tag
250
+		if len(tag) == 0 {
251
+			tag = "latest"
252
+		}
253
+		triggers = []deployapi.DeploymentTriggerPolicy{
253 254
 			{
254
-				Type: deploy.DeploymentTriggerOnImageChange,
255
-				ImageChangeParams: &deploy.DeploymentTriggerImageChangeParams{
255
+				Type: deployapi.DeploymentTriggerOnImageChange,
256
+				ImageChangeParams: &deployapi.DeploymentTriggerImageChangeParams{
256 257
 					Automatic:      true,
257 258
 					ContainerNames: []string{name},
258
-					RepositoryName: r.NameReference(),
259
-					Tag:            r.Tag,
259
+					From: kapi.ObjectReference{
260
+						Name: name,
261
+					},
262
+					Tag: tag,
260 263
 				},
261 264
 			},
262 265
 		}
... ...
@@ -269,7 +281,8 @@ func (r *ImageRef) DeployableContainer() (container *kapi.Container, triggers []
269 269
 
270 270
 	// If imageInfo present, append ports
271 271
 	if r.Info != nil {
272
-		for p := range r.Info.Config.ExposedPorts {
272
+		for sp := range r.Info.Config.ExposedPorts {
273
+			p := docker.Port(sp)
273 274
 			port, err := strconv.Atoi(p.Port())
274 275
 			if err != nil {
275 276
 				return nil, nil, fmt.Errorf("failed to parse port %q: %v", p.Port(), err)
... ...
@@ -294,30 +307,34 @@ type BuildRef struct {
294 294
 	Output   *ImageRef
295 295
 }
296 296
 
297
-func (r *BuildRef) BuildConfig() (*build.BuildConfig, error) {
297
+func (r *BuildRef) BuildConfig() (*buildapi.BuildConfig, error) {
298 298
 	name, ok := NameSuggestions{r.Source, r.Output}.SuggestName()
299 299
 	if !ok {
300 300
 		return nil, fmt.Errorf("unable to suggest a name for this build config from %q", r.Source.URL)
301 301
 	}
302
-	source := &build.BuildSource{}
303
-	sourceTriggers := []build.BuildTriggerPolicy{}
302
+	source := &buildapi.BuildSource{}
303
+	sourceTriggers := []buildapi.BuildTriggerPolicy{}
304 304
 	if r.Source != nil {
305 305
 		source, sourceTriggers = r.Source.BuildSource()
306 306
 	}
307
-	strategy := &build.BuildStrategy{}
308
-	strategyTriggers := []build.BuildTriggerPolicy{}
307
+	strategy := &buildapi.BuildStrategy{}
308
+	strategyTriggers := []buildapi.BuildTriggerPolicy{}
309 309
 	if r.Strategy != nil {
310 310
 		strategy, strategyTriggers = r.Strategy.BuildStrategy()
311 311
 	}
312
-	return &build.BuildConfig{
312
+	output, err := r.Output.BuildOutput()
313
+	if err != nil {
314
+		return nil, err
315
+	}
316
+	return &buildapi.BuildConfig{
313 317
 		ObjectMeta: kapi.ObjectMeta{
314 318
 			Name: name,
315 319
 		},
316 320
 		Triggers: append(sourceTriggers, strategyTriggers...),
317
-		Parameters: build.BuildParameters{
321
+		Parameters: buildapi.BuildParameters{
318 322
 			Source:   *source,
319 323
 			Strategy: *strategy,
320
-			Output:   *r.Output.BuildOutput(),
324
+			Output:   *output,
321 325
 		},
322 326
 	}, nil
323 327
 }
... ...
@@ -328,7 +345,7 @@ type DeploymentConfigRef struct {
328 328
 }
329 329
 
330 330
 // TODO: take a pod template spec as argument
331
-func (r *DeploymentConfigRef) DeploymentConfig() (*deploy.DeploymentConfig, error) {
331
+func (r *DeploymentConfigRef) DeploymentConfig() (*deployapi.DeploymentConfig, error) {
332 332
 	suggestions := NameSuggestions{}
333 333
 	for i := range r.Images {
334 334
 		suggestions = append(suggestions, r.Images[i])
... ...
@@ -342,10 +359,10 @@ func (r *DeploymentConfigRef) DeploymentConfig() (*deploy.DeploymentConfig, erro
342 342
 		"deploymentconfig": name,
343 343
 	}
344 344
 
345
-	triggers := []deploy.DeploymentTriggerPolicy{
345
+	triggers := []deployapi.DeploymentTriggerPolicy{
346 346
 		// By default, always deploy on change
347 347
 		{
348
-			Type: deploy.DeploymentTriggerOnConfigChange,
348
+			Type: deployapi.DeploymentTriggerOnConfigChange,
349 349
 		},
350 350
 	}
351 351
 
... ...
@@ -364,11 +381,14 @@ func (r *DeploymentConfigRef) DeploymentConfig() (*deploy.DeploymentConfig, erro
364 364
 		template.Containers[i].Env = append(template.Containers[i].Env, r.Env.List()...)
365 365
 	}
366 366
 
367
-	return &deploy.DeploymentConfig{
367
+	return &deployapi.DeploymentConfig{
368 368
 		ObjectMeta: kapi.ObjectMeta{
369 369
 			Name: name,
370 370
 		},
371
-		Template: deploy.DeploymentTemplate{
371
+		Template: deployapi.DeploymentTemplate{
372
+			Strategy: deployapi.DeploymentStrategy{
373
+				Type: deployapi.DeploymentStrategyTypeRecreate,
374
+			},
372 375
 			ControllerTemplate: kapi.ReplicationControllerSpec{
373 376
 				Replicas: 1,
374 377
 				Selector: selector,
... ...
@@ -5,19 +5,17 @@ import (
5 5
 	"net/url"
6 6
 	"testing"
7 7
 
8
-	"github.com/fsouza/go-dockerclient"
9
-
10 8
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
11 9
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
12 10
 
13 11
 	"github.com/openshift/origin/pkg/api/latest"
14 12
 	build "github.com/openshift/origin/pkg/build/api"
15
-	config "github.com/openshift/origin/pkg/config/api"
13
+	imageapi "github.com/openshift/origin/pkg/image/api"
16 14
 )
17 15
 
18
-func testImageInfo() *docker.Image {
19
-	return &docker.Image{
20
-		Config: &docker.Config{},
16
+func testImageInfo() *imageapi.DockerImage {
17
+	return &imageapi.DockerImage{
18
+		Config: imageapi.DockerConfig{},
21 19
 	}
22 20
 }
23 21
 
... ...
@@ -75,7 +73,7 @@ func TestSimpleBuildConfig(t *testing.T) {
75 75
 	if err != nil {
76 76
 		t.Fatalf("unexpected error: %v", err)
77 77
 	}
78
-	if config.Name != "origin" || config.Parameters.Output.ImageTag != "myregistry/openshift/origin" || config.Parameters.Output.Registry != "myregistry" {
78
+	if config.Name != "origin" || config.Parameters.Output.To.Name != "origin" {
79 79
 		t.Errorf("unexpected name: %#v", config)
80 80
 	}
81 81
 }
... ...
@@ -121,11 +119,8 @@ func ExampleGenerateSimpleDockerApp() {
121 121
 		buildConfig,
122 122
 		deployConfig,
123 123
 	}
124
-	rawItems := []runtime.RawExtension{}
125
-	kapi.Scheme.Convert(items, &rawItems)
126
-
127
-	out := &config.Config{
128
-		Items: rawItems,
124
+	out := &kapi.List{
125
+		Items: items,
129 126
 	}
130 127
 
131 128
 	data, err := latest.Codec.Encode(out)
... ...
@@ -1,13 +1,14 @@
1 1
 package app
2 2
 
3 3
 import (
4
-	"github.com/fsouza/go-dockerclient"
5 4
 	"strings"
5
+
6
+	imageapi "github.com/openshift/origin/pkg/image/api"
6 7
 )
7 8
 
8 9
 var stiEnvironmentNames = []string{"STI_LOCATION", "STI_SCRIPTS_URL", "STI_BUILDER"}
9 10
 
10
-func IsBuilderImage(image *docker.Image) bool {
11
+func IsBuilderImage(image *imageapi.DockerImage) bool {
11 12
 	for _, env := range image.Config.Env {
12 13
 		for _, name := range stiEnvironmentNames {
13 14
 			if strings.HasPrefix(env, name+"=") {
14 15
new file mode 100644
... ...
@@ -0,0 +1,391 @@
0
+package cmd
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+	"strings"
6
+
7
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
9
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
10
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
11
+	"github.com/fsouza/go-dockerclient"
12
+	"github.com/golang/glog"
13
+
14
+	"github.com/openshift/origin/pkg/client"
15
+	cmdutil "github.com/openshift/origin/pkg/cmd/util"
16
+	"github.com/openshift/origin/pkg/dockerregistry"
17
+	"github.com/openshift/origin/pkg/generate/app"
18
+	"github.com/openshift/origin/pkg/generate/dockerfile"
19
+	"github.com/openshift/origin/pkg/generate/source"
20
+)
21
+
22
+type AppConfig struct {
23
+	SourceRepositories util.StringList
24
+
25
+	Components   util.StringList
26
+	ImageStreams util.StringList
27
+	DockerImages util.StringList
28
+	Groups       util.StringList
29
+	Environment  util.StringList
30
+
31
+	TypeOfBuild string
32
+
33
+	localDockerResolver    app.Resolver
34
+	dockerRegistryResolver app.Resolver
35
+	imageStreamResolver    app.Resolver
36
+
37
+	searcher app.Searcher
38
+	detector app.Detector
39
+}
40
+
41
+type UsageError interface {
42
+	UsageError(commandName string) string
43
+}
44
+
45
+// TODO: replace with upstream converting [1]error to error
46
+type errlist interface {
47
+	Errors() []error
48
+}
49
+
50
+func NewAppConfig() *AppConfig {
51
+	return &AppConfig{
52
+		searcher: &mockSearcher{},
53
+		detector: app.SourceRepositoryEnumerator{
54
+			Detectors: source.DefaultDetectors,
55
+			Tester:    dockerfile.NewTester(),
56
+		},
57
+		dockerRegistryResolver: app.DockerRegistryResolver{dockerregistry.NewClient()},
58
+	}
59
+}
60
+
61
+func (c *AppConfig) SetDockerClient(dockerclient *docker.Client) {
62
+	c.localDockerResolver = app.DockerClientResolver{dockerclient}
63
+}
64
+
65
+func (c *AppConfig) SetOpenShiftClient(osclient client.Interface, originNamespace string) {
66
+	c.imageStreamResolver = app.ImageStreamResolver{
67
+		Client:     osclient,
68
+		Images:     osclient,
69
+		Namespaces: []string{originNamespace, "default"},
70
+	}
71
+}
72
+
73
+// addArguments converts command line arguments into the appropriate bucket based on what they look like
74
+func (c *AppConfig) AddArguments(args []string) []string {
75
+	unknown := []string{}
76
+	for _, s := range args {
77
+		switch {
78
+		case cmdutil.IsEnvironmentArgument(s):
79
+			c.Environment = append(c.Environment, s)
80
+		case app.IsPossibleSourceRepository(s):
81
+			c.SourceRepositories = append(c.SourceRepositories, s)
82
+		case app.IsComponentReference(s):
83
+			c.Components = append(c.Components, s)
84
+		default:
85
+			if len(s) == 0 {
86
+				break
87
+			}
88
+			unknown = append(unknown, s)
89
+		}
90
+	}
91
+	return unknown
92
+}
93
+
94
+// validate converts all of the arguments on the config into references to objects, or returns an error
95
+func (c *AppConfig) validate() (app.ComponentReferences, []*app.SourceRepository, cmdutil.Environment, error) {
96
+	b := &app.ReferenceBuilder{}
97
+	for _, s := range c.SourceRepositories {
98
+		b.AddSourceRepository(s)
99
+	}
100
+	b.AddImages(c.DockerImages, func(input *app.ComponentInput) app.ComponentReference {
101
+		input.Argument = fmt.Sprintf("--docker-image=%q", input.From)
102
+		input.Resolver = c.dockerRegistryResolver
103
+		return input
104
+	})
105
+	b.AddImages(c.ImageStreams, func(input *app.ComponentInput) app.ComponentReference {
106
+		input.Argument = fmt.Sprintf("--image=%q", input.From)
107
+		input.Resolver = c.imageStreamResolver
108
+		return input
109
+	})
110
+	b.AddImages(c.Components, func(input *app.ComponentInput) app.ComponentReference {
111
+		input.Resolver = app.PerfectMatchWeightedResolver{
112
+			app.WeightedResolver{Resolver: c.imageStreamResolver, Weight: 0.0},
113
+			app.WeightedResolver{Resolver: c.dockerRegistryResolver, Weight: 0.0},
114
+			app.WeightedResolver{Resolver: c.localDockerResolver, Weight: 0.0},
115
+		}
116
+		return input
117
+	})
118
+	b.AddGroups(c.Groups)
119
+	refs, repos, errs := b.Result()
120
+	if len(c.TypeOfBuild) != 0 && len(repos) == 0 {
121
+		errs = append(errs, fmt.Errorf("when --build is specified you must provide at least one source code location"))
122
+	}
123
+
124
+	env, duplicate, envErrs := cmdutil.ParseEnvironmentArguments(c.Environment)
125
+	for _, s := range duplicate {
126
+		glog.V(1).Infof("The environment variable %q was overwritten", s)
127
+	}
128
+	errs = append(errs, envErrs...)
129
+
130
+	return refs, repos, env, errors.NewAggregate(errs)
131
+}
132
+
133
+// resolve the references to ensure they are all valid, and identify any images that don't match user input.
134
+func (c *AppConfig) resolve(components app.ComponentReferences) error {
135
+	errs := []error{}
136
+	for _, ref := range components {
137
+		if err := ref.Resolve(); err != nil {
138
+			errs = append(errs, err)
139
+			continue
140
+		}
141
+		switch input := ref.Input(); {
142
+		case !input.ExpectToBuild && input.Match.Builder:
143
+			if c.TypeOfBuild != "docker" {
144
+				glog.Infof("Image %q is a builder, so a repository will be expected unless you also specify --build=docker", input)
145
+				input.ExpectToBuild = true
146
+			}
147
+		case input.ExpectToBuild && !input.Match.Builder:
148
+			if len(c.TypeOfBuild) == 0 {
149
+				errs = append(errs, fmt.Errorf("none of the images that match %q can build source code - check whether this is the image you want to use, then use --build=source to build using source or --build=docker to treat this as a Docker base image and set up a layered Docker build", ref))
150
+				continue
151
+			}
152
+		}
153
+	}
154
+	return errors.NewAggregate(errs)
155
+}
156
+
157
+// ensureHasSource ensure every builder component has source code associated with it
158
+func (c *AppConfig) ensureHasSource(components app.ComponentReferences, repositories []*app.SourceRepository) error {
159
+	requiresSource := components.NeedsSource()
160
+	if len(requiresSource) > 0 {
161
+		switch {
162
+		case len(repositories) > 1:
163
+			// TODO: harder problem - need to match repos up
164
+			if len(requiresSource) == 1 {
165
+				// TODO: print all suggestions
166
+				return fmt.Errorf("there are multiple code locations provided - use '%s~<repo>' to declare which code goes with the image", requiresSource[0])
167
+			}
168
+			// TODO: indicate which args don't match, and which repos don't match
169
+			return fmt.Errorf("there are multiple code locations provided - use '[image]~[repo]' to declare which code goes with which image")
170
+		case len(repositories) == 1:
171
+			glog.Infof("Using %q as the source for build", repositories[0])
172
+			for _, component := range requiresSource {
173
+				component.Input().Use(repositories[0])
174
+				repositories[0].UsedBy(component)
175
+			}
176
+		default:
177
+			if len(requiresSource) == 1 {
178
+				return fmt.Errorf("the image %q will build source code, so you must specify a repository via --code", requiresSource[0])
179
+			}
180
+			// TODO: array of pointers won't print correctly
181
+			return fmt.Errorf("you must provide at least one source code repository with --code for the images: %v", requiresSource)
182
+		}
183
+	}
184
+	return nil
185
+}
186
+
187
+// detectSource tries to match each source repository to an image type
188
+func (c *AppConfig) detectSource(repositories []*app.SourceRepository) error {
189
+	errs := []error{}
190
+	for _, repo := range repositories {
191
+		// if the repository is being used by one of the images, we don't need to detect its type (unless we want to double check)
192
+		if repo.InUse() {
193
+			continue
194
+		}
195
+		path, err := repo.LocalPath()
196
+		if err != nil {
197
+			errs = append(errs, err)
198
+			continue
199
+		}
200
+		info, err := c.detector.Detect(path)
201
+		if err != nil {
202
+			errs = append(errs, err)
203
+			continue
204
+		}
205
+		if info.Dockerfile != nil {
206
+			// TODO: this should be using the reference builder flow, possibly by moving detectSource up before other steps
207
+			/*if from, ok := info.Dockerfile.GetDirective("FROM"); ok {
208
+				input, _, err := NewComponentInput(from[0])
209
+				if err != nil {
210
+					errs = append(errs, err)
211
+					continue
212
+				}
213
+				input.
214
+			}*/
215
+			repo.BuildWithDocker()
216
+			continue
217
+		}
218
+
219
+		terms := info.Terms()
220
+		matches, err := c.searcher.Search(terms)
221
+		if err != nil {
222
+			errs = append(errs, err)
223
+			continue
224
+		}
225
+		if len(matches) == 0 {
226
+			errs = append(errs, fmt.Errorf("we could not find any images that match the source repo %q (looked for: %v) and this repository does not have a Dockerfile - you'll need to choose a source builder image to continue", repo, terms))
227
+			continue
228
+		}
229
+		errs = append(errs, fmt.Errorf("found the following possible images to use to build this source repository: %v - to continue, you'll need to specify which image to use with %q", matches, repo))
230
+	}
231
+	return errors.NewAggregate(errs)
232
+}
233
+
234
+// buildPipelines converts a set of resolved, valid references into pipelines.
235
+func (c *AppConfig) buildPipelines(components app.ComponentReferences, environment app.Environment) (app.PipelineGroup, error) {
236
+	pipelines := app.PipelineGroup{}
237
+	for _, group := range components.Group() {
238
+		glog.V(2).Infof("found group: %#v", group)
239
+		common := app.PipelineGroup{}
240
+		for _, ref := range group {
241
+
242
+			var pipeline *app.Pipeline
243
+			if ref.Input().ExpectToBuild {
244
+				glog.V(2).Infof("will use %q as the base image for a source build of %q", ref, ref.Input().Uses)
245
+				input, err := app.InputImageFromMatch(ref.Input().Match)
246
+				if err != nil {
247
+					return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
248
+				}
249
+				strategy, source, err := app.StrategyAndSourceForRepository(ref.Input().Uses)
250
+				if err != nil {
251
+					return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
252
+				}
253
+				if pipeline, err = app.NewBuildPipeline(ref.Input().String(), input, strategy, source); err != nil {
254
+					return nil, fmt.Errorf("can't build %q: %v", ref.Input(), err)
255
+				}
256
+
257
+			} else {
258
+				glog.V(2).Infof("will include %q", ref)
259
+				input, err := app.InputImageFromMatch(ref.Input().Match)
260
+				if err != nil {
261
+					return nil, fmt.Errorf("can't include %q: %v", ref.Input(), err)
262
+				}
263
+				if pipeline, err = app.NewImagePipeline(ref.Input().String(), input); err != nil {
264
+					return nil, fmt.Errorf("can't include %q: %v", ref.Input(), err)
265
+				}
266
+			}
267
+
268
+			if err := pipeline.NeedsDeployment(environment); err != nil {
269
+				return nil, fmt.Errorf("can't set up a deployment for %q: %v", ref.Input(), err)
270
+			}
271
+			common = append(common, pipeline)
272
+		}
273
+
274
+		if err := common.Reduce(); err != nil {
275
+			return nil, fmt.Errorf("can't create a pipeline from %s: %v", common, err)
276
+		}
277
+		pipelines = append(pipelines, common...)
278
+	}
279
+	return pipelines, nil
280
+}
281
+
282
+// Run executes the provided config.
283
+func (c *AppConfig) Run(out io.Writer, helpFn func() error) error {
284
+	components, repositories, environment, err := c.validate()
285
+	if err != nil {
286
+		return err
287
+	}
288
+
289
+	hasSource := len(repositories) != 0
290
+	hasImages := len(components) != 0
291
+	if !hasSource && !hasImages {
292
+		// display help page
293
+		// TODO: return usage error, which should trigger help display
294
+		return helpFn()
295
+	}
296
+
297
+	if err := c.resolve(components); err != nil {
298
+		return err
299
+	}
300
+
301
+	if err := c.ensureHasSource(components, repositories); err != nil {
302
+		return err
303
+	}
304
+
305
+	glog.V(4).Infof("Code %v", repositories)
306
+	glog.V(4).Infof("Images %v", components)
307
+
308
+	if err := c.detectSource(repositories); err != nil {
309
+		return err
310
+	}
311
+
312
+	pipelines, err := c.buildPipelines(components, app.Environment(environment))
313
+	if err != nil {
314
+		return err
315
+	}
316
+
317
+	objects := app.Objects{}
318
+	accept := app.NewAcceptFirst()
319
+	for _, p := range pipelines {
320
+		obj, err := p.Objects(accept)
321
+		if err != nil {
322
+			return fmt.Errorf("can't setup %q: %v", p.From, err)
323
+		}
324
+		objects = append(objects, obj...)
325
+	}
326
+
327
+	objects = app.AddServices(objects)
328
+
329
+	list := &kapi.List{Items: objects}
330
+	p, _, err := kubectl.GetPrinter("yaml", "")
331
+	if err != nil {
332
+		return err
333
+	}
334
+	return p.PrintObj(list, out)
335
+}
336
+
337
+type mockSearcher struct{}
338
+
339
+func (mockSearcher) Search(terms []string) ([]*app.ComponentMatch, error) {
340
+	for _, term := range terms {
341
+		term = strings.ToLower(term)
342
+		switch term {
343
+		case "redhat/mysql:5.6":
344
+			return []*app.ComponentMatch{
345
+				{
346
+					Value:       term,
347
+					Argument:    "redhat/mysql:5.6",
348
+					Name:        "MySQL 5.6",
349
+					Description: "The Open Source SQL database",
350
+				},
351
+			}, nil
352
+		case "mysql", "mysql5", "mysql-5", "mysql-5.x":
353
+			return []*app.ComponentMatch{
354
+				{
355
+					Value:       term,
356
+					Argument:    "redhat/mysql:5.6",
357
+					Name:        "MySQL 5.6",
358
+					Description: "The Open Source SQL database",
359
+				},
360
+				{
361
+					Value:       term,
362
+					Argument:    "mysql",
363
+					Name:        "MySQL 5.X",
364
+					Description: "Something out there on the Docker Hub.",
365
+				},
366
+			}, nil
367
+		case "php", "php-5", "php5", "redhat/php:5", "redhat/php-5":
368
+			return []*app.ComponentMatch{
369
+				{
370
+					Value:       term,
371
+					Argument:    "redhat/php:5",
372
+					Name:        "PHP 5.5",
373
+					Description: "A fast and easy to use scripting language for building websites.",
374
+					Builder:     true,
375
+				},
376
+			}, nil
377
+		case "ruby":
378
+			return []*app.ComponentMatch{
379
+				{
380
+					Value:       term,
381
+					Argument:    "redhat/ruby:2",
382
+					Name:        "Ruby 2.0",
383
+					Description: "A fast and easy to use scripting language for building websites.",
384
+					Builder:     true,
385
+				},
386
+			}, nil
387
+		}
388
+	}
389
+	return []*app.ComponentMatch{}, nil
390
+}
0 391
new file mode 100644
... ...
@@ -0,0 +1,151 @@
0
+package cmd
1
+
2
+import (
3
+	"reflect"
4
+	"testing"
5
+
6
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
7
+)
8
+
9
+func TestAddArguments(t *testing.T) {
10
+	tests := map[string]struct {
11
+		args       []string
12
+		env        util.StringList
13
+		repos      util.StringList
14
+		components util.StringList
15
+		unknown    []string
16
+	}{
17
+		"components": {
18
+			args:       []string{"one", "two+three", "four~five"},
19
+			components: util.StringList{"one", "two+three", "four~five"},
20
+			unknown:    []string{},
21
+		},
22
+		"source": {
23
+			args:    []string{".", "./test/one/two/three", "/var/go/src/test", "git://server/repo.git"},
24
+			repos:   util.StringList{".", "./test/one/two/three", "/var/go/src/test", "git://server/repo.git"},
25
+			unknown: []string{},
26
+		},
27
+		"env": {
28
+			args:    []string{"first=one", "second=two", "third=three"},
29
+			env:     util.StringList{"first=one", "second=two", "third=three"},
30
+			unknown: []string{},
31
+		},
32
+		"mix 1": {
33
+			args:       []string{"git://server/repo.git", "mysql+ruby~git@test.server/repo.git", "env1=test"},
34
+			repos:      util.StringList{"git://server/repo.git"},
35
+			components: util.StringList{"mysql+ruby~git@test.server/repo.git"},
36
+			env:        util.StringList{"env1=test"},
37
+			unknown:    []string{},
38
+		},
39
+	}
40
+
41
+	for n, c := range tests {
42
+		a := AppConfig{}
43
+		unknown := a.AddArguments(c.args)
44
+		if !reflect.DeepEqual(a.Environment, c.env) {
45
+			t.Errorf("%s: Different env variables. Expected: %v, Actual: %v", n, c.env, a.Environment)
46
+		}
47
+		if !reflect.DeepEqual(a.SourceRepositories, c.repos) {
48
+			t.Errorf("%s: Different source repos. Expected: %v, Actual: %v", n, c.repos, a.SourceRepositories)
49
+		}
50
+		if !reflect.DeepEqual(a.Components, c.components) {
51
+			t.Errorf("%s: Different components. Expected: %v, Actual: %v", n, c.components, a.Components)
52
+		}
53
+		if !reflect.DeepEqual(unknown, c.unknown) {
54
+			t.Errorf("%s: Different unknown result. Expected: %v, Actual: %v", n, c.unknown, unknown)
55
+		}
56
+	}
57
+
58
+}
59
+
60
+func TestValidate(t *testing.T) {
61
+	tests := map[string]struct {
62
+		cfg                 AppConfig
63
+		componentValues     []string
64
+		sourceRepoLocations []string
65
+		env                 map[string]string
66
+	}{
67
+		"components": {
68
+			cfg: AppConfig{
69
+				Components: util.StringList{"one", "two", "three/four"},
70
+			},
71
+			componentValues:     []string{"one", "two", "three/four"},
72
+			sourceRepoLocations: []string{},
73
+			env:                 map[string]string{},
74
+		},
75
+		"sourcerepos": {
76
+			cfg: AppConfig{
77
+				SourceRepositories: []string{".", "/test/var/src", "https://server/repo.git"},
78
+			},
79
+			componentValues:     []string{},
80
+			sourceRepoLocations: []string{".", "/test/var/src", "https://server/repo.git"},
81
+			env:                 map[string]string{},
82
+		},
83
+		"envs": {
84
+			cfg: AppConfig{
85
+				Environment: util.StringList{"one=first", "two=second", "three=third"},
86
+			},
87
+			componentValues:     []string{},
88
+			sourceRepoLocations: []string{},
89
+			env:                 map[string]string{"one": "first", "two": "second", "three": "third"},
90
+		},
91
+		"component+source": {
92
+			cfg: AppConfig{
93
+				Components: util.StringList{"one~https://server/repo.git"},
94
+			},
95
+			componentValues:     []string{"one"},
96
+			sourceRepoLocations: []string{"https://server/repo.git"},
97
+			env:                 map[string]string{},
98
+		},
99
+		"components+source": {
100
+			cfg: AppConfig{
101
+				Components: util.StringList{"mysql+ruby~git://github.com/namespace/repo.git"},
102
+			},
103
+			componentValues:     []string{"mysql", "ruby"},
104
+			sourceRepoLocations: []string{"git://github.com/namespace/repo.git"},
105
+			env:                 map[string]string{},
106
+		},
107
+		"components+env": {
108
+			cfg: AppConfig{
109
+				Components:  util.StringList{"mysql+php"},
110
+				Environment: util.StringList{"one=first", "two=second"},
111
+			},
112
+			componentValues:     []string{"mysql", "php"},
113
+			sourceRepoLocations: []string{},
114
+			env: map[string]string{
115
+				"one": "first",
116
+				"two": "second",
117
+			},
118
+		},
119
+	}
120
+
121
+	for n, c := range tests {
122
+		cr, repos, env, err := c.cfg.validate()
123
+		if err != nil {
124
+			t.Errorf("%s: Unexpected error: %v", n, err)
125
+		}
126
+		compValues := []string{}
127
+		for _, r := range cr {
128
+			compValues = append(compValues, r.Input().Value)
129
+		}
130
+		if !reflect.DeepEqual(c.componentValues, compValues) {
131
+			t.Errorf("%s: Component values don't match. Expected: %v, Got: %v", n, c.componentValues, compValues)
132
+		}
133
+		repoLocations := []string{}
134
+		for _, r := range repos {
135
+			repoLocations = append(repoLocations, r.String())
136
+		}
137
+		if !reflect.DeepEqual(c.sourceRepoLocations, repoLocations) {
138
+			t.Errorf("%s: Repository locations don't match. Expected: %v, Got: %v", n, c.sourceRepoLocations, repoLocations)
139
+		}
140
+		if len(env) != len(c.env) {
141
+			t.Errorf("%s: Environment variables don't match. Expected: %v, Got: %v", n, c.env, env)
142
+		}
143
+		for e, v := range env {
144
+			if c.env[e] != v {
145
+				t.Errorf("%s: Environment variables don't match. Expected: %v, Got: %v", n, c.env, env)
146
+				break
147
+			}
148
+		}
149
+	}
150
+}
0 151
new file mode 100644
... ...
@@ -0,0 +1,321 @@
0
+package app
1
+
2
+import (
3
+	"fmt"
4
+	"sort"
5
+	"strings"
6
+
7
+	imageapi "github.com/openshift/origin/pkg/image/api"
8
+	templateapi "github.com/openshift/origin/pkg/template/api"
9
+)
10
+
11
+// isComponentReference returns true if the provided string appears to be a reference to a source repository
12
+// on disk, at a URL, a docker image name (which might be on a Docker registry or an OpenShift image stream),
13
+// or a template.
14
+func IsComponentReference(s string) bool {
15
+	if len(s) == 0 {
16
+		return false
17
+	}
18
+	all := strings.Split(s, "+")
19
+	_, _, _, err := componentWithSource(all[0])
20
+	return err == nil
21
+}
22
+
23
+func componentWithSource(s string) (component, repo string, builder bool, err error) {
24
+	if strings.Contains(s, "~") {
25
+		segs := strings.SplitN(s, "~", 2)
26
+		if len(segs) == 2 {
27
+			builder = true
28
+			switch {
29
+			case len(segs[0]) == 0:
30
+				err = fmt.Errorf("when using '[image]~[code]' form for %q, you must specify a image name", s)
31
+				return
32
+			case len(segs[1]) == 0:
33
+				component = segs[0]
34
+			default:
35
+				component = segs[0]
36
+				repo = segs[1]
37
+			}
38
+		}
39
+	} else {
40
+		component = s
41
+	}
42
+	// TODO: component must be of the form compatible with a pull spec *or* <namespace>/<name>
43
+	if !imageapi.IsPullSpec(component) {
44
+		return "", "", false, fmt.Errorf("%q is not a valid Docker pull specification", component)
45
+	}
46
+	return
47
+}
48
+
49
+type ComponentReference interface {
50
+	Input() *ComponentInput
51
+	// Sets Input.Match or returns an error
52
+	Resolve() error
53
+	NeedsSource() bool
54
+}
55
+
56
+type ComponentReferences []ComponentReference
57
+
58
+func (r ComponentReferences) NeedsSource() (refs ComponentReferences) {
59
+	for _, ref := range r {
60
+		if ref.NeedsSource() {
61
+			refs = append(refs, ref)
62
+		}
63
+	}
64
+	return
65
+}
66
+
67
+type GroupedComponentReferences ComponentReferences
68
+
69
+func (m GroupedComponentReferences) Len() int      { return len(m) }
70
+func (m GroupedComponentReferences) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
71
+func (m GroupedComponentReferences) Less(i, j int) bool {
72
+	return m[i].Input().Group < m[j].Input().Group
73
+}
74
+
75
+func (r ComponentReferences) Group() (refs []ComponentReferences) {
76
+	sorted := make(GroupedComponentReferences, len(r))
77
+	copy(sorted, r)
78
+	sort.Sort(sorted)
79
+	group := -1
80
+	for _, ref := range sorted {
81
+		if ref.Input().Group != group {
82
+			refs = append(refs, ComponentReferences{})
83
+		}
84
+		group = ref.Input().Group
85
+		refs[len(refs)-1] = append(refs[len(refs)-1], ref)
86
+	}
87
+	return
88
+}
89
+
90
+type ComponentMatch struct {
91
+	Value       string
92
+	Argument    string
93
+	Name        string
94
+	Description string
95
+	Score       float32
96
+
97
+	Builder     bool
98
+	Image       *imageapi.DockerImage
99
+	ImageStream *imageapi.ImageRepository
100
+	ImageTag    string
101
+	Template    *templateapi.Template
102
+}
103
+
104
+func (m *ComponentMatch) String() string {
105
+	return m.Argument
106
+}
107
+
108
+type Resolver interface {
109
+	// resolvers should return ErrMultipleMatches when more than one result could
110
+	// be construed as a match. Resolvers should set the score to 0.0 if this is a
111
+	// perfect match, and to higher values the less adequate the match is.
112
+	Resolve(value string) (*ComponentMatch, error)
113
+}
114
+
115
+type ScoredComponentMatches []*ComponentMatch
116
+
117
+func (m ScoredComponentMatches) Len() int           { return len(m) }
118
+func (m ScoredComponentMatches) Swap(i, j int)      { m[i], m[j] = m[j], m[i] }
119
+func (m ScoredComponentMatches) Less(i, j int) bool { return m[i].Score < m[j].Score }
120
+
121
+type WeightedResolver struct {
122
+	Resolver
123
+	Weight float32
124
+}
125
+
126
+// PerfectMatchWeightedResolver returns only matches from resolvers that are identified as exact
127
+// (weight 0.0), and only matches from those resolvers that qualify as exact (score = 0.0). If no
128
+// perfect matches exist, an ErrMultipleMatches is returned indicating the remaining candidate(s).
129
+// Note that this metchod may resolve ErrMultipleMatches with a single match, indicating an error
130
+// (no perfect match) but with only one candidate.
131
+type PerfectMatchWeightedResolver []WeightedResolver
132
+
133
+func (r PerfectMatchWeightedResolver) Resolve(value string) (*ComponentMatch, error) {
134
+	match, err := WeightedResolvers(r).Resolve(value)
135
+	if err != nil {
136
+		if multiple, ok := err.(ErrMultipleMatches); ok {
137
+			sort.Sort(ScoredComponentMatches(multiple.matches))
138
+			if multiple.matches[0].Score == 0.0 && (len(multiple.matches) == 1 || multiple.matches[1].Score != 0.0) {
139
+				return multiple.matches[0], nil
140
+			}
141
+		}
142
+		return nil, err
143
+	}
144
+	if match.Score != 0.0 {
145
+		return nil, ErrMultipleMatches{value, []*ComponentMatch{match}}
146
+	}
147
+	return match, nil
148
+}
149
+
150
+type WeightedResolvers []WeightedResolver
151
+
152
+func (r WeightedResolvers) Resolve(value string) (*ComponentMatch, error) {
153
+	candidates := []*ComponentMatch{}
154
+	errs := []error{}
155
+	for _, resolver := range r {
156
+		match, err := resolver.Resolve(value)
157
+		if err != nil {
158
+			if multiple, ok := err.(ErrMultipleMatches); ok {
159
+				for _, match := range multiple.matches {
160
+					if resolver.Weight != 0.0 {
161
+						match.Score = match.Score * resolver.Weight
162
+					}
163
+					candidates = append(candidates, match)
164
+				}
165
+				continue
166
+			}
167
+			if _, ok := err.(ErrNoMatch); ok {
168
+				continue
169
+			}
170
+			errs = append(errs, err)
171
+			continue
172
+		}
173
+		candidates = append(candidates, match)
174
+	}
175
+	switch len(candidates) {
176
+	case 0:
177
+		return nil, ErrNoMatch{value: value}
178
+	case 1:
179
+		return candidates[0], nil
180
+	default:
181
+		return nil, ErrMultipleMatches{value, candidates}
182
+	}
183
+}
184
+
185
+type ReferenceBuilder struct {
186
+	refs  ComponentReferences
187
+	repos []*SourceRepository
188
+	errs  []error
189
+	group int
190
+}
191
+
192
+func (r *ReferenceBuilder) AddImages(inputs []string, fn func(*ComponentInput) ComponentReference) {
193
+	for _, s := range inputs {
194
+		for _, s := range strings.Split(s, "+") {
195
+			input, repo, err := NewComponentInput(s)
196
+			if err != nil {
197
+				r.errs = append(r.errs, err)
198
+				continue
199
+			}
200
+			input.Group = r.group
201
+			ref := fn(input)
202
+			if len(repo) != 0 {
203
+				repository, ok := r.AddSourceRepository(repo)
204
+				if !ok {
205
+					continue
206
+				}
207
+				input.Use(repository)
208
+				repository.UsedBy(ref)
209
+			}
210
+			r.refs = append(r.refs, ref)
211
+		}
212
+		r.group++
213
+	}
214
+}
215
+
216
+func (r *ReferenceBuilder) AddGroups(inputs []string) {
217
+	for _, s := range inputs {
218
+		groups := strings.Split(s, "+")
219
+		if len(groups) == 1 {
220
+			r.errs = append(r.errs, fmt.Errorf("group %q only contains a single name", s))
221
+			continue
222
+		}
223
+		to := -1
224
+		for _, group := range groups {
225
+			var match ComponentReference
226
+			for _, ref := range r.refs {
227
+				if group == ref.Input().Value {
228
+					match = ref
229
+					break
230
+				}
231
+			}
232
+			if match == nil {
233
+				r.errs = append(r.errs, fmt.Errorf("the name %q from the group definition is not in use, and can't be used", group))
234
+				break
235
+			}
236
+			if to == -1 {
237
+				to = match.Input().Group
238
+			} else {
239
+				match.Input().Group = to
240
+			}
241
+		}
242
+	}
243
+}
244
+
245
+func (r *ReferenceBuilder) AddSourceRepository(input string) (*SourceRepository, bool) {
246
+	for _, existing := range r.repos {
247
+		if input == existing.location {
248
+			return existing, true
249
+		}
250
+	}
251
+	source, err := NewSourceRepository(input)
252
+	if err != nil {
253
+		r.errs = append(r.errs, err)
254
+		return nil, false
255
+	}
256
+	r.repos = append(r.repos, source)
257
+	return source, true
258
+}
259
+
260
+func (r *ReferenceBuilder) Result() (ComponentReferences, []*SourceRepository, []error) {
261
+	return r.refs, r.repos, r.errs
262
+}
263
+
264
+func NewComponentInput(input string) (*ComponentInput, string, error) {
265
+	// check for image using [image]~ (to indicate builder) or [image]~[code] (builder plus code)
266
+	component, repo, builder, err := componentWithSource(input)
267
+	if err != nil {
268
+		return nil, "", err
269
+	}
270
+	return &ComponentInput{
271
+		From:          input,
272
+		Argument:      input,
273
+		Value:         component,
274
+		ExpectToBuild: builder,
275
+	}, repo, nil
276
+}
277
+
278
+type ComponentInput struct {
279
+	Group         int
280
+	From          string
281
+	Argument      string
282
+	Value         string
283
+	ExpectToBuild bool
284
+
285
+	Uses  *SourceRepository
286
+	Match *ComponentMatch
287
+
288
+	Resolver
289
+}
290
+
291
+func (i *ComponentInput) Input() *ComponentInput {
292
+	return i
293
+}
294
+
295
+func (i *ComponentInput) NeedsSource() bool {
296
+	return i.ExpectToBuild && i.Uses == nil
297
+}
298
+
299
+func (i *ComponentInput) Resolve() error {
300
+	if i.Resolver == nil {
301
+		return ErrNoMatch{value: i.Value, qualifier: "no resolver defined"}
302
+	}
303
+	match, err := i.Resolver.Resolve(i.Value)
304
+	if err != nil {
305
+		return err
306
+	}
307
+	i.Value = match.Value
308
+	i.Argument = match.Argument
309
+	i.Match = match
310
+
311
+	return nil
312
+}
313
+
314
+func (i *ComponentInput) String() string {
315
+	return i.Value
316
+}
317
+
318
+func (i *ComponentInput) Use(repo *SourceRepository) {
319
+	i.Uses = repo
320
+}
0 321
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+package app
1
+
2
+import (
3
+	"bytes"
4
+	"fmt"
5
+)
6
+
7
+type ErrNoMatch struct {
8
+	value     string
9
+	qualifier string
10
+}
11
+
12
+func (e ErrNoMatch) Error() string {
13
+	if len(e.qualifier) != 0 {
14
+		return fmt.Sprintf("no image matched %q: %s", e.value, e.qualifier)
15
+	}
16
+	return fmt.Sprintf("no image matched %q", e.value)
17
+}
18
+
19
+func (e ErrNoMatch) UsageError(commandName string) string {
20
+	return fmt.Sprintf(`
21
+%[3]s - you can try to search for images or templates that may match this name with:
22
+
23
+    $ %[2]s -S %[1]q
24
+
25
+`, e.value, commandName, e.Error())
26
+}
27
+
28
+type ErrMultipleMatches struct {
29
+	image   string
30
+	matches []*ComponentMatch
31
+}
32
+
33
+func (e ErrMultipleMatches) Error() string {
34
+	return fmt.Sprintf("multiple images matched %q: %d", e.image, len(e.matches))
35
+}
36
+
37
+func (e ErrMultipleMatches) UsageError(commandName string) string {
38
+	buf := &bytes.Buffer{}
39
+	for _, match := range e.matches {
40
+		fmt.Fprintf(buf, "* %[1]s (use %[2]s)\n", match.Name, match.Argument)
41
+		fmt.Fprintf(buf, "  %s\n\n", match.Description)
42
+	}
43
+	return fmt.Sprintf(`
44
+The argument %[1]q could apply to the following images or templates:
45
+
46
+%[2]s
47
+`, e.image, buf.String())
48
+}
0 49
new file mode 100644
... ...
@@ -0,0 +1,170 @@
0
+package app
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/fsouza/go-dockerclient"
6
+
7
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
9
+	"github.com/golang/glog"
10
+
11
+	"github.com/openshift/origin/pkg/client"
12
+	"github.com/openshift/origin/pkg/dockerregistry"
13
+	imageapi "github.com/openshift/origin/pkg/image/api"
14
+)
15
+
16
+type DockerClientResolver struct {
17
+	Client *docker.Client
18
+}
19
+
20
+func (r DockerClientResolver) Resolve(value string) (*ComponentMatch, error) {
21
+	image, err := r.Client.InspectImage(value)
22
+	switch {
23
+	case err == docker.ErrNoSuchImage:
24
+		return nil, ErrNoMatch{value: value}
25
+	case err != nil:
26
+		return nil, err
27
+	}
28
+	return &ComponentMatch{
29
+		Value:       value,
30
+		Argument:    fmt.Sprintf("--docker-image=%q", value),
31
+		Name:        value,
32
+		Description: fmt.Sprintf("Docker image %q by %s\n%s", value, image.Author, image.Comment),
33
+		Builder:     false,
34
+		Score:       0,
35
+	}, nil
36
+}
37
+
38
+type DockerRegistryResolver struct {
39
+	Client dockerregistry.Client
40
+}
41
+
42
+func (r DockerRegistryResolver) Resolve(value string) (*ComponentMatch, error) {
43
+	registry, namespace, name, tag, err := imageapi.SplitDockerPullSpec(value)
44
+	if err != nil {
45
+		return nil, err
46
+	}
47
+	connection, err := r.Client.Connect(registry)
48
+	if err != nil {
49
+		if dockerregistry.IsRegistryNotFound(err) {
50
+			return nil, ErrNoMatch{value: value}
51
+		}
52
+		return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("can't connect to %q: %v", registry, err)}
53
+	}
54
+	image, err := connection.ImageByTag(namespace, name, tag)
55
+	if err != nil {
56
+		if dockerregistry.IsNotFound(err) {
57
+			return nil, ErrNoMatch{value: value, qualifier: err.Error()}
58
+		}
59
+		return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("can't connect to %q: %v", registry, err)}
60
+	}
61
+	if len(tag) == 0 {
62
+		tag = "latest"
63
+	}
64
+	glog.V(4).Infof("found image: %#v", image)
65
+	dockerImage := &imageapi.DockerImage{}
66
+	if err = kapi.Scheme.Convert(image, dockerImage); err != nil {
67
+		return nil, err
68
+	}
69
+	return &ComponentMatch{
70
+		Value:       value,
71
+		Argument:    fmt.Sprintf("--docker-image=%q", value),
72
+		Name:        value,
73
+		Description: fmt.Sprintf("Docker image %q (%q)", value, image.ID),
74
+		Builder:     IsBuilderImage(dockerImage),
75
+		Score:       0,
76
+		Image:       dockerImage,
77
+		ImageTag:    tag,
78
+	}, nil
79
+}
80
+
81
+type ImageStreamResolver struct {
82
+	Client     client.ImageRepositoriesNamespacer
83
+	Images     client.ImagesNamespacer
84
+	Namespaces []string
85
+}
86
+
87
+func (r ImageStreamResolver) Resolve(value string) (*ComponentMatch, error) {
88
+	registry, namespace, name, tag, err := imageapi.SplitOpenShiftPullSpec(value)
89
+	if err != nil || len(registry) != 0 {
90
+		return nil, fmt.Errorf("image repositories must be of the form [<namespace>/]<name>[:<tag>]")
91
+	}
92
+	namespaces := r.Namespaces
93
+	if len(namespace) != 0 {
94
+		namespaces = []string{namespace}
95
+	}
96
+	for _, namespace := range namespaces {
97
+		glog.V(4).Infof("checking image stream %s/%s with tag %q", namespace, name, tag)
98
+		repo, err := r.Client.ImageRepositories(namespace).Get(name)
99
+		if err != nil {
100
+			if errors.IsNotFound(err) {
101
+				continue
102
+			}
103
+			return nil, err
104
+		}
105
+		searchTag := tag
106
+		// TODO: move to a lookup function on repo, or better yet, have the repo.Status.Tags field automatically infer latest
107
+		if len(searchTag) == 0 {
108
+			searchTag = "latest"
109
+		}
110
+		id, ok := repo.Tags[searchTag]
111
+		if !ok {
112
+			if len(tag) == 0 {
113
+				return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("the default tag %q has not been set", searchTag)}
114
+			}
115
+			return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("tag %q has not been set", tag)}
116
+		}
117
+		imageData, err := r.Images.Images(namespace).Get(id)
118
+		if err != nil {
119
+			if errors.IsNotFound(err) {
120
+				return nil, ErrNoMatch{value: value, qualifier: fmt.Sprintf("tag %q is set, but image %q has been removed", tag, id)}
121
+			}
122
+			return nil, err
123
+		}
124
+
125
+		spec := imageapi.JoinDockerPullSpec("", namespace, name, tag)
126
+		return &ComponentMatch{
127
+			Value:       spec,
128
+			Argument:    fmt.Sprintf("--image=%q", spec),
129
+			Name:        name,
130
+			Description: fmt.Sprintf("Image stream %s (tag %q) in namespace %s, tracks %q", name, searchTag, namespace, repo.Status.DockerImageRepository),
131
+			Builder:     IsBuilderImage(&imageData.DockerImageMetadata),
132
+			Score:       0,
133
+
134
+			ImageStream: repo,
135
+			Image:       &imageData.DockerImageMetadata,
136
+			ImageTag:    searchTag,
137
+		}, nil
138
+	}
139
+	return nil, ErrNoMatch{value: value}
140
+}
141
+
142
+type Searcher interface {
143
+	Search(terms []string) ([]*ComponentMatch, error)
144
+}
145
+
146
+func InputImageFromMatch(match *ComponentMatch) (*ImageRef, error) {
147
+	switch {
148
+	case match.ImageStream != nil:
149
+		input, err := ImageFromRepository(match.ImageStream, match.ImageTag)
150
+		if err != nil {
151
+			return nil, err
152
+		}
153
+		input.AsImageRepository = true
154
+		input.Info = match.Image
155
+		return input, nil
156
+
157
+	case match.Image != nil:
158
+		input, err := ImageFromName(match.Value, match.ImageTag)
159
+		if err != nil {
160
+			return nil, err
161
+		}
162
+		input.AsImageRepository = false
163
+		input.Info = match.Image
164
+		return input, nil
165
+
166
+	default:
167
+		return nil, fmt.Errorf("no image or image stream, can't setup a build")
168
+	}
169
+}
... ...
@@ -3,10 +3,12 @@ package app
3 3
 import (
4 4
 	"fmt"
5 5
 	"math/rand"
6
+	"regexp"
6 7
 	"strings"
7 8
 
8 9
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
9 10
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
11
+	kutil "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
10 12
 
11 13
 	deploy "github.com/openshift/origin/pkg/deploy/api"
12 14
 )
... ...
@@ -44,9 +46,11 @@ func NewBuildPipeline(from string, input *ImageRef, strategy *BuildStrategyRef,
44 44
 		Tag:  "latest",
45 45
 
46 46
 		AsImageRepository: true,
47
+	}
48
+	if input != nil {
47 49
 		// TODO: assumes that build doesn't change the image metadata. In the future
48 50
 		// we could get away with deferred generation possibly.
49
-		Info: input.Info,
51
+		output.Info = input.Info
50 52
 	}
51 53
 
52 54
 	build := &BuildRef{
... ...
@@ -136,25 +140,44 @@ func (g PipelineGroup) String() string {
136 136
 	return strings.Join(s, "+")
137 137
 }
138 138
 
139
+const MaxServiceNameLen = 24
140
+
141
+var InvalidServiceChars = regexp.MustCompile("[^-a-z0-9]")
142
+
143
+func makeValidServiceName(name string) string {
144
+	name = strings.ToLower(name)
145
+	name = InvalidServiceChars.ReplaceAllString(name, "")
146
+	if len(name) == 0 {
147
+		return fmt.Sprintf("svc-%d", rand.Intn(100000))
148
+	}
149
+	if len(name) > MaxServiceNameLen-5 {
150
+		name = name[:MaxServiceNameLen-5]
151
+	}
152
+	name = fmt.Sprintf("%s-%d", name, rand.Intn(9999))
153
+	if strings.HasPrefix(name, "-") {
154
+		name = "0" + name[1:]
155
+	}
156
+	return name
157
+}
158
+
139 159
 func AddServices(objects Objects) Objects {
140 160
 	svcs := []runtime.Object{}
141 161
 	for _, o := range objects {
142 162
 		switch t := o.(type) {
143 163
 		case *deploy.DeploymentConfig:
144
-			// TODO: expose all ports, or try to find the one that matches a given protocol
145 164
 			for _, container := range t.Template.ControllerTemplate.Template.Spec.Containers {
146 165
 				for _, port := range container.Ports {
147 166
 					p := port.ContainerPort
148 167
 					svcs = append(svcs, &kapi.Service{
149 168
 						ObjectMeta: kapi.ObjectMeta{
150
-							Name: t.Name,
169
+							Name: makeValidServiceName(t.Name),
151 170
 						},
152 171
 						Spec: kapi.ServiceSpec{
153
-							Port:     p,
154
-							Selector: t.Template.ControllerTemplate.Selector,
172
+							ContainerPort: kutil.NewIntOrStringFromInt(p),
173
+							Port:          p,
174
+							Selector:      t.Template.ControllerTemplate.Selector,
155 175
 						},
156 176
 					})
157
-					break
158 177
 				}
159 178
 				break
160 179
 			}
161 180
new file mode 100644
... ...
@@ -0,0 +1,184 @@
0
+package app
1
+
2
+import (
3
+	"fmt"
4
+	"net/url"
5
+	"os"
6
+	"path/filepath"
7
+	"regexp"
8
+	"strings"
9
+
10
+	"github.com/openshift/origin/pkg/generate/dockerfile"
11
+	"github.com/openshift/origin/pkg/generate/source"
12
+)
13
+
14
+var (
15
+	argumentGit         = regexp.MustCompile("^(http://|https://|git@|git://).*\\.git(?:#([a-zA-Z0-9]*))?$")
16
+	argumentGitProtocol = regexp.MustCompile("^git@")
17
+	argumentPath        = regexp.MustCompile("^\\.|^\\/[^/]+")
18
+)
19
+
20
+func IsPossibleSourceRepository(s string) bool {
21
+	return argumentGit.MatchString(s) || argumentGitProtocol.MatchString(s) || argumentPath.MatchString(s)
22
+}
23
+
24
+// SourceRepository represents an code repository that may be the target of a build.
25
+type SourceRepository struct {
26
+	location string
27
+	url      url.URL
28
+
29
+	usedBy          []ComponentReference
30
+	buildWithDocker bool
31
+}
32
+
33
+// NewSourceRepository creates a reference to a local or remote source code repository from
34
+// a URL or path.
35
+func NewSourceRepository(s string) (*SourceRepository, error) {
36
+	var location *url.URL
37
+	switch {
38
+	case strings.HasPrefix(s, "git@"):
39
+		base := "git://" + strings.TrimPrefix(s, "git@")
40
+		url, err := url.Parse(base)
41
+		if err != nil {
42
+			return nil, err
43
+		}
44
+		location = url
45
+
46
+	default:
47
+		uri, err := url.Parse(s)
48
+		if err != nil {
49
+			return nil, err
50
+		}
51
+
52
+		if uri.Scheme == "" {
53
+			path := s
54
+			ref := ""
55
+			segments := strings.SplitN(path, "#", 2)
56
+			if len(segments) == 2 {
57
+				path, ref = segments[0], segments[1]
58
+			}
59
+			path, err := filepath.Abs(path)
60
+			if err != nil {
61
+				return nil, err
62
+			}
63
+			uri = &url.URL{
64
+				Scheme:   "file",
65
+				Path:     path,
66
+				Fragment: ref,
67
+			}
68
+		}
69
+
70
+		location = uri
71
+	}
72
+	return &SourceRepository{
73
+		location: s,
74
+		url:      *location,
75
+	}, nil
76
+}
77
+
78
+func (r *SourceRepository) UsedBy(ref ComponentReference) {
79
+	r.usedBy = append(r.usedBy, ref)
80
+}
81
+
82
+func (r *SourceRepository) Remote() bool {
83
+	return r.url.Scheme != "file"
84
+}
85
+
86
+func (r *SourceRepository) InUse() bool {
87
+	return len(r.usedBy) > 0
88
+}
89
+
90
+func (r *SourceRepository) BuildWithDocker() {
91
+	r.buildWithDocker = true
92
+}
93
+
94
+func (r *SourceRepository) IsDockerBuild() bool {
95
+	return r.buildWithDocker
96
+}
97
+
98
+func (r *SourceRepository) String() string {
99
+	return r.location
100
+}
101
+
102
+func (r *SourceRepository) LocalPath() (string, error) {
103
+	switch {
104
+	case r.url.Scheme == "file":
105
+		return r.url.Path, nil
106
+		// TODO: implement other types
107
+		// TODO: lazy cache (predictably?)
108
+	default:
109
+
110
+		return "", fmt.Errorf("reading local repositories is not implemented: %q", r.location)
111
+	}
112
+}
113
+
114
+type SourceRepositoryInfo struct {
115
+	Path       string
116
+	Types      []SourceLanguageType
117
+	Dockerfile dockerfile.Dockerfile
118
+}
119
+
120
+func (info *SourceRepositoryInfo) Terms() []string {
121
+	terms := []string{}
122
+	for i := range info.Types {
123
+		terms = append(terms, info.Types[i].Platform)
124
+	}
125
+	return terms
126
+}
127
+
128
+type SourceLanguageType struct {
129
+	Platform string
130
+	Version  string
131
+}
132
+
133
+type Detector interface {
134
+	Detect(dir string) (*SourceRepositoryInfo, error)
135
+}
136
+
137
+type SourceRepositoryEnumerator struct {
138
+	Detectors source.Detectors
139
+	Tester    dockerfile.Tester
140
+}
141
+
142
+func (e SourceRepositoryEnumerator) Detect(dir string) (*SourceRepositoryInfo, error) {
143
+	info := &SourceRepositoryInfo{
144
+		Path: dir,
145
+	}
146
+	for _, d := range e.Detectors {
147
+		if detected, ok := d(dir); ok {
148
+			info.Types = append(info.Types, SourceLanguageType{
149
+				Platform: detected.Platform,
150
+				Version:  detected.Version,
151
+			})
152
+		}
153
+	}
154
+	if path, ok, err := e.Tester.Has(dir); err == nil && ok {
155
+		file, err := os.Open(path)
156
+		if err != nil {
157
+			return nil, err
158
+		}
159
+		defer file.Close()
160
+		dockerfile, err := dockerfile.NewParser().Parse(file)
161
+		if err != nil {
162
+			return nil, err
163
+		}
164
+		info.Dockerfile = dockerfile
165
+	}
166
+	return info, nil
167
+}
168
+
169
+func StrategyAndSourceForRepository(repo *SourceRepository) (*BuildStrategyRef, *SourceRef, error) {
170
+	// TODO: replace with repository origin lookup, then in the future replace with auto push repository to server
171
+	if !repo.Remote() {
172
+		return nil, nil, fmt.Errorf("the repository %q can't be used, as the CLI does not yet support pushing a local repository from your filesystem to OpenShift", repo)
173
+	}
174
+	strategy := &BuildStrategyRef{
175
+		IsDockerBuild: repo.IsDockerBuild(),
176
+		DockerContext: "",
177
+	}
178
+	source := &SourceRef{
179
+		URL: &repo.url,
180
+		Ref: repo.url.Fragment,
181
+	}
182
+	return strategy, source, nil
183
+}
... ...
@@ -46,6 +46,8 @@ func (_ *parser) Parse(input io.Reader) (Dockerfile, error) {
46 46
 	return d, nil
47 47
 }
48 48
 
49
+// GetDirective returns a list of lines that begin with the given directive
50
+// and a flag that is true if the directive was found in the Dockerfile
49 51
 func (d dockerfile) GetDirective(s string) ([]string, bool) {
50 52
 	values := []string{}
51 53
 	s = strings.ToLower(s)
... ...
@@ -20,19 +20,19 @@ const (
20 20
 func (e GenerationError) Error() string {
21 21
 	switch e {
22 22
 	case NoGit:
23
-		return "Git was not detected in your system. It is needed for build config generation."
23
+		return "git was not detected in your system. It is needed for build config generation."
24 24
 	case SourceDirAndURL:
25
-		return "A source directory and a source URL were specified. Please only specify one."
25
+		return "a source directory and a source URL were specified. Please only specify one."
26 26
 	case InvalidSourceDir:
27
-		return "The source directory is not readable or is invalid."
27
+		return "the source directory is not readable or is invalid."
28 28
 	case CouldNotDetect:
29
-		return "Could not detect a build type from the source."
29
+		return "could not detect a build type from the source."
30 30
 	case NoBuilderFound:
31
-		return "Could not find a builder to match the STI source repository."
31
+		return "could not find a builder to match the STI source repository."
32 32
 	case InvalidDockerfile:
33
-		return "Invalid Dockerfile. Does not contain a FROM clause."
33
+		return "invalid Dockerfile. Does not contain a FROM clause."
34 34
 	case ImageNotFound:
35
-		return "Image data could not be found."
35
+		return "image data could not be found."
36 36
 	}
37 37
 	return ""
38 38
 }
... ...
@@ -47,13 +47,13 @@ func NewMultipleDockerfilesErr(paths []string) error {
47 47
 type multipleDockerFilesError []string
48 48
 
49 49
 func (e multipleDockerFilesError) Error() string {
50
-	result := "Error: Multiple Dockerfile(s) found.\nSpecify one of the following flags:\n"
50
+	result := "multiple Dockerfile(s) found.\nSpecify one of the following flags:\n"
51 51
 	for _, f := range e {
52 52
 		dir := filepath.Dir(f)
53 53
 		if dir == "" {
54 54
 			dir = "."
55 55
 		}
56
-		result += "--context=\"" + dir + "\""
56
+		result += "--docker-context=\"" + dir + "\""
57 57
 		result += "\n"
58 58
 	}
59 59
 	return result
60 60
deleted file mode 100644
... ...
@@ -1,44 +0,0 @@
1
-package generator
2
-
3
-import (
4
-	"github.com/openshift/origin/pkg/generate/app"
5
-	"github.com/openshift/origin/pkg/generate/imageinfo"
6
-)
7
-
8
-// Generators for ImageInfo
9
-// - ImageRef       -> ImageInfo
10
-
11
-// NewImageInfoGenerator creates a new ImageInfoGenerator
12
-func NewImageInfoGenerator(retriever imageinfo.Retriever) *ImageInfoGenerator {
13
-	return &ImageInfoGenerator{
14
-		retriever: retriever,
15
-	}
16
-}
17
-
18
-// ImageInfoGenerator generates ImageInfo objects from ImageRef
19
-type ImageInfoGenerator struct {
20
-	retriever imageinfo.Retriever
21
-}
22
-
23
-// FromImageRef generates an ImageInfo from an ImageRef
24
-func (g *ImageInfoGenerator) FromImageRef(imageRef app.ImageRef) *app.ImageRef {
25
-	info, err := g.retriever.Retrieve(imageRef.NameReference())
26
-	if err != nil {
27
-		// If image info could not be retrieved, return a simple image info
28
-		// without the additional image metadata
29
-		return &imageRef
30
-	}
31
-	imageRef.Info = info
32
-	return &imageRef
33
-}
34
-
35
-// FromImageRefs generates an array of ImageInfo from an array of ImageRef
36
-func (g *ImageInfoGenerator) FromImageRefs(imageRefs []app.ImageRef) []*app.ImageRef {
37
-	result := []*app.ImageRef{}
38
-	for _, ir := range imageRefs {
39
-		info := g.FromImageRef(ir)
40
-		result = append(result, info)
41
-	}
42
-	return result
43
-
44
-}
45 1
deleted file mode 100644
... ...
@@ -1,50 +0,0 @@
1
-package generator
2
-
3
-import (
4
-	"testing"
5
-
6
-	"github.com/fsouza/go-dockerclient"
7
-
8
-	"github.com/openshift/origin/pkg/generate/app"
9
-)
10
-
11
-func TestFromImageRef(t *testing.T) {
12
-	img := &docker.Image{
13
-		ID: "test/image",
14
-	}
15
-	g := NewImageInfoGenerator(&fakeRetriever{img})
16
-
17
-	imageRef := app.ImageRef{Namespace: "test", Name: "image"}
18
-	imageInfo := g.FromImageRef(imageRef)
19
-	if imageInfo.Info != img {
20
-		t.Errorf("Unexpected image info returned.")
21
-	}
22
-}
23
-
24
-func TestFromImageRefs(t *testing.T) {
25
-	img := &docker.Image{
26
-		ID: "test/image",
27
-	}
28
-	g := NewImageInfoGenerator(&fakeRetriever{img})
29
-
30
-	imageRefs := []app.ImageRef{
31
-		{Namespace: "test", Name: "image1"},
32
-		{Namespace: "test", Name: "image2"},
33
-		{Namespace: "test", Name: "image3"},
34
-	}
35
-	imageInfos := g.FromImageRefs(imageRefs)
36
-	if len(imageInfos) != 3 {
37
-		t.Errorf("Unexpected number of imagerefs returned")
38
-	}
39
-	if imageInfos[0].Info != img {
40
-		t.Errorf("Unexpected image info returned.")
41
-	}
42
-}
43
-
44
-type fakeRetriever struct {
45
-	image *docker.Image
46
-}
47
-
48
-func (r *fakeRetriever) Retrieve(name string) (*docker.Image, error) {
49
-	return r.image, nil
50
-}
... ...
@@ -2,9 +2,13 @@ package generator
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"os"
6
+	"path/filepath"
7
+	"strings"
5 8
 
6 9
 	"github.com/openshift/origin/pkg/generate/app"
7
-	image "github.com/openshift/origin/pkg/image/api"
10
+	"github.com/openshift/origin/pkg/generate/dockerfile"
11
+	imageapi "github.com/openshift/origin/pkg/image/api"
8 12
 )
9 13
 
10 14
 // Generators for ImageRef
... ...
@@ -14,38 +18,89 @@ import (
14 14
 // ImageRefGenerator generates ImageRefs
15 15
 type ImageRefGenerator interface {
16 16
 	FromName(name string) (*app.ImageRef, error)
17
-	FromRepository(repo *image.ImageRepository, tag string) (*app.ImageRef, error)
17
+	FromNameAndPorts(name string, ports []string) (*app.ImageRef, error)
18
+	FromRepository(repo *imageapi.ImageRepository, tag string) (*app.ImageRef, error)
19
+	FromDockerfile(name string, dir string, context string) (*app.ImageRef, error)
18 20
 }
19 21
 
20
-type imageRefGenerator struct{}
22
+type imageRefGenerator struct {
23
+	dockerParser dockerfile.Parser
24
+}
21 25
 
22 26
 // NewImageRefGenerator creates a new ImageRefGenerator
23 27
 func NewImageRefGenerator() ImageRefGenerator {
24
-	return &imageRefGenerator{}
28
+	return &imageRefGenerator{
29
+		dockerParser: dockerfile.NewParser(),
30
+	}
25 31
 }
26 32
 
27 33
 // FromName generates an ImageRef from a given name
28 34
 func (g *imageRefGenerator) FromName(name string) (*app.ImageRef, error) {
29
-	registry, namespace, name, tag, err := image.SplitDockerPullSpec(name)
35
+	registry, namespace, name, tag, err := imageapi.SplitDockerPullSpec(name)
30 36
 	if err != nil {
31 37
 		return nil, err
32 38
 	}
33 39
 	return &app.ImageRef{
34
-		Registry:  registry,
35
-		Namespace: namespace,
36
-		Name:      name,
37
-		Tag:       tag,
40
+		Registry:          registry,
41
+		Namespace:         namespace,
42
+		Name:              name,
43
+		Tag:               tag,
44
+		AsImageRepository: true,
38 45
 	}, nil
39 46
 }
40 47
 
48
+func (g *imageRefGenerator) FromNameAndPorts(name string, ports []string) (*app.ImageRef, error) {
49
+	present := struct{}{}
50
+	imageRef, err := g.FromName(name)
51
+	if err != nil {
52
+		return nil, err
53
+	}
54
+	exposedPorts := map[string]struct{}{}
55
+
56
+	for _, p := range ports {
57
+		exposedPorts[p] = present
58
+	}
59
+
60
+	imageRef.Info = &imageapi.DockerImage{
61
+		Config: imageapi.DockerConfig{
62
+			ExposedPorts: exposedPorts,
63
+		},
64
+	}
65
+	return imageRef, nil
66
+}
67
+
68
+func (g *imageRefGenerator) FromDockerfile(name string, dir string, context string) (*app.ImageRef, error) {
69
+	// Look for Dockerfile in repository
70
+	file, err := os.Open(filepath.Join(dir, context, "Dockerfile"))
71
+	if err != nil {
72
+		return nil, err
73
+	}
74
+
75
+	dockerFile, err := g.dockerParser.Parse(file)
76
+	if err != nil {
77
+		return nil, err
78
+	}
79
+
80
+	expose, ok := dockerFile.GetDirective("EXPOSE")
81
+	if !ok {
82
+		return nil, err
83
+	}
84
+	ports := []string{}
85
+	for _, e := range expose {
86
+		ps := strings.Split(e, " ")
87
+		ports = append(ports, ps...)
88
+	}
89
+	return g.FromNameAndPorts(name, ports)
90
+}
91
+
41 92
 // FromRepository generates an ImageRef from an OpenShift ImageRepository
42
-func (g *imageRefGenerator) FromRepository(repo *image.ImageRepository, tag string) (*app.ImageRef, error) {
93
+func (g *imageRefGenerator) FromRepository(repo *imageapi.ImageRepository, tag string) (*app.ImageRef, error) {
43 94
 	pullSpec := repo.Status.DockerImageRepository
44 95
 	if len(pullSpec) == 0 {
45 96
 		// need to know the default OpenShift registry
46 97
 		return nil, fmt.Errorf("the repository does not resolve to a pullable Docker repository")
47 98
 	}
48
-	registry, namespace, name, repoTag, err := image.SplitDockerPullSpec(pullSpec)
99
+	registry, namespace, name, repoTag, err := imageapi.SplitDockerPullSpec(pullSpec)
49 100
 	if err != nil {
50 101
 		return nil, err
51 102
 	}
52 103
deleted file mode 100644
... ...
@@ -1,2 +0,0 @@
1
-// Package imageinfo contains retrievers for Docker image information (from OpenShift master, Docker registry, or local Docker)
2
-package imageinfo
3 1
deleted file mode 100644
... ...
@@ -1,73 +0,0 @@
1
-package imageinfo
2
-
3
-import (
4
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
5
-	"github.com/fsouza/go-dockerclient"
6
-
7
-	"github.com/openshift/origin/pkg/generate/errors"
8
-	image "github.com/openshift/origin/pkg/image/api"
9
-)
10
-
11
-type Retriever interface {
12
-	Retrieve(name string) (*docker.Image, error)
13
-}
14
-
15
-type retriever struct {
16
-	streamLister imageStreamLister
17
-	imageGetter  imageGetter
18
-	dockerClient dockerClient
19
-}
20
-
21
-type dockerClient interface {
22
-	InspectImage(name string) (*docker.Image, error)
23
-}
24
-
25
-type imageStreamLister interface {
26
-	List(label, field labels.Selector) (*image.ImageRepositoryList, error)
27
-}
28
-
29
-type imageGetter interface {
30
-	Get(name string) (*image.Image, error)
31
-}
32
-
33
-func NewRetriever(streamLister imageStreamLister, imageGetter imageGetter, dockerClient dockerClient) Retriever {
34
-	return &retriever{
35
-		streamLister: streamLister,
36
-		imageGetter:  imageGetter,
37
-		dockerClient: dockerClient,
38
-	}
39
-}
40
-
41
-func (r *retriever) Retrieve(name string) (*docker.Image, error) {
42
-	// TODO: implement a way to discover images on the server via REST client
43
-	// Try finding the image on the openshift server first
44
-	if r.streamLister != nil {
45
-		streamList, err := r.streamLister.List(labels.Everything(), labels.Everything())
46
-		if err == nil {
47
-			for _, imageStream := range streamList.Items {
48
-				_, ns, nm, _, err := image.SplitDockerPullSpec(imageStream.DockerImageRepository)
49
-				if err != nil {
50
-					continue
51
-				}
52
-				if name == ns+"/"+nm {
53
-					for _, imageName := range imageStream.Tags {
54
-						img, err := r.imageGetter.Get(imageName)
55
-						if err != nil {
56
-							continue
57
-						}
58
-						return &img.DockerImageMetadata, nil
59
-					}
60
-				}
61
-			}
62
-		}
63
-	}
64
-
65
-	// TODO: Use the Docker registry API to retrieve image information if possible
66
-
67
-	// If that doesn't work try Docker if present
68
-	if r.dockerClient != nil {
69
-		return r.dockerClient.InspectImage(name)
70
-	}
71
-
72
-	return nil, errors.ImageNotFound
73
-}
74 1
deleted file mode 100644
... ...
@@ -1,81 +0,0 @@
1
-package imageinfo
2
-
3
-import (
4
-	"fmt"
5
-	"reflect"
6
-	"testing"
7
-
8
-	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
9
-	"github.com/fsouza/go-dockerclient"
10
-
11
-	image "github.com/openshift/origin/pkg/image/api"
12
-)
13
-
14
-func TestRetrieveFromOpenShift(t *testing.T) {
15
-	imageMetadata := docker.Image{
16
-		ID:   "image2",
17
-		Size: 2048,
18
-	}
19
-	client := fakeOSClient{
20
-		repositories: []image.ImageRepository{
21
-			{
22
-				DockerImageRepository: "test/repository1",
23
-			},
24
-			{
25
-				DockerImageRepository: "test/repository2",
26
-				Tags: map[string]string{
27
-					"a_test_image_tag": "image1",
28
-					"a_second_tag":     "image2",
29
-				},
30
-			},
31
-		},
32
-		images: map[string]image.Image{
33
-			"image2": {
34
-				DockerImageMetadata: imageMetadata,
35
-			},
36
-		},
37
-	}
38
-	r := NewRetriever(&client, &client, &fakeDockerClient{})
39
-	result, err := r.Retrieve("test/repository2")
40
-	if err != nil {
41
-		t.Errorf("Unexpected error: %v", err)
42
-	}
43
-	if !reflect.DeepEqual(*result, imageMetadata) {
44
-		t.Errorf("Unexpected result: %#v", result)
45
-	}
46
-}
47
-
48
-func TestRetrieveFromLocalDocker(t *testing.T) {
49
-
50
-}
51
-
52
-type fakeOSClient struct {
53
-	repositories []image.ImageRepository
54
-	images       map[string]image.Image
55
-}
56
-
57
-func (c *fakeOSClient) List(label, field labels.Selector) (*image.ImageRepositoryList, error) {
58
-	return &image.ImageRepositoryList{
59
-		Items: c.repositories,
60
-	}, nil
61
-}
62
-
63
-func (c *fakeOSClient) Get(name string) (*image.Image, error) {
64
-	img, ok := c.images[name]
65
-	if ok {
66
-		return &img, nil
67
-	}
68
-	return nil, fmt.Errorf("Not found")
69
-}
70
-
71
-type fakeDockerClient struct {
72
-	images map[string]docker.Image
73
-}
74
-
75
-func (d *fakeDockerClient) InspectImage(name string) (*docker.Image, error) {
76
-	img, ok := d.images[name]
77
-	if ok {
78
-		return &img, nil
79
-	}
80
-	return nil, fmt.Errorf("Not found")
81
-}
... ...
@@ -5,19 +5,21 @@ import (
5 5
 	"path/filepath"
6 6
 )
7 7
 
8
+// Info is detected platform information from a source directory
8 9
 type Info struct {
9 10
 	Platform string
10 11
 	Version  string
11 12
 }
12 13
 
13
-type DetectSource interface {
14
-	DetectSource(dir string) (*Info, bool)
15
-}
16
-
14
+// DetectorFunc is a function that returns source Info from a given directory.
15
+// It returns true if it was able to detect the code in the given directory.
17 16
 type DetectorFunc func(dir string) (*Info, bool)
18 17
 
18
+// Detectors is a set of DetectorFunc that is used to detect the
19
+// language/platform for a given source directory
19 20
 type Detectors []DetectorFunc
20 21
 
22
+// DefafultDetectors is a default set of Detector functions
21 23
 var DefaultDetectors = Detectors{
22 24
 	DetectRuby,
23 25
 	DetectJava,
... ...
@@ -28,6 +30,8 @@ type sourceDetector struct {
28 28
 	detectors []DetectorFunc
29 29
 }
30 30
 
31
+// DetectSource returns source information from a given directory using
32
+// a set of Detectors
31 33
 func (s Detectors) DetectSource(dir string) (*Info, bool) {
32 34
 	for _, d := range s {
33 35
 		if info, found := d(dir); found {
... ...
@@ -37,8 +41,9 @@ func (s Detectors) DetectSource(dir string) (*Info, bool) {
37 37
 	return nil, false
38 38
 }
39 39
 
40
+// DetectRuby detects whether the source code in the given repository is Ruby
40 41
 func DetectRuby(dir string) (*Info, bool) {
41
-	if filesPresent(dir, []string{"Gemfile", "Rakefile"}) {
42
+	if filesPresent(dir, []string{"Gemfile", "Rakefile", "config.ru"}) {
42 43
 		return &Info{
43 44
 			Platform: "Ruby",
44 45
 		}, true
... ...
@@ -46,6 +51,7 @@ func DetectRuby(dir string) (*Info, bool) {
46 46
 	return nil, false
47 47
 }
48 48
 
49
+// DetectJava detects whether the source code in the given repository is Java
49 50
 func DetectJava(dir string) (*Info, bool) {
50 51
 	if filesPresent(dir, []string{"pom.xml"}) {
51 52
 		return &Info{
... ...
@@ -55,8 +61,9 @@ func DetectJava(dir string) (*Info, bool) {
55 55
 	return nil, false
56 56
 }
57 57
 
58
+// DetectNodeJS detects whether the source code in the given repository is NodeJS
58 59
 func DetectNodeJS(dir string) (*Info, bool) {
59
-	if filesPresent(dir, []string{"config.json"}) {
60
+	if filesPresent(dir, []string{"config.json", "package.json"}) {
60 61
 		return &Info{
61 62
 			Platform: "NodeJS",
62 63
 		}, true
... ...
@@ -1,9 +1,46 @@
1 1
 package source
2 2
 
3 3
 import (
4
+	"strings"
4 5
 	"testing"
5 6
 )
6 7
 
7 8
 func TestDetectSource(t *testing.T) {
9
+	d := Detectors{fake1, fake2}
10
+	i, ok := d.DetectSource("test_fake1")
11
+	if !ok {
12
+		t.Errorf("Unable to detect source for test_fake1")
13
+	}
14
+	if i.Platform != "fake1" {
15
+		t.Errorf("Invalid platform for test_fake1")
16
+	}
17
+	if i, ok = d.DetectSource("test_fake3"); ok {
18
+		t.Errorf("Detected source for invalid dir: test_fake3")
19
+	}
20
+	i, ok = d.DetectSource("test_fake2")
21
+	if !ok {
22
+		t.Errorf("Unable to detect source for test_fake2")
23
+	}
24
+	if i.Platform != "fake2" {
25
+		t.Errorf("Invalid platform for test_fake2")
26
+	}
27
+}
28
+
29
+func fake1(dir string) (*Info, bool) {
30
+	if strings.Contains(dir, "fake1") {
31
+		return &Info{
32
+			Platform: "fake1",
33
+		}, true
34
+	}
35
+	return nil, false
36
+}
37
+
38
+func fake2(dir string) (*Info, bool) {
39
+	if strings.Contains(dir, "fake2") {
40
+		return &Info{
41
+			Platform: "fake2",
42
+		}, true
43
+	}
44
+	return nil, false
8 45
 
9 46
 }
10 47
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+package source
1
+
2
+import (
3
+	"regexp"
4
+)
5
+
6
+var (
7
+	argumentGit         = regexp.MustCompile("^(http://|https://|git@|git://).*\\.git(?:#([a-zA-Z0-9]*))?$")
8
+	argumentGitProtocol = regexp.MustCompile("^git@")
9
+)
10
+
11
+func IsRemoteRepository(s string) bool {
12
+	return argumentGit.MatchString(s) || argumentGitProtocol.MatchString(s)
13
+}