/
push.go
131 lines (111 loc) · 4.25 KB
/
push.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Copyright (c) 2020, Control Command Inc. All rights reserved.
// Copyright (c) 2019-2023, Sylabs Inc. All rights reserved.
// This software is licensed under a 3-clause BSD license. Please consult the
// LICENSE.md file distributed with the sources of this project regarding your
// rights to use or distribute this software.
package library
import (
"context"
"errors"
"fmt"
"os"
"time"
scslibrary "github.com/sylabs/scs-library-client/client"
"github.com/sylabs/sif/v2/pkg/sif"
"github.com/sylabs/singularity/v4/internal/pkg/client/ocisif"
"github.com/sylabs/singularity/v4/internal/pkg/client/progress"
"github.com/sylabs/singularity/v4/internal/pkg/remote/endpoint"
"github.com/sylabs/singularity/v4/pkg/sylog"
"golang.org/x/term"
)
// PushOptions provides options/configuration that determine the behavior of a
// push to the library.
type PushOptions struct {
// Description sets the optional description for an image pushed via the
// library's own API.
Description string
// Endpoint is the active remote endpoint, against which the OCI registry
// backing the library can be discovered.
Endpoint *endpoint.Config
// LibraryConfig configures operations against the library using its native
// API, via sylabs/scs-library-client.
LibraryConfig *scslibrary.Config
}
// Push will upload an image file to the library.
// Returns the upload completion response on success, if available, containing
// container path and quota usage for v1 libraries.
func Push(ctx context.Context, sourceFile string, destRef *scslibrary.Ref, opts PushOptions) (uploadResponse *scslibrary.UploadImageComplete, err error) {
f, err := sif.LoadContainerFromPath(sourceFile, sif.OptLoadWithFlag(os.O_RDONLY))
if err != nil {
return nil, fmt.Errorf("unable to open: %v: %w", sourceFile, err)
}
defer f.UnloadContainer()
if _, err := f.GetDescriptor(sif.WithDataType(sif.DataOCIRootIndex)); err == nil {
return nil, pushOCI(ctx, sourceFile, destRef, opts)
}
return pushNative(ctx, sourceFile, destRef, opts)
}
// pushNative pushes a non-OCI SIF image, as a SIF, using the library client.
func pushNative(ctx context.Context, sourceFile string, destRef *scslibrary.Ref, opts PushOptions) (uploadResponse *scslibrary.UploadImageComplete, err error) {
arch, err := sifArch(sourceFile)
if err != nil {
return nil, err
}
libraryClient, err := scslibrary.NewClient(opts.LibraryConfig)
if err != nil {
return nil, fmt.Errorf("error initializing library client: %v", err)
}
if destRef.Host != "" && destRef.Host != libraryClient.BaseURL.Host {
return nil, errors.New("push to location other than current remote is not supported")
}
// open image for uploading
f, err := os.Open(sourceFile)
if err != nil {
return nil, fmt.Errorf("error opening image %s for reading: %v", sourceFile, err)
}
defer f.Close()
// Get file size by seeking to end and back
fSize, err := f.Seek(0, 2)
if err != nil {
return nil, err
}
if _, err := f.Seek(0, 0); err != nil {
return nil, err
}
var progressBar scslibrary.UploadCallback
if term.IsTerminal(2) {
progressBar = &progress.UploadBar{}
}
defer func(t time.Time) {
if err == nil && progressBar == nil {
sylog.Infof("Uploaded %d bytes in %v\n", fSize, time.Since(t))
}
}(time.Now())
return libraryClient.UploadImage(ctx, f, destRef.Path, arch, destRef.Tags, opts.Description, progressBar)
}
func sifArch(filename string) (string, error) {
f, err := sif.LoadContainerFromPath(filename, sif.OptLoadWithFlag(os.O_RDONLY))
if err != nil {
return "", fmt.Errorf("unable to open: %v: %w", filename, err)
}
defer f.UnloadContainer()
arch := f.PrimaryArch()
if arch == "unknown" {
return arch, fmt.Errorf("unknown architecture in SIF file")
}
return arch, nil
}
// pushOCI pushes an OCI SIF image, as an OCI image, using the ocisif client.
func pushOCI(ctx context.Context, sourceFile string, destRef *scslibrary.Ref, opts PushOptions) error {
sylog.Infof("Pushing an OCI-SIF to the library OCI registry. Use `--oci` to pull this image.")
lr, err := newLibraryRegistry(opts.Endpoint, opts.LibraryConfig)
if err != nil {
return err
}
pushRef, err := lr.convertRef(*destRef)
if err != nil {
return err
}
sylog.Debugf("Pushing to OCI registry at: %s", pushRef)
return ocisif.PushOCISIF(ctx, sourceFile, pushRef, lr.authConfig(), "")
}