| OLD | NEW |
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | 1 // Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 // Package catalog implements a part that talks to luci-config service to fetch | 5 // Package catalog implements a part that talks to luci-config service to fetch |
| 6 // and parse job definitions. Catalog knows about all task types and can | 6 // and parse job definitions. Catalog knows about all task types and can |
| 7 // instantiate task.Manager's. | 7 // instantiate task.Manager's. |
| 8 package catalog | 8 package catalog |
| 9 | 9 |
| 10 import ( | 10 import ( |
| 11 "fmt" | 11 "fmt" |
| 12 "net/url" | 12 "net/url" |
| 13 "reflect" | 13 "reflect" |
| 14 "regexp" | 14 "regexp" |
| 15 "strings" | 15 "strings" |
| 16 | 16 |
| 17 "github.com/golang/protobuf/proto" | 17 "github.com/golang/protobuf/proto" |
| 18 "golang.org/x/net/context" | 18 "golang.org/x/net/context" |
| 19 | 19 |
| 20 "github.com/luci/gae/service/info" | 20 "github.com/luci/gae/service/info" |
| 21 "github.com/luci/luci-go/common/config" | |
| 22 "github.com/luci/luci-go/common/logging" | 21 "github.com/luci/luci-go/common/logging" |
| 22 "github.com/luci/luci-go/luci_config/common/cfgtypes" |
| 23 "github.com/luci/luci-go/luci_config/server/cfgclient" |
| 24 "github.com/luci/luci-go/luci_config/server/cfgclient/textproto" |
| 23 | 25 |
| 24 "github.com/luci/luci-go/scheduler/appengine/messages" | 26 "github.com/luci/luci-go/scheduler/appengine/messages" |
| 25 "github.com/luci/luci-go/scheduler/appengine/schedule" | 27 "github.com/luci/luci-go/scheduler/appengine/schedule" |
| 26 "github.com/luci/luci-go/scheduler/appengine/task" | 28 "github.com/luci/luci-go/scheduler/appengine/task" |
| 27 ) | 29 ) |
| 28 | 30 |
| 29 var ( | 31 var ( |
| 30 // jobIDRe is used to validate job ID field. | 32 // jobIDRe is used to validate job ID field. |
| 31 jobIDRe = regexp.MustCompile(`^[0-9A-Za-z_\-\.]{1,100}$`) | 33 jobIDRe = regexp.MustCompile(`^[0-9A-Za-z_\-\.]{1,100}$`) |
| 32 ) | 34 ) |
| (...skipping 21 matching lines...) Expand all Loading... |
| 54 GetTaskManager(m proto.Message) task.Manager | 56 GetTaskManager(m proto.Message) task.Manager |
| 55 | 57 |
| 56 // UnmarshalTask takes a serialized task definition (as in Definition.Ta
sk), | 58 // UnmarshalTask takes a serialized task definition (as in Definition.Ta
sk), |
| 57 // unmarshals and validates it, and returns proto.Message that represent | 59 // unmarshals and validates it, and returns proto.Message that represent |
| 58 // the concrete task to run (e.g. SwarmingTask proto). It can be passed
to | 60 // the concrete task to run (e.g. SwarmingTask proto). It can be passed
to |
| 59 // corresponding task.Manager. | 61 // corresponding task.Manager. |
| 60 UnmarshalTask(task []byte) (proto.Message, error) | 62 UnmarshalTask(task []byte) (proto.Message, error) |
| 61 | 63 |
| 62 // GetAllProjects returns a list of all known project ids. | 64 // GetAllProjects returns a list of all known project ids. |
| 63 // | 65 // |
| 64 » // It assumes there's config.Interface implementation installed in | 66 » // It assumes there's cfgclient implementation installed in |
| 65 // the context, will panic if it's not there. | 67 // the context, will panic if it's not there. |
| 66 GetAllProjects(c context.Context) ([]string, error) | 68 GetAllProjects(c context.Context) ([]string, error) |
| 67 | 69 |
| 68 // GetProjectJobs returns a list of scheduler jobs defined within a proj
ect or | 70 // GetProjectJobs returns a list of scheduler jobs defined within a proj
ect or |
| 69 // empty list if no such project. | 71 // empty list if no such project. |
| 70 // | 72 // |
| 71 » // It assumes there's config.Interface implementation installed in | 73 » // It assumes there's cfgclient implementation installed in |
| 72 // the context, will panic if it's not there. | 74 // the context, will panic if it's not there. |
| 73 GetProjectJobs(c context.Context, projectID string) ([]Definition, error
) | 75 GetProjectJobs(c context.Context, projectID string) ([]Definition, error
) |
| 74 } | 76 } |
| 75 | 77 |
| 76 // JobFlavor describes a category of jobs. | 78 // JobFlavor describes a category of jobs. |
| 77 type JobFlavor int | 79 type JobFlavor int |
| 78 | 80 |
| 79 const ( | 81 const ( |
| 80 // JobFlavorPeriodic is a regular job (Swarming, Buildbucket) that runs
on | 82 // JobFlavorPeriodic is a regular job (Swarming, Buildbucket) that runs
on |
| 81 // a schedule or via a trigger. | 83 // a schedule or via a trigger. |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 157 msg := messages.TaskDefWrapper{} | 159 msg := messages.TaskDefWrapper{} |
| 158 if err := proto.Unmarshal(task, &msg); err != nil { | 160 if err := proto.Unmarshal(task, &msg); err != nil { |
| 159 return nil, err | 161 return nil, err |
| 160 } | 162 } |
| 161 return cat.extractTaskProto(&msg) | 163 return cat.extractTaskProto(&msg) |
| 162 } | 164 } |
| 163 | 165 |
| 164 func (cat *catalog) GetAllProjects(c context.Context) ([]string, error) { | 166 func (cat *catalog) GetAllProjects(c context.Context) ([]string, error) { |
| 165 // Enumerate all projects that have <config>.cfg. Do not fetch actual co
nfigs | 167 // Enumerate all projects that have <config>.cfg. Do not fetch actual co
nfigs |
| 166 // yet. | 168 // yet. |
| 167 » cfgs, err := config.GetProjectConfigs(c, cat.configFile(c), true) | 169 » var metas []*cfgclient.Meta |
| 168 » if err != nil { | 170 » if err := cfgclient.Projects(c, cfgclient.AsService, cat.configFile(c),
nil, &metas); err != nil { |
| 169 return nil, err | 171 return nil, err |
| 170 } | 172 } |
| 171 » out := make([]string, 0, len(cfgs)) | 173 |
| 172 » for _, cfg := range cfgs { | 174 » out := make([]string, 0, len(metas)) |
| 173 » » // ConfigSet must be "projects/<project-id>". Verify that. | 175 » for _, meta := range metas { |
| 174 » » chunks := strings.Split(cfg.ConfigSet, "/") | 176 » » projectName, _, _ := meta.ConfigSet.SplitProject() |
| 175 » » if len(chunks) != 2 || chunks[0] != "projects" { | 177 » » if projectName == "" { |
| 176 » » » logging.Warningf(c, "Unexpected ConfigSet: %s", cfg.Conf
igSet) | 178 » » » logging.Warningf(c, "Unexpected ConfigSet: %s", meta.Con
figSet) |
| 177 } else { | 179 } else { |
| 178 » » » out = append(out, chunks[1]) | 180 » » » out = append(out, string(projectName)) |
| 179 } | 181 } |
| 180 } | 182 } |
| 181 return out, nil | 183 return out, nil |
| 182 } | 184 } |
| 183 | 185 |
| 184 func (cat *catalog) GetProjectJobs(c context.Context, projectID string) ([]Defin
ition, error) { | 186 func (cat *catalog) GetProjectJobs(c context.Context, projectID string) ([]Defin
ition, error) { |
| 185 » configSetURL, err := config.GetConfigSetLocation(c, "projects/"+projectI
D) | 187 » configSet := cfgtypes.ProjectConfigSet(cfgtypes.ProjectName(projectID)) |
| 188 » configSetURL, err := cfgclient.GetConfigSetURL(c, cfgclient.AsService, c
onfigSet) |
| 186 switch err { | 189 switch err { |
| 187 case nil: // Continue | 190 case nil: // Continue |
| 188 » case config.ErrNoConfig: | 191 » case cfgclient.ErrNoConfig: |
| 189 return nil, nil | 192 return nil, nil |
| 190 default: | 193 default: |
| 191 return nil, err | 194 return nil, err |
| 192 } | 195 } |
| 193 | 196 |
| 194 » rawCfg, err := config.GetConfig(c, "projects/"+projectID, cat.configFile
(c), false) | 197 » var ( |
| 195 » if err == config.ErrNoConfig { | 198 » » cfg messages.ProjectConfig |
| 199 » » meta cfgclient.Meta |
| 200 » ) |
| 201 » switch err := cfgclient.Get(c, cfgclient.AsService, configSet, cat.confi
gFile(c), textproto.Message(&cfg), &meta); err { |
| 202 » case nil: |
| 203 » » break |
| 204 » case cfgclient.ErrNoConfig: |
| 196 return nil, nil | 205 return nil, nil |
| 197 » } | 206 » default: |
| 198 » if err != nil { | |
| 199 return nil, err | 207 return nil, err |
| 200 } | 208 } |
| 201 » revisionURL := getRevisionURL(configSetURL, rawCfg.Revision, rawCfg.Path
) | 209 |
| 210 » revisionURL := getRevisionURL(&configSetURL, meta.Revision, meta.Path) |
| 202 if revisionURL != "" { | 211 if revisionURL != "" { |
| 203 logging.Infof(c, "Importing %s", revisionURL) | 212 logging.Infof(c, "Importing %s", revisionURL) |
| 204 } | 213 } |
| 205 cfg := messages.ProjectConfig{} | |
| 206 if err = proto.UnmarshalText(rawCfg.Content, &cfg); err != nil { | |
| 207 return nil, err | |
| 208 } | |
| 209 | 214 |
| 210 out := make([]Definition, 0, len(cfg.Job)+len(cfg.Trigger)) | 215 out := make([]Definition, 0, len(cfg.Job)+len(cfg.Trigger)) |
| 211 | 216 |
| 212 // Regular jobs, triggered jobs. | 217 // Regular jobs, triggered jobs. |
| 213 for _, job := range cfg.Job { | 218 for _, job := range cfg.Job { |
| 214 if job.Disabled { | 219 if job.Disabled { |
| 215 continue | 220 continue |
| 216 } | 221 } |
| 217 id := "(empty)" | 222 id := "(empty)" |
| 218 if job.Id != "" { | 223 if job.Id != "" { |
| (...skipping 13 matching lines...) Expand all Loading... |
| 232 if schedule == "" { | 237 if schedule == "" { |
| 233 schedule = defaultJobSchedule | 238 schedule = defaultJobSchedule |
| 234 } | 239 } |
| 235 flavor := JobFlavorTriggered | 240 flavor := JobFlavorTriggered |
| 236 if schedule != "triggered" { | 241 if schedule != "triggered" { |
| 237 flavor = JobFlavorPeriodic | 242 flavor = JobFlavorPeriodic |
| 238 } | 243 } |
| 239 out = append(out, Definition{ | 244 out = append(out, Definition{ |
| 240 JobID: fmt.Sprintf("%s/%s", projectID, job.Id), | 245 JobID: fmt.Sprintf("%s/%s", projectID, job.Id), |
| 241 Flavor: flavor, | 246 Flavor: flavor, |
| 242 » » » Revision: rawCfg.Revision, | 247 » » » Revision: meta.Revision, |
| 243 RevisionURL: revisionURL, | 248 RevisionURL: revisionURL, |
| 244 Schedule: schedule, | 249 Schedule: schedule, |
| 245 Task: packed, | 250 Task: packed, |
| 246 }) | 251 }) |
| 247 } | 252 } |
| 248 | 253 |
| 249 // Triggering jobs. | 254 // Triggering jobs. |
| 250 for _, trigger := range cfg.Trigger { | 255 for _, trigger := range cfg.Trigger { |
| 251 if trigger.Disabled { | 256 if trigger.Disabled { |
| 252 continue | 257 continue |
| (...skipping 12 matching lines...) Expand all Loading... |
| 265 logging.Errorf(c, "Failed to marshal the task: %s/%s: %s
", projectID, id, err) | 270 logging.Errorf(c, "Failed to marshal the task: %s/%s: %s
", projectID, id, err) |
| 266 continue | 271 continue |
| 267 } | 272 } |
| 268 schedule := trigger.Schedule | 273 schedule := trigger.Schedule |
| 269 if schedule == "" { | 274 if schedule == "" { |
| 270 schedule = defaultTriggerSchedule | 275 schedule = defaultTriggerSchedule |
| 271 } | 276 } |
| 272 out = append(out, Definition{ | 277 out = append(out, Definition{ |
| 273 JobID: fmt.Sprintf("%s/%s", projectID, trigger.Id)
, | 278 JobID: fmt.Sprintf("%s/%s", projectID, trigger.Id)
, |
| 274 Flavor: JobFlavorTrigger, | 279 Flavor: JobFlavorTrigger, |
| 275 » » » Revision: rawCfg.Revision, | 280 » » » Revision: meta.Revision, |
| 276 RevisionURL: revisionURL, | 281 RevisionURL: revisionURL, |
| 277 Schedule: schedule, | 282 Schedule: schedule, |
| 278 Task: packed, | 283 Task: packed, |
| 279 }) | 284 }) |
| 280 } | 285 } |
| 281 | 286 |
| 282 return out, nil | 287 return out, nil |
| 283 } | 288 } |
| 284 | 289 |
| 285 // configFile returns a name of *.cfg file (inside project's config set) with | 290 // configFile returns a name of *.cfg file (inside project's config set) with |
| (...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 411 field := v.Field(i) | 416 field := v.Field(i) |
| 412 if field.Type() == taskType { | 417 if field.Type() == taskType { |
| 413 field.Set(reflect.ValueOf(task)) | 418 field.Set(reflect.ValueOf(task)) |
| 414 return proto.Marshal(&wrapper) | 419 return proto.Marshal(&wrapper) |
| 415 } | 420 } |
| 416 } | 421 } |
| 417 // This can happen only if TaskDefWrapper wasn't updated when a new task
type | 422 // This can happen only if TaskDefWrapper wasn't updated when a new task
type |
| 418 // was added. This is a developer's mistake, not a config mistake. | 423 // was added. This is a developer's mistake, not a config mistake. |
| 419 return nil, fmt.Errorf("could not find a field of type %T in TaskDefWrap
per", task) | 424 return nil, fmt.Errorf("could not find a field of type %T in TaskDefWrap
per", task) |
| 420 } | 425 } |
| OLD | NEW |