carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "os"
8
9 "github.com/carlosframework/platform/internal/updatewords"
10)
11
12// ErrAuthorizeChanged is an authorize whose version was reshipped between the
13// plan and the confirmation; ErrAuthorizeRootOnly a scoped key.
14var (
15 ErrAuthorizeChanged = errors.New("release: the manifest changed since the authorize plan was made")
16 ErrAuthorizeRootOnly = errors.New("release: authorizing a build needs the root key")
17)
18
19// AuthorizePlan is what `carlos releases authorize` shows: the digest the
20// record will carry and every artifact's sha256 (spec §4.1).
21type AuthorizePlan struct {
22 Account, App, Version, ManifestDigest string
23 Artifacts map[string]Artifact
24}
25
26// PlanAuthorize reads the version's canonical manifest. It writes nothing.
27func PlanAuthorize(ctx context.Context, s Store, acct, app, version string) (*AuthorizePlan, error) {
28 m, raw, err := GetManifestRaw(ctx, s, acct, app, version)
29 if err != nil {
30 return nil, fmt.Errorf("%s: %w", fmt.Sprintf(updatewords.Text("settings.authorize.not-shipped"), version), err)
31 }
32 return &AuthorizePlan{Account: acct, App: app, Version: version, ManifestDigest: ManifestDigest(raw), Artifacts: m.Artifacts}, nil
33}
34
35// claimRawAuthorize signs a record for exactly the plan's bytes: an explicit
36// act by the publisher, never automatic (spec §4.1). The manifest is read
37// again and must still hash to the plan's digest. already is true when a
38// record for these bytes exists, in which case nothing is written.
39func (sg Signer) claimRawAuthorize(ctx context.Context, s Store, plan *AuthorizePlan, actor string) (already bool, err error) {
40 if sg.scope != nil || sg.grant != nil {
41 return false, fmt.Errorf("%w: %s", ErrAuthorizeRootOnly, updatewords.Text("settings.authorize.root-only"))
42 }
43 _, raw, err := GetManifestRaw(ctx, s, plan.Account, plan.App, plan.Version)
44 if err != nil {
45 // If the version vanished (os.ErrNotExist), that's a change; keep the cause
46 if errors.Is(err, os.ErrNotExist) {
47 return false, fmt.Errorf("%w: %s: %w", ErrAuthorizeChanged, fmt.Sprintf(updatewords.Text("settings.authorize.changed"), plan.Version), err)
48 }
49 // Any other error is a real failure, not a change
50 return false, fmt.Errorf("authorize: read the manifest of %s: %w", plan.Version, err)
51 }
52 // Read succeeded, check if digest changed
53 if ManifestDigest(raw) != plan.ManifestDigest {
54 return false, fmt.Errorf("%w: %s", ErrAuthorizeChanged, fmt.Sprintf(updatewords.Text("settings.authorize.changed"), plan.Version))
55 }
56 if cur, err := ReadPromotionRecord(ctx, s, sg.verifyPub(), plan.Account, plan.App, plan.Version); err == nil && cur.ManifestDigest == plan.ManifestDigest {
57 return true, nil
58 }
59 return false, sg.WritePromotionRecord(ctx, s, PromotionRecord{Account: plan.Account, App: plan.App, Version: plan.Version,
60 ManifestDigest: plan.ManifestDigest, Authorized: true, Actor: actor})
61}
62