github.com/jaylevin/jenkins-library@v1.230.4/pkg/maven/settings.go (about)

     1  package maven
     2  
     3  import (
     4  	"encoding/xml"
     5  	"fmt"
     6  	"net/http"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/SAP/jenkins-library/pkg/log"
    12  )
    13  
    14  var getenv = os.Getenv
    15  
    16  // SettingsDownloadUtils defines an interface for downloading and storing maven settings files.
    17  type SettingsDownloadUtils interface {
    18  	DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error
    19  	FileExists(filename string) (bool, error)
    20  	Copy(src, dest string) (int64, error)
    21  	MkdirAll(path string, perm os.FileMode) error
    22  	FileWrite(path string, content []byte, perm os.FileMode) error
    23  	FileRead(path string) ([]byte, error)
    24  }
    25  
    26  // DownloadAndGetMavenParameters downloads the global or project settings file if the strings contain URLs.
    27  // It then constructs the arguments that need to be passed to maven in order to point to use these settings files.
    28  func DownloadAndGetMavenParameters(globalSettingsFile string, projectSettingsFile string, utils SettingsDownloadUtils) ([]string, error) {
    29  	mavenArgs := []string{}
    30  	if len(globalSettingsFile) > 0 {
    31  		globalSettingsFileName, err := downloadSettingsIfURL(globalSettingsFile, ".pipeline/mavenGlobalSettings.xml", utils, false)
    32  		if err != nil {
    33  			return nil, err
    34  		}
    35  		mavenArgs = append(mavenArgs, "--global-settings", globalSettingsFileName)
    36  	} else {
    37  
    38  		log.Entry().Debugf("Global settings file not provided via configuration.")
    39  	}
    40  
    41  	if len(projectSettingsFile) > 0 {
    42  		projectSettingsFileName, err := downloadSettingsIfURL(projectSettingsFile, ".pipeline/mavenProjectSettings.xml", utils, false)
    43  		if err != nil {
    44  			return nil, err
    45  		}
    46  		mavenArgs = append(mavenArgs, "--settings", projectSettingsFileName)
    47  	} else {
    48  
    49  		log.Entry().Debugf("Project settings file not provided via configuration.")
    50  	}
    51  	return mavenArgs, nil
    52  }
    53  
    54  // DownloadAndCopySettingsFiles downloads the global or project settings file if the strings contain URLs.
    55  // It copies the given files to either the locations specified in the environment variables M2_HOME and HOME
    56  // or the default locations where maven expects them.
    57  func DownloadAndCopySettingsFiles(globalSettingsFile string, projectSettingsFile string, utils SettingsDownloadUtils) error {
    58  	if len(projectSettingsFile) > 0 {
    59  		destination, err := getProjectSettingsFileDest()
    60  		if err != nil {
    61  			return err
    62  		}
    63  
    64  		if err := downloadAndCopySettingsFile(projectSettingsFile, destination, utils); err != nil {
    65  			return err
    66  		}
    67  	} else {
    68  
    69  		log.Entry().Debugf("Project settings file not provided via configuration.")
    70  	}
    71  
    72  	if len(globalSettingsFile) > 0 {
    73  		destination, err := getGlobalSettingsFileDest()
    74  		if err != nil {
    75  			return err
    76  		}
    77  		if err := downloadAndCopySettingsFile(globalSettingsFile, destination, utils); err != nil {
    78  			return err
    79  		}
    80  	} else {
    81  
    82  		log.Entry().Debugf("Global settings file not provided via configuration.")
    83  	}
    84  
    85  	return nil
    86  }
    87  
    88  func UpdateActiveProfileInSettingsXML(newActiveProfiles []string, utils SettingsDownloadUtils) error {
    89  	settingsFile, err := getGlobalSettingsFileDest()
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	settingsXMLContent, err := utils.FileRead(settingsFile)
    95  	if err != nil {
    96  		return fmt.Errorf("error reading global settings xml file at %v , continuing without active profile update", settingsFile)
    97  	}
    98  
    99  	var projectSettings Settings
   100  	err = xml.Unmarshal([]byte(settingsXMLContent), &projectSettings)
   101  
   102  	if err != nil {
   103  		return fmt.Errorf("failed to unmarshal settings xml file '%v': %w", settingsFile, err)
   104  	}
   105  
   106  	if len(projectSettings.ActiveProfiles.ActiveProfile) == 0 {
   107  		log.Entry().Warnf("no active profile found to replace in settings xml %v , continuing without file edit", settingsFile)
   108  	} else {
   109  		projectSettings.Xsi = "http://www.w3.org/2001/XMLSchema-instance"
   110  		projectSettings.SchemaLocation = "http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd"
   111  
   112  		projectSettings.ActiveProfiles.ActiveProfile = []string{}
   113  		projectSettings.ActiveProfiles.ActiveProfile = append(projectSettings.ActiveProfiles.ActiveProfile, newActiveProfiles...)
   114  
   115  		settingsXml, err := xml.MarshalIndent(projectSettings, "", "    ")
   116  		if err != nil {
   117  			return fmt.Errorf("failed to marshal maven project settings xml: %w", err)
   118  		}
   119  
   120  		settingsXmlString := string(settingsXml)
   121  		Replacer := strings.NewReplacer("
", "", "	", "")
   122  		settingsXmlString = Replacer.Replace(settingsXmlString)
   123  		xmlstring := []byte(xml.Header + settingsXmlString)
   124  
   125  		err = utils.FileWrite(settingsFile, xmlstring, 0777)
   126  
   127  		if err != nil {
   128  			return fmt.Errorf("failed to write maven Settings during <activeProfile> update xml: %w", err)
   129  		}
   130  		log.Entry().Infof("Successfully updated <acitveProfile> details in maven settings file : '%s'", settingsFile)
   131  
   132  	}
   133  	return nil
   134  }
   135  
   136  func CreateNewProjectSettingsXML(altDeploymentRepositoryID string, altDeploymentRepositoryUser string, altDeploymentRepositoryPassword string, utils SettingsDownloadUtils) (string, error) {
   137  	settingsXML := Settings{
   138  		XMLName:        xml.Name{Local: "settings"},
   139  		Xsi:            "http://www.w3.org/2001/XMLSchema-instance",
   140  		SchemaLocation: "http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd",
   141  		Servers: ServersType{
   142  			ServerType: []Server{
   143  				{
   144  					ID:       altDeploymentRepositoryID,
   145  					Username: altDeploymentRepositoryUser,
   146  					Password: altDeploymentRepositoryPassword,
   147  				},
   148  			},
   149  		},
   150  	}
   151  
   152  	xmlstring, err := xml.MarshalIndent(settingsXML, "", "    ")
   153  	if err != nil {
   154  		return "", fmt.Errorf("failed to marshal Settings.xml: %w", err)
   155  	}
   156  
   157  	xmlstring = []byte(xml.Header + string(xmlstring))
   158  
   159  	err = utils.FileWrite(".pipeline/mavenProjectSettings.xml", xmlstring, 0777)
   160  	if err != nil {
   161  		return "", fmt.Errorf("failed to write maven Project Settings xml: %w", err)
   162  	}
   163  
   164  	log.Entry().Infof("Successfully created maven project settings with <server> details at .pipeline/mavenProjectSettings.xml")
   165  
   166  	return ".pipeline/mavenProjectSettings.xml", nil
   167  
   168  }
   169  
   170  func UpdateProjectSettingsXML(projectSettingsFile string, altDeploymentRepositoryID string, altDeploymentRepositoryUser string, altDeploymentRepositoryPassword string, utils SettingsDownloadUtils) (string, error) {
   171  	projectSettingsFileDestination := ".pipeline/mavenProjectSettings"
   172  	if exists, _ := utils.FileExists(projectSettingsFile); exists {
   173  		projectSettingsFileDestination = projectSettingsFile
   174  		addServerTagtoProjectSettingsXML(projectSettingsFile, altDeploymentRepositoryID, altDeploymentRepositoryUser, altDeploymentRepositoryPassword, utils)
   175  	} else {
   176  		addServerTagtoProjectSettingsXML(".pipeline/mavenProjectSettings", altDeploymentRepositoryID, altDeploymentRepositoryUser, altDeploymentRepositoryPassword, utils)
   177  	}
   178  	return projectSettingsFileDestination, nil
   179  
   180  }
   181  
   182  func addServerTagtoProjectSettingsXML(projectSettingsFile string, altDeploymentRepositoryID string, altDeploymentRepositoryUser string, altDeploymentRepositoryPassword string, utils SettingsDownloadUtils) error {
   183  	var projectSettings Settings
   184  	settingsXMLContent, err := utils.FileRead(projectSettingsFile)
   185  	if err != nil {
   186  		return fmt.Errorf("failed to read file '%v': %w", projectSettingsFile, err)
   187  	}
   188  
   189  	err = xml.Unmarshal([]byte(settingsXMLContent), &projectSettings)
   190  	if err != nil {
   191  		return fmt.Errorf("failed to unmarshal settings xml file '%v': %w", projectSettingsFile, err)
   192  	}
   193  
   194  	if len(projectSettings.Servers.ServerType) == 0 {
   195  		projectSettings.Xsi = "http://www.w3.org/2001/XMLSchema-instance"
   196  		projectSettings.SchemaLocation = "http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd"
   197  		projectSettings.Servers.ServerType = []Server{
   198  			{
   199  				ID:       altDeploymentRepositoryID,
   200  				Username: altDeploymentRepositoryUser,
   201  				Password: altDeploymentRepositoryPassword,
   202  			},
   203  		}
   204  	} else if len(projectSettings.Servers.ServerType) > 0 { // if <server> tag is present then add the staging server tag
   205  		stagingServer := Server{
   206  			ID:       altDeploymentRepositoryID,
   207  			Username: altDeploymentRepositoryUser,
   208  			Password: altDeploymentRepositoryPassword,
   209  		}
   210  		projectSettings.Servers.ServerType = append(projectSettings.Servers.ServerType, stagingServer)
   211  	}
   212  
   213  	settingsXml, err := xml.MarshalIndent(projectSettings, "", "    ")
   214  	if err != nil {
   215  		fmt.Errorf("failed to marshal maven project settings xml: %w", err)
   216  	}
   217  	settingsXmlString := string(settingsXml)
   218  	Replacer := strings.NewReplacer("&#xA;", "", "&#x9;", "")
   219  	settingsXmlString = Replacer.Replace(settingsXmlString)
   220  
   221  	xmlstring := []byte(xml.Header + settingsXmlString)
   222  
   223  	err = utils.FileWrite(projectSettingsFile, xmlstring, 0777)
   224  	if err != nil {
   225  		fmt.Errorf("failed to write maven Settings xml: %w", err)
   226  	}
   227  	log.Entry().Infof("Successfully updated <server> details in maven project settings file : '%s'", projectSettingsFile)
   228  
   229  	return nil
   230  }
   231  
   232  func downloadAndCopySettingsFile(src string, dest string, utils SettingsDownloadUtils) error {
   233  	if len(src) == 0 {
   234  		return fmt.Errorf("Settings file source location not provided")
   235  	}
   236  
   237  	if len(dest) == 0 {
   238  		return fmt.Errorf("Settings file destination location not provided")
   239  	}
   240  
   241  	log.Entry().Debugf("Copying file \"%s\" to \"%s\"", src, dest)
   242  
   243  	if strings.HasPrefix(src, "http:") || strings.HasPrefix(src, "https:") {
   244  		err := downloadSettingsFromURL(src, dest, utils, true)
   245  		if err != nil {
   246  			return err
   247  		}
   248  	} else {
   249  
   250  		// for sake os symmetry it would be better to use a file protocol prefix here (file:)
   251  
   252  		parent := filepath.Dir(dest)
   253  
   254  		parentFolderExists, err := utils.FileExists(parent)
   255  
   256  		if err != nil {
   257  			return err
   258  		}
   259  
   260  		if !parentFolderExists {
   261  			if err = utils.MkdirAll(parent, 0775); err != nil {
   262  				return err
   263  			}
   264  		}
   265  
   266  		if _, err := utils.Copy(src, dest); err != nil {
   267  			return err
   268  		}
   269  	}
   270  
   271  	return nil
   272  }
   273  
   274  func downloadSettingsIfURL(settingsFileOption, settingsFile string, utils SettingsDownloadUtils, overwrite bool) (string, error) {
   275  	result := settingsFileOption
   276  	if strings.HasPrefix(settingsFileOption, "http:") || strings.HasPrefix(settingsFileOption, "https:") {
   277  		err := downloadSettingsFromURL(settingsFileOption, settingsFile, utils, overwrite)
   278  		if err != nil {
   279  			return "", err
   280  		}
   281  		result = settingsFile
   282  	}
   283  	return result, nil
   284  }
   285  
   286  func downloadSettingsFromURL(url, filename string, utils SettingsDownloadUtils, overwrite bool) error {
   287  	exists, _ := utils.FileExists(filename)
   288  	if exists && !overwrite {
   289  		log.Entry().Infof("Not downloading maven settings file, because it already exists at '%s'", filename)
   290  		return nil
   291  	}
   292  	err := utils.DownloadFile(url, filename, nil, nil)
   293  	if err != nil {
   294  		return fmt.Errorf("failed to download maven settings from URL '%s' to file '%s': %w",
   295  			url, filename, err)
   296  	}
   297  	return nil
   298  }
   299  
   300  func getGlobalSettingsFileDest() (string, error) {
   301  
   302  	m2Home, err := getEnvironmentVariable("M2_HOME")
   303  	if err != nil {
   304  		return "", err
   305  	}
   306  	return filepath.Join(m2Home, "conf", "settings.xml"), nil
   307  }
   308  
   309  func getProjectSettingsFileDest() (string, error) {
   310  	home, err := getEnvironmentVariable("HOME")
   311  	if err != nil {
   312  		return "", err
   313  	}
   314  	return filepath.Join(home, ".m2", "settings.xml"), nil
   315  }
   316  
   317  func getEnvironmentVariable(name string) (string, error) {
   318  
   319  	envVar := getenv(name)
   320  
   321  	if len(envVar) == 0 {
   322  		return "", fmt.Errorf("Environment variable \"%s\" not set or empty", name)
   323  	}
   324  
   325  	return envVar, nil
   326  }