Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable new-app #904

Merged
merged 2 commits into from
Feb 5, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/cmd/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func NewCommandCLI(name, fullName string) *cobra.Command {
cmds.AddCommand(f.NewCmdProxy(out))

// Origin commands
// cmds.AddCommand(cmd.NewCmdNewApplication(f, out))
cmds.AddCommand(cmd.NewCmdNewApplication(f, out))
cmds.AddCommand(cmd.NewCmdProcess(f, out))

// Origin build commands
Expand Down
102 changes: 86 additions & 16 deletions pkg/cmd/cli/cmd/newapp.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package cmd

import (
"fmt"
"io"
"os"

kcmd "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
"github.com/golang/glog"
"github.com/spf13/cobra"
Expand All @@ -18,54 +22,117 @@ type usage interface {
const longNewAppDescription = `
Create a new application in OpenShift by specifying source code, templates, and/or images.

This command will try to build up the components of an application using images or code
located on your system. It will lookup the images on the local Docker installation (if
available), a Docker registry, or an OpenShift image repository. If you specify a source
code URL, it will set up a build that takes your source code and converts it into an
image that can run inside of a pod. The images will be deployed via a deployment
configuration, and a service will be hookup up to the first public port of the app.

Examples:
$ osc new-app .
<try to create an application based on the source code in the current directory>

$ osc new-app mysql
<use the public DockerHub MySQL image to create an app>

$ osc new-app .
<try to create an application based on the source code in the current directory>
$ osc new-app myregistry.com/mycompany/mysql
<use a MySQL image in a private registry to create an app>

$ osc new-app mysql
<use the public DockerHub MySQL image to create an app>
$ osc new-app openshift/[email protected]/mfojtik/sinatra-app-example
<build an application using the OpenShift Ruby DockerHub image and an example repo>

$ osc new-app myregistry.com/mycompany/mysql
<use a MySQL image in a private registry to create an app>
If you specify source code, you may need to run a build with 'start-build' after the
application is created.

$ osc new-app openshift/[email protected]/mfojtik/sinatra-app-example
<build an application using the OpenShift Ruby DockerHub image and an example repo>`
ALPHA: This command is under active development - feedback is appreciated.
`

func NewCmdNewApplication(f *Factory, out io.Writer) *cobra.Command {
config := newcmd.NewAppConfig()

helper := dockerutil.NewHelper()
cmd := &cobra.Command{
Use: "new-app <components> [--code=<path|url>]",
Short: "Create a new application",
Long: longNewAppDescription,

Run: func(c *cobra.Command, args []string) {
namespace, err := f.DefaultNamespace(c)
checkErr(err)

if dockerClient, _, err := helper.GetClient(); err == nil {
config.SetDockerClient(dockerClient)
if err := dockerClient.Ping(); err == nil {
config.SetDockerClient(dockerClient)
} else {
glog.V(2).Infof("No local Docker daemon detected: %v", err)
}
}
if osclient, _, err := f.Clients(c); err == nil {
namespace, err := f.DefaultNamespace(c)
checkErr(err)
config.SetOpenShiftClient(osclient, namespace)
} else {
glog.Warningf("error getting client: %v", err)

osclient, _, err := f.Clients(c)
if err != nil {
glog.Fatalf("Error getting client: %v", err)
}
config.SetOpenShiftClient(osclient, namespace)

unknown := config.AddArguments(args)
if len(unknown) != 0 {
glog.Fatalf("Did not recognize the following arguments: %v", unknown)
}
if err := config.Run(out, c.Help); err != nil {

obj, err := config.Run(out)
if err != nil {
if errs, ok := err.(errors.Aggregate); ok {
if len(errs.Errors()) == 1 {
err = errs.Errors()[0]
}
}
if err == newcmd.ErrNoInputs {
// TODO: suggest things to the user
glog.Fatal("You must specify one or more images, image repositories, or source code locations to create an application.")
}
if u, ok := err.(usage); ok {
glog.Fatal(u.UsageError(c.CommandPath()))
}
glog.Fatalf("Error: %v", err)
}

if len(kcmd.GetFlagString(c, "output")) != 0 {
if err := kcmd.PrintObject(c, obj.List, f.Factory, out); err != nil {
glog.Fatalf("Error: %v", err)
}
return
}

mapper, typer := f.Object(c)
resourceMapper := &resource.Mapper{typer, mapper, kcmd.ClientMapperForCommand(c, f.Factory)}
errs := []error{}
for _, item := range obj.List.Items {
info, err := resourceMapper.InfoForObject(item)
if err != nil {
errs = append(errs, err)
continue
}
data, err := info.Mapping.Codec.Encode(item)
if err != nil {
errs = append(errs, err)
glog.Error(err)
continue
}
if err := resource.NewHelper(info.Client, info.Mapping).Create(namespace, false, data); err != nil {
errs = append(errs, err)
glog.Error(err)
continue
}
fmt.Fprintf(out, "%s\n", info.Name)
}
if len(errs) != 0 {
os.Exit(1)
}

for _, s := range obj.BuildNames {
fmt.Fprintf(os.Stderr, "A build was created - run `osc start-build %s` to start it\n", s)
}
},
}

Expand All @@ -75,5 +142,8 @@ func NewCmdNewApplication(f *Factory, out io.Writer) *cobra.Command {
cmd.Flags().Var(&config.Groups, "group", "Indicate components that should be grouped together as <comp1>+<comp2>.")
cmd.Flags().VarP(&config.Environment, "env", "e", "Specify key value pairs of environment variables to set into each container.")
cmd.Flags().StringVar(&config.TypeOfBuild, "build", "", "Specify the type of build to use if you don't want to detect (docker|source)")

kcmd.AddPrinterFlags(cmd)

return cmd
}
2 changes: 1 addition & 1 deletion pkg/cmd/experimental/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func newImageResolver(namespace string, osClient osclient.Interface, dockerClien
resolver := genapp.PerfectMatchWeightedResolver{}

if dockerClient != nil {
localDockerResolver := &genapp.DockerClientResolver{dockerClient}
localDockerResolver := &genapp.DockerClientResolver{Client: dockerClient}
resolver = append(resolver, genapp.WeightedResolver{localDockerResolver, 0.0})
}

Expand Down
6 changes: 1 addition & 5 deletions pkg/cmd/openshift/openshift.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,7 @@ OpenShift is built around Docker and the Kubernetes cluster container manager.
Docker installed on this machine to start your server.

Note: This is an alpha release of OpenShift and will change significantly. See

https://github.com/openshift/origin

for the latest information on OpenShift.

https://github.com/openshift/origin for the latest information on OpenShift.
`

// CommandFor returns the appropriate command for this base name,
Expand Down
66 changes: 41 additions & 25 deletions pkg/generate/app/cmd/newapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import (
"strings"

kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
"github.com/fsouza/go-dockerclient"
"github.com/golang/glog"

buildapi "github.com/openshift/origin/pkg/build/api"
"github.com/openshift/origin/pkg/client"
cmdutil "github.com/openshift/origin/pkg/cmd/util"
"github.com/openshift/origin/pkg/dockerregistry"
Expand All @@ -31,9 +31,8 @@ type AppConfig struct {

TypeOfBuild string

localDockerResolver app.Resolver
dockerRegistryResolver app.Resolver
imageStreamResolver app.Resolver
dockerResolver app.Resolver
imageStreamResolver app.Resolver

searcher app.Searcher
detector app.Detector
Expand All @@ -50,17 +49,20 @@ type errlist interface {

func NewAppConfig() *AppConfig {
return &AppConfig{
searcher: &mockSearcher{},
detector: app.SourceRepositoryEnumerator{
Detectors: source.DefaultDetectors,
Tester: dockerfile.NewTester(),
},
dockerRegistryResolver: app.DockerRegistryResolver{dockerregistry.NewClient()},
dockerResolver: app.DockerRegistryResolver{dockerregistry.NewClient()},
}
}

func (c *AppConfig) SetDockerClient(dockerclient *docker.Client) {
c.localDockerResolver = app.DockerClientResolver{dockerclient}
c.dockerResolver = app.DockerClientResolver{
Client: dockerclient,

RegistryResolver: c.dockerResolver,
}
}

func (c *AppConfig) SetOpenShiftClient(osclient client.Interface, originNamespace string) {
Expand Down Expand Up @@ -100,7 +102,7 @@ func (c *AppConfig) validate() (app.ComponentReferences, []*app.SourceRepository
}
b.AddImages(c.DockerImages, func(input *app.ComponentInput) app.ComponentReference {
input.Argument = fmt.Sprintf("--docker-image=%q", input.From)
input.Resolver = c.dockerRegistryResolver
input.Resolver = c.dockerResolver
return input
})
b.AddImages(c.ImageStreams, func(input *app.ComponentInput) app.ComponentReference {
Expand All @@ -111,8 +113,7 @@ func (c *AppConfig) validate() (app.ComponentReferences, []*app.SourceRepository
b.AddImages(c.Components, func(input *app.ComponentInput) app.ComponentReference {
input.Resolver = app.PerfectMatchWeightedResolver{
app.WeightedResolver{Resolver: c.imageStreamResolver, Weight: 0.0},
app.WeightedResolver{Resolver: c.dockerRegistryResolver, Weight: 0.0},
app.WeightedResolver{Resolver: c.localDockerResolver, Weight: 0.0},
app.WeightedResolver{Resolver: c.dockerResolver, Weight: 0.0},
}
return input
})
Expand Down Expand Up @@ -280,59 +281,74 @@ func (c *AppConfig) buildPipelines(components app.ComponentReferences, environme
return pipelines, nil
}

var ErrNoInputs = fmt.Errorf("no inputs provided")

type AppResult struct {
List *kapi.List

BuildNames []string
HasSource bool
}

// Run executes the provided config.
func (c *AppConfig) Run(out io.Writer, helpFn func() error) error {
func (c *AppConfig) Run(out io.Writer) (*AppResult, error) {
components, repositories, environment, err := c.validate()
if err != nil {
return err
return nil, err
}

hasSource := len(repositories) != 0
hasImages := len(components) != 0
if !hasSource && !hasImages {
// display help page
// TODO: return usage error, which should trigger help display
return helpFn()
return nil, ErrNoInputs
}

if err := c.resolve(components); err != nil {
return err
return nil, err
}

if err := c.ensureHasSource(components, repositories); err != nil {
return err
return nil, err
}

glog.V(4).Infof("Code %v", repositories)
glog.V(4).Infof("Images %v", components)

if err := c.detectSource(repositories); err != nil {
return err
return nil, err
}

pipelines, err := c.buildPipelines(components, app.Environment(environment))
if err != nil {
return err
return nil, err
}

objects := app.Objects{}
accept := app.NewAcceptFirst()
for _, p := range pipelines {
obj, err := p.Objects(accept)
if err != nil {
return fmt.Errorf("can't setup %q: %v", p.From, err)
return nil, fmt.Errorf("can't setup %q: %v", p.From, err)
}
objects = append(objects, obj...)
}

objects = app.AddServices(objects)

list := &kapi.List{Items: objects}
p, _, err := kubectl.GetPrinter("yaml", "")
if err != nil {
return err
buildNames := []string{}
for _, obj := range objects {
switch t := obj.(type) {
case *buildapi.BuildConfig:
buildNames = append(buildNames, t.Name)
}
}
return p.PrintObj(list, out)

list := &kapi.List{Items: objects}
return &AppResult{
List: list,
BuildNames: buildNames,
HasSource: hasSource,
}, nil
}

type mockSearcher struct{}
Expand Down
10 changes: 10 additions & 0 deletions pkg/generate/app/componentref.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ func (m ScoredComponentMatches) Len() int { return len(m) }
func (m ScoredComponentMatches) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m ScoredComponentMatches) Less(i, j int) bool { return m[i].Score < m[j].Score }

func (m ScoredComponentMatches) Exact() []*ComponentMatch {
out := []*ComponentMatch{}
for _, match := range m {
if match.Score == 0.0 {
out = append(out, match)
}
}
return out
}

type WeightedResolver struct {
Resolver
Weight float32
Expand Down
12 changes: 7 additions & 5 deletions pkg/generate/app/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ func (e ErrNoMatch) Error() string {
}

func (e ErrNoMatch) UsageError(commandName string) string {
return fmt.Sprintf(`
%[3]s - you can try to search for images or templates that may match this name with:
return fmt.Sprintf("%[3]s - does a Docker image with that name exist?", e.value, commandName, e.Error())

/*`
%[3]s - you can try to search for images or templates that may match this name with:

$ %[2]s -S %[1]q
$ %[2]s -S %[1]q

`, e.value, commandName, e.Error())
`*/
}

type ErrMultipleMatches struct {
Expand All @@ -42,7 +44,7 @@ func (e ErrMultipleMatches) UsageError(commandName string) string {
fmt.Fprintf(buf, " %s\n\n", match.Description)
}
return fmt.Sprintf(`
The argument %[1]q could apply to the following images or templates:
The argument %[1]q could apply to the following images or image repositories:

%[2]s
`, e.image, buf.String())
Expand Down
Loading