Skip to content

Commit

Permalink
Provide informational output in new-app and new-build
Browse files Browse the repository at this point in the history
  • Loading branch information
smarterclayton committed Oct 29, 2015
1 parent 98cd35b commit 0509daa
Show file tree
Hide file tree
Showing 13 changed files with 401 additions and 144 deletions.
2 changes: 1 addition & 1 deletion hack/util.sh
Expand Up @@ -220,7 +220,7 @@ function tryuntil {
timeout=$(($(date +%s) + 90))
echo "++ Retrying until success or timeout: ${@}"
while [ 1 ]; do
if eval "${@}" 2>&1 >/dev/null; then
if eval "${@}" >/dev/null 2>&1; then
return 0
fi
if [[ $(date +%s) -gt $timeout ]]; then
Expand Down
90 changes: 66 additions & 24 deletions pkg/cmd/cli/cmd/newapp.go
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"io"
"io/ioutil"
"os"
"sort"
"strings"
Expand All @@ -17,6 +18,7 @@ import (
kcmd "k8s.io/kubernetes/pkg/kubectl/cmd"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/sets"
Expand Down Expand Up @@ -115,6 +117,7 @@ To search templates, image streams, and Docker images that match the arguments p
// NewCmdNewApplication implements the OpenShift cli new-app command
func NewCmdNewApplication(fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
config := newcmd.NewAppConfig()
config.Deploy = true

cmd := &cobra.Command{
Use: "new-app (IMAGE | IMAGESTREAM | TEMPLATE | PATH | URL ...)",
Expand Down Expand Up @@ -155,6 +158,7 @@ func NewCmdNewApplication(fullName string, f *clientcmd.Factory, out io.Writer)
cmd.Flags().BoolVar(&config.AllowMissingImages, "allow-missing-images", false, "If true, indicates that referenced Docker images that cannot be found locally or in a registry should still be used.")
cmd.Flags().BoolVar(&config.AllowSecretUse, "grant-install-rights", false, "If true, a component that requires access to your account may use your token to install software into your project. Only grant images you trust the right to run with your token.")
cmd.Flags().BoolVar(&config.SkipGeneration, "no-install", false, "Do not attempt to run images that describe themselves as being installable")
cmd.Flags().BoolVar(&config.DryRun, "dry-run", false, "If true, do not actually create resources.")

// TODO AddPrinterFlags disabled so that it doesn't conflict with our own "template" flag.
// Need a better solution.
Expand All @@ -170,10 +174,15 @@ func NewCmdNewApplication(fullName string, f *clientcmd.Factory, out io.Writer)
// RunNewApplication contains all the necessary functionality for the OpenShift cli new-app command
func RunNewApplication(fullName string, f *clientcmd.Factory, out io.Writer, c *cobra.Command, args []string, config *newcmd.AppConfig) error {
output := cmdutil.GetFlagString(c, "output")
shortOutput := output == "name"

if err := setupAppConfig(f, out, c, args, config); err != nil {
return err
}
if shortOutput || len(output) != 0 {
config.Out = ioutil.Discard
}

if config.Querying() {
result, err := config.RunQuery()
if err != nil {
Expand All @@ -189,11 +198,15 @@ func RunNewApplication(fullName string, f *clientcmd.Factory, out io.Writer, c *
if err := setAppConfigLabels(c, config); err != nil {
return err
}
result, err := config.RunAll()
result, err := config.Run()
if err := handleRunError(c, err, fullName); err != nil {
return err
}

if len(config.Labels) == 0 && len(result.Name) > 0 {
config.Labels = map[string]string{"app": result.Name}
}

if err := setLabels(config.Labels, result); err != nil {
return err
}
Expand All @@ -202,15 +215,41 @@ func RunNewApplication(fullName string, f *clientcmd.Factory, out io.Writer, c *
return err
}

if len(output) != 0 && output != "name" {
indent := " "
switch {
case shortOutput:
indent = ""
case len(output) != 0:
return f.Factory.PrintObject(c, result.List, out)
case !result.GeneratedJobs:
if len(config.Labels) > 0 {
fmt.Fprintf(out, "--> Creating resources with label %s ...\n", labels.SelectorFromSet(config.Labels).String())
} else {
fmt.Fprintf(out, "--> Creating resources ...\n")
}
}
if config.DryRun {
return nil
}

mapper, _ := f.Object()
var afterFn func(*resource.Info, error)
switch {
// only print success if we don't have installables
if err := createObjects(f, out, c.Out(), output == "name", !result.GeneratedJobs, result); err != nil {
case !result.GeneratedJobs:
afterFn = configcmd.NewPrintNameOrErrorAfterIndent(mapper, shortOutput, "created", out, c.Out(), indent)
default:
afterFn = configcmd.NewPrintErrorAfter(mapper, c.Out())
}

if err := createObjects(f, afterFn, result); err != nil {
return err
}

if !shortOutput && !result.GeneratedJobs {
fmt.Fprintf(out, "--> Success\n")
}

hasMissingRepo := false
installing := []*kapi.Pod{}
for _, item := range result.List.Items {
Expand All @@ -221,18 +260,23 @@ func RunNewApplication(fullName string, f *clientcmd.Factory, out io.Writer, c *
}
case *buildapi.BuildConfig:
if len(t.Spec.Triggers) > 0 {
fmt.Fprintf(c.Out(), "Build scheduled for %q - use the build-logs command to track its progress.\n", t.Name)
fmt.Fprintf(out, "%sBuild scheduled for %q - use the build-logs command to track its progress.\n", indent, t.Name)
}
case *imageapi.ImageStream:
if len(t.Status.DockerImageRepository) == 0 {
if hasMissingRepo {
continue
}
hasMissingRepo = true
fmt.Fprint(c.Out(), "WARNING: No Docker registry has been configured with the server. Automatic builds and deployments may not function.\n")
fmt.Fprintf(out, "%sWARNING: No Docker registry has been configured with the server. Automatic builds and deployments may not function.\n", indent)
}
}
}

if shortOutput {
return nil
}

switch {
case len(installing) == 1:
// TODO: should get this set on the config or up above
Expand All @@ -244,16 +288,16 @@ func RunNewApplication(fullName string, f *clientcmd.Factory, out io.Writer, c *
return followInstallation(f, jobInput, installing[0], kclient, out)
case len(installing) > 1:
for i := range installing {
fmt.Fprintf(c.Out(), "Track installation of %s with '%s logs %s'.\n", installing[i].Name, fullName, installing[i].Name)
fmt.Fprintf(out, "%sTrack installation of %s with '%s logs %s'.\n", indent, installing[i].Name, fullName, installing[i].Name)
}
case len(result.List.Items) > 0:
fmt.Fprintf(c.Out(), "Run '%s %s' to view your app.\n", fullName, StatusRecommendedName)
fmt.Fprintf(out, "%sRun '%s %s' to view your app.\n", indent, fullName, StatusRecommendedName)
}
return nil
}

func followInstallation(f *clientcmd.Factory, input string, pod *kapi.Pod, kclient kclient.Interface, out io.Writer) error {
fmt.Fprintf(out, "Installing %q with pod %q ...\n", input, pod.Name)
fmt.Fprintf(out, "--> Installing ...\n")

// we cannot retrieve logs until the pod is out of pending
// TODO: move this to the server side
Expand Down Expand Up @@ -307,7 +351,9 @@ func installationStarted(c kclient.PodInterface, name string, s kclient.SecretsI
if secret, err := s.Get(name); err == nil {
if secret.Annotations[newcmd.GeneratedForJob] == "true" &&
secret.Annotations[newcmd.GeneratedForJobFor] == pod.Annotations[newcmd.GeneratedForJobFor] {
s.Delete(name)
if err := s.Delete(name); err != nil {
glog.V(4).Infof("Failed to delete install secret %s: %v", name, err)
}
}
}
return true, nil
Expand All @@ -325,8 +371,10 @@ func installationComplete(c kclient.PodInterface, name string, out io.Writer) wa
}
switch pod.Status.Phase {
case kapi.PodSucceeded:
fmt.Fprintf(out, "Installation complete\n")
c.Delete(name, nil)
fmt.Fprintf(out, "--> Success\n")
if err := c.Delete(name, nil); err != nil {
glog.V(4).Infof("Failed to delete install pod %s: %v", name, err)
}
return true, nil
case kapi.PodFailed:
return true, fmt.Errorf("installation of %q did not complete successfully", name)
Expand Down Expand Up @@ -404,11 +452,6 @@ func setAnnotations(annotations map[string]string, result *newcmd.AppResult) err
}

func setLabels(labels map[string]string, result *newcmd.AppResult) error {
if len(labels) == 0 {
if len(result.Name) > 0 {
labels = map[string]string{"app": result.Name}
}
}
for _, object := range result.List.Items {
err := util.AddObjectLabels(object, labels)
if err != nil {
Expand Down Expand Up @@ -459,22 +502,18 @@ func retryBuildConfig(info *resource.Info, err error) runtime.Object {
return nil
}

func createObjects(f *clientcmd.Factory, out, errout io.Writer, shortOutput, includeSuccess bool, result *newcmd.AppResult) error {
func createObjects(f *clientcmd.Factory, after func(*resource.Info, error), result *newcmd.AppResult) error {
mapper, typer := f.Factory.Object()
bulk := configcmd.Bulk{
Mapper: mapper,
Typer: typer,
RESTClientFactory: f.Factory.RESTClient,

After: after,
// Retry is used to support previous versions of the API server that will
// consider the presence of an unknown trigger type to be an error.
Retry: retryBuildConfig,
}
switch {
case includeSuccess:
bulk.After = configcmd.NewPrintNameOrErrorAfter(mapper, shortOutput, "created", out, errout)
default:
bulk.After = configcmd.NewPrintErrorAfter(mapper, errout)
}
if errs := bulk.Create(result.List, result.Namespace); len(errs) != 0 {
return errExit
}
Expand All @@ -492,7 +531,10 @@ func handleRunError(c *cobra.Command, err error, fullName string) error {
}
switch t := err.(type) {
case newcmd.ErrRequiresExplicitAccess:
return fmt.Errorf("installing %q requires that you grant the image access to run with your credentials; if you trust the provided image, include the flag --grant-install-rights", t.Match.Value)
return fmt.Errorf(`installing %q requires that you grant the image access to run with your credentials
You can see more information about the image by adding the --dry-run flag.
If you trust the provided image, include the flag --grant-install-rights.`, t.Match.Value)
case newapp.ErrNoMatch:
return fmt.Errorf(`%[1]v
Expand Down
40 changes: 35 additions & 5 deletions pkg/cmd/cli/cmd/newbuild.go
Expand Up @@ -12,8 +12,10 @@ import (

buildapi "github.com/openshift/origin/pkg/build/api"
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
configcmd "github.com/openshift/origin/pkg/config/cmd"
newapp "github.com/openshift/origin/pkg/generate/app"
newcmd "github.com/openshift/origin/pkg/generate/app/cmd"
"k8s.io/kubernetes/pkg/labels"
)

const (
Expand Down Expand Up @@ -104,6 +106,7 @@ func NewCmdNewBuild(fullName string, f *clientcmd.Factory, in io.Reader, out io.
cmd.Flags().StringP("labels", "l", "", "Label to set in all generated resources.")
cmd.Flags().BoolVar(&config.AllowMissingImages, "allow-missing-images", false, "If true, indicates that referenced Docker images that cannot be found locally or in a registry should still be used.")
cmd.Flags().StringVar(&config.ContextDir, "context-dir", "", "Context directory to be used for the build.")
cmd.Flags().BoolVar(&config.DryRun, "dry-run", false, "If true, do not actually create resources.")
cmdutil.AddPrinterFlags(cmd)

return cmd
Expand All @@ -112,6 +115,7 @@ func NewCmdNewBuild(fullName string, f *clientcmd.Factory, in io.Reader, out io.
// RunNewBuild contains all the necessary functionality for the OpenShift cli new-build command
func RunNewBuild(fullName string, f *clientcmd.Factory, out io.Writer, in io.Reader, c *cobra.Command, args []string, config *newcmd.AppConfig) error {
output := cmdutil.GetFlagString(c, "output")
shortOutput := output == "name"

if config.Dockerfile == "-" {
data, err := ioutil.ReadAll(in)
Expand All @@ -128,31 +132,57 @@ func RunNewBuild(fullName string, f *clientcmd.Factory, out io.Writer, in io.Rea
if err := setAppConfigLabels(c, config); err != nil {
return err
}
result, err := config.RunBuilds()
result, err := config.Run()
if err != nil {
return handleBuildError(c, err, fullName)
}

if len(config.Labels) == 0 && len(result.Name) > 0 {
config.Labels = map[string]string{"build": result.Name}
}

if err := setLabels(config.Labels, result); err != nil {
return err
}
if err := setAnnotations(map[string]string{newcmd.GeneratedByNamespace: newcmd.GeneratedByNewBuild}, result); err != nil {
return err
}
if len(output) != 0 && output != "name" {

indent := " "
switch {
case shortOutput:
indent = ""
case len(output) != 0:
return f.Factory.PrintObject(c, result.List, out)
default:
if len(config.Labels) > 0 {
fmt.Fprintf(out, "--> Creating resources with label %s ...\n", labels.SelectorFromSet(config.Labels).String())
} else {
fmt.Fprintf(out, "--> Creating resources ...\n")
}
}
if err := createObjects(f, out, c.Out(), output == "name", true, result); err != nil {
if config.DryRun {
return nil
}

mapper, _ := f.Object()
if err := createObjects(f, configcmd.NewPrintNameOrErrorAfterIndent(mapper, shortOutput, "created", out, c.Out(), indent), result); err != nil {
return err
}

if shortOutput {
return nil
}

fmt.Fprintf(out, "--> Success\n")
for _, item := range result.List.Items {
switch t := item.(type) {
case *buildapi.BuildConfig:
fmt.Fprintf(c.Out(), "Build configuration %q created and build triggered.\n", t.Name)
fmt.Fprintf(out, "%sBuild configuration %q created and build triggered.\n", indent, t.Name)
}
}
if len(result.List.Items) > 0 {
fmt.Fprintf(c.Out(), "Run '%s %s' to check the progress.\n", fullName, StatusRecommendedName)
fmt.Fprintf(out, "%sRun '%s %s' to check the progress.\n", indent, fullName, StatusRecommendedName)
}

return nil
Expand Down
5 changes: 5 additions & 0 deletions pkg/cmd/cli/describe/helpers.go
Expand Up @@ -113,6 +113,11 @@ func formatRelativeTime(t time.Time) string {
return units.HumanDuration(timeNowFn().Sub(t))
}

// FormatRelativeTime converts a time field into a human readable age string (hours, minutes, days).
func FormatRelativeTime(t time.Time) string {
return formatRelativeTime(t)
}

func formatMeta(out *tabwriter.Writer, m api.ObjectMeta) {
formatString(out, "Name", m.Name)
if !m.CreationTimestamp.IsZero() {
Expand Down
9 changes: 7 additions & 2 deletions pkg/config/cmd/cmd.go
Expand Up @@ -21,11 +21,16 @@ type Bulk struct {
}

func NewPrintNameOrErrorAfter(mapper meta.RESTMapper, short bool, operation string, out, errs io.Writer) func(*resource.Info, error) {
return NewPrintNameOrErrorAfterIndent(mapper, short, operation, out, errs, "")
}

func NewPrintNameOrErrorAfterIndent(mapper meta.RESTMapper, short bool, operation string, out, errs io.Writer, indent string) func(*resource.Info, error) {
return func(info *resource.Info, err error) {
if err == nil {
cmdutil.PrintSuccess(mapper, false, out, info.Mapping.Kind, info.Name, operation)
fmt.Fprintf(out, indent)
cmdutil.PrintSuccess(mapper, short, out, info.Mapping.Kind, info.Name, operation)
} else {
fmt.Fprintf(errs, "error: %v\n", err)
fmt.Fprintf(errs, "%serror: %v\n", indent, err)
}
}
}
Expand Down

0 comments on commit 0509daa

Please sign in to comment.