func parseZypperPatchInfo()

in packages/zypper.go [281:422]


func parseZypperPatchInfo(out []byte) (map[string][]string, error) {
	/*
		Loading repository data...
		Reading installed packages...
		Information for patch SUSE-SLE-SERVER-12-SP4-2019-2974:
		-------------------------------------------------------
		Repository  : SLES12-SP4-Updates
		Name        : SUSE-SLE-SERVER-12-SP4-2019-2974
		Version     : 1
		Arch        : noarch
		Vendor      : maint-coord@suse.de
		Status      : needed
		Category    : recommended
		Severity    : important
		Created On  : Thu Nov 14 13:17:48 2019
		Interactive : ---
		Summary     : Recommended update for irqbalance
		Description :
		    This update for irqbalance fixes the following issues:
		    - Irqbalanced spreads the IRQs between the available virtual machines. (bsc#1119465, bsc#1154905)
		Provides    : patch:SUSE-SLE-SERVER-12-SP4-2019-2974 = 1
		Conflicts   : [2]
		    irqbalance.src < 1.1.0-9.3.1
		    irqbalance.x86_64 < 1.1.0-9.3.1
	*/
	patchInfo := make(map[string][]string)
	var validConflictLine = regexp.MustCompile(`\s*Conflicts\s*:\s*\[\d*\]\s*`)
	var conflictLineExtract = regexp.MustCompile(`\[[0-9]+\]`)
	var nameLine = regexp.MustCompile(`\s*Name\s*:\s*`)
	lines := bytes.Split(bytes.TrimSpace(out), []byte("\n"))
	i := 0
	for {
		// find the name line
		for ; i < len(lines); i++ {
			b := nameLine.Find([]byte(lines[i]))
			if b != nil {
				break
			}
		}
		if i >= len(lines) {
			// we do not have any more patch info blobs
			break
		}

		parts := strings.Split(string(lines[i]), ":")
		i++

		if len(parts) != 2 {
			return nil, fmt.Errorf("invalid name output")
		}
		patchName := strings.Trim(parts[1], " ")

		for ; i < len(lines); i++ {
			b := validConflictLine.Find([]byte(lines[i]))
			if b != nil {
				//	Conflicts   : [2]
				break
			}
		}

		if i >= len(lines) {
			// did not find conflicting packages
			// this should not happen
			return nil, nil
		}

		matches := conflictLineExtract.FindAllString(string(lines[i]), -1)
		if len(matches) != 1 {
			return nil, fmt.Errorf("invalid patch info")
		}

		// get the number of package lines to parse
		conflicts := strings.Trim(matches[0], "[")
		conflicts = strings.Trim(conflicts, "]")
		conflictLines, err := strconv.Atoi(conflicts)
		if err != nil {
			return nil, fmt.Errorf("invalid patch info: invalid conflict info")
		}
		ctr := i + 1
		ctrEnd := ctr + conflictLines
		for ; ctr < ctrEnd; ctr++ {
			//libsolv.src < 0.6.36-2.27.19.8
			//libsolv-tools.x86_64 < 0.6.36-2.27.19.8
			//libzypp.src < 16.20.2-27.60.4
			//libzypp.x86_64 < 16.20.2-27.60.4
			//perl-solv.x86_64 < 0.6.36-2.27.19.8
			//python-solv.x86_64 < 0.6.36-2.27.19.8
			//zypper.src < 1.13.54-18.40.2
			//zypper.x86_64 < 1.13.54-18.40.2
			//zypper-log < 1.13.54-18.40.2

			//srcpackage:ruby2.5 < 2.5.9-150000.4.29.1
			//ruby2.5.noarch < 2.5.9-150000.4.29.1
			//ruby2.5.x86_64 < 2.5.9-150000.4.29.1
			//srcpackage:zypper
			//zypper-log < 1.14.64-150400.3.32.1
			//zypper-needs-restarting < 1.14.64-150400.3.32.1
			parts := strings.Split(string(lines[ctr]), "<")
			if len(parts) != 2 {
				return nil, fmt.Errorf("invalid package info, can't parse line: %v", string(lines[ctr]))
			}

			nameArch := parts[0]
			pkgName := ""
			if strings.Contains(nameArch, "srcpackage:") {
				colonIdx := strings.Index(nameArch, ":")
				pkgName = strings.Trim(nameArch[colonIdx+1:], " ")
				if len(pkgName) == 0 {
					return nil, fmt.Errorf("invalid package info, can't parse line: %v", string(lines[ctr]))
				}
			} else {
				// Get the last index to handle the case if pkg has float version
				// (e.g. `ruby2.5.noarch < 2.5.9-150000.4.29.1`)
				lastDotIdx := strings.LastIndex(nameArch, ".")

				// In case if there's NO dot exist, then the package name doen't contain
				// the architecture details (`e.g. zypper-log < 1.14.64-150400.3.32.1`)
				if lastDotIdx == -1 {
					pkgName = strings.Trim(nameArch, " ")
				} else {
					pkgName = strings.Trim(nameArch[:lastDotIdx], " ")
				}
			}
			patches, ok := patchInfo[pkgName]
			if !ok {
				patches = make([]string, 0)
			}
			patches = append(patches, patchName)
			patchInfo[pkgName] = patches
		}

		// set i for next patch information blob
		i = ctrEnd
	}
	// TODO: instead of returning a map of <string, []string>
	// make it more concrete type returns with more information
	// about the package and patch
	if len(patchInfo) == 0 {
		return nil, fmt.Errorf("invalid patch information, did not find patch blobs")
	}
	return patchInfo, nil
}