diff --git a/api/v1alpha1/gitrepository_types.go b/api/v1alpha1/gitrepository_types.go new file mode 100644 index 00000000..2146316c --- /dev/null +++ b/api/v1alpha1/gitrepository_types.go @@ -0,0 +1,75 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type GitRepositorySpec struct { + Source GitRepositorySource `json:"source,omitempty"` + // GitURL is the base URL of Git server used for API calls. + // +kubebuilder:validation:Required + // +kubebuilder:validation:Pattern=`^https?:\/\/.+$` + GitURL string `json:"gitURL"` + // SecretRef is the reference to secret that contain Git server credentials + // +kubebuilder:validation:Optional + SecretRef SecretReference `json:"secretRef"` +} + +type GitRepositorySource struct { + // +kubebuilder:validation:Enum:=argocd;backstage;crossplane;gitea;nginx + // +kubebuilder:validation:Optional + EmbeddedAppName string `json:"embeddedAppName"` + // Path is the absolute path to directory that contains Kustomize structure or raw manifests. + // This is required when Type is set to local. + // +kubebuilder:validation:Optional + Path string `json:"path"` + // Type is the source type. + // +kubebuilder:validation:Enum:=local;embedded + // +kubebuilder:default:=embedded + Type string `json:"type"` +} + +type SecretReference struct { + Name string `json:"name"` + Namespace string `json:"namespace"` +} + +type Commit struct { + // Hash is the digest of the most recent commit + // +kubebuilder:validation:Optional + Hash string `json:"hash"` +} + +type GitRepositoryStatus struct { + // LatestCommit is the most recent commit known to the controller + // +kubebuilder:validation:Optional + LatestCommit Commit `json:"commit"` + // ExternalGitRepositoryUrl is the url for the in-cluster repository accessible from local machine. + // +kubebuilder:validation:Optional + ExternalGitRepositoryUrl string `json:"externalGitRepositoryUrl"` + // InternalGitRepositoryUrl is the url for the in-cluster repository accessible within the cluster. + // +kubebuilder:validation:Optional + InternalGitRepositoryUrl string `json:"internalGitRepositoryUrl"` + // Path is the path within the repository that contains the files. + // +kubebuilder:validation:Optional + Path string `json:"path"` + + Synced bool `json:"synced"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type GitRepository struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GitRepositorySpec `json:"spec,omitempty"` + Status GitRepositoryStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +type GitRepositoryList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GitRepository `json:"items"` +} diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go index 3714e3e7..4a493739 100644 --- a/api/v1alpha1/groupversion_info.go +++ b/api/v1alpha1/groupversion_info.go @@ -21,4 +21,5 @@ var ( func init() { SchemeBuilder.Register(&Localbuild{}, &LocalbuildList{}) SchemeBuilder.Register(&GitServer{}, &GitServerList{}) + SchemeBuilder.Register(&GitRepository{}, &GitRepositoryList{}) } diff --git a/api/v1alpha1/localbuild_types.go b/api/v1alpha1/localbuild_types.go index 227cb6d6..f76169ea 100644 --- a/api/v1alpha1/localbuild_types.go +++ b/api/v1alpha1/localbuild_types.go @@ -39,14 +39,27 @@ type LocalbuildSpec struct { type LocalbuildStatus struct { // ObservedGeneration is the 'Generation' of the Service that was last processed by the controller. // +optional - ObservedGeneration int64 `json:"observedGeneration,omitempty"` - - GitServerAvailable bool `json:"gitServerAvailable,omitempty"` - ArgoAvailable bool `json:"argoAvailable,omitempty"` - NginxAvailable bool `json:"nginxAvailable,omitempty"` - GiteaAvailable bool `json:"giteaAvailable,omitempty"` - ArgoAppsCreated bool `json:"argoAppsCreated,omitempty"` - GiteaSecretName string `json:"giteaSecret,omitempty"` + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + ArgoCD ArgoCDStatus `json:"ArgoCD,omitempty"` + Nginx NginxStatus `json:"nginx,omitempty"` + Gitea GiteaStatus `json:"gitea,omitempty"` +} + +type GiteaStatus struct { + Available bool `json:"available,omitempty"` + ExternalURL string `json:"externalURL,omitempty"` + InternalURL string `json:"internalURL,omitempty"` + AdminUserSecretName string `json:"adminUserSecretNameecret,omitempty"` + AdminUserSecretNamespace string `json:"adminUserSecretNamespace,omitempty"` +} + +type ArgoCDStatus struct { + Available bool `json:"available,omitempty"` + AppsCreated bool `json:"appsCreated,omitempty"` +} + +type NginxStatus struct { + Available bool `json:"available,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 53926bc5..6399c8b1 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -25,6 +25,21 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArgoCDStatus) DeepCopyInto(out *ArgoCDStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDStatus. +func (in *ArgoCDStatus) DeepCopy() *ArgoCDStatus { + if in == nil { + return nil + } + out := new(ArgoCDStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ArgoPackageConfigSpec) DeepCopyInto(out *ArgoPackageConfigSpec) { *out = *in @@ -40,6 +55,21 @@ func (in *ArgoPackageConfigSpec) DeepCopy() *ArgoPackageConfigSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Commit) DeepCopyInto(out *Commit) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Commit. +func (in *Commit) DeepCopy() *Commit { + if in == nil { + return nil + } + out := new(Commit) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EmbeddedArgoApplicationsPackageConfigSpec) DeepCopyInto(out *EmbeddedArgoApplicationsPackageConfigSpec) { *out = *in @@ -70,6 +100,113 @@ func (in *GitConfigSpec) DeepCopy() *GitConfigSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitRepository) DeepCopyInto(out *GitRepository) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepository. +func (in *GitRepository) DeepCopy() *GitRepository { + if in == nil { + return nil + } + out := new(GitRepository) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GitRepository) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitRepositoryList) DeepCopyInto(out *GitRepositoryList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GitRepository, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositoryList. +func (in *GitRepositoryList) DeepCopy() *GitRepositoryList { + if in == nil { + return nil + } + out := new(GitRepositoryList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GitRepositoryList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitRepositorySource) DeepCopyInto(out *GitRepositorySource) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositorySource. +func (in *GitRepositorySource) DeepCopy() *GitRepositorySource { + if in == nil { + return nil + } + out := new(GitRepositorySource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitRepositorySpec) DeepCopyInto(out *GitRepositorySpec) { + *out = *in + out.Source = in.Source + out.SecretRef = in.SecretRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositorySpec. +func (in *GitRepositorySpec) DeepCopy() *GitRepositorySpec { + if in == nil { + return nil + } + out := new(GitRepositorySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitRepositoryStatus) DeepCopyInto(out *GitRepositoryStatus) { + *out = *in + out.LatestCommit = in.LatestCommit +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositoryStatus. +func (in *GitRepositoryStatus) DeepCopy() *GitRepositoryStatus { + if in == nil { + return nil + } + out := new(GitRepositoryStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GitServer) DeepCopyInto(out *GitServer) { *out = *in @@ -175,6 +312,21 @@ func (in *GitServerStatus) DeepCopy() *GitServerStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GiteaStatus) DeepCopyInto(out *GiteaStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GiteaStatus. +func (in *GiteaStatus) DeepCopy() *GiteaStatus { + if in == nil { + return nil + } + out := new(GiteaStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Localbuild) DeepCopyInto(out *Localbuild) { *out = *in @@ -253,6 +405,9 @@ func (in *LocalbuildSpec) DeepCopy() *LocalbuildSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LocalbuildStatus) DeepCopyInto(out *LocalbuildStatus) { *out = *in + out.ArgoCD = in.ArgoCD + out.Nginx = in.Nginx + out.Gitea = in.Gitea } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalbuildStatus. @@ -265,6 +420,21 @@ func (in *LocalbuildStatus) DeepCopy() *LocalbuildStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NginxStatus) DeepCopyInto(out *NginxStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NginxStatus. +func (in *NginxStatus) DeepCopy() *NginxStatus { + if in == nil { + return nil + } + out := new(NginxStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PackageConfigsSpec) DeepCopyInto(out *PackageConfigsSpec) { *out = *in @@ -282,3 +452,18 @@ func (in *PackageConfigsSpec) DeepCopy() *PackageConfigsSpec { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretReference) DeepCopyInto(out *SecretReference) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretReference. +func (in *SecretReference) DeepCopy() *SecretReference { + if in == nil { + return nil + } + out := new(SecretReference) + in.DeepCopyInto(out) + return out +} diff --git a/go.mod b/go.mod index 507b2feb..72a4f9b8 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,11 @@ module github.com/cnoe-io/idpbuilder go 1.20 require ( + code.gitea.io/sdk/gitea v0.16.0 github.com/argoproj/argo-cd/v2 v2.8.3 github.com/docker/docker v24.0.6+incompatible github.com/docker/go-connections v0.4.0 + github.com/go-git/go-git/v5 v5.10.0 github.com/google/go-cmp v0.5.9 github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 @@ -20,12 +22,13 @@ require ( require ( cloud.google.com/go/compute v1.19.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect + dario.cat/mergo v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/BurntSushi/toml v1.0.0 // indirect github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/alessio/shellescape v1.4.1 // indirect github.com/argoproj/gitops-engine v0.7.1-0.20230607163028-425d65e07695 // indirect @@ -38,7 +41,9 @@ require ( github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/containerd/containerd v1.7.5 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davidmz/go-pageant v1.0.2 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect @@ -51,9 +56,9 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fvbommel/sortorder v1.0.1 // indirect github.com/go-errors/errors v1.4.2 // indirect + github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.4.1 // indirect - github.com/go-git/go-git/v5 v5.7.0 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/zapr v1.2.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect @@ -74,6 +79,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/hashicorp/go-version v1.5.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect @@ -114,7 +120,7 @@ require ( github.com/russross/blackfriday v1.6.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/skeema/knownhosts v1.1.1 // indirect + github.com/skeema/knownhosts v1.2.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/vmihailenco/go-tinylfu v0.2.2 // indirect github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect @@ -125,16 +131,16 @@ require ( go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.1 // indirect - golang.org/x/crypto v0.10.0 // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.11.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.9.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/term v0.9.0 // indirect - golang.org/x/text v0.10.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/tools v0.13.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect diff --git a/go.sum b/go.sum index 259ed52f..69833dbb 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,10 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo 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= +code.gitea.io/sdk/gitea v0.16.0 h1:gAfssETO1Hv9QbE+/nhWu7EjoFQYKt6kPoyDytQgw00= +code.gitea.io/sdk/gitea v0.16.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= @@ -79,8 +83,8 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0 github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= -github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek= -github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= @@ -202,10 +206,14 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= +github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= @@ -227,7 +235,7 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= @@ -276,13 +284,15 @@ github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= +github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= -github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= -github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE= -github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git/v5 v5.10.0 h1:F0x3xXrAWmhwtzoCokU4IMPcBdncG+HAAqi9FcOOjbQ= +github.com/go-git/go-git/v5 v5.10.0/go.mod h1:1FOZ/pQnqw24ghP2n7cunVl0ON55BsjPYvhWHvZGhoo= 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= @@ -439,6 +449,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= +github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -616,8 +628,8 @@ github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8lu github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= -github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= @@ -694,8 +706,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -719,8 +731,8 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= -github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= +github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= +github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -860,15 +872,18 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -919,8 +934,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -983,8 +998,9 @@ golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 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= @@ -1114,8 +1130,9 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1125,8 +1142,9 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 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= @@ -1142,8 +1160,9 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1219,8 +1238,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/controllers/gitrepository/controller.go b/pkg/controllers/gitrepository/controller.go new file mode 100644 index 00000000..b2d5a751 --- /dev/null +++ b/pkg/controllers/gitrepository/controller.go @@ -0,0 +1,283 @@ +package gitrepository + +import ( + "context" + "fmt" + "os" + "path/filepath" + "time" + + "code.gitea.io/sdk/gitea" + "github.com/cnoe-io/idpbuilder/api/v1alpha1" + "github.com/cnoe-io/idpbuilder/pkg/controllers/localbuild" + "github.com/cnoe-io/idpbuilder/pkg/util" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/plumbing/transport/http" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +const ( + DefaultBranchName = "main" + giteaAdminUsernameKey = "username" + giteaAdminPasswordKey = "password" + requeueTime = time.Second * 30 + gitCommitAuthorName = "git-reconciler" + gitCommitAuthorEmail = "idpbuilder-agent@cnoe.io" +) + +type GiteaClientFunc func(url string, options ...gitea.ClientOption) (GiteaClient, error) + +func NewGiteaClient(url string, options ...gitea.ClientOption) (GiteaClient, error) { + return gitea.NewClient(url, options...) +} + +type RepositoryReconciler struct { + client.Client + GiteaClientFunc GiteaClientFunc + Recorder record.EventRecorder + Scheme *runtime.Scheme +} + +func getRepositoryName(repo v1alpha1.GitRepository) string { + return fmt.Sprintf("%s-%s", repo.Namespace, repo.Name) +} + +func getOrganizationName(repo v1alpha1.GitRepository) string { + return "giteaAdmin" +} + +func (r *RepositoryReconciler) getCredentials(ctx context.Context, repo *v1alpha1.GitRepository) (string, string, error) { + var secret v1.Secret + err := r.Client.Get(ctx, types.NamespacedName{ + Namespace: repo.Spec.SecretRef.Namespace, + Name: repo.Spec.SecretRef.Name, + }, &secret) + if err != nil { + return "", "", err + } + + username, ok := secret.Data[giteaAdminUsernameKey] + if !ok { + return "", "", fmt.Errorf("%s key not found in secret %s in %s ns", giteaAdminUsernameKey, repo.Spec.SecretRef.Name, repo.Spec.SecretRef.Namespace) + } + password, ok := secret.Data[giteaAdminPasswordKey] + if !ok { + return "", "", fmt.Errorf("%s key not found in secret %s in %s ns", giteaAdminPasswordKey, repo.Spec.SecretRef.Name, repo.Spec.SecretRef.Namespace) + } + return string(username), string(password), nil +} + +func (r *RepositoryReconciler) getBasicAuth(ctx context.Context, repo *v1alpha1.GitRepository) (http.BasicAuth, error) { + u, p, err := r.getCredentials(ctx, repo) + if err != nil { + return http.BasicAuth{}, err + } + return http.BasicAuth{ + Username: u, + Password: p, + }, nil +} + +func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := log.FromContext(ctx) + + var gitRepo v1alpha1.GitRepository + err := r.Get(ctx, req.NamespacedName, &gitRepo) + if err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if !r.shouldProcess(gitRepo) { + return ctrl.Result{Requeue: false}, nil + } + + logger.Info("reconciling GitRepository", "name", req.Name, "namespace", req.Namespace) + defer r.postProcessReconcile(ctx, req, &gitRepo) + + result, err := r.reconcileGitRepo(ctx, &gitRepo) + if err != nil { + r.Recorder.Event(&gitRepo, "Warning", "reconcile error", err.Error()) + } else { + r.Recorder.Event(&gitRepo, "Normal", "reconcile success", "Successfully reconciled") + } + + return result, err +} + +func (r *RepositoryReconciler) postProcessReconcile(ctx context.Context, req ctrl.Request, repo *v1alpha1.GitRepository) { + logger := log.FromContext(ctx) + err := r.Status().Update(ctx, repo) + if err != nil { + logger.Error(err, "failed updating repo status") + } +} + +func (r *RepositoryReconciler) reconcileGitRepo(ctx context.Context, repo *v1alpha1.GitRepository) (ctrl.Result, error) { + logger := log.FromContext(ctx) + logger.Info("reconciling", "name", repo.Name, "dir", repo.Spec.Source) + giteaClient, err := r.GiteaClientFunc(repo.Spec.GitURL) + if err != nil { + return ctrl.Result{Requeue: true, RequeueAfter: requeueTime}, fmt.Errorf("failed to get gitea client: %w", err) + } + + user, pass, err := r.getCredentials(ctx, repo) + if err != nil { + return ctrl.Result{Requeue: true, RequeueAfter: requeueTime}, fmt.Errorf("failed to get gitea credentials: %w", err) + } + + giteaClient.SetBasicAuth(user, pass) + giteaClient.SetContext(ctx) + + giteaRepo, err := reconcileRepo(giteaClient, repo) + if err != nil { + return ctrl.Result{Requeue: true, RequeueAfter: requeueTime}, fmt.Errorf("failed to create or update repo %w", err) + } + repo.Status.ExternalGitRepositoryUrl = giteaRepo.CloneURL + + err = r.reconcileRepoContent(ctx, repo, giteaRepo) + if err != nil { + return ctrl.Result{Requeue: true, RequeueAfter: requeueTime}, fmt.Errorf("failed to reconcile repo content %w", err) + } + repo.Status.Synced = true + return ctrl.Result{Requeue: true, RequeueAfter: requeueTime}, nil +} + +func (r *RepositoryReconciler) reconcileRepoContent(ctx context.Context, repo *v1alpha1.GitRepository, giteaRepo *gitea.Repository) error { + tempDir, err := os.MkdirTemp("", fmt.Sprintf("%s-%s", repo.Name, repo.Namespace)) + defer os.RemoveAll(tempDir) + if err != nil { + return fmt.Errorf("creating temporary directory: %w", err) + } + + clonedRepo, err := git.PlainClone(tempDir, false, &git.CloneOptions{ + URL: giteaRepo.CloneURL, + NoCheckout: true, + }) + if err != nil { + return fmt.Errorf("cloning repo: %w", err) + } + + err = writeRepoContents(repo, tempDir) + if err != nil { + return err + } + + tree, err := clonedRepo.Worktree() + if err != nil { + return fmt.Errorf("getting git worktree: %w", err) + } + + err = tree.AddGlob("*") + if err != nil { + return fmt.Errorf("adding git files: %w", err) + } + + status, err := tree.Status() + if err != nil { + return fmt.Errorf("getting git status: %w", err) + } + + if status.IsClean() { + h, _ := clonedRepo.Head() + repo.Status.LatestCommit.Hash = h.Hash().String() + return nil + } + + commit, err := tree.Commit(fmt.Sprintf("updated from %s", repo.Spec.Source.Path), &git.CommitOptions{ + All: true, + AllowEmptyCommits: false, + Author: &object.Signature{ + Name: gitCommitAuthorName, + Email: gitCommitAuthorEmail, + When: time.Now(), + }, + }) + if err != nil { + return fmt.Errorf("committing git files: %w", err) + } + + auth, err := r.getBasicAuth(ctx, repo) + if err != nil { + return fmt.Errorf("getting basic auth: %w", err) + } + err = clonedRepo.Push(&git.PushOptions{ + Auth: &auth, + }) + if err != nil { + return fmt.Errorf("pushing to git: %w", err) + } + + repo.Status.LatestCommit.Hash = commit.String() + + return nil +} + +func reconcileRepo(giteaClient GiteaClient, repo *v1alpha1.GitRepository) (*gitea.Repository, error) { + resp, repoResp, err := giteaClient.GetRepo(getOrganizationName(*repo), getRepositoryName(*repo)) + if err != nil { + if repoResp.StatusCode == 404 { + createResp, _, CErr := giteaClient.CreateRepo(gitea.CreateRepoOption{ + Name: getRepositoryName(*repo), + Description: fmt.Sprintf("created by Git Repository controller for %s in %s namespace", repo.Name, repo.Namespace), + // we should reconsider this when targeting non-local clusters. + Private: false, + DefaultBranch: DefaultBranchName, + AutoInit: true, + }) + if CErr != nil { + return &gitea.Repository{}, fmt.Errorf("failed to create git repository: %w", CErr) + } + return createResp, nil + } + } + return resp, nil +} + +func (r *RepositoryReconciler) SetupWithManager(mgr ctrl.Manager, notifyChan chan event.GenericEvent) error { + // TODO: should use notifyChan to trigger reconcile when FS changes + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.GitRepository{}). + Complete(r) +} + +func (r *RepositoryReconciler) shouldProcess(repo v1alpha1.GitRepository) bool { + if repo.Spec.Source.Type == "local" && !filepath.IsAbs(repo.Spec.Source.Path) { + return false + } + // embedded fs does not change + if repo.Spec.Source.Type == "embedded" && repo.Status.Synced { + return false + } + return true +} + +func writeRepoContents(repo *v1alpha1.GitRepository, dstPath string) error { + if repo.Spec.Source.EmbeddedAppName != "" { + resources, err := localbuild.GetEmbeddedRawInstallResources(repo.Spec.Source.EmbeddedAppName) + if err != nil { + return fmt.Errorf("getting embedded resource; %w", err) + } + for i := range resources { + filePath := filepath.Join(dstPath, fmt.Sprintf("resource%d.yaml", i)) + err = os.WriteFile(filePath, resources[i], 0644) + if err != nil { + return fmt.Errorf("writing embedded resource; %w", err) + } + } + return nil + } + + err := util.CopyDirectory(repo.Spec.Source.Path, dstPath) + if err != nil { + return fmt.Errorf("copying files: %w", err) + } + return nil +} diff --git a/pkg/controllers/gitrepository/controller_test.go b/pkg/controllers/gitrepository/controller_test.go new file mode 100644 index 00000000..78c73e7c --- /dev/null +++ b/pkg/controllers/gitrepository/controller_test.go @@ -0,0 +1,377 @@ +package gitrepository + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "testing" + "time" + + "code.gitea.io/sdk/gitea" + "github.com/cnoe-io/idpbuilder/api/v1alpha1" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/filemode" + "github.com/go-git/go-git/v5/plumbing/object" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const addFileContent = "added\n" + +type mockGitea struct { + GiteaClient + getRepo func() (*gitea.Repository, *gitea.Response, error) + createRepo func() (*gitea.Repository, *gitea.Response, error) +} + +func (g mockGitea) SetBasicAuth(user, pass string) {} + +func (g mockGitea) SetContext(ctx context.Context) {} + +func (g mockGitea) CreateOrgRepo(org string, opt gitea.CreateRepoOption) (*gitea.Repository, *gitea.Response, error) { + if g.createRepo != nil { + return g.createRepo() + } + return &gitea.Repository{}, &gitea.Response{}, nil +} + +func (g mockGitea) GetRepo(owner, reponame string) (*gitea.Repository, *gitea.Response, error) { + if g.getRepo != nil { + return g.getRepo() + } + return &gitea.Repository{}, &gitea.Response{}, nil +} + +type expect struct { + resource v1alpha1.GitRepositoryStatus + err error +} + +type testCase struct { + giteaClient func(url string, options ...gitea.ClientOption) (GiteaClient, error) + input v1alpha1.GitRepository + expect expect +} + +type fakeClient struct { + client.Client +} + +func (f *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error { + s := obj.(*v1.Secret) + s.Data = map[string][]byte{ + giteaAdminUsernameKey: []byte("abc"), + giteaAdminPasswordKey: []byte("abc"), + } + return nil +} + +func setUpLocalRepo() (string, string, error) { + repoDir, err := os.MkdirTemp("", fmt.Sprintf("test")) + if err != nil { + return "", "", fmt.Errorf("creating temporary directory: %w", err) + } + // create a repo for pushing. MUST BE BARE + repo, err := git.PlainInit(repoDir, true) + if err != nil { + return "", "", fmt.Errorf("repo init: %w", err) + } + + // init it with a static file (in-memory), set default branch name, then get the hash + defaultBranchName := plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", DefaultBranchName)) + + repoConfig, _ := repo.Config() + repoConfig.Init.DefaultBranch = DefaultBranchName + repo.SetConfig(repoConfig) + + h := plumbing.NewSymbolicReference(plumbing.HEAD, defaultBranchName) + repo.Storer.SetReference(h) + + fileObject := plumbing.MemoryObject{} + fileObject.SetType(plumbing.BlobObject) + w, _ := fileObject.Writer() + + file, err := os.ReadFile("test/resources/file1") + if err != nil { + return "", "", fmt.Errorf("reading file from resources dir: %w", err) + } + w.Write(file) + w.Close() + + fileHash, _ := repo.Storer.SetEncodedObject(&fileObject) + + treeEntry := object.TreeEntry{ + Name: "file1", + Mode: filemode.Regular, + Hash: fileHash, + } + + tree := object.Tree{ + Entries: []object.TreeEntry{treeEntry}, + } + + treeObject := plumbing.MemoryObject{} + tree.Encode(&treeObject) + + initHash, _ := repo.Storer.SetEncodedObject(&treeObject) + + commit := object.Commit{ + Author: object.Signature{ + Name: gitCommitAuthorName, + Email: gitCommitAuthorEmail, + When: time.Now(), + }, + Message: "init", + TreeHash: initHash, + } + + commitObject := plumbing.MemoryObject{} + commit.Encode(&commitObject) + + commitHash, _ := repo.Storer.SetEncodedObject(&commitObject) + + repo.Storer.SetReference(plumbing.NewHashReference(defaultBranchName, commitHash)) + + return repoDir, commitHash.String(), nil +} + +func setupDir() (string, error) { + tempDir, err := os.MkdirTemp("", fmt.Sprintf("test")) + if err != nil { + return "", fmt.Errorf("creating temporary directory: %w", err) + } + + file, err := os.ReadFile("test/resources/file1") + if err != nil { + return "", fmt.Errorf("reading file from resources dir: %w", err) + } + err = os.WriteFile(filepath.Join(tempDir, "file1"), file, 0644) + if err != nil { + return "", fmt.Errorf("writing file to temp dir: %w", err) + } + + err = os.WriteFile(filepath.Join(tempDir, "add"), []byte(addFileContent), 0644) + if err != nil { + return "", fmt.Errorf("writing file: %w", err) + } + + return tempDir, nil +} + +func TestGitRepositoryContentReconcile(t *testing.T) { + ctx := context.Background() + dir, _, err := setUpLocalRepo() + defer os.RemoveAll(dir) + if err != nil { + t.Fatalf("failed setting up local git repo: %v", err) + } + + addDir, err := setupDir() + defer os.RemoveAll(addDir) + if err != nil { + t.Fatalf("failed to set up dirs: %v", err) + } + + m := metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + } + resource := v1alpha1.GitRepository{ + ObjectMeta: m, + Spec: v1alpha1.GitRepositorySpec{ + Source: v1alpha1.GitRepositorySource{ + Path: addDir, + Type: "local", + }, + }, + } + + t.Run("files modified", func(t *testing.T) { + reconciler := RepositoryReconciler{ + Client: &fakeClient{}, + GiteaClientFunc: func(url string, options ...gitea.ClientOption) (GiteaClient, error) { + return mockGitea{}, nil + }, + } + // add file to source directory, reconcile, clone the repo and check if the added file exists + err := reconciler.reconcileRepoContent(ctx, &resource, &gitea.Repository{CloneURL: dir}) + if err != nil { + t.Fatalf("failed adding %v", err) + } + tmpDir, _ := os.MkdirTemp("", "add") + defer os.RemoveAll(tmpDir) + repo, _ := git.PlainClone(tmpDir, false, &git.CloneOptions{ + URL: dir, + }) + c, err := os.ReadFile(filepath.Join(tmpDir, "add")) + if err != nil { + t.Fatalf("failed to read file at %s. %v", filepath.Join(tmpDir, "add"), err) + } + if string(c) != addFileContent { + t.Fatalf("expected %s, got %s", addFileContent, c) + } + + // remove added file, reconcile, pull, check if the file is removed + err = os.Remove(filepath.Join(addDir, "add")) + if err != nil { + t.Fatalf("failed to remove added file %v", err) + } + err = reconciler.reconcileRepoContent(ctx, &resource, &gitea.Repository{CloneURL: dir}) + if err != nil { + t.Fatalf("failed removing %v", err) + } + w, _ := repo.Worktree() + err = w.Pull(&git.PullOptions{}) + if err != nil { + t.Fatalf("failed pulling changes %v", err) + } + _, err = os.Stat(filepath.Join(tmpDir, "add")) + if err == nil { + t.Fatalf("file should not exist") + } + if !errors.Is(err, os.ErrNotExist) { + t.Fatalf("received unexpected error %v", err) + } + }) +} + +func TestGitRepositoryContentReconcileEmbedded(t *testing.T) { + ctx := context.Background() + dir, _, err := setUpLocalRepo() + defer os.RemoveAll(dir) + if err != nil { + t.Fatalf("failed setting up local git repo: %v", err) + } + + m := metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + } + resource := v1alpha1.GitRepository{ + ObjectMeta: m, + Spec: v1alpha1.GitRepositorySpec{ + Source: v1alpha1.GitRepositorySource{ + EmbeddedAppName: "nginx", + Type: "embedded", + }, + }, + } + + t.Run("should sync embedded", func(t *testing.T) { + reconciler := RepositoryReconciler{ + Client: &fakeClient{}, + GiteaClientFunc: func(url string, options ...gitea.ClientOption) (GiteaClient, error) { + return mockGitea{}, nil + }, + } + + err := reconciler.reconcileRepoContent(ctx, &resource, &gitea.Repository{CloneURL: dir}) + if err != nil { + t.Fatalf("failed adding %v", err) + } + }) +} + +func TestGitRepositoryReconcile(t *testing.T) { + dir, hash, err := setUpLocalRepo() + defer os.RemoveAll(dir) + if err != nil { + t.Fatalf("failed setting up local git repo: %v", err) + } + resourcePath, err := filepath.Abs("./test/resources") + if err != nil { + t.Fatalf("failed to get absolute path: %v", err) + } + + addDir, err := setupDir() + defer os.RemoveAll(addDir) + if err != nil { + t.Fatalf("failed to set up dirs: %v", err) + } + + m := metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + } + + cases := map[string]testCase{ + "no op": { + giteaClient: func(url string, options ...gitea.ClientOption) (GiteaClient, error) { + return mockGitea{ + getRepo: func() (*gitea.Repository, *gitea.Response, error) { + return &gitea.Repository{CloneURL: dir}, nil, nil + }, + }, nil + }, + input: v1alpha1.GitRepository{ + ObjectMeta: m, + Spec: v1alpha1.GitRepositorySpec{ + Source: v1alpha1.GitRepositorySource{ + Path: resourcePath, + Type: "local", + }, + }, + }, + expect: expect{ + resource: v1alpha1.GitRepositoryStatus{ + ExternalGitRepositoryUrl: dir, + LatestCommit: v1alpha1.Commit{Hash: hash}, + Synced: true, + }, + }, + }, + "update": { + giteaClient: func(url string, options ...gitea.ClientOption) (GiteaClient, error) { + return mockGitea{ + getRepo: func() (*gitea.Repository, *gitea.Response, error) { + return &gitea.Repository{CloneURL: dir}, nil, nil + }, + }, nil + }, + input: v1alpha1.GitRepository{ + ObjectMeta: m, + Spec: v1alpha1.GitRepositorySpec{ + Source: v1alpha1.GitRepositorySource{ + Path: addDir, + Type: "local", + }, + }, + }, + expect: expect{ + resource: v1alpha1.GitRepositoryStatus{ + ExternalGitRepositoryUrl: dir, + Synced: true, + }, + }, + }, + } + + ctx := context.Background() + + for k := range cases { + v := cases[k] + t.Run(k, func(t *testing.T) { + reconciler := RepositoryReconciler{ + Client: &fakeClient{}, + GiteaClientFunc: v.giteaClient, + } + _, err := reconciler.reconcileGitRepo(ctx, &v.input) + if v.expect.err == nil && err != nil { + t.Fatalf("failed %s: %v", k, err) + } + + if v.expect.resource.LatestCommit.Hash == "" { + v.expect.resource.LatestCommit.Hash = v.input.Status.LatestCommit.Hash + } + + if !reflect.DeepEqual(v.input.Status, v.expect.resource) { + t.Fatalf("objects not equal") + } + }) + } +} diff --git a/pkg/controllers/gitrepository/git_repository.go b/pkg/controllers/gitrepository/git_repository.go new file mode 100644 index 00000000..8cf18c7f --- /dev/null +++ b/pkg/controllers/gitrepository/git_repository.go @@ -0,0 +1,19 @@ +package gitrepository + +import ( + "context" + + "code.gitea.io/sdk/gitea" +) + +type GiteaClient interface { + CreateAccessToken(option gitea.CreateAccessTokenOption) (*gitea.AccessToken, *gitea.Response, error) + CreateOrg(opt gitea.CreateOrgOption) (*gitea.Organization, *gitea.Response, error) + CreateRepo(opt gitea.CreateRepoOption) (*gitea.Repository, *gitea.Response, error) + DeleteOrg(orgname string) (*gitea.Response, error) + DeleteRepo(owner, repo string) (*gitea.Response, error) + GetOrg(orgname string) (*gitea.Organization, *gitea.Response, error) + GetRepo(owner, reponame string) (*gitea.Repository, *gitea.Response, error) + SetBasicAuth(username, password string) + SetContext(ctx context.Context) +} diff --git a/pkg/controllers/gitrepository/test/resources/file1 b/pkg/controllers/gitrepository/test/resources/file1 new file mode 100644 index 00000000..b6fc4c62 --- /dev/null +++ b/pkg/controllers/gitrepository/test/resources/file1 @@ -0,0 +1 @@ +hello \ No newline at end of file diff --git a/pkg/controllers/localbuild/argo.go b/pkg/controllers/localbuild/argo.go index 94e42158..8da63361 100644 --- a/pkg/controllers/localbuild/argo.go +++ b/pkg/controllers/localbuild/argo.go @@ -5,6 +5,7 @@ import ( "embed" "github.com/cnoe-io/idpbuilder/api/v1alpha1" + "github.com/cnoe-io/idpbuilder/pkg/util" "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" ) @@ -16,6 +17,10 @@ const ( argocdNamespace string = "argocd" ) +func RawArgocdInstallResources() ([][]byte, error) { + return util.ConvertFSToBytes(installArgoFS, "resources/argo") +} + func (r *LocalbuildReconciler) ReconcileArgo(ctx context.Context, req ctrl.Request, resource *v1alpha1.Localbuild) (ctrl.Result, error) { argocd := EmbeddedInstallation{ name: "Argo CD", @@ -46,6 +51,6 @@ func (r *LocalbuildReconciler) ReconcileArgo(ctx context.Context, req ctrl.Reque return result, err } - resource.Status.ArgoAvailable = true + resource.Status.ArgoCD.Available = true return ctrl.Result{}, nil } diff --git a/pkg/controllers/localbuild/controller.go b/pkg/controllers/localbuild/controller.go index 2ffb941d..04a2047a 100644 --- a/pkg/controllers/localbuild/controller.go +++ b/pkg/controllers/localbuild/controller.go @@ -10,6 +10,7 @@ import ( "github.com/cnoe-io/idpbuilder/globals" "github.com/cnoe-io/idpbuilder/pkg/apps" "github.com/cnoe-io/idpbuilder/pkg/resources/localbuild" + "github.com/cnoe-io/idpbuilder/pkg/util" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -21,11 +22,10 @@ import ( ) const ( - defaultArgoCDProjectName string = "default" - EmbeddedGitServerName string = "embedded" - gitServerDeploymentContainerName string = "httpd" - gitServerIngressHostnameBase string = ".cnoe.localtest.me" - repoUrlFmt string = "http://%s.%s.svc/idpbuilder-resources.git" + defaultArgoCDProjectName string = "default" + EmbeddedGitServerName string = "embedded" + gitServerIngressHostnameBase string = ".cnoe.localtest.me" + repoUrlFmt string = "http://%s.%s.svc/idpbuilder-resources.git" ) func getRepoUrl(resource *v1alpha1.GitServer) string { @@ -141,7 +141,7 @@ func (r *LocalbuildReconciler) ReconcileEmbeddedGitServer(ctx context.Context, r log := log.FromContext(ctx) // Bail if argo is not yet available - if !resource.Status.ArgoAvailable { + if !resource.Status.ArgoCD.Available { log.Info("argo not yet available, not installing embedded git server") return ctrl.Result{}, nil } @@ -252,6 +252,7 @@ func (r *LocalbuildReconciler) ReconcileArgoAppsWithGitServer(ctx context.Contex repoUrl, embedApp.Path, defaultArgoCDProjectName, + "argocd", nil, ) @@ -264,7 +265,7 @@ func (r *LocalbuildReconciler) ReconcileArgoAppsWithGitServer(ctx context.Contex } } - resource.Status.ArgoAppsCreated = true + resource.Status.ArgoCD.AppsCreated = true r.shouldShutdown = true return ctrl.Result{}, nil @@ -278,9 +279,123 @@ func (r *LocalbuildReconciler) SetupWithManager(mgr ctrl.Manager) error { } func (r *LocalbuildReconciler) ReconcileArgoAppsWithGitea(ctx context.Context, req ctrl.Request, resource *v1alpha1.Localbuild) (ctrl.Result, error) { - log := log.FromContext(ctx) + logger := log.FromContext(ctx) + logger.Info("installing bootstrap apps to ArgoCD") + + // push bootstrap app manifests to Gitea. let ArgoCD take over + // will need a way to filter them based on user input + bootStrapApps := []string{"argocd", "nginx", "gitea"} + for _, n := range bootStrapApps { + result, err := r.reconcileEmbeddedApp(ctx, n, resource) + if err != nil { + return result, fmt.Errorf("reconciling bootstrap apps %w", err) + } + } + // do the same for embedded applications + for _, embedApp := range apps.EmbedApps { + result, err := r.reconcileEmbeddedApp(ctx, embedApp.Name, resource) + if err != nil { + return result, fmt.Errorf("reconciling embedded apps %w", err) + } + } + + shutdown, err := r.shouldShutDown(ctx, resource) + if err != nil { + return ctrl.Result{Requeue: true}, err + } + r.shouldShutdown = shutdown + + return ctrl.Result{RequeueAfter: time.Second * 30}, nil +} + +func (r *LocalbuildReconciler) reconcileEmbeddedApp(ctx context.Context, appName string, resource *v1alpha1.Localbuild) (ctrl.Result, error) { + logger := log.FromContext(ctx) + + logger.Info("Ensuring Argo Application", "name", appName) + repo := &v1alpha1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: appName, + Namespace: globals.GetProjectNamespace(resource.Name), + }, + Spec: v1alpha1.GitRepositorySpec{ + Source: v1alpha1.GitRepositorySource{ + EmbeddedAppName: appName, + Type: "embedded", + }, + GitURL: resource.Status.Gitea.ExternalURL, + SecretRef: v1alpha1.SecretReference{ + Name: resource.Status.Gitea.AdminUserSecretName, + Namespace: resource.Status.Gitea.AdminUserSecretNamespace, + }, + }, + } + + _, err := controllerutil.CreateOrUpdate(ctx, r.Client, repo, func() error { + if err := controllerutil.SetControllerReference(resource, repo, r.Scheme); err != nil { + return err + } + return nil + }) + if err != nil { + return ctrl.Result{}, fmt.Errorf("creating %s repo CR: %w", appName, err) + } + + app := &argov1alpha1.Application{ + ObjectMeta: metav1.ObjectMeta{ + Name: appName, + Namespace: "argocd", + }, + } + + if err := controllerutil.SetControllerReference(resource, app, r.Scheme); err != nil { + return ctrl.Result{}, err + } + + err = r.Client.Get(ctx, client.ObjectKeyFromObject(app), app) + if err != nil && errors.IsNotFound(err) { + localbuild.SetApplicationSpec( + app, + getRepositoryURL(repo.Namespace, repo.Name, resource.Status.Gitea.InternalURL), + ".", + defaultArgoCDProjectName, + appName, + nil, + ) + err = r.Client.Create(ctx, app) + if err != nil { + return ctrl.Result{}, fmt.Errorf("creating %s app CR: %w", appName, err) + } + } - log.Info("TODO(nimak): enable installing Argo Apps") - r.shouldShutdown = true return ctrl.Result{}, nil } + +func (r *LocalbuildReconciler) shouldShutDown(ctx context.Context, resource *v1alpha1.Localbuild) (bool, error) { + repos := &v1alpha1.GitRepositoryList{} + err := r.Client.List(ctx, repos, client.InNamespace(resource.Namespace)) + if err != nil { + return false, fmt.Errorf("getting repo list %w", err) + } + for i := range repos.Items { + repo := repos.Items[i] + if !repo.Status.Synced { + return false, nil + } + } + return true, nil +} + +func GetEmbeddedRawInstallResources(name string) ([][]byte, error) { + switch name { + case "argocd": + return RawArgocdInstallResources() + case "backstage", "crossplane": + return util.ConvertFSToBytes(apps.EmbeddedAppsFS, fmt.Sprintf("srv/%s", name)) + case "gitea": + return RawGiteaInstallResources() + case "nginx": + return RawNginxInstallResources() + default: + return nil, fmt.Errorf("unsupported embedded app name %s", name) + } +} diff --git a/pkg/controllers/localbuild/gitea.go b/pkg/controllers/localbuild/gitea.go index b8ac5459..300f1f04 100644 --- a/pkg/controllers/localbuild/gitea.go +++ b/pkg/controllers/localbuild/gitea.go @@ -3,21 +3,32 @@ package localbuild import ( "context" "embed" + "fmt" "github.com/cnoe-io/idpbuilder/api/v1alpha1" + "github.com/cnoe-io/idpbuilder/pkg/util" "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" ) const ( - giteaNamespace = "gitea" - // hardcoded secret name from what we have in the yaml installation file + // hardcoded values from what we have in the yaml installation file. + giteaNamespace = "gitea" giteaAdminSecret = "gitea-admin-secret" + // this is the URL accessible outside cluster. resolves to localhost + giteaIngressURL = "http://gitea.cnoe.localtest.me:8880" + // this is the URL accessible within cluster for ArgoCD to fetch resources. + // resolves to cluster ip + giteaSvcURL = "http://my-gitea-http.gitea.svc.cluster.local:3000" ) //go:embed resources/gitea/k8s/* var installGiteaFS embed.FS +func RawGiteaInstallResources() ([][]byte, error) { + return util.ConvertFSToBytes(installGiteaFS, "resources/gitea/k8s") +} + func (r *LocalbuildReconciler) ReconcileGitea(ctx context.Context, req ctrl.Request, resource *v1alpha1.Localbuild) (ctrl.Result, error) { gitea := EmbeddedInstallation{ name: "Gitea", @@ -36,8 +47,14 @@ func (r *LocalbuildReconciler) ReconcileGitea(ctx context.Context, req ctrl.Requ if result, err := gitea.Install(ctx, req, resource, r.Client, r.Scheme); err != nil { return result, err } - - resource.Status.GiteaSecretName = giteaAdminSecret - resource.Status.GiteaAvailable = true + resource.Status.Gitea.ExternalURL = giteaIngressURL + resource.Status.Gitea.InternalURL = giteaSvcURL + resource.Status.Gitea.AdminUserSecretName = giteaAdminSecret + resource.Status.Gitea.AdminUserSecretNamespace = giteaNamespace + resource.Status.Gitea.Available = true return ctrl.Result{}, nil } + +func getRepositoryURL(namespace, name, baseUrl string) string { + return fmt.Sprintf("%s/giteaAdmin/%s-%s.git", baseUrl, namespace, name) +} diff --git a/pkg/controllers/localbuild/nginx.go b/pkg/controllers/localbuild/nginx.go index dbc41ddb..74848ad2 100644 --- a/pkg/controllers/localbuild/nginx.go +++ b/pkg/controllers/localbuild/nginx.go @@ -5,6 +5,7 @@ import ( "embed" "github.com/cnoe-io/idpbuilder/api/v1alpha1" + "github.com/cnoe-io/idpbuilder/pkg/util" "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" ) @@ -16,6 +17,10 @@ const ( //go:embed resources/nginx/k8s/* var installNginxFS embed.FS +func RawNginxInstallResources() ([][]byte, error) { + return util.ConvertFSToBytes(installNginxFS, "resources/nginx/k8s") +} + func (r *LocalbuildReconciler) ReconcileNginx(ctx context.Context, req ctrl.Request, resource *v1alpha1.Localbuild) (ctrl.Result, error) { nginx := EmbeddedInstallation{ name: "Nginx", @@ -35,6 +40,6 @@ func (r *LocalbuildReconciler) ReconcileNginx(ctx context.Context, req ctrl.Requ return result, err } - resource.Status.NginxAvailable = true + resource.Status.Nginx.Available = true return ctrl.Result{}, nil } diff --git a/pkg/controllers/resources/idpbuilder.cnoe.io_gitrepositories.yaml b/pkg/controllers/resources/idpbuilder.cnoe.io_gitrepositories.yaml new file mode 100644 index 00000000..22b3ec88 --- /dev/null +++ b/pkg/controllers/resources/idpbuilder.cnoe.io_gitrepositories.yaml @@ -0,0 +1,109 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: gitrepositories.idpbuilder.cnoe.io +spec: + group: idpbuilder.cnoe.io + names: + kind: GitRepository + listKind: GitRepositoryList + plural: gitrepositories + singular: gitrepository + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + gitURL: + description: GitURL is the base URL of Git server used for API calls. + pattern: ^https?:\/\/.+$ + type: string + secretRef: + description: SecretRef is the reference to secret that contain Git + server credentials + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + source: + properties: + embeddedAppName: + enum: + - argocd + - backstage + - crossplane + - gitea + - nginx + type: string + path: + description: Path is the absolute path to directory that contains + Kustomize structure or raw manifests. This is required when + Type is set to local. + type: string + type: + default: embedded + description: Type is the source type. + enum: + - local + - embedded + type: string + required: + - type + type: object + required: + - gitURL + type: object + status: + properties: + commit: + description: LatestCommit is the most recent commit known to the controller + properties: + hash: + description: Hash is the digest of the most recent commit + type: string + type: object + externalGitRepositoryUrl: + description: ExternalGitRepositoryUrl is the url for the in-cluster + repository accessible from local machine. + type: string + internalGitRepositoryUrl: + description: InternalGitRepositoryUrl is the url for the in-cluster + repository accessible within the cluster. + type: string + path: + description: Path is the path within the repository that contains + the files. + type: string + synced: + type: boolean + required: + - synced + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml b/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml index af65389f..a426d676 100644 --- a/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml +++ b/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml @@ -64,18 +64,31 @@ spec: type: object status: properties: - argoAppsCreated: - type: boolean - argoAvailable: - type: boolean - gitServerAvailable: - type: boolean - giteaAvailable: - type: boolean - giteaSecret: - type: string - nginxAvailable: - type: boolean + ArgoCD: + properties: + appsCreated: + type: boolean + available: + type: boolean + type: object + gitea: + properties: + adminUserSecretNameecret: + type: string + adminUserSecretNamespace: + type: string + available: + type: boolean + externalURL: + type: string + internalURL: + type: string + type: object + nginx: + properties: + available: + type: boolean + type: object observedGeneration: description: ObservedGeneration is the 'Generation' of the Service that was last processed by the controller. diff --git a/pkg/controllers/run.go b/pkg/controllers/run.go index ffc42d58..13976d56 100644 --- a/pkg/controllers/run.go +++ b/pkg/controllers/run.go @@ -4,6 +4,7 @@ import ( "context" "github.com/cnoe-io/idpbuilder/pkg/apps" + "github.com/cnoe-io/idpbuilder/pkg/controllers/gitrepository" "github.com/cnoe-io/idpbuilder/pkg/controllers/gitserver" "github.com/cnoe-io/idpbuilder/pkg/controllers/localbuild" "sigs.k8s.io/controller-runtime/pkg/log" @@ -38,6 +39,16 @@ func RunControllers(ctx context.Context, mgr manager.Manager, exitCh chan error, return err } + err = (&gitrepository.RepositoryReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("gitrepository-controller"), + GiteaClientFunc: gitrepository.NewGiteaClient, + }).SetupWithManager(mgr, nil) + if err != nil { + log.Error(err, "unable to create repo controller") + } + // Start our manager in another goroutine log.Info("starting manager") go func() { diff --git a/pkg/resources/localbuild/application.go b/pkg/resources/localbuild/application.go index 1713151a..a92b73ee 100644 --- a/pkg/resources/localbuild/application.go +++ b/pkg/resources/localbuild/application.go @@ -28,7 +28,7 @@ func SetProjectSpec(project *argov1alpha1.AppProject) { } } -func SetApplicationSpec(app *argov1alpha1.Application, repoUrl, path, project string, targetRevision *string) { +func SetApplicationSpec(app *argov1alpha1.Application, repoUrl, path, project, dstNS string, targetRevision *string) { headRev := "HEAD" if targetRevision == nil { targetRevision = &headRev @@ -36,7 +36,7 @@ func SetApplicationSpec(app *argov1alpha1.Application, repoUrl, path, project st app.Spec.Destination = argov1alpha1.ApplicationDestination{ Server: "https://kubernetes.default.svc", - Namespace: "argocd", + Namespace: dstNS, } app.Spec.Project = project diff --git a/pkg/util/files.go b/pkg/util/files.go new file mode 100644 index 00000000..bad26027 --- /dev/null +++ b/pkg/util/files.go @@ -0,0 +1,92 @@ +package util + +import ( + "fmt" + "io" + "os" + "path/filepath" +) + +func CopyDirectory(scrDir, dest string) error { + entries, err := os.ReadDir(scrDir) + if err != nil { + return err + } + for _, entry := range entries { + sourcePath := filepath.Join(scrDir, entry.Name()) + destPath := filepath.Join(dest, entry.Name()) + + fileInfo, err := os.Stat(sourcePath) + if err != nil { + return err + } + + switch fileInfo.Mode() & os.ModeType { + case os.ModeDir: + if err := CreateIfNotExists(destPath, 0755); err != nil { + return err + } + if err := CopyDirectory(sourcePath, destPath); err != nil { + return err + } + case os.ModeSymlink: + continue + default: + if err := Copy(sourcePath, destPath); err != nil { + return err + } + } + + fInfo, err := entry.Info() + if err != nil { + return err + } + if err := os.Chmod(destPath, fInfo.Mode()); err != nil { + return err + } + } + return nil +} + +func Copy(srcFile, dstFile string) error { + out, err := os.Create(dstFile) + if err != nil { + return err + } + + defer out.Close() + + in, err := os.Open(srcFile) + if err != nil { + return err + } + + defer in.Close() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + + return nil +} + +func Exists(filePath string) bool { + if _, err := os.Stat(filePath); os.IsNotExist(err) { + return false + } + + return true +} + +func CreateIfNotExists(dir string, perm os.FileMode) error { + if Exists(dir) { + return nil + } + + if err := os.MkdirAll(dir, perm); err != nil { + return fmt.Errorf("failed to create directory: '%s', error: '%s'", dir, err.Error()) + } + + return nil +}