github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/api/configserver/save.go (about) 1 package configserver 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 10 "code.cloudfoundry.org/lager" 11 "github.com/pf-qiu/concourse/v6/atc" 12 "github.com/pf-qiu/concourse/v6/atc/configvalidate" 13 "github.com/pf-qiu/concourse/v6/atc/creds" 14 "github.com/pf-qiu/concourse/v6/atc/db" 15 "github.com/pf-qiu/concourse/v6/atc/exec" 16 "github.com/pf-qiu/concourse/v6/vars" 17 "github.com/hashicorp/go-multierror" 18 "github.com/tedsuo/rata" 19 ) 20 21 func (s *Server) SaveConfig(w http.ResponseWriter, r *http.Request) { 22 session := s.logger.Session("set-config") 23 24 query := r.URL.Query() 25 26 checkCredentials := false 27 if _, exists := query[atc.SaveConfigCheckCreds]; exists { 28 checkCredentials = true 29 } 30 31 var version db.ConfigVersion 32 if configVersionStr := r.Header.Get(atc.ConfigVersionHeader); len(configVersionStr) != 0 { 33 _, err := fmt.Sscanf(configVersionStr, "%d", &version) 34 if err != nil { 35 session.Error("malformed-config-version", err) 36 s.handleBadRequest(w, fmt.Sprintf("config version is malformed: %s", err)) 37 return 38 } 39 } 40 41 var config atc.Config 42 switch r.Header.Get("Content-type") { 43 case "application/json", "application/x-yaml": 44 body, err := ioutil.ReadAll(r.Body) 45 if err != nil { 46 s.handleBadRequest(w, fmt.Sprintf("read failed: %s", err)) 47 return 48 } 49 50 err = atc.UnmarshalConfig(body, &config) 51 if err != nil { 52 session.Error("malformed-request-payload", err, lager.Data{ 53 "content-type": r.Header.Get("Content-Type"), 54 }) 55 56 s.handleBadRequest(w, fmt.Sprintf("malformed config: %s", err)) 57 return 58 } 59 default: 60 w.WriteHeader(http.StatusUnsupportedMediaType) 61 return 62 } 63 64 warnings, errorMessages := configvalidate.Validate(config) 65 if len(errorMessages) > 0 { 66 session.Info("ignoring-invalid-config", lager.Data{"errors": errorMessages}) 67 s.handleBadRequest(w, errorMessages...) 68 return 69 } 70 71 pipelineName := rata.Param(r, "pipeline_name") 72 warning := atc.ValidateIdentifier(pipelineName, "pipeline") 73 if warning != nil { 74 warnings = append(warnings, *warning) 75 } 76 77 teamName := rata.Param(r, "team_name") 78 warning = atc.ValidateIdentifier(teamName, "team") 79 if warning != nil { 80 warnings = append(warnings, *warning) 81 } 82 83 pipelineRef := atc.PipelineRef{Name: pipelineName} 84 if atc.EnablePipelineInstances { 85 if instanceVars := query.Get("instance_vars"); instanceVars != "" { 86 err := json.Unmarshal([]byte(instanceVars), &pipelineRef.InstanceVars) 87 if err != nil { 88 session.Error("malformed-instance-vars", err) 89 s.handleBadRequest(w, fmt.Sprintf("instance_vars is malformed: %s", err)) 90 return 91 } 92 } 93 } else if query.Get("instance_vars") != "" { 94 s.handleBadRequest(w, "support for `instance-vars` is disabled") 95 return 96 } 97 98 if checkCredentials { 99 variables := creds.NewVariables(s.secretManager, teamName, pipelineName, false) 100 101 errs := validateCredParams(variables, config, session) 102 if errs != nil { 103 s.handleBadRequest(w, fmt.Sprintf("credential validation failed\n\n%s", errs)) 104 return 105 } 106 } 107 108 session.Info("saving") 109 110 team, found, err := s.teamFactory.FindTeam(teamName) 111 if err != nil { 112 session.Error("failed-to-find-team", err) 113 w.WriteHeader(http.StatusInternalServerError) 114 return 115 } 116 117 if !found { 118 session.Debug("team-not-found") 119 w.WriteHeader(http.StatusNotFound) 120 return 121 } 122 123 _, created, err := team.SavePipeline(pipelineRef, config, version, true) 124 if err != nil { 125 session.Error("failed-to-save-config", err) 126 w.WriteHeader(http.StatusInternalServerError) 127 fmt.Fprintf(w, "failed to save config: %s", err) 128 return 129 } 130 131 if !created { 132 if err = s.teamFactory.NotifyResourceScanner(); err != nil { 133 session.Error("failed-to-notify-resource-scanner", err) 134 } 135 } 136 137 session.Info("saved") 138 139 w.Header().Set("Content-Type", "application/json") 140 141 if created { 142 w.WriteHeader(http.StatusCreated) 143 } else { 144 w.WriteHeader(http.StatusOK) 145 } 146 147 s.writeSaveConfigResponse(w, atc.SaveConfigResponse{Warnings: warnings}) 148 } 149 150 // Simply validate that the credentials exist; don't do anything with the actual secrets 151 func validateCredParams(credMgrVars vars.Variables, config atc.Config, session lager.Logger) error { 152 var errs error 153 154 for _, resourceType := range config.ResourceTypes { 155 _, err := creds.NewSource(credMgrVars, resourceType.Source).Evaluate() 156 if err != nil { 157 errs = multierror.Append(errs, err) 158 } 159 } 160 161 for _, resource := range config.Resources { 162 _, err := creds.NewSource(credMgrVars, resource.Source).Evaluate() 163 if err != nil { 164 errs = multierror.Append(errs, err) 165 } 166 167 _, err = creds.NewString(credMgrVars, resource.WebhookToken).Evaluate() 168 if err != nil { 169 errs = multierror.Append(errs, err) 170 } 171 } 172 173 for _, job := range config.Jobs { 174 _ = job.StepConfig().Visit(atc.StepRecursor{ 175 OnTask: func(step *atc.TaskStep) error { 176 err := creds.NewTaskEnvValidator(credMgrVars, step.Params).Validate() 177 if err != nil { 178 errs = multierror.Append(errs, err) 179 } 180 181 err = creds.NewTaskVarsValidator(credMgrVars, step.Vars).Validate() 182 if err != nil { 183 errs = multierror.Append(errs, err) 184 } 185 186 if step.Config != nil { 187 // embedded task - we can fully validate it, interpolating with cred mgr variables 188 var taskConfigSource exec.TaskConfigSource 189 embeddedTaskVars := []vars.Variables{credMgrVars} 190 taskConfigSource = exec.StaticConfigSource{Config: step.Config} 191 taskConfigSource = exec.InterpolateTemplateConfigSource{ 192 ConfigSource: taskConfigSource, 193 Vars: embeddedTaskVars, 194 ExpectAllKeys: true, 195 } 196 taskConfigSource = exec.ValidatingConfigSource{ConfigSource: taskConfigSource} 197 _, err = taskConfigSource.FetchConfig(context.TODO(), session, nil) 198 if err != nil { 199 errs = multierror.Append(errs, err) 200 } 201 } 202 203 return nil 204 }, 205 }) 206 } 207 208 if errs != nil { 209 session.Info("config-has-invalid-creds", lager.Data{"errors": errs.Error()}) 210 } 211 212 return errs 213 } 214 215 func (s *Server) handleBadRequest(w http.ResponseWriter, errorMessages ...string) { 216 w.Header().Set("Content-Type", "application/json") 217 w.WriteHeader(http.StatusBadRequest) 218 s.writeSaveConfigResponse(w, atc.SaveConfigResponse{ 219 Errors: errorMessages, 220 }) 221 } 222 223 func (s *Server) writeSaveConfigResponse(w http.ResponseWriter, saveConfigResponse atc.SaveConfigResponse) { 224 responseJSON, err := json.Marshal(saveConfigResponse) 225 if err != nil { 226 w.WriteHeader(http.StatusInternalServerError) 227 fmt.Fprintf(w, "failed to generate error response: %s", err) 228 return 229 } 230 231 _, err = w.Write(responseJSON) 232 if err != nil { 233 w.WriteHeader(http.StatusInternalServerError) 234 return 235 } 236 }