package distros import ( "encoding/json" "errors" "fmt" "io" "net/http" "strings" "github.com/rs/zerolog/log" "libguestfs.org/guestfs" "git.csclub.uwaterloo.ca/cloud/cloudbuild/pkg/config" ) type UbuntuTemplateManager struct { TemplateManager } // NewUbuntuTemplateManager returns a UbuntuTemplateManager with the given // config values. func NewUbuntuTemplateManager(cfg *config.Config) *UbuntuTemplateManager { logger := log.With().Str("distro", "ubuntu").Logger() // The child embeds the parent, and the parent has a reference to the child ubuntuTemplateManager := UbuntuTemplateManager{ TemplateManager{ cfg: cfg, logger: &logger, impl: nil, }, } ubuntuTemplateManager.TemplateManager.impl = &ubuntuTemplateManager return &ubuntuTemplateManager } type UbuntuVersions struct { Products map[string]struct { Arch string `json:"arch"` Release string `json:"release"` ReleaseTitle string `json:"release_title"` Version string `json:"version"` } `json:"products"` } // GetLatestVersion returns the version and codename of the latest Ubuntu release. // e.g. version = 22.04, codename = jammy func (mgr *UbuntuTemplateManager) GetLatestVersion() (version string, codename string, err error) { resp, err := http.Get("https://cloud-images.ubuntu.com/releases/streams/v1/com.ubuntu.cloud:released:download.json") if err != nil { return } defer resp.Body.Close() buf, err := io.ReadAll(resp.Body) if err != nil { return } var data UbuntuVersions if err = json.Unmarshal(buf, &data); err != nil { return } for _, val := range data.Products { if val.Arch == "amd64" && strings.HasSuffix(val.ReleaseTitle, "LTS") && val.Version > version { version = val.Version codename = val.Release } } if version == "" { err = errors.New("Could not find latest LTS version of Ubuntu") } return } // DownloadTemplate downloads the cloud image for the given Ubuntu codename, // and returns the filename to which it was written. func (mgr *UbuntuTemplateManager) DownloadTemplate(version, codename string) (path string, err error) { filename := fmt.Sprintf("%s-server-cloudimg-amd64-disk-kvm.img", codename) url := fmt.Sprintf("https://cloud-images.ubuntu.com/%s/current/%s", codename, filename) return mgr.DownloadTemplateGeneric(filename, url) } func (mgr *UbuntuTemplateManager) addUbuntuCloudInitSnippet(handle *guestfs.Guestfs) error { path := "/etc/cloud/cloud.cfg.d/99_csclub_misc.cfg" mgr.logger.Debug().Msg("Writing to " + path) return handle.Write(path, getResource("ubuntu-cloud-init")) } func (mgr *UbuntuTemplateManager) disableNoisyMotdMessages(handle *guestfs.Guestfs) { log := mgr.logger // Setting these files to non-executable (chmod a-x) disables them filesToDisable := []string{ "00-header", "10-help-text", "50-landscape-sysinfo", //"50-motd-news", "88-esm-announce", } for _, filename := range filesToDisable { path := "/etc/update-motd.d/" + filename log.Debug().Msg("Disabling " + path) if err := handle.Chmod(0644, path); err != nil { log.Warn().Err(err).Msg("Could not disable " + path) } } } func (mgr *UbuntuTemplateManager) CommandToUpdatePackageCache() []string { return debianCommandToUpdatePackageCache() } func (mgr *UbuntuTemplateManager) PerformDistroSpecificModifications(handle *guestfs.Guestfs) (err error) { if err = mgr.replaceDebianMirrorUrls(handle); err != nil { return } if err = mgr.addUbuntuCloudInitSnippet(handle); err != nil { return } mgr.disableNoisyMotdMessages(handle) return }