pkg/cmd/cli/cmd/importer/appjson.go
01196c80
 package importer
 
 import (
 	"fmt"
 	"io"
 	"io/ioutil"
 	"net/http"
 	"net/url"
 	"os"
 	"path"
 	"path/filepath"
 	"strings"
 
 	"github.com/spf13/cobra"
 
 	kapi "k8s.io/kubernetes/pkg/api"
 	"k8s.io/kubernetes/pkg/api/unversioned"
 	"k8s.io/kubernetes/pkg/apimachinery/registered"
 	"k8s.io/kubernetes/pkg/kubectl"
 	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
 	"k8s.io/kubernetes/pkg/runtime"
 
 	"github.com/openshift/origin/pkg/client"
6267dded
 	"github.com/openshift/origin/pkg/cmd/templates"
01196c80
 	cmdutil "github.com/openshift/origin/pkg/cmd/util"
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
 	configcmd "github.com/openshift/origin/pkg/config/cmd"
 	"github.com/openshift/origin/pkg/generate/app"
 	appcmd "github.com/openshift/origin/pkg/generate/app/cmd"
 	"github.com/openshift/origin/pkg/generate/appjson"
 )
 
6267dded
 const AppJSONV1GeneratorName = "app-json/v1"
01196c80
 
6267dded
 var (
 	appJSONLong = templates.LongDesc(`
 		Import app.json files as OpenShift objects
01196c80
 
6267dded
 		app.json defines the pattern of a simple, stateless web application that can be horizontally scaled.
 		This command will transform a provided app.json object into its OpenShift equivalent.
 		During transformation fields in the app.json syntax that are not relevant when running on top of
 		a containerized platform will be ignored and a warning printed.
d37ade3f
 
6267dded
 		The command will create objects unless you pass the -o yaml or --as-template flags to generate a
 		configuration file for later use.
01196c80
 
6267dded
 		Experimental: This command is under active development and may change without notice.`)
01196c80
 
6267dded
 	appJSONExample = templates.Examples(`
 		# Import a directory containing an app.json file
 	  $ %[1]s app.json -f .
01196c80
 
6267dded
 	  # Turn an app.json file into a template
 	  $ %[1]s app.json -f ./app.json -o yaml --as-template`)
01196c80
 )
 
 type AppJSONOptions struct {
 	Action configcmd.BulkAction
 
 	In        io.Reader
 	Filenames []string
 
 	BaseImage  string
 	Generator  string
 	AsTemplate string
 
 	PrintObject    func(runtime.Object) error
 	OutputVersions []unversioned.GroupVersion
 
 	Namespace string
 	Client    client.TemplateConfigsNamespacer
 }
 
 // NewCmdAppJSON imports an app.json file (schema described here: https://devcenter.heroku.com/articles/app-json-schema)
 // as a template.
 func NewCmdAppJSON(fullName string, f *clientcmd.Factory, in io.Reader, out, errout io.Writer) *cobra.Command {
 	options := &AppJSONOptions{
 		Action: configcmd.BulkAction{
 			Out:    out,
 			ErrOut: errout,
 		},
 		In:        in,
 		Generator: AppJSONV1GeneratorName,
 	}
 	cmd := &cobra.Command{
 		Use:     "app.json -f APPJSON",
d37ade3f
 		Short:   "Import an app.json definition into OpenShift (experimental)",
01196c80
 		Long:    appJSONLong,
 		Example: fmt.Sprintf(appJSONExample, fullName),
 		Run: func(cmd *cobra.Command, args []string) {
 			kcmdutil.CheckErr(options.Complete(f, cmd, args))
 			kcmdutil.CheckErr(options.Validate())
 			if err := options.Run(); err != nil {
d7da290f
 				// TODO: move me to kcmdutil
01196c80
 				if err == cmdutil.ErrExit {
 					os.Exit(1)
 				}
 				kcmdutil.CheckErr(err)
 			}
 		},
 	}
 	usage := "Filename, directory, or URL to app.json file to use"
 	kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage)
 	cmd.MarkFlagRequired("filename")
 
 	cmd.Flags().StringVar(&options.BaseImage, "image", options.BaseImage, "An optional image to use as your base Docker build (must have ONBUILD directives)")
 	cmd.Flags().String("generator", options.Generator, "The name of the generator strategy to use - specify this value to for backwards compatibility.")
 	cmd.Flags().StringVar(&options.AsTemplate, "as-template", "", "If set, generate a template with the provided name")
 
 	options.Action.BindForOutput(cmd.Flags())
 	cmd.Flags().String("output-version", "", "The preferred API versions of the output objects")
 
 	return cmd
 }
 
 func (o *AppJSONOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string) error {
 	version, _ := cmd.Flags().GetString("output-version")
 	for _, v := range strings.Split(version, ",") {
 		gv, err := unversioned.ParseGroupVersion(v)
 		if err != nil {
 			return fmt.Errorf("provided output-version %q is not valid: %v", v, err)
 		}
 		o.OutputVersions = append(o.OutputVersions, gv)
 	}
 	o.OutputVersions = append(o.OutputVersions, registered.EnabledVersions()...)
 
 	o.Action.Bulk.Mapper = clientcmd.ResourceMapper(f)
 	o.Action.Bulk.Op = configcmd.Create
 	mapper, _ := f.Object(false)
 	o.PrintObject = cmdutil.VersionedPrintObject(f.PrintObject, cmd, mapper, o.Action.Out)
 
 	o.Generator, _ = cmd.Flags().GetString("generator")
 
 	ns, _, err := f.DefaultNamespace()
 	if err != nil {
 		return err
 	}
 	o.Namespace = ns
 
97e6f1de
 	o.Client, _, _, err = f.Clients()
01196c80
 	return err
 }
 
 func (o *AppJSONOptions) Validate() error {
 	if len(o.Filenames) != 1 {
 		return fmt.Errorf("you must provide the path to an app.json file or directory containing app.json")
 	}
 	switch o.Generator {
 	case AppJSONV1GeneratorName:
 	default:
 		return fmt.Errorf("the generator %q is not supported, use: %s", o.Generator, AppJSONV1GeneratorName)
 	}
 	return nil
 }
 
 func (o *AppJSONOptions) Run() error {
 	localPath, contents, err := contentsForPathOrURL(o.Filenames[0], o.In, "app.json")
 	if err != nil {
 		return err
 	}
 
 	g := &appjson.Generator{
 		LocalPath: localPath,
 		BaseImage: o.BaseImage,
 	}
 	switch {
 	case len(o.AsTemplate) > 0:
 		g.Name = o.AsTemplate
 	case len(localPath) > 0:
 		g.Name = filepath.Base(localPath)
 	default:
 		g.Name = path.Base(path.Dir(o.Filenames[0]))
 	}
 	if len(g.Name) == 0 {
 		g.Name = "app"
 	}
 
 	template, err := g.Generate(contents)
 	if err != nil {
 		return err
 	}
 
 	template.ObjectLabels = map[string]string{"app.json": template.Name}
 
 	// all the types generated into the template should be known
 	if errs := app.AsVersionedObjects(template.Objects, kapi.Scheme, kapi.Scheme, o.OutputVersions...); len(errs) > 0 {
 		for _, err := range errs {
 			fmt.Fprintf(o.Action.ErrOut, "error: %v\n", err)
 		}
 	}
 
 	if o.Action.ShouldPrint() || (o.Action.Output == "name" && len(o.AsTemplate) > 0) {
 		var out runtime.Object
 		if len(o.AsTemplate) > 0 {
 			template.Name = o.AsTemplate
 			out = template
 		} else {
 			out = &kapi.List{Items: template.Objects}
 		}
 		return o.PrintObject(out)
 	}
 
 	result, err := appcmd.TransformTemplate(template, o.Client, o.Namespace, nil)
 	if err != nil {
 		return err
 	}
 
 	if o.Action.Verbose() {
 		appcmd.DescribeGeneratedTemplate(o.Action.Out, "", result, o.Namespace)
 	}
 
 	if errs := o.Action.WithMessage("Importing app.json", "creating").Run(&kapi.List{Items: result.Objects}, o.Namespace); len(errs) > 0 {
 		return cmdutil.ErrExit
 	}
 	return nil
 }
 
 func contentsForPathOrURL(s string, in io.Reader, subpaths ...string) (string, []byte, error) {
 	switch {
 	case s == "-":
 		contents, err := ioutil.ReadAll(in)
 		return "", contents, err
 	case strings.Index(s, "http://") == 0 || strings.Index(s, "https://") == 0:
 		_, err := url.Parse(s)
 		if err != nil {
 			return "", nil, fmt.Errorf("the URL passed to filename %q is not valid: %v", s, err)
 		}
 		res, err := http.Get(s)
 		if err != nil {
 			return "", nil, err
 		}
 		defer res.Body.Close()
 		contents, err := ioutil.ReadAll(res.Body)
 		return "", contents, err
 	default:
 		stat, err := os.Stat(s)
 		if err != nil {
 			return s, nil, err
 		}
 		if !stat.IsDir() {
 			contents, err := ioutil.ReadFile(s)
 			return s, contents, err
 		}
 		for _, sub := range subpaths {
 			path := filepath.Join(s, sub)
 			stat, err := os.Stat(path)
 			if err != nil {
 				continue
 			}
 			if stat.IsDir() {
 				continue
 			}
 			contents, err := ioutil.ReadFile(s)
 			return path, contents, err
 		}
 		return s, nil, os.ErrNotExist
 	}
 }