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

feat: add support for chisel release format "v1" #115

Merged
merged 2 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,50 @@ archives:
Example:
```yaml
format: chisel-v1
format: v1

archives:
ubuntu:
version: 22.04
components: [main, universe]
suites: [jammy, jammy-security, jammy-updates]
public-keys: [ubuntu-archive-key-2018]

public-keys:
# Ubuntu Archive Automatic Signing Key (2018) <ftpmaster@ubuntu.com>
# rsa4096/f6ecb3762474eda9d21b7022871920d1991bc93c 2018-09-17T15:01:46Z
ubuntu-archive-key-2018:
id: "871920D1991BC93C"
armor: |
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBFufwdoBEADv/Gxytx/LcSXYuM0MwKojbBye81s0G1nEx+lz6VAUpIUZnbkq
dXBHC+dwrGS/CeeLuAjPRLU8AoxE/jjvZVp8xFGEWHYdklqXGZ/gJfP5d3fIUBtZ
HZEJl8B8m9pMHf/AQQdsC+YzizSG5t5Mhnotw044LXtdEEkx2t6Jz0OGrh+5Ioxq
X7pZiq6Cv19BohaUioKMdp7ES6RYfN7ol6HSLFlrMXtVfh/ijpN9j3ZhVGVeRC8k
KHQsJ5PkIbmvxBiUh7SJmfZUx0IQhNMaDHXfdZAGNtnhzzNReb1FqNLSVkrS/Pns
AQzMhG1BDm2VOSF64jebKXffFqM5LXRQTeqTLsjUbbrqR6s/GCO8UF7jfUj6I7ta
LygmsHO/JD4jpKRC0gbpUBfaiJyLvuepx3kWoqL3sN0LhlMI80+fA7GTvoOx4tpq
VlzlE6TajYu+jfW3QpOFS5ewEMdL26hzxsZg/geZvTbArcP+OsJKRmhv4kNo6Ayd
yHQ/3ZV/f3X9mT3/SPLbJaumkgp3Yzd6t5PeBu+ZQk/mN5WNNuaihNEV7llb1Zhv
Y0Fxu9BVd/BNl0rzuxp3rIinB2TX2SCg7wE5xXkwXuQ/2eTDE0v0HlGntkuZjGow
DZkxHZQSxZVOzdZCRVaX/WEFLpKa2AQpw5RJrQ4oZ/OfifXyJzP27o03wQARAQAB
tEJVYnVudHUgQXJjaGl2ZSBBdXRvbWF0aWMgU2lnbmluZyBLZXkgKDIwMTgpIDxm
dHBtYXN0ZXJAdWJ1bnR1LmNvbT6JAjgEEwEKACIFAlufwdoCGwMGCwkIBwMCBhUI
AgkKCwQWAgMBAh4BAheAAAoJEIcZINGZG8k8LHMQAKS2cnxz/5WaoCOWArf5g6UH
beOCgc5DBm0hCuFDZWWv427aGei3CPuLw0DGLCXZdyc5dqE8mvjMlOmmAKKlj1uG
g3TYCbQWjWPeMnBPZbkFgkZoXJ7/6CB7bWRht1sHzpt1LTZ+SYDwOwJ68QRp7DRa
Zl9Y6QiUbeuhq2DUcTofVbBxbhrckN4ZteLvm+/nG9m/ciopc66LwRdkxqfJ32Cy
q+1TS5VaIJDG7DWziG+Kbu6qCDM4QNlg3LH7p14CrRxAbc4lvohRgsV4eQqsIcdF
kuVY5HPPj2K8TqpY6STe8Gh0aprG1RV8ZKay3KSMpnyV1fAKn4fM9byiLzQAovC0
LZ9MMMsrAS/45AvC3IEKSShjLFn1X1dRCiO6/7jmZEoZtAp53hkf8SMBsi78hVNr
BumZwfIdBA1v22+LY4xQK8q4XCoRcA9G+pvzU9YVW7cRnDZZGl0uwOw7z9PkQBF5
KFKjWDz4fCk+K6+YtGpovGKekGBb8I7EA6UpvPgqA/QdI0t1IBP0N06RQcs1fUaA
QEtz6DGy5zkRhR4pGSZn+dFET7PdAjEK84y7BdY4t+U1jcSIvBj0F2B7LwRL7xGp
SpIKi/ekAXLs117bvFHaCvmUYN7JVp1GMmVFxhIdx6CFm3fxG8QjNb5tere/YqK+
uOgcXny1UlwtCUzlrSaP
=9AdM
-----END PGP PUBLIC KEY BLOCK-----
```
#### Slice definitions
Expand Down
110 changes: 110 additions & 0 deletions internal/setup/releaseChiselV1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package setup

