1 package backend
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "strconv"
8 "strings"
9
10 "k8s.io/klog"
11
12 "github.com/redhat-developer/odo/pkg/libdevfile"
13 "github.com/redhat-developer/odo/pkg/registry"
14
15 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
16 "github.com/devfile/api/v2/pkg/devfile"
17 "github.com/devfile/library/v2/pkg/devfile/parser"
18 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
19 dfutil "github.com/devfile/library/v2/pkg/util"
20
21 "github.com/redhat-developer/odo/pkg/api"
22 "github.com/redhat-developer/odo/pkg/devfile/location"
23 "github.com/redhat-developer/odo/pkg/testingutil/filesystem"
24 )
25
26 const (
27 FLAG_NAME = "name"
28 FLAG_DEVFILE = "devfile"
29 FLAG_DEVFILE_REGISTRY = "devfile-registry"
30 FLAG_STARTER = "starter"
31 FLAG_DEVFILE_PATH = "devfile-path"
32 FLAG_DEVFILE_VERSION = "devfile-version"
33 FLAG_RUN_PORT = "run-port"
34 FLAG_ARCHITECTURE = "architecture"
35 )
36
37
38 type FlagsBackend struct {
39 registryClient registry.Client
40 }
41
42 var _ InitBackend = (*FlagsBackend)(nil)
43
44 var knownArchitectures []string = []string{
45 string(devfile.AMD64),
46 string(devfile.ARM64),
47 string(devfile.PPC64LE),
48 string(devfile.S390X),
49 }
50
51 func NewFlagsBackend(registryClient registry.Client) *FlagsBackend {
52 return &FlagsBackend{
53 registryClient: registryClient,
54 }
55 }
56
57 func (o *FlagsBackend) Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error {
58 if flags[FLAG_NAME] == "" {
59 return errors.New("missing --name parameter: please add --name <name> to specify a name for the component")
60 }
61 if flags[FLAG_DEVFILE] == "" && flags[FLAG_DEVFILE_PATH] == "" {
62 return errors.New("either --devfile or --devfile-path parameter should be specified")
63 }
64 if flags[FLAG_DEVFILE] != "" && flags[FLAG_DEVFILE_PATH] != "" {
65 return errors.New("only one of --devfile or --devfile-path parameter should be specified")
66 }
67
68 registryName := flags[FLAG_DEVFILE_REGISTRY]
69 if registryName != "" {
70 registries, err := o.registryClient.GetDevfileRegistries(registryName)
71 if err != nil {
72 return err
73 }
74 if len(registries) == 0 {
75
76 return fmt.Errorf(`Registry %q not found in the list of devfile registries.
77 Please use 'odo preference <add/remove> registry'' command to configure devfile registries or add an in-cluster registry (see https://devfile.io/docs/2.2.0/deploying-a-devfile-registry).`,
78 registryName)
79
80 }
81 for _, r := range registries {
82 isGithubRegistry, err := registry.IsGithubBasedRegistry(r.URL)
83 if err != nil {
84 return err
85 }
86 if r.Name == registryName && isGithubRegistry {
87 return ®istry.ErrGithubRegistryNotSupported{}
88 }
89 }
90 }
91
92 if flags[FLAG_DEVFILE_PATH] != "" && registryName != "" {
93 return errors.New("--devfile-registry parameter cannot be used with --devfile-path")
94 }
95
96 err := dfutil.ValidateK8sResourceName("name", flags[FLAG_NAME])
97 if err != nil {
98 return err
99 }
100
101 empty, err := location.DirIsEmpty(fs, dir)
102 if err != nil {
103 return err
104 }
105 if !empty && flags[FLAG_STARTER] != "" {
106 return errors.New("--starter parameter cannot be used when the directory is not empty")
107 }
108
109 archs, err := parseStringArrayFlagValue(flags[FLAG_ARCHITECTURE])
110 if err != nil {
111 return err
112 }
113 for _, arch := range archs {
114 if !isKnownArch(arch) {
115 return fmt.Errorf("value %q is not valid for flag --architecture. Possible values are: %s", arch, strings.Join(knownArchitectures, ", "))
116 }
117 }
118
119 return nil
120 }
121
122 func isKnownArch(arch string) bool {
123 for _, known := range knownArchitectures {
124 if known == arch {
125 return true
126 }
127 }
128 return false
129 }
130
131 func (o *FlagsBackend) SelectDevfile(ctx context.Context, flags map[string]string, _ filesystem.Filesystem, _ string) (*api.DetectionResult, error) {
132
133 archs, _ := parseStringArrayFlagValue(flags[FLAG_ARCHITECTURE])
134 return &api.DetectionResult{
135 Devfile: flags[FLAG_DEVFILE],
136 DevfileRegistry: flags[FLAG_DEVFILE_REGISTRY],
137 DevfilePath: flags[FLAG_DEVFILE_PATH],
138 DevfileVersion: flags[FLAG_DEVFILE_VERSION],
139 Architectures: archs,
140 }, nil
141 }
142
143 func (o *FlagsBackend) SelectStarterProject(devfile parser.DevfileObj, flags map[string]string) (*v1alpha2.StarterProject, error) {
144 starter := flags[FLAG_STARTER]
145 if starter == "" {
146 return nil, nil
147 }
148 projects, err := devfile.Data.GetStarterProjects(common.DevfileOptions{})
149 if err != nil {
150 return nil, err
151 }
152 var prj v1alpha2.StarterProject
153 for _, prj = range projects {
154 if prj.Name == starter {
155 return &prj, nil
156 }
157 }
158 return nil, fmt.Errorf("starter project %q not found in devfile", starter)
159 }
160
161 func (o *FlagsBackend) PersonalizeName(_ parser.DevfileObj, flags map[string]string) (string, error) {
162 if validK8sNameErr := dfutil.ValidateK8sResourceName("name", flags[FLAG_NAME]); validK8sNameErr != nil {
163 return "", validK8sNameErr
164 }
165 return flags[FLAG_NAME], nil
166
167 }
168
169 func (o FlagsBackend) PersonalizeDevfileConfig(devfileobj parser.DevfileObj) (parser.DevfileObj, error) {
170 return devfileobj, nil
171 }
172
173 func (o FlagsBackend) HandleApplicationPorts(devfileobj parser.DevfileObj, _ []int, flags map[string]string) (parser.DevfileObj, error) {
174 d, err := setPortsForFlag(devfileobj, flags, FLAG_RUN_PORT)
175 if err != nil {
176 return parser.DevfileObj{}, err
177 }
178
179 return d, nil
180 }
181
182 func setPortsForFlag(devfileobj parser.DevfileObj, flags map[string]string, flagName string) (parser.DevfileObj, error) {
183 flagVal := flags[flagName]
184
185 split, err := parseStringArrayFlagValue(flagVal)
186 if err != nil || len(split) == 0 {
187 return devfileobj, nil
188 }
189
190 var ports []int
191 for _, s := range split {
192 var p int
193 p, err = strconv.Atoi(s)
194 if err != nil {
195 return parser.DevfileObj{}, fmt.Errorf("invalid value for %s (%q): %w", flagName, s, err)
196 }
197 ports = append(ports, p)
198 }
199
200 var kind v1alpha2.CommandGroupKind
201 switch flagName {
202 case FLAG_RUN_PORT:
203 kind = v1alpha2.RunCommandGroupKind
204 default:
205 return parser.DevfileObj{}, fmt.Errorf("unknown flag: %q", flagName)
206 }
207
208 cmd, ok, err := libdevfile.GetCommand(devfileobj, "", kind)
209 if err != nil {
210 return parser.DevfileObj{}, err
211 }
212 if !ok {
213 klog.V(3).Infof("Specified %s flag will not be applied - no default (or single non-default) command found for kind %v", flagName, kind)
214 return devfileobj, nil
215 }
216
217 cmdType, err := common.GetCommandType(cmd)
218 if err != nil {
219 return parser.DevfileObj{}, err
220 }
221 if cmdType != v1alpha2.ExecCommandType {
222 return parser.DevfileObj{},
223 fmt.Errorf("%v cannot be used with non-exec commands. Found out that command (id: %s) for kind %v is of type %q instead",
224 flagName, cmd.Id, kind, cmdType)
225 }
226
227 cmp, ok, err := libdevfile.FindComponentByName(devfileobj.Data, cmd.Exec.Component)
228 if err != nil {
229 return parser.DevfileObj{}, err
230 }
231 if !ok {
232 return parser.DevfileObj{}, fmt.Errorf("component not found in Devfile for exec command %q", cmd.Id)
233 }
234 cmpType, err := common.GetComponentType(cmp)
235 if err != nil {
236 return parser.DevfileObj{}, err
237 }
238 if cmpType != v1alpha2.ContainerComponentType {
239 return parser.DevfileObj{},
240 fmt.Errorf("%v cannot be used with non-container components. Found out that command (id: %s) for kind %v points to a compoenent of type %q instead",
241 flagName, cmd.Id, kind, cmpType)
242 }
243
244 err = setPortsInContainerComponent(&devfileobj, &cmp, ports, false)
245 if err != nil {
246 return parser.DevfileObj{}, err
247 }
248 return devfileobj, nil
249 }
250
251 func parseStringArrayFlagValue(flagVal string) ([]string, error) {
252 if flagVal == "" {
253 return []string{}, nil
254 }
255
256 if !(strings.HasPrefix(flagVal, "[") && strings.HasSuffix(flagVal, "]")) {
257 return nil, fmt.Errorf("malformed value %q", flagVal)
258 }
259 portsStr := flagVal[1 : len(flagVal)-1]
260 return strings.Split(portsStr, ","), nil
261 }
262
View as plain text