package distros import ( "fmt" "net/url" "strings" "libguestfs.org/guestfs" ) func debianCommandToUpdatePackageCache() []string { return []string{"sudo", "apt", "update"} } func (mgr *TemplateManager) replaceDebianMirrorUrl(handle *guestfs.Guestfs, uri string) (string, error) { parsedUrl, err := url.Parse(uri) if err != nil { return "", fmt.Errorf("Could not parse URL '%s': %w", uri, err) } // See https://manpages.debian.org/stable/apt/sources.list.5.en.html#URI_SPECIFICATION // There are many more URI types, but these are the ones which I've seen so far in the // official Debian cloud images if parsedUrl.Scheme == "http" || parsedUrl.Scheme == "https" { parsedUrl.Host = mgr.cfg.MirrorHost return parsedUrl.String(), nil } else if parsedUrl.Scheme == "mirror+file" { // The file is a mirrorlist filePath := uri[len("mirror+file://"):] content, err := handle.Cat(filePath) if err != nil { return "", fmt.Errorf("Could not read %s: %w", filePath, err) } oldLines := strings.Split(content, "\n") newLines := []string{} for _, oldLine := range oldLines { if oldLine == "" || strings.HasPrefix(oldLine, "#") { newLines = append(newLines, oldLine) continue } parsedUrl, err := url.Parse(oldLine) if err != nil { return "", fmt.Errorf("Could not parse URL in %s: '%s'", filePath, oldLine) } if parsedUrl.Scheme != "http" && parsedUrl.Scheme != "https" { return "", fmt.Errorf("Unexpected URL scheme in %s: '%s'", filePath, oldLine) } parsedUrl.Host = mgr.cfg.MirrorHost newLine := parsedUrl.String() mgr.logger.Debug(). Str("file", filePath). Str("oldURL", oldLine). Str("newURL", newLine). Msg("Replacing URL") newLines = append(newLines, newLine) } newContent := strings.Join(newLines, "\n") err = handle.Write(filePath, []byte(newContent)) if err != nil { return "", fmt.Errorf("Error writing to %s: %w", filePath, err) } // Original URL (mirror+file://) does not change return uri, nil } else { return "", fmt.Errorf("Unexpected scheme in URL: %s", uri) } } // See https://manpages.debian.org/stable/apt/sources.list.5.en.html#ONE-LINE-STYLE_FORMAT func (mgr *TemplateManager) replaceDebianMirrorUrls_OneLineStyle(handle *guestfs.Guestfs, filePath string) error { log := mgr.logger // Some Augeas nodes under /files/etc/apt/sources.list are comments, // so we use /*/uri to make sure that we only get the actual entries sourcesListEntries, err := handle.Aug_match("/files" + filePath + "/*/uri") if err != nil { return err } for _, uriPath := range sourcesListEntries { var ( oldURL string newURL string typeValue string distValue string ) if oldURL, err = handle.Aug_get(uriPath); err != nil { return err } newURL, err = mgr.replaceDebianMirrorUrl(handle, oldURL) if err != nil { return err } // strip off the "/uri" from the node path entryPath := uriPath[:len(uriPath)-4] typePath := entryPath + "/type" if typeValue, err = handle.Aug_get(typePath); err != nil { return err } distPath := entryPath + "/distribution" if distValue, err = handle.Aug_get(distPath); err != nil { return err } if typeValue == "deb-src" { log.Debug(). Str("URL", oldURL). Str("distribution", distValue). Msg("Removing deb-src entry") if _, err = handle.Aug_rm(entryPath); err != nil { return err } continue } if oldURL == newURL { continue } log.Debug(). Str("distribution", distValue). Str("oldURL", oldURL). Str("newURL", newURL). Str("file", filePath). Msg("Replacing URL") if err = handle.Aug_set(uriPath, newURL); err != nil { return err } } return nil } // See https://manpages.debian.org/stable/apt/sources.list.5.en.html#DEB822-STYLE_FORMAT func (mgr *TemplateManager) replaceDebianMirrorUrls_Deb822Style(handle *guestfs.Guestfs, filePath string) error { // Augeas doesn't support this style unfortunately, so we're going to // have to parse the file manually content, err := handle.Cat(filePath) if err != nil { return fmt.Errorf("Could not read %s: %w", filePath, err) } oldLines := strings.Split(content, "\n") newLines := []string{} for _, oldLine := range oldLines { if strings.HasPrefix(oldLine, "Types:") { oldTypes := strings.Split( strings.TrimLeft(oldLine[len("Types:"):], " "), " ", ) if len(oldTypes) == 0 || len(oldTypes) > 2 { return fmt.Errorf( "Could not determine Debian archive types from this line in %s: '%s'", filePath, oldLine, ) } if oldTypes[0] == "deb-src" || (len(oldTypes) == 2 && oldTypes[1] == "deb-src") { mgr.logger.Debug().Str("file", filePath).Msg("Removing deb-src type") } newLines = append(newLines, "Types: deb") } else if strings.HasPrefix(oldLine, "URIs:") { oldURL := strings.TrimLeft(oldLine[len("URIs:"):], " ") newURL, err := mgr.replaceDebianMirrorUrl(handle, oldURL) if err != nil { return err } if oldURL != newURL { mgr.logger.Debug(). Str("file", filePath). Str("oldURL", oldURL). Str("newURL", newURL). Msg("Replacing URL") } newLines = append(newLines, "URIs: "+newURL) } else { newLines = append(newLines, oldLine) } } newContent := strings.Join(newLines, "\n") err = handle.Write(filePath, []byte(newContent)) if err != nil { return fmt.Errorf("Error writing to %s: %w", filePath, err) } return nil } func (mgr *TemplateManager) replaceDebianMirrorUrls(handle *guestfs.Guestfs) error { filePaths := []string{"/etc/apt/sources.list"} extraSourceDirPath := "/etc/apt/sources.list.d" extraSourceDirExists, err := handle.Is_dir(extraSourceDirPath, nil) if err != nil { return fmt.Errorf("Could not determine if %s is a directory: %w", extraSourceDirPath, err) } if extraSourceDirExists { extraSourceFiles, err := handle.Ls(extraSourceDirPath) if err != nil { return fmt.Errorf("Could not list files in %s: %w", extraSourceDirPath, err) } for _, filename := range extraSourceFiles { filePaths = append(filePaths, extraSourceDirPath+"/"+filename) } } for _, filePath := range filePaths { if strings.HasSuffix(filePath, ".list") { err = mgr.replaceDebianMirrorUrls_OneLineStyle(handle, filePath) } else if strings.HasSuffix(filePath, ".sources") { err = mgr.replaceDebianMirrorUrls_Deb822Style(handle, filePath) } else { err = fmt.Errorf("Could not determine type of Debian source file %s", filePath) } if err != nil { return err } } return nil }