import (
"bytes"
"fmt"

"golang.org/x/crypto/openpgp/packet"
"gopkg.in/yaml.v3"

"github.com/canonical/chisel/internal/pgputil"
)

func parseReleaseChiselV1(baseDir, filePath string, data []byte) (*Release, error) {
Copy link
Collaborator Author

@letFunny letFunny Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[For reviewer]: The code was moved from setup.go and the only change was moving the types inside of the function to prevent name clashes with v1. Proof: https://www.diffchecker.com/zg6qjOmM/ .

type yamlArchive struct {
Version string `yaml:"version"`
Suites []string `yaml:"suites"`
Components []string `yaml:"components"`
Default bool `yaml:"default"`
PubKeys []string `yaml:"v1-public-keys"`
}
type yamlPubKey struct {
ID string `yaml:"id"`
Armor string `yaml:"armor"`
}
type yamlRelease struct {
Format string `yaml:"format"`
Archives map[string]yamlArchive `yaml:"archives"`
PubKeys map[string]yamlPubKey `yaml:"v1-public-keys"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't feel like we need all of this logic when the only thing changing is the name of this field. How about adding the two fields to the same struct, and if the format is the old one, just put it in the new field? Everything else could be the same, I guess?

Copy link
Collaborator Author

@letFunny letFunny Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the same discussion with Rafid on Friday, which is why I added a comment to try to explain it. I see your point about the code being much sorter that way (even with some extra logic for error reporting). However, I think it couples both format parsers when they should be independent, for example if we have to change v1 or support a third format at the same time. It just happens that the implementation is similar even though they represent different things.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will make your proposed changes if you disagree, just wanted to make sure I got the rationale across correctly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few points:

  1. A couple of lines is much better to maintain than hundreds and multiple files with repeated code
  2. We cannot break v1 after it's released, so we won't break chisel-v1 either because the only delta is the two lines
  3. chisel-v1 will be completely gone in a few weeks because chisel-releases will be updated

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your points, but I still think the flexibility and separation of concerns is still worth it. Especially if there are unforeseen changes that make it necessary to not drop "chisel-v1" in a few weeks. That said, I disagree and commit, I have made the changes to the PR.

}
const yamlReleaseFormat = "chisel-v1"

release := &Release{
Path: baseDir,
Packages: make(map[string]*Package),
Archives: make(map[string]*Archive),
}

fileName := stripBase(baseDir, filePath)

yamlVar := yamlRelease{}
dec := yaml.NewDecoder(bytes.NewBuffer(data))
dec.KnownFields(false)
err := dec.Decode(&yamlVar)
if err != nil {
return nil, fmt.Errorf("%s: cannot parse release definition: %v", fileName, err)
}
if yamlVar.Format != yamlReleaseFormat {
return nil, fmt.Errorf("%s: expected format %q, got %q", fileName, yamlReleaseFormat, yamlVar.Format)
}
if len(yamlVar.Archives) == 0 {
return nil, fmt.Errorf("%s: no archives defined", fileName)
}

// Decode the public keys and match against provided IDs.
pubKeys := make(map[string]*packet.PublicKey, len(yamlVar.PubKeys))
for keyName, yamlPubKey := range yamlVar.PubKeys {
key, err := pgputil.DecodePubKey([]byte(yamlPubKey.Armor))
if err != nil {
return nil, fmt.Errorf("%s: cannot decode public key %q: %w", fileName, keyName, err)
}
if yamlPubKey.ID != key.KeyIdString() {
return nil, fmt.Errorf("%s: public key %q armor has incorrect ID: expected %q, got %q", fileName, keyName, yamlPubKey.ID, key.KeyIdString())
}
pubKeys[keyName] = key
}

for archiveName, details := range yamlVar.Archives {
if details.Version == "" {
return nil, fmt.Errorf("%s: archive %q missing version field", fileName, archiveName)
}
if len(details.Suites) == 0 {
adjective := ubuntuAdjectives[details.Version]
if adjective == "" {
return nil, fmt.Errorf("%s: archive %q missing suites field", fileName, archiveName)
}
details.Suites = []string{adjective}
}
if len(details.Components) == 0 {
return nil, fmt.Errorf("%s: archive %q missing components field", fileName, archiveName)
}
if len(yamlVar.Archives) == 1 {
details.Default = true
} else if details.Default && release.DefaultArchive != "" {
return nil, fmt.Errorf("%s: more than one default archive: %s, %s", fileName, release.DefaultArchive, archiveName)
}
if details.Default {
release.DefaultArchive = archiveName
}
if len(details.PubKeys) == 0 {
return nil, fmt.Errorf("%s: archive %q missing v1-public-keys field", fileName, archiveName)
}
var archiveKeys []*packet.PublicKey
for _, keyName := range details.PubKeys {
key, ok := pubKeys[keyName]
if !ok {
return nil, fmt.Errorf("%s: archive %q refers to undefined public key %q", fileName, archiveName, keyName)
}
archiveKeys = append(archiveKeys, key)
}
release.Archives[archiveName] = &Archive{
Name: archiveName,
Version: details.Version,
Suites: details.Suites,
Components: details.Components,
PubKeys: archiveKeys,
}
}

return release, err
}
110 changes: 110 additions & 0 deletions internal/setup/releaseV1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package setup

import (
"bytes"
"fmt"

"golang.org/x/crypto/openpgp/packet"
"gopkg.in/yaml.v3"

"github.com/canonical/chisel/internal/pgputil"
)

func parseReleaseV1(baseDir, filePath string, data []byte) (*Release, error) {
Copy link
Collaborator Author

@letFunny letFunny Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[For reviewer]: Diff against chisel-v1 https://www.diffchecker.com/nYQFzN77/ .

type yamlArchive struct {
Version string `yaml:"version"`
Suites []string `yaml:"suites"`
Components []string `yaml:"components"`
Default bool `yaml:"default"`
PubKeys []string `yaml:"public-keys"`
}
type yamlPubKey struct {
ID string `yaml:"id"`
Armor string `yaml:"armor"`
}
type yamlRelease struct {
Format string `yaml:"format"`
Archives map[string]yamlArchive `yaml:"archives"`
PubKeys map[string]yamlPubKey `yaml:"public-keys"`
}
const yamlReleaseFormat = "v1"

release := &Release{
Path: baseDir,
Packages: make(map[string]*Package),
Archives: make(map[string]*Archive),
}

fileName := stripBase(baseDir, filePath)

yamlVar := yamlRelease{}
dec := yaml.NewDecoder(bytes.NewBuffer(data))
dec.KnownFields(false)
err := dec.Decode(&yamlVar)
if err != nil {
return nil, fmt.Errorf("%s: cannot parse release definition: %v", fileName, err)
}
if yamlVar.Format != yamlReleaseFormat {
return nil, fmt.Errorf("%s: expected format %q, got %q", fileName, yamlReleaseFormat, yamlVar.Format)
}
if len(yamlVar.Archives) == 0 {
return nil, fmt.Errorf("%s: no archives defined", fileName)
}

// Decode the public keys and match against provided IDs.
pubKeys := make(map[string]*packet.PublicKey, len(yamlVar.PubKeys))
for keyName, yamlPubKey := range yamlVar.PubKeys {
key, err := pgputil.DecodePubKey([]byte(yamlPubKey.Armor))
if err != nil {
return nil, fmt.Errorf("%s: cannot decode public key %q: %w", fileName, keyName, err)
}
if yamlPubKey.ID != key.KeyIdString() {
return nil, fmt.Errorf("%s: public key %q armor has incorrect ID: expected %q, got %q", fileName, keyName, yamlPubKey.ID, key.KeyIdString())
}
pubKeys[keyName] = key
}

for archiveName, details := range yamlVar.Archives {
if details.Version == "" {
return nil, fmt.Errorf("%s: archive %q missing version field", fileName, archiveName)
}
if len(details.Suites) == 0 {
adjective := ubuntuAdjectives[details.Version]
if adjective == "" {
return nil, fmt.Errorf("%s: archive %q missing suites field", fileName, archiveName)
}
details.Suites = []string{adjective}
}
if len(details.Components) == 0 {
return nil, fmt.Errorf("%s: archive %q missing components field", fileName, archiveName)
}
if len(yamlVar.Archives) == 1 {
details.Default = true
} else if details.Default && release.DefaultArchive != "" {
return nil, fmt.Errorf("%s: more than one default archive: %s, %s", fileName, release.DefaultArchive, archiveName)
}
if details.Default {
release.DefaultArchive = archiveName
}
if len(details.PubKeys) == 0 {
return nil, fmt.Errorf("%s: archive %q missing public-keys field", fileName, archiveName)
}
var archiveKeys []*packet.PublicKey
for _, keyName := range details.PubKeys {
key, ok := pubKeys[keyName]
if !ok {
return nil, fmt.Errorf("%s: archive %q refers to undefined public key %q", fileName, archiveName, keyName)
}
archiveKeys = append(archiveKeys, key)
}
release.Archives[archiveName] = &Archive{
Name: archiveName,
Version: details.Version,
Suites: details.Suites,
Components: details.Components,
PubKeys: archiveKeys,
}
}

return release, err
}
Loading
Loading