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
5 changes: 5 additions & 0 deletions docs/package/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ container's rootfs. The file should be named `urunc.json`, it should be
placed in the root directory of the container's rootfs and it should have a JSON
format with the above information, where the values are base64 encoded.

When the required annotations are missing or incomplete, `urunc` logs the
missing fields and then tries to load the configuration from `urunc.json`.
This can help identify runtime setups where image annotations are not reaching
the OCI runtime.

## Tools to construct OCI images with `urunc`'s annotations

As previously mentioned we currently provide 2 different tools to build and
Expand Down
25 changes: 21 additions & 4 deletions pkg/unikontainers/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"io"
"os"
"path/filepath"
"strings"

"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -62,16 +63,25 @@ type UnikernelConfig struct {

// validate checks if the mandatory configuration fields are present.
func (c *UnikernelConfig) validate() error {
missingFields := c.missingMandatoryFields()
if len(missingFields) > 0 {
return fmt.Errorf("unikernel configuration is missing mandatory field(s): %s", strings.Join(missingFields, ", "))
}
return nil
}

func (c *UnikernelConfig) missingMandatoryFields() []string {
var missingFields []string
if c.UnikernelType == "" {
return fmt.Errorf("unikernel configuration is missing mandatory field: %s", annotType)
missingFields = append(missingFields, annotType)
}
if c.Hypervisor == "" {
return fmt.Errorf("unikernel configuration is missing mandatory field: %s", annotHypervisor)
missingFields = append(missingFields, annotHypervisor)
}
if c.UnikernelBinary == "" {
return fmt.Errorf("unikernel configuration is missing mandatory field: %s", annotBinary)
missingFields = append(missingFields, annotBinary)
}
return nil
return missingFields
}

// GetUnikernelConfig tries to get the Unikernel config from the bundle annotations.
Expand All @@ -90,8 +100,14 @@ func GetUnikernelConfig(bundleDir string, spec *specs.Spec) (*UnikernelConfig, e
if err := conf.decode(); err != nil {
return nil, err
}
uniklog.WithField("source", "spec").Debug("using urunc config from OCI annotations")
return conf, nil
}
uniklog.WithError(err).WithFields(logrus.Fields{
"source": "spec",
"fallback": uruncJSONFilename,
"missing_fields": conf.missingMandatoryFields(),
}).Debug("urunc annotations are incomplete, trying fallback config")

rootFSDir := spec.Root.Path
var jsonFilePath string
Expand All @@ -113,6 +129,7 @@ func GetUnikernelConfig(bundleDir string, spec *specs.Spec) (*UnikernelConfig, e
if err := jsonConf.decode(); err != nil {
return nil, err
}
uniklog.WithField("source", uruncJSONFilename).Debug("using urunc config from fallback file")
return jsonConf, nil
}

Expand Down
24 changes: 24 additions & 0 deletions pkg/unikontainers/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ func TestGetConfigFromSpec(t *testing.T) {
err := config.validate()
assert.Error(t, err, "Expected validation to fail for an empty config")
assert.ErrorContains(t, err, annotType, "Expected error to mention missing type field")
assert.ErrorContains(t, err, annotHypervisor, "Expected error to mention missing hypervisor field")
assert.ErrorContains(t, err, annotBinary, "Expected error to mention missing binary field")
})

t.Run("get config from spec with partial (invalid) annotations", func(t *testing.T) {
Expand All @@ -90,6 +92,28 @@ func TestGetConfigFromSpec(t *testing.T) {
})
}

func TestMissingMandatoryFields(t *testing.T) {
t.Run("all required fields present", func(t *testing.T) {
t.Parallel()
config := &UnikernelConfig{
UnikernelType: "type1",
Hypervisor: "hypervisor1",
UnikernelBinary: "binary1",
}

assert.Empty(t, config.missingMandatoryFields())
})

t.Run("partial required fields", func(t *testing.T) {
t.Parallel()
config := &UnikernelConfig{
UnikernelType: "type1",
}

assert.Equal(t, []string{annotHypervisor, annotBinary}, config.missingMandatoryFields())
})
}

func TestGetConfigFromJSON(t *testing.T) {
t.Run("get config from json success", func(t *testing.T) {
t.Parallel()
Expand Down