github.com/canonical/ubuntu-image@v0.0.0-20240430122802-2202fe98b290/internal/imagedefinition/image_definition.go (about)

     1  /*
     2  Package imagedefinition provides the structure for the
     3  image definition that will be parsed from a YAML file.
     4  */
     5  package imagedefinition
     6  
     7  import (
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/xeipuuv/gojsonschema"
    12  )
    13  
    14  // ImageDefinition is the parent struct for the data
    15  // contained within a classic image definition file
    16  type ImageDefinition struct {
    17  	ImageName      string         `yaml:"name"            json:"ImageName"`
    18  	DisplayName    string         `yaml:"display-name"    json:"DisplayName"`
    19  	Revision       int            `yaml:"revision"        json:"Revision,omitempty"`
    20  	Architecture   string         `yaml:"architecture"    json:"Architecture"`
    21  	Series         string         `yaml:"series"          json:"Series"`
    22  	Kernel         string         `yaml:"kernel"          json:"Kernel,omitempty"`
    23  	Gadget         *Gadget        `yaml:"gadget"          json:"Gadget,omitempty"`
    24  	ModelAssertion string         `yaml:"model-assertion" json:"ModelAssertion,omitempty" jsonschema:"type=string,format=uri"`
    25  	Rootfs         *Rootfs        `yaml:"rootfs"          json:"Rootfs"`
    26  	Customization  *Customization `yaml:"customization"   json:"Customization,omitempty"`
    27  	Artifacts      *Artifact      `yaml:"artifacts"       json:"Artifacts,omitempty"`
    28  	Class          string         `yaml:"class"           json:"Class"                    jsonschema:"enum=preinstalled,enum=cloud,enum=installer"`
    29  }
    30  
    31  // Gadget defines the gadget section of the image definition file
    32  type Gadget struct {
    33  	Ref          string `yaml:"ref"    json:"Ref,omitempty"`
    34  	GadgetTarget string `yaml:"target" json:"GadgetTarget,omitempty"`
    35  	GadgetBranch string `yaml:"branch" json:"GadgetBranch,omitempty"`
    36  	GadgetType   string `yaml:"type"   json:"GadgetType"             jsonschema:"enum=git,enum=directory,enum=prebuilt"`
    37  	GadgetURL    string `yaml:"url"    json:"GadgetURL,omitempty"    jsonschema:"type=string,format=uri"`
    38  }
    39  
    40  // Rootfs defines the rootfs section of the image definition file
    41  type Rootfs struct {
    42  	Components        []string `yaml:"components"    json:"Components,omitempty"   default:"main,restricted"`
    43  	Archive           string   `yaml:"archive"       json:"Archive"                default:"ubuntu"`
    44  	Flavor            string   `yaml:"flavor"        json:"Flavor"                 default:"ubuntu"`
    45  	Mirror            string   `yaml:"mirror"        json:"Mirror"                 default:"http://archive.ubuntu.com/ubuntu/"`
    46  	Pocket            string   `yaml:"pocket"        json:"Pocket"                 jsonschema:"enum=release,enum=Release,enum=updates,enum=Updates,enum=security,enum=Security,enum=proposed,enum=Proposed" default:"release"`
    47  	Seed              *Seed    `yaml:"seed"          json:"Seed,omitempty"         jsonschema:"oneof_required=Seed"`
    48  	Tarball           *Tarball `yaml:"tarball"       json:"Tarball,omitempty"      jsonschema:"oneof_required=Tarball"`
    49  	ArchiveTasks      []string `yaml:"archive-tasks" json:"ArchiveTasks,omitempty" jsonschema:"oneof_required=ArchiveTasks"`
    50  	SourcesListDeb822 *bool    `yaml:"sources-list-deb822" json:"SourcesListDeb822" default:"false"`
    51  }
    52  
    53  // Seed defines the seed section of rootfs, which is used to
    54  // build a rootfs via seed germination
    55  type Seed struct {
    56  	SeedBranch string   `yaml:"branch" json:"SeedBranch,omitempty"`
    57  	SeedURLs   []string `yaml:"urls"   json:"SeedURLs"             jsonschema:"type=array,format=uri"`
    58  	Names      []string `yaml:"names"  json:"Names"`
    59  	Vcs        *bool    `yaml:"vcs"    json:"Vcs"                  default:"true"`
    60  }
    61  
    62  // Tarball defines the tarball section of rootfs, which is used
    63  // to create images from a pre-built rootfs
    64  type Tarball struct {
    65  	TarballURL string `yaml:"url"       json:"TarballURL"          jsonschema:"type=string,format=uri"`
    66  	GPG        string `yaml:"gpg"       json:"GPG,omitempty"       jsonschema:"type=string,format=uri"`
    67  	SHA256sum  string `yaml:"sha256sum" json:"SHA256sum,omitempty" jsonschema:"minLength=64,maxLength=64"`
    68  }
    69  
    70  // Customization defines the customization section of the image definition file.
    71  type Customization struct {
    72  	Components    []string   `yaml:"components"     json:"Components,omitempty"   default:"main,restricted,universe"`
    73  	Pocket        string     `yaml:"pocket"         json:"Pocket"                 jsonschema:"enum=release,enum=Release,enum=updates,enum=Updates,enum=security,enum=Security,enum=proposed,enum=Proposed" default:"release"`
    74  	Installer     *Installer `yaml:"installer"      json:"Installer,omitempty"`
    75  	CloudInit     *CloudInit `yaml:"cloud-init"     json:"CloudInit,omitempty"`
    76  	ExtraPPAs     []*PPA     `yaml:"extra-ppas"     json:"ExtraPPAs,omitempty"`
    77  	ExtraPackages []*Package `yaml:"extra-packages" json:"ExtraPackages,omitempty"`
    78  	ExtraSnaps    []*Snap    `yaml:"extra-snaps"    json:"ExtraSnaps,omitempty"`
    79  	Fstab         []*Fstab   `yaml:"fstab"          json:"Fstab,omitempty"`
    80  	Manual        *Manual    `yaml:"manual"         json:"Manual,omitempty"`
    81  }
    82  
    83  // Installer provides customization options specific to installer images
    84  type Installer struct {
    85  	Preseeds []string `yaml:"preseeds" json:"Preseeds,omitempty"`
    86  	Layers   []string `yaml:"layers"   json:"Layers,omitempty"`
    87  }
    88  
    89  // CloudInit provides customizations for running cloud-init
    90  type CloudInit struct {
    91  	MetaData      string `yaml:"meta-data"      json:"MetaData,omitempty"`
    92  	UserData      string `yaml:"user-data"      json:"UserData,omitempty"`
    93  	NetworkConfig string `yaml:"network-config" json:"NetworkConfig,omitempty"`
    94  }
    95  
    96  // PPA contains information about a public or private PPA
    97  type PPA struct {
    98  	Name        string `yaml:"name"         json:"PPAName"               jsonschema:"pattern=^[a-zA-Z0-9_.+-]+/[a-zA-Z0-9_.+-]+$"`
    99  	Auth        string `yaml:"auth"         json:"Auth,omitempty"        jsonschema:"pattern=^[a-zA-Z0-9_.+-]+:[a-zA-Z0-9]+$"`
   100  	Fingerprint string `yaml:"fingerprint"  json:"Fingerprint,omitempty"`
   101  	KeepEnabled *bool  `yaml:"keep-enabled" json:"KeepEnabled"           default:"true"`
   102  }
   103  
   104  // Package contains information about packages
   105  type Package struct {
   106  	PackageName string `yaml:"name" json:"PackageName"`
   107  }
   108  
   109  // Snap contains information about snaps
   110  type Snap struct {
   111  	SnapName     string `yaml:"name"     json:"SnapName"`
   112  	SnapRevision int    `yaml:"revision" json:"SnapRevision,omitempty" jsonschema:"type=integer"`
   113  	Store        string `yaml:"store"    json:"Store"                  default:"canonical"`
   114  	Channel      string `yaml:"channel"  json:"Channel"                default:"stable"`
   115  }
   116  
   117  // Manual provides manual customization options
   118  type Manual struct {
   119  	MakeDirs  []*MakeDirs  `yaml:"make-dirs"  json:"MakeDirs,omitempty"`
   120  	CopyFile  []*CopyFile  `yaml:"copy-file"  json:"CopyFile,omitempty"`
   121  	Execute   []*Execute   `yaml:"execute"    json:"Execute,omitempty"`
   122  	TouchFile []*TouchFile `yaml:"touch-file" json:"TouchFile,omitempty"`
   123  	AddGroup  []*AddGroup  `yaml:"add-group"  json:"AddGroup,omitempty"`
   124  	AddUser   []*AddUser   `yaml:"add-user"   json:"AddUser,omitempty"`
   125  }
   126  
   127  // Fstab defines the information that gets rendered into an fstab
   128  type Fstab struct {
   129  	Label        string `yaml:"label"           json:"Label"`
   130  	Mountpoint   string `yaml:"mountpoint"      json:"Mountpoint"`
   131  	FSType       string `yaml:"filesystem-type" json:"FSType"`
   132  	MountOptions string `yaml:"mount-options"   json:"MountOptions" default:"defaults"`
   133  	Dump         bool   `yaml:"dump"            json:"Dump,omitempty"`
   134  	FsckOrder    int    `yaml:"fsck-order"      json:"FsckOrder"`
   135  }
   136  
   137  // MakeDirs allows users to copy files into the rootfs of an image
   138  type MakeDirs struct {
   139  	Path        string `yaml:"path" json:"Path"`
   140  	Permissions uint32 `yaml:"permissions"      json:"Permissions" default:"0755"`
   141  }
   142  
   143  // CopyFile allows users to copy files into the rootfs of an image
   144  type CopyFile struct {
   145  	Dest   string `yaml:"destination" json:"Dest"`
   146  	Source string `yaml:"source"      json:"Source"`
   147  }
   148  
   149  // Execute allows users to execute a script in the rootfs of an image
   150  type Execute struct {
   151  	ExecutePath string `yaml:"path" json:"ExecutePath"`
   152  }
   153  
   154  // TouchFile allows users to touch a file in the rootfs of an image
   155  type TouchFile struct {
   156  	TouchPath string `yaml:"path" json:"TouchPath"`
   157  }
   158  
   159  // AddGroup allows users to add a group in the image that is being built
   160  type AddGroup struct {
   161  	GroupName string `yaml:"name" json:"GroupName"`
   162  	GroupID   string `yaml:"id"   json:"GroupID,omitempty"`
   163  }
   164  
   165  // AddUser allows users to add a user in the image that is being built
   166  type AddUser struct {
   167  	UserName     string `yaml:"name"          json:"UserName"`
   168  	UserID       string `yaml:"id"            json:"UserID,omitempty"`
   169  	Password     string `yaml:"password"      json:"Password,omitempty"`
   170  	PasswordType string `yaml:"password-type" json:"PasswordType"        default:"hash" jsonschema:"enum=text,enum=hash"`
   171  }
   172  
   173  // Artifact contains information about the files that are created
   174  // during and as a result of the image build process
   175  type Artifact struct {
   176  	Img       *[]Img     `yaml:"img"            json:"Img,omitempty"       is_disk:"true"`
   177  	Iso       *[]Iso     `yaml:"iso"            json:"Iso,omitempty"       is_disk:"true"`
   178  	Qcow2     *[]Qcow2   `yaml:"qcow2"          json:"Qcow2,omitempty"     is_disk:"true"`
   179  	Manifest  *Manifest  `yaml:"manifest"       json:"Manifest,omitempty"  is_disk:"false"`
   180  	Filelist  *Filelist  `yaml:"filelist"       json:"Filelist,omitempty"  is_disk:"false"`
   181  	Changelog *Changelog `yaml:"changelog"      json:"Changelog,omitempty" is_disk:"false"`
   182  	RootfsTar *RootfsTar `yaml:"rootfs-tarball" json:"RootfsTar,omitempty" is_disk:"false"`
   183  }
   184  
   185  // Img specifies the name of the resulting .img file.
   186  // If left emtpy no .img file will be created
   187  type Img struct {
   188  	ImgName   string `yaml:"name"   json:"ImgName"`
   189  	ImgVolume string `yaml:"volume" json:"ImgVolume"`
   190  }
   191  
   192  // Iso specifies the name of the resulting .iso file
   193  // and optionally the xorrisofs command used to create it.
   194  // If left emtpy no .iso file will be created
   195  type Iso struct {
   196  	IsoName   string `yaml:"name"            json:"IsoName"`
   197  	IsoVolume string `yaml:"volume"          json:"IsoVolume"`
   198  	Command   string `yaml:"xorriso-command" json:"Command,omitempty"`
   199  }
   200  
   201  // Qcow2 specifies the name of the resulting .qcow2 file
   202  // If left emtpy no .qcow2 file will be created
   203  type Qcow2 struct {
   204  	Qcow2Name   string `yaml:"name"   json:"Qcow2Name"`
   205  	Qcow2Volume string `yaml:"volume" json:"Qcow2Volume"`
   206  }
   207  
   208  // Manifest specifies the name of the manifest file.
   209  // If left emtpy no manifest file will be created
   210  type Manifest struct {
   211  	ManifestName string `yaml:"name" json:"ManifestName"`
   212  }
   213  
   214  // Filelist specifies the name of the filelist file.
   215  // If left emtpy no filelist file will be created
   216  type Filelist struct {
   217  	FilelistName string `yaml:"name" json:"FilelistName"`
   218  }
   219  
   220  // Changelog specifies the name of the changelog file.
   221  // If left emtpy no changelog file will be created
   222  type Changelog struct {
   223  	ChangelogName string `yaml:"name" json:"ChangelogName"`
   224  }
   225  
   226  // RootfsTar specifies the name of a tarball to create from the
   227  // rootfs build steps and the compression to use on it
   228  type RootfsTar struct {
   229  	RootfsTarName string `yaml:"name"        json:"RootfsTarName"`
   230  	Compression   string `yaml:"compression" json:"Compression"   jsonschema:"enum=uncompressed,enum=bzip2,enum=gzip,enum=xz,enum=zstd" default:"uncompressed"`
   231  }
   232  
   233  // NewMissingURLError fails the image definition parsing when a dict
   234  // requires a URL conditionally based on the value of other keys
   235  // in the dict but does not have one included
   236  func NewMissingURLError(context *gojsonschema.JsonContext, value interface{}, details gojsonschema.ErrorDetails) *MissingURLError {
   237  	err := MissingURLError{}
   238  	err.SetContext(context)
   239  	err.SetType("missing_url_error")
   240  	err.SetDescriptionFormat("When key {{.key}} is specified as {{.value}}, a URL must be provided")
   241  	err.SetValue(value)
   242  	err.SetDetails(details)
   243  
   244  	return &err
   245  }
   246  
   247  // MissingURLError implements gojsonschema.ErrorType. It is used for custom errors for
   248  // fields that require a url based on the value of other fields
   249  // based on the values in other fields
   250  type MissingURLError struct {
   251  	gojsonschema.ResultErrorFields
   252  }
   253  
   254  // NewInvalidPPAError fails the image definition parsing when a private PPA
   255  // is configured with no fingerprint
   256  func NewInvalidPPAError(context *gojsonschema.JsonContext, value interface{}, details gojsonschema.ErrorDetails) *InvalidPPAError {
   257  	err := InvalidPPAError{}
   258  	err.SetContext(context)
   259  	err.SetType("private_ppa_without_fingerprint")
   260  	err.SetDescriptionFormat("Fingerprint is required for private PPAs")
   261  	err.SetValue(value)
   262  	err.SetDetails(details)
   263  
   264  	return &err
   265  }
   266  
   267  // InvalidPPAError implements gojsonschema.ErrorType. It is used for custom errors
   268  // when a private PPA does not have a fingerprint specified
   269  type InvalidPPAError struct {
   270  	gojsonschema.ResultErrorFields
   271  }
   272  
   273  // NewPathNotAbsoluteError fails the image definition parsing when a relative path is given
   274  func NewPathNotAbsoluteError(context *gojsonschema.JsonContext, value interface{}, details gojsonschema.ErrorDetails) *PathNotAbsoluteError {
   275  	err := PathNotAbsoluteError{}
   276  	err.SetContext(context)
   277  	err.SetType("path_not_absolute_error")
   278  	err.SetDescriptionFormat("Key {{.key}} needs to be an absolute path ({{.value}})")
   279  	err.SetValue(value)
   280  	err.SetDetails(details)
   281  
   282  	return &err
   283  }
   284  
   285  // PathNotAbsoluteError implements gojsonschema.ErrorType. It is used for custom errors for
   286  // fields that should be absolute but are not
   287  type PathNotAbsoluteError struct {
   288  	gojsonschema.ResultErrorFields
   289  }
   290  
   291  // NewDependentKeyError fails the image definition parsing when one
   292  // field depends on another being specified
   293  func NewDependentKeyError(context *gojsonschema.JsonContext, value interface{}, details gojsonschema.ErrorDetails) *DependentKeyError {
   294  	err := DependentKeyError{}
   295  	err.SetContext(context)
   296  	err.SetType("dependent_key_error")
   297  	err.SetDescriptionFormat("Key {{.key1}} cannot be used without key {{.key2}}")
   298  	err.SetValue(value)
   299  	err.SetDetails(details)
   300  
   301  	return &err
   302  }
   303  
   304  // DependentKeyError implements gojsonschema.ErrorType.
   305  // It is used for custom errors for keys that depend on
   306  // other keys being specified
   307  type DependentKeyError struct {
   308  	gojsonschema.ResultErrorFields
   309  }
   310  
   311  func (i ImageDefinition) securityMirror() string {
   312  	if i.Architecture == "amd64" || i.Architecture == "i386" {
   313  		return "http://security.ubuntu.com/ubuntu/"
   314  	}
   315  	return i.Rootfs.Mirror
   316  }
   317  
   318  // generateLegacySourcesList returns the content to write to the sources.list file
   319  // under the legacy format.
   320  func generateLegacySourcesList(series string, components []string, mirror string, securityMirror string, pocket string) string {
   321  	baseList := fmt.Sprintf("deb %%s %s%%s %s", series, strings.Join(components, " "))
   322  
   323  	releaseSourceComment := `# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
   324  # newer versions of the distribution.
   325  `
   326  	updatesSourceComment := `## Major bug fix updates produced after the final release of the
   327  ## distribution.
   328  `
   329  
   330  	releaseSource := releaseSourceComment + fmt.Sprintf(baseList, mirror, "")
   331  	securitySource := fmt.Sprintf(baseList, securityMirror, "-security")
   332  	updatesSource := updatesSourceComment + fmt.Sprintf(baseList, mirror, "-updates")
   333  	proposedSource := fmt.Sprintf(baseList, mirror, "-proposed")
   334  
   335  	sourcesList := make([]string, 0)
   336  
   337  	switch pocket {
   338  	case "release":
   339  		sourcesList = append(sourcesList, releaseSource)
   340  	case "security":
   341  		sourcesList = append(sourcesList, releaseSource, securitySource)
   342  	case "updates":
   343  		sourcesList = append(sourcesList, releaseSource, securitySource, updatesSource)
   344  	case "proposed":
   345  		sourcesList = append(sourcesList, releaseSource, securitySource, updatesSource, proposedSource)
   346  	}
   347  
   348  	return strings.Join(sourcesList, "\n") + "\n"
   349  }
   350  
   351  // LegacyBuildSourcesList returns the content of the /etc/apt/sources.list to be used
   352  // during the build process
   353  func (i *ImageDefinition) LegacyBuildSourcesList() string {
   354  	return i.legacySourcesList(false)
   355  }
   356  
   357  // LegacyTargetSourcesList returns the content of the /etc/apt/sources.list for the target
   358  // image
   359  func (i *ImageDefinition) LegacyTargetSourcesList() string {
   360  	return i.legacySourcesList(true)
   361  }
   362  
   363  // legacySourcesList returns the content of the /etc/apt/sources.list file in the
   364  // legacy format (not deb822).
   365  func (i *ImageDefinition) legacySourcesList(target bool) string {
   366  	pocket := i.Rootfs.Pocket
   367  	if target {
   368  		pocket = i.Customization.Pocket
   369  	}
   370  
   371  	return generateLegacySourcesList(
   372  		i.Series,
   373  		i.Customization.Components,
   374  		i.Rootfs.Mirror,
   375  		i.securityMirror(),
   376  		strings.ToLower(pocket))
   377  }
   378  
   379  // generateDeb822Section returns a deb822 section/paragraph to be used in a sources list file
   380  // This function is tailored to what is expected in an official ubuntu image and should not be
   381  // used as is to generate arbitrary deb822 sections.
   382  func generateDeb822Section(mirror string, series string, components []string, pocket string) string {
   383  	sectionTmpl := `Types: deb
   384  URIs: %s
   385  Suites: %s
   386  Components: %s
   387  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
   388  
   389  `
   390  
   391  	suites := make([]string, 0)
   392  
   393  	switch pocket {
   394  	case "security":
   395  		suites = []string{series + "-security"}
   396  	case "proposed":
   397  		suites = append([]string{series + "-proposed"}, suites...)
   398  		fallthrough
   399  	case "updates":
   400  		suites = append([]string{series + "-updates"}, suites...)
   401  		fallthrough
   402  	case "release":
   403  		suites = append([]string{series}, suites...)
   404  	}
   405  
   406  	return fmt.Sprintf(sectionTmpl,
   407  		mirror,
   408  		strings.Join(suites, " "),
   409  		strings.Join(components, " "),
   410  	)
   411  }
   412  
   413  var LegacySourcesListComment = `# Ubuntu sources have moved to the /etc/apt/sources.list.d/ubuntu.sources
   414  # file, which uses the deb822 format. Use deb822-formatted .sources files
   415  # to manage package sources in the /etc/apt/sources.list.d/ directory.
   416  # See the sources.list(5) manual page for details.
   417  `
   418  
   419  var ubuntuSourceHeader = `## Ubuntu distribution repository
   420  ##
   421  ## The following settings can be adjusted to configure which packages to use from Ubuntu.
   422  ## Mirror your choices (except for URIs and Suites) in the security section below to
   423  ## ensure timely security updates.
   424  ##
   425  ## Types: Append deb-src to enable the fetching of source package.
   426  ## URIs: A URL to the repository (you may add multiple URLs)
   427  ## Suites: The following additional suites can be configured
   428  ##   <name>-updates   - Major bug fix updates produced after the final release of the
   429  ##                      distribution.
   430  ##   <name>-backports - software from this repository may not have been tested as
   431  ##                      extensively as that contained in the main release, although it includes
   432  ##                      newer versions of some applications which may provide useful features.
   433  ##                      Also, please note that software in backports WILL NOT receive any review
   434  ##                      or updates from the Ubuntu security team.
   435  ## Components: Aside from main, the following components can be added to the list
   436  ##   restricted  - Software that may not be under a free license, or protected by patents.
   437  ##   universe    - Community maintained packages. Software in this repository receives maintenance
   438  ##                 from volunteers in the Ubuntu community, or a 10 year security maintenance
   439  ##                 commitment from Canonical when an Ubuntu Pro subscription is attached.
   440  ##   multiverse  - Community maintained of restricted. Software from this repository is
   441  ##                 ENTIRELY UNSUPPORTED by the Ubuntu team, and may not be under a free
   442  ##                 licence. Please satisfy yourself as to your rights to use the software.
   443  ##                 Also, please note that software in multiverse WILL NOT receive any
   444  ##                 review or updates from the Ubuntu security team.
   445  ##
   446  ## See the sources.list(5) manual page for further settings.
   447  `
   448  
   449  var ubuntuSourceSecurityHeader = `## Ubuntu security updates. Aside from URIs and Suites,
   450  ## this should mirror your choices in the previous section.
   451  `
   452  
   453  // deb822SourcesList returns the content of /etc/apt/sources.list.d/ubuntu.sources
   454  // to be used during the build process
   455  func (i *ImageDefinition) Deb822BuildSourcesList() string {
   456  	return i.deb822SourcesList(false)
   457  }
   458  
   459  // deb822SourcesList returns the content of /etc/apt/sources.list.d/ubuntu.sources
   460  // for the target image
   461  func (i *ImageDefinition) Deb822TargetSourcesList() string {
   462  	return i.deb822SourcesList(true)
   463  }
   464  
   465  // deb822SourcesList returns the content of /etc/apt/sources.list.d/ubuntu.sources
   466  // in the deb822 format.
   467  // The target param defines if the generated sources list will be used in the target image.
   468  func (i *ImageDefinition) deb822SourcesList(target bool) string {
   469  	pocket := i.Rootfs.Pocket
   470  	if target {
   471  		pocket = i.Customization.Pocket
   472  	}
   473  	pocket = strings.ToLower(pocket)
   474  
   475  	ubuntuSources := ubuntuSourceHeader + generateDeb822Section(
   476  		i.Rootfs.Mirror,
   477  		i.Series,
   478  		i.Rootfs.Components,
   479  		pocket,
   480  	)
   481  
   482  	ubuntuSources += ubuntuSourceSecurityHeader + generateDeb822Section(
   483  		i.securityMirror(),
   484  		i.Series,
   485  		i.Rootfs.Components,
   486  		pocket,
   487  	)
   488  
   489  	return ubuntuSources
   490  }