github.com/osievert/jfrog-cli-core@v1.2.7/artifactory/commands/npm/publish.go (about) 1 package npm 2 3 import ( 4 "archive/tar" 5 "compress/gzip" 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "strings" 14 15 "github.com/jfrog/jfrog-cli-core/artifactory/utils" 16 "github.com/jfrog/jfrog-cli-core/artifactory/utils/npm" 17 "github.com/jfrog/jfrog-cli-core/utils/config" 18 "github.com/jfrog/jfrog-client-go/artifactory/buildinfo" 19 "github.com/jfrog/jfrog-client-go/artifactory/services" 20 specutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils" 21 clientutils "github.com/jfrog/jfrog-client-go/utils" 22 "github.com/jfrog/jfrog-client-go/utils/errorutils" 23 "github.com/jfrog/jfrog-client-go/utils/log" 24 ) 25 26 type NpmPublishCommandArgs struct { 27 NpmCommand 28 executablePath string 29 workingDirectory string 30 collectBuildInfo bool 31 packedFilePath string 32 packageInfo *npm.PackageInfo 33 publishPath string 34 tarballProvided bool 35 artifactData []specutils.FileInfo 36 } 37 38 type NpmPublishCommand struct { 39 configFilePath string 40 commandName string 41 *NpmPublishCommandArgs 42 } 43 44 func NewNpmPublishCommand() *NpmPublishCommand { 45 return &NpmPublishCommand{NpmPublishCommandArgs: NewNpmPublishCommandArgs(), commandName: "rt_npm_publish"} 46 } 47 48 func NewNpmPublishCommandArgs() *NpmPublishCommandArgs { 49 return &NpmPublishCommandArgs{} 50 } 51 52 func (npc *NpmPublishCommand) RtDetails() (*config.ArtifactoryDetails, error) { 53 return npc.rtDetails, nil 54 } 55 56 func (npc *NpmPublishCommand) SetConfigFilePath(configFilePath string) *NpmPublishCommand { 57 npc.configFilePath = configFilePath 58 return npc 59 } 60 61 func (nic *NpmPublishCommand) SetArgs(args []string) *NpmPublishCommand { 62 nic.NpmPublishCommandArgs.npmArgs = args 63 return nic 64 } 65 66 func (npc *NpmPublishCommand) Run() error { 67 if npc.configFilePath != "" { 68 // Read config file. 69 log.Debug("Preparing to read the config file", npc.configFilePath) 70 vConfig, err := utils.ReadConfigFile(npc.configFilePath, utils.YAML) 71 if err != nil { 72 return err 73 } 74 deployerParams, err := utils.GetRepoConfigByPrefix(npc.configFilePath, utils.ProjectConfigDeployerPrefix, vConfig) 75 if err != nil { 76 return err 77 } 78 _, _, filteredNpmArgs, buildConfiguration, err := npm.ExtractNpmOptionsFromArgs(npc.NpmPublishCommandArgs.npmArgs) 79 if err != nil { 80 return err 81 } 82 rtDetails, err := deployerParams.RtDetails() 83 if err != nil { 84 return errorutils.CheckError(err) 85 } 86 npc.SetBuildConfiguration(buildConfiguration).SetRepo(deployerParams.TargetRepo()).SetNpmArgs(filteredNpmArgs).SetRtDetails(rtDetails) 87 } 88 return npc.run() 89 } 90 91 func (npc *NpmPublishCommand) run() error { 92 log.Info("Running npm Publish") 93 if err := npc.preparePrerequisites(); err != nil { 94 return err 95 } 96 97 if !npc.tarballProvided { 98 if err := npc.pack(); err != nil { 99 return err 100 } 101 } 102 103 if err := npc.deploy(); err != nil { 104 if npc.tarballProvided { 105 return err 106 } 107 // We should delete the tarball we created 108 return deleteCreatedTarballAndError(npc.packedFilePath, err) 109 } 110 111 if !npc.tarballProvided { 112 if err := deleteCreatedTarball(npc.packedFilePath); err != nil { 113 return err 114 } 115 } 116 117 if !npc.collectBuildInfo { 118 log.Info("npm publish finished successfully.") 119 return nil 120 } 121 122 if err := npc.saveArtifactData(); err != nil { 123 return err 124 } 125 126 log.Info("npm publish finished successfully.") 127 return nil 128 } 129 130 func (npc *NpmPublishCommand) CommandName() string { 131 return npc.commandName 132 } 133 134 func (npc *NpmPublishCommand) preparePrerequisites() error { 135 log.Debug("Preparing prerequisites.") 136 npmExecPath, err := exec.LookPath("npm") 137 if err != nil { 138 return errorutils.CheckError(err) 139 } 140 141 if npmExecPath == "" { 142 return errorutils.CheckError(errors.New("Could not find 'npm' executable")) 143 } 144 145 npc.executablePath = npmExecPath 146 log.Debug("Using npm executable:", npc.executablePath) 147 currentDir, err := os.Getwd() 148 if err != nil { 149 return errorutils.CheckError(err) 150 } 151 152 currentDir, err = filepath.Abs(currentDir) 153 if err != nil { 154 return errorutils.CheckError(err) 155 } 156 157 npc.workingDirectory = currentDir 158 log.Debug("Working directory set to:", npc.workingDirectory) 159 npc.collectBuildInfo = len(npc.buildConfiguration.BuildName) > 0 && len(npc.buildConfiguration.BuildNumber) > 0 160 if err = npc.setPublishPath(); err != nil { 161 return err 162 } 163 164 artDetails, err := npc.rtDetails.CreateArtAuthConfig() 165 if err != nil { 166 return err 167 } 168 169 if err = utils.CheckIfRepoExists(npc.repo, artDetails); err != nil { 170 return err 171 } 172 173 return npc.setPackageInfo() 174 } 175 176 func (npc *NpmPublishCommand) pack() error { 177 log.Debug("Creating npm package.") 178 if err := npm.Pack(npc.npmArgs, npc.executablePath); err != nil { 179 return err 180 } 181 182 npc.packedFilePath = filepath.Join(npc.workingDirectory, npc.packageInfo.GetExpectedPackedFileName()) 183 log.Debug("Created npm package at", npc.packedFilePath) 184 return nil 185 } 186 187 func (npc *NpmPublishCommand) deploy() (err error) { 188 log.Debug("Deploying npm package.") 189 if err = npc.readPackageInfoFromTarball(); err != nil { 190 return err 191 } 192 193 target := fmt.Sprintf("%s/%s", npc.repo, npc.packageInfo.GetDeployPath()) 194 artifactsFileInfo, err := npc.doDeploy(target, npc.rtDetails) 195 if err != nil { 196 return err 197 } 198 199 npc.artifactData = artifactsFileInfo 200 return nil 201 } 202 203 func (npc *NpmPublishCommand) doDeploy(target string, artDetails *config.ArtifactoryDetails) (artifactsFileInfo []specutils.FileInfo, err error) { 204 servicesManager, err := utils.CreateServiceManager(artDetails, false) 205 if err != nil { 206 return nil, err 207 } 208 up := services.UploadParams{} 209 up.ArtifactoryCommonParams = &specutils.ArtifactoryCommonParams{Pattern: npc.packedFilePath, Target: target} 210 if npc.collectBuildInfo { 211 utils.SaveBuildGeneralDetails(npc.buildConfiguration.BuildName, npc.buildConfiguration.BuildNumber) 212 props, err := utils.CreateBuildProperties(npc.buildConfiguration.BuildName, npc.buildConfiguration.BuildNumber) 213 if err != nil { 214 return nil, err 215 } 216 up.ArtifactoryCommonParams.Props = props 217 } 218 resultsReader, _, failed, err := servicesManager.UploadFilesWithResultReader(up) 219 defer resultsReader.Close() 220 err, resultBuildInfo := utils.ReadResultBuildInfo(resultsReader) 221 if err != nil { 222 return nil, err 223 } 224 artifactsFileInfo = resultBuildInfo.FilesInfo 225 226 // We deploying only one Artifact which have to be deployed, in case of failure we should fail 227 if failed > 0 { 228 return nil, errorutils.CheckError(errors.New("Failed to upload the npm package to Artifactory. See Artifactory logs for more details.")) 229 } 230 return artifactsFileInfo, nil 231 } 232 233 func (npc *NpmPublishCommand) saveArtifactData() error { 234 log.Debug("Saving npm package artifact build info data.") 235 var buildArtifacts []buildinfo.Artifact 236 for _, artifact := range npc.artifactData { 237 buildArtifacts = append(buildArtifacts, artifact.ToBuildArtifacts()) 238 } 239 240 populateFunc := func(partial *buildinfo.Partial) { 241 partial.Artifacts = buildArtifacts 242 if npc.buildConfiguration.Module == "" { 243 npc.buildConfiguration.Module = npc.packageInfo.BuildInfoModuleId() 244 } 245 partial.ModuleId = npc.buildConfiguration.Module 246 partial.ModuleType = buildinfo.Npm 247 } 248 return utils.SavePartialBuildInfo(npc.buildConfiguration.BuildName, npc.buildConfiguration.BuildNumber, populateFunc) 249 } 250 251 func (npc *NpmPublishCommand) setPublishPath() error { 252 log.Debug("Reading Package Json.") 253 254 npc.publishPath = npc.workingDirectory 255 if len(npc.npmArgs) > 0 && !strings.HasPrefix(strings.TrimSpace(npc.npmArgs[0]), "-") { 256 path := strings.TrimSpace(npc.npmArgs[0]) 257 path = clientutils.ReplaceTildeWithUserHome(path) 258 259 if filepath.IsAbs(path) { 260 npc.publishPath = path 261 } else { 262 npc.publishPath = filepath.Join(npc.workingDirectory, path) 263 } 264 } 265 return nil 266 } 267 268 func (npc *NpmPublishCommand) setPackageInfo() error { 269 log.Debug("Setting Package Info.") 270 fileInfo, err := os.Stat(npc.publishPath) 271 if err != nil { 272 return errorutils.CheckError(err) 273 } 274 275 if fileInfo.IsDir() { 276 npc.packageInfo, err = npm.ReadPackageInfoFromPackageJson(npc.publishPath) 277 return err 278 } 279 log.Debug("The provided path is not a directory, we assume this is a compressed npm package") 280 npc.tarballProvided = true 281 npc.packedFilePath = npc.publishPath 282 return npc.readPackageInfoFromTarball() 283 } 284 285 func (npc *NpmPublishCommand) readPackageInfoFromTarball() error { 286 log.Debug("Extracting info from npm package:", npc.packedFilePath) 287 tarball, err := os.Open(npc.packedFilePath) 288 if err != nil { 289 return errorutils.CheckError(err) 290 } 291 defer tarball.Close() 292 gZipReader, err := gzip.NewReader(tarball) 293 if err != nil { 294 return errorutils.CheckError(err) 295 } 296 297 tarReader := tar.NewReader(gZipReader) 298 for { 299 hdr, err := tarReader.Next() 300 if err != nil { 301 if err == io.EOF { 302 return errorutils.CheckError(errors.New("Could not find 'package.json' in the compressed npm package: " + npc.packedFilePath)) 303 } 304 return errorutils.CheckError(err) 305 } 306 parent := filepath.Dir(hdr.Name) 307 if filepath.Base(parent) == "package" && strings.HasSuffix(hdr.Name, "package.json") { 308 packageJson, err := ioutil.ReadAll(tarReader) 309 if err != nil { 310 return errorutils.CheckError(err) 311 } 312 313 npc.packageInfo, err = npm.ReadPackageInfo(packageJson) 314 return err 315 } 316 } 317 } 318 319 func deleteCreatedTarballAndError(packedFilePath string, currentError error) error { 320 if err := deleteCreatedTarball(packedFilePath); err != nil { 321 errorText := fmt.Sprintf("Two errors occurred: \n%s \n%s", currentError, err) 322 return errorutils.CheckError(errors.New(errorText)) 323 } 324 return currentError 325 } 326 327 func deleteCreatedTarball(packedFilePath string) error { 328 if err := os.Remove(packedFilePath); err != nil { 329 return errorutils.CheckError(err) 330 } 331 log.Debug("Successfully deleted the created npm package:", packedFilePath) 332 return nil 333 }