1 package init
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "net/url"
8 "path/filepath"
9 "strings"
10
11 "github.com/AlecAivazis/survey/v2/terminal"
12
13 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
14 "github.com/devfile/library/v2/pkg/devfile/parser"
15 dfutil "github.com/devfile/library/v2/pkg/util"
16
17 "github.com/redhat-developer/odo/pkg/alizer"
18 "github.com/redhat-developer/odo/pkg/api"
19 "github.com/redhat-developer/odo/pkg/devfile"
20 "github.com/redhat-developer/odo/pkg/devfile/location"
21 "github.com/redhat-developer/odo/pkg/init/asker"
22 "github.com/redhat-developer/odo/pkg/init/backend"
23 "github.com/redhat-developer/odo/pkg/log"
24 "github.com/redhat-developer/odo/pkg/preference"
25 "github.com/redhat-developer/odo/pkg/registry"
26 "github.com/redhat-developer/odo/pkg/segment"
27 "github.com/redhat-developer/odo/pkg/testingutil/filesystem"
28 )
29
30 type InitClient struct {
31
32 flagsBackend *backend.FlagsBackend
33 interactiveBackend *backend.InteractiveBackend
34 alizerBackend *backend.AlizerBackend
35
36
37 fsys filesystem.Filesystem
38 preferenceClient preference.Client
39 registryClient registry.Client
40 }
41
42 var _ Client = (*InitClient)(nil)
43
44
45 var _initFlags = []string{
46 backend.FLAG_NAME,
47 backend.FLAG_DEVFILE,
48 backend.FLAG_DEVFILE_REGISTRY,
49 backend.FLAG_STARTER,
50 backend.FLAG_DEVFILE_PATH,
51 backend.FLAG_DEVFILE_VERSION,
52 backend.FLAG_RUN_PORT,
53 backend.FLAG_ARCHITECTURE,
54 }
55
56 func NewInitClient(fsys filesystem.Filesystem, preferenceClient preference.Client, registryClient registry.Client, alizerClient alizer.Client) *InitClient {
57
58 askerClient := asker.NewSurveyAsker()
59 return &InitClient{
60 flagsBackend: backend.NewFlagsBackend(registryClient),
61 interactiveBackend: backend.NewInteractiveBackend(askerClient, registryClient, alizerClient),
62 alizerBackend: backend.NewAlizerBackend(askerClient, alizerClient),
63 fsys: fsys,
64 preferenceClient: preferenceClient,
65 registryClient: registryClient,
66 }
67 }
68
69
70
71 func (o *InitClient) GetFlags(flags map[string]string) map[string]string {
72 initFlags := map[string]string{}
73 outer:
74 for flag, value := range flags {
75 for _, f := range _initFlags {
76 if flag == f {
77 initFlags[flag] = value
78 continue outer
79 }
80 }
81 }
82 return initFlags
83 }
84
85
86 func (o *InitClient) Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error {
87 var backend backend.InitBackend
88 if len(flags) == 0 {
89 backend = o.interactiveBackend
90 } else {
91 backend = o.flagsBackend
92 }
93 return backend.Validate(flags, fs, dir)
94 }
95
96
97 func (o *InitClient) SelectDevfile(ctx context.Context, flags map[string]string, fs filesystem.Filesystem, dir string) (*api.DetectionResult, error) {
98 var backend backend.InitBackend
99
100 empty, err := location.DirIsEmpty(fs, dir)
101 if err != nil {
102 return nil, err
103 }
104 if empty && len(flags) == 0 {
105 backend = o.interactiveBackend
106 } else if len(flags) == 0 {
107 backend = o.alizerBackend
108 } else {
109 backend = o.flagsBackend
110 }
111 location, err := backend.SelectDevfile(ctx, flags, fs, dir)
112 if err != nil || location == nil {
113 if backend == o.alizerBackend {
114
115 if err != nil {
116 if errors.Is(err, context.Canceled) || errors.Is(err, terminal.InterruptErr) {
117 return nil, err
118 }
119 log.Warningf("Could not determine a Devfile based on the files in the current directory: %v", err)
120 }
121 return o.interactiveBackend.SelectDevfile(ctx, flags, fs, dir)
122 }
123 if err != nil {
124 return nil, err
125 }
126 return nil, errors.New("unable to determine the devfile location")
127 }
128
129 return location, err
130 }
131
132 func (o *InitClient) DownloadDevfile(ctx context.Context, devfileLocation *api.DetectionResult, destDir string) (string, error) {
133 destDevfile := filepath.Join(destDir, "devfile.yaml")
134 if devfileLocation.DevfilePath != "" {
135 return destDevfile, o.downloadDirect(devfileLocation.DevfilePath, destDevfile)
136 } else {
137 devfile := devfileLocation.Devfile
138 if devfileLocation.DevfileVersion != "" {
139 devfile = fmt.Sprintf("%s:%s", devfileLocation.Devfile, devfileLocation.DevfileVersion)
140 }
141 return destDevfile, o.downloadFromRegistry(ctx, devfileLocation.DevfileRegistry, devfile, destDir, devfileLocation.Architectures)
142 }
143 }
144
145
146 func (o *InitClient) downloadDirect(URL string, dest string) error {
147 parsedURL, err := url.Parse(URL)
148 if err != nil {
149 return err
150 }
151 if strings.HasPrefix(parsedURL.Scheme, "http") {
152 downloadSpinner := log.Spinnerf("Downloading devfile from %q", URL)
153 defer downloadSpinner.End(false)
154 params := dfutil.HTTPRequestParams{
155 URL: URL,
156 }
157 devfileData, err := o.registryClient.DownloadFileInMemory(params)
158 if err != nil {
159 return err
160 }
161 err = o.fsys.WriteFile(dest, devfileData, 0644)
162 if err != nil {
163 return err
164 }
165 downloadSpinner.End(true)
166 } else {
167 downloadSpinner := log.Spinnerf("Copying devfile from %q", URL)
168 defer downloadSpinner.End(false)
169 content, err := o.fsys.ReadFile(URL)
170 if err != nil {
171 return err
172 }
173 info, err := o.fsys.Stat(URL)
174 if err != nil {
175 return err
176 }
177 err = o.fsys.WriteFile(dest, content, info.Mode().Perm())
178 if err != nil {
179 return err
180 }
181 downloadSpinner.End(true)
182 }
183
184 return nil
185 }
186
187
188
189
190 func (o *InitClient) downloadFromRegistry(ctx context.Context, registryName string, devfile string, dest string, architectures []string) error {
191
192 registryOptions := segment.GetRegistryOptions(ctx)
193 registryOptions.NewIndexSchema = true
194 if len(architectures) > 0 {
195 registryOptions.Filter.Architectures = architectures
196 }
197
198 var downloadSpinner *log.Status
199 var forceRegistry bool
200 if registryName == "" {
201 downloadSpinner = log.Spinnerf("Downloading devfile %q", devfile)
202 forceRegistry = false
203 } else {
204 downloadSpinner = log.Spinnerf("Downloading devfile %q from registry %q", devfile, registryName)
205 forceRegistry = true
206 }
207 defer downloadSpinner.End(false)
208
209 registries, err := o.registryClient.GetDevfileRegistries(registryName)
210 if err != nil {
211 return err
212 }
213 for _, reg := range registries {
214 if forceRegistry && reg.Name == registryName {
215 err := o.registryClient.PullStackFromRegistry(reg.URL, devfile, dest, registryOptions)
216 if err != nil {
217 return err
218 }
219 downloadSpinner.End(true)
220 return nil
221 } else if !forceRegistry {
222 err := o.registryClient.PullStackFromRegistry(reg.URL, devfile, dest, registryOptions)
223 if err != nil {
224 continue
225 }
226 downloadSpinner.End(true)
227 return nil
228 }
229 }
230
231 return fmt.Errorf("unable to find the registry with name %q", devfile)
232 }
233
234
235 func (o *InitClient) SelectStarterProject(devfile parser.DevfileObj, flags map[string]string, isEmptyDir bool) (*v1alpha2.StarterProject, error) {
236 var backend backend.InitBackend
237
238 if isEmptyDir && len(flags) == 0 {
239 backend = o.interactiveBackend
240 } else if len(flags) == 0 {
241 backend = o.alizerBackend
242 } else {
243 backend = o.flagsBackend
244 }
245 return backend.SelectStarterProject(devfile, flags)
246 }
247
248 func (o *InitClient) DownloadStarterProject(starter *v1alpha2.StarterProject, dest string) (containsDevfile bool, err error) {
249 downloadSpinner := log.Spinnerf("Downloading starter project %q", starter.Name)
250 containsDevfile, err = o.registryClient.DownloadStarterProject(starter, "", dest, false)
251 if err != nil {
252 downloadSpinner.End(false)
253 return containsDevfile, err
254 }
255 downloadSpinner.End(true)
256 return containsDevfile, nil
257 }
258
259
260 func (o *InitClient) PersonalizeName(devfile parser.DevfileObj, flags map[string]string) (string, error) {
261 var backend backend.InitBackend
262
263 if len(flags) == 0 {
264 backend = o.interactiveBackend
265 } else {
266 backend = o.flagsBackend
267 }
268 return backend.PersonalizeName(devfile, flags)
269 }
270
271 func (o *InitClient) HandleApplicationPorts(devfileobj parser.DevfileObj, ports []int, flags map[string]string, fs filesystem.Filesystem, dir string) (parser.DevfileObj, error) {
272 var backend backend.InitBackend
273 onlyDevfile, err := location.DirContainsOnlyDevfile(fs, dir)
274 if err != nil {
275 return parser.DevfileObj{}, err
276 }
277
278
279 if len(flags) == 0 && !onlyDevfile {
280
281 backend = o.interactiveBackend
282 } else {
283 backend = o.flagsBackend
284 }
285 return backend.HandleApplicationPorts(devfileobj, ports, flags)
286 }
287
288 func (o *InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj, flags map[string]string, fs filesystem.Filesystem, dir string) (parser.DevfileObj, error) {
289 var backend backend.InitBackend
290
291
292 if len(flags) == 0 {
293 backend = o.interactiveBackend
294 } else {
295 backend = o.flagsBackend
296 }
297 return backend.PersonalizeDevfileConfig(devfileobj)
298 }
299
300 func (o *InitClient) SelectAndPersonalizeDevfile(ctx context.Context, flags map[string]string, contextDir string) (parser.DevfileObj, string, *api.DetectionResult, error) {
301 devfileLocation, err := o.SelectDevfile(ctx, flags, o.fsys, contextDir)
302 if err != nil {
303 return parser.DevfileObj{}, "", nil, err
304 }
305
306 devfilePath, err := o.DownloadDevfile(ctx, devfileLocation, contextDir)
307 if err != nil {
308 return parser.DevfileObj{}, "", nil, fmt.Errorf("unable to download devfile: %w", err)
309 }
310
311 devfileObj, err := devfile.ParseAndValidateFromFile(devfilePath, "", false)
312 if err != nil {
313 return parser.DevfileObj{}, "", nil, fmt.Errorf("unable to parse devfile: %w", err)
314 }
315
316 devfileObj, err = o.HandleApplicationPorts(devfileObj, devfileLocation.ApplicationPorts, flags, o.fsys, contextDir)
317 if err != nil {
318 return parser.DevfileObj{}, "", nil, fmt.Errorf("unable to set application ports in devfile: %w", err)
319 }
320
321 devfileObj, err = o.PersonalizeDevfileConfig(devfileObj, flags, o.fsys, contextDir)
322 if err != nil {
323 return parser.DevfileObj{}, "", nil, fmt.Errorf("failed to configure devfile: %w", err)
324 }
325 return devfileObj, devfilePath, devfileLocation, nil
326 }
327
328 func (o *InitClient) InitDevfile(ctx context.Context, flags map[string]string, contextDir string,
329 preInitHandlerFunc func(interactiveMode bool), newDevfileHandlerFunc func(newDevfileObj parser.DevfileObj) error) error {
330
331 containsDevfile, err := location.DirectoryContainsDevfile(o.fsys, contextDir)
332 if err != nil {
333 return err
334 }
335 if containsDevfile {
336 return nil
337 }
338
339 if preInitHandlerFunc != nil {
340 preInitHandlerFunc(len(flags) == 0)
341 }
342
343 devfileObj, _, _, err := o.SelectAndPersonalizeDevfile(ctx, map[string]string{}, contextDir)
344 if err != nil {
345 return err
346 }
347
348
349 name, err := o.PersonalizeName(devfileObj, map[string]string{})
350 if err != nil {
351 return fmt.Errorf("failed to update the devfile's name: %w", err)
352 }
353 metadata := devfileObj.Data.GetMetadata()
354 metadata.Name = name
355 devfileObj.Data.SetMetadata(metadata)
356
357 if newDevfileHandlerFunc != nil {
358 err = newDevfileHandlerFunc(devfileObj)
359 }
360
361 return err
362 }
363
View as plain text