github.com/joshdk/godel@v0.0.0-20170529232908-862138a45aee/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  	gitHubOwnerFlagName    = "owner"
    52  )
    53  
    54  var (
    55  	urlFlag = flag.StringFlag{
    56  		Name:     urlFlagName,
    57  		Usage:    "URL for a remote repository (such as https://repository.domain.com)",
    58  		Required: true,
    59  	}
    60  	userFlag = flag.StringFlag{
    61  		Name:     userFlagName,
    62  		Usage:    "Username for authentication for repository",
    63  		Required: true,
    64  	}
    65  	passwordFlag = flag.StringFlag{
    66  		Name:     passwordFlagName,
    67  		Usage:    "Password for repository",
    68  		Required: true,
    69  	}
    70  	almanacURLFlag = flag.StringFlag{
    71  		Name:     almanacURLFlagName,
    72  		Usage:    "URL for an Almanac instance (such as https://almanac.domain.com)",
    73  		Required: false,
    74  	}
    75  	almanacIDFlag = flag.StringFlag{
    76  		Name:     almanacIDFlagName,
    77  		Usage:    "Almanac access ID",
    78  		Required: false,
    79  	}
    80  	almanacSecretFlag = flag.StringFlag{
    81  		Name:     almanacSecretFlagName,
    82  		Usage:    "Almanac secret",
    83  		Required: false,
    84  	}
    85  	almanacReleaseFlag = flag.BoolFlag{
    86  		Name:  almanacReleaseFlagName,
    87  		Usage: "Perform an Almanac release after publish",
    88  	}
    89  	failFastFlag = flag.BoolFlag{
    90  		Name:  failFastFlagName,
    91  		Usage: "Fail immediately if the publish operation for an individual product fails",
    92  	}
    93  )
    94  
    95  func Command() cli.Command {
    96  	publishCmd := cli.Command{
    97  		Name:  "publish",
    98  		Usage: "Publish product distributions",
    99  		Subcommands: []cli.Command{
   100  			localType.createCommand(),
   101  			artifactoryType.createCommand(),
   102  			bintrayType.createCommand(),
   103  			githubType.createCommand(),
   104  		},
   105  	}
   106  
   107  	for i := range publishCmd.Subcommands {
   108  		publishCmd.Subcommands[i].Flags = append(publishCmd.Subcommands[i].Flags, cmd.ProductsParam)
   109  	}
   110  
   111  	return publishCmd
   112  }
   113  
   114  type publisherType struct {
   115  	name      string
   116  	usage     string
   117  	flags     []flag.Flag
   118  	publisher func(ctx cli.Context) Publisher
   119  }
   120  
   121  func (p *publisherType) createCommand() cli.Command {
   122  	return cli.Command{
   123  		Name:  p.name,
   124  		Usage: p.usage,
   125  		Flags: p.flags,
   126  		Action: func(ctx cli.Context) error {
   127  			wd, err := dirs.GetwdEvalSymLinks()
   128  			if err != nil {
   129  				return err
   130  			}
   131  			return publishAction(p.publisher(ctx), ctx.Slice(cmd.ProductsParamName), newAlmanacInfo(ctx), ctx.Bool(failFastFlagName), ctx.App.Stdout, wd)
   132  		},
   133  	}
   134  }
   135  
   136  var (
   137  	localType = publisherType{
   138  		name:  "local",
   139  		usage: "Publish products to a local directory",
   140  		flags: []flag.Flag{
   141  			flag.StringFlag{
   142  				Name:  pathFlagName,
   143  				Usage: "Path to local publish root",
   144  				Value: path.Join(os.Getenv("HOME"), ".m2", "repository"),
   145  			},
   146  			failFastFlag,
   147  		},
   148  		publisher: func(ctx cli.Context) Publisher {
   149  			return &LocalPublishInfo{
   150  				Path: ctx.String(pathFlagName),
   151  			}
   152  		},
   153  	}
   154  	artifactoryType = publisherType{
   155  		name:  "artifactory",
   156  		usage: "Publish products to an Artifactory repository",
   157  		flags: remotePublishFlags(flag.StringFlag{
   158  			Name:     repositoryFlagName,
   159  			Usage:    "Repository that is the destination for the publish",
   160  			Required: true,
   161  		}),
   162  		publisher: func(ctx cli.Context) Publisher {
   163  			return &ArtifactoryConnectionInfo{
   164  				BasicConnectionInfo: basicRemoteInfo(ctx),
   165  				Repository:          ctx.String(repositoryFlagName),
   166  			}
   167  		},
   168  	}
   169  	bintrayType = publisherType{
   170  		name:  "bintray",
   171  		usage: "Publish products to a Bintray repository",
   172  		flags: remotePublishFlags(
   173  			flag.StringFlag{
   174  				Name:     subjectFlagName,
   175  				Usage:    "Subject that is the destination for the publish",
   176  				Required: true,
   177  			},
   178  			flag.StringFlag{
   179  				Name:     repositoryFlagName,
   180  				Usage:    "Repository that is the destination for the publish",
   181  				Required: true,
   182  			},
   183  			flag.BoolFlag{
   184  				Name:  publishFlagName,
   185  				Usage: "Publish uploaded content",
   186  			},
   187  			flag.BoolFlag{
   188  				Name:  downloadsListFlagName,
   189  				Usage: "Add uploaded artifact to downloads list for package",
   190  			},
   191  		),
   192  		publisher: func(ctx cli.Context) Publisher {
   193  			return &BintrayConnectionInfo{
   194  				BasicConnectionInfo: basicRemoteInfo(ctx),
   195  				Subject:             ctx.String(subjectFlagName),
   196  				Repository:          ctx.String(repositoryFlagName),
   197  				Release:             ctx.Bool(publishFlagName),
   198  				DownloadsList:       ctx.Bool(downloadsListFlagName),
   199  			}
   200  		},
   201  	}
   202  	githubType = publisherType{
   203  		name:  "github",
   204  		usage: "Publish products to a GitHub repository",
   205  		flags: remotePublishFlags(
   206  			flag.StringFlag{
   207  				Name:     repositoryFlagName,
   208  				Usage:    "Repository that is the destination for the publish",
   209  				Required: true,
   210  			},
   211  			flag.StringFlag{
   212  				Name:  gitHubOwnerFlagName,
   213  				Usage: "GitHub owner of the destination repository for the publish (if unspecified, user will be used)",
   214  			},
   215  		),
   216  		publisher: func(ctx cli.Context) Publisher {
   217  			return &GitHubConnectionInfo{
   218  				APIURL:     basicRemoteInfo(ctx).URL,
   219  				User:       basicRemoteInfo(ctx).Username,
   220  				Token:      basicRemoteInfo(ctx).Password,
   221  				Owner:      ctx.String(gitHubOwnerFlagName),
   222  				Repository: ctx.String(repositoryFlagName),
   223  			}
   224  		},
   225  	}
   226  )
   227  
   228  func remotePublishFlags(flags ...flag.Flag) []flag.Flag {
   229  	remoteFlags := []flag.Flag{
   230  		urlFlag,
   231  		userFlag,
   232  		passwordFlag,
   233  	}
   234  	remoteFlags = append(remoteFlags, flags...)
   235  	remoteFlags = append(remoteFlags,
   236  		failFastFlag,
   237  		almanacURLFlag,
   238  		almanacIDFlag,
   239  		almanacSecretFlag,
   240  		almanacReleaseFlag,
   241  	)
   242  	return remoteFlags
   243  }
   244  
   245  func basicRemoteInfo(ctx cli.Context) BasicConnectionInfo {
   246  	return BasicConnectionInfo{
   247  		URL:      ctx.String(urlFlagName),
   248  		Username: ctx.String(userFlagName),
   249  		Password: ctx.String(passwordFlagName),
   250  	}
   251  }
   252  
   253  func newAlmanacInfo(ctx cli.Context) *AlmanacInfo {
   254  	if !ctx.Has(almanacURLFlagName) || ctx.String(almanacURLFlagName) == "" {
   255  		return nil
   256  	}
   257  	return &AlmanacInfo{
   258  		URL:      ctx.String(almanacURLFlagName),
   259  		AccessID: ctx.String(almanacIDFlagName),
   260  		Secret:   ctx.String(almanacSecretFlagName),
   261  		Release:  ctx.Bool(almanacReleaseFlagName),
   262  	}
   263  }
   264  
   265  func publishAction(publisher Publisher, products []string, almanacInfo *AlmanacInfo, failFast bool, stdout io.Writer, wd string) error {
   266  	cfg, err := config.Load(cfgcli.ConfigPath, cfgcli.ConfigJSON)
   267  	if err != nil {
   268  		return err
   269  	}
   270  
   271  	return build.RunBuildFunc(func(buildSpecWithDeps []params.ProductBuildSpecWithDeps, stdout io.Writer) error {
   272  		distsNotBuilt := DistsNotBuilt(buildSpecWithDeps)
   273  		var specsToBuild []params.ProductBuildSpec
   274  		for _, currSpecWithDeps := range distsNotBuilt {
   275  			specsToBuild = append(specsToBuild, build.RequiresBuild(currSpecWithDeps, nil).Specs()...)
   276  		}
   277  		if len(specsToBuild) > 0 {
   278  			if err := build.Run(specsToBuild, nil, build.DefaultContext(), stdout); err != nil {
   279  				return errors.Wrapf(err, "failed to build products required for dist")
   280  			}
   281  		}
   282  
   283  		if err := cmd.ProcessSerially(dist.Run)(distsNotBuilt, stdout); err != nil {
   284  			return errors.Wrapf(err, "failed to build dists required for publish")
   285  		}
   286  
   287  		var processFunc cmd.ProcessFunc
   288  		if failFast {
   289  			processFunc = cmd.ProcessSerially
   290  		} else {
   291  			processFunc = cmd.ProcessSeriallyBatchErrors
   292  		}
   293  
   294  		if err := processFunc(func(buildSpecWithDeps params.ProductBuildSpecWithDeps, stdout io.Writer) error {
   295  			return Run(buildSpecWithDeps, publisher, almanacInfo, stdout)
   296  		})(buildSpecWithDeps, stdout); err != nil {
   297  			// if publish failed with bulk errors, print nice error message
   298  			if specErrors, ok := err.(*cmd.SpecErrors); ok {
   299  				var parts []string
   300  				for _, v := range specErrors.Errors {
   301  					parts = append(parts, fmt.Sprintf("%v", v))
   302  				}
   303  				return fmt.Errorf(strings.Join(parts, "\n"))
   304  			}
   305  			return err
   306  		}
   307  
   308  		return nil
   309  	}, cfg, products, wd, stdout)
   310  }