github.com/jonsyu1/godel@v0.0.0-20171017211503-64567a0cf169/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 }