forked from project-stacker/stacker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
publisher.go
216 lines (183 loc) · 5.57 KB
/
publisher.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package stacker
import (
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/opencontainers/umoci"
"github.com/opencontainers/umoci/oci/casext"
"github.com/pkg/errors"
"github.com/project-stacker/stacker/lib"
"github.com/project-stacker/stacker/log"
"github.com/project-stacker/stacker/types"
)
type PublishArgs struct {
Config types.StackerConfig
ShowOnly bool
Substitute []string
Tags []string
Url string
Username string
Password string
Force bool
Progress bool
SkipTLS bool
LayerTypes []types.LayerType
}
// Publisher is responsible for publishing the layers based on stackerfiles
type Publisher struct {
stackerfiles types.StackerFiles // Keep track of all the Stackerfiles to publish
opts *PublishArgs // Publish options
}
// NewPublisher initializes a new Publisher struct
func NewPublisher(opts *PublishArgs) *Publisher {
return &Publisher{
stackerfiles: make(map[string]*types.Stackerfile, 1),
opts: opts,
}
}
// Publish layers in a single stackerfile
func (p *Publisher) Publish(file string) error {
opts := p.opts
// Use absolute path to identify the file in stackerfile map
absPath, err := filepath.Abs(file)
if err != nil {
return err
}
sf, ok := p.stackerfiles[absPath]
if !ok {
return errors.Errorf("could not find entry for %s(%s) in stackerfiles", absPath, file)
}
var oci casext.Engine
oci, err = umoci.OpenLayout(opts.Config.OCIDir)
if err != nil {
return err
}
defer oci.Close()
buildCache, err := OpenCache(opts.Config, oci, p.stackerfiles)
if err != nil {
return err
}
// Determine list of tags to be used
tags := make([]string, len(opts.Tags))
copy(tags, opts.Tags)
if len(tags) == 0 {
return errors.Errorf("can't save OCI images in %s since list of tags is empty\n", file)
}
// Need to determine if URL is docker/oci or something else
is, err := types.NewImageSource(opts.Url)
if err != nil {
return err
}
// Iterate through all layers defined in this stackerfile
for _, name := range sf.FileOrder {
// Verify layer is not build only
l, ok := sf.Get(name)
if !ok {
return errors.Errorf("layer cannot be found in stackerfile: %s", name)
}
if l.BuildOnly {
log.Infof("will not publish: %s build_only %s", file, name)
continue
}
// Verify layer is in build cache
_, ok, err = buildCache.Lookup(name)
if err != nil {
return err
}
if !ok && !opts.Force {
return errors.Errorf("layer needs to be rebuilt before publishing: %s", name)
}
// Iterate through all tags
for _, tag := range tags {
for _, layerType := range opts.LayerTypes {
layerTypeTag := layerType.LayerName(tag)
layerName := layerType.LayerName(name)
// Determine full destination URL
var destUrl string
switch is.Type {
case types.DockerLayer:
destUrl = fmt.Sprintf("%s/%s:%s", strings.TrimRight(opts.Url, "/"), name, layerTypeTag)
case types.OCILayer:
destUrl = fmt.Sprintf("%s:%s_%s", opts.Url, name, layerTypeTag)
default:
return errors.Errorf("can't save layers to destination type: %s", is.Type)
}
if opts.ShowOnly {
// User has requested only to see what would be published
log.Infof("would publish: %s %s to %s", file, name, destUrl)
continue
}
var progressWriter io.Writer
if p.opts.Progress {
progressWriter = os.Stderr
}
// Store the layers to new destination
log.Infof("publishing %s %s to %s\n", file, layerName, destUrl)
err = lib.ImageCopy(lib.ImageCopyOpts{
Src: fmt.Sprintf("oci:%s:%s", opts.Config.OCIDir, layerName),
Dest: destUrl,
DestUsername: opts.Username,
DestPassword: opts.Password,
Progress: progressWriter,
SrcSkipTLS: true,
DestSkipTLS: opts.SkipTLS,
})
if err != nil {
return err
}
}
}
}
return nil
}
// PublishMultiple published layers defined in a list of stackerfiles
func (p *Publisher) PublishMultiple(paths []string) error {
// Verify the OCI layout exists
if _, err := os.Stat(p.opts.Config.OCIDir); err != nil {
return err
}
// Read stackerfiles and update substitutions
sfm, err := p.readStackerFiles(paths)
if err != nil {
return err
}
p.stackerfiles = sfm
// Publish all Stackerfiles
for _, path := range paths {
err := p.Publish(path)
if err != nil {
return err
}
}
return nil
}
// readStackerFiles reads stacker recipes and applies substitutions
// it has a hack for determining if a value is not substituted
// if it should be substituted but is is not, substitute it with 'dummy'
func (p *Publisher) readStackerFiles(paths []string) (types.StackerFiles, error) {
// Read all the stacker recipes
sfm, err := types.NewStackerFiles(paths, false, append(p.opts.Substitute, p.opts.Config.Substitutions()...))
if err != nil {
// Verify if the error is related to an invalid substitution
re := regexp.MustCompile(`no value for substitution (.*)`)
matches := re.FindAllStringSubmatch(err.Error(), -1)
// If the error is not related to an invalid substitution, report it
if len(matches) == 0 {
return nil, err
}
// If the error is related to an invalid substitution,
// determine the missing variable and add it to the variable to substitute
if len(matches[0]) < 2 {
// For some strange reason the first capturing group has not caught anything
return nil, err
}
// Add the value dummy to the missing substitute variables
p.opts.Substitute = append(p.opts.Substitute, fmt.Sprintf("%s=dummy", matches[0][1]))
// Try again, this time with the new substitute variables
return p.readStackerFiles(paths)
}
return sfm, nil
}