github.com/jfrog/jfrog-cli-core@v1.12.1/artifactory/utils/buildinfoproperties.go (about)

     1  package utils
     2  
     3  import (
     4  	"errors"
     5  	"net"
     6  	"net/url"
     7  	"os"
     8  	"path/filepath"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/jfrog/jfrog-cli-core/utils/ioutils"
    13  
    14  	"github.com/jfrog/jfrog-cli-core/utils/config"
    15  	"github.com/jfrog/jfrog-cli-core/utils/coreutils"
    16  
    17  	"github.com/jfrog/jfrog-client-go/auth"
    18  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    19  	"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
    20  	"github.com/spf13/viper"
    21  )
    22  
    23  const (
    24  	HttpProxy = "HTTP_PROXY"
    25  )
    26  
    27  type BuildConfigMapping map[ProjectType][]*map[string]string
    28  
    29  var buildTypeConfigMapping = BuildConfigMapping{
    30  	Maven:  {&commonConfigMapping, &mavenConfigMapping},
    31  	Gradle: {&commonConfigMapping, &gradleConfigMapping},
    32  }
    33  
    34  type ConfigType string
    35  
    36  const (
    37  	YAML       ConfigType = "yaml"
    38  	PROPERTIES ConfigType = "properties"
    39  )
    40  
    41  // For key/value binding
    42  const BuildName = "build.name"
    43  const BuildNumber = "build.number"
    44  const BuildProject = "build.project"
    45  const BuildTimestamp = "build.timestamp"
    46  const GeneratedBuildInfo = "buildInfo.generated"
    47  const DeployableArtifacts = "deployable.artifacts.map"
    48  const InsecureTls = "insecureTls"
    49  
    50  const ResolverPrefix = "resolver."
    51  const DeployerPrefix = "deployer."
    52  
    53  const Repo = "repo"
    54  const SnapshotRepo = "snapshotRepo"
    55  const ReleaseRepo = "releaseRepo"
    56  
    57  const ServerId = "serverId"
    58  const Url = "url"
    59  const Username = "username"
    60  const Password = "password"
    61  const DeployArtifacts = "artifacts"
    62  
    63  const MavenDescriptor = "deployMavenDescriptors"
    64  const IvyDescriptor = "deployIvyDescriptors"
    65  const IvyPattern = "ivyPattern"
    66  const ArtifactPattern = "artifactPattern"
    67  const ForkCount = "forkCount"
    68  
    69  const IncludePatterns = "includePatterns"
    70  const ExcludePatterns = "excludePatterns"
    71  const FilterExcludedArtifactsFromBuild = "filterExcludedArtifactsFromBuild"
    72  
    73  // For path and temp files
    74  const PropertiesTempPrefix = "buildInfoProperties"
    75  const PropertiesTempPath = "jfrog/properties/"
    76  const GeneratedBuildInfoTempPrefix = "generatedBuildInfo"
    77  
    78  const Proxy = "proxy."
    79  const Host = "host"
    80  const Port = "port"
    81  
    82  // Config mapping are used to create buildInfo properties file to be used by BuildInfo extractors.
    83  // Build config provided by the user may contain other properties that will not be included in the properties file.
    84  var defaultPropertiesValues = map[string]string{
    85  	"artifactory.publish.artifacts":                        "true",
    86  	"artifactory.publish.buildInfo":                        "false",
    87  	"artifactory.publish.unstable":                         "false",
    88  	"artifactory.publish.maven":                            "false",
    89  	"artifactory.publish.ivy":                              "false",
    90  	"buildInfoConfig.includeEnvVars":                       "false",
    91  	"buildInfoConfig.envVarsExcludePatterns":               "*password*,*psw*,*secret*,*key*,*token*",
    92  	"buildInfo.agent.name":                                 coreutils.GetClientAgentName() + "/" + coreutils.GetClientAgentVersion(),
    93  	"org.jfrog.build.extractor.maven.recorder.activate":    "true",
    94  	"buildInfo.env.extractor.used":                         "true",
    95  	"artifactory.publish.forkCount":                        "3",
    96  	"artifactory.publish.filterExcludedArtifactsFromBuild": "true",
    97  }
    98  
    99  var commonConfigMapping = map[string]string{
   100  	"artifactory.publish.buildInfo":          "",
   101  	"artifactory.publish.unstable":           "",
   102  	"buildInfoConfig.includeEnvVars":         "",
   103  	"buildInfoConfig.envVarsExcludePatterns": "",
   104  	"buildInfo.agent.name":                   "",
   105  	"artifactory.resolve.contextUrl":         ResolverPrefix + Url,
   106  	"artifactory.resolve.username":           ResolverPrefix + Username,
   107  	"artifactory.resolve.password":           ResolverPrefix + Password,
   108  	"artifactory.publish.contextUrl":         DeployerPrefix + Url,
   109  	"artifactory.publish.username":           DeployerPrefix + Username,
   110  	"artifactory.publish.password":           DeployerPrefix + Password,
   111  	"artifactory.publish.artifacts":          DeployerPrefix + DeployArtifacts,
   112  	"artifactory.deploy.build.name":          BuildName,
   113  	"artifactory.deploy.build.number":        BuildNumber,
   114  	"artifactory.deploy.build.project":       BuildProject,
   115  	"artifactory.deploy.build.timestamp":     BuildTimestamp,
   116  	"buildInfo.generated.build.info":         GeneratedBuildInfo,
   117  	"buildInfo.deployable.artifacts.map":     DeployableArtifacts,
   118  	"artifactory.proxy.host":                 Proxy + Host,
   119  	"artifactory.proxy.port":                 Proxy + Port,
   120  	"artifactory.publish.forkCount":          ForkCount,
   121  	"artifactory.insecureTls":                InsecureTls,
   122  }
   123  
   124  var mavenConfigMapping = map[string]string{
   125  	"org.jfrog.build.extractor.maven.recorder.activate":    "",
   126  	"artifactory.resolve.repoKey":                          ResolverPrefix + ReleaseRepo,
   127  	"artifactory.resolve.downSnapshotRepoKey":              ResolverPrefix + SnapshotRepo,
   128  	"artifactory.publish.repoKey":                          DeployerPrefix + ReleaseRepo,
   129  	"artifactory.publish.snapshot.repoKey":                 DeployerPrefix + SnapshotRepo,
   130  	"artifactory.publish.includePatterns":                  DeployerPrefix + IncludePatterns,
   131  	"artifactory.publish.excludePatterns":                  DeployerPrefix + ExcludePatterns,
   132  	"artifactory.publish.filterExcludedArtifactsFromBuild": DeployerPrefix + FilterExcludedArtifactsFromBuild,
   133  }
   134  
   135  var gradleConfigMapping = map[string]string{
   136  	"buildInfo.env.extractor.used":                      "",
   137  	"org.jfrog.build.extractor.maven.recorder.activate": "",
   138  	"artifactory.resolve.repoKey":                       ResolverPrefix + Repo,
   139  	"artifactory.resolve.downSnapshotRepoKey":           ResolverPrefix + Repo,
   140  	"artifactory.publish.repoKey":                       DeployerPrefix + Repo,
   141  	"artifactory.publish.snapshot.repoKey":              DeployerPrefix + Repo,
   142  	"artifactory.publish.maven":                         DeployerPrefix + MavenDescriptor,
   143  	"artifactory.publish.ivy":                           DeployerPrefix + IvyDescriptor,
   144  	"artifactory.publish.ivy.ivyPattern":                DeployerPrefix + IvyPattern,
   145  	"artifactory.publish.ivy.artPattern":                DeployerPrefix + ArtifactPattern,
   146  	"buildInfo.build.name":                              BuildName,
   147  	"buildInfo.build.number":                            BuildNumber,
   148  }
   149  
   150  func ReadConfigFile(configPath string, configType ConfigType) (*viper.Viper, error) {
   151  	config := viper.New()
   152  	config.SetConfigType(string(configType))
   153  
   154  	f, err := os.Open(configPath)
   155  	if err != nil {
   156  		return config, errorutils.CheckError(err)
   157  	}
   158  	err = config.ReadConfig(f)
   159  	if err != nil {
   160  		return config, errorutils.CheckError(err)
   161  	}
   162  
   163  	return config, nil
   164  }
   165  
   166  // Returns the Artifactory details
   167  // Checks first for the deployer information if exists and if not, checks for the resolver information.
   168  func GetServerDetails(vConfig *viper.Viper) (*config.ServerDetails, error) {
   169  	if vConfig.IsSet(DeployerPrefix + ServerId) {
   170  		serverId := vConfig.GetString(DeployerPrefix + ServerId)
   171  		return config.GetSpecificConfig(serverId, true, true)
   172  	}
   173  
   174  	if vConfig.IsSet(ResolverPrefix + ServerId) {
   175  		serverId := vConfig.GetString(ResolverPrefix + ServerId)
   176  		return config.GetSpecificConfig(serverId, true, true)
   177  	}
   178  	return nil, nil
   179  }
   180  
   181  func CreateBuildInfoPropertiesFile(buildName, buildNumber, projectKey string, detailedSummary bool, config *viper.Viper, projectType ProjectType) (string, error) {
   182  	if config.GetString("type") != projectType.String() {
   183  		return "", errorutils.CheckError(errors.New("Incompatible build config, expected: " + projectType.String() + " got: " + config.GetString("type")))
   184  	}
   185  
   186  	propertiesPath := filepath.Join(coreutils.GetCliPersistentTempDirPath(), PropertiesTempPath)
   187  	err := os.MkdirAll(propertiesPath, 0777)
   188  	if errorutils.CheckError(err) != nil {
   189  		return "", err
   190  	}
   191  	propertiesFile, err := os.CreateTemp(propertiesPath, PropertiesTempPrefix)
   192  	defer propertiesFile.Close()
   193  	if err != nil {
   194  		return "", errorutils.CheckError(err)
   195  	}
   196  	err = setServerDetailsToConfig(ResolverPrefix, config)
   197  	if err != nil {
   198  		return "", err
   199  	}
   200  	err = setServerDetailsToConfig(DeployerPrefix, config)
   201  	if err != nil {
   202  		return "", err
   203  	}
   204  	if buildName != "" || buildNumber != "" {
   205  		err = SaveBuildGeneralDetails(buildName, buildNumber, projectKey)
   206  		if err != nil {
   207  			return "", err
   208  		}
   209  		err = setBuildTimestampToConfig(buildName, buildNumber, projectKey, config)
   210  		if err != nil {
   211  			return "", err
   212  		}
   213  		err = createGeneratedBuildInfoFile(buildName, buildNumber, projectKey, config)
   214  		if err != nil {
   215  			return "", err
   216  		}
   217  	}
   218  	err = setProxyIfDefined(config)
   219  	if err != nil {
   220  		return "", err
   221  	}
   222  	if detailedSummary {
   223  		err = createDeployableArtifactsFile(config)
   224  		if err != nil {
   225  			return "", err
   226  		}
   227  	}
   228  
   229  	// Iterate over all the required properties keys according to the buildType and create properties file.
   230  	// If a value is provided by the build config file write it,
   231  	// otherwise use the default value from defaultPropertiesValues map.
   232  	for _, partialMapping := range buildTypeConfigMapping[projectType] {
   233  		for propertyKey, configKey := range *partialMapping {
   234  			if config.IsSet(configKey) {
   235  				_, err = propertiesFile.WriteString(propertyKey + "=" + config.GetString(configKey) + "\n")
   236  			} else if defaultVal, ok := defaultPropertiesValues[propertyKey]; ok {
   237  				_, err = propertiesFile.WriteString(propertyKey + "=" + defaultVal + "\n")
   238  			}
   239  			if err != nil {
   240  				return "", errorutils.CheckError(err)
   241  			}
   242  		}
   243  	}
   244  	return propertiesFile.Name(), nil
   245  }
   246  
   247  // If the HTTP_PROXY environment variable is set, add to the config proxy details.
   248  func setProxyIfDefined(config *viper.Viper) error {
   249  	// Add HTTP_PROXY if exists
   250  	proxy := os.Getenv(HttpProxy)
   251  	if proxy != "" {
   252  		url, err := url.Parse(proxy)
   253  		if err != nil {
   254  			return errorutils.CheckError(err)
   255  		}
   256  		host, port, err := net.SplitHostPort(url.Host)
   257  		if err != nil {
   258  			return errorutils.CheckError(err)
   259  		}
   260  		config.Set(Proxy+Host, host)
   261  		config.Set(Proxy+Port, port)
   262  	}
   263  	return nil
   264  }
   265  
   266  func setServerDetailsToConfig(contextPrefix string, vConfig *viper.Viper) error {
   267  	if !vConfig.IsSet(contextPrefix + ServerId) {
   268  		return nil
   269  	}
   270  
   271  	serverId := vConfig.GetString(contextPrefix + ServerId)
   272  	artDetails, err := config.GetSpecificConfig(serverId, true, true)
   273  	if err != nil {
   274  		return err
   275  	}
   276  	if artDetails.GetArtifactoryUrl() == "" {
   277  		return errorutils.CheckError(errors.New("Server ID " + serverId + ": URL is required."))
   278  	}
   279  	vConfig.Set(contextPrefix+Url, artDetails.GetArtifactoryUrl())
   280  
   281  	if artDetails.GetApiKey() != "" {
   282  		return errorutils.CheckError(errors.New("Server ID " + serverId + ": Configuring an API key without a username is not supported."))
   283  	}
   284  
   285  	if artDetails.GetAccessToken() != "" {
   286  		username, err := auth.ExtractUsernameFromAccessToken(artDetails.GetAccessToken())
   287  		if err != nil {
   288  			return err
   289  		}
   290  		vConfig.Set(contextPrefix+Username, username)
   291  		vConfig.Set(contextPrefix+Password, artDetails.GetAccessToken())
   292  		return nil
   293  	}
   294  
   295  	if artDetails.GetUser() != "" && artDetails.GetPassword() != "" {
   296  		vConfig.Set(contextPrefix+Username, artDetails.GetUser())
   297  		vConfig.Set(contextPrefix+Password, artDetails.GetPassword())
   298  	}
   299  	return nil
   300  }
   301  
   302  // Generated build info file is template file where build-info will be written to during the
   303  // Maven or Gradle build.
   304  // Creating this file only if build name and number is provided.
   305  func createGeneratedBuildInfoFile(buildName, buildNumber, projectKey string, config *viper.Viper) error {
   306  	config.Set(BuildName, buildName)
   307  	config.Set(BuildNumber, buildNumber)
   308  	config.Set(BuildProject, projectKey)
   309  
   310  	buildPath, err := GetBuildDir(buildName, buildNumber, projectKey)
   311  	if err != nil {
   312  		return err
   313  	}
   314  	var tempFile *os.File
   315  	tempFile, err = os.CreateTemp(buildPath, GeneratedBuildInfoTempPrefix)
   316  	defer tempFile.Close()
   317  	if err != nil {
   318  		return err
   319  	}
   320  	// If this is a Windows machine there is a need to modify the path for the build info file to match Java syntax with double \\
   321  	path := ioutils.DoubleWinPathSeparator(tempFile.Name())
   322  	config.Set(GeneratedBuildInfo, path)
   323  	return nil
   324  }
   325  
   326  func createDeployableArtifactsFile(config *viper.Viper) error {
   327  	tempFile, err := fileutils.CreateTempFile()
   328  	defer tempFile.Close()
   329  	if err != nil {
   330  		return err
   331  	}
   332  	// If this is a Windows machine there is a need to modify the path for the build info file to match Java syntax with double \\
   333  	path := ioutils.DoubleWinPathSeparator(tempFile.Name())
   334  	config.Set(DeployableArtifacts, path)
   335  	return nil
   336  }
   337  func setBuildTimestampToConfig(buildName, buildNumber, projectKey string, config *viper.Viper) error {
   338  	buildGeneralDetails, err := ReadBuildInfoGeneralDetails(buildName, buildNumber, projectKey)
   339  	if err != nil {
   340  		return err
   341  	}
   342  	config.Set(BuildTimestamp, strconv.FormatInt(buildGeneralDetails.Timestamp.UnixNano()/int64(time.Millisecond), 10))
   343  	return nil
   344  }