github.com/jenkins-x/jx/v2@v2.1.155/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/jenkins-x/jx/v2/pkg/kube/naming" 13 "github.com/jenkins-x/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 }