github.com/drone/go-convert@v0.0.0-20240307072510-6bd371c65e61/convert/circle/util.go (about) 1 // Copyright 2022 Harness, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package circle 16 17 import ( 18 "fmt" 19 "strings" 20 21 circle "github.com/drone/go-convert/convert/circle/yaml" 22 harness "github.com/drone/spec/dist/go" 23 ) 24 25 // helper function splits the orb alias and command. 26 func splitOrb(s string) (alias string, command string) { 27 parts := strings.Split(s, "/") 28 alias = parts[0] 29 if len(parts) > 1 { 30 command = parts[1] 31 } 32 return 33 } 34 35 // helper function splits the orb alias and command. 36 func splitOrbVersion(s string) (orb string, version string) { 37 parts := strings.Split(s, "@") 38 orb = parts[0] 39 if len(parts) > 1 { 40 version = parts[1] 41 } 42 return 43 } 44 45 // helper function converts docker containers from the 46 // docker executor to background steps. 47 func defaultBackgroundSteps(job *circle.Job, config *circle.Config) []*harness.Step { 48 var steps []*harness.Step 49 50 executor := extractExecutor(job, config) 51 // execit if the executor is nil, or if there is 52 // less than 1 docker container defined. The first 53 // container is used for execution, and subsequent 54 // containers are used as background steps. 55 if executor == nil || len(executor.Docker) < 1 { 56 return nil 57 } 58 // loop through and convert the docker containers 59 // to background steps. 60 for i, docker := range executor.Docker { 61 // skip the first docker container in the list, 62 // since this is used as the run step execution 63 // container only. 64 if i == 0 { 65 continue 66 } 67 steps = append(steps, &harness.Step{ 68 Type: "background", 69 Spec: &harness.StepBackground{ 70 Envs: docker.Environment, 71 Image: docker.Image, 72 // TODO entrypoint 73 // Entrypoint: docker.Entrypoint, 74 Args: docker.Command, 75 User: docker.User, 76 }, 77 }) 78 } 79 return steps 80 } 81 82 // helper function extracts the docker configuration 83 // from a job. 84 func extractDocker(job *circle.Job, config *circle.Config) *circle.Docker { 85 executor := extractExecutor(job, config) 86 // if the executor defines a docker environment, 87 // use the first docker container as the execution 88 // container. 89 if executor != nil && len(executor.Docker) != 0 { 90 return executor.Docker[0] 91 } 92 return nil 93 } 94 95 // helper function extrats an executor from a job. 96 func extractExecutor(job *circle.Job, config *circle.Config) *circle.Executor { 97 // return the named executor for the job 98 if job.Executor != nil { 99 // loop through the global executors. 100 for name, executor := range config.Executors { 101 if name == job.Executor.Name { 102 return executor 103 } 104 } 105 } 106 // else create an executor based on the job 107 // configuration. we do this because it is easier to 108 // work with an executor struct, than both an executor 109 // and a job struct. 110 return &circle.Executor{ 111 Docker: job.Docker, 112 ResourceClass: job.ResourceClass, 113 Machine: job.Machine, 114 Macos: job.Macos, 115 Windows: nil, 116 Shell: job.Shell, 117 WorkingDir: job.WorkingDir, 118 Environment: job.Environment, 119 } 120 } 121 122 // helper function extracts matrix parameters. 123 func extractMatrixParams(matrix *circle.Matrix) []string { 124 var params []string 125 if matrix != nil { 126 for name := range matrix.Parameters { 127 params = append(params, name) 128 } 129 } 130 return params 131 } 132 133 // helper function converts a map[string]interface to 134 // a map[string]string. 135 func convertMatrix(job *circle.Job, matrix *circle.Matrix) *harness.Strategy { 136 spec := new(harness.Matrix) 137 spec.Axis = map[string][]string{} 138 spec.Concurrency = int64(job.Parallelism) 139 140 // convert from map[string]interface{} to 141 // map[string]string 142 for name, params := range matrix.Parameters { 143 var items []string 144 for _, param := range params { 145 items = append(items, fmt.Sprint(param)) 146 } 147 spec.Axis[name] = items 148 } 149 150 // convert from map[string]interface{} to 151 // map[string]string 152 for _, exclude := range matrix.Exclude { 153 m := map[string]string{} 154 for name, param := range exclude { 155 // Convert parameters to a string 156 // and concatenate if they form a list 157 switch v := param.(type) { 158 case []interface{}: 159 var items []string 160 for _, item := range v { 161 items = append(items, fmt.Sprint(item)) 162 } 163 m[name] = strings.Join(items, ",") 164 default: 165 m[name] = fmt.Sprint(param) 166 } 167 } 168 spec.Exclude = append(spec.Exclude, m) 169 } 170 171 return &harness.Strategy{ 172 Type: "matrix", 173 Spec: spec, 174 } 175 } 176 177 // helper function extracts and aggregates the circle 178 // input parameters from the circle pipeline and job. 179 func extractParameters(config *circle.Config) map[string]*circle.Parameter { 180 params := map[string]*circle.Parameter{} 181 182 // extract the parameters from the jobs. 183 for _, job := range config.Jobs { 184 for k, v := range job.Parameters { 185 params[k] = v 186 } 187 } 188 // extract the parameters from the pipeline. 189 // these will override job parameters by design. 190 for k, v := range config.Parameters { 191 params[k] = v 192 } 193 return params 194 } 195 196 // helper function converts circle parameters to 197 // harness inputs. 198 func convertParameters(in map[string]*circle.Parameter) map[string]*harness.Input { 199 out := map[string]*harness.Input{} 200 for name, param := range in { 201 t := param.Type 202 switch t { 203 case "integer": 204 t = "number" 205 case "string", "enum", "env_var_name": 206 t = "string" 207 case "boolean": 208 t = "boolean" 209 case "executor", "steps": 210 // TODO parameter.type execution not supported 211 // TODO parameter.type steps not supported 212 continue // skip 213 } 214 var d string 215 if param.Default != nil { 216 d = fmt.Sprint(param.Default) 217 } 218 out[name] = &harness.Input{ 219 Type: t, 220 Default: d, 221 Description: param.Description, 222 } 223 } 224 return out 225 } 226 227 // helper function converts circle executor to a 228 // harness platform. 229 func convertPlatform(job *circle.Job, config *circle.Config) *harness.Platform { 230 executor := extractExecutor(job, config) 231 if executor == nil { 232 return nil 233 } 234 if executor.Windows != nil { 235 return &harness.Platform{ 236 Os: harness.OSWindows.String(), 237 Arch: harness.ArchAmd64.String(), 238 } 239 } 240 if executor.Machine != nil { 241 if strings.Contains(executor.Machine.Image, "win") || 242 strings.Contains(executor.ResourceClass, "win") { 243 return &harness.Platform{ 244 Os: harness.OSWindows.String(), 245 Arch: harness.ArchAmd64.String(), 246 } 247 } 248 if strings.Contains(executor.Machine.Image, "arm") || 249 strings.Contains(executor.ResourceClass, "arm") { 250 return &harness.Platform{ 251 Os: harness.OSLinux.String(), 252 Arch: harness.ArchArm64.String(), 253 } 254 } 255 } 256 if executor.Macos != nil { 257 return &harness.Platform{ 258 Os: harness.OSMacos.String(), 259 Arch: harness.ArchArm64.String(), 260 } 261 } 262 return &harness.Platform{ 263 Os: harness.OSLinux.String(), 264 Arch: harness.ArchAmd64.String(), 265 } 266 } 267 268 // helper function converts circle resource class 269 // to a harness resource class. 270 func convertResourceClass(s string) string { 271 // TODO map circle resource class to harness resource classes 272 switch s { 273 case "small": 274 case "medium": 275 case "medium+": 276 case "large": 277 case "xlarge": 278 case "2xlarge": 279 case "2xlarge+": 280 case "arm.medium": 281 case "arm.large": 282 case "arm.xlarge": 283 case "arm.2xlarge": 284 case "macos.m1.large.gen1": 285 case "macos.x86.metal.gen1": 286 case "gpu.nvidia.small": 287 case "gpu.nvidia.medium": 288 case "gpu.nvidia.large": 289 case "windows.gpu.nvidia.medium": 290 } 291 292 return "" 293 } 294 295 // helper function converts circle executor to a 296 // harness runtime. 297 func convertRuntime(job *circle.Job, config *circle.Config) *harness.Runtime { 298 spec := new(harness.RuntimeCloud) 299 // get the executor associated with this config 300 // and convert the resource class to harness size. 301 if exec := extractExecutor(job, config); exec != nil { 302 spec.Size = convertResourceClass(exec.ResourceClass) 303 } 304 return &harness.Runtime{ 305 Type: "cloud", 306 Spec: spec, 307 } 308 } 309 310 // helper function combines environment variables. 311 func combineEnvs(env ...map[string]string) map[string]string { 312 c := map[string]string{} 313 for _, e := range env { 314 for k, v := range e { 315 c[k] = v 316 } 317 } 318 return c 319 }