Skip to content

Commit

Permalink
add initial nri code
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Crosby <michael@thepasture.io>
  • Loading branch information
crosbymichael committed Jul 28, 2020
1 parent cd8655f commit 49220b1
Show file tree
Hide file tree
Showing 3 changed files with 281 additions and 0 deletions.
135 changes: 135 additions & 0 deletions client.go
@@ -0,0 +1,135 @@
package nri

import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"os/exec"

"github.com/containerd/containerd"
"github.com/containerd/containerd/oci"
"github.com/containerd/nri/types"
"github.com/pkg/errors"
)

const (
// DefaultBinaryPath for nri plugins
DefaultBinaryPath = "/opt/nri/bin"
// DefaultConfPath for the global nri configuration
DefaultConfPath = "/etc/nri/conf.json"
)

// New nri client
func New() (*Client, error) {
conf, err := loadConfig(DefaultConfPath)
if err != nil {
return nil, err
}
if err := os.Setenv("PATH", fmt.Sprintf("%s:%s", os.Getenv("PATH"), DefaultBinaryPath)); err != nil {
return nil, err
}
return &Client{
conf: conf,
}, nil
}

// Client for calling nri plugins
type Client struct {
conf *types.ConfigList
}

// Invoke the ConfList of nri plugins
func (c *Client) Invoke(ctx context.Context, task containerd.Task, state types.State) ([]*types.Result, error) {
spec, err := task.Spec(ctx)
if err != nil {
return nil, err
}
rs, err := createSpec(spec)
if err != nil {
return nil, err
}
var results []*types.Result
r := &types.Request{
Version: c.conf.Version,
ID: task.ID(),
Pid: int(task.Pid()),
State: state,
Spec: rs,
}
for _, p := range c.conf.Plugins {
r.Conf = p.Conf
result, err := c.invokePlugin(ctx, p.Type, r)
if err != nil {
return nil, err
}
results = append(results, result)
}
return results, nil
}

func createSpec(spec *oci.Spec) (*types.Spec, error) {
s := types.Spec{
Namespaces: make(map[string]string),
Annotations: spec.Annotations,
}
switch {
case spec.Linux != nil:
s.CgroupsPath = spec.Linux.CgroupsPath
data, err := json.Marshal(spec.Linux.Resources)
if err != nil {
return nil, err
}
s.Resources = json.RawMessage(data)
for _, ns := range spec.Linux.Namespaces {
s.Namespaces[string(ns.Type)] = ns.Path
}
case spec.Windows != nil:
data, err := json.Marshal(spec.Windows.Resources)
if err != nil {
return nil, err
}
s.Resources = json.RawMessage(data)
}
return &s, nil
}

func (c *Client) invokePlugin(ctx context.Context, name string, r *types.Request) (*types.Result, error) {
payload, err := json.Marshal(r)
if err != nil {
return nil, err
}
cmd := exec.CommandContext(ctx, name, "invoke")
cmd.Stdin = bytes.NewBuffer(payload)
cmd.Stderr = os.Stderr

out, err := cmd.Output()
if err != nil {
return nil, errors.Wrapf(err, "%s: %s", name, out)
}
var result types.Result
if err := json.Unmarshal(out, &result); err != nil {
return nil, err
}
return &result, nil
}

func loadConfig(path string) (*types.ConfigList, error) {
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return &types.ConfigList{
Version: "0.1",
}, nil
}
return nil, err
}
var c types.ConfigList
err = json.NewDecoder(f).Decode(&c)
f.Close()
if err != nil {
return nil, err
}
return &c, nil
}
41 changes: 41 additions & 0 deletions skel/skel.go
@@ -0,0 +1,41 @@
package skel

import (
"context"
"encoding/json"
"os"

"github.com/containerd/nri/types"
"github.com/pkg/errors"
)

// Plugin for modifications of resources
type Plugin interface {
// Type or plugin name
Type() string
// Invoke the plugin
Invoke(context.Context, *types.Request) (*types.Result, error)
}

// Run the plugin from a main() function
func Run(ctx context.Context, plugin Plugin) error {
var (
enc = json.NewEncoder(os.Stdout)
out interface{}
)
var request types.Request
if err := json.NewDecoder(os.Stdin).Decode(&request); err != nil {
return err
}
switch os.Args[1] {
case "invoke":
result, err := plugin.Invoke(ctx, &request)
if err != nil {
return err
}
out = result
default:
return errors.New("undefined arg")
}
return enc.Encode(out)
}
105 changes: 105 additions & 0 deletions types/types.go
@@ -0,0 +1,105 @@
package types

import "encoding/json"

// Plugin type and configuration
type Plugin struct {
// Type of plugin
Type string `json:"type"`
// Conf for the specific plugin
Conf json.RawMessage `json:"conf,omitempty"`
}

// ConfigList for the global configuration of NRI
//
// Normally located at /etc/nri/conf.json
type ConfigList struct {
// Verion of the list
Version string `json:"version"`
// Plugins
Plugins []*Plugin `json:"plugins"`
}

// Spec for the container being processed
type Spec struct {
// Resources struct from the OCI specification
//
// Can be WindowsResources or LinuxResources
Resources json.RawMessage `json:"resources"`
// Namespaces for the container
Namespaces map[string]string `json:"namespaces,omitempty"`
// CgroupsPath for the container
CgroupsPath string `json:"cgroupsPath,omitempty"`
// Annotations passed down to the OCI runtime specification
Annotations map[string]string `json:"annotations,omitempty"`
}

// State of the request
type State string

const (
// Create the initial resource for the container
Create State = "create"
// Delete any resources for the container
Delete State = "delete"
// Update the resources for the container
Update State = "update"
// Pause action of the container
Pause State = "pause"
// Resume action for the container
Resume State = "resume"
)

// Request for a plugin invocation
type Request struct {
// Conf specific for the plugin
Conf json.RawMessage `json:"conf"`

// Version of the plugin
Version string `json:"version"`
// State action for the request
State State `json:"state"`
// ID for the container
ID string `json:"id"`
// SandboxID for the sandbox that the request belongs to
//
// If ID and SandboxID are the same, this is a request for the sandbox
// SandboxID is empty for a non sandboxed container
SandboxID string `json:"sandboxID"`
// Pid of the container
//
// -1 if there is no pid
Pid int `json:"pid,omitempty"`
// Spec generated from the OCI runtime specification
Spec *Spec `json:"spec"`
}

// IsSandbox returns true if the request is for a sandbox
func (r *Request) IsSandbox() bool {
return r.ID == r.SandboxID
}

// NewResult returns a result from the original request
func (r *Request) NewResult() *Result {
return &Result{
ID: r.ID,
State: r.State,
Pid: r.Pid,
Version: r.Version,
CgroupsPath: r.Spec.CgroupsPath,
}
}

// Result of the plugin invocation
type Result struct {
// Version of the plugin
Version string `json:"version"`
// State of the invocation
State State `json:"state"`
// ID of the container
ID string `json:"id"`
// Pid of the container
Pid int `json:"pid"`
// CgroupsPath of the container
CgroupsPath string `json:"cgroupsPath"`
}

0 comments on commit 49220b1

Please sign in to comment.