From b7834664690dc7b11a630236872abf06c67e8beb Mon Sep 17 00:00:00 2001 From: Frederik Lotter Date: Wed, 23 Oct 2024 12:28:04 +0200 Subject: [PATCH] feat(plan): add plan sub-directory support (#509) Pebble currently only supports loading configuration layer files from the root of $PEBBLE/layers. Add support for adding sub-directory support at the root level (2 levels in total only). [Spec KO071.](https://docs.google.com/document/d/1-9GLCJTx9o_hSd0JGvDp7o5xNQjkJMvLam6o1Ba_bYM/edit?tab=t.0) This scheme has an impact on the meaning of ```label``` and ```order```. See the following examples to understand the new mapping: - $PEBBLE/layers/001-foo.yaml Order: 001-000 => 1000 Label: foo - $PEBBLE/layers/005-bar.d/010-abc.yaml Order: 005-010 => 5010 Label: bar/abc - $PEBBLE/layers/005-bar.d/012-def.yaml Order: 005-012 => 5012 Label: bar/def - $PEBBLE/layers/006-baz.yaml Order: 006-000 => 6000 Label: baz Directory support allows other uses of Pebble using read-only file systems more flexibility by now giving the option to bind-mount a sub-directory to a writable disk location, for example. --- client/plan.go | 15 ++ client/plan_test.go | 23 ++- docs/reference/cli-commands/add.md | 2 + internals/cli/cmd_add.go | 3 + internals/cli/cmd_add_test.go | 1 + internals/daemon/api_exec_test.go | 4 +- internals/daemon/api_plan.go | 5 +- internals/overlord/planstate/manager.go | 94 ++++++++-- internals/overlord/planstate/manager_test.go | 128 ++++++++++--- internals/overlord/planstate/package_test.go | 2 +- internals/overlord/servstate/manager_test.go | 2 +- internals/plan/extensions_test.go | 18 +- internals/plan/plan.go | 182 ++++++++++++++++--- internals/plan/plan_test.go | 152 ++++++++++++++++ 14 files changed, 543 insertions(+), 88 deletions(-) diff --git a/client/plan.go b/client/plan.go index 25b447f08..9c34f58e7 100644 --- a/client/plan.go +++ b/client/plan.go @@ -25,6 +25,11 @@ type AddLayerOptions struct { // has the given label. False (the default) means append a new layer. Combine bool + // Inner set to true means a new layer append may go into an existing + // subdirectory, even though it may not result in appending it + // to the end of the layers slice (it becomes an insert). + Inner bool + // Label is the label for the new layer if appending, and the label of the // layer to combine with if Combine is true. Label string @@ -38,16 +43,26 @@ func (client *Client) AddLayer(opts *AddLayerOptions) error { var payload = struct { Action string `json:"action"` Combine bool `json:"combine"` + Inner bool `json:"inner"` Label string `json:"label"` Format string `json:"format"` Layer string `json:"layer"` }{ Action: "add", Combine: opts.Combine, + Inner: opts.Inner, Label: opts.Label, Format: "yaml", Layer: string(opts.LayerData), } + + // Add label validation here once layer persistence is supported over + // the API. We cannot do this in the plan library because JUJU already + // has labels in production systems that violates the layers file + // naming convention (which includes the label). Since JUJU uses its + // own client, we can enforce the label naming convention on all other + // systems using the Pebble supplied client by validating it here. + var body bytes.Buffer if err := json.NewEncoder(&body).Encode(&payload); err != nil { return err diff --git a/client/plan_test.go b/client/plan_test.go index 385d6e202..38fd081a4 100644 --- a/client/plan_test.go +++ b/client/plan_test.go @@ -24,7 +24,22 @@ import ( ) func (cs *clientSuite) TestAddLayer(c *check.C) { - for _, combine := range []bool{false, true} { + for _, option := range []struct { + combine bool + inner bool + }{{ + combine: false, + inner: false, + }, { + combine: true, + inner: false, + }, { + combine: false, + inner: true, + }, { + combine: true, + inner: true, + }} { cs.rsp = `{ "type": "sync", "status-code": 200, @@ -37,7 +52,8 @@ services: command: cmd `[1:] err := cs.cli.AddLayer(&client.AddLayerOptions{ - Combine: combine, + Combine: option.combine, + Inner: option.inner, Label: "foo", LayerData: []byte(layerYAML), }) @@ -49,10 +65,11 @@ services: c.Assert(json.NewDecoder(cs.req.Body).Decode(&body), check.IsNil) c.Assert(body, check.DeepEquals, map[string]interface{}{ "action": "add", - "combine": combine, + "combine": option.combine, "label": "foo", "format": "yaml", "layer": layerYAML, + "inner": option.inner, }) } } diff --git a/docs/reference/cli-commands/add.md b/docs/reference/cli-commands/add.md index 5ef741108..9e2299799 100644 --- a/docs/reference/cli-commands/add.md +++ b/docs/reference/cli-commands/add.md @@ -19,5 +19,7 @@ label (or append if the label is not found). [add command options] --combine Combine the new layer with an existing layer that has the given label (default is to append) + --inner Allow appending a new layer inside an existing + subdirectory ``` diff --git a/internals/cli/cmd_add.go b/internals/cli/cmd_add.go index 1d8e841cd..247a4d6c8 100644 --- a/internals/cli/cmd_add.go +++ b/internals/cli/cmd_add.go @@ -35,6 +35,7 @@ type cmdAdd struct { client *client.Client Combine bool `long:"combine"` + Inner bool `long:"inner"` Positional struct { Label string `positional-arg-name:"