d76645680f
Bump containers/image (pulling in its new dependency on ostree-go), containers/storage, and updated image-spec. This pulls in the OCI v1.0 specifications and code that allows us to support 1.0 images. Signed-off-by: Dan Walsh <dwalsh@redhat.com> Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
482 lines
16 KiB
Go
482 lines
16 KiB
Go
package otbuiltin
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
"unsafe"
|
|
|
|
glib "github.com/ostreedev/ostree-go/pkg/glibobject"
|
|
)
|
|
|
|
// #cgo pkg-config: ostree-1
|
|
// #include <stdlib.h>
|
|
// #include <glib.h>
|
|
// #include <ostree.h>
|
|
// #include "builtin.go.h"
|
|
import "C"
|
|
|
|
// Declare global variable to store commitOptions
|
|
var options commitOptions
|
|
|
|
// Declare a function prototype for being passed into another function
|
|
type handleLineFunc func(string, *glib.GHashTable) error
|
|
|
|
// Contains all of the options for commmiting to an ostree repo. Initialize
|
|
// with NewCommitOptions()
|
|
type commitOptions struct {
|
|
Subject string // One line subject
|
|
Body string // Full description
|
|
Parent string // Parent of the commit
|
|
Tree []string // 'dir=PATH' or 'tar=TARFILE' or 'ref=COMMIT': overlay the given argument as a tree
|
|
AddMetadataString []string // Add a key/value pair to metadata
|
|
AddDetachedMetadataString []string // Add a key/value pair to detached metadata
|
|
OwnerUID int // Set file ownership to user id
|
|
OwnerGID int // Set file ownership to group id
|
|
NoXattrs bool // Do not import extended attributes
|
|
LinkCheckoutSpeedup bool // Optimize for commits of trees composed of hardlinks in the repository
|
|
TarAutoCreateParents bool // When loading tar archives, automatically create parent directories as needed
|
|
SkipIfUnchanged bool // If the contents are unchanged from a previous commit, do nothing
|
|
StatOverrideFile string // File containing list of modifications to make permissions
|
|
SkipListFile string // File containing list of file paths to skip
|
|
GenerateSizes bool // Generate size information along with commit metadata
|
|
GpgSign []string // GPG Key ID with which to sign the commit (if you have GPGME - GNU Privacy Guard Made Easy)
|
|
GpgHomedir string // GPG home directory to use when looking for keyrings (if you have GPGME - GNU Privacy Guard Made Easy)
|
|
Timestamp time.Time // Override the timestamp of the commit
|
|
Orphan bool // Commit does not belong to a branch
|
|
Fsync bool // Specify whether fsync should be used or not. Default to true
|
|
}
|
|
|
|
// Initializes a commitOptions struct and sets default values
|
|
func NewCommitOptions() commitOptions {
|
|
var co commitOptions
|
|
co.OwnerUID = -1
|
|
co.OwnerGID = -1
|
|
co.Fsync = true
|
|
return co
|
|
}
|
|
|
|
type OstreeRepoTransactionStats struct {
|
|
metadata_objects_total int32
|
|
metadata_objects_written int32
|
|
content_objects_total int32
|
|
content_objects_written int32
|
|
content_bytes_written uint64
|
|
}
|
|
|
|
func (repo *Repo) PrepareTransaction() (bool, error) {
|
|
var cerr *C.GError = nil
|
|
var resume C.gboolean
|
|
|
|
r := glib.GoBool(glib.GBoolean(C.ostree_repo_prepare_transaction(repo.native(), &resume, nil, &cerr)))
|
|
if !r {
|
|
return false, generateError(cerr)
|
|
}
|
|
return glib.GoBool(glib.GBoolean(resume)), nil
|
|
}
|
|
|
|
func (repo *Repo) CommitTransaction() (*OstreeRepoTransactionStats, error) {
|
|
var cerr *C.GError = nil
|
|
var stats OstreeRepoTransactionStats = OstreeRepoTransactionStats{}
|
|
statsPtr := (*C.OstreeRepoTransactionStats)(unsafe.Pointer(&stats))
|
|
r := glib.GoBool(glib.GBoolean(C.ostree_repo_commit_transaction(repo.native(), statsPtr, nil, &cerr)))
|
|
if !r {
|
|
return nil, generateError(cerr)
|
|
}
|
|
return &stats, nil
|
|
}
|
|
|
|
func (repo *Repo) TransactionSetRef(remote string, ref string, checksum string) {
|
|
var cRemote *C.char = nil
|
|
var cRef *C.char = nil
|
|
var cChecksum *C.char = nil
|
|
|
|
if remote != "" {
|
|
cRemote = C.CString(remote)
|
|
}
|
|
if ref != "" {
|
|
cRef = C.CString(ref)
|
|
}
|
|
if checksum != "" {
|
|
cChecksum = C.CString(checksum)
|
|
}
|
|
C.ostree_repo_transaction_set_ref(repo.native(), cRemote, cRef, cChecksum)
|
|
}
|
|
|
|
func (repo *Repo) AbortTransaction() error {
|
|
var cerr *C.GError = nil
|
|
r := glib.GoBool(glib.GBoolean(C.ostree_repo_abort_transaction(repo.native(), nil, &cerr)))
|
|
if !r {
|
|
return generateError(cerr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (repo *Repo) RegenerateSummary() error {
|
|
var cerr *C.GError = nil
|
|
r := glib.GoBool(glib.GBoolean(C.ostree_repo_regenerate_summary(repo.native(), nil, nil, &cerr)))
|
|
if !r {
|
|
return generateError(cerr)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Commits a directory, specified by commitPath, to an ostree repo as a given branch
|
|
func (repo *Repo) Commit(commitPath, branch string, opts commitOptions) (string, error) {
|
|
options = opts
|
|
|
|
var err error
|
|
var modeAdds *glib.GHashTable
|
|
var skipList *glib.GHashTable
|
|
var objectToCommit *glib.GFile
|
|
var skipCommit bool = false
|
|
var ccommitChecksum *C.char
|
|
defer C.free(unsafe.Pointer(ccommitChecksum))
|
|
var flags C.OstreeRepoCommitModifierFlags = 0
|
|
var filter_data C.CommitFilterData
|
|
|
|
var cerr *C.GError
|
|
defer C.free(unsafe.Pointer(cerr))
|
|
var metadata *C.GVariant = nil
|
|
defer func(){
|
|
if metadata != nil {
|
|
defer C.g_variant_unref(metadata)
|
|
}
|
|
}()
|
|
|
|
var detachedMetadata *C.GVariant = nil
|
|
defer C.free(unsafe.Pointer(detachedMetadata))
|
|
var mtree *C.OstreeMutableTree
|
|
defer C.free(unsafe.Pointer(mtree))
|
|
var root *C.GFile
|
|
defer C.free(unsafe.Pointer(root))
|
|
var modifier *C.OstreeRepoCommitModifier
|
|
defer C.free(unsafe.Pointer(modifier))
|
|
var cancellable *C.GCancellable
|
|
defer C.free(unsafe.Pointer(cancellable))
|
|
|
|
cpath := C.CString(commitPath)
|
|
defer C.free(unsafe.Pointer(cpath))
|
|
csubject := C.CString(options.Subject)
|
|
defer C.free(unsafe.Pointer(csubject))
|
|
cbody := C.CString(options.Body)
|
|
defer C.free(unsafe.Pointer(cbody))
|
|
cbranch := C.CString(branch)
|
|
defer C.free(unsafe.Pointer(cbranch))
|
|
cparent := C.CString(options.Parent)
|
|
defer C.free(unsafe.Pointer(cparent))
|
|
|
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_is_writable(repo.native(), &cerr))) {
|
|
goto out
|
|
}
|
|
|
|
// If the user provided a stat override file
|
|
if strings.Compare(options.StatOverrideFile, "") != 0 {
|
|
modeAdds = glib.ToGHashTable(unsafe.Pointer(C._g_hash_table_new_full()))
|
|
if err = parseFileByLine(options.StatOverrideFile, handleStatOverrideLine, modeAdds, cancellable); err != nil {
|
|
goto out
|
|
}
|
|
}
|
|
|
|
// If the user provided a skiplist file
|
|
if strings.Compare(options.SkipListFile, "") != 0 {
|
|
skipList = glib.ToGHashTable(unsafe.Pointer(C._g_hash_table_new_full()))
|
|
if err = parseFileByLine(options.SkipListFile, handleSkipListline, skipList, cancellable); err != nil {
|
|
goto out
|
|
}
|
|
}
|
|
|
|
if options.AddMetadataString != nil {
|
|
metadata, err = parseKeyValueStrings(options.AddMetadataString)
|
|
if err != nil {
|
|
goto out
|
|
}
|
|
}
|
|
|
|
if options.AddDetachedMetadataString != nil {
|
|
_, err := parseKeyValueStrings(options.AddDetachedMetadataString)
|
|
if err != nil {
|
|
goto out
|
|
}
|
|
}
|
|
|
|
if strings.Compare(branch, "") == 0 && !options.Orphan {
|
|
err = errors.New("A branch must be specified or use commitOptions.Orphan")
|
|
goto out
|
|
}
|
|
|
|
if options.NoXattrs {
|
|
C._ostree_repo_append_modifier_flags(&flags, C.OSTREE_REPO_COMMIT_MODIFIER_FLAGS_SKIP_XATTRS)
|
|
}
|
|
if options.GenerateSizes {
|
|
C._ostree_repo_append_modifier_flags(&flags, C.OSTREE_REPO_COMMIT_MODIFIER_FLAGS_GENERATE_SIZES)
|
|
}
|
|
if !options.Fsync {
|
|
C.ostree_repo_set_disable_fsync(repo.native(), C.TRUE)
|
|
}
|
|
|
|
if flags != 0 || options.OwnerUID >= 0 || options.OwnerGID >= 0 || strings.Compare(options.StatOverrideFile, "") != 0 || options.NoXattrs {
|
|
filter_data.mode_adds = (*C.GHashTable)(modeAdds.Ptr())
|
|
filter_data.skip_list = (*C.GHashTable)(skipList.Ptr())
|
|
C._set_owner_uid((C.guint32)(options.OwnerUID))
|
|
C._set_owner_gid((C.guint32)(options.OwnerGID))
|
|
modifier = C._ostree_repo_commit_modifier_new_wrapper(flags, C.gpointer(&filter_data), nil)
|
|
}
|
|
|
|
if strings.Compare(options.Parent, "") != 0 {
|
|
if strings.Compare(options.Parent, "none") == 0 {
|
|
options.Parent = ""
|
|
}
|
|
} else if !options.Orphan {
|
|
cerr = nil
|
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_resolve_rev(repo.native(), cbranch, C.TRUE, &cparent, &cerr))) {
|
|
goto out
|
|
}
|
|
}
|
|
|
|
if options.LinkCheckoutSpeedup && !glib.GoBool(glib.GBoolean(C.ostree_repo_scan_hardlinks(repo.native(), cancellable, &cerr))) {
|
|
goto out
|
|
}
|
|
|
|
mtree = C.ostree_mutable_tree_new()
|
|
|
|
if len(commitPath) == 0 && (len(options.Tree) == 0 || len(options.Tree[0]) == 0) {
|
|
currentDir := (*C.char)(C.g_get_current_dir())
|
|
objectToCommit = glib.ToGFile(unsafe.Pointer(C.g_file_new_for_path(currentDir)))
|
|
C.g_free(C.gpointer(currentDir))
|
|
|
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_write_directory_to_mtree(repo.native(), (*C.GFile)(objectToCommit.Ptr()), mtree, modifier, cancellable, &cerr))) {
|
|
goto out
|
|
}
|
|
} else if len(options.Tree) != 0 {
|
|
var eq int = -1
|
|
cerr = nil
|
|
for tree := range options.Tree {
|
|
eq = strings.Index(options.Tree[tree], "=")
|
|
if eq == -1 {
|
|
C._g_set_error_onearg(cerr, C.CString("Missing type in tree specification"), C.CString(options.Tree[tree]))
|
|
goto out
|
|
}
|
|
treeType := options.Tree[tree][:eq]
|
|
treeVal := options.Tree[tree][eq+1:]
|
|
|
|
if strings.Compare(treeType, "dir") == 0 {
|
|
objectToCommit = glib.ToGFile(unsafe.Pointer(C.g_file_new_for_path(C.CString(treeVal))))
|
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_write_directory_to_mtree(repo.native(), (*C.GFile)(objectToCommit.Ptr()), mtree, modifier, cancellable, &cerr))) {
|
|
goto out
|
|
}
|
|
} else if strings.Compare(treeType, "tar") == 0 {
|
|
objectToCommit = glib.ToGFile(unsafe.Pointer(C.g_file_new_for_path(C.CString(treeVal))))
|
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_write_archive_to_mtree(repo.native(), (*C.GFile)(objectToCommit.Ptr()), mtree, modifier, (C.gboolean)(glib.GBool(opts.TarAutoCreateParents)), cancellable, &cerr))) {
|
|
fmt.Println("error 1")
|
|
goto out
|
|
}
|
|
} else if strings.Compare(treeType, "ref") == 0 {
|
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_read_commit(repo.native(), C.CString(treeVal), (**C.GFile)(objectToCommit.Ptr()), nil, cancellable, &cerr))) {
|
|
goto out
|
|
}
|
|
|
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_write_directory_to_mtree(repo.native(), (*C.GFile)(objectToCommit.Ptr()), mtree, modifier, cancellable, &cerr))) {
|
|
goto out
|
|
}
|
|
} else {
|
|
C._g_set_error_onearg(cerr, C.CString("Missing type in tree specification"), C.CString(treeVal))
|
|
goto out
|
|
}
|
|
}
|
|
} else {
|
|
objectToCommit = glib.ToGFile(unsafe.Pointer(C.g_file_new_for_path(cpath)))
|
|
cerr = nil
|
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_write_directory_to_mtree(repo.native(), (*C.GFile)(objectToCommit.Ptr()), mtree, modifier, cancellable, &cerr))) {
|
|
goto out
|
|
}
|
|
}
|
|
|
|
if modeAdds != nil && C.g_hash_table_size((*C.GHashTable)(modeAdds.Ptr())) > 0 {
|
|
var hashIter *C.GHashTableIter
|
|
|
|
var key, value C.gpointer
|
|
|
|
C.g_hash_table_iter_init(hashIter, (*C.GHashTable)(modeAdds.Ptr()))
|
|
|
|
for glib.GoBool(glib.GBoolean(C.g_hash_table_iter_next(hashIter, &key, &value))) {
|
|
C._g_printerr_onearg(C.CString("Unmatched StatOverride path: "), C._gptr_to_str(key))
|
|
}
|
|
err = errors.New("Unmatched StatOverride paths")
|
|
C.free(unsafe.Pointer(hashIter))
|
|
C.free(unsafe.Pointer(key))
|
|
C.free(unsafe.Pointer(value))
|
|
goto out
|
|
}
|
|
|
|
if skipList != nil && C.g_hash_table_size((*C.GHashTable)(skipList.Ptr())) > 0 {
|
|
var hashIter *C.GHashTableIter
|
|
var key, value C.gpointer
|
|
|
|
C.g_hash_table_iter_init(hashIter, (*C.GHashTable)(skipList.Ptr()))
|
|
|
|
for glib.GoBool(glib.GBoolean(C.g_hash_table_iter_next(hashIter, &key, &value))) {
|
|
C._g_printerr_onearg(C.CString("Unmatched SkipList path: "), C._gptr_to_str(key))
|
|
}
|
|
err = errors.New("Unmatched SkipList paths")
|
|
C.free(unsafe.Pointer(hashIter))
|
|
C.free(unsafe.Pointer(key))
|
|
C.free(unsafe.Pointer(value))
|
|
goto out
|
|
}
|
|
|
|
cerr = nil
|
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_write_mtree(repo.native(), mtree, &root, cancellable, &cerr))) {
|
|
goto out
|
|
}
|
|
|
|
if options.SkipIfUnchanged && strings.Compare(options.Parent, "") != 0 {
|
|
var parentRoot *C.GFile
|
|
|
|
cerr = nil
|
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_read_commit(repo.native(), cparent, &parentRoot, nil, cancellable, &cerr))) {
|
|
C.free(unsafe.Pointer(parentRoot))
|
|
goto out
|
|
}
|
|
|
|
if glib.GoBool(glib.GBoolean(C.g_file_equal(root, parentRoot))) {
|
|
skipCommit = true
|
|
}
|
|
C.free(unsafe.Pointer(parentRoot))
|
|
}
|
|
|
|
if !skipCommit {
|
|
var timestamp C.guint64
|
|
|
|
if options.Timestamp.IsZero() {
|
|
var now *C.GDateTime = C.g_date_time_new_now_utc()
|
|
timestamp = (C.guint64)(C.g_date_time_to_unix(now))
|
|
C.g_date_time_unref(now)
|
|
|
|
cerr = nil
|
|
ret := C.ostree_repo_write_commit(repo.native(), cparent, csubject, cbody, metadata, C._ostree_repo_file(root), &ccommitChecksum, cancellable, &cerr)
|
|
if !glib.GoBool(glib.GBoolean(ret)) {
|
|
goto out
|
|
}
|
|
} else {
|
|
timestamp = (C.guint64)(options.Timestamp.Unix())
|
|
|
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_write_commit_with_time(repo.native(), cparent, csubject, cbody,
|
|
metadata, C._ostree_repo_file(root), timestamp, &ccommitChecksum, cancellable, &cerr))) {
|
|
goto out
|
|
}
|
|
}
|
|
|
|
if detachedMetadata != nil {
|
|
C.ostree_repo_write_commit_detached_metadata(repo.native(), ccommitChecksum, detachedMetadata, cancellable, &cerr)
|
|
}
|
|
|
|
if len(options.GpgSign) != 0 {
|
|
for key := range options.GpgSign {
|
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_sign_commit(repo.native(), (*C.gchar)(ccommitChecksum), (*C.gchar)(C.CString(options.GpgSign[key])), (*C.gchar)(C.CString(options.GpgHomedir)), cancellable, &cerr))) {
|
|
goto out
|
|
}
|
|
}
|
|
}
|
|
|
|
if strings.Compare(branch, "") != 0 {
|
|
C.ostree_repo_transaction_set_ref(repo.native(), nil, cbranch, ccommitChecksum)
|
|
} else if !options.Orphan {
|
|
goto out
|
|
} else {
|
|
// TODO: Looks like I forgot to implement this.
|
|
}
|
|
} else {
|
|
ccommitChecksum = C.CString(options.Parent)
|
|
}
|
|
|
|
return C.GoString(ccommitChecksum), nil
|
|
out:
|
|
if repo.native() != nil {
|
|
C.ostree_repo_abort_transaction(repo.native(), cancellable, nil)
|
|
//C.free(unsafe.Pointer(repo.native()))
|
|
}
|
|
if modifier != nil {
|
|
C.ostree_repo_commit_modifier_unref(modifier)
|
|
}
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return "", generateError(cerr)
|
|
}
|
|
|
|
// Parse an array of key value pairs of the format KEY=VALUE and add them to a GVariant
|
|
func parseKeyValueStrings(pairs []string) (*C.GVariant, error) {
|
|
builder := C.g_variant_builder_new(C._g_variant_type(C.CString("a{sv}")))
|
|
defer C.g_variant_builder_unref(builder)
|
|
|
|
for iter := range pairs {
|
|
index := strings.Index(pairs[iter], "=")
|
|
if index <= 0 {
|
|
var buffer bytes.Buffer
|
|
buffer.WriteString("Missing '=' in KEY=VALUE metadata '%s'")
|
|
buffer.WriteString(pairs[iter])
|
|
return nil, errors.New(buffer.String())
|
|
}
|
|
|
|
key := C.CString(pairs[iter][:index])
|
|
value := C.CString(pairs[iter][index+1:])
|
|
|
|
valueVariant := C.g_variant_new_string((*C.gchar)(value))
|
|
|
|
C._g_variant_builder_add_twoargs(builder, C.CString("{sv}"), key, valueVariant)
|
|
}
|
|
|
|
metadata := C.g_variant_builder_end(builder)
|
|
return C.g_variant_ref_sink(metadata), nil
|
|
}
|
|
|
|
// Parse a file linue by line and handle the line with the handleLineFunc
|
|
func parseFileByLine(path string, fn handleLineFunc, table *glib.GHashTable, cancellable *C.GCancellable) error {
|
|
var contents *C.char
|
|
var file *glib.GFile
|
|
var lines []string
|
|
var gerr = glib.NewGError()
|
|
cerr := (*C.GError)(gerr.Ptr())
|
|
|
|
file = glib.ToGFile(unsafe.Pointer(C.g_file_new_for_path(C.CString(path))))
|
|
if !glib.GoBool(glib.GBoolean(C.g_file_load_contents((*C.GFile)(file.Ptr()), cancellable, &contents, nil, nil, &cerr))) {
|
|
return generateError(cerr)
|
|
}
|
|
|
|
lines = strings.Split(C.GoString(contents), "\n")
|
|
for line := range lines {
|
|
if strings.Compare(lines[line], "") == 0 {
|
|
continue
|
|
}
|
|
|
|
if err := fn(lines[line], table); err != nil {
|
|
return generateError(cerr)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Handle an individual line from a Statoverride file
|
|
func handleStatOverrideLine(line string, table *glib.GHashTable) error {
|
|
var space int
|
|
var modeAdd C.guint
|
|
|
|
if space = strings.IndexRune(line, ' '); space == -1 {
|
|
return errors.New("Malformed StatOverrideFile (no space found)")
|
|
}
|
|
|
|
modeAdd = (C.guint)(C.g_ascii_strtod((*C.gchar)(C.CString(line)), nil))
|
|
C.g_hash_table_insert((*C.GHashTable)(table.Ptr()), C.gpointer(C.g_strdup((*C.gchar)(C.CString(line[space+1:])))), C._guint_to_pointer(modeAdd))
|
|
|
|
return nil
|
|
}
|
|
|
|
// Handle an individual line from a Skiplist file
|
|
func handleSkipListline(line string, table *glib.GHashTable) error {
|
|
C.g_hash_table_add((*C.GHashTable)(table.Ptr()), C.gpointer( C.g_strdup((*C.gchar)(C.CString(line)))))
|
|
|
|
return nil
|
|
}
|