The controller plans config, templates and moved providers' provisions, hands bus accounts to the vault and judges the new manifest words, on the feature's SDK and node-engine validator (feature 57, config and provisioning).
126 lines
6.3 KiB
Go
126 lines
6.3 KiB
Go
package catalogue
|
||
|
||
import (
|
||
"reflect"
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// gitea's server.env, as the catalogue keeps it: its database as the provider told it and the consumer's own login.
|
||
const giteaServerEnv = "GITEA__security__INTERNAL_TOKEN=${secret:internal-token}\n" +
|
||
"GITEA__database__DB_TYPE=postgres\n" +
|
||
"GITEA__database__HOST=${bound:postgres-database:at}:${bound:postgres-database:port}\n" +
|
||
"GITEA__database__NAME=${bound:postgres-database:as}\n" +
|
||
"GITEA__database__USER=${bound:postgres-database:as}\n" +
|
||
"GITEA__database__PASSWD=${secret:postgres-database}\n"
|
||
|
||
// databaseConsumer is a handed module, with the template gitea's server.env is, requiring a database from a provider
|
||
// that has moved or not (issue 788); slug is the module's identity source when it declares one.
|
||
func databaseConsumer(t *testing.T, slug string, moved bool) Resolution {
|
||
t.Helper()
|
||
raw := strings.Replace(botMoved(), `"own-secrets":{"token":"${dir:state}/token"}`,
|
||
`"requires":["postgres-database"],"own-secrets":{"internal-token":"${dir:state}/internal-token"}`, 1)
|
||
if slug != "" {
|
||
raw = strings.Replace(raw, `"version":"1",`, `"version":"1","slug":"`+slug+`",`, 1)
|
||
}
|
||
return withDatabase(t, raw, giteaServerEnv, moved)
|
||
}
|
||
|
||
// withDatabase resolves the module in raw, whose template is text, against a provider of postgres-database that has
|
||
// moved or not.
|
||
func withDatabase(t *testing.T, raw, text string, moved bool) Resolution {
|
||
t.Helper()
|
||
r, err := Resolve(shelf(held(t, raw, builtBot(text)),
|
||
Manifest{Module: "postgres", Version: "1", Provides: []Offer{{Name: "postgres-database", Scope: ScopeNode}}}),
|
||
[]string{"bot"}, workstation(), World{})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
r.Account = "op"
|
||
// Answered on this machine, it states no need; one from the provider's machine is stated, as the plan does.
|
||
r.Needs = []Needed{{Name: "postgres-database", For: "bot", Module: "postgres", From: "novox", At: "10.7.0.1",
|
||
CredentialForm: CredentialFormPair, IssuedToHolder: moved, ProviderMoved: moved}}
|
||
return r
|
||
}
|
||
|
||
// The forge's server.env composes for a handed module when what it requires has moved, its plan entry carrying the
|
||
// provider's address, machine and the consumer's identity as today's binding file gives them; and is refused with the
|
||
// text it has always had when the provider has not.
|
||
func TestAHandedModuleMayNameWhatItRequiresWhereItsProviderHasMoved(t *testing.T) {
|
||
c, got := composeWith(t, databaseConsumer(t, "", true), handingTo("store:5000"))
|
||
if strings.Contains(got, "sealed") {
|
||
t.Fatalf("a handed module's declaration carries a sealed value: %s", got)
|
||
}
|
||
want := []PlanProvision{{Name: "postgres-database", Provision: "postgres-database", Provider: "novox",
|
||
ProviderModule: "postgres", CredentialForm: CredentialFormPair, At: "10.7.0.1", From: "novox",
|
||
As: ConsumerIdentity("workstation", "bot")}}
|
||
if !reflect.DeepEqual(c.Plans["bot"].Provisions, want) {
|
||
t.Fatalf("the plan names %+v, want %+v", c.Plans["bot"].Provisions, want)
|
||
}
|
||
for _, unmoved := range []Resolution{databaseConsumer(t, "", false)} {
|
||
_, err := unmoved.Compose(handingTo("store:5000"))
|
||
if err == nil || !strings.Contains(err.Error(), "which is no secret of its own; a credential it requires moves "+
|
||
"with its provider (design 57 B1–B3)") {
|
||
t.Fatalf("a provider not moved, the credential: %v", err)
|
||
}
|
||
bound := strings.Replace(giteaServerEnv, "${secret:postgres-database}", "x", 1)
|
||
r := withDatabase(t, strings.Replace(botMoved(), `"own-secrets":{"token":"${dir:state}/token"}`,
|
||
`"requires":["postgres-database"],"own-secrets":{"internal-token":"${dir:state}/internal-token"}`, 1),
|
||
bound, false)
|
||
_, err = r.Compose(handingTo("store:5000"))
|
||
if err == nil || !strings.Contains(err.Error(), "the provisioner's client fills only config, secrets, "+
|
||
"collections and placement until that domain moves (design 57)") {
|
||
t.Fatalf("a provider not moved, the binding: %v", err)
|
||
}
|
||
}
|
||
}
|
||
|
||
// A module with a slug is told who it is as its binding file says, the slug first (issue 788).
|
||
func TestAHandedModulesPlanEntryCarriesItsIdentityAsTheBindingFileGivesIt(t *testing.T) {
|
||
c, _ := composeWith(t, databaseConsumer(t, "agent", true), handingTo("store:5000"))
|
||
if got := c.Plans["bot"].Provisions[0].As; got != ConsumerIdentity("workstation", "agent") {
|
||
t.Fatalf("the plan entry says %q", got)
|
||
}
|
||
// Nothing known of where the provider is: the entry says none.
|
||
c, _ = composeWith(t, forgeReader(t, true, true), handingTo("store:5000"))
|
||
if p := c.Plans["bot"].Provisions[0]; p.At != "" || p.From != "novox" {
|
||
t.Fatalf("the entry says at %q from %q", p.At, p.From)
|
||
}
|
||
}
|
||
|
||
// A handed module's credentials the vault keeps as one login per local name (issue 793): gitea's internal token and
|
||
// admin password, which the `secret` provision hands as `${secret:<local>}`. Each is a plan entry naming its local,
|
||
// its file declared placed and without a value; the template naming the local is allowed.
|
||
func TestAHandedModulesLocalCredentialsOfTheVaultAreNamedByLocalInItsPlan(t *testing.T) {
|
||
raw := strings.Replace(botMoved(), `"own-secrets":{"token":"${dir:state}/token"}`,
|
||
`"requires":["secret"],"secrets":{"secret":{"internal-token":"${dir:state}/internal-token.secret",`+
|
||
`"admin":"${dir:state}/admin.secret"}},"own-secrets":{"token":"${dir:state}/token"}`, 1)
|
||
r, err := Resolve(shelf(held(t, raw, builtBot("T=${secret:internal-token}\n")),
|
||
Manifest{Module: "vault", Version: "1", Provides: []Offer{{Name: "secret", Scope: ScopeNode}}}),
|
||
[]string{"bot"}, workstation(), World{})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
r.Account = "op"
|
||
r.Needs = []Needed{
|
||
{Name: "secret", Local: "internal-token", For: "bot", Module: "vault", From: "novox",
|
||
CredentialForm: CredentialFormPair, IssuedToHolder: true, ProviderMoved: true},
|
||
{Name: "secret", Local: "admin", For: "bot", Module: "vault", From: "novox",
|
||
CredentialForm: CredentialFormPair, IssuedToHolder: true, ProviderMoved: true},
|
||
}
|
||
c, got := composeWith(t, r, handingTo("store:5000"))
|
||
if strings.Contains(got, "sealed") {
|
||
t.Fatalf("the declaration carries a sealed value: %s", got)
|
||
}
|
||
var names []string
|
||
for _, p := range c.Plans["bot"].Provisions {
|
||
if p.Provision != "secret" || p.Provider != "novox" || p.Path == "" || p.Name != p.Local {
|
||
t.Errorf("the plan entry is %+v", p)
|
||
}
|
||
names = append(names, p.Name)
|
||
}
|
||
if !reflect.DeepEqual(names, []string{"admin", "internal-token"}) {
|
||
t.Fatalf("the plan names %v, want admin and internal-token", names)
|
||
}
|
||
}
|