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  }