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) {}