github.com/jdhenke/godel@v0.0.0-20161213181855-abeb3861bf0d/apps/distgo/cmd/publish/cmd.go (about)

     1  // Copyright 2016 Palantir Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package publish
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"os"
    21  	"path"
    22  	"strings"
    23  
    24  	"github.com/nmiyake/pkg/dirs"
    25  	"github.com/palantir/pkg/cli"
    26  	"github.com/palantir/pkg/cli/cfgcli"
    27  	"github.com/palantir/pkg/cli/flag"
    28  	"github.com/pkg/errors"
    29  
    30  	"github.com/palantir/godel/apps/distgo/cmd"
    31  	"github.com/palantir/godel/apps/distgo/cmd/build"
    32  	"github.com/palantir/godel/apps/distgo/cmd/dist"
    33  	"github.com/palantir/godel/apps/distgo/config"
    34  	"github.com/palantir/godel/apps/distgo/params"
    35  )
    36  
    37  const (
    38  	urlFlagName            = "url"
    39  	userFlagName           = "user"
    40  	passwordFlagName       = "password"
    41  	almanacURLFlagName     = "almanac-url"
    42  	almanacIDFlagName      = "almanac-id"
    43  	almanacSecretFlagName  = "almanac-secret"
    44  	almanacReleaseFlagName = "almanac-release"
    45  	repositoryFlagName     = "repository"
    46  	subjectFlagName        = "subject"
    47  	publishFlagName        = "publish"
    48  	downloadsListFlagName  = "downloads-list"
    49  	pathFlagName           = "path"
    50  	failFastFlagName       = "fail-fast"
    51  )
    52  
    53  var (
    54  	urlFlag = flag.StringFlag{
    55  		Name:     urlFlagName,
    56  		Usage:    "URL for a remote repository (such as https://repository.domain.com)",
    57  		Required: true,
    58  	}
    59  	userFlag = flag.StringFlag{
    60  		Name:     userFlagName,
    61  		Usage:    "Username for repository",
    62  		Required: true,
    63  	}
    64  	passwordFlag = flag.StringFlag{
    65  		Name:     passwordFlagName,
    66  		Usage:    "Password for repository",
    67  		Required: true,
    68  	}
    69  	almanacURLFlag = flag.StringFlag{
    70  		Name:     almanacURLFlagName,
    71  		Usage:    "URL for an Almanac instance (such as https://almanac.domain.com)",
    72  		Required: false,
    73  	}
    74  	almanacIDFlag = flag.StringFlag{
    75  		Name:     almanacIDFlagName,
    76  		Usage:    "Almanac access ID",
    77  		Required: false,
    78  	}
    79  	almanacSecretFlag = flag.StringFlag{
    80  		Name:     almanacSecretFlagName,
    81  		Usage:    "Almanac secret",
    82  		Required: false,
    83  	}
    84  	almanacReleaseFlag = flag.BoolFlag{
    85  		Name:  almanacReleaseFlagName,
    86  		Usage: "Perform an Almanac release after publish",
    87  	}
    88  	failFastFlag = flag.BoolFlag{
    89  		Name:  failFastFlagName,
    90  		Usage: "Fail immediately if the publish operation for an individual product fails",
    91  	}
    92  )
    93  
    94  func Command() cli.Command {
    95  	publishCmd := cli.Command{
    96  		Name:  "publish",
    97  		Usage: "Publish product distributions",
    98  		Subcommands: []cli.Command{
    99  			local.createCommand(),
   100  			artifactory.createCommand(),
   101  			bintray.createCommand(),
   102  		},
   103  	}
   104  
   105  	for i := range publishCmd.Subcommands {
   106  		publishCmd.Subcommands[i].Flags = append(publishCmd.Subcommands[i].Flags, cmd.ProductsParam)
   107  	}
   108  
   109  	return publishCmd
   110  }
   111  
   112  type publisherType struct {
   113  	name      string
   114  	usage     string
   115  	flags     []flag.Flag
   116  	publisher func(ctx cli.Context) Publisher
   117  }
   118  
   119  func (p *publisherType) createCommand() cli.Command {
   120  	return cli.Command{
   121  		Name:  p.name,
   122  		Usage: p.usage,
   123  		Flags: p.flags,
   124  		Action: func(ctx cli.Context) error {
   125  			wd, err := dirs.GetwdEvalSymLinks()
   126  			if err != nil {
   127  				return err
   128  			}
   129  			return publishAction(p.publisher(ctx), ctx.Slice(cmd.ProductsParamName), newAlmanacInfo(ctx), ctx.Bool(failFastFlagName), ctx.App.Stdout, wd)
   130  		},
   131  	}
   132  }
   133  
   134  var (
   135  	local = publisherType{
   136  		name:  "local",
   137  		usage: "Publish products to a local directory",
   138  		flags: []flag.Flag{
   139  			flag.StringFlag{
   140  				Name:  pathFlagName,
   141  				Usage: "Path to local publish root",
   142  				Value: path.Join(os.Getenv("HOME"), ".m2", "repository"),
   143  			},
   144  			failFastFlag,
   145  		},
   146  		publisher: func(ctx cli.Context) Publisher {
   147  			return LocalPublishInfo{
   148  				Path: ctx.String(pathFlagName),
   149  			}
   150  		},
   151  	}
   152  	artifactory = publisherType{
   153  		name:  "artifactory",
   154  		usage: "Publish products to an Artifactory repository",
   155  		flags: remotePublishFlags(flag.StringFlag{
   156  			Name:     repositoryFlagName,
   157  			Usage:    "Repository that is the destination for the publish",
   158  			Required: true,
   159  		}),
   160  		publisher: func(ctx cli.Context) Publisher {
   161  			return ArtifactoryConnectionInfo{
   162  				BasicConnectionInfo: basicRemoteInfo(ctx),
   163  				Repository:          ctx.String(repositoryFlagName),
   164  			}
   165  		},
   166  	}
   167  	bintray = publisherType{
   168  		name:  "bintray",
   169  		usage: "Publish products to a Bintray repository",
   170  		flags: remotePublishFlags(
   171  			flag.StringFlag{
   172  				Name:     subjectFlagName,
   173  				Usage:    "Subject that is the destination for the publish",
   174  				Required: true,
   175  			},
   176  			flag.StringFlag{
   177  				Name:     repositoryFlagName,
   178  				Usage:    "Repository that is the destination for the publish",
   179  				Required: true,
   180  			},
   181  			flag.BoolFlag{
   182  				Name:  publishFlagName,
   183  				Usage: "Publish uploaded content",
   184  			},
   185  			flag.BoolFlag{
   186  				Name:  downloadsListFlagName,
   187  				Usage: "Add uploaded artifact to downloads list for package",
   188  			},
   189  		),
   190  		publisher: func(ctx cli.Context) Publisher {
   191  			return BintrayConnectionInfo{
   192  				BasicConnectionInfo: basicRemoteInfo(ctx),
   193  				Subject:             ctx.String(subjectFlagName),
   194  				Repository:          ctx.String(repositoryFlagName),
   195  				Release:             ctx.Bool(publishFlagName),
   196  				DownloadsList:       ctx.Bool(downloadsListFlagName),
   197  			}
   198  		},
   199  	}
   200  )
   201  
   202  func remotePublishFlags(flags ...flag.Flag) []flag.Flag {
   203  	remoteFlags := []flag.Flag{
   204  		urlFlag,
   205  		userFlag,
   206  		passwordFlag,
   207  	}
   208  	remoteFlags = append(remoteFlags, flags...)
   209  	remoteFlags = append(remoteFlags,
   210  		failFastFlag,
   211  		almanacURLFlag,
   212  		almanacIDFlag,
   213  		almanacSecretFlag,
   214  		almanacReleaseFlag,
   215  	)
   216  	return remoteFlags
   217  }
   218  
   219  func basicRemoteInfo(ctx cli.Context) BasicConnectionInfo {
   220  	return BasicConnectionInfo{
   221  		URL:      ctx.String(urlFlagName),
   222  		Username: ctx.String(userFlagName),
   223  		Password: ctx.String(passwordFlagName),
   224  	}
   225  }
   226  
   227  func newAlmanacInfo(ctx cli.Context) *AlmanacInfo {
   228  	if !ctx.Has(almanacURLFlagName) || ctx.String(almanacURLFlagName) == "" {
   229  		return nil
   230  	}
   231  	return &AlmanacInfo{
   232  		URL:      ctx.String(almanacURLFlagName),
   233  		AccessID: ctx.String(almanacIDFlagName),
   234  		Secret:   ctx.String(almanacSecretFlagName),
   235  		Release:  ctx.Bool(almanacReleaseFlagName),
   236  	}
   237  }
   238  
   239  func publishAction(publisher Publisher, products []string, almanacInfo *AlmanacInfo, failFast bool, stdout io.Writer, wd string) error {
   240  	cfg, err := config.Load(cfgcli.ConfigPath, cfgcli.ConfigJSON)
   241  	if err != nil {
   242  		return err
   243  	}
   244  
   245  	return build.RunBuildFunc(func(buildSpecWithDeps []params.ProductBuildSpecWithDeps, stdout io.Writer) error {
   246  		distsNotBuilt := DistsNotBuilt(buildSpecWithDeps)
   247  		var specsToBuild []params.ProductBuildSpec
   248  		for _, currSpecWithDeps := range distsNotBuilt {
   249  			specsToBuild = append(specsToBuild, build.RequiresBuild(currSpecWithDeps, nil).Specs()...)
   250  		}
   251  		if len(specsToBuild) > 0 {
   252  			if err := build.Run(specsToBuild, nil, build.DefaultContext(), stdout); err != nil {
   253  				return errors.Wrapf(err, "failed to build products required for dist")
   254  			}
   255  		}
   256  
   257  		if err := cmd.ProcessSerially(dist.Run)(distsNotBuilt, stdout); err != nil {
   258  			return errors.Wrapf(err, "failed to build dists required for publish")
   259  		}
   260  
   261  		var processFunc cmd.ProcessFunc
   262  		if failFast {
   263  			processFunc = cmd.ProcessSerially
   264  		} else {
   265  			processFunc = cmd.ProcessSeriallyBatchErrors
   266  		}
   267  
   268  		if err := processFunc(func(buildSpecWithDeps params.ProductBuildSpecWithDeps, stdout io.Writer) error {
   269  			return Run(buildSpecWithDeps, publisher, almanacInfo, stdout)
   270  		})(buildSpecWithDeps, stdout); err != nil {
   271  			// if publish failed with bulk errors, print nice error message
   272  			if specErrors, ok := err.(*cmd.SpecErrors); ok {
   273  				var parts []string
   274  				for _, v := range specErrors.Errors {
   275  					parts = append(parts, fmt.Sprintf("%v", v))
   276  				}
   277  				return fmt.Errorf(strings.Join(parts, "\n"))
   278  			}
   279  			return err
   280  		}
   281  
   282  		return nil
   283  	}, cfg, products, wd, stdout)
   284  }