github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/kube/sourcerepositories.go (about)

     1  package kube
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"reflect"
     7  	"strings"
     8  
     9  	jenkinsio "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io"
    10  	v1 "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io/v1"
    11  	"github.com/jenkins-x/jx-api/pkg/client/clientset/versioned"
    12  	"github.com/olli-ai/jx/v2/pkg/kube/naming"
    13  	"github.com/olli-ai/jx/v2/pkg/util"
    14  	"github.com/pkg/errors"
    15  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  )
    18  
    19  // GetRepositoryGitURL returns the git repository clone URL
    20  func GetRepositoryGitURL(s *v1.SourceRepository) (string, error) {
    21  	spec := s.Spec
    22  	provider := spec.Provider
    23  	owner := spec.Org
    24  	repo := spec.Repo
    25  	if spec.HTTPCloneURL == "" {
    26  		if spec.ProviderKind == "bitbucketserver" {
    27  			provider = util.UrlJoin(provider, "scm")
    28  		}
    29  		if provider == "" {
    30  			return spec.HTTPCloneURL, fmt.Errorf("missing provider in SourceRepository %s", s.Name)
    31  		}
    32  		if owner == "" {
    33  			return spec.HTTPCloneURL, fmt.Errorf("missing org in SourceRepository %s", s.Name)
    34  		}
    35  		if repo == "" {
    36  			return spec.HTTPCloneURL, fmt.Errorf("missing repo in SourceRepository %s", s.Name)
    37  		}
    38  		spec.HTTPCloneURL = util.UrlJoin(provider, owner, repo) + ".git"
    39  	}
    40  	return spec.HTTPCloneURL, nil
    41  }
    42  
    43  // FindSourceRepositoryWithoutProvider returns a SourceRepository for the given namespace, owner and repo name.
    44  // If no SourceRepository is found, return nil.
    45  func FindSourceRepositoryWithoutProvider(jxClient versioned.Interface, ns string, owner string, name string) (*v1.SourceRepository, error) {
    46  	return FindSourceRepository(jxClient, ns, owner, name, "")
    47  }
    48  
    49  // findSourceRepositoryByLabels returns a SourceRepository matching the given label selector, if it exists.
    50  func findSourceRepositoryByLabels(jxClient versioned.Interface, ns string, labelSelector string) (*v1.SourceRepository, error) {
    51  	repos, err := jxClient.JenkinsV1().SourceRepositories(ns).List(metav1.ListOptions{
    52  		LabelSelector: labelSelector,
    53  	})
    54  	if err != nil {
    55  		return nil, errors.Wrapf(err, "listing SourceRepositorys matching label selector %s in namespace %s", labelSelector, ns)
    56  	}
    57  	if repos != nil && len(repos.Items) == 1 {
    58  		return &repos.Items[0], nil
    59  	}
    60  	return nil, nil
    61  }
    62  
    63  // FindSourceRepository returns a SourceRepository for the given namespace, owner, repo name, and (optional) provider name.
    64  // If no SourceRepository is found, return nil.
    65  func FindSourceRepository(jxClient versioned.Interface, ns string, owner string, name string, providerName string) (*v1.SourceRepository, error) {
    66  	// Look up by resource name is retained for compatibility with SourceRepositorys created before they were always created with labels
    67  	resourceName := naming.ToValidName(owner + "-" + name)
    68  	repo, err := jxClient.JenkinsV1().SourceRepositories(ns).Get(resourceName, metav1.GetOptions{})
    69  	if err != nil {
    70  		if apierrors.IsNotFound(err) {
    71  			labelSelector := fmt.Sprintf("%s=%s,%s=%s", v1.LabelOwner, owner, v1.LabelRepository, name)
    72  			if providerName != "" {
    73  				labelSelector += fmt.Sprintf(",%s=%s", v1.LabelProvider, providerName)
    74  			}
    75  
    76  			return findSourceRepositoryByLabels(jxClient, ns, labelSelector)
    77  		}
    78  		return nil, errors.Wrapf(err, "getting SourceRepository %s in namespace %s", resourceName, ns)
    79  	}
    80  	return repo, nil
    81  }
    82  
    83  // GetOrCreateSourceRepositoryCallback gets or creates the SourceRepository for the given repository name and
    84  // organisation invoking the given callback to modify the resource before create/udpate
    85  func GetOrCreateSourceRepositoryCallback(jxClient versioned.Interface, ns string, name, organisation, providerURL string, callback func(*v1.SourceRepository)) (*v1.SourceRepository, error) {
    86  	resourceName := naming.ToValidName(organisation + "-" + name)
    87  
    88  	repositories := jxClient.JenkinsV1().SourceRepositories(ns)
    89  
    90  	providerName := ToProviderName(providerURL)
    91  
    92  	foundSr, err := FindSourceRepository(jxClient, ns, organisation, name, providerName)
    93  	if err != nil {
    94  		return nil, errors.Wrapf(err, "failed to find existing SourceRepository")
    95  	}
    96  
    97  	// If we did not find an existing SourceRepository for this org/repo, create one
    98  	if foundSr == nil {
    99  		return createSourceRepositoryCallback(jxClient, ns, name, organisation, providerURL, callback)
   100  	}
   101  
   102  	// If we did find a SourceRepository, use that as our basis and see if we need to update it.
   103  	description := fmt.Sprintf("Imported application for %s/%s", organisation, name)
   104  
   105  	srCopy := foundSr.DeepCopy()
   106  	srCopy.Name = foundSr.Name
   107  	srCopy.Spec.Description = description
   108  	srCopy.Spec.Org = organisation
   109  	srCopy.Spec.Provider = providerURL
   110  	srCopy.Spec.Repo = name
   111  
   112  	srCopy.Labels = map[string]string{}
   113  	for k, v := range foundSr.Labels {
   114  		srCopy.Labels[k] = v
   115  	}
   116  	srCopy.Labels[v1.LabelProvider] = providerName
   117  	srCopy.Labels[v1.LabelOwner] = organisation
   118  	srCopy.Labels[v1.LabelRepository] = name
   119  
   120  	if callback != nil {
   121  		callback(srCopy)
   122  	}
   123  	srCopy.Sanitize()
   124  
   125  	// If we don't need to update the found SourceRepository, return it.
   126  	if reflect.DeepEqual(srCopy.Spec, foundSr.Spec) && reflect.DeepEqual(srCopy.Labels, foundSr.Labels) {
   127  		return foundSr, nil
   128  	}
   129  
   130  	// Otherwise, update the SourceRepository and return it.
   131  	answer, err := repositories.Update(srCopy)
   132  	if err != nil {
   133  		return answer, errors.Wrapf(err, "failed to update SourceRepository %s", resourceName)
   134  	}
   135  	answer, err = repositories.Get(foundSr.Name, metav1.GetOptions{})
   136  	if err != nil {
   137  		return answer, errors.Wrapf(err, "failed to get SourceRepository %s", resourceName)
   138  	}
   139  
   140  	return answer, nil
   141  }
   142  
   143  // GetOrCreateSourceRepository gets or creates the SourceRepository for the given repository name and organisation
   144  func GetOrCreateSourceRepository(jxClient versioned.Interface, ns string, name, organisation, providerURL string) (*v1.SourceRepository, error) {
   145  	return GetOrCreateSourceRepositoryCallback(jxClient, ns, name, organisation, providerURL, nil)
   146  }
   147  
   148  // ToProviderName takes the git URL and converts it to a provider name which can be used as a label selector
   149  func ToProviderName(gitURL string) string {
   150  	if gitURL == "" {
   151  		return ""
   152  	}
   153  	u, err := url.Parse(gitURL)
   154  	if err == nil {
   155  		host := strings.TrimSuffix(u.Host, ".com")
   156  		return naming.ToValidName(host)
   157  	}
   158  	idx := strings.Index(gitURL, "://")
   159  	if idx > 0 {
   160  		gitURL = gitURL[idx+3:]
   161  	}
   162  	gitURL = strings.TrimSuffix(gitURL, "/")
   163  	gitURL = strings.TrimSuffix(gitURL, ".com")
   164  	return naming.ToValidName(gitURL)
   165  }
   166  
   167  // createSourceRepositoryCallback creates a repo, returning the created repo and an error if it couldn't be created
   168  func createSourceRepositoryCallback(client versioned.Interface, namespace string, name, organisation, providerURL string, callback func(*v1.SourceRepository)) (*v1.SourceRepository, error) {
   169  	resourceName := naming.ToValidName(organisation + "-" + name)
   170  
   171  	description := fmt.Sprintf("Imported application for %s/%s", organisation, name)
   172  
   173  	providerName := ToProviderName(providerURL)
   174  	labels := map[string]string{
   175  		v1.LabelProvider:   providerName,
   176  		v1.LabelOwner:      organisation,
   177  		v1.LabelRepository: name,
   178  	}
   179  
   180  	sr := &v1.SourceRepository{
   181  		TypeMeta: metav1.TypeMeta{
   182  			Kind:       "SourceRepository",
   183  			APIVersion: jenkinsio.GroupName + "/" + jenkinsio.Version,
   184  		},
   185  		ObjectMeta: metav1.ObjectMeta{
   186  			Name:   resourceName,
   187  			Labels: labels,
   188  		},
   189  		Spec: v1.SourceRepositorySpec{
   190  			Description:  description,
   191  			Org:          organisation,
   192  			Provider:     providerURL,
   193  			ProviderName: providerName,
   194  			Repo:         name,
   195  		},
   196  	}
   197  	if callback != nil {
   198  		callback(sr)
   199  	}
   200  	sr.Sanitize()
   201  	answer, err := client.JenkinsV1().SourceRepositories(namespace).Create(sr)
   202  	if err != nil {
   203  		return nil, errors.Wrapf(err, "failed to create new SourceRepository for organisation %s and repository %s", organisation, name)
   204  	}
   205  
   206  	return answer, nil
   207  }
   208  
   209  // IsRemoteEnvironmentRepository returns true if the given repository is a remote environment
   210  func IsRemoteEnvironmentRepository(environments map[string]*v1.Environment, repository *v1.SourceRepository) bool {
   211  	gitURL, err := GetRepositoryGitURL(repository)
   212  	if err != nil {
   213  		return false
   214  	}
   215  	u2 := gitURL + ".git"
   216  
   217  	for _, env := range environments {
   218  		if env.Spec.Kind != v1.EnvironmentKindTypePermanent {
   219  			continue
   220  		}
   221  		if env.Spec.Source.URL == gitURL || env.Spec.Source.URL == u2 {
   222  			if env.Spec.RemoteCluster {
   223  				return true
   224  			}
   225  		}
   226  	}
   227  	return false
   228  }
   229  
   230  // IsIncludedInTheGivenEnvs returns true if the given repository is an environment repository
   231  func IsIncludedInTheGivenEnvs(environments map[string]*v1.Environment, repository *v1.SourceRepository) bool {
   232  	gitURL, err := GetRepositoryGitURL(repository)
   233  	if err != nil {
   234  		return false
   235  	}
   236  	u2 := gitURL + ".git"
   237  
   238  	for _, env := range environments {
   239  		if env.Spec.Source.URL == gitURL || env.Spec.Source.URL == u2 {
   240  			return true
   241  		}
   242  	}
   243  	return false
   244  }