github.com/chenbh/concourse/v6@v6.4.2/atc/task.go (about) 1 package atc 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "strings" 8 9 "sigs.k8s.io/yaml" 10 ) 11 12 type TaskConfig struct { 13 // The platform the task must run on (e.g. linux, windows). 14 Platform string `json:"platform,omitempty"` 15 16 // Optional string specifying an image to use for the build. Depending on the 17 // platform, this may or may not be required (e.g. Windows/OS X vs. Linux). 18 RootfsURI string `json:"rootfs_uri,omitempty"` 19 20 ImageResource *ImageResource `json:"image_resource,omitempty"` 21 22 // Limits to set on the Task Container 23 Limits *ContainerLimits `json:"container_limits,omitempty"` 24 25 // Parameters to pass to the task via environment variables. 26 Params TaskEnv `json:"params,omitempty"` 27 28 // Script to execute. 29 Run TaskRunConfig `json:"run,omitempty"` 30 31 // The set of (logical, name-only) inputs required by the task. 32 Inputs []TaskInputConfig `json:"inputs,omitempty"` 33 34 // The set of (logical, name-only) outputs provided by the task. 35 Outputs []TaskOutputConfig `json:"outputs,omitempty"` 36 37 // Path to cached directory that will be shared between builds for the same task. 38 Caches []TaskCacheConfig `json:"caches,omitempty"` 39 } 40 41 type ContainerLimits struct { 42 CPU *uint64 `json:"cpu,omitempty"` 43 Memory *uint64 `json:"memory,omitempty"` 44 } 45 46 type ImageResource struct { 47 Type string `json:"type"` 48 Source Source `json:"source"` 49 50 Params Params `json:"params,omitempty"` 51 Version Version `json:"version,omitempty"` 52 } 53 54 func NewTaskConfig(configBytes []byte) (TaskConfig, error) { 55 var config TaskConfig 56 err := yaml.UnmarshalStrict(configBytes, &config, yaml.DisallowUnknownFields) 57 if err != nil { 58 return TaskConfig{}, err 59 } 60 61 err = config.Validate() 62 if err != nil { 63 return TaskConfig{}, err 64 } 65 66 return config, nil 67 } 68 69 type TaskValidationError struct { 70 Errors []string 71 } 72 73 func (err TaskValidationError) Error() string { 74 return fmt.Sprintf("invalid task configuration:\n%s", strings.Join(err.Errors, "\n")) 75 } 76 77 func (config TaskConfig) Validate() error { 78 var errors []string 79 80 if config.Platform == "" { 81 errors = append(errors, "missing 'platform'") 82 } 83 84 if config.Run.Path == "" { 85 errors = append(errors, "missing path to executable to run") 86 } 87 88 errors = append(errors, config.validateInputContainsNames()...) 89 errors = append(errors, config.validateOutputContainsNames()...) 90 91 if len(errors) > 0 { 92 return TaskValidationError{ 93 Errors: errors, 94 } 95 } 96 97 return nil 98 } 99 100 func (config TaskConfig) validateOutputContainsNames() []string { 101 var messages []string 102 103 for i, output := range config.Outputs { 104 if output.Name == "" { 105 messages = append(messages, fmt.Sprintf(" output in position %d is missing a name", i)) 106 } 107 } 108 109 return messages 110 } 111 112 func (config TaskConfig) validateInputContainsNames() []string { 113 messages := []string{} 114 115 for i, input := range config.Inputs { 116 if input.Name == "" { 117 messages = append(messages, fmt.Sprintf(" input in position %d is missing a name", i)) 118 } 119 } 120 121 return messages 122 } 123 124 type TaskRunConfig struct { 125 Path string `json:"path"` 126 Args []string `json:"args,omitempty"` 127 Dir string `json:"dir,omitempty"` 128 129 // The user that the task will run as (defaults to whatever the docker image specifies) 130 User string `json:"user,omitempty"` 131 } 132 133 type TaskInputConfig struct { 134 Name string `json:"name"` 135 Path string `json:"path,omitempty"` 136 Optional bool `json:"optional,omitempty"` 137 } 138 139 type TaskOutputConfig struct { 140 Name string `json:"name"` 141 Path string `json:"path,omitempty"` 142 } 143 144 type TaskCacheConfig struct { 145 Path string `json:"path,omitempty"` 146 } 147 148 type TaskEnv map[string]string 149 150 func (te *TaskEnv) UnmarshalJSON(p []byte) error { 151 raw := map[string]CoercedString{} 152 err := json.Unmarshal(p, &raw) 153 if err != nil { 154 return err 155 } 156 157 m := map[string]string{} 158 for k, v := range raw { 159 m[k] = string(v) 160 } 161 162 *te = m 163 164 return nil 165 } 166 167 func (te TaskEnv) Env() []string { 168 env := make([]string, 0, len(te)) 169 170 for k, v := range te { 171 env = append(env, k+"="+v) 172 } 173 174 return env 175 } 176 177 type CoercedString string 178 179 func (cs *CoercedString) UnmarshalJSON(p []byte) error { 180 var raw interface{} 181 dec := json.NewDecoder(bytes.NewReader(p)) 182 dec.UseNumber() 183 err := dec.Decode(&raw) 184 if err != nil { 185 return err 186 } 187 188 if raw == nil { 189 *cs = CoercedString("") 190 return nil 191 } 192 switch v := raw.(type) { 193 case string: 194 *cs = CoercedString(v) 195 196 case json.Number: 197 *cs = CoercedString(v) 198 199 default: 200 j, err := json.Marshal(v) 201 if err != nil { 202 return err 203 } 204 205 *cs = CoercedString(j) 206 } 207 208 return nil 209 }