github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/mungegithub/mungers/publisher.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package mungers 18 19 import ( 20 "bytes" 21 "fmt" 22 "os" 23 "os/exec" 24 "path/filepath" 25 "strings" 26 27 "github.com/golang/glog" 28 "k8s.io/kubernetes/pkg/util/sets" 29 "k8s.io/test-infra/mungegithub/features" 30 "k8s.io/test-infra/mungegithub/github" 31 "k8s.io/test-infra/mungegithub/options" 32 ) 33 34 // coordinate of a piece of code 35 type coordinate struct { 36 repo string 37 branch string 38 // dir from repo root 39 dir string 40 } 41 42 func (c coordinate) String() string { 43 return fmt.Sprintf("[repository %s, branch %s, subdir %s]", c.repo, c.branch, c.dir) 44 } 45 46 type branchRule struct { 47 src coordinate 48 dst coordinate 49 // k8s.io/* repos the dst dependes on 50 deps []coordinate 51 } 52 53 // a collection of publishing rules for a single destination repo 54 type repoRules struct { 55 dstRepo string 56 // publisher.go has assumption that src.repo is always kubernetes. 57 srcToDst []branchRule 58 // if empty (e.g., for client-go), publisher will use its default publish script 59 publishScript string 60 } 61 62 // PublisherMunger publishes content from one repository to another one. 63 type PublisherMunger struct { 64 reposRules []repoRules 65 features *features.Features 66 githubConfig *github.Config 67 // plog duplicates the logs at glog and a file 68 plog *plog 69 // absolute path to the k8s repos. 70 k8sIOPath string 71 } 72 73 func init() { 74 RegisterMungerOrDie(&PublisherMunger{}) 75 } 76 77 // Name is the name usable in --pr-mungers 78 func (p *PublisherMunger) Name() string { return "publisher" } 79 80 // RequiredFeatures is a slice of 'features' that must be provided 81 func (p *PublisherMunger) RequiredFeatures() []string { return []string{} } 82 83 // Initialize will initialize the munger 84 func (p *PublisherMunger) Initialize(config *github.Config, features *features.Features) error { 85 gopath := os.Getenv("GOPATH") 86 p.k8sIOPath = filepath.Join(gopath, "src", "k8s.io") 87 88 clientGo := repoRules{ 89 dstRepo: "client-go", 90 srcToDst: []branchRule{ 91 { 92 // rule for the client-go master branch 93 src: coordinate{repo: config.Project, branch: "master", dir: "staging/src/k8s.io/client-go"}, 94 dst: coordinate{repo: "client-go", branch: "master", dir: "./"}, 95 deps: []coordinate{{repo: "apimachinery", branch: "master"}}, 96 }, 97 { 98 // rule for the client-go release-2.0 branch 99 src: coordinate{repo: config.Project, branch: "release-1.5", dir: "staging/src/k8s.io/client-go"}, 100 dst: coordinate{repo: "client-go", branch: "release-2.0", dir: "./"}, 101 }, 102 { 103 // rule for the client-go release-3.0 branch 104 src: coordinate{repo: config.Project, branch: "release-1.6", dir: "staging/src/k8s.io/client-go"}, 105 dst: coordinate{repo: "client-go", branch: "release-3.0", dir: "./"}, 106 deps: []coordinate{{repo: "apimachinery", branch: "release-1.6"}}, 107 }, 108 { 109 // rule for the client-go release-4.0 branch 110 src: coordinate{repo: config.Project, branch: "release-1.7", dir: "staging/src/k8s.io/client-go"}, 111 dst: coordinate{repo: "client-go", branch: "release-4.0", dir: "./"}, 112 deps: []coordinate{{repo: "apimachinery", branch: "release-1.7"}}, 113 }, 114 }, 115 publishScript: "/publish_scripts/publish_client_go.sh", 116 } 117 118 apimachinery := repoRules{ 119 dstRepo: "apimachinery", 120 srcToDst: []branchRule{ 121 { 122 // rule for the apimachinery master branch 123 src: coordinate{repo: config.Project, branch: "master", dir: "staging/src/k8s.io/apimachinery"}, 124 dst: coordinate{repo: "apimachinery", branch: "master", dir: "./"}, 125 }, 126 { 127 // rule for the apimachinery 1.6 branch 128 src: coordinate{repo: config.Project, branch: "release-1.6", dir: "staging/src/k8s.io/apimachinery"}, 129 dst: coordinate{repo: "apimachinery", branch: "release-1.6", dir: "./"}, 130 }, 131 { 132 // rule for the apimachinery 1.7 branch 133 src: coordinate{repo: config.Project, branch: "release-1.7", dir: "staging/src/k8s.io/apimachinery"}, 134 dst: coordinate{repo: "apimachinery", branch: "release-1.7", dir: "./"}, 135 }, 136 }, 137 publishScript: "/publish_scripts/publish_apimachinery.sh", 138 } 139 140 apiserver := repoRules{ 141 dstRepo: "apiserver", 142 srcToDst: []branchRule{ 143 { 144 // rule for the apiserver master branch 145 src: coordinate{repo: config.Project, branch: "master", dir: "staging/src/k8s.io/apiserver"}, 146 dst: coordinate{repo: "apiserver", branch: "master", dir: "./"}, 147 deps: []coordinate{ 148 {repo: "apimachinery", branch: "master"}, 149 {repo: "client-go", branch: "master"}, 150 }, 151 }, 152 { 153 // rule for the apiserver 1.6 branch 154 src: coordinate{repo: config.Project, branch: "release-1.6", dir: "staging/src/k8s.io/apiserver"}, 155 dst: coordinate{repo: "apiserver", branch: "release-1.6", dir: "./"}, 156 deps: []coordinate{ 157 {repo: "apimachinery", branch: "release-1.6"}, 158 {repo: "client-go", branch: "release-3.0"}, 159 }, 160 }, 161 }, 162 publishScript: "/publish_scripts/publish_apiserver.sh", 163 } 164 165 kubeAggregator := repoRules{ 166 dstRepo: "kube-aggregator", 167 srcToDst: []branchRule{ 168 { 169 // rule for the kube-aggregator master branch 170 src: coordinate{repo: config.Project, branch: "master", dir: "staging/src/k8s.io/kube-aggregator"}, 171 dst: coordinate{repo: "kube-aggregator", branch: "master", dir: "./"}, 172 deps: []coordinate{ 173 {repo: "apimachinery", branch: "master"}, 174 {repo: "client-go", branch: "master"}, 175 {repo: "apiserver", branch: "master"}, 176 }, 177 }, 178 { 179 // rule for the kube-aggregator 1.6 branch 180 src: coordinate{repo: config.Project, branch: "release-1.6", dir: "staging/src/k8s.io/kube-aggregator"}, 181 dst: coordinate{repo: "kube-aggregator", branch: "release-1.6", dir: "./"}, 182 deps: []coordinate{ 183 {repo: "apimachinery", branch: "release-1.6"}, 184 {repo: "client-go", branch: "release-3.0"}, 185 {repo: "apiserver", branch: "release-1.6"}, 186 }, 187 }, 188 }, 189 publishScript: "/publish_scripts/publish_kube_aggregator.sh", 190 } 191 192 sampleAPIServer := repoRules{ 193 dstRepo: "sample-apiserver", 194 srcToDst: []branchRule{ 195 { 196 // rule for the sample-apiserver master branch 197 src: coordinate{repo: config.Project, branch: "master", dir: "staging/src/k8s.io/sample-apiserver"}, 198 dst: coordinate{repo: "sample-apiserver", branch: "master", dir: "./"}, 199 deps: []coordinate{ 200 {repo: "apimachinery", branch: "master"}, 201 {repo: "client-go", branch: "master"}, 202 {repo: "apiserver", branch: "master"}, 203 }, 204 }, 205 { 206 // rule for the sample-apiserver 1.6 branch 207 src: coordinate{repo: config.Project, branch: "release-1.6", dir: "staging/src/k8s.io/sample-apiserver"}, 208 dst: coordinate{repo: "sample-apiserver", branch: "release-1.6", dir: "./"}, 209 deps: []coordinate{ 210 {repo: "apimachinery", branch: "release-1.6"}, 211 {repo: "client-go", branch: "release-3.0"}, 212 {repo: "apiserver", branch: "release-1.6"}, 213 }, 214 }, 215 }, 216 publishScript: "/publish_scripts/publish_sample_apiserver.sh", 217 } 218 219 apiExtensionsAPIServer := repoRules{ 220 dstRepo: "apiextensions-apiserver", 221 srcToDst: []branchRule{ 222 { 223 // rule for the sample-apiserver master branch 224 src: coordinate{repo: config.Project, branch: "master", dir: "staging/src/k8s.io/apiextensions-apiserver"}, 225 dst: coordinate{repo: "apiextensions-apiserver", branch: "master", dir: "./"}, 226 deps: []coordinate{ 227 {repo: "apimachinery", branch: "master"}, 228 {repo: "client-go", branch: "master"}, 229 {repo: "apiserver", branch: "master"}, 230 }, 231 }, 232 }, 233 publishScript: "/publish_scripts/publish_apiextensions_apiserver.sh", 234 } 235 236 api := repoRules{ 237 dstRepo: "api", 238 srcToDst: []branchRule{ 239 { 240 // rule for the api master branch 241 src: coordinate{repo: config.Project, branch: "master", dir: "staging/src/k8s.io/api"}, 242 dst: coordinate{repo: "api", branch: "master", dir: "./"}, 243 deps: []coordinate{ 244 {repo: "apimachinery", branch: "master"}, 245 }, 246 }, 247 }, 248 publishScript: "/publish_scripts/publish_api.sh", 249 } 250 // NOTE: Order of the repos is sensitive!!! A dependent repo needs to be published first, so that other repos can vendor its latest revision. 251 p.reposRules = []repoRules{apimachinery, api, clientGo, apiserver, kubeAggregator, sampleAPIServer, apiExtensionsAPIServer} 252 glog.Infof("publisher munger rules: %#v\n", p.reposRules) 253 p.features = features 254 p.githubConfig = config 255 return nil 256 } 257 258 // update the local checkout of k8s.io/kubernetes 259 func (p *PublisherMunger) updateKubernetes() error { 260 cmd := exec.Command("git", "fetch", "origin") 261 cmd.Dir = filepath.Join(p.k8sIOPath, "kubernetes") 262 output, err := cmd.CombinedOutput() 263 p.plog.Infof("%s", output) 264 if err != nil { 265 return err 266 } 267 // update kubernetes branches that are needed by other k8s.io repos. 268 for _, repoRules := range p.reposRules { 269 for _, branchRule := range repoRules.srcToDst { 270 src := branchRule.src 271 // we assume src.repo is always kubernetes 272 cmd := exec.Command("git", "branch", "-f", src.branch, fmt.Sprintf("origin/%s", src.branch)) 273 cmd.Dir = filepath.Join(p.k8sIOPath, "kubernetes") 274 output, err := cmd.CombinedOutput() 275 p.plog.Infof("%s", output) 276 if err == nil { 277 continue 278 } 279 // probably the error is because we cannot do `git branch -f` while 280 // current branch is src.branch, so try `git reset --hard` instead. 281 cmd = exec.Command("git", "reset", "--hard", fmt.Sprintf("origin/%s", src.branch)) 282 cmd.Dir = filepath.Join(p.k8sIOPath, "kubernetes") 283 output, err = cmd.CombinedOutput() 284 p.plog.Infof("%s", output) 285 if err != nil { 286 return err 287 } 288 } 289 } 290 return nil 291 } 292 293 // git clone dstURL to dst if dst doesn't exist yet. 294 func (p *PublisherMunger) ensureCloned(dst string, dstURL string) error { 295 if _, err := os.Stat(dst); err == nil { 296 return nil 297 } 298 299 err := exec.Command("mkdir", "-p", dst).Run() 300 if err != nil { 301 return err 302 } 303 err = exec.Command("git", "clone", dstURL, dst).Run() 304 return err 305 } 306 307 // constructs all the repos, but does not push the changes to remotes. 308 func (p *PublisherMunger) construct() error { 309 kubernetesRemote := filepath.Join(p.k8sIOPath, "kubernetes", ".git") 310 for _, repoRules := range p.reposRules { 311 // clone the destination repo 312 dstDir := filepath.Join(p.k8sIOPath, repoRules.dstRepo, "") 313 dstURL := fmt.Sprintf("https://github.com/%s/%s.git", p.githubConfig.Org, repoRules.dstRepo) 314 if err := p.ensureCloned(dstDir, dstURL); err != nil { 315 p.plog.Errorf("%v", err) 316 return err 317 } 318 p.plog.Infof("Successfully ensured %s exists", dstDir) 319 if err := os.Chdir(dstDir); err != nil { 320 return err 321 } 322 // construct branches 323 formatDeps := func(deps []coordinate) string { 324 var depStrings []string 325 for _, dep := range deps { 326 depStrings = append(depStrings, fmt.Sprintf("%s:%s", dep.repo, dep.branch)) 327 } 328 return strings.Join(depStrings, ",") 329 } 330 331 for _, branchRule := range repoRules.srcToDst { 332 cmd := exec.Command(repoRules.publishScript, branchRule.src.branch, branchRule.dst.branch, formatDeps(branchRule.deps), kubernetesRemote) 333 output, err := cmd.CombinedOutput() 334 p.plog.Infof("%s", output) 335 if err != nil { 336 return err 337 } 338 p.plog.Infof("Successfully constructed %s", branchRule.dst) 339 } 340 } 341 return nil 342 } 343 344 // publish to remotes. 345 func (p *PublisherMunger) publish() error { 346 // NOTE: because some repos depend on each other, e.g., client-go depends on 347 // apimachinery, they should be published atomically, but it's not supported 348 // by github. 349 for _, repoRules := range p.reposRules { 350 dstDir := filepath.Join(p.k8sIOPath, repoRules.dstRepo, "") 351 if err := os.Chdir(dstDir); err != nil { 352 return err 353 } 354 for _, branchRule := range repoRules.srcToDst { 355 cmd := exec.Command("/publish_scripts/push.sh", p.githubConfig.Token(), branchRule.dst.branch) 356 output, err := cmd.CombinedOutput() 357 p.plog.Infof("%s", output) 358 if err != nil { 359 return err 360 } 361 } 362 } 363 return nil 364 } 365 366 // EachLoop is called at the start of every munge loop 367 func (p *PublisherMunger) EachLoop() error { 368 buf := bytes.NewBuffer(nil) 369 p.plog = NewPublisherLog(buf) 370 371 if err := p.updateKubernetes(); err != nil { 372 p.plog.Errorf("%v", err) 373 p.plog.Flush() 374 return err 375 } 376 if err := p.construct(); err != nil { 377 p.plog.Errorf("%v", err) 378 p.plog.Flush() 379 return err 380 } 381 if err := p.publish(); err != nil { 382 p.plog.Errorf("%v", err) 383 p.plog.Flush() 384 return err 385 } 386 return nil 387 } 388 389 // RegisterOptions registers options for this munger; returns any that require a restart when changed. 390 func (p *PublisherMunger) RegisterOptions(opts *options.Options) sets.String { 391 opts.RegisterUpdateCallback(func(changed sets.String) error { 392 // project is used to init repo rules so if it changes we must re-initialize. 393 if changed.Has("project") { 394 return p.Initialize(p.githubConfig, p.features) 395 } 396 return nil 397 }) 398 return nil 399 } 400 401 // Munge is the workhorse the will actually make updates to the PR 402 func (p *PublisherMunger) Munge(obj *github.MungeObject) {}