diff --git a/api/v1alpha1/applicationset_types.go b/api/v1alpha1/applicationset_types.go index 19c5bdc2..3c4c29a8 100644 --- a/api/v1alpha1/applicationset_types.go +++ b/api/v1alpha1/applicationset_types.go @@ -21,6 +21,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// Utility struct for a reference to a secret key. +type SecretRef struct { + SecretName string `json:"secretName"` + Key string `json:"key"` +} + // ApplicationSet is a set of Application resources // +kubebuilder:object:root=true // +kubebuilder:resource:path=applicationsets,shortName=appset;appsets @@ -65,9 +71,10 @@ type ApplicationSetTemplateMeta struct { // ApplicationSetGenerator include list item info type ApplicationSetGenerator struct { - List *ListGenerator `json:"list,omitempty"` - Clusters *ClusterGenerator `json:"clusters,omitempty"` - Git *GitGenerator `json:"git,omitempty"` + List *ListGenerator `json:"list,omitempty"` + Clusters *ClusterGenerator `json:"clusters,omitempty"` + Git *GitGenerator `json:"git,omitempty"` + SCMProvider *SCMProviderGenerator `json:"scmProvider,omitempty"` } // ListGenerator include items info @@ -114,6 +121,47 @@ type GitFileGeneratorItem struct { Path string `json:"path"` } +// SCMProviderGenerator defines a generator that scrapes a SCMaaS API to find candidate repos. +type SCMProviderGenerator struct { + // Which provider to use and config for it. + Github *SCMProviderGeneratorGithub `json:"github,omitempty"` + // TODO other providers. + // Filters for which repos should be considered. + Filters []SCMProviderGeneratorFilter `json:"filters,omitempty"` + // Which protocol to use for the SCM URL. Default is provider-specific but ssh if possible. Not all providers + // necessarily support all protocols. + CloneProtocol string `json:"cloneProtocol,omitempty"` + // Standard parameters. + RequeueAfterSeconds *int64 `json:"requeueAfterSeconds,omitempty"` + Template ApplicationSetTemplate `json:"template,omitempty"` +} + +// SCMProviderGeneratorGithub defines a connection info specific to GitHub. +type SCMProviderGeneratorGithub struct { + // GitHub org to scan. Required. + Organization string `json:"organization"` + // The GitHub API URL to talk to. If blank, use https://api.github.com/. + API string `json:"api,omitempty"` + // Authentication token reference. + TokenRef *SecretRef `json:"tokenRef,omitempty"` + // Scan all branches instead of just the default branch. + AllBranches bool `json:"allBranches,omitempty"` +} + +// SCMProviderGeneratorFilter is a single repository filter. +// If multiple filter types are set on a single struct, they will be AND'd together. All filters must +// pass for a repo to be included. +type SCMProviderGeneratorFilter struct { + // A regex for repo names. + RepositoryMatch *string `json:"repositoryMatch,omitempty"` + // An array of paths, all of which must exist. + PathsExist []string `json:"pathsExist,omitempty"` + // A regex which must match at least one label. + LabelMatch *string `json:"labelMatch,omitempty"` + // A regex which must match the branch name. + BranchMatch *string `json:"branchMatch,omitempty"` +} + // ApplicationSetStatus defines the observed state of ApplicationSet type ApplicationSetStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 4353e89e..d3a74ac5 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -69,6 +69,11 @@ func (in *ApplicationSetGenerator) DeepCopyInto(out *ApplicationSetGenerator) { *out = new(GitGenerator) (*in).DeepCopyInto(*out) } + if in.SCMProvider != nil { + in, out := &in.SCMProvider, &out.SCMProvider + *out = new(SCMProviderGenerator) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSetGenerator. @@ -346,3 +351,106 @@ func (in *ListGeneratorElement) DeepCopy() *ListGeneratorElement { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SCMProviderGenerator) DeepCopyInto(out *SCMProviderGenerator) { + *out = *in + if in.Github != nil { + in, out := &in.Github, &out.Github + *out = new(SCMProviderGeneratorGithub) + (*in).DeepCopyInto(*out) + } + if in.Filters != nil { + in, out := &in.Filters, &out.Filters + *out = make([]SCMProviderGeneratorFilter, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.RequeueAfterSeconds != nil { + in, out := &in.RequeueAfterSeconds, &out.RequeueAfterSeconds + *out = new(int64) + **out = **in + } + in.Template.DeepCopyInto(&out.Template) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SCMProviderGenerator. +func (in *SCMProviderGenerator) DeepCopy() *SCMProviderGenerator { + if in == nil { + return nil + } + out := new(SCMProviderGenerator) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SCMProviderGeneratorFilter) DeepCopyInto(out *SCMProviderGeneratorFilter) { + *out = *in + if in.RepositoryMatch != nil { + in, out := &in.RepositoryMatch, &out.RepositoryMatch + *out = new(string) + **out = **in + } + if in.PathsExist != nil { + in, out := &in.PathsExist, &out.PathsExist + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.LabelMatch != nil { + in, out := &in.LabelMatch, &out.LabelMatch + *out = new(string) + **out = **in + } + if in.BranchMatch != nil { + in, out := &in.BranchMatch, &out.BranchMatch + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SCMProviderGeneratorFilter. +func (in *SCMProviderGeneratorFilter) DeepCopy() *SCMProviderGeneratorFilter { + if in == nil { + return nil + } + out := new(SCMProviderGeneratorFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SCMProviderGeneratorGithub) DeepCopyInto(out *SCMProviderGeneratorGithub) { + *out = *in + if in.TokenRef != nil { + in, out := &in.TokenRef, &out.TokenRef + *out = new(SecretRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SCMProviderGeneratorGithub. +func (in *SCMProviderGeneratorGithub) DeepCopy() *SCMProviderGeneratorGithub { + if in == nil { + return nil + } + out := new(SCMProviderGeneratorGithub) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretRef) DeepCopyInto(out *SecretRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretRef. +func (in *SecretRef) DeepCopy() *SecretRef { + if in == nil { + return nil + } + out := new(SecretRef) + in.DeepCopyInto(out) + return out +} diff --git a/docs/Generators.md b/docs/Generators.md index 4cdff460..9ff20a94 100644 --- a/docs/Generators.md +++ b/docs/Generators.md @@ -369,3 +369,124 @@ spec: Any `config.json` files found under the `cluster-config` directory will be parameterized based on the `path` wildcard pattern specified. Within each file JSON fields are flattened into key/value pairs, with this ApplicationSet example using the `cluster.address` as `cluster.name` parameters in the template. As with other generators, clusters *must* already be defined within Argo CD, in order to generate Applications for them. + +## SCM Provider Generator + +The SCMProvider generator uses the API of an SCMaaS provider to discover repositories. This fits well with many repos following the same GitOps layout patterns such as microservices. + +Support is currently limited to GitHub, PRs are welcome to add more SCM providers. + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: myapps +spec: + generators: + - scmProvider: + # Which protocol to clone using. + cloneProtocol: ssh + # See below for provider specific options. + github: + # ... +``` + +* `cloneProtocol`: Which protocol to use for the SCM URL. Default is provider-specific but ssh if possible. Not all providers necessarily support all protocols, see provider documentation below for available options. + +### GitHub + +The GitHub mode uses the GitHub API to scan and organization in either github.com or GitHub Enterprise. + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: myapps +spec: + generators: + - scmProvider: + github: + # The GitHub organization to scan. + organization: myorg + # For GitHub Enterprise: + api: https://git.example.com/ + # If true, scan every branch of every repository. If false, scan only the default branch. Defaults to false. + allBranches: true + # Reference to a Secret containing an access token. (optional) + tokenRef: + secretName: github-token + key: token + template: + # ... +``` + +* `organization`: Required name of the GitHub organization to scan. If you have multiple orgs, use multiple generators. +* `api`: If using GitHub Enterprise, the URL to access it. +* `allBranches`: By default (false) the template will only be evaluated for the default branch of each repo. If this is true, every branch of every repository will be passed to the filters. If using this flag, you likely want to use a `branchMatch` filter. +* `tokenRef`: A Secret name and key containing the GitHub access token to use for requests. If not specified, will make anonymous requests which have a lower rate limit and can only see public repositories. + +For label filtering, the repository topics are used. + +Available clone protocols are `ssh` and `https`. + +### Filters + +Filters allow selecting which repositories to generate for. Each filter can declare one or more conditions, all of which must pass. If multiple filters are present, any can match for a repository to be included. If no filters are specified, all repositories will be processed. + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: myapps +spec: + generators: + - scmProvider: + filters: + # Include any repository starting with "myapp" AND including a Kustomize config AND labeled with "deploy-ok" ... + - repositoryMatch: ^myapp + pathsExist: [kubernetes/kustomization.yaml] + labelMatch: deploy-ok + # ... OR any repository starting with "otherapp" AND a Helm folder. + - repositoryMatch: ^otherapp + pathsExist: [helm] + template: + # ... +``` + +* `repositoryMatch`: A regexp matched against the repository name. +* `pathsExist`: An array of paths within the repository that must exist. Can be a file or directory, but do not include the trailing `/` for directories. +* `labelMatch`: A regexp matched against repository labels. If any label matches, the repository is included. +* `branchMatch`: A regexp matched against branch names. + +### Template + +As with all generators, several keys are available for replacement in the generated application. + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: myapps +spec: + generators: + - scmProvider: + # ... + template: + metadata: + name: '{{ repository }}' + spec: + source: + repoURL: '{{ url }}' + targetRevision: '{{ branch }}' + path: kubernetes/ + project: default + destination: + server: https://kubernetes.default.svc + namespace: default +``` + +* `organization`: The name of the organization the repository is in. +* `repository`: The name of the repository. +* `url`: The clone URL for the repository. +* `branch`: The default branch of the repository. + diff --git a/examples/scm-provider-generator/scm-provider-example.yaml b/examples/scm-provider-generator/scm-provider-example.yaml new file mode 100644 index 00000000..24d8ba41 --- /dev/null +++ b/examples/scm-provider-generator/scm-provider-example.yaml @@ -0,0 +1,24 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: guestbook +spec: + generators: + - scmProvider: + github: + organization: argoproj + cloneProtocol: https + filters: + - repositoryMatch: example-apps + template: + metadata: + name: '{{ repository }}-guestbook' + spec: + project: "default" + source: + repoURL: '{{ url }}' + targetRevision: '{{ branch }}' + path: guestbook + destination: + server: https://kubernetes.default.svc + namespace: guestbook diff --git a/go.mod b/go.mod index cc3a0358..0614b4f8 100644 --- a/go.mod +++ b/go.mod @@ -7,12 +7,14 @@ require ( github.com/argoproj/gitops-engine v0.2.1 github.com/argoproj/pkg v0.2.0 github.com/go-logr/logr v0.3.0 + github.com/google/go-github/v35 v35.0.0 github.com/imdario/mergo v0.3.10 github.com/jeremywohl/flatten v1.0.1 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.6.0 github.com/stretchr/testify v1.6.1 github.com/valyala/fasttemplate v1.2.1 + golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78 k8s.io/api v0.19.2 k8s.io/apimachinery v0.19.2 k8s.io/client-go v11.0.1-0.20190816222228-6d55c1b1f1ca+incompatible diff --git a/go.sum b/go.sum index 68c16b0d..056d9a52 100644 --- a/go.sum +++ b/go.sum @@ -6,12 +6,33 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v43.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= @@ -202,6 +223,7 @@ github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.9.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= @@ -283,12 +305,16 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= @@ -305,17 +331,28 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github/v35 v35.0.0 h1:oLrHdYkSQvbhN4gJihpEkTFKAZnIFgTCj1p/OlE4Os4= +github.com/google/go-github/v35 v35.0.0/go.mod h1:s0515YVTI+IMrDoy9Y4pHt9ShGpzHvHO8rZ7L7acgvs= github.com/google/go-jsonnet v0.17.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -623,7 +660,9 @@ github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0B github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/gopher-lua v0.0.0-20190115140932-732aa6820ec4/go.mod h1:fFiAh+CowNFr0NK5VASokuwKwkbacRmHsVA7Yb1Tqac= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -635,6 +674,8 @@ go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -676,7 +717,12 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200821190819-94841d0725da/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= @@ -688,8 +734,10 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -717,6 +765,7 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -725,10 +774,18 @@ golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201022231255-08b38378de70/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201024042810-be3efd7ff127 h1:pZPp9+iYUqwYKLjht0SDBbRCRK/9gAXDy7pz5fRDpjo= @@ -736,14 +793,17 @@ golang.org/x/net v0.0.0-20201024042810-be3efd7ff127/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78 h1:rPRtHfUb0UKZeZ6GH4K4Nt4YRbE9V1u+QZX5upZXqJQ= +golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -781,21 +841,31 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -836,16 +906,38 @@ golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200616133436-c1934b75d054 h1:HHeAlu5H9b71C+Fx0K+1dGgVFN1DM1/wz4aoGOA5qS8= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d h1:W07d4xkoAUSNOkOzdzXCdFGxT7o2rW4q8M34tB2i//k= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.1.0 h1:Phva6wqu+xR//Njw6iorylFFgn/z547tw5Ne3HZPQ+k= gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= @@ -858,9 +950,20 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.1-0.20200106000736-b8fc810ca6b5/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.1/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -878,12 +981,31 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.15.0 h1:Az/KuahOM4NAidTEuJCv/RonAA7rYsTPkqXVjr+8OOw= google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= @@ -936,8 +1058,10 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms= k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= k8s.io/apiextensions-apiserver v0.19.2 h1:oG84UwiDsVDu7dlsGQs5GySmQHCzMhknfhFExJMz9tA= @@ -994,6 +1118,8 @@ modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0= sigs.k8s.io/controller-runtime v0.7.0 h1:bU20IBBEPccWz5+zXpLnpVsgBYxqclaHu1pVDl/gEt8= sigs.k8s.io/controller-runtime v0.7.0/go.mod h1:pJ3YBrJiAqMAZKi6UVGuE98ZrroV1p+pIhoHsMm9wdU= diff --git a/main.go b/main.go index 2781e6e7..c3b3caaf 100644 --- a/main.go +++ b/main.go @@ -136,9 +136,10 @@ func main() { if err = (&controllers.ApplicationSetReconciler{ Generators: map[string]generators.Generator{ - "List": generators.NewListGenerator(), - "Clusters": generators.NewClusterGenerator(mgr.GetClient(), context.Background(), k8s, namespace), - "Git": generators.NewGitGenerator(services.NewArgoCDService(argoCDDB, argocdRepoServer)), + "List": generators.NewListGenerator(), + "Clusters": generators.NewClusterGenerator(mgr.GetClient(), context.Background(), k8s, namespace), + "Git": generators.NewGitGenerator(services.NewArgoCDService(argoCDDB, argocdRepoServer)), + "SCMProvider": generators.NewSCMProviderGenerator(mgr.GetClient()), }, Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("ApplicationSet"), diff --git a/manifests/crds/argoproj.io_applicationsets.yaml b/manifests/crds/argoproj.io_applicationsets.yaml index 04feb567..fb2dc646 100644 --- a/manifests/crds/argoproj.io_applicationsets.yaml +++ b/manifests/crds/argoproj.io_applicationsets.yaml @@ -1344,6 +1344,469 @@ spec: required: - elements type: object + scmProvider: + description: SCMProviderGenerator defines a generator that scrapes + a SCMaaS API to find candidate repos. + properties: + cloneProtocol: + description: Which protocol to use for the SCM URL. Default + is provider-specific but ssh if possible. Not all providers + necessarily support all protocols. + type: string + filters: + description: TODO other providers. Filters for which repos + should be considered. + items: + description: SCMProviderGeneratorFilter is a single repository + filter. If multiple filter types are set on a single + struct, they will be AND'd together. All filters must + pass for a repo to be included. + properties: + branchMatch: + description: A regex which must match the branch name. + type: string + labelMatch: + description: A regex which must match at least one + label. + type: string + pathsExist: + description: An array of paths, all of which must + exist. + items: + type: string + type: array + repositoryMatch: + description: A regex for repo names. + type: string + type: object + type: array + github: + description: Which provider to use and config for it. + properties: + allBranches: + description: Scan all branches instead of just the default + branch. + type: boolean + api: + description: The GitHub API URL to talk to. If blank, + use https://api.github.com/. + type: string + organization: + description: GitHub org to scan. Required. + type: string + tokenRef: + description: Authentication token reference. + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + requeueAfterSeconds: + description: Standard parameters. + format: int64 + type: integer + template: + description: ApplicationSetTemplate represents argocd ApplicationSpec + properties: + metadata: + description: ApplicationSetTemplateMeta represents the + Argo CD application fields that may be used for Applications + generated from the ApplicationSet (based on metav1.ObjectMeta) + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + description: ApplicationSpec represents desired application + state. Contains link to repository with application + definition and additional parameters link definition + revision. + properties: + destination: + description: Destination overrides the kubernetes + server and namespace defined in the environment + ksonnet app.yaml + properties: + name: + description: Name of the destination cluster + which can be used instead of server (url) + field + type: string + namespace: + description: Namespace overrides the environment + namespace value in the ksonnet app.yaml + type: string + server: + description: Server overrides the environment + server value in the ksonnet app.yaml + type: string + type: object + ignoreDifferences: + description: IgnoreDifferences controls resources + fields which should be ignored during comparison + items: + description: ResourceIgnoreDifferences contains + resource filter and list of json paths which + should be ignored during comparison with live + state. + properties: + group: + type: string + jsonPointers: + items: + type: string + type: array + kind: + type: string + name: + type: string + namespace: + type: string + required: + - jsonPointers + - kind + type: object + type: array + info: + description: Infos contains a list of useful information + (URLs, email addresses, and plain text) that relates + to the application + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + description: Project is a application project name. + Empty name means that application belongs to 'default' + project. + type: string + revisionHistoryLimit: + description: This limits this number of items kept + in the apps revision history. This should only + be changed in exceptional circumstances. Setting + to zero will store no history. This will reduce + storage used. Increasing will increase the space + used to store the history, so we do not recommend + increasing it. Default is 10. + format: int64 + type: integer + source: + description: Source is a reference to the location + ksonnet application definition + properties: + chart: + description: Chart is a Helm chart name + type: string + directory: + description: Directory holds path/directory + specific options + properties: + exclude: + type: string + jsonnet: + description: ApplicationSourceJsonnet holds + jsonnet specific options + properties: + extVars: + description: ExtVars is a list of Jsonnet + External Variables + items: + description: JsonnetVar is a jsonnet + variable + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search + dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet + Top-level Arguments + items: + description: JsonnetVar is a jsonnet + variable + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters + to the helm template + items: + description: HelmFileParameter is a file + parameter to a helm template + properties: + name: + description: Name is the name of the + helm parameter + type: string + path: + description: Path is the path value + for the helm parameter + type: string + type: object + type: array + parameters: + description: Parameters are parameters to + the helm template + items: + description: HelmParameter is a parameter + to a helm template + properties: + forceString: + description: ForceString determines + whether to tell Helm to interpret + booleans and numbers as strings + type: boolean + name: + description: Name is the name of the + helm parameter + type: string + value: + description: Value is the value for + the helm parameter + type: string + type: object + type: array + releaseName: + description: The Helm release name. If omitted + it will use the application name + type: string + valueFiles: + description: ValuesFiles is a list of Helm + value files to use when generating a template + items: + type: string + type: array + values: + description: Values is Helm values, typically + defined as a block + type: string + version: + description: Version is the Helm version + to use for templating with + type: string + type: object + ksonnet: + description: Ksonnet holds ksonnet specific + options + properties: + environment: + description: Environment is a ksonnet application + environment name + type: string + parameters: + description: Parameters are a list of ksonnet + component parameter override values + items: + description: KsonnetParameter is a ksonnet + component parameter + properties: + component: + type: string + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + kustomize: + description: Kustomize holds kustomize specific + options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations adds additional + kustomize commonAnnotations + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels adds additional + kustomize commonLabels + type: object + images: + description: Images are kustomize image + overrides + items: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended + to resources for kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended + to resources for kustomize apps + type: string + version: + description: Version contains optional Kustomize + version + type: string + type: object + path: + description: Path is a directory path within + the Git repository + type: string + plugin: + description: ConfigManagementPlugin holds config + management plugin specific options + properties: + env: + items: + properties: + name: + description: the name, usually uppercase + type: string + value: + description: the value + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + description: RepoURL is the repository URL of + the application manifests + type: string + targetRevision: + description: TargetRevision defines the commit, + tag, or branch in which to sync the application + to. If omitted, will sync to HEAD + type: string + required: + - repoURL + type: object + syncPolicy: + description: SyncPolicy controls when a sync will + be performed + properties: + automated: + description: Automated will keep an application + synced to the target revision + properties: + allowEmpty: + description: 'AllowEmpty allows apps have + zero live resources (default: false)' + type: boolean + prune: + description: 'Prune will prune resources + automatically as part of automated sync + (default: false)' + type: boolean + selfHeal: + description: 'SelfHeal enables auto-syncing + if (default: false)' + type: boolean + type: object + retry: + description: Retry controls failed sync retry + behavior + properties: + backoff: + description: Backoff is a backoff strategy + properties: + duration: + description: Duration is the amount + to back off. Default unit is seconds, + but could also be a duration (e.g. + "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply + the base duration after each failed + retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum + amount of time allowed for the backoff + strategy + type: string + type: object + limit: + description: Limit is the maximum number + of attempts when retrying a container + format: int64 + type: integer + type: object + syncOptions: + description: Options allow you to specify whole + app sync-options + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + type: object type: object type: array syncPolicy: diff --git a/manifests/install-with-argo-cd.yaml b/manifests/install-with-argo-cd.yaml index caf23803..4686a3d5 100644 --- a/manifests/install-with-argo-cd.yaml +++ b/manifests/install-with-argo-cd.yaml @@ -2840,6 +2840,379 @@ spec: required: - elements type: object + scmProvider: + description: SCMProviderGenerator defines a generator that scrapes a SCMaaS API to find candidate repos. + properties: + cloneProtocol: + description: Which protocol to use for the SCM URL. Default is provider-specific but ssh if possible. Not all providers necessarily support all protocols. + type: string + filters: + description: TODO other providers. Filters for which repos should be considered. + items: + description: SCMProviderGeneratorFilter is a single repository filter. If multiple filter types are set on a single struct, they will be AND'd together. All filters must pass for a repo to be included. + properties: + branchMatch: + description: A regex which must match the branch name. + type: string + labelMatch: + description: A regex which must match at least one label. + type: string + pathsExist: + description: An array of paths, all of which must exist. + items: + type: string + type: array + repositoryMatch: + description: A regex for repo names. + type: string + type: object + type: array + github: + description: Which provider to use and config for it. + properties: + allBranches: + description: Scan all branches instead of just the default branch. + type: boolean + api: + description: The GitHub API URL to talk to. If blank, use https://api.github.com/. + type: string + organization: + description: GitHub org to scan. Required. + type: string + tokenRef: + description: Authentication token reference. + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + requeueAfterSeconds: + description: Standard parameters. + format: int64 + type: integer + template: + description: ApplicationSetTemplate represents argocd ApplicationSpec + properties: + metadata: + description: ApplicationSetTemplateMeta represents the Argo CD application fields that may be used for Applications generated from the ApplicationSet (based on metav1.ObjectMeta) + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + description: ApplicationSpec represents desired application state. Contains link to repository with application definition and additional parameters link definition revision. + properties: + destination: + description: Destination overrides the kubernetes server and namespace defined in the environment ksonnet app.yaml + properties: + name: + description: Name of the destination cluster which can be used instead of server (url) field + type: string + namespace: + description: Namespace overrides the environment namespace value in the ksonnet app.yaml + type: string + server: + description: Server overrides the environment server value in the ksonnet app.yaml + type: string + type: object + ignoreDifferences: + description: IgnoreDifferences controls resources fields which should be ignored during comparison + items: + description: ResourceIgnoreDifferences contains resource filter and list of json paths which should be ignored during comparison with live state. + properties: + group: + type: string + jsonPointers: + items: + type: string + type: array + kind: + type: string + name: + type: string + namespace: + type: string + required: + - jsonPointers + - kind + type: object + type: array + info: + description: Infos contains a list of useful information (URLs, email addresses, and plain text) that relates to the application + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + description: Project is a application project name. Empty name means that application belongs to 'default' project. + type: string + revisionHistoryLimit: + description: This limits this number of items kept in the apps revision history. This should only be changed in exceptional circumstances. Setting to zero will store no history. This will reduce storage used. Increasing will increase the space used to store the history, so we do not recommend increasing it. Default is 10. + format: int64 + type: integer + source: + description: Source is a reference to the location ksonnet application definition + properties: + chart: + description: Chart is a Helm chart name + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + type: string + jsonnet: + description: ApplicationSourceJsonnet holds jsonnet specific options + properties: + extVars: + description: ExtVars is a list of Jsonnet External Variables + items: + description: JsonnetVar is a jsonnet variable + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar is a jsonnet variable + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the helm template + items: + description: HelmFileParameter is a file parameter to a helm template + properties: + name: + description: Name is the name of the helm parameter + type: string + path: + description: Path is the path value for the helm parameter + type: string + type: object + type: array + parameters: + description: Parameters are parameters to the helm template + items: + description: HelmParameter is a parameter to a helm template + properties: + forceString: + description: ForceString determines whether to tell Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the helm parameter + type: string + value: + description: Value is the value for the helm parameter + type: string + type: object + type: array + releaseName: + description: The Helm release name. If omitted it will use the application name + type: string + valueFiles: + description: ValuesFiles is a list of Helm value files to use when generating a template + items: + type: string + type: array + values: + description: Values is Helm values, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating with + type: string + type: object + ksonnet: + description: Ksonnet holds ksonnet specific options + properties: + environment: + description: Environment is a ksonnet application environment name + type: string + parameters: + description: Parameters are a list of ksonnet component parameter override values + items: + description: KsonnetParameter is a ksonnet component parameter + properties: + component: + type: string + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations adds additional kustomize commonAnnotations + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels adds additional kustomize commonLabels + type: object + images: + description: Images are kustomize image overrides + items: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources for kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources for kustomize apps + type: string + version: + description: Version contains optional Kustomize version + type: string + type: object + path: + description: Path is a directory path within the Git repository + type: string + plugin: + description: ConfigManagementPlugin holds config management plugin specific options + properties: + env: + items: + properties: + name: + description: the name, usually uppercase + type: string + value: + description: the value + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + description: RepoURL is the repository URL of the application manifests + type: string + targetRevision: + description: TargetRevision defines the commit, tag, or branch in which to sync the application to. If omitted, will sync to HEAD + type: string + required: + - repoURL + type: object + syncPolicy: + description: SyncPolicy controls when a sync will be performed + properties: + automated: + description: Automated will keep an application synced to the target revision + properties: + allowEmpty: + description: 'AllowEmpty allows apps have zero live resources (default: false)' + type: boolean + prune: + description: 'Prune will prune resources automatically as part of automated sync (default: false)' + type: boolean + selfHeal: + description: 'SelfHeal enables auto-syncing if (default: false)' + type: boolean + type: object + retry: + description: Retry controls failed sync retry behavior + properties: + backoff: + description: Backoff is a backoff strategy + properties: + duration: + description: Duration is the amount to back off. Default unit is seconds, but could also be a duration (e.g. "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base duration after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of time allowed for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts when retrying a container + format: int64 + type: integer + type: object + syncOptions: + description: Options allow you to specify whole app sync-options + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + type: object type: object type: array syncPolicy: diff --git a/manifests/install.yaml b/manifests/install.yaml index 6f1e5fca..24d7d660 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -1079,6 +1079,379 @@ spec: required: - elements type: object + scmProvider: + description: SCMProviderGenerator defines a generator that scrapes a SCMaaS API to find candidate repos. + properties: + cloneProtocol: + description: Which protocol to use for the SCM URL. Default is provider-specific but ssh if possible. Not all providers necessarily support all protocols. + type: string + filters: + description: TODO other providers. Filters for which repos should be considered. + items: + description: SCMProviderGeneratorFilter is a single repository filter. If multiple filter types are set on a single struct, they will be AND'd together. All filters must pass for a repo to be included. + properties: + branchMatch: + description: A regex which must match the branch name. + type: string + labelMatch: + description: A regex which must match at least one label. + type: string + pathsExist: + description: An array of paths, all of which must exist. + items: + type: string + type: array + repositoryMatch: + description: A regex for repo names. + type: string + type: object + type: array + github: + description: Which provider to use and config for it. + properties: + allBranches: + description: Scan all branches instead of just the default branch. + type: boolean + api: + description: The GitHub API URL to talk to. If blank, use https://api.github.com/. + type: string + organization: + description: GitHub org to scan. Required. + type: string + tokenRef: + description: Authentication token reference. + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + required: + - organization + type: object + requeueAfterSeconds: + description: Standard parameters. + format: int64 + type: integer + template: + description: ApplicationSetTemplate represents argocd ApplicationSpec + properties: + metadata: + description: ApplicationSetTemplateMeta represents the Argo CD application fields that may be used for Applications generated from the ApplicationSet (based on metav1.ObjectMeta) + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + type: object + spec: + description: ApplicationSpec represents desired application state. Contains link to repository with application definition and additional parameters link definition revision. + properties: + destination: + description: Destination overrides the kubernetes server and namespace defined in the environment ksonnet app.yaml + properties: + name: + description: Name of the destination cluster which can be used instead of server (url) field + type: string + namespace: + description: Namespace overrides the environment namespace value in the ksonnet app.yaml + type: string + server: + description: Server overrides the environment server value in the ksonnet app.yaml + type: string + type: object + ignoreDifferences: + description: IgnoreDifferences controls resources fields which should be ignored during comparison + items: + description: ResourceIgnoreDifferences contains resource filter and list of json paths which should be ignored during comparison with live state. + properties: + group: + type: string + jsonPointers: + items: + type: string + type: array + kind: + type: string + name: + type: string + namespace: + type: string + required: + - jsonPointers + - kind + type: object + type: array + info: + description: Infos contains a list of useful information (URLs, email addresses, and plain text) that relates to the application + items: + properties: + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + project: + description: Project is a application project name. Empty name means that application belongs to 'default' project. + type: string + revisionHistoryLimit: + description: This limits this number of items kept in the apps revision history. This should only be changed in exceptional circumstances. Setting to zero will store no history. This will reduce storage used. Increasing will increase the space used to store the history, so we do not recommend increasing it. Default is 10. + format: int64 + type: integer + source: + description: Source is a reference to the location ksonnet application definition + properties: + chart: + description: Chart is a Helm chart name + type: string + directory: + description: Directory holds path/directory specific options + properties: + exclude: + type: string + jsonnet: + description: ApplicationSourceJsonnet holds jsonnet specific options + properties: + extVars: + description: ExtVars is a list of Jsonnet External Variables + items: + description: JsonnetVar is a jsonnet variable + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + libs: + description: Additional library search dirs + items: + type: string + type: array + tlas: + description: TLAS is a list of Jsonnet Top-level Arguments + items: + description: JsonnetVar is a jsonnet variable + properties: + code: + type: boolean + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + recurse: + type: boolean + type: object + helm: + description: Helm holds helm specific options + properties: + fileParameters: + description: FileParameters are file parameters to the helm template + items: + description: HelmFileParameter is a file parameter to a helm template + properties: + name: + description: Name is the name of the helm parameter + type: string + path: + description: Path is the path value for the helm parameter + type: string + type: object + type: array + parameters: + description: Parameters are parameters to the helm template + items: + description: HelmParameter is a parameter to a helm template + properties: + forceString: + description: ForceString determines whether to tell Helm to interpret booleans and numbers as strings + type: boolean + name: + description: Name is the name of the helm parameter + type: string + value: + description: Value is the value for the helm parameter + type: string + type: object + type: array + releaseName: + description: The Helm release name. If omitted it will use the application name + type: string + valueFiles: + description: ValuesFiles is a list of Helm value files to use when generating a template + items: + type: string + type: array + values: + description: Values is Helm values, typically defined as a block + type: string + version: + description: Version is the Helm version to use for templating with + type: string + type: object + ksonnet: + description: Ksonnet holds ksonnet specific options + properties: + environment: + description: Environment is a ksonnet application environment name + type: string + parameters: + description: Parameters are a list of ksonnet component parameter override values + items: + description: KsonnetParameter is a ksonnet component parameter + properties: + component: + type: string + name: + type: string + value: + type: string + required: + - name + - value + type: object + type: array + type: object + kustomize: + description: Kustomize holds kustomize specific options + properties: + commonAnnotations: + additionalProperties: + type: string + description: CommonAnnotations adds additional kustomize commonAnnotations + type: object + commonLabels: + additionalProperties: + type: string + description: CommonLabels adds additional kustomize commonLabels + type: object + images: + description: Images are kustomize image overrides + items: + type: string + type: array + namePrefix: + description: NamePrefix is a prefix appended to resources for kustomize apps + type: string + nameSuffix: + description: NameSuffix is a suffix appended to resources for kustomize apps + type: string + version: + description: Version contains optional Kustomize version + type: string + type: object + path: + description: Path is a directory path within the Git repository + type: string + plugin: + description: ConfigManagementPlugin holds config management plugin specific options + properties: + env: + items: + properties: + name: + description: the name, usually uppercase + type: string + value: + description: the value + type: string + required: + - name + - value + type: object + type: array + name: + type: string + type: object + repoURL: + description: RepoURL is the repository URL of the application manifests + type: string + targetRevision: + description: TargetRevision defines the commit, tag, or branch in which to sync the application to. If omitted, will sync to HEAD + type: string + required: + - repoURL + type: object + syncPolicy: + description: SyncPolicy controls when a sync will be performed + properties: + automated: + description: Automated will keep an application synced to the target revision + properties: + allowEmpty: + description: 'AllowEmpty allows apps have zero live resources (default: false)' + type: boolean + prune: + description: 'Prune will prune resources automatically as part of automated sync (default: false)' + type: boolean + selfHeal: + description: 'SelfHeal enables auto-syncing if (default: false)' + type: boolean + type: object + retry: + description: Retry controls failed sync retry behavior + properties: + backoff: + description: Backoff is a backoff strategy + properties: + duration: + description: Duration is the amount to back off. Default unit is seconds, but could also be a duration (e.g. "2m", "1h") + type: string + factor: + description: Factor is a factor to multiply the base duration after each failed retry + format: int64 + type: integer + maxDuration: + description: MaxDuration is the maximum amount of time allowed for the backoff strategy + type: string + type: object + limit: + description: Limit is the maximum number of attempts when retrying a container + format: int64 + type: integer + type: object + syncOptions: + description: Options allow you to specify whole app sync-options + items: + type: string + type: array + type: object + required: + - destination + - project + - source + type: object + required: + - metadata + - spec + type: object + type: object type: object type: array syncPolicy: diff --git a/pkg/controllers/applicationset_controller.go b/pkg/controllers/applicationset_controller.go index e14f8e72..a173d9e1 100644 --- a/pkg/controllers/applicationset_controller.go +++ b/pkg/controllers/applicationset_controller.go @@ -352,7 +352,7 @@ func (r *ApplicationSetReconciler) generateApplications(applicationSetInfo argop continue } - params, err := g.GenerateParams(&requestedGenerator) + params, err := g.GenerateParams(&requestedGenerator, &applicationSetInfo) if err != nil { log.WithError(err).WithField("generator", g). Error("error generating params") diff --git a/pkg/controllers/applicationset_controller_test.go b/pkg/controllers/applicationset_controller_test.go index 846cc9fa..5f04a9c4 100644 --- a/pkg/controllers/applicationset_controller_test.go +++ b/pkg/controllers/applicationset_controller_test.go @@ -40,7 +40,7 @@ func (g *generatorMock) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicat return args.Get(0).(*argoprojiov1alpha1.ApplicationSetTemplate) } -func (g *generatorMock) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) ([]map[string]string, error) { +func (g *generatorMock) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, _ *argoprojiov1alpha1.ApplicationSet) ([]map[string]string, error) { args := g.Called(appSetGenerator) return args.Get(0).([]map[string]string), args.Error(1) diff --git a/pkg/generators/cluster.go b/pkg/generators/cluster.go index 1920728b..1e9f9ad2 100644 --- a/pkg/generators/cluster.go +++ b/pkg/generators/cluster.go @@ -57,7 +57,7 @@ func (g *ClusterGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Appli } func (g *ClusterGenerator) GenerateParams( - appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) ([]map[string]string, error) { + appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, _ *argoprojiov1alpha1.ApplicationSet) ([]map[string]string, error) { if appSetGenerator == nil { return nil, EmptyAppSetGeneratorError diff --git a/pkg/generators/cluster_test.go b/pkg/generators/cluster_test.go index 11ba21df..6bec3f81 100644 --- a/pkg/generators/cluster_test.go +++ b/pkg/generators/cluster_test.go @@ -229,7 +229,7 @@ func TestGenerateParams(t *testing.T) { Selector: testCase.selector, Values: testCase.values, }, - }) + }, nil) if testCase.expectedError != nil { assert.Error(t, testCase.expectedError, err) diff --git a/pkg/generators/git.go b/pkg/generators/git.go index 1aec283b..753f2bef 100644 --- a/pkg/generators/git.go +++ b/pkg/generators/git.go @@ -46,7 +46,7 @@ func (g *GitGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Appli return DefaultRequeueAfterSeconds } -func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) ([]map[string]string, error) { +func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, _ *argoprojiov1alpha1.ApplicationSet) ([]map[string]string, error) { if appSetGenerator == nil { return nil, EmptyAppSetGeneratorError diff --git a/pkg/generators/git_test.go b/pkg/generators/git_test.go index 239ec7ba..b9fba2bc 100644 --- a/pkg/generators/git_test.go +++ b/pkg/generators/git_test.go @@ -164,7 +164,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) { }, } - got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0]) + got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], nil) if c.expectedError != nil { assert.EqualError(t, err, c.expectedError.Error()) @@ -451,7 +451,7 @@ cluster: }, } - got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0]) + got, err := gitGenerator.GenerateParams(&applicationSetInfo.Spec.Generators[0], nil) fmt.Println(got, err) if c.expectedError != nil { diff --git a/pkg/generators/interface.go b/pkg/generators/interface.go index 78fe2521..3d9ff11e 100644 --- a/pkg/generators/interface.go +++ b/pkg/generators/interface.go @@ -12,7 +12,7 @@ type Generator interface { // GenerateParams interprets the ApplicationSet and generates all relevant parameters for the application template. // The expected / desired list of parameters is returned, it then will be render and reconciled // against the current state of the Applications in the cluster. - GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) ([]map[string]string, error) + GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]string, error) // GetRequeueAfter is the the generator can controller the next reconciled loop // In case there is more then one generator the time will be the minimum of the times. diff --git a/pkg/generators/list.go b/pkg/generators/list.go index 8997bfa0..9cd85bda 100644 --- a/pkg/generators/list.go +++ b/pkg/generators/list.go @@ -26,7 +26,7 @@ func (g *ListGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.Applicat return &appSetGenerator.List.Template } -func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) ([]map[string]string, error) { +func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, _ *argoprojiov1alpha1.ApplicationSet) ([]map[string]string, error) { if appSetGenerator == nil { return nil, EmptyAppSetGeneratorError } diff --git a/pkg/generators/list_test.go b/pkg/generators/list_test.go index 74b36acb..472d8211 100644 --- a/pkg/generators/list_test.go +++ b/pkg/generators/list_test.go @@ -31,7 +31,7 @@ func TestGenerateListParams(t *testing.T) { got, err := listGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{List: &argoprojiov1alpha1.ListGenerator{ Elements: testCase.elements, - }}) + }}, nil) assert.NoError(t, err) assert.ElementsMatch(t, testCase.expected, got) diff --git a/pkg/generators/scm_provider.go b/pkg/generators/scm_provider.go new file mode 100644 index 00000000..0dfab3e9 --- /dev/null +++ b/pkg/generators/scm_provider.go @@ -0,0 +1,114 @@ +package generators + +import ( + "context" + "fmt" + "strings" + "time" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + argoprojiov1alpha1 "github.com/argoproj-labs/applicationset/api/v1alpha1" + "github.com/argoproj-labs/applicationset/pkg/services/scm_provider" +) + +var _ Generator = (*SCMProviderGenerator)(nil) + +const ( + DefaultSCMProviderRequeueAfterSeconds = 30 * time.Minute +) + +type SCMProviderGenerator struct { + client client.Client + // Testing hooks. + overrideProvider scm_provider.SCMProviderService +} + +func NewSCMProviderGenerator(client client.Client) Generator { + return &SCMProviderGenerator{client: client} +} + +func (g *SCMProviderGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration { + // Return a requeue default of 30 minutes, if no default is specified. + + if appSetGenerator.SCMProvider.RequeueAfterSeconds != nil { + return time.Duration(*appSetGenerator.SCMProvider.RequeueAfterSeconds) * time.Second + } + + return DefaultSCMProviderRequeueAfterSeconds +} + +func (g *SCMProviderGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate { + return &appSetGenerator.SCMProvider.Template +} + +func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetInfo *argoprojiov1alpha1.ApplicationSet) ([]map[string]string, error) { + if appSetGenerator == nil { + return nil, EmptyAppSetGeneratorError + } + + if appSetGenerator.SCMProvider == nil { + return nil, EmptyAppSetGeneratorError + } + + ctx := context.Background() + + // Create the SCM provider helper. + providerConfig := appSetGenerator.SCMProvider + var provider scm_provider.SCMProviderService + if g.overrideProvider != nil { + provider = g.overrideProvider + } else if providerConfig.Github != nil { + token, err := g.getSecretRef(ctx, providerConfig.Github.TokenRef, applicationSetInfo.Namespace) + if err != nil { + return nil, fmt.Errorf("error fetching Github token: %v", err) + } + provider, err = scm_provider.NewGithubProvider(ctx, providerConfig.Github.Organization, token, providerConfig.Github.API, providerConfig.Github.AllBranches) + if err != nil { + return nil, fmt.Errorf("error initializing Github service: %v", err) + } + } else { + return nil, fmt.Errorf("no SCM provider implementation configured") + } + + // Find all the available repos. + repos, err := scm_provider.ListRepos(ctx, provider, providerConfig.Filters, providerConfig.CloneProtocol) + if err != nil { + return nil, fmt.Errorf("error listing repos: %v", err) + } + params := make([]map[string]string, 0, len(repos)) + for _, repo := range repos { + params = append(params, map[string]string{ + "organization": repo.Organization, + "repository": repo.Repository, + "url": repo.URL, + "branch": repo.Branch, + "labels": strings.Join(repo.Labels, ","), + }) + } + return params, nil +} + +func (g *SCMProviderGenerator) getSecretRef(ctx context.Context, ref *argoprojiov1alpha1.SecretRef, namespace string) (string, error) { + if ref == nil { + return "", nil + } + + secret := &corev1.Secret{} + err := g.client.Get( + ctx, + client.ObjectKey{ + Name: ref.SecretName, + Namespace: namespace, + }, + secret) + if err != nil { + return "", fmt.Errorf("error fetching secret %s/%s: %v", namespace, ref.SecretName, err) + } + tokenBytes, ok := secret.Data[ref.Key] + if !ok { + return "", fmt.Errorf("key %q in secret %s/%s not found", ref.Key, namespace, ref.SecretName) + } + return string(tokenBytes), nil +} diff --git a/pkg/generators/scm_provider_test.go b/pkg/generators/scm_provider_test.go new file mode 100644 index 00000000..518f9a2a --- /dev/null +++ b/pkg/generators/scm_provider_test.go @@ -0,0 +1,112 @@ +package generators + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + argoprojiov1alpha1 "github.com/argoproj-labs/applicationset/api/v1alpha1" + "github.com/argoproj-labs/applicationset/pkg/services/scm_provider" +) + +func TestSCMProviderGetSecretRef(t *testing.T) { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "test-secret", Namespace: "test"}, + Data: map[string][]byte{ + "my-token": []byte("secret"), + }, + } + gen := &SCMProviderGenerator{client: fake.NewClientBuilder().WithObjects(secret).Build()} + ctx := context.Background() + + cases := []struct { + name, namespace, token string + ref *argoprojiov1alpha1.SecretRef + hasError bool + }{ + { + name: "valid ref", + ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"}, + namespace: "test", + token: "secret", + hasError: false, + }, + { + name: "nil ref", + ref: nil, + namespace: "test", + token: "", + hasError: false, + }, + { + name: "wrong name", + ref: &argoprojiov1alpha1.SecretRef{SecretName: "other", Key: "my-token"}, + namespace: "test", + token: "", + hasError: true, + }, + { + name: "wrong key", + ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "other-token"}, + namespace: "test", + token: "", + hasError: true, + }, + { + name: "wrong namespace", + ref: &argoprojiov1alpha1.SecretRef{SecretName: "test-secret", Key: "my-token"}, + namespace: "other", + token: "", + hasError: true, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + token, err := gen.getSecretRef(ctx, c.ref, c.namespace) + if c.hasError { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + assert.Equal(t, c.token, token) + + }) + } +} + +func TestSCMProviderGenerateParams(t *testing.T) { + mockProvider := &scm_provider.MockProvider{ + Repos: []*scm_provider.Repository{ + { + Organization: "myorg", + Repository: "repo1", + URL: "git@github.com:myorg/repo1.git", + Branch: "main", + Labels: []string{"prod", "staging"}, + }, + { + Organization: "myorg", + Repository: "repo2", + URL: "git@github.com:myorg/repo2.git", + Branch: "main", + }, + }, + } + gen := &SCMProviderGenerator{overrideProvider: mockProvider} + params, err := gen.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{ + SCMProvider: &argoprojiov1alpha1.SCMProviderGenerator{}, + }, nil) + assert.Nil(t, err) + assert.Len(t, params, 2) + assert.Equal(t, "myorg", params[0]["organization"]) + assert.Equal(t, "repo1", params[0]["repository"]) + assert.Equal(t, "git@github.com:myorg/repo1.git", params[0]["url"]) + assert.Equal(t, "main", params[0]["branch"]) + assert.Equal(t, "prod,staging", params[0]["labels"]) + assert.Equal(t, "repo2", params[1]["repository"]) +} diff --git a/pkg/services/scm_provider/github.go b/pkg/services/scm_provider/github.go new file mode 100644 index 00000000..2f01e6c9 --- /dev/null +++ b/pkg/services/scm_provider/github.go @@ -0,0 +1,129 @@ +package scm_provider + +import ( + "context" + "fmt" + "os" + + "github.com/google/go-github/v35/github" + "golang.org/x/oauth2" +) + +type GithubProvider struct { + client *github.Client + organization string + allBranches bool +} + +var _ SCMProviderService = &GithubProvider{} + +func NewGithubProvider(ctx context.Context, organization string, token string, url string, allBranches bool) (*GithubProvider, error) { + var ts oauth2.TokenSource + // Undocumented environment variable to set a default token, to be used in testing to dodge anonymous rate limits. + if token == "" { + token = os.Getenv("GITHUB_TOKEN") + } + if token != "" { + ts = oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: token}, + ) + } + httpClient := oauth2.NewClient(ctx, ts) + var client *github.Client + if url == "" { + client = github.NewClient(httpClient) + } else { + var err error + client, err = github.NewEnterpriseClient(url, url, httpClient) + if err != nil { + return nil, err + } + } + return &GithubProvider{client: client, organization: organization, allBranches: allBranches}, nil +} + +func (g *GithubProvider) ListRepos(ctx context.Context, cloneProtocol string) ([]*Repository, error) { + opt := &github.RepositoryListByOrgOptions{ + ListOptions: github.ListOptions{PerPage: 100}, + } + repos := []*Repository{} + for { + githubRepos, resp, err := g.client.Repositories.ListByOrg(ctx, g.organization, opt) + if err != nil { + return nil, fmt.Errorf("error listing repositories for %s: %v", g.organization, err) + } + for _, githubRepo := range githubRepos { + var url string + switch cloneProtocol { + // Default to SSH if unspecified (i.e. if ""). + case "", "ssh": + url = githubRepo.GetSSHURL() + case "https": + url = githubRepo.GetCloneURL() + default: + return nil, fmt.Errorf("unknown clone protocol for GitHub %v", cloneProtocol) + } + + branches, err := g.listBranches(ctx, githubRepo) + if err != nil { + return nil, fmt.Errorf("error listing branches for %s/%s: %v", githubRepo.Owner.GetLogin(), githubRepo.GetName(), err) + } + + for _, branch := range branches { + repos = append(repos, &Repository{ + Organization: githubRepo.Owner.GetLogin(), + Repository: githubRepo.GetName(), + URL: url, + Branch: branch, + Labels: githubRepo.Topics, + }) + } + } + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + return repos, nil +} + +func (g *GithubProvider) RepoHasPath(ctx context.Context, repo *Repository, path string) (bool, error) { + _, _, resp, err := g.client.Repositories.GetContents(ctx, repo.Organization, repo.Repository, path, &github.RepositoryContentGetOptions{ + Ref: repo.Branch, + }) + // 404s are not an error here, just a normal false. + if resp != nil && resp.StatusCode == 404 { + return false, nil + } + if err != nil { + return false, err + } + return true, nil +} + +func (g *GithubProvider) listBranches(ctx context.Context, repo *github.Repository) ([]string, error) { + // If we don't specifically want to query for all branches, just use the default branch and call it a day. + if !g.allBranches { + return []string{repo.GetDefaultBranch()}, nil + } + // Otherwise, scrape the ListBranches API. + opt := &github.BranchListOptions{ + ListOptions: github.ListOptions{PerPage: 100}, + } + branches := []string{} + for { + githubBranches, resp, err := g.client.Repositories.ListBranches(ctx, repo.Owner.GetLogin(), repo.GetName(), opt) + if err != nil { + return nil, err + } + for _, githubBranch := range githubBranches { + branches = append(branches, githubBranch.GetName()) + } + + if resp.NextPage == 0 { + break + } + opt.Page = resp.NextPage + } + return branches, nil +} diff --git a/pkg/services/scm_provider/github_test.go b/pkg/services/scm_provider/github_test.go new file mode 100644 index 00000000..c57b4513 --- /dev/null +++ b/pkg/services/scm_provider/github_test.go @@ -0,0 +1,103 @@ +package scm_provider + +import ( + "context" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func checkRateLimit(t *testing.T, err error) { + // Check if we've hit a rate limit, don't fail the test if so. + if err != nil && strings.Contains(err.Error(), "rate limit exceeded") { + allowRateLimitErrors := os.Getenv("CI") == "" + t.Logf("Got a rate limit error, consider setting $GITHUB_TOKEN to increase your GitHub API rate limit: %v\n", err) + if allowRateLimitErrors { + t.SkipNow() + } else { + t.FailNow() + } + } +} + +func TestGithubListRepos(t *testing.T) { + cases := []struct { + name, proto, url string + hasError, allBranches bool + branches []string + }{ + { + name: "blank protocol", + url: "git@github.com:argoproj-labs/applicationset.git", + branches: []string{"master"}, + }, + { + name: "ssh protocol", + proto: "ssh", + url: "git@github.com:argoproj-labs/applicationset.git", + }, + { + name: "https protocol", + proto: "https", + url: "https://github.com/argoproj-labs/applicationset.git", + }, + { + name: "other protocol", + proto: "other", + hasError: true, + }, + { + name: "all branches", + allBranches: true, + url: "git@github.com:argoproj-labs/applicationset.git", + branches: []string{"master", "release-0.1.0"}, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + provider, _ := NewGithubProvider(context.Background(), "argoproj-labs", "", "", c.allBranches) + rawRepos, err := provider.ListRepos(context.Background(), c.proto) + if c.hasError { + assert.NotNil(t, err) + } else { + checkRateLimit(t, err) + assert.Nil(t, err) + // Just check that this one project shows up. Not a great test but better thing nothing? + repos := []*Repository{} + branches := []string{} + for _, r := range rawRepos { + if r.Repository == "applicationset" { + repos = append(repos, r) + branches = append(branches, r.Branch) + } + } + assert.NotEmpty(t, repos) + assert.Equal(t, c.url, repos[0].URL) + for _, b := range c.branches { + assert.Contains(t, branches, b) + } + } + }) + } +} + +func TestGithubHasPath(t *testing.T) { + host, _ := NewGithubProvider(context.Background(), "argoproj-labs", "", "", false) + repo := &Repository{ + Organization: "argoproj-labs", + Repository: "applicationset", + Branch: "master", + } + ok, err := host.RepoHasPath(context.Background(), repo, "pkg/") + checkRateLimit(t, err) + assert.Nil(t, err) + assert.True(t, ok) + + ok, err = host.RepoHasPath(context.Background(), repo, "notathing/") + checkRateLimit(t, err) + assert.Nil(t, err) + assert.False(t, ok) +} diff --git a/pkg/services/scm_provider/mock.go b/pkg/services/scm_provider/mock.go new file mode 100644 index 00000000..54b864a4 --- /dev/null +++ b/pkg/services/scm_provider/mock.go @@ -0,0 +1,17 @@ +package scm_provider + +import "context" + +type MockProvider struct { + Repos []*Repository +} + +var _ SCMProviderService = &MockProvider{} + +func (m *MockProvider) ListRepos(_ context.Context, _ string) ([]*Repository, error) { + return m.Repos, nil +} + +func (*MockProvider) RepoHasPath(_ context.Context, repo *Repository, path string) (bool, error) { + return path == repo.Repository, nil +} diff --git a/pkg/services/scm_provider/types.go b/pkg/services/scm_provider/types.go new file mode 100644 index 00000000..a46d8121 --- /dev/null +++ b/pkg/services/scm_provider/types.go @@ -0,0 +1,28 @@ +package scm_provider + +import ( + "context" + "regexp" +) + +// An abstract repository from an API provider. +type Repository struct { + Organization string + Repository string + URL string + Branch string + Labels []string +} + +type SCMProviderService interface { + ListRepos(context.Context, string) ([]*Repository, error) + RepoHasPath(context.Context, *Repository, string) (bool, error) +} + +// A compiled version of SCMProviderGeneratorFilter for performance. +type Filter struct { + RepositoryMatch *regexp.Regexp + PathsExist []string + LabelMatch *regexp.Regexp + BranchMatch *regexp.Regexp +} diff --git a/pkg/services/scm_provider/utils.go b/pkg/services/scm_provider/utils.go new file mode 100644 index 00000000..5b1d2011 --- /dev/null +++ b/pkg/services/scm_provider/utils.go @@ -0,0 +1,109 @@ +package scm_provider + +import ( + "context" + "fmt" + "regexp" + + argoprojiov1alpha1 "github.com/argoproj-labs/applicationset/api/v1alpha1" +) + +func compileFilters(filters []argoprojiov1alpha1.SCMProviderGeneratorFilter) ([]*Filter, error) { + outFilters := make([]*Filter, 0, len(filters)) + for _, filter := range filters { + outFilter := &Filter{} + var err error + if filter.RepositoryMatch != nil { + outFilter.RepositoryMatch, err = regexp.Compile(*filter.RepositoryMatch) + if err != nil { + return nil, fmt.Errorf("error compiling RepositoryMatch regexp %q: %v", *filter.RepositoryMatch, err) + } + } + if filter.LabelMatch != nil { + outFilter.LabelMatch, err = regexp.Compile(*filter.LabelMatch) + if err != nil { + return nil, fmt.Errorf("error compiling LabelMatch regexp %q: %v", *filter.LabelMatch, err) + } + } + if filter.PathsExist != nil { + outFilter.PathsExist = filter.PathsExist + } + if filter.BranchMatch != nil { + outFilter.BranchMatch, err = regexp.Compile(*filter.BranchMatch) + if err != nil { + return nil, fmt.Errorf("error compiling BranchMatch regexp %q: %v", *filter.LabelMatch, err) + } + } + outFilters = append(outFilters, outFilter) + } + return outFilters, nil +} + +func matchFilter(ctx context.Context, provider SCMProviderService, repo *Repository, filter *Filter) (bool, error) { + if filter.RepositoryMatch != nil && !filter.RepositoryMatch.MatchString(repo.Repository) { + return false, nil + } + + if filter.BranchMatch != nil && !filter.BranchMatch.MatchString(repo.Branch) { + return false, nil + } + + if filter.LabelMatch != nil { + found := false + for _, label := range repo.Labels { + if filter.LabelMatch.MatchString(label) { + found = true + break + } + } + if !found { + return false, nil + } + } + + if len(filter.PathsExist) != 0 { + for _, path := range filter.PathsExist { + hasPath, err := provider.RepoHasPath(ctx, repo, path) + if err != nil { + return false, err + } + if !hasPath { + return false, nil + } + } + } + + return true, nil +} + +func ListRepos(ctx context.Context, provider SCMProviderService, filters []argoprojiov1alpha1.SCMProviderGeneratorFilter, cloneProtocol string) ([]*Repository, error) { + compiledFilters, err := compileFilters(filters) + if err != nil { + return nil, err + } + + repos, err := provider.ListRepos(ctx, cloneProtocol) + if err != nil { + return nil, err + } + + // Special case, if we have no filters, allow everything. + if len(compiledFilters) == 0 { + return repos, nil + } + + filteredRepos := make([]*Repository, 0, len(repos)) + for _, repo := range repos { + for _, filter := range compiledFilters { + matches, err := matchFilter(ctx, provider, repo, filter) + if err != nil { + return nil, err + } + if matches { + filteredRepos = append(filteredRepos, repo) + break + } + } + } + return filteredRepos, nil +} diff --git a/pkg/services/scm_provider/utils_test.go b/pkg/services/scm_provider/utils_test.go new file mode 100644 index 00000000..4068df18 --- /dev/null +++ b/pkg/services/scm_provider/utils_test.go @@ -0,0 +1,257 @@ +package scm_provider + +import ( + "context" + "testing" + + argoprojiov1alpha1 "github.com/argoproj-labs/applicationset/api/v1alpha1" + "github.com/stretchr/testify/assert" +) + +func strp(s string) *string { + return &s +} + +func TestFilterRepoMatch(t *testing.T) { + provider := &MockProvider{ + Repos: []*Repository{ + { + Repository: "one", + }, + { + Repository: "two", + }, + { + Repository: "three", + }, + { + Repository: "four", + }, + }, + } + filters := []argoprojiov1alpha1.SCMProviderGeneratorFilter{ + { + RepositoryMatch: strp("n|hr"), + }, + } + repos, err := ListRepos(context.Background(), provider, filters, "") + assert.Nil(t, err) + assert.Len(t, repos, 2) + assert.Equal(t, "one", repos[0].Repository) + assert.Equal(t, "three", repos[1].Repository) +} + +func TestFilterLabelMatch(t *testing.T) { + provider := &MockProvider{ + Repos: []*Repository{ + { + Repository: "one", + Labels: []string{"prod-one", "prod-two", "staging"}, + }, + { + Repository: "two", + Labels: []string{"prod-two"}, + }, + { + Repository: "three", + Labels: []string{"staging"}, + }, + }, + } + filters := []argoprojiov1alpha1.SCMProviderGeneratorFilter{ + { + LabelMatch: strp("^prod-.*$"), + }, + } + repos, err := ListRepos(context.Background(), provider, filters, "") + assert.Nil(t, err) + assert.Len(t, repos, 2) + assert.Equal(t, "one", repos[0].Repository) + assert.Equal(t, "two", repos[1].Repository) +} + +func TestFilterPatchExists(t *testing.T) { + provider := &MockProvider{ + Repos: []*Repository{ + { + Repository: "one", + }, + { + Repository: "two", + }, + { + Repository: "three", + }, + }, + } + filters := []argoprojiov1alpha1.SCMProviderGeneratorFilter{ + { + PathsExist: []string{"two"}, + }, + } + repos, err := ListRepos(context.Background(), provider, filters, "") + assert.Nil(t, err) + assert.Len(t, repos, 1) + assert.Equal(t, "two", repos[0].Repository) +} + +func TestFilterRepoMatchBadRegexp(t *testing.T) { + provider := &MockProvider{ + Repos: []*Repository{ + { + Repository: "one", + }, + }, + } + filters := []argoprojiov1alpha1.SCMProviderGeneratorFilter{ + { + RepositoryMatch: strp("("), + }, + } + _, err := ListRepos(context.Background(), provider, filters, "") + assert.NotNil(t, err) +} + +func TestFilterLabelMatchBadRegexp(t *testing.T) { + provider := &MockProvider{ + Repos: []*Repository{ + { + Repository: "one", + }, + }, + } + filters := []argoprojiov1alpha1.SCMProviderGeneratorFilter{ + { + LabelMatch: strp("("), + }, + } + _, err := ListRepos(context.Background(), provider, filters, "") + assert.NotNil(t, err) +} + +func TestFilterBranchMatch(t *testing.T) { + provider := &MockProvider{ + Repos: []*Repository{ + { + Repository: "one", + Branch: "one", + }, + { + Repository: "one", + Branch: "two", + }, + { + Repository: "two", + Branch: "one", + }, + { + Repository: "three", + Branch: "one", + }, + { + Repository: "three", + Branch: "two", + }, + }, + } + filters := []argoprojiov1alpha1.SCMProviderGeneratorFilter{ + { + BranchMatch: strp("w"), + }, + } + repos, err := ListRepos(context.Background(), provider, filters, "") + assert.Nil(t, err) + assert.Len(t, repos, 2) + assert.Equal(t, "one", repos[0].Repository) + assert.Equal(t, "two", repos[0].Branch) + assert.Equal(t, "three", repos[1].Repository) + assert.Equal(t, "two", repos[1].Branch) +} + +func TestMultiFilterAnd(t *testing.T) { + provider := &MockProvider{ + Repos: []*Repository{ + { + Repository: "one", + Labels: []string{"prod-one", "prod-two", "staging"}, + }, + { + Repository: "two", + Labels: []string{"prod-two"}, + }, + { + Repository: "three", + Labels: []string{"staging"}, + }, + }, + } + filters := []argoprojiov1alpha1.SCMProviderGeneratorFilter{ + { + RepositoryMatch: strp("w"), + LabelMatch: strp("^prod-.*$"), + }, + } + repos, err := ListRepos(context.Background(), provider, filters, "") + assert.Nil(t, err) + assert.Len(t, repos, 1) + assert.Equal(t, "two", repos[0].Repository) +} + +func TestMultiFilterOr(t *testing.T) { + provider := &MockProvider{ + Repos: []*Repository{ + { + Repository: "one", + Labels: []string{"prod-one", "prod-two", "staging"}, + }, + { + Repository: "two", + Labels: []string{"prod-two"}, + }, + { + Repository: "three", + Labels: []string{"staging"}, + }, + }, + } + filters := []argoprojiov1alpha1.SCMProviderGeneratorFilter{ + { + RepositoryMatch: strp("e"), + }, + { + LabelMatch: strp("^prod-.*$"), + }, + } + repos, err := ListRepos(context.Background(), provider, filters, "") + assert.Nil(t, err) + assert.Len(t, repos, 3) + assert.Equal(t, "one", repos[0].Repository) + assert.Equal(t, "two", repos[1].Repository) + assert.Equal(t, "three", repos[2].Repository) +} + +func TestNoFilters(t *testing.T) { + provider := &MockProvider{ + Repos: []*Repository{ + { + Repository: "one", + Labels: []string{"prod-one", "prod-two", "staging"}, + }, + { + Repository: "two", + Labels: []string{"prod-two"}, + }, + { + Repository: "three", + Labels: []string{"staging"}, + }, + }, + } + filters := []argoprojiov1alpha1.SCMProviderGeneratorFilter{} + repos, err := ListRepos(context.Background(), provider, filters, "") + assert.Nil(t, err) + assert.Len(t, repos, 3) + assert.Equal(t, "one", repos[0].Repository) + assert.Equal(t, "two", repos[1].Repository) + assert.Equal(t, "three", repos[2].Repository) +} diff --git a/test/e2e/applicationset/applicationset_test.go b/test/e2e/applicationset/applicationset_test.go index 77d69dc7..a6e898ae 100644 --- a/test/e2e/applicationset/applicationset_test.go +++ b/test/e2e/applicationset/applicationset_test.go @@ -418,3 +418,70 @@ func TestSimpleGitFilesPreserveResourcesOnDeletion(t *testing.T) { When(). Delete().Then().Expect(ApplicationsExist(expectedAppsNewMetadata)) } + +func TestSimpleSCMProviderGenerator(t *testing.T) { + expectedApp := argov1alpha1.Application{ + TypeMeta: metav1.TypeMeta{ + Kind: "Application", + APIVersion: "argoproj.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "argocd-example-apps-guestbook", + Namespace: utils.ArgoCDNamespace, + Finalizers: []string{"resources-finalizer.argocd.argoproj.io"}, + }, + Spec: argov1alpha1.ApplicationSpec{ + Project: "default", + Source: argov1alpha1.ApplicationSource{ + RepoURL: "git@github.com:argoproj/argocd-example-apps.git", + TargetRevision: "master", + Path: "guestbook", + }, + Destination: argov1alpha1.ApplicationDestination{ + Server: "https://kubernetes.default.svc", + Namespace: "guestbook", + }, + }, + } + + // Because you can't &"". + repoMatch := "example-apps" + + Given(t). + // Create an SCMProviderGenerator-based ApplicationSet + When().Create(v1alpha1.ApplicationSet{ObjectMeta: metav1.ObjectMeta{ + Name: "simple-scm-provider-generator", + }, + Spec: v1alpha1.ApplicationSetSpec{ + Template: v1alpha1.ApplicationSetTemplate{ + ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{ repository }}-guestbook"}, + Spec: argov1alpha1.ApplicationSpec{ + Project: "default", + Source: argov1alpha1.ApplicationSource{ + RepoURL: "{{ url }}", + TargetRevision: "{{ branch }}", + Path: "guestbook", + }, + Destination: argov1alpha1.ApplicationDestination{ + Server: "https://kubernetes.default.svc", + Namespace: "guestbook", + }, + }, + }, + Generators: []v1alpha1.ApplicationSetGenerator{ + { + SCMProvider: &v1alpha1.SCMProviderGenerator{ + Github: &v1alpha1.SCMProviderGeneratorGithub{ + Organization: "argoproj", + }, + Filters: []v1alpha1.SCMProviderGeneratorFilter{ + { + RepositoryMatch: &repoMatch, + }, + }, + }, + }, + }, + }, + }).Then().Expect(ApplicationsExist([]argov1alpha1.Application{expectedApp})) +}