| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | |
| 3 // that can be found in the LICENSE file. | |
| 4 | |
| 5 package buildbot | |
| 6 | |
| 7 import ( | |
| 8 "bytes" | |
| 9 "compress/gzip" | |
| 10 "encoding/json" | |
| 11 "fmt" | |
| 12 "sort" | |
| 13 "time" | |
| 14 | |
| 15 ds "github.com/luci/gae/service/datastore" | |
| 16 "github.com/luci/luci-go/common/logging" | |
| 17 "github.com/luci/luci-go/milo/api/resp" | |
| 18 "github.com/luci/luci-go/milo/appengine/common" | |
| 19 "github.com/luci/luci-go/server/auth" | |
| 20 "github.com/luci/luci-go/server/auth/identity" | |
| 21 | |
| 22 "golang.org/x/net/context" | |
| 23 ) | |
| 24 | |
| 25 func decodeMasterEntry( | |
| 26 c context.Context, entry *buildbotMasterEntry, master *buildbotMaster) e
rror { | |
| 27 | |
| 28 reader, err := gzip.NewReader(bytes.NewReader(entry.Data)) | |
| 29 if err != nil { | |
| 30 return err | |
| 31 } | |
| 32 defer reader.Close() | |
| 33 if err = json.NewDecoder(reader).Decode(master); err != nil { | |
| 34 return err | |
| 35 } | |
| 36 return nil | |
| 37 } | |
| 38 | |
| 39 // User not logged in, master found, master public: nil | |
| 40 // User not logged in, master not found: 401 | |
| 41 // User not logged in, master internal: 401 | |
| 42 // User logged in, master found, master internal: nil | |
| 43 // User logged in, master not found: 404 | |
| 44 // User logged in, master found, master internal: 404 | |
| 45 // Other error: 500 | |
| 46 func checkAccess(c context.Context, err error, internal bool) error { | |
| 47 cu := auth.CurrentUser(c) | |
| 48 switch { | |
| 49 case err == ds.ErrNoSuchEntity: | |
| 50 if cu.Identity == identity.AnonymousIdentity { | |
| 51 return errNotAuth | |
| 52 } | |
| 53 return errMasterNotFound | |
| 54 case err != nil: | |
| 55 return err | |
| 56 } | |
| 57 | |
| 58 // Do the ACL check if the entry is internal. | |
| 59 if internal { | |
| 60 allowed, err := common.IsAllowedInternal(c) | |
| 61 if err != nil { | |
| 62 return err | |
| 63 } | |
| 64 if !allowed { | |
| 65 if cu.Identity == identity.AnonymousIdentity { | |
| 66 return errNotAuth | |
| 67 } | |
| 68 return errMasterNotFound | |
| 69 } | |
| 70 } | |
| 71 | |
| 72 return nil | |
| 73 } | |
| 74 | |
| 75 // getMasterEntry feches the named master and does an ACL check on the | |
| 76 // current user. | |
| 77 // It returns: | |
| 78 func getMasterEntry(c context.Context, name string) (*buildbotMasterEntry, error
) { | |
| 79 entry := buildbotMasterEntry{Name: name} | |
| 80 err := ds.Get(c, &entry) | |
| 81 err = checkAccess(c, err, entry.Internal) | |
| 82 return &entry, err | |
| 83 } | |
| 84 | |
| 85 // getMasterJSON fetches the latest known buildbot master data and returns | |
| 86 // the buildbotMaster struct (if found), whether or not it is internal, | |
| 87 // the last modified time, and an error if not found. | |
| 88 func getMasterJSON(c context.Context, name string) ( | |
| 89 master *buildbotMaster, internal bool, t time.Time, err error) { | |
| 90 master = &buildbotMaster{} | |
| 91 entry, err := getMasterEntry(c, name) | |
| 92 if err != nil { | |
| 93 return | |
| 94 } | |
| 95 t = entry.Modified | |
| 96 internal = entry.Internal | |
| 97 err = decodeMasterEntry(c, entry, master) | |
| 98 return | |
| 99 } | |
| 100 | |
| 101 // GetAllBuilders returns a resp.Module object containing all known masters | |
| 102 // and builders. | |
| 103 func GetAllBuilders(c context.Context) (*resp.CIService, error) { | |
| 104 result := &resp.CIService{Name: "Buildbot"} | |
| 105 // Fetch all Master entries from datastore | |
| 106 q := ds.NewQuery("buildbotMasterEntry") | |
| 107 // TODO(hinoka): Maybe don't look past like a month or so? | |
| 108 entries := []*buildbotMasterEntry{} | |
| 109 err := (&ds.Batcher{}).GetAll(c, q, &entries) | |
| 110 if err != nil { | |
| 111 return nil, err | |
| 112 } | |
| 113 | |
| 114 // Add each builder from each master entry into the result. | |
| 115 // TODO(hinoka): FanInOut this? | |
| 116 for _, entry := range entries { | |
| 117 if entry.Internal { | |
| 118 // Bypass the master if it's an internal master and the
user is not | |
| 119 // part of the buildbot-private project. | |
| 120 allowed, err := common.IsAllowedInternal(c) | |
| 121 if err != nil { | |
| 122 logging.WithError(err).Errorf(c, "Could not proc
ess master %s", entry.Name) | |
| 123 return nil, err | |
| 124 } | |
| 125 if !allowed { | |
| 126 continue | |
| 127 } | |
| 128 } | |
| 129 master := &buildbotMaster{} | |
| 130 err = decodeMasterEntry(c, entry, master) | |
| 131 if err != nil { | |
| 132 logging.WithError(err).Errorf(c, "Could not decode %s",
entry.Name) | |
| 133 continue | |
| 134 } | |
| 135 ml := resp.BuilderGroup{Name: entry.Name} | |
| 136 // Sort the builder listing. | |
| 137 sb := make([]string, 0, len(master.Builders)) | |
| 138 for bn := range master.Builders { | |
| 139 sb = append(sb, bn) | |
| 140 } | |
| 141 sort.Strings(sb) | |
| 142 for _, bn := range sb { | |
| 143 // Go templates escapes this for us, and also | |
| 144 // slashes are not allowed in builder names. | |
| 145 ml.Builders = append(ml.Builders, *resp.NewLink( | |
| 146 bn, fmt.Sprintf("/buildbot/%s/%s", entry.Name, b
n))) | |
| 147 } | |
| 148 result.BuilderGroups = append(result.BuilderGroups, ml) | |
| 149 } | |
| 150 return result, nil | |
| 151 } | |
| OLD | NEW |