package distros import ( "bufio" "bytes" "errors" "fmt" "net/http" "regexp" "github.com/rs/zerolog/log" "libguestfs.org/guestfs" "git.csclub.uwaterloo.ca/cloud/cloudbuild/pkg/config" ) type DebianTemplateManager struct { TemplateManager } // NewDebianTemplateManager returns a DebianTemplateManager with the given // config values. func NewDebianTemplateManager(cfg *config.Config) *DebianTemplateManager { logger := log.With().Str("distro", "debian").Logger() debianTemplateManager := DebianTemplateManager{ TemplateManager{ cfg: cfg, logger: &logger, impl: nil, }, } debianTemplateManager.TemplateManager.impl = &debianTemplateManager return &debianTemplateManager } func (mgr *DebianTemplateManager) GetLatestVersion() (version string, codename string, err error) { // We're only interested in the major version (integer part) versionPattern := regexp.MustCompile(`^Version: (\d+)(?:\.\d+)?$`) codenamePattern := regexp.MustCompile("^Codename: ([a-z-]+)$") resp, err := http.Get("https://mirror.csclub.uwaterloo.ca/debian/dists/stable/InRelease") if err != nil { return } defer resp.Body.Close() scanner := bufio.NewScanner(resp.Body) for scanner.Scan() { if version == "" { submatches := versionPattern.FindStringSubmatch(scanner.Text()) if submatches != nil { version = submatches[1] } } if codename == "" { submatches := codenamePattern.FindStringSubmatch(scanner.Text()) if submatches != nil { codename = submatches[1] } } if version != "" && codename != "" { break } } if version == "" { err = errors.New("Could not determine latest version of Debian") } return } func (mgr *DebianTemplateManager) DownloadTemplate(version, codename string) (path string, err error) { filename := fmt.Sprintf("debian-%s-genericcloud-amd64.qcow2", version) url := fmt.Sprintf("https://cloud.debian.org/images/cloud/%s/latest/%s", codename, filename) return mgr.DownloadTemplateGeneric(filename, url) } func (mgr *DebianTemplateManager) replaceFailsafeMirrorsInCloudCfg(handle *guestfs.Guestfs) error { // In Debian 12 (bookworm), /etc/cloud/cloud.cfg has the following snippet: // // system_info: // package_mirrors: // - arches: [default] // failsafe: // primary: https://deb.debian.org/debian // security: https://deb.debian.org/debian-security // // Since we can't override the system_info settings, we need to overwrite this file. filePaths := []string{"/etc/cloud/cloud.cfg"} // To be safe, we check other config files under /etc/cloud/cloud.cfg.d as well. extraFilePaths, err := handle.Glob_expand("/etc/cloud/cloud.cfg.d/*.cfg", nil) if err != nil { return fmt.Errorf("Could not expand glob /etc/cloud/cloud.cfg.d/*.cfg: %w", err) } filePaths = append(filePaths, extraFilePaths...) debMirrorUrlRegex := regexp.MustCompile(`https?://deb\.debian\.org`) replacement := []byte("http://" + mgr.cfg.MirrorHost) for _, filePath := range filePaths { mgr.logger.Debug().Str("file", filePath).Msg("Checking for deb.debian.org URLs") content, err := handle.Read_file(filePath) if err != nil { return fmt.Errorf("Could not read %s: %w", filePath, err) } newContent := debMirrorUrlRegex.ReplaceAll(content, replacement) if bytes.Equal(content, newContent) { continue } mgr.logger.Debug().Str("file", filePath).Msg("Replacing deb.debian.org URLs") err = handle.Write(filePath, newContent) if err != nil { return fmt.Errorf("Could not write to %s: %w", filePath, err) } } return nil } func (mgr *DebianTemplateManager) CommandToUpdatePackageCache() []string { return debianCommandToUpdatePackageCache() } func (mgr *DebianTemplateManager) PerformDistroSpecificModifications(handle *guestfs.Guestfs) error { if err := mgr.replaceFailsafeMirrorsInCloudCfg(handle); err != nil { return err } if err := mgr.replaceDebianMirrorUrls(handle); err != nil { return err } return nil }