Skip to content
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
33 changes: 21 additions & 12 deletions module/services/accounts/code/fixtures/installations.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,18 @@ func (i *Installations) Converge(ctx context.Context) error {
// interfaces decide with (coreInstall). A target the core has not
// composed yet keeps the row for the next pass.
coreWaits := false
switch err := i.coreInstall(ctx, row); {
switch active, err := i.coreInstall(ctx, row); {
case errors.Is(err, errCoreTargetNotComposed):
coreWaits = true
case err != nil:
failures = append(failures, fmt.Errorf("fixture installation %s: %w", row.declared.describe(), err))
case active:
// accounts' record of the installation follows the core's: the
// core's is active, so accounts' is, and its teams can be
// exposed below in this same pass.
if err := i.service.FollowCoreInstallation(ctx, row.orgID, installation.GetId()); err != nil {
failures = append(failures, fmt.Errorf("fixture installation %s: %w", row.declared.describe(), err))
}
}
// Each declared team is exposed once the installation admits it (an
// installation still pending its install step does not); one not
Expand Down Expand Up @@ -187,46 +194,48 @@ var errCoreTargetNotComposed = errors.New("the authority core has not composed t
// owner and its role to each declared team (GrantRoleAsHost), as the local
// authority's fixture does. Each step is idempotent, so a reseed repeats it
// harmlessly. A host whose core is not configured installs nothing there.
func (i *Installations) coreInstall(ctx context.Context, row pendingInstallation) error {
func (i *Installations) coreInstall(ctx context.Context, row pendingInstallation) (bool, error) {
core, err := i.service.AuthorityCore()
if errors.Is(err, business.ErrAuthorityCoreUnconfigured) {
return nil
return false, nil
}
if err != nil {
return err
return false, err
}
// A module's installation is accounts' own record, which Converge's install
// made; the core reads it and installs no module itself. A target the core
// has not composed yet is waited on: the pass that applies its delivery
// composes it.
m, err := core.Composed(ctx, row.declared.Target)
if code, ok := domain.CodeOf(err); ok && code == domain.NotFound {
return fmt.Errorf("%w: %s", errCoreTargetNotComposed, row.declared.Target)
return false, fmt.Errorf("%w: %s", errCoreTargetNotComposed, row.declared.Target)
}
if err != nil {
return fmt.Errorf("the authority core: %w", err)
return false, fmt.Errorf("the authority core: %w", err)
}
if m.Kind != domain.KindSolution {
return nil
return false, nil
}
inst, err := core.InstallAsHost(ctx, row.orgID, row.declared.Target, row.declared.Role, row.ownerID, i.service.AuthorityInstallStep())
switch {
case errors.Is(err, domain.ErrAlreadyInstalled):
// Installed already: its grants are granted again, idempotently.
// Installed already, and active: its grants are granted again,
// idempotently.
inst.ID = authority.StableInstallationID(row.orgID, row.declared.Target)
inst.Status = domain.Active
case err != nil:
return fmt.Errorf("the authority core: %w", err)
return false, fmt.Errorf("the authority core: %w", err)
}
grants := []authority.RoleRequest{{Organization: row.orgID, InstallationID: inst.ID, Role: domain.InstallerRole, PersonID: row.ownerID}}
for _, teamID := range row.allTeamIDs {
grants = append(grants, authority.RoleRequest{Organization: row.orgID, InstallationID: inst.ID, Role: row.declared.Role, TeamID: teamID})
}
for _, grant := range grants {
if _, err := core.GrantRoleAsHost(ctx, grant); err != nil {
return fmt.Errorf("the authority core: grant %s: %w", grant.Role, err)
return false, fmt.Errorf("the authority core: grant %s: %w", grant.Role, err)
}
}
return nil
return inst.Status == domain.Active, nil
}

// ConvergeAuthority performs every declared installation in the authority core
Expand All @@ -241,7 +250,7 @@ func (i *Installations) ConvergeAuthority(ctx context.Context) error {
defer i.mu.Unlock()
var failures []error
for _, row := range i.pending {
err := i.coreInstall(ctx, row)
_, err := i.coreInstall(ctx, row)
if code, ok := domain.CodeOf(err); errors.Is(err, errCoreTargetNotComposed) || ok && code == domain.NotFound {
continue
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const (
conformanceAlex = "00000000-0000-7000-8000-0000000000b3"
conformanceIvy = "00000000-0000-7000-8000-0000000000b9"
conformanceZoe = "00000000-0000-7000-8000-0000000000b5"
conformanceMaya = "00000000-0000-7000-8000-0000000000b1"
)

// TestAccountsPassesTheHostInterfaceConformanceSuite boots accounts over a
Expand Down Expand Up @@ -887,6 +888,41 @@ func runHostInterfaceFixture(t *testing.T) {
require.NoError(t, err)
finance := teamNamed(t, ctx, svc, fixtureOrganization, "Finance")
require.Contains(t, grants, domain.RoleGrant{InstallationID: inst.ID, Role: "viewer", TeamID: finance}, "the declared team holds its role")

// accounts' own record of the installation follows the core's: active,
// with no install step acknowledged, and exposed to the declared team.
available, _, err := svc.ListAvailableSolutions(ctx, fixtureOrganization, "", 500)
require.NoError(t, err)
var target string
for _, entry := range available {
if entry.RouteAlias == "conformance-late" {
target = entry.TargetID
}
}
require.NotEmpty(t, target)
rows, _, err := svc.ListOrganizationInstallations(ctx, fixtureOrganization, "", 100)
require.NoError(t, err)
var native *gen.Installation
for _, row := range rows {
if row.Summary.GetInstallation().GetTargetId() == target {
native = row.Summary.GetInstallation()
}
}
require.NotNil(t, native, "accounts holds its own record of the installation")
require.Equal(t, gen.InstallationStatus_INSTALLATION_STATUS_ACTIVE, native.GetStatus(), "accounts' record follows the core's")
entitled, err := svc.ListSolutionEntitlements(ctx, &gen.ListSolutionEntitlementsRequest{OrgId: fixtureOrganization, SubjectId: conformanceMaya})
require.NoError(t, err)
var exposed bool
for _, entitlement := range entitled.GetEntitlements() {
exposed = exposed || entitlement.GetInstallationId() == native.GetId()
}
require.True(t, exposed, "the declared team is exposed to it")

// A person on the team is issued a capability through the solution.
issued, err := authorityv1.NewAuthorityServiceClient(conn).Issue(signIn("fixture-maya"), &authorityv1.IssueRequest{
As: &authorityv1.IssueRequest_Person{Person: &authorityv1.PersonSession{}}, Audience: "conformance-late"})
require.NoError(t, err, "a member of the declared team holds the solution's role")
require.NotEmpty(t, issued.GetContext().GetAuthorityScopes())
})

// Last, because the envelope it writes becomes every module's ceiling: a
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package business

import (
"context"
"fmt"
)

// installationActivator activates accounts' record of an installation the
// authority core holds active.
type installationActivator interface {
ActivateInstallationFollowingCore(ctx context.Context, orgID, installationID string) (bool, error)
}

// FollowCoreInstallation makes accounts' record of a solution installation
// follow the authority core's, which the host interfaces decide with: once
// the core's is active (consented and composed), accounts' record of the same
// installation, pending its install step or failed at it, is active too, with
// the root scope node it was installed with, so its teams can be exposed.
// One source of truth: accounts' record never activates on its own here; it
// is called with the core's installation active. An installation already
// active, or revoked, is left as it is. The activation is the core's, which
// its journal records (saas.authority.installation_activated); this records
// no second event for it.
func (s *Service) FollowCoreInstallation(ctx context.Context, orgID, installationID string) error {
store, ok := s.store.(installationActivator)
if !ok {
return fmt.Errorf("the installation store cannot follow the authority core")
}
return s.store.WithOrgTx(ctx, orgID, func(ctx context.Context) error {
_, err := store.ActivateInstallationFollowingCore(ctx, orgID, installationID)
return err
})
}
18 changes: 18 additions & 0 deletions module/services/accounts/code/pkg/infra/postgres_installations.go
Original file line number Diff line number Diff line change
Expand Up @@ -1156,3 +1156,21 @@ func (s *PostgresStore) InstallationRoleRevision(ctx context.Context, org, id st
}
return roleID, role, revision, nil
}

// ActivateInstallationFollowingCore activates accounts' record of an
// installation whose authority core installation is active: pending its
// install step, or failed at it, it becomes active, keeping the root scope
// node it was installed with. It reports whether it changed the row; an
// active or revoked installation is left as it is. The installation-step
// projection (record_installation_step) only ever moves a pending row, so a
// later acknowledgement changes nothing.
func (s *PostgresStore) ActivateInstallationFollowingCore(ctx context.Context, orgID, installationID string) (bool, error) {
tag, err := s.getQueryExecutor(ctx).Exec(ctx, `
UPDATE installations SET status = 'active', failure_reason = NULL
WHERE org_id = $1 AND id::text = $2 AND status IN ('pending', 'failed')`,
orgID, installationID)
if err != nil {
return false, fmt.Errorf("activate installation %s following the authority core: %w", installationID, err)
}
return tag.RowsAffected() == 1, nil
}
Loading