func setDifference(this, other map[string]repos.TrackedRepository) map[repos.TrackedRepository]bool {
	diff := make(map[repos.TrackedRepository]bool)
	for k, v := range this {
		if _, ok := other[k]; !ok {
			diff[v] = true
		}
	}
	return diff
}
Returns the difference between this set
and other. The returned set will contain
all elements of this set that are not also
elements of other.
package sprvsr
import (
	"context"
	"fmt"
	"net/http"
	"strings"
	"sync"
	"github.com/GoogleCloudPlatform/devrel-services/repos"
	"github.com/gorilla/mux"
	"github.com/sirupsen/logrus"
	"github.com/urfave/negroni"
	appsv1 "k8s.io/api/apps/v1"
	apiv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)
const (
	labelgenkeyfmt = "%v-sprvsr-autogen"
	labelgenvalue  = "true"
)
var _ Supervisor = &k8supervisor{}
type k8supervisor struct {
	mu        sync.RWMutex
	log       *logrus.Logger
	clientset kubernetes.Interface
	serviceNamer      ServiceNamer
	deploymentNamer   DeploymentNamer
	serviceBuilder    ServiceBuilder
	deploymentPrep    DeploymentPrep
	deploymentCheck   DeploymentCheck
	deploymentBuilder DeploymentBuilder
	repoList repos.RepoList
	labelgenkey string
}
type ServiceNamer func(repos.TrackedRepository) (string, error)
type DeploymentNamer func(repos.TrackedRepository) (string, error)
type DeploymentBuilder func(repos.TrackedRepository) (*appsv1.Deployment, error)
type ServiceBuilder func(repos.TrackedRepository) (*apiv1.Service, error)
type DeploymentPrep func(repos.TrackedRepository) error
type DeploymentCheck func(repos.TrackedRepository) bool
type K8sConfiguration struct {
	ServiceNamer      ServiceNamer
	DeploymentNamer   DeploymentNamer
	ServiceBuilder    ServiceBuilder
	DeploymentBuilder DeploymentBuilder
	PreDeploy         DeploymentPrep
	ShouldDeploy      DeploymentCheck
}
func NewK8sSupervisor(log *logrus.Logger, clientset kubernetes.Interface, kconfig K8sConfiguration,
	rl repos.RepoList,
	appid string) (Supervisor, error) {
	return newK8sSupervisor(log, clientset, kconfig, rl, appid)
}
func newK8sSupervisor(log *logrus.Logger, clientset kubernetes.Interface, kconfig K8sConfiguration,
	rl repos.RepoList,
	appid string) (*k8supervisor, error) {
	lblkey := fmt.Sprintf(labelgenkeyfmt, appid)
	return &k8supervisor{
		clientset:         clientset,
		log:               log,
		serviceNamer:      kconfig.ServiceNamer,
		deploymentNamer:   kconfig.DeploymentNamer,
		serviceBuilder:    kconfig.ServiceBuilder,
		deploymentBuilder: kconfig.DeploymentBuilder,
		deploymentPrep:    kconfig.PreDeploy,
		deploymentCheck:   kconfig.ShouldDeploy,
		repoList:          rl,
		labelgenkey:       lblkey,
	}, nil
}
func (s *k8supervisor) Supervise(address string, handle func(error)) error {
	go s.updateCorpusRepoList(context.Background(), handle)
	s.log.Debugf("creating router")
	r := mux.NewRouter()
	s.log.Debugf("handling update")
	r.HandleFunc("/update", func(w http.ResponseWriter, r *http.Request) {
		s.updateCorpusRepoList(r.Context(), handle)
	}).Methods("GET", "POST")
	s.log.Debugf("handling healthz")
	r.HandleFunc("/_healthz", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(200)
		w.Write([]byte("ok"))
	})
	n := negroni.New()
	l := negroni.NewLogger()
	n.Use(l)
	n.Use(negroni.NewRecovery())
	n.UseHandler(r)
	return http.ListenAndServe(address, n)
}
func (s *k8supervisor) updateCorpusRepoList(ctx context.Context, handle func(error)) {
	s.mu.RLock()
	changed, err := s.repoList.UpdateTrackedRepos(ctx)
	if err != nil {
		handle(err)
		s.mu.RUnlock()
		return
	}
	s.mu.RUnlock()
	if !changed {
		s.log.Debug("skipping updating corpus repo list. unchanged")
		return
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	trackedRepos := s.repoList.GetTrackedRepos()
	filteredRepos := make([]repos.TrackedRepository, 0)
	for _, tr := range trackedRepos {
		if !s.deploymentCheck(tr) {
			continue
		}
		filteredRepos = append(filteredRepos, tr)
	}
	s.log.Debugf("got n tracked repos: %v", len(filteredRepos))
	trSet := make(map[string]repos.TrackedRepository)
	for _, tr := range filteredRepos {
		d, err := s.deploymentBuilder(tr)
		if err != nil {
			handle(err)
			return
		}
		imgs := make([]string, 0)
		for _, i := range d.Spec.Template.Spec.Containers {
			imgs = append(imgs, i.Image)
		}
		name := fmt.Sprintf("%v-%v-%v", tr.String(), strings.Join(imgs, "/"), tr.DefaultBranch)
		trSet[name] = tr
	}
	s.log.Debugf("trSet: %v", trSet)
	tServicesSet := make(map[string]repos.TrackedRepository)
	for _, tr := range filteredRepos {
		tServicesSet[tr.String()] = tr
	}
	s.log.Debugf("tServicesSet: %v", tServicesSet)
	deploymentsSet := make(map[string]repos.TrackedRepository)
	servicesSet := make(map[string]repos.TrackedRepository)
	ns := apiv1.NamespaceDefault
	labelSelector := fmt.Sprintf("%v=%v", s.labelgenkey, labelgenvalue)
	deployments, err := s.clientset.AppsV1().Deployments(ns).List(metav1.ListOptions{
		LabelSelector: labelSelector,
	})
	if err != nil {
		handle(err)
		return
	}
	for _, deployment := range deployments.Items {
		s.log.Infof("processing deployment: %v. Labels: %v", deployment.Name, deployment.Labels)
		if _, ok := deployment.Labels["owner"]; !ok {
			continue
		}
		if _, ok := deployment.Labels["repository"]; !ok {
			continue
		}
		o := deployment.Labels["owner"]
		r := deployment.Labels["repository"]
		tr := repos.TrackedRepository{
			Owner: o,
			Name:  r,
		}
		if db, ok := deployment.Labels["branch"]; ok {
			tr.DefaultBranch = db
		}
		imgs := make([]string, 0)
		for _, c := range deployment.Spec.Template.Spec.Containers {
			imgs = append(imgs, c.Image)
		}
		name := fmt.Sprintf("%v-%v-%v", tr.String(), strings.Join(imgs, "/"), tr.DefaultBranch)
		deploymentsSet[name] = tr
	}
	s.log.Debugf("have deployments from k8s: %v", deploymentsSet)
	services, err := s.clientset.CoreV1().Services(ns).List(metav1.ListOptions{
		LabelSelector: labelSelector,
	})
	if err != nil {
		handle(err)
		return
	}
	for _, service := range services.Items {
		if _, ok := service.Labels["owner"]; !ok {
			continue
		}
		if _, ok := service.Labels["repository"]; !ok {
			continue
		}
		o := service.Labels["owner"]
		r := service.Labels["repository"]
		tr := repos.TrackedRepository{
			Owner: o,
			Name:  r,
		}
		servicesSet[tr.String()] = tr
	}
	s.log.Debugf("have services from k8s: %v", servicesSet)
	servicesToDelete := setDifference(servicesSet, tServicesSet)
	s.log.Debugf("have services to delete: %v", servicesToDelete)
	for td := range servicesToDelete {
		sn, err := s.serviceNamer(td)
		if err != nil {
			handle(err)
			continue
		}
		err = s.clientset.CoreV1().Services(ns).Delete(sn, &metav1.DeleteOptions{})
		if err != nil {
			handle(err)
		}
	}
	deploymentsToDelete := setDifference(deploymentsSet, trSet)
	s.log.Debugf("have deployments to delete: %v", deploymentsToDelete)
	for td := range deploymentsToDelete {
		dn, err := s.deploymentNamer(td)
		if err != nil {
			handle(err)
			continue
		}
		err = s.clientset.AppsV1().Deployments(ns).Delete(dn, &metav1.DeleteOptions{})
		if err != nil {
			handle(err)
		}
	}
	deploymentsToAdd := setDifference(trSet, deploymentsSet)
	s.log.Debugf("have deployments to add: %v", deploymentsToAdd)
	for ta := range deploymentsToAdd {
		if err := s.deploymentPrep(ta); err != nil {
			handle(err)
			continue
		}
		if err := createDeployment(s.clientset, ns, s.labelgenkey, s.deploymentBuilder, s.deploymentNamer, ta); err != nil {
			handle(err)
		}
	}
	servicesToAdd := setDifference(tServicesSet, servicesSet)
	s.log.Debugf("have services to add: %v", servicesToAdd)
	for ta := range servicesToAdd {
		if err := createService(s.clientset, ns, s.labelgenkey, s.serviceBuilder, ta); err != nil {
			handle(err)
		}
		s.log.Debugf("created service for %v", ta)
	}
}
func createDeployment(cs kubernetes.Interface, ns string, lblkey string, db DeploymentBuilder, dn DeploymentNamer, ta repos.TrackedRepository) error {
	d, err := db(ta)
	if err != nil {
		return err
	}
	dname, err := dn(ta)
	if err != nil {
		return err
	}
	if d.ObjectMeta.Labels == nil {
		d.ObjectMeta.Labels = make(map[string]string, 0)
	}
	d.ObjectMeta.Labels[lblkey] = labelgenvalue
	d.ObjectMeta.Labels["owner"] = ta.Owner
	d.ObjectMeta.Labels["repository"] = ta.Name
	defaultBranch := "master"
	if ta.DefaultBranch != "" {
		defaultBranch = ta.DefaultBranch
	}
	d.ObjectMeta.Labels["branch"] = defaultBranch
	if d.Spec.Template.ObjectMeta.Labels == nil {
		d.Spec.Template.ObjectMeta.Labels = make(map[string]string, 0)
	}
	d.Spec.Template.ObjectMeta.Labels[lblkey] = labelgenvalue
	d.Spec.Template.ObjectMeta.Labels["owner"] = ta.Owner
	d.Spec.Template.ObjectMeta.Labels["repository"] = ta.Name
	_, err = cs.AppsV1().Deployments(ns).Create(d)
	if err != nil && err.Error() == fmt.Sprintf("deployments.apps: \"%v\" already exists", dname) {
		err = nil
	}
	return err
}
func createService(cs kubernetes.Interface, ns string, labelgenkey string, sb ServiceBuilder, ta repos.TrackedRepository) error {
	svc, err := sb(ta)
	if err != nil {
		return err
	}
	if svc.ObjectMeta.Labels == nil {
		svc.ObjectMeta.Labels = make(map[string]string, 0)
	}
	svc.ObjectMeta.Labels[labelgenkey] = labelgenvalue
	svc.ObjectMeta.Labels["owner"] = ta.Owner
	svc.ObjectMeta.Labels["repository"] = ta.Name
	_, err = cs.CoreV1().Services(ns).Create(svc)
	return err
}