Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pro archive support ci #174

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/spread.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ jobs:
go-version: '>=1.17.0'

- name: Build and run spread
env:
PRO_TOKEN: ${{ secrets.PRO_TOKEN }}
run: |
(cd _spread/cmd/spread && go build)
_spread/cmd/spread/spread -v focal jammy mantic noble
43 changes: 43 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,46 @@ jobs:
with:
name: chisel-test-coverage.html
path: ./*.html

real-archive-tests:
# Do not change to newer releases as "fips" may not be available there.
runs-on: ubuntu-20.04
name: Real Archive Tests
steps:
- uses: actions/checkout@v3

- uses: actions/setup-go@v3
with:
go-version-file: 'go.mod'

- name: Run real archive tests
env:
PRO_TOKEN: ${{ secrets.PRO_TOKEN }}
run: |
set -ex

detach() {
sudo pro detach --assume-yes || true
sudo rm -f /etc/apt/auth.conf.d/90ubuntu-advantage
}
trap detach EXIT

# Attach pro token and enable services
sudo pro attach ${PRO_TOKEN} --no-auto-enable

# Cannot enable fips and fips-updates at the same time.
# Hack: enable fips, copy the credentials and then after enabling
# other services, add the credentials back.
sudo pro enable fips --assume-yes
sudo cp /etc/apt/auth.conf.d/90ubuntu-advantage /etc/apt/auth.conf.d/90ubuntu-advantage.fips-creds
# This will disable the fips service.
sudo pro enable fips-updates esm-apps esm-infra --assume-yes
# Add the fips credentials back.
sudo sh -c 'cat /etc/apt/auth.conf.d/90ubuntu-advantage.fips-creds >> /etc/apt/auth.conf.d/90ubuntu-advantage'
sudo rm /etc/apt/auth.conf.d/90ubuntu-advantage.fips-creds

# Make apt credentials accessible to USER.
sudo setfacl -m u:$USER:r /etc/apt/auth.conf.d/90ubuntu-advantage

# Run tests on Pro and non-Pro real archives.
go test ./internal/archive/ -v --real-archive --real-pro-archive
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,48 @@ provided packages and install only the desired slices into the *myrootfs*
folder, according to the slice definitions available in the
["ubuntu-22.04" chisel-releases branch](<https://github.com/canonical/chisel-releases/tree/ubuntu-22.04>).

## Support for Pro archives
> [!IMPORTANT]
> To chisel a Pro package you need to have a Pro-enabled host.

To fetch and install slices from Ubuntu Pro packages, the Pro archive has to be
defined with the `archives.<name>.pro` field in `chisel.yaml`:


```yaml
# chisel.yaml
format: v1
archives:
<name>:
pro: <value>
...
...
```

The following Pro archives are currently supported:

| `pro` value | Archive URL | Corresponding Ubuntu Pro service |
| - | - |----------------------------------|
| fips | https://esm.ubuntu.com/fips/ubuntu | fips |
| fips-updates | https://esm.ubuntu.com/fips-updates/ubuntu | fips-updates |
| apps | https://esm.ubuntu.com/apps/ubuntu | esm-apps |
| infra | https://esm.ubuntu.com/infra/ubuntu | esm-infra |

If the system is using the [Pro client](https://ubuntu.com/pro/tutorial), and the
services are enabled, the credentials will be automatically picked up from
`/etc/apt/auth.conf.d/`. However, the default permissions of the credentials file
need to be changed so that Chisel can read it. Example:
```shell
sudo pro enable fips

sudo setfacl -m u:$USER:r /etc/apt/auth.conf.d/90ubuntu-advantage
# or, alternatively,
sudo chmod u+r /etc/apt/auth.conf.d/90ubuntu-advantage
```

The location of the credentials can be configured using the environment variable
`CHISEL_AUTH_DIR`.

## Reference

### Chisel releases
Expand Down
5 changes: 5 additions & 0 deletions cmd/chisel/cmd_cut.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,15 @@ func (cmd *cmdCut) Execute(args []string) error {
Arch: cmd.Arch,
Suites: archiveInfo.Suites,
Components: archiveInfo.Components,
Pro: archiveInfo.Pro,
CacheDir: cache.DefaultDir("chisel"),
PubKeys: archiveInfo.PubKeys,
})
if err != nil {
if err == archive.ErrCredentialsNotFound {
logf("Archive %q ignored: credentials not found", archiveName)
continue
}
return err
}
archives[archiveName] = openArchive
Expand Down
1 change: 1 addition & 0 deletions cmd/chisel/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ func run() error {
deb.SetLogger(log.Default())
setup.SetLogger(log.Default())
slicer.SetLogger(log.Default())
SetLogger(log.Default())

parser := Parser()
xtra, err := parser.Parse()
Expand Down
109 changes: 97 additions & 12 deletions internal/archive/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Options struct {
Arch string
Suites []string
Components []string
Pro string
CacheDir string
PubKeys []*packet.PublicKey
}
Expand Down Expand Up @@ -77,6 +78,8 @@ type ubuntuArchive struct {
indexes []*ubuntuIndex
cache *cache.Cache
pubKeys []*packet.PublicKey
baseURL string
creds *credentials
}

type ubuntuIndex struct {
Expand Down Expand Up @@ -147,6 +150,54 @@ func (a *ubuntuArchive) Info(pkg string) (*PackageInfo, error) {
const ubuntuURL = "http://archive.ubuntu.com/ubuntu/"
const ubuntuPortsURL = "http://ports.ubuntu.com/ubuntu-ports/"

const (
ProFIPS = "fips"
ProFIPSUpdates = "fips-updates"
ProApps = "apps"
ProInfra = "infra"
)

var proArchiveInfo = map[string]struct {
BaseURL, Label string
}{
ProFIPS: {
BaseURL: "https://esm.ubuntu.com/fips/ubuntu/",
Label: "UbuntuFIPS",
},
ProFIPSUpdates: {
BaseURL: "https://esm.ubuntu.com/fips-updates/ubuntu/",
Label: "UbuntuFIPSUpdates",
},
ProApps: {
BaseURL: "https://esm.ubuntu.com/apps/ubuntu/",
Label: "UbuntuESMApps",
},
ProInfra: {
BaseURL: "https://esm.ubuntu.com/infra/ubuntu/",
Label: "UbuntuESM",
},
}

func archiveURL(pro, arch string) (string, *credentials, error) {
if pro != "" {
archiveInfo, ok := proArchiveInfo[pro]
if !ok {
return "", nil, fmt.Errorf("invalid pro value: %q", pro)
}
url := archiveInfo.BaseURL
creds, err := findCredentials(url)
if err != nil {
return "", nil, err
}
return url, creds, nil
}

if arch == "amd64" || arch == "i386" {
return ubuntuURL, nil, nil
}
return ubuntuPortsURL, nil, nil
}

func openUbuntu(options *Options) (Archive, error) {
if len(options.Components) == 0 {
return nil, fmt.Errorf("archive options missing components")
Expand All @@ -158,12 +209,19 @@ func openUbuntu(options *Options) (Archive, error) {
return nil, fmt.Errorf("archive options missing version")
}

baseURL, creds, err := archiveURL(options.Pro, options.Arch)
if err != nil {
return nil, err
}

archive := &ubuntuArchive{
options: *options,
cache: &cache.Cache{
Dir: options.CacheDir,
},
pubKeys: options.PubKeys,
baseURL: baseURL,
creds: creds,
}

for _, suite := range options.Suites {
Expand All @@ -184,6 +242,11 @@ func openUbuntu(options *Options) (Archive, error) {
return nil, err
}
release = index.release
if !index.supportsArch(options.Arch) {
// Release does not support the specified architecture, do
// not add any of its indexes.
break
}
err = index.checkComponents(options.Components)
if err != nil {
return nil, err
Expand All @@ -201,7 +264,7 @@ func openUbuntu(options *Options) (Archive, error) {
}

func (index *ubuntuIndex) fetchRelease() error {
logf("Fetching %s %s %s suite details...", index.label, index.version, index.suite)
logf("Fetching %s %s %s suite details...", index.displayName(), index.version, index.suite)
reader, err := index.fetch("InRelease", "", fetchDefault)
if err != nil {
return err
Expand Down Expand Up @@ -235,12 +298,14 @@ func (index *ubuntuIndex) fetchRelease() error {
if err != nil {
return fmt.Errorf("cannot parse InRelease file: %v", err)
}
section := ctrl.Section("Ubuntu")
// Parse the appropriate section for the type of archive.
label := "Ubuntu"
if index.archive.options.Pro != "" {
label = proArchiveInfo[index.archive.options.Pro].Label
}
section := ctrl.Section(label)
if section == nil {
section = ctrl.Section("UbuntuProFIPS")
if section == nil {
return fmt.Errorf("corrupted archive InRelease file: no Ubuntu section")
}
return fmt.Errorf("corrupted archive InRelease file: no %s section", label)
}
logf("Release date: %s", section.Get("Date"))

Expand All @@ -256,7 +321,7 @@ func (index *ubuntuIndex) fetchIndex() error {
return fmt.Errorf("%s is missing from %s %s component digests", packagesPath, index.suite, index.component)
}

logf("Fetching index for %s %s %s %s component...", index.label, index.version, index.suite, index.component)
logf("Fetching index for %s %s %s %s component...", index.displayName(), index.version, index.suite, index.component)
reader, err := index.fetch(packagesPath+".gz", digest, fetchBulk)
if err != nil {
return err
Expand All @@ -270,6 +335,17 @@ func (index *ubuntuIndex) fetchIndex() error {
return nil
}

// supportsArch returns true if the Architectures field in the index release
// contains "arch". Per the Debian wiki [1], index release files should list the
// supported architectures in the "Architectures" field.
// The "ubuntuURL" archive only supports the amd64 and i386 architectures
// whereas the "ubuntuPortsURL" one supports the rest. But each of them
// (faultly) specifies all those architectures in their InRelease files.
// Reference: [1] https://wiki.debian.org/DebianRepository/Format#Architectures
func (index *ubuntuIndex) supportsArch(arch string) bool {
return strings.Contains(index.release.Get("Architectures"), arch)
}

func (index *ubuntuIndex) checkComponents(components []string) error {
releaseComponents := strings.Fields(index.release.Get("Components"))
for _, c1 := range components {
Expand All @@ -295,10 +371,7 @@ func (index *ubuntuIndex) fetch(suffix, digest string, flags fetchFlags) (io.Rea
return nil, err
}

baseURL := ubuntuURL
if index.arch != "amd64" && index.arch != "i386" {
baseURL = ubuntuPortsURL
}
baseURL, creds := index.archive.baseURL, index.archive.creds

var url string
if strings.HasPrefix(suffix, "pool/") {
Expand All @@ -311,6 +384,9 @@ func (index *ubuntuIndex) fetch(suffix, digest string, flags fetchFlags) (io.Rea
if err != nil {
return nil, fmt.Errorf("cannot create HTTP request: %v", err)
}
if creds != nil && !creds.Empty() {
req.SetBasicAuth(creds.Username, creds.Password)
}
var resp *http.Response
if flags&fetchBulk != 0 {
resp, err = bulkDo(req)
Expand All @@ -325,7 +401,9 @@ func (index *ubuntuIndex) fetch(suffix, digest string, flags fetchFlags) (io.Rea
switch resp.StatusCode {
case 200:
// ok
case 401, 404:
case 401:
return nil, fmt.Errorf("cannot fetch from %q: unauthorized", index.label)
case 404:
return nil, fmt.Errorf("cannot find archive data")
default:
return nil, fmt.Errorf("error from archive: %v", resp.Status)
Expand Down Expand Up @@ -363,3 +441,10 @@ func sectionPackageInfo(section control.Section) *PackageInfo {
SHA256: section.Get("SHA256"),
}
}

func (index *ubuntuIndex) displayName() string {
if index.archive.options.Pro == "" {
return index.label
}
return index.label + " " + index.archive.options.Pro + " (pro)"
}
Loading
Loading