package maven import ( "encoding/xml" "fmt" "net/http" "os" "path/filepath" "strings" "github.com/SAP/jenkins-library/pkg/log" ) var getenv = os.Getenv // SettingsDownloadUtils defines an interface for downloading and storing maven settings files. type SettingsDownloadUtils interface { DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error FileExists(filename string) (bool, error) Copy(src, dest string) (int64, error) MkdirAll(path string, perm os.FileMode) error FileWrite(path string, content []byte, perm os.FileMode) error FileRead(path string) ([]byte, error) } // DownloadAndGetMavenParameters downloads the global or project settings file if the strings contain URLs. // It then constructs the arguments that need to be passed to maven in order to point to use these settings files. func DownloadAndGetMavenParameters(globalSettingsFile string, projectSettingsFile string, utils SettingsDownloadUtils) ([]string, error) { mavenArgs := []string{} if len(globalSettingsFile) > 0 { globalSettingsFileName, err := downloadSettingsIfURL(globalSettingsFile, ".pipeline/mavenGlobalSettings.xml", utils, false) if err != nil { return nil, err } mavenArgs = append(mavenArgs, "--global-settings", globalSettingsFileName) } else { log.Entry().Debugf("Global settings file not provided via configuration.") } if len(projectSettingsFile) > 0 { projectSettingsFileName, err := downloadSettingsIfURL(projectSettingsFile, ".pipeline/mavenProjectSettings.xml", utils, false) if err != nil { return nil, err } mavenArgs = append(mavenArgs, "--settings", projectSettingsFileName) } else { log.Entry().Debugf("Project settings file not provided via configuration.") } return mavenArgs, nil } // DownloadAndCopySettingsFiles downloads the global or project settings file if the strings contain URLs. // It copies the given files to either the locations specified in the environment variables M2_HOME and HOME // or the default locations where maven expects them. func DownloadAndCopySettingsFiles(globalSettingsFile string, projectSettingsFile string, utils SettingsDownloadUtils) error { if len(projectSettingsFile) > 0 { destination, err := getProjectSettingsFileDest() if err != nil { return err } if err := downloadAndCopySettingsFile(projectSettingsFile, destination, utils); err != nil { return err } } else { log.Entry().Debugf("Project settings file not provided via configuration.") } if len(globalSettingsFile) > 0 { destination, err := getGlobalSettingsFileDest() if err != nil { return err } if err := downloadAndCopySettingsFile(globalSettingsFile, destination, utils); err != nil { return err } } else { log.Entry().Debugf("Global settings file not provided via configuration.") } return nil } func UpdateActiveProfileInSettingsXML(newActiveProfiles []string, utils SettingsDownloadUtils) error { settingsFile, err := getGlobalSettingsFileDest() if err != nil { return err } settingsXMLContent, err := utils.FileRead(settingsFile) if err != nil { return fmt.Errorf("error reading global settings xml file at %v , continuing without active profile update", settingsFile) } var projectSettings Settings err = xml.Unmarshal([]byte(settingsXMLContent), &projectSettings) if err != nil { return fmt.Errorf("failed to unmarshal settings xml file '%v': %w", settingsFile, err) } if len(projectSettings.ActiveProfiles.ActiveProfile) == 0 { log.Entry().Warnf("no active profile found to replace in settings xml %v , continuing without file edit", settingsFile) } else { projectSettings.Xsi = "http://www.w3.org/2001/XMLSchema-instance" projectSettings.SchemaLocation = "http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd" projectSettings.ActiveProfiles.ActiveProfile = []string{} projectSettings.ActiveProfiles.ActiveProfile = append(projectSettings.ActiveProfiles.ActiveProfile, newActiveProfiles...) settingsXml, err := xml.MarshalIndent(projectSettings, "", " ") if err != nil { return fmt.Errorf("failed to marshal maven project settings xml: %w", err) } settingsXmlString := string(settingsXml) Replacer := strings.NewReplacer(" ", "", " ", "") settingsXmlString = Replacer.Replace(settingsXmlString) xmlstring := []byte(xml.Header + settingsXmlString) err = utils.FileWrite(settingsFile, xmlstring, 0777) if err != nil { return fmt.Errorf("failed to write maven Settings during update xml: %w", err) } log.Entry().Infof("Successfully updated details in maven settings file : '%s'", settingsFile) } return nil } func CreateNewProjectSettingsXML(altDeploymentRepositoryID string, altDeploymentRepositoryUser string, altDeploymentRepositoryPassword string, utils SettingsDownloadUtils) (string, error) { settingsXML := Settings{ XMLName: xml.Name{Local: "settings"}, Xsi: "http://www.w3.org/2001/XMLSchema-instance", SchemaLocation: "http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd", Servers: ServersType{ ServerType: []Server{ { ID: altDeploymentRepositoryID, Username: altDeploymentRepositoryUser, Password: altDeploymentRepositoryPassword, }, }, }, } xmlstring, err := xml.MarshalIndent(settingsXML, "", " ") if err != nil { return "", fmt.Errorf("failed to marshal Settings.xml: %w", err) } xmlstring = []byte(xml.Header + string(xmlstring)) err = utils.FileWrite(".pipeline/mavenProjectSettings.xml", xmlstring, 0777) if err != nil { return "", fmt.Errorf("failed to write maven Project Settings xml: %w", err) } log.Entry().Infof("Successfully created maven project settings with details at .pipeline/mavenProjectSettings.xml") return ".pipeline/mavenProjectSettings.xml", nil } func UpdateProjectSettingsXML(projectSettingsFile string, altDeploymentRepositoryID string, altDeploymentRepositoryUser string, altDeploymentRepositoryPassword string, utils SettingsDownloadUtils) (string, error) { projectSettingsFileDestination := ".pipeline/mavenProjectSettings" if exists, _ := utils.FileExists(projectSettingsFile); exists { projectSettingsFileDestination = projectSettingsFile addServerTagtoProjectSettingsXML(projectSettingsFile, altDeploymentRepositoryID, altDeploymentRepositoryUser, altDeploymentRepositoryPassword, utils) } else { addServerTagtoProjectSettingsXML(".pipeline/mavenProjectSettings", altDeploymentRepositoryID, altDeploymentRepositoryUser, altDeploymentRepositoryPassword, utils) } return projectSettingsFileDestination, nil } func addServerTagtoProjectSettingsXML(projectSettingsFile string, altDeploymentRepositoryID string, altDeploymentRepositoryUser string, altDeploymentRepositoryPassword string, utils SettingsDownloadUtils) error { var projectSettings Settings settingsXMLContent, err := utils.FileRead(projectSettingsFile) if err != nil { return fmt.Errorf("failed to read file '%v': %w", projectSettingsFile, err) } err = xml.Unmarshal([]byte(settingsXMLContent), &projectSettings) if err != nil { return fmt.Errorf("failed to unmarshal settings xml file '%v': %w", projectSettingsFile, err) } if len(projectSettings.Servers.ServerType) == 0 { projectSettings.Xsi = "http://www.w3.org/2001/XMLSchema-instance" projectSettings.SchemaLocation = "http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd" projectSettings.Servers.ServerType = []Server{ { ID: altDeploymentRepositoryID, Username: altDeploymentRepositoryUser, Password: altDeploymentRepositoryPassword, }, } } else if len(projectSettings.Servers.ServerType) > 0 { // if tag is present then add the staging server tag stagingServer := Server{ ID: altDeploymentRepositoryID, Username: altDeploymentRepositoryUser, Password: altDeploymentRepositoryPassword, } projectSettings.Servers.ServerType = append(projectSettings.Servers.ServerType, stagingServer) } settingsXml, err := xml.MarshalIndent(projectSettings, "", " ") if err != nil { fmt.Errorf("failed to marshal maven project settings xml: %w", err) } settingsXmlString := string(settingsXml) Replacer := strings.NewReplacer(" ", "", " ", "") settingsXmlString = Replacer.Replace(settingsXmlString) xmlstring := []byte(xml.Header + settingsXmlString) err = utils.FileWrite(projectSettingsFile, xmlstring, 0777) if err != nil { fmt.Errorf("failed to write maven Settings xml: %w", err) } log.Entry().Infof("Successfully updated details in maven project settings file : '%s'", projectSettingsFile) return nil } func downloadAndCopySettingsFile(src string, dest string, utils SettingsDownloadUtils) error { if len(src) == 0 { return fmt.Errorf("Settings file source location not provided") } if len(dest) == 0 { return fmt.Errorf("Settings file destination location not provided") } log.Entry().Debugf("Copying file \"%s\" to \"%s\"", src, dest) if strings.HasPrefix(src, "http:") || strings.HasPrefix(src, "https:") { err := downloadSettingsFromURL(src, dest, utils, true) if err != nil { return err } } else { // for sake os symmetry it would be better to use a file protocol prefix here (file:) parent := filepath.Dir(dest) parentFolderExists, err := utils.FileExists(parent) if err != nil { return err } if !parentFolderExists { if err = utils.MkdirAll(parent, 0775); err != nil { return err } } if _, err := utils.Copy(src, dest); err != nil { return err } } return nil } func downloadSettingsIfURL(settingsFileOption, settingsFile string, utils SettingsDownloadUtils, overwrite bool) (string, error) { result := settingsFileOption if strings.HasPrefix(settingsFileOption, "http:") || strings.HasPrefix(settingsFileOption, "https:") { err := downloadSettingsFromURL(settingsFileOption, settingsFile, utils, overwrite) if err != nil { return "", err } result = settingsFile } return result, nil } func downloadSettingsFromURL(url, filename string, utils SettingsDownloadUtils, overwrite bool) error { exists, _ := utils.FileExists(filename) if exists && !overwrite { log.Entry().Infof("Not downloading maven settings file, because it already exists at '%s'", filename) return nil } err := utils.DownloadFile(url, filename, nil, nil) if err != nil { return fmt.Errorf("failed to download maven settings from URL '%s' to file '%s': %w", url, filename, err) } return nil } func getGlobalSettingsFileDest() (string, error) { m2Home, err := getEnvironmentVariable("M2_HOME") if err != nil { return "", err } return filepath.Join(m2Home, "conf", "settings.xml"), nil } func getProjectSettingsFileDest() (string, error) { home, err := getEnvironmentVariable("HOME") if err != nil { return "", err } return filepath.Join(home, ".m2", "settings.xml"), nil } func getEnvironmentVariable(name string) (string, error) { envVar := getenv(name) if len(envVar) == 0 { return "", fmt.Errorf("Environment variable \"%s\" not set or empty", name) } return envVar, nil }