Version v1.0.5

SDK

This guide explains how to implement a Plakar Integration (Source, Destination, or Storage) in Go, and how to use the provided SDK to run it.

📌 What is a Plakar Plugin?

A Plakar Plugin is an external binary that implements one of the Plakar interfaces:

  • Storage: Provides the implementation of a storage for backups
  • Importer: Provides an implementation to import from a source into plakar
  • Exporter: Provides an implementation to export from plakar to a destination

✅ Step 1 — Implement the Interface

Each plugin must implement the right interface from the Plakar source tree.


Store Interface

type Store interface {
	Create(ctx context.Context, config []byte) error
	Open(ctx context.Context) ([]byte, error)
	Location() string
	Mode() Mode
	Size() int64

	GetStates() ([]objects.MAC, error)
	PutState(mac objects.MAC, rd io.Reader) (int64, error)
	GetState(mac objects.MAC) (io.Reader, error)
	DeleteState(mac objects.MAC) error

	GetPackfiles() ([]objects.MAC, error)
	PutPackfile(mac objects.MAC, rd io.Reader) (int64, error)
	GetPackfile(mac objects.MAC) (io.Reader, error)
	GetPackfileBlob(mac objects.MAC, offset uint64, length uint32) (io.Reader, error)
	DeletePackfile(mac objects.MAC) error

	GetLocks() ([]objects.MAC, error)
	PutLock(lockID objects.MAC, rd io.Reader) (int64, error)
	GetLock(lockID objects.MAC) (io.Reader, error)
	DeleteLock(lockID objects.MAC) error

	Close() error
}

By implementing the Store interface, an integration can provide access to a new data source for fetching and creating backups.

Create method creates a new storage backend with the provided configuration.

Open method opens an existing storage backend and returns its configuration.

Location method returns the storage location (e.g., file path, URL).

Mode method returns the storage mode (e.g., read, write).

Size method returns the size of the storage backend.

For managing states, packfiles, and locks, the following methods are defined:

  • GetXs: Retrieves all Xs from the storage.
  • PutX: Stores a X in the storage, returning the size of the stored data.
  • GetX: Retrieves a specific X from the storage.
  • DeleteX: Deletes a specific X from the storage.

GetPackfileBlob: Retrieves a specific blob from a packfile, given an offset and length.

Close method is called to clean up resources after the storage operation is complete.


Importer Interface

type Importer interface {
	Origin() string
	Type() string
	Root() string
	Scan() (<-chan *ScanResult, error)
	Close() error
}

type ScanResult struct {
	Record *ScanRecord
	Error  *ScanError
}

type ScanRecord struct {
	Reader io.ReadCloser
	Pathname           string
	Target             string
	FileInfo           objects.FileInfo
	ExtendedAttributes []string
	FileAttributes     uint32
	IsXattr            bool
	XattrName          string
	XattrType          objects.Attribute
}

type ScanError struct {
	Pathname string
	Err      error
}

By implementing the Importer interface, an integration can provide storage of backups on any backend that supports listing, storing and fetching byte streams.

Interface: plakarkorp/kloset/snapshot/importer/importer.go

Scan method returns a channel of *ScanResult, which contains the results of the scan operation.

If the ScanResult contains a Record, it means the scan was successful for that file. Record includes the file’s metadata and a reader to access its content (Reader).

If it contains an Error, it indicates a problem encountered during the scan.

Origin method returns the host or source of the data being imported.

Type method returns the name of the importer type (e.g., “fs”, “s3”, “notion”).

Root method returns the root directory of the data being imported.

close method returns called to clean up resources after the import operation is complete.


type Exporter interface {
	Root() string
	CreateDirectory(pathname string) error
	StoreFile(pathname string, fp io.Reader, size int64) error
	SetPermissions(pathname string, fileinfo *objects.FileInfo) error
	Close() error
}

Exporter Interface

Path: plakarkorp/kloset/snapshot/exporter/exporter.go

