-
Notifications
You must be signed in to change notification settings - Fork 39
/
unpackage.go
181 lines (148 loc) · 4.78 KB
/
unpackage.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
// Copyright 2020 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"fmt"
"os"
"github.com/cppforlife/go-cli-ui/ui"
regname "github.com/google/go-containerregistry/pkg/name"
ctlconf "github.com/k14s/kbld/pkg/kbld/config"
ctllog "github.com/k14s/kbld/pkg/kbld/logger"
ctlreg "github.com/k14s/kbld/pkg/kbld/registry"
ctlres "github.com/k14s/kbld/pkg/kbld/resources"
ctlser "github.com/k14s/kbld/pkg/kbld/search"
"github.com/k14s/kbld/pkg/kbld/version"
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
)
type UnpackageOptions struct {
ui ui.UI
FileFlags FileFlags
RegistryFlags RegistryFlags
InputPath string
Repository string
LockOutput string
Concurrency int
}
func NewUnpackageOptions(ui ui.UI) *UnpackageOptions {
return &UnpackageOptions{ui: ui}
}
func NewUnpackageCmd(o *UnpackageOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "unpackage",
Aliases: []string{"unpkg"},
Short: "Unpackage configuration and images from tarball",
RunE: func(_ *cobra.Command, _ []string) error { return o.Run() },
}
o.FileFlags.Set(cmd)
o.RegistryFlags.Set(cmd)
cmd.Flags().StringVarP(&o.InputPath, "input", "i", "", "Input tarball path")
cmd.Flags().StringVarP(&o.Repository, "repository", "r", "", "Import images into given image repository (e.g. docker.io/dkalinin/my-project)")
cmd.Flags().StringVar(&o.LockOutput, "lock-output", "", "File path to emit configuration with resolved image references")
cmd.Flags().IntVar(&o.Concurrency, "concurrency", 5, "Set maximum number of concurrent imports")
return cmd
}
func (o *UnpackageOptions) Run() error {
if len(o.InputPath) == 0 {
return fmt.Errorf("Expected 'input' flag to be non-empty")
}
if len(o.Repository) == 0 {
return fmt.Errorf("Expected 'repository' flag to be non-empty")
}
logger := ctllog.NewLogger(os.Stderr)
prefixedLogger := logger.NewPrefixedWriter("unpackage | ")
nonConfigRs, conf, err := o.FileFlags.ResourcesAndConfig()
if err != nil {
return err
}
importRepo, err := regname.NewRepository(o.Repository)
if err != nil {
return fmt.Errorf("Building import repository ref: %s", err)
}
registry, err := ctlreg.NewRegistry(o.RegistryFlags.AsRegistryOpts())
if err != nil {
return err
}
imageSet := TarImageSet{ImageSet{o.Concurrency, prefixedLogger}, o.Concurrency, prefixedLogger}
// Import images used in the manifests
importedImages, err := imageSet.Import(o.InputPath, importRepo, registry)
if err != nil {
return err
}
err = o.emitLockOutput(conf, importedImages)
if err != nil {
return err
}
// Update previous image references with new references
resBss, err := o.updateRefsInResources(nonConfigRs, conf, importedImages)
if err != nil {
return err
}
// Print all resources as one YAML stream
for _, resBs := range resBss {
resBs = append([]byte("---\n"), resBs...)
o.ui.PrintBlock(resBs)
}
return nil
}
func (o *UnpackageOptions) updateRefsInResources(
nonConfigRs []ctlres.Resource, conf ctlconf.Conf,
resolvedImages *ProcessedImages) ([][]byte, error) {
var missingImageErrs []error
var resBss [][]byte
for _, res := range nonConfigRs {
resContents := res.DeepCopyRaw()
imageRefs := ctlser.NewImageRefs(resContents, conf.SearchRules())
imageRefs.Visit(func(imgURL string) (string, bool) {
outputImg, found := resolvedImages.FindByURL(UnprocessedImageURL{imgURL})
if found {
return outputImg.URL, true
}
missingImageErrs = append(missingImageErrs, fmt.Errorf("Expected to find image for '%s'", imgURL))
return "", false
})
resBs, err := yaml.Marshal(resContents)
if err != nil {
return nil, err
}
resBss = append(resBss, resBs)
}
err := errFromErrs(missingImageErrs)
if err != nil {
return nil, err
}
return resBss, nil
}
func (o *UnpackageOptions) emitLockOutput(conf ctlconf.Conf, resolvedImages *ProcessedImages) error {
if len(o.LockOutput) == 0 {
return nil
}
c := ctlconf.NewConfig()
c.MinimumRequiredVersion = version.Version
c.SearchRules = conf.SearchRulesWithoutDefaults()
for _, override := range conf.ImageOverrides() {
if override.Preresolved {
img, found := resolvedImages.FindByURL(UnprocessedImageURL{override.NewImage})
if !found {
return fmt.Errorf("Expected to find imported image for '%s'", override.NewImage)
}
c.Overrides = append(c.Overrides, ctlconf.ImageOverride{
ImageRef: override.ImageRef,
NewImage: img.URL,
Preresolved: true,
})
}
}
// TODO should we dedup overrides?
for _, urlImagePair := range resolvedImages.All() {
c.Overrides = append(c.Overrides, ctlconf.ImageOverride{
ImageRef: ctlconf.ImageRef{
Image: urlImagePair.UnprocessedImageURL.URL,
},
NewImage: urlImagePair.Image.URL,
Preresolved: true,
})
}
c.Overrides = ctlconf.UniqueImageOverrides(c.Overrides)
return c.WriteToFile(o.LockOutput)
}