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

     1  package imagedefinition
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  
     7  	"github.com/google/go-cmp/cmp"
     8  	"github.com/xeipuuv/gojsonschema"
     9  
    10  	"github.com/canonical/ubuntu-image/internal/helper"
    11  )
    12  
    13  func TestGeneratePocketList(t *testing.T) {
    14  	t.Parallel()
    15  	asserter := helper.Asserter{T: t}
    16  	type args struct {
    17  		series         string
    18  		components     []string
    19  		mirror         string
    20  		securityMirror string
    21  		pocket         string
    22  	}
    23  
    24  	testCases := []struct {
    25  		name                string
    26  		imageDef            ImageDefinition
    27  		args                args
    28  		expectedSourcesList string
    29  	}{
    30  		{
    31  			name: "release",
    32  			args: args{
    33  				series:         "jammy",
    34  				components:     []string{"main", "universe"},
    35  				mirror:         "http://archive.ubuntu.com/ubuntu/",
    36  				securityMirror: "http://security.ubuntu.com/ubuntu/",
    37  				pocket:         "release",
    38  			},
    39  			expectedSourcesList: `# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
    40  # newer versions of the distribution.
    41  deb http://archive.ubuntu.com/ubuntu/ jammy main universe
    42  `,
    43  		},
    44  		{
    45  			name: "security",
    46  			args: args{
    47  				series:         "jammy",
    48  				components:     []string{"main"},
    49  				mirror:         "http://archive.ubuntu.com/ubuntu/",
    50  				pocket:         "security",
    51  				securityMirror: "http://security.ubuntu.com/ubuntu/",
    52  			},
    53  			expectedSourcesList: `# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
    54  # newer versions of the distribution.
    55  deb http://archive.ubuntu.com/ubuntu/ jammy main
    56  deb http://security.ubuntu.com/ubuntu/ jammy-security main
    57  `,
    58  		},
    59  		{
    60  			name: "updates",
    61  			args: args{
    62  				series:         "jammy",
    63  				components:     []string{"main", "universe", "multiverse"},
    64  				mirror:         "http://ports.ubuntu.com/",
    65  				securityMirror: "http://ports.ubuntu.com/",
    66  				pocket:         "updates",
    67  			},
    68  			expectedSourcesList: `# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
    69  # newer versions of the distribution.
    70  deb http://ports.ubuntu.com/ jammy main universe multiverse
    71  deb http://ports.ubuntu.com/ jammy-security main universe multiverse
    72  ## Major bug fix updates produced after the final release of the
    73  ## distribution.
    74  deb http://ports.ubuntu.com/ jammy-updates main universe multiverse
    75  `,
    76  		},
    77  		{
    78  			name: "proposed",
    79  			args: args{
    80  				series:         "jammy",
    81  				components:     []string{"main", "universe", "multiverse", "restricted"},
    82  				mirror:         "http://archive.ubuntu.com/ubuntu/",
    83  				securityMirror: "http://security.ubuntu.com/ubuntu/",
    84  				pocket:         "proposed",
    85  			},
    86  			expectedSourcesList: `# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
    87  # newer versions of the distribution.
    88  deb http://archive.ubuntu.com/ubuntu/ jammy main universe multiverse restricted
    89  deb http://security.ubuntu.com/ubuntu/ jammy-security main universe multiverse restricted
    90  ## Major bug fix updates produced after the final release of the
    91  ## distribution.
    92  deb http://archive.ubuntu.com/ubuntu/ jammy-updates main universe multiverse restricted
    93  deb http://archive.ubuntu.com/ubuntu/ jammy-proposed main universe multiverse restricted
    94  `,
    95  		},
    96  	}
    97  	for _, tc := range testCases {
    98  		t.Run(tc.name, func(t *testing.T) {
    99  			gotSourcesList := generateLegacySourcesList(
   100  				tc.args.series,
   101  				tc.args.components,
   102  				tc.args.mirror,
   103  				tc.args.securityMirror,
   104  				tc.args.pocket,
   105  			)
   106  
   107  			asserter.AssertEqual(tc.expectedSourcesList, gotSourcesList)
   108  		})
   109  	}
   110  }
   111  
   112  // TestCustomErrors tests the custom json schema errors that we define
   113  func TestCustomErrors(t *testing.T) {
   114  	t.Parallel()
   115  	jsonContext := gojsonschema.NewJsonContext("testContext", nil)
   116  	errDetail := gojsonschema.ErrorDetails{
   117  		"key":   "testKey",
   118  		"value": "testValue",
   119  	}
   120  	missingURLErr := NewMissingURLError(
   121  		gojsonschema.NewJsonContext("testMissingURL", jsonContext),
   122  		52,
   123  		errDetail,
   124  	)
   125  	// spot check the description format
   126  	if !strings.Contains(missingURLErr.DescriptionFormat(),
   127  		"When key {{.key}} is specified as {{.value}}, a URL must be provided") {
   128  		t.Errorf("missingURLError description format \"%s\" is invalid",
   129  			missingURLErr.DescriptionFormat())
   130  	}
   131  
   132  	invalidPPAErr := NewInvalidPPAError(
   133  		gojsonschema.NewJsonContext("testInvalidPPA", jsonContext),
   134  		52,
   135  		errDetail,
   136  	)
   137  	// spot check the description format
   138  	if !strings.Contains(invalidPPAErr.DescriptionFormat(),
   139  		"Fingerprint is required for private PPAs") {
   140  		t.Errorf("invalidPPAError description format \"%s\" is invalid",
   141  			invalidPPAErr.DescriptionFormat())
   142  	}
   143  
   144  	pathNotAbsoluteErr := NewPathNotAbsoluteError(
   145  		gojsonschema.NewJsonContext("testPathNotAbsolute", jsonContext),
   146  		52,
   147  		errDetail,
   148  	)
   149  	// spot check the description format
   150  	if !strings.Contains(pathNotAbsoluteErr.DescriptionFormat(),
   151  		"Key {{.key}} needs to be an absolute path ({{.value}})") {
   152  		t.Errorf("pathNotAbsoluteError description format \"%s\" is invalid",
   153  			pathNotAbsoluteErr.DescriptionFormat())
   154  	}
   155  	dependentKeyErr := NewDependentKeyError(
   156  		gojsonschema.NewJsonContext("testDependentKey", jsonContext),
   157  		52,
   158  		errDetail,
   159  	)
   160  	// spot check the description format
   161  	if !strings.Contains(dependentKeyErr.DescriptionFormat(),
   162  		"Key {{.key1}} cannot be used without key {{.key2}}") {
   163  		t.Errorf("dependentKeyError description format \"%s\" is invalid",
   164  			dependentKeyErr.DescriptionFormat())
   165  	}
   166  }
   167  
   168  // TestImageDefinition_SetDefaults make sure we do not add a boolean field
   169  // with a default value (because we cannot properly apply the default value)
   170  func TestImageDefinition_SetDefaults(t *testing.T) {
   171  	t.Parallel()
   172  
   173  	tests := []struct {
   174  		name     string
   175  		imageDef *ImageDefinition
   176  		want     *ImageDefinition
   177  	}{
   178  		{
   179  			name: "full",
   180  			imageDef: &ImageDefinition{
   181  				Gadget: &Gadget{},
   182  				Rootfs: &Rootfs{
   183  					Seed:    &Seed{},
   184  					Tarball: &Tarball{},
   185  				},
   186  				Customization: &Customization{
   187  					Installer:     &Installer{},
   188  					CloudInit:     &CloudInit{},
   189  					ExtraPPAs:     []*PPA{{}},
   190  					ExtraPackages: []*Package{{}},
   191  					ExtraSnaps:    []*Snap{{}},
   192  					Fstab:         []*Fstab{{}},
   193  					Manual: &Manual{
   194  						AddUser: []*AddUser{
   195  							{},
   196  						},
   197  					},
   198  				},
   199  				Artifacts: &Artifact{
   200  					Img:       &[]Img{{}},
   201  					Iso:       &[]Iso{{}},
   202  					Qcow2:     &[]Qcow2{{}},
   203  					Manifest:  &Manifest{},
   204  					Filelist:  &Filelist{},
   205  					Changelog: &Changelog{},
   206  					RootfsTar: &RootfsTar{},
   207  				},
   208  			},
   209  			want: &ImageDefinition{
   210  				Gadget: &Gadget{},
   211  				Rootfs: &Rootfs{
   212  					Seed: &Seed{
   213  						Vcs: helper.BoolPtr(true),
   214  					},
   215  					Tarball:           &Tarball{},
   216  					Components:        []string{"main", "restricted"},
   217  					Archive:           "ubuntu",
   218  					Flavor:            "ubuntu",
   219  					Mirror:            "http://archive.ubuntu.com/ubuntu/",
   220  					Pocket:            "release",
   221  					SourcesListDeb822: helper.BoolPtr(false),
   222  				},
   223  				Customization: &Customization{
   224  					Components: []string{"main", "restricted", "universe"},
   225  					Pocket:     "release",
   226  					Installer:  &Installer{},
   227  					CloudInit:  &CloudInit{},
   228  					ExtraPPAs: []*PPA{{
   229  						KeepEnabled: helper.BoolPtr(true),
   230  					}},
   231  					ExtraPackages: []*Package{{}},
   232  					ExtraSnaps: []*Snap{{
   233  						Store:   "canonical",
   234  						Channel: "stable",
   235  					}},
   236  					Fstab: []*Fstab{{
   237  						MountOptions: "defaults",
   238  					}},
   239  					Manual: &Manual{
   240  						AddUser: []*AddUser{
   241  							{
   242  								PasswordType: "hash",
   243  							},
   244  						},
   245  					},
   246  				},
   247  				Artifacts: &Artifact{
   248  					Img:       &[]Img{{}},
   249  					Iso:       &[]Iso{{}},
   250  					Qcow2:     &[]Qcow2{{}},
   251  					Manifest:  &Manifest{},
   252  					Filelist:  &Filelist{},
   253  					Changelog: &Changelog{},
   254  					RootfsTar: &RootfsTar{
   255  						Compression: "uncompressed",
   256  					},
   257  				},
   258  			},
   259  		},
   260  		{
   261  			name: "minimal conf",
   262  			imageDef: &ImageDefinition{
   263  				Gadget: &Gadget{},
   264  				Rootfs: &Rootfs{
   265  					Seed:    &Seed{},
   266  					Tarball: &Tarball{},
   267  				},
   268  			},
   269  			want: &ImageDefinition{
   270  				Gadget: &Gadget{},
   271  				Rootfs: &Rootfs{
   272  					Seed: &Seed{
   273  						Vcs: helper.BoolPtr(true),
   274  					},
   275  					Tarball:           &Tarball{},
   276  					Components:        []string{"main", "restricted"},
   277  					Archive:           "ubuntu",
   278  					Flavor:            "ubuntu",
   279  					Mirror:            "http://archive.ubuntu.com/ubuntu/",
   280  					Pocket:            "release",
   281  					SourcesListDeb822: helper.BoolPtr(false),
   282  				},
   283  			},
   284  		},
   285  	}
   286  	for _, tt := range tests {
   287  		t.Run(tt.name, func(t *testing.T) {
   288  			asserter := helper.Asserter{T: t}
   289  			err := helper.SetDefaults(tt.imageDef)
   290  			asserter.AssertErrNil(err, true)
   291  
   292  			asserter.AssertEqual(tt.want, tt.imageDef, cmp.AllowUnexported(ImageDefinition{}))
   293  		})
   294  	}
   295  }
   296  
   297  func TestImageDefinition_securityMirror(t *testing.T) {
   298  	type fields struct {
   299  		Architecture string
   300  		Rootfs       *Rootfs
   301  	}
   302  	tests := []struct {
   303  		name   string
   304  		fields fields
   305  		want   string
   306  	}{
   307  		{
   308  			name: "amd64",
   309  			fields: fields{
   310  				Architecture: "amd64",
   311  				Rootfs: &Rootfs{
   312  					Mirror: "http://archive.ubuntu.com/ubuntu/",
   313  				},
   314  			},
   315  			want: "http://security.ubuntu.com/ubuntu/",
   316  		},
   317  		{
   318  			name: "i386",
   319  			fields: fields{
   320  				Architecture: "i386",
   321  				Rootfs: &Rootfs{
   322  					Mirror: "http://archive.ubuntu.com/ubuntu/",
   323  				},
   324  			},
   325  			want: "http://security.ubuntu.com/ubuntu/",
   326  		},
   327  		{
   328  			name: "arm64",
   329  			fields: fields{
   330  				Architecture: "arm64",
   331  				Rootfs: &Rootfs{
   332  					Mirror: "http://archive.ubuntu.com/ubuntu/",
   333  				},
   334  			},
   335  			want: "http://archive.ubuntu.com/ubuntu/",
   336  		},
   337  		{
   338  			name: "no arch",
   339  			fields: fields{
   340  				Rootfs: &Rootfs{
   341  					Mirror: "http://archive.ubuntu.com/ubuntu/",
   342  				},
   343  			},
   344  			want: "http://archive.ubuntu.com/ubuntu/",
   345  		},
   346  	}
   347  	for _, tt := range tests {
   348  		t.Run(tt.name, func(t *testing.T) {
   349  			imageDef := ImageDefinition{
   350  				Architecture: tt.fields.Architecture,
   351  				Rootfs:       tt.fields.Rootfs,
   352  			}
   353  			if got := imageDef.securityMirror(); got != tt.want {
   354  				t.Errorf("ImageDefinition.securityMirror() = %v, want %v", got, tt.want)
   355  			}
   356  		})
   357  	}
   358  }
   359  
   360  func Test_generateDeb822Section(t *testing.T) {
   361  	asserter := helper.Asserter{T: t}
   362  	type args struct {
   363  		mirror     string
   364  		series     string
   365  		components []string
   366  		pocket     string
   367  	}
   368  	tests := []struct {
   369  		name string
   370  		args args
   371  		want string
   372  	}{
   373  		{
   374  			name: "release",
   375  			args: args{
   376  				mirror:     "http://archive.ubuntu.com/ubuntu/",
   377  				series:     "jammy",
   378  				components: []string{"main", "restricted"},
   379  				pocket:     "release",
   380  			},
   381  			want: `Types: deb
   382  URIs: http://archive.ubuntu.com/ubuntu/
   383  Suites: jammy
   384  Components: main restricted
   385  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
   386  
   387  `,
   388  		},
   389  		{
   390  			name: "security",
   391  			args: args{
   392  				mirror:     "http://security.ubuntu.com/ubuntu/",
   393  				series:     "jammy",
   394  				components: []string{"main", "restricted"},
   395  				pocket:     "security",
   396  			},
   397  			want: `Types: deb
   398  URIs: http://security.ubuntu.com/ubuntu/
   399  Suites: jammy-security
   400  Components: main restricted
   401  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
   402  
   403  `,
   404  		},
   405  		{
   406  			name: "proposed",
   407  			args: args{
   408  				mirror:     "http://archive.ubuntu.com/ubuntu/",
   409  				series:     "jammy",
   410  				components: []string{"main", "restricted"},
   411  				pocket:     "proposed",
   412  			},
   413  			want: `Types: deb
   414  URIs: http://archive.ubuntu.com/ubuntu/
   415  Suites: jammy jammy-updates jammy-proposed
   416  Components: main restricted
   417  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
   418  
   419  `,
   420  		},
   421  		{
   422  			name: "updates",
   423  			args: args{
   424  				mirror:     "http://archive.ubuntu.com/ubuntu/",
   425  				series:     "jammy",
   426  				components: []string{"main", "restricted"},
   427  				pocket:     "updates",
   428  			},
   429  			want: `Types: deb
   430  URIs: http://archive.ubuntu.com/ubuntu/
   431  Suites: jammy jammy-updates
   432  Components: main restricted
   433  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
   434  
   435  `,
   436  		},
   437  		{
   438  			name: "no pocket",
   439  			args: args{
   440  				mirror:     "http://archive.ubuntu.com/ubuntu/",
   441  				series:     "jammy",
   442  				components: []string{"main", "restricted"},
   443  				pocket:     "",
   444  			},
   445  			want: `Types: deb
   446  URIs: http://archive.ubuntu.com/ubuntu/
   447  Suites: 
   448  Components: main restricted
   449  Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
   450  
   451  `,
   452  		},
   453  	}
   454  	for _, tc := range tests {
   455  		t.Run(tc.name, func(t *testing.T) {
   456  			got := generateDeb822Section(tc.args.mirror, tc.args.series, tc.args.components, tc.args.pocket)
   457  			asserter.AssertEqual(tc.want, got)
   458  		})
   459  	}
   460  }