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"
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"
)
const (
appJSONLong = `
Import app.json files as OpenShift objects
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.
The command will create objects unless you pass the -o yaml or --as-template flags to generate a
configuration file for later use.`
appJSONExample = ` # Import a directory containing an app.json file
$ %[1]s app.json -f .
# Turn an app.json file into a template
$ %[1]s app.json -f ./app.json -o yaml --as-template
`
AppJSONV1GeneratorName = "app-json/v1"
)
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",
Short: "Import an app.json definition into OpenShift",
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 {
// TODO: move met to kcmdutil
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
o.Client, _, err = f.Clients()
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
}
} |