package distros import ( "errors" "fmt" "net/http" "regexp" "strconv" "github.com/rs/zerolog/log" "golang.org/x/net/html" "libguestfs.org/guestfs" "git.csclub.uwaterloo.ca/cloud/cloudbuild/pkg/config" ) type FedoraTemplateManager struct { TemplateManager } // NewFedoraTemplateManager returns a FedoraTemplateManager with the given // config values. func NewFedoraTemplateManager(cfg *config.Config) *FedoraTemplateManager { logger := log.With().Str("distro", "fedora").Logger() fedoraTemplateManager := FedoraTemplateManager{ TemplateManager{ cfg: cfg, logger: &logger, impl: nil, }, } fedoraTemplateManager.TemplateManager.impl = &fedoraTemplateManager return &fedoraTemplateManager } var numberSlashPattern *regexp.Regexp = regexp.MustCompile("^\\d+/$") func isHyperlink(node *html.Node) bool { return node.Type == html.ElementNode && node.Data == "a" && node.FirstChild != nil && node.FirstChild.Type == html.TextNode } func getMatchingLinkFromHtml(node *html.Node, pattern *regexp.Regexp) string { if isHyperlink(node) { text := node.FirstChild.Data return pattern.FindString(text) } for c := node.FirstChild; c != nil; c = c.NextSibling { result := getMatchingLinkFromHtml(c, pattern) if result != "" { return result } } return "" } func getMaxVersionFromHtml(node *html.Node, maxVersion *int64) { if isHyperlink(node) { text := node.FirstChild.Data if numberSlashPattern.FindString(text) != "" { version, _ := strconv.ParseInt(text[:len(text)-1], 10, 64) if version > *maxVersion { *maxVersion = version } } return } for c := node.FirstChild; c != nil; c = c.NextSibling { getMaxVersionFromHtml(c, maxVersion) } } func (mgr *FedoraTemplateManager) GetLatestVersion() (version string, codename string, err error) { resp, err := http.Get("https://mirror.csclub.uwaterloo.ca/fedora/linux/releases/") if err != nil { return } defer resp.Body.Close() node, err := html.Parse(resp.Body) if err != nil { return } var intVersion int64 getMaxVersionFromHtml(node, &intVersion) if intVersion == 0 { err = errors.New("Could not determine latest Fedora version from HTML") } else { version = strconv.FormatInt(intVersion, 10) // Fedora doesn't have codenames, only numbered versions codename = version } return } func (mgr *FedoraTemplateManager) DownloadTemplate(version, codename string) (path string, err error) { pattern := regexp.MustCompile(fmt.Sprintf("^Fedora-Cloud-Base-%s-\\d+(\\.\\d+)?\\.x86_64\\.qcow2$", version)) imagesUrl := fmt.Sprintf("https://mirror.csclub.uwaterloo.ca/fedora/linux/releases/%s/Cloud/x86_64/images/", version) resp, err := http.Get(imagesUrl) if err != nil { return } defer resp.Body.Close() node, err := html.Parse(resp.Body) if err != nil { return } filename := getMatchingLinkFromHtml(node, pattern) if filename == "" { err = errors.New("Could not find hyperlink for Fedora Cloud image") return } url := imagesUrl + filename return mgr.DownloadTemplateGeneric(filename, url) } func (mgr *FedoraTemplateManager) UsesNetworkManager() bool { return true } func (mgr *FedoraTemplateManager) addCloudInitSnippet(handle *guestfs.Guestfs) error { path := "/etc/cloud/cloud.cfg.d/99_csclub.cfg" mgr.logger.Debug().Msg("Writing to " + path) return handle.Write(path, getResource("fedora-cloud-init")) } func (mgr *FedoraTemplateManager) HasSELinuxEnabled() bool { return true } func (mgr *FedoraTemplateManager) CommandToUpdatePackageCache() []string { return fedoraCommandToUpdatePackageCache() } var fedoraYumRepoBaseUrlPattern *regexp.Regexp = regexp.MustCompile( "^(?Phttps?://)[A-Za-z0-9./-]+(?P/fedora/linux/[A-Za-z0-9./$-]+)$", ) func (mgr *FedoraTemplateManager) transformFedoraYumRepoBaseUrl(url string) string { submatches := fedoraYumRepoBaseUrlPattern.FindStringSubmatch(url) if submatches != nil { scheme, path := submatches[1], submatches[2] url = scheme + mgr.cfg.MirrorHost + path } return url } func (mgr *FedoraTemplateManager) PerformDistroSpecificModifications(handle *guestfs.Guestfs) (err error) { if err = mgr.setChronyOptions(handle, "/etc/chrony.conf"); err != nil { return } if err = mgr.setNetworkManagerOptions(handle); err != nil { return } if err = mgr.addCloudInitSnippet(handle); err != nil { return } if err = mgr.replaceYumMirrorUrls(handle, mgr.transformFedoraYumRepoBaseUrl); err != nil { return } if err = mgr.dnfRemoveUnnecessaryPackages(handle); err != nil { return } if err = mgr.setJournaldConf(handle); err != nil { return } return }