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 }