github.com/jfrog/jfrog-cli-go@v1.22.1-0.20200318093948-4826ef344ffd/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-go/artifactory/utils"
    16  	"github.com/jfrog/jfrog-cli-go/artifactory/utils/npm"
    17  	"github.com/jfrog/jfrog-cli-go/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  	artifactsFileInfo, _, failed, err := servicesManager.UploadFiles(up)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  
   223  	// We deploying only one Artifact which have to be deployed, in case of failure we should fail
   224  	if failed > 0 {
   225  		return nil, errorutils.CheckError(errors.New("Failed to upload the npm package to Artifactory. See Artifactory logs for more details."))
   226  	}
   227  	return artifactsFileInfo, nil
   228  }
   229  
   230  func (npc *NpmPublishCommand) saveArtifactData() error {
   231  	log.Debug("Saving npm package artifact build info data.")
   232  	var buildArtifacts []buildinfo.Artifact
   233  	for _, artifact := range npc.artifactData {
   234  		buildArtifacts = append(buildArtifacts, artifact.ToBuildArtifacts())
   235  	}
   236  
   237  	populateFunc := func(partial *buildinfo.Partial) {
   238  		partial.Artifacts = buildArtifacts
   239  		if npc.buildConfiguration.Module == "" {
   240  			npc.buildConfiguration.Module = npc.packageInfo.BuildInfoModuleId()
   241  		}
   242  		partial.ModuleId = npc.buildConfiguration.Module
   243  	}
   244  	return utils.SavePartialBuildInfo(npc.buildConfiguration.BuildName, npc.buildConfiguration.BuildNumber, populateFunc)
   245  }
   246  
   247  func (npc *NpmPublishCommand) setPublishPath() error {
   248  	log.Debug("Reading Package Json.")
   249  
   250  	npc.publishPath = npc.workingDirectory
   251  	if len(npc.npmArgs) > 0 && !strings.HasPrefix(strings.TrimSpace(npc.npmArgs[0]), "-") {
   252  		path := strings.TrimSpace(npc.npmArgs[0])
   253  		path = clientutils.ReplaceTildeWithUserHome(path)
   254  
   255  		if filepath.IsAbs(path) {
   256  			npc.publishPath = path
   257  		} else {
   258  			npc.publishPath = filepath.Join(npc.workingDirectory, path)
   259  		}
   260  	}
   261  	return nil
   262  }
   263  
   264  func (npc *NpmPublishCommand) setPackageInfo() error {
   265  	log.Debug("Setting Package Info.")
   266  	fileInfo, err := os.Stat(npc.publishPath)
   267  	if err != nil {
   268  		return errorutils.CheckError(err)
   269  	}
   270  
   271  	if fileInfo.IsDir() {
   272  		npc.packageInfo, err = npm.ReadPackageInfoFromPackageJson(npc.publishPath)
   273  		return err
   274  	}
   275  	log.Debug("The provided path is not a directory, we assume this is a compressed npm package")
   276  	npc.tarballProvided = true
   277  	npc.packedFilePath = npc.publishPath
   278  	return npc.readPackageInfoFromTarball()
   279  }
   280  
   281  func (npc *NpmPublishCommand) readPackageInfoFromTarball() error {
   282  	log.Debug("Extracting info from npm package:", npc.packedFilePath)
   283  	tarball, err := os.Open(npc.packedFilePath)
   284  	if err != nil {
   285  		return errorutils.CheckError(err)
   286  	}
   287  	defer tarball.Close()
   288  	gZipReader, err := gzip.NewReader(tarball)
   289  	if err != nil {
   290  		return errorutils.CheckError(err)
   291  	}
   292  
   293  	tarReader := tar.NewReader(gZipReader)
   294  	for {
   295  		hdr, err := tarReader.Next()
   296  		if err != nil {
   297  			if err == io.EOF {
   298  				return errorutils.CheckError(errors.New("Could not find 'package.json' in the compressed npm package: " + npc.packedFilePath))
   299  			}
   300  			return errorutils.CheckError(err)
   301  		}
   302  		parent := filepath.Dir(hdr.Name)
   303  		if filepath.Base(parent) == "package" && strings.HasSuffix(hdr.Name, "package.json") {
   304  			packageJson, err := ioutil.ReadAll(tarReader)
   305  			if err != nil {
   306  				return errorutils.CheckError(err)
   307  			}
   308  
   309  			npc.packageInfo, err = npm.ReadPackageInfo(packageJson)
   310  			return err
   311  		}
   312  	}
   313  }
   314  
   315  func deleteCreatedTarballAndError(packedFilePath string, currentError error) error {
   316  	if err := deleteCreatedTarball(packedFilePath); err != nil {
   317  		errorText := fmt.Sprintf("Two errors occurred: \n%s \n%s", currentError, err)
   318  		return errorutils.CheckError(errors.New(errorText))
   319  	}
   320  	return currentError
   321  }
   322  
   323  func deleteCreatedTarball(packedFilePath string) error {
   324  	if err := os.Remove(packedFilePath); err != nil {
   325  		return errorutils.CheckError(err)
   326  	}
   327  	log.Debug("Successfully deleted the created npm package:", packedFilePath)
   328  	return nil
   329  }