Root method returns the root directory where files will be stored.

CreateDirectory method creates a directory at the specified pathname.

StoreFile method stores a file at the specified pathname, reading from the provided io.Reader.

SetPermissions method sets the file permissions based on the provided FileInfo.

Close method is called to clean up resources after the export operation is complete.

🛠️ Step 2 — Write Your Implementation

Example: Implementing an Exporter:

package myexporter

import (
	"io"
	"os"
	"path/filepath"

	"github.com/PlakarKorp/kloset/objects"
)

struct MyExporter struct {
    rootDir string
}

func NewMyExporter(ctx context.Context, opts *exporter.Options, name string, config map[string]string) (exporter.Exporter, error) {
	return &MyExporter{
		rootDir: config["location"], // Location where files will be restored
	}, nil
}

type MyExporter struct {
	root string
}

func (l *MyExporter) Root() string {
	return l.root
}

func (l *MyExporter) CreateDirectory(pathname string) error {
	return os.MkdirAll(filepath.Join(l.root, pathname), 0755)
}

func (l *MyExporter) StoreFile(pathname string, fp io.Reader, size int64) error {
	f, err := os.Create(filepath.Join(l.root, pathname))
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = io.CopyN(f, fp, size)
	return err
}

func (l *MyExporter) SetPermissions(pathname string, fileinfo *objects.FileInfo) error {
	return os.Chmod(filepath.Join(l.root, pathname), fileinfo.Mode())
}

func (l *MyExporter) Close() error {
	return nil
}

🚀 Step 3 — Make a main function

For the plugin to be executable, you need a main function that uses the SDK to run your plugin.

package main

import (
	"context"
	
	"github.com/PlakarKorp/go-kloset-sdk"

    // Import your implementation package based on the plugin type
	"myexporter"
    // or
    "myimporter"
    // or
    "mystorage"
)

func main() {
    // Use the correct SDK function based on your plugin type
    // For Exporter: sdk.RunExporter()
    // For Importer: sdk.RunImporter()
    // For Storage: sdk.RunStorage()

    // Example for Exporter
	if err := sdk.RunExporter(myexporter.NewMyExporter); err != nil {
		panic(err)
	}
}

🧪 Step 4 — Create a Makefile

To being integrate your plugin in the Plakar system, you need to create a Makefile that builds your plugin binary. Add the following rules in your Makefile but only the ones that you have implemented (Exporter, Importer, and/or Storage):

all: exporter importer storage

importer:
    go build -o myimporter -v ./importer

exporter:
    go build -o myexporter -v ./exporter

storage:
    go build -o mystorage -v ./storage

🏗️ Step 5 — Add a Manifest.yaml

To integrate your plugin with Plakar, you need to create a Manifest.yaml file in the root of your plugin directory. This file describes your plugin and its capabilities.

name: my-plugin-name
description: A brief description of your plugin
version: 1.0.0
connectors:
  - type: importer
    executable: myimporter
    protocols: [my-plugin]
 - type: exporter
    executable: myexporter
    protocols: [my-plugin]
  - type: storage
    executable: mystorage
    protocols: [my-plugin]

The protocols field specifies the protocols your plugin supports (e.g., notion, fs, s3). It will be used by Plakar cli to determine which plugins to use for specific operations.

🏁 Step 6 — Create the plugin pkg

To create the plugin package, you need to run the following command in the root of your plugin directory:

Your Manifest.yaml file should be in the same directory as your Makefile.

./plakar pkg create <path-to-your-manifest.yaml>

From this line, an ptar file will be created in the current directory.

📦 Step 7 — Install the ptar file

To install your plugin, you can use the Plakar CLI:

plakar pkg install <path-to-your-plugin.ptar>

You can check if your plugin is well installed by running:

plakar version

If your plugin is installed correctly, you should see it listed in the output.

Now you can use your plugin with Plakar commands, such as a classical plakar connector, if you don’t how to use it, you can check the Plakar documentation for more information on how to use connectors.