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
1type Store interface {
2 Create(ctx context.Context, config []byte) error
3 Open(ctx context.Context) ([]byte, error)
4 Location() string
5 Mode() Mode
6 Size() int64
7
8 GetStates() ([]objects.MAC, error)
9 PutState(mac objects.MAC, rd io.Reader) (int64, error)
10 GetState(mac objects.MAC) (io.Reader, error)
11 DeleteState(mac objects.MAC) error
12
13 GetPackfiles() ([]objects.MAC, error)
14 PutPackfile(mac objects.MAC, rd io.Reader) (int64, error)
15 GetPackfile(mac objects.MAC) (io.Reader, error)
16 GetPackfileBlob(mac objects.MAC, offset uint64, length uint32) (io.Reader, error)
17 DeletePackfile(mac objects.MAC) error
18
19 GetLocks() ([]objects.MAC, error)
20 PutLock(lockID objects.MAC, rd io.Reader) (int64, error)
21 GetLock(lockID objects.MAC) (io.Reader, error)
22 DeleteLock(lockID objects.MAC) error
23
24 Close() error
25}
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
1type Importer interface {
2 Origin() string
3 Type() string
4 Root() string
5 Scan() (<-chan *ScanResult, error)
6 Close() error
7}
8
9type ScanResult struct {
10 Record *ScanRecord
11 Error *ScanError
12}
13
14type ScanRecord struct {
15 Reader io.ReadCloser
16 Pathname string
17 Target string
18 FileInfo objects.FileInfo
19 ExtendedAttributes []string
20 FileAttributes uint32
21 IsXattr bool
22 XattrName string
23 XattrType objects.Attribute
24}
25
26type ScanError struct {
27 Pathname string
28 Err error
29}
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.
1type Exporter interface {
2 Root() string
3 CreateDirectory(pathname string) error
4 StoreFile(pathname string, fp io.Reader, size int64) error
5 SetPermissions(pathname string, fileinfo *objects.FileInfo) error
6 Close() error
7}
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:
1package myexporter
2
3import (
4 "io"
5 "os"
6 "path/filepath"
7
8 "github.com/PlakarKorp/kloset/objects"
9)
10
11struct MyExporter struct {
12 rootDir string
13}
14
15func NewMyExporter(ctx context.Context, opts *exporter.Options, name string, config map[string]string) (exporter.Exporter, error) {
16 return &MyExporter{
17 rootDir: config["location"], // Location where files will be restored
18 }, nil
19}
20
21type MyExporter struct {
22 root string
23}
24
25func (l *MyExporter) Root() string {
26 return l.root
27}
28
29func (l *MyExporter) CreateDirectory(pathname string) error {
30 return os.MkdirAll(filepath.Join(l.root, pathname), 0755)
31}
32
33func (l *MyExporter) StoreFile(pathname string, fp io.Reader, size int64) error {
34 f, err := os.Create(filepath.Join(l.root, pathname))
35 if err != nil {
36 return err
37 }
38 defer f.Close()
39
40 _, err = io.CopyN(f, fp, size)
41 return err
42}
43
44func (l *MyExporter) SetPermissions(pathname string, fileinfo *objects.FileInfo) error {
45 return os.Chmod(filepath.Join(l.root, pathname), fileinfo.Mode())
46}
47
48func (l *MyExporter) Close() error {
49 return nil
50}
🚀 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.
1package main
2
3import (
4 "context"
5
6 "github.com/PlakarKorp/go-kloset-sdk"
7
8 // Import your implementation package based on the plugin type
9 "myexporter"
10 // or
11 "myimporter"
12 // or
13 "mystorage"
14)
15
16func main() {
17 // Use the correct SDK function based on your plugin type
18 // For Exporter: sdk.RunExporter()
19 // For Importer: sdk.RunImporter()
20 // For Storage: sdk.RunStorage()
21
22 // Example for Exporter
23 if err := sdk.RunExporter(myexporter.NewMyExporter); err != nil {
24 panic(err)
25 }
26}
🧪 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):
1all: exporter importer storage
2
3importer:
4 go build -o myimporter -v ./importer
5
6exporter:
7 go build -o myexporter -v ./exporter
8
9storage:
10 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.
1name: my-plugin-name
2description: A brief description of your plugin
3version: 1.0.0
4connectors:
5 - type: importer
6 executable: myimporter
7 protocols: [my-plugin]
8 - type: exporter
9 executable: myexporter
10 protocols: [my-plugin]
11 - type: storage
12 executable: mystorage
13 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.
1./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:
1plakar pkg install <path-to-your-plugin.ptar>
You can check if your plugin is well installed by running:
1plakar 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.