зеркало из https://github.com/Azure/orkestra.git
Merge dev branch into main (#36)
* feat: Initialise application controller with chart pull & push functionality (#16) * feat: init application reconciler Signed-off-by: Kush Trivedi <kushthedude@gmail.com> * use controller-gen 0.2.9 Signed-off-by: Kush Trivedi <kushthedude@gmail.com> * fix:ci Signed-off-by: Kush Trivedi <kushthedude@gmail.com> * chore: introduct groupName Signed-off-by: Kush Trivedi <kushthedude@gmail.com> * feat: add groupID Signed-off-by: Kush Trivedi <kushthedude@gmail.com> * feat: add fetch function Signed-off-by: Kush Trivedi <kushthedude@gmail.com> * feat: add fetch function Signed-off-by: Kush Trivedi <kushthedude@gmail.com> * add test for AB#13 Signed-off-by: Kush Trivedi <kushthedude@gmail.com> * initiate push logic Signed-off-by: Kush Trivedi <kushthedude@gmail.com> * chore: complete push function Signed-off-by: Kush Trivedi <kushthedude@gmail.com> * chore: add configuration Signed-off-by: Kush Trivedi <kushthedude@gmail.com> * fix: golangcilint and tests Signed-off-by: Kush Trivedi <kushthedude@gmail.com> * chore: change branch name pattern to dev (#23) * retrigger pipeline Signed-off-by: Kush Trivedi <kushthedude@gmail.com> * Nitishm/feature/ab#15/appgroup reconciler scaffold (#25) * Add PUML sequence diagrams Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Setup azure-pipeline for Go Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * feat: Scaffolding for application group reconciler Created the reconciler method for ApplicationGroup controller Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> Added reconciler lookup functionality Base functionality of application reconciler. Still needs the generation functions for Argo Workflow object. Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> Workflow interface and appgroup reconciler features Added a bunch of changes to this commit: - Created a Workflow engine interface to support any type of workflow engine - Added logic to appgroup_reconciler. Mostly WIP but sets the skeleton for the upcoming commits. Fixes ab#21 Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> feat: AppGroup reconciler update TODO: - Generate the HelmRelease object - Set the executor template Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> feat: ApplicationGroup first pass dev complete - Needs more unit tests - Not yet fit for usage Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> Added unit tests (incomplete WIP) WIP UTs for ApplicationGroup functions: Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Fix go.mod errors (#26) * Add PUML sequence diagrams Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Setup azure-pipeline for Go Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * chore: change branch name pattern to dev (#23) * Fix go.mod dependencies go.mod was failing to resolve dependencies Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> Co-authored-by: Kush Trivedi <44091822+kushthedude@users.noreply.github.com> * Application and ApplicationReconciler and supported packages into dev branch (#28) * Add PUML sequence diagrams Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Setup azure-pipeline for Go Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * chore: change branch name pattern to dev (#23) * Fix go.mod dependencies go.mod was failing to resolve dependencies Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Add additional tests for Argo.go functions Added more test and test cases Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Updated status field for application type Made Chartstatus inline so that we reference it under obj.status instead of obj.status.status which causes stutter Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Created registry Client and related methods - Modifications to rest of the code tree to support registry client Still a WIP Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * New configurer and additional controller members Added new members to Application and ApplicationGroup controller objects Refactored the controller Config struct Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * feat: Restructuring w/ major changes Bunch of changes to the controller and reconciler Updated configurer and config.yaml format Changes to application and applicationgroup CRD Status fields Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> Co-authored-by: Kush Trivedi <44091822+kushthedude@users.noreply.github.com> * Nitishm/feat/chart/push (#31) * Add PUML sequence diagrams Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Setup azure-pipeline for Go Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * chore: change branch name pattern to dev (#23) * Fix go.mod dependencies go.mod was failing to resolve dependencies Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Add additional tests for Argo.go functions Added more test and test cases Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Updated status field for application type Made Chartstatus inline so that we reference it under obj.status instead of obj.status.status which causes stutter Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Created registry Client and related methods - Modifications to rest of the code tree to support registry client Still a WIP Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * New configurer and additional controller members Added new members to Application and ApplicationGroup controller objects Refactored the controller Config struct Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * feat: Restructuring w/ major changes Bunch of changes to the controller and reconciler Updated configurer and config.yaml format Changes to application and applicationgroup CRD Status fields Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Update orkestra-core illustration (#30) * Add PUML sequence diagrams Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Setup azure-pipeline for Go Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Update diagram and switch harbor for chartmuseum Update the orkestra-core diagram replacing harbor for chartmuseum Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * feat: Implemented the Push functionlity Verfied manually that charts get uploaded to the staging directory Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * bug: resolve merge errors Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> Co-authored-by: Kush Trivedi <44091822+kushthedude@users.noreply.github.com> * Fixed lint errors and test failures (#33) * Add PUML sequence diagrams Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Setup azure-pipeline for Go Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * chore: change branch name pattern to dev (#23) * Fix go.mod dependencies go.mod was failing to resolve dependencies Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Add additional tests for Argo.go functions Added more test and test cases Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Updated status field for application type Made Chartstatus inline so that we reference it under obj.status instead of obj.status.status which causes stutter Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Created registry Client and related methods - Modifications to rest of the code tree to support registry client Still a WIP Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * New configurer and additional controller members Added new members to Application and ApplicationGroup controller objects Refactored the controller Config struct Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * feat: Restructuring w/ major changes Bunch of changes to the controller and reconciler Updated configurer and config.yaml format Changes to application and applicationgroup CRD Status fields Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Update orkestra-core illustration (#30) * Add PUML sequence diagrams Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Setup azure-pipeline for Go Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Update diagram and switch harbor for chartmuseum Update the orkestra-core diagram replacing harbor for chartmuseum Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * feat: Implemented the Push functionlity Verfied manually that charts get uploaded to the staging directory Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * bug: resolve merge errors Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * feat: Modifications and enhanced argo logic - Multiple changes in argo object to support generation of valid workflow - Added overlays field to Applications CR as a workaround for https://github.com/kubernetes/kubernetes/issues/98683 - Helm chart for orkestra init. All dependency charts helm-operator, chartmuseum, argo added to chart dependencies. - Update main.go Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Fix lint and argo_test errors Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> Co-authored-by: Kush Trivedi <44091822+kushthedude@users.noreply.github.com> * chore: Update docker targets in Make and comment cleanup (#34) * Add PUML sequence diagrams Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Setup azure-pipeline for Go Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * chore: change branch name pattern to dev (#23) * Fix go.mod dependencies go.mod was failing to resolve dependencies Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Add additional tests for Argo.go functions Added more test and test cases Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Updated status field for application type Made Chartstatus inline so that we reference it under obj.status instead of obj.status.status which causes stutter Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Created registry Client and related methods - Modifications to rest of the code tree to support registry client Still a WIP Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * New configurer and additional controller members Added new members to Application and ApplicationGroup controller objects Refactored the controller Config struct Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * feat: Restructuring w/ major changes Bunch of changes to the controller and reconciler Updated configurer and config.yaml format Changes to application and applicationgroup CRD Status fields Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Update orkestra-core illustration (#30) * Add PUML sequence diagrams Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Setup azure-pipeline for Go Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Update diagram and switch harbor for chartmuseum Update the orkestra-core diagram replacing harbor for chartmuseum Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * feat: Implemented the Push functionlity Verfied manually that charts get uploaded to the staging directory Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * bug: resolve merge errors Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * feat: Modifications and enhanced argo logic - Multiple changes in argo object to support generation of valid workflow - Added overlays field to Applications CR as a workaround for https://github.com/kubernetes/kubernetes/issues/98683 - Helm chart for orkestra init. All dependency charts helm-operator, chartmuseum, argo added to chart dependencies. - Update main.go Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Fix lint and argo_test errors Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Docker target changes and TODO comment cleanup Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> Co-authored-by: Kush Trivedi <44091822+kushthedude@users.noreply.github.com> * [chore] Helm chart for orkestra and relevant changes (#35) * Add PUML sequence diagrams Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Setup azure-pipeline for Go Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * chore: change branch name pattern to dev (#23) * Fix go.mod dependencies go.mod was failing to resolve dependencies Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Add additional tests for Argo.go functions Added more test and test cases Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Updated status field for application type Made Chartstatus inline so that we reference it under obj.status instead of obj.status.status which causes stutter Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Created registry Client and related methods - Modifications to rest of the code tree to support registry client Still a WIP Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * New configurer and additional controller members Added new members to Application and ApplicationGroup controller objects Refactored the controller Config struct Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * feat: Restructuring w/ major changes Bunch of changes to the controller and reconciler Updated configurer and config.yaml format Changes to application and applicationgroup CRD Status fields Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Update orkestra-core illustration (#30) * Add PUML sequence diagrams Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Setup azure-pipeline for Go Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Update diagram and switch harbor for chartmuseum Update the orkestra-core diagram replacing harbor for chartmuseum Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * feat: Implemented the Push functionlity Verfied manually that charts get uploaded to the staging directory Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * bug: resolve merge errors Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * feat: Modifications and enhanced argo logic - Multiple changes in argo object to support generation of valid workflow - Added overlays field to Applications CR as a workaround for https://github.com/kubernetes/kubernetes/issues/98683 - Helm chart for orkestra init. All dependency charts helm-operator, chartmuseum, argo added to chart dependencies. - Update main.go Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Fix lint and argo_test errors Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Docker target changes and TODO comment cleanup Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Helm chart for orkestra and related changes - Created the helm chart for orkestra - Made relevant changes based on feedback from running it on the cluster Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Create example dir and edits to README.md Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Update example and set ownersreference for helmrelease Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Fix argo tests failures from previous set of changes Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> * Fix lint error in argo test Signed-off-by: Nitish Malhotra <nitish.malhotra@gmail.com> Co-authored-by: Kush Trivedi <44091822+kushthedude@users.noreply.github.com> Co-authored-by: Kush Trivedi <44091822+kushthedude@users.noreply.github.com>
This commit is contained in:
Родитель
ac115b6f54
Коммит
f1c574a36b
|
@ -31,6 +31,7 @@ debug
|
|||
|
||||
# IntelliJ / Goland
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Emacs
|
||||
*~
|
||||
|
|
11
Dockerfile
11
Dockerfile
|
@ -12,16 +12,19 @@ RUN go mod download
|
|||
# Copy the go source
|
||||
COPY main.go main.go
|
||||
COPY api/ api/
|
||||
COPY pkg/ pkg/
|
||||
COPY controllers/ controllers/
|
||||
COPY config.yaml config.yaml
|
||||
|
||||
# Build
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go
|
||||
|
||||
# Use distroless as minimal base image to package the manager binary
|
||||
# Refer to https://github.com/GoogleContainerTools/distroless for more details
|
||||
FROM gcr.io/distroless/static:nonroot
|
||||
FROM alpine:3.7
|
||||
RUN apk add --no-cache bash
|
||||
RUN mkdir -p /etc/orkestra/charts/pull/
|
||||
|
||||
WORKDIR /
|
||||
COPY --from=builder /workspace/manager .
|
||||
USER nonroot:nonroot
|
||||
COPY --from=builder /workspace/config.yaml /etc/controller/config.yaml
|
||||
|
||||
ENTRYPOINT ["/manager"]
|
||||
|
|
4
Makefile
4
Makefile
|
@ -1,6 +1,6 @@
|
|||
|
||||
# Image URL to use all building/pushing image targets
|
||||
IMG ?= controller:latest
|
||||
IMG ?= azureorkestra/orkestra:latest
|
||||
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
|
||||
CRD_OPTIONS ?= "crd:trivialVersions=true"
|
||||
|
||||
|
@ -76,7 +76,7 @@ ifeq (, $(shell which controller-gen))
|
|||
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
|
||||
cd $$CONTROLLER_GEN_TMP_DIR ;\
|
||||
go mod init tmp ;\
|
||||
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.5 ;\
|
||||
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.9 ;\
|
||||
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
|
||||
}
|
||||
CONTROLLER_GEN=$(GOBIN)/controller-gen
|
||||
|
|
|
@ -26,7 +26,7 @@ To solve the complex application orchestration problem Orkestra builds a [Direct
|
|||
2. For each `Application` in `ApplicationGroup` download Helm chart from “primary” Helm Registry
|
||||
3. (*optional) For each dependency in the `Application` chart, if dependency chart is embedded in `charts/` directory, push to ”staging” Helm Registry (Chart-museum).
|
||||
4. Generate and submit Argo Workflow DAG
|
||||
5. (Executor nodes only) Submit and probe deployment state of `HelmRelease CR.
|
||||
5. (Executor nodes only) Submit and probe deployment state of `HelmRelease` CR.
|
||||
6. Fetch and deploy Helm charts referred to by each `HelmRelease` CR to the Kubernetes cluster.
|
||||
(*optional) Embedded subcharts are fetched from the “staging” registry instead of the “primary/remote” registry.
|
||||
|
||||
|
|
|
@ -22,8 +22,9 @@ type DAG struct {
|
|||
|
||||
// ApplicationGroupStatus defines the observed state of ApplicationGroup
|
||||
type ApplicationGroupStatus struct {
|
||||
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
Applications []ApplicationStatus `json:"status,omitempty"`
|
||||
Ready bool `json:"ready"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
|
|
@ -14,15 +14,30 @@ import (
|
|||
// ApplicationSpec defines the desired state of Application
|
||||
type ApplicationSpec struct {
|
||||
// Namespace to which the HelmRelease object will be deployed
|
||||
Namespace string `json:"namespace"`
|
||||
Subcharts []DAG `json:"subcharts,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
Subcharts []DAG `json:"subcharts,omitempty"`
|
||||
GroupID string `json:"groupID,omitempty"`
|
||||
// ChartRepoNickname is used to lookup the repository config in the registries config map
|
||||
ChartRepoNickname string `json:"repo,omitempty"`
|
||||
// XXX (nitishm) : Workaround for https://github.com/kubernetes/kubernetes/issues/98683
|
||||
Overlays string `json:"overlays,omitempty"`
|
||||
|
||||
helmopv1.HelmReleaseSpec `json:",inline"`
|
||||
}
|
||||
|
||||
// ChartStatus denotes the current status of the Application Reconciliation
|
||||
type ChartStatus struct {
|
||||
Ready bool `json:"ready,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Staged bool `json:"staged,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// ApplicationStatus defines the observed state of Application
|
||||
type ApplicationStatus struct {
|
||||
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
||||
// Important: Run "make" to regenerate code after modifying this file
|
||||
Name string `json:"name"`
|
||||
Application ChartStatus `json:"application"`
|
||||
Subcharts map[string]ChartStatus `json:"subcharts"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
|
|
@ -17,7 +17,7 @@ func (in *Application) DeepCopyInto(out *Application) {
|
|||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Status = in.Status
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Application.
|
||||
|
@ -44,7 +44,7 @@ func (in *ApplicationGroup) DeepCopyInto(out *ApplicationGroup) {
|
|||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Status = in.Status
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationGroup.
|
||||
|
@ -122,6 +122,13 @@ func (in *ApplicationGroupSpec) DeepCopy() *ApplicationGroupSpec {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ApplicationGroupStatus) DeepCopyInto(out *ApplicationGroupStatus) {
|
||||
*out = *in
|
||||
if in.Applications != nil {
|
||||
in, out := &in.Applications, &out.Applications
|
||||
*out = make([]ApplicationStatus, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationGroupStatus.
|
||||
|
@ -192,6 +199,14 @@ func (in *ApplicationSpec) DeepCopy() *ApplicationSpec {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ApplicationStatus) DeepCopyInto(out *ApplicationStatus) {
|
||||
*out = *in
|
||||
out.Application = in.Application
|
||||
if in.Subcharts != nil {
|
||||
in, out := &in.Subcharts, &out.Subcharts
|
||||
*out = make(map[string]ChartStatus, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationStatus.
|
||||
|
@ -204,6 +219,21 @@ func (in *ApplicationStatus) DeepCopy() *ApplicationStatus {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ChartStatus) DeepCopyInto(out *ChartStatus) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChartStatus.
|
||||
func (in *ChartStatus) DeepCopy() *ChartStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ChartStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DAG) DeepCopyInto(out *DAG) {
|
||||
*out = *in
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
|
@ -0,0 +1,12 @@
|
|||
dependencies:
|
||||
- name: chartmuseum
|
||||
repository: https://chartmuseum.github.io/charts
|
||||
version: 2.15.0
|
||||
- name: argo
|
||||
repository: https://argoproj.github.io/argo-helm
|
||||
version: 0.15.3
|
||||
- name: helm-operator
|
||||
repository: https://charts.fluxcd.io
|
||||
version: 1.2.0
|
||||
digest: sha256:f8d6513a1fa72b84138ea34c60fc017231725b09fbd0a2a274819d1a3b8d926c
|
||||
generated: "2021-01-31T16:17:12.2035772-08:00"
|
|
@ -0,0 +1,37 @@
|
|||
apiVersion: v2
|
||||
name: orkestra
|
||||
description: A Helm chart for Azure Orkestra operator and supporting components
|
||||
|
||||
sources:
|
||||
- https://github.com/Azure/Orkestra
|
||||
|
||||
maintainers:
|
||||
- name: Nitish Malhotra (nitishm)
|
||||
email: nitishm@microsoft.com
|
||||
|
||||
# Chart version
|
||||
version: 0.1.0
|
||||
|
||||
type: application
|
||||
|
||||
# Application version
|
||||
appVersion: "0.1.0"
|
||||
|
||||
dependencies:
|
||||
- name: chartmuseum
|
||||
version: "2.15.0"
|
||||
repository: "https://chartmuseum.github.io/charts"
|
||||
- name: argo
|
||||
version: "0.15.3"
|
||||
repository: "https://argoproj.github.io/argo-helm"
|
||||
- name: helm-operator
|
||||
version: "1.2.0"
|
||||
repository: "https://charts.fluxcd.io"
|
||||
|
||||
keywords:
|
||||
- helmops
|
||||
- application release
|
||||
- orchestration
|
||||
- continuous delivery
|
||||
- conituous deployment
|
||||
- 5G
|
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -0,0 +1 @@
|
|||
Happy Helming with Azure/Orkestra
|
|
@ -0,0 +1,62 @@
|
|||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "orkestra.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "orkestra.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "orkestra.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "orkestra.labels" -}}
|
||||
helm.sh/chart: {{ include "orkestra.chart" . }}
|
||||
{{ include "orkestra.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "orkestra.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "orkestra.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "orkestra.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "orkestra.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
|
@ -0,0 +1,69 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "orkestra.fullname" . }}
|
||||
labels:
|
||||
{{- include "orkestra.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "orkestra.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "orkestra.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "orkestra.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
args:
|
||||
- --staging-repo-name
|
||||
- staging
|
||||
- --config
|
||||
- /etc/controller/config.yaml
|
||||
- --chart-store-path
|
||||
- /etc/orkestra/charts/pull
|
||||
env:
|
||||
- name: WORKFLOW_NAMESPACE
|
||||
value: orkestra
|
||||
# ports:
|
||||
# - name: http
|
||||
# containerPort: 80
|
||||
# protocol: TCP
|
||||
# livenessProbe:
|
||||
# httpGet:
|
||||
# path: /
|
||||
# port: http
|
||||
# readinessProbe:
|
||||
# httpGet:
|
||||
# path: /
|
||||
# port: http
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
|
@ -0,0 +1,35 @@
|
|||
kind: ClusterRole
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: {{ include "orkestra.serviceAccountName" . }}
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["*"]
|
||||
verbs: ["*"]
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["*"]
|
||||
verbs: ["*"]
|
||||
- apiGroups: ["orkestra.azure.microsoft.com"]
|
||||
resources: ["*"]
|
||||
verbs: ["*"]
|
||||
- apiGroups: ["helm.fluxcd.io"]
|
||||
resources: ["*"]
|
||||
verbs: ["*"]
|
||||
- apiGroups: ["argoproj.io"]
|
||||
resources: ["*"]
|
||||
verbs: ["*"]
|
||||
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: {{ include "orkestra.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
namespace: orkestra
|
||||
name: {{ include "orkestra.serviceAccountName" . }}
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: {{ include "orkestra.serviceAccountName" . }}
|
||||
apiGroup: rbac.authorization.k8s.io
|
|
@ -0,0 +1,12 @@
|
|||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "orkestra.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "orkestra.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
|
@ -0,0 +1,85 @@
|
|||
namespace: &namespace orkestra
|
||||
serviceAccount: &serviceAccount orkestra
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: azureorkestra/orkestra
|
||||
pullPolicy: Always
|
||||
tag: "latest"
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
serviceAccount:
|
||||
create: true
|
||||
annotations: {}
|
||||
name: *serviceAccount
|
||||
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext: {}
|
||||
|
||||
securityContext: {}
|
||||
|
||||
resources: {}
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
|
||||
# Dependency overlay values
|
||||
chartmuseum:
|
||||
env:
|
||||
open:
|
||||
DISABLE_API: false
|
||||
|
||||
helm-operator:
|
||||
configureRepositories:
|
||||
enable: true
|
||||
repositories:
|
||||
- name: bitnami
|
||||
url: https://charts.bitnami.com/bitnami
|
||||
- name: chartmuseum
|
||||
url: http://orkestra-chartmuseum.orkestra:8080
|
||||
rbac:
|
||||
create: false
|
||||
pspEnabled: false
|
||||
serviceAccount:
|
||||
create: false
|
||||
annotations: {}
|
||||
name: *serviceAccount
|
||||
helm:
|
||||
versions: "v3"
|
||||
|
||||
argo:
|
||||
images:
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
init:
|
||||
serviceAccount: *serviceAccount
|
||||
|
||||
workflow:
|
||||
namespace: *namespace
|
||||
serviceAccount:
|
||||
name: *serviceAccount
|
||||
rbac:
|
||||
enabled: false
|
||||
|
||||
controller:
|
||||
# serviceAccount: *serviceAccount
|
||||
name: workflow-controller
|
||||
workflowNamespaces:
|
||||
- *namespace
|
||||
containerRuntimeExecutor: docker
|
||||
# For KinD use -
|
||||
# containerRuntimeExecutor: k8sapi
|
||||
|
||||
server:
|
||||
enabled: true
|
||||
name: argo-server
|
||||
serviceAccount: *serviceAccount
|
||||
createServiceAccount: false
|
|
@ -0,0 +1,7 @@
|
|||
registries:
|
||||
bitnami:
|
||||
url: "https://charts.bitnami.com/bitnami"
|
||||
insecureSkipVerify: true
|
||||
|
||||
staging:
|
||||
url: "http://orkestra-chartmuseum.orkestra:8080"
|
|
@ -1,10 +1,10 @@
|
|||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.9
|
||||
controller-gen.kubebuilder.io/version: (unknown)
|
||||
creationTimestamp: null
|
||||
name: applicationgroups.orkestra.azure.microsoft.com
|
||||
spec:
|
||||
|
@ -15,48 +15,94 @@ spec:
|
|||
plural: applicationgroups
|
||||
singular: applicationgroup
|
||||
scope: Cluster
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: ApplicationGroup is the Schema for the applicationgroups API
|
||||
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:
|
||||
description: ApplicationGroupSpec defines the desired state of ApplicationGroup
|
||||
properties:
|
||||
applications:
|
||||
items:
|
||||
properties:
|
||||
dependencies:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
status:
|
||||
description: ApplicationGroupStatus defines the observed state of ApplicationGroup
|
||||
type: object
|
||||
type: object
|
||||
version: v1alpha1
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: ApplicationGroup is the Schema for the applicationgroups API
|
||||
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:
|
||||
description: ApplicationGroupSpec defines the desired state of ApplicationGroup
|
||||
properties:
|
||||
applications:
|
||||
items:
|
||||
properties:
|
||||
dependencies:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
status:
|
||||
description: ApplicationGroupStatus defines the observed state of ApplicationGroup
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
ready:
|
||||
type: boolean
|
||||
status:
|
||||
items:
|
||||
description: ApplicationStatus defines the observed state of Application
|
||||
properties:
|
||||
application:
|
||||
description: ChartStatus denotes the current status of the Application
|
||||
Reconciliation
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
ready:
|
||||
type: boolean
|
||||
staged:
|
||||
type: boolean
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
subcharts:
|
||||
additionalProperties:
|
||||
description: ChartStatus denotes the current status of the
|
||||
Application Reconciliation
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
ready:
|
||||
type: boolean
|
||||
staged:
|
||||
type: boolean
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- application
|
||||
- name
|
||||
- subcharts
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- ready
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.2.9
|
||||
controller-gen.kubebuilder.io/version: (unknown)
|
||||
creationTimestamp: null
|
||||
name: applications.orkestra.azure.microsoft.com
|
||||
spec:
|
||||
|
@ -15,296 +15,342 @@ spec:
|
|||
plural: applications
|
||||
singular: application
|
||||
scope: Cluster
|
||||
subresources:
|
||||
status: {}
|
||||
validation:
|
||||
openAPIV3Schema:
|
||||
description: Application is the Schema for the applications API
|
||||
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:
|
||||
description: ApplicationSpec defines the desired state of Application
|
||||
properties:
|
||||
chart:
|
||||
properties:
|
||||
chartPullSecret:
|
||||
description: ChartPullSecret holds the reference to the authentication
|
||||
secret for accessing the Helm repository using HTTPS basic auth.
|
||||
NOT IMPLEMENTED!
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
git:
|
||||
description: Git URL is the URL of the Git repository, e.g. `git@github.com:org/repo`,
|
||||
`http://github.com/org/repo`, or `ssh://git@example.com:2222/org/repo.git`.
|
||||
type: string
|
||||
name:
|
||||
description: Name is the name of the Helm chart _without_ an alias,
|
||||
e.g. redis (for `helm upgrade [flags] stable/redis`).
|
||||
type: string
|
||||
path:
|
||||
description: Path is the path to the chart relative to the repository
|
||||
root.
|
||||
type: string
|
||||
ref:
|
||||
description: Ref is the Git branch (or other reference) to use.
|
||||
Defaults to 'master', or the configured default Git ref.
|
||||
type: string
|
||||
repository:
|
||||
description: RepoURL is the URL of the Helm repository, e.g. `https://kubernetes-charts.storage.googleapis.com`
|
||||
or `https://charts.example.com`.
|
||||
type: string
|
||||
secretRef:
|
||||
description: SecretRef holds the authentication secret for accessing
|
||||
the Git repository (over HTTPS). The credentials will be added
|
||||
to an HTTPS GitURL before the mirror is started.
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
skipDepUpdate:
|
||||
description: SkipDepUpdate will tell the operator to skip running
|
||||
'helm dep update' before installing or upgrading the chart, the
|
||||
chart dependencies _must_ be present for this to succeed.
|
||||
type: boolean
|
||||
version:
|
||||
description: Version is the targeted Helm chart version, e.g. 7.0.1.
|
||||
type: string
|
||||
type: object
|
||||
disableOpenAPIValidation:
|
||||
description: DisableOpenAPIValidation controls whether OpenAPI validation
|
||||
is enforced.
|
||||
type: boolean
|
||||
forceUpgrade:
|
||||
description: Force will mark this Helm release to `--force` upgrades.
|
||||
This forces the resource updates through delete/recreate if needed.
|
||||
type: boolean
|
||||
helmVersion:
|
||||
description: 'HelmVersion is the version of Helm to target. If not supplied,
|
||||
the lowest _enabled Helm version_ will be targeted. Valid HelmVersion
|
||||
values are: "v2", "v3"'
|
||||
enum:
|
||||
- v2
|
||||
- v3
|
||||
type: string
|
||||
maxHistory:
|
||||
description: MaxHistory is the maximum amount of revisions to keep for
|
||||
the Helm release. If not supplied, it defaults to 10.
|
||||
type: integer
|
||||
namespace:
|
||||
description: Namespace to which the HelmRelease object will be deployed
|
||||
type: string
|
||||
releaseName:
|
||||
description: ReleaseName is the name of the The Helm release. If not
|
||||
supplied, it will be generated by affixing the namespace to the resource
|
||||
name.
|
||||
type: string
|
||||
resetValues:
|
||||
description: ResetValues will mark this Helm release to reset the values
|
||||
to the defaults of the targeted chart before performing an upgrade.
|
||||
Not explicitly setting this to `false` equals to `true` due to the
|
||||
declarative nature of the operator.
|
||||
type: boolean
|
||||
rollback:
|
||||
description: The rollback settings for this Helm release.
|
||||
properties:
|
||||
disableHooks:
|
||||
description: DisableHooks will mark this Helm release to prevent
|
||||
hooks from running during the rollback.
|
||||
type: boolean
|
||||
enable:
|
||||
description: Enable will mark this Helm release for rollbacks.
|
||||
type: boolean
|
||||
force:
|
||||
description: Force will mark this Helm release to `--force` rollbacks.
|
||||
This forces the resource updates through delete/recreate if needed.
|
||||
type: boolean
|
||||
maxRetries:
|
||||
description: MaxRetries is the maximum amount of upgrade retries
|
||||
the operator should make before bailing.
|
||||
format: int64
|
||||
type: integer
|
||||
recreate:
|
||||
description: Recreate will mark this Helm release to `--recreate-pods`
|
||||
for if applicable. This performs pod restarts.
|
||||
type: boolean
|
||||
retry:
|
||||
description: Retry will mark this Helm release for upgrade retries
|
||||
after a rollback.
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for any individual Kubernetes
|
||||
operation (like Jobs for hooks) during rollback.
|
||||
format: int64
|
||||
type: integer
|
||||
wait:
|
||||
description: Wait will mark this Helm release to wait until all
|
||||
Pods, PVCs, Services, and minimum number of Pods of a Deployment,
|
||||
StatefulSet, or ReplicaSet are in a ready state before marking
|
||||
the release as successful.
|
||||
type: boolean
|
||||
type: object
|
||||
skipCRDs:
|
||||
description: SkipCRDs will mark this Helm release to skip the creation
|
||||
of CRDs during a Helm 3 installation.
|
||||
type: boolean
|
||||
subcharts:
|
||||
items:
|
||||
properties:
|
||||
dependencies:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
targetNamespace:
|
||||
description: TargetNamespace overrides the targeted namespace for the
|
||||
Helm release. The default namespace equals to the namespace of the
|
||||
HelmRelease resource.
|
||||
type: string
|
||||
test:
|
||||
description: The test settings for this Helm release.
|
||||
properties:
|
||||
cleanup:
|
||||
description: Cleanup, when targeting Helm 2, determines whether
|
||||
to delete test pods between each test run initiated by the Helm
|
||||
Operator.
|
||||
type: boolean
|
||||
enable:
|
||||
description: Enable will mark this Helm release for tests.
|
||||
type: boolean
|
||||
ignoreFailures:
|
||||
description: IgnoreFailures will cause a Helm release to be rolled
|
||||
back if it fails otherwise it will be left in a released state
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for any individual Kubernetes
|
||||
operation (like Jobs for hooks) during test.
|
||||
format: int64
|
||||
type: integer
|
||||
type: object
|
||||
timeout:
|
||||
description: Timeout is the time to wait for any individual Kubernetes
|
||||
operation (like Jobs for hooks) during installation and upgrade operations.
|
||||
format: int64
|
||||
type: integer
|
||||
valueFileSecrets:
|
||||
description: ValueFileSecrets holds the local name references to secrets.
|
||||
DEPRECATED, use ValuesFrom.secretKeyRef instead.
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
values:
|
||||
description: Values holds the values for this Helm release.
|
||||
type: object
|
||||
valuesFrom:
|
||||
items:
|
||||
properties:
|
||||
chartFileRef:
|
||||
description: The reference to a local chart file with release
|
||||
values.
|
||||
properties:
|
||||
optional:
|
||||
description: Optional will mark this ChartFileSelector as
|
||||
optional. The result of this are that operations are permitted
|
||||
without the source, due to it e.g. being temporarily unavailable.
|
||||
type: boolean
|
||||
path:
|
||||
description: Path is the file path to the source relative
|
||||
to the chart root.
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
configMapKeyRef:
|
||||
description: The reference to a config map with release values.
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
externalSourceRef:
|
||||
description: The reference to an external source with release
|
||||
values.
|
||||
properties:
|
||||
optional:
|
||||
description: Optional will mark this ExternalSourceSelector
|
||||
as optional. The result of this are that operations are
|
||||
permitted without the source, due to it e.g. being temporarily
|
||||
unavailable.
|
||||
type: boolean
|
||||
url:
|
||||
description: URL is the URL of the external source.
|
||||
type: string
|
||||
required:
|
||||
- url
|
||||
type: object
|
||||
secretKeyRef:
|
||||
description: The reference to a secret with release values.
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
wait:
|
||||
description: Wait will mark this Helm release to wait until all Pods,
|
||||
PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet,
|
||||
or ReplicaSet are in a ready state before marking the release as successful.
|
||||
type: boolean
|
||||
required:
|
||||
- chart
|
||||
- namespace
|
||||
type: object
|
||||
status:
|
||||
description: ApplicationStatus defines the observed state of Application
|
||||
type: object
|
||||
type: object
|
||||
version: v1alpha1
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Application is the Schema for the applications API
|
||||
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:
|
||||
description: ApplicationSpec defines the desired state of Application
|
||||
properties:
|
||||
chart:
|
||||
properties:
|
||||
chartPullSecret:
|
||||
description: ChartPullSecret holds the reference to the authentication
|
||||
secret for accessing the Helm repository using HTTPS basic auth.
|
||||
NOT IMPLEMENTED!
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
git:
|
||||
description: Git URL is the URL of the Git repository, e.g. `git@github.com:org/repo`,
|
||||
`http://github.com/org/repo`, or `ssh://git@example.com:2222/org/repo.git`.
|
||||
type: string
|
||||
name:
|
||||
description: Name is the name of the Helm chart _without_ an alias,
|
||||
e.g. redis (for `helm upgrade [flags] stable/redis`).
|
||||
type: string
|
||||
path:
|
||||
description: Path is the path to the chart relative to the repository
|
||||
root.
|
||||
type: string
|
||||
ref:
|
||||
description: Ref is the Git branch (or other reference) to use.
|
||||
Defaults to 'master', or the configured default Git ref.
|
||||
type: string
|
||||
repository:
|
||||
description: RepoURL is the URL of the Helm repository, e.g. `https://kubernetes-charts.storage.googleapis.com`
|
||||
or `https://charts.example.com`.
|
||||
type: string
|
||||
secretRef:
|
||||
description: SecretRef holds the authentication secret for accessing
|
||||
the Git repository (over HTTPS). The credentials will be added
|
||||
to an HTTPS GitURL before the mirror is started.
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
skipDepUpdate:
|
||||
description: SkipDepUpdate will tell the operator to skip running
|
||||
'helm dep update' before installing or upgrading the chart,
|
||||
the chart dependencies _must_ be present for this to succeed.
|
||||
type: boolean
|
||||
version:
|
||||
description: Version is the targeted Helm chart version, e.g.
|
||||
7.0.1.
|
||||
type: string
|
||||
type: object
|
||||
disableOpenAPIValidation:
|
||||
description: DisableOpenAPIValidation controls whether OpenAPI validation
|
||||
is enforced.
|
||||
type: boolean
|
||||
forceUpgrade:
|
||||
description: Force will mark this Helm release to `--force` upgrades.
|
||||
This forces the resource updates through delete/recreate if needed.
|
||||
type: boolean
|
||||
groupID:
|
||||
type: string
|
||||
helmVersion:
|
||||
description: 'HelmVersion is the version of Helm to target. If not
|
||||
supplied, the lowest _enabled Helm version_ will be targeted. Valid
|
||||
HelmVersion values are: "v2", "v3"'
|
||||
enum:
|
||||
- v2
|
||||
- v3
|
||||
type: string
|
||||
maxHistory:
|
||||
description: MaxHistory is the maximum amount of revisions to keep
|
||||
for the Helm release. If not supplied, it defaults to 10.
|
||||
type: integer
|
||||
namespace:
|
||||
description: Namespace to which the HelmRelease object will be deployed
|
||||
type: string
|
||||
overlays:
|
||||
description: 'XXX (nitishm) : Workaround for https://github.com/kubernetes/kubernetes/issues/98683'
|
||||
type: string
|
||||
releaseName:
|
||||
description: ReleaseName is the name of the The Helm release. If not
|
||||
supplied, it will be generated by affixing the namespace to the
|
||||
resource name.
|
||||
type: string
|
||||
repo:
|
||||
description: ChartRepoNickname is used to lookup the repository config
|
||||
in the registries config map
|
||||
type: string
|
||||
resetValues:
|
||||
description: ResetValues will mark this Helm release to reset the
|
||||
values to the defaults of the targeted chart before performing an
|
||||
upgrade. Not explicitly setting this to `false` equals to `true`
|
||||
due to the declarative nature of the operator.
|
||||
type: boolean
|
||||
rollback:
|
||||
description: The rollback settings for this Helm release.
|
||||
properties:
|
||||
disableHooks:
|
||||
description: DisableHooks will mark this Helm release to prevent
|
||||
hooks from running during the rollback.
|
||||
type: boolean
|
||||
enable:
|
||||
description: Enable will mark this Helm release for rollbacks.
|
||||
type: boolean
|
||||
force:
|
||||
description: Force will mark this Helm release to `--force` rollbacks.
|
||||
This forces the resource updates through delete/recreate if
|
||||
needed.
|
||||
type: boolean
|
||||
maxRetries:
|
||||
description: MaxRetries is the maximum amount of upgrade retries
|
||||
the operator should make before bailing.
|
||||
format: int64
|
||||
type: integer
|
||||
recreate:
|
||||
description: Recreate will mark this Helm release to `--recreate-pods`
|
||||
for if applicable. This performs pod restarts.
|
||||
type: boolean
|
||||
retry:
|
||||
description: Retry will mark this Helm release for upgrade retries
|
||||
after a rollback.
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for any individual Kubernetes
|
||||
operation (like Jobs for hooks) during rollback.
|
||||
format: int64
|
||||
type: integer
|
||||
wait:
|
||||
description: Wait will mark this Helm release to wait until all
|
||||
Pods, PVCs, Services, and minimum number of Pods of a Deployment,
|
||||
StatefulSet, or ReplicaSet are in a ready state before marking
|
||||
the release as successful.
|
||||
type: boolean
|
||||
type: object
|
||||
skipCRDs:
|
||||
description: SkipCRDs will mark this Helm release to skip the creation
|
||||
of CRDs during a Helm 3 installation.
|
||||
type: boolean
|
||||
subcharts:
|
||||
items:
|
||||
properties:
|
||||
dependencies:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
targetNamespace:
|
||||
description: TargetNamespace overrides the targeted namespace for
|
||||
the Helm release. The default namespace equals to the namespace
|
||||
of the HelmRelease resource.
|
||||
type: string
|
||||
test:
|
||||
description: The test settings for this Helm release.
|
||||
properties:
|
||||
cleanup:
|
||||
description: Cleanup, when targeting Helm 2, determines whether
|
||||
to delete test pods between each test run initiated by the Helm
|
||||
Operator.
|
||||
type: boolean
|
||||
enable:
|
||||
description: Enable will mark this Helm release for tests.
|
||||
type: boolean
|
||||
ignoreFailures:
|
||||
description: IgnoreFailures will cause a Helm release to be rolled
|
||||
back if it fails otherwise it will be left in a released state
|
||||
type: boolean
|
||||
timeout:
|
||||
description: Timeout is the time to wait for any individual Kubernetes
|
||||
operation (like Jobs for hooks) during test.
|
||||
format: int64
|
||||
type: integer
|
||||
type: object
|
||||
timeout:
|
||||
description: Timeout is the time to wait for any individual Kubernetes
|
||||
operation (like Jobs for hooks) during installation and upgrade
|
||||
operations.
|
||||
format: int64
|
||||
type: integer
|
||||
valueFileSecrets:
|
||||
description: ValueFileSecrets holds the local name references to secrets.
|
||||
DEPRECATED, use ValuesFrom.secretKeyRef instead.
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
values:
|
||||
description: Values holds the values for this Helm release.
|
||||
type: object
|
||||
valuesFrom:
|
||||
items:
|
||||
properties:
|
||||
chartFileRef:
|
||||
description: The reference to a local chart file with release
|
||||
values.
|
||||
properties:
|
||||
optional:
|
||||
description: Optional will mark this ChartFileSelector as
|
||||
optional. The result of this are that operations are permitted
|
||||
without the source, due to it e.g. being temporarily unavailable.
|
||||
type: boolean
|
||||
path:
|
||||
description: Path is the file path to the source relative
|
||||
to the chart root.
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
configMapKeyRef:
|
||||
description: The reference to a config map with release values.
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
externalSourceRef:
|
||||
description: The reference to an external source with release
|
||||
values.
|
||||
properties:
|
||||
optional:
|
||||
description: Optional will mark this ExternalSourceSelector
|
||||
as optional. The result of this are that operations are
|
||||
permitted without the source, due to it e.g. being temporarily
|
||||
unavailable.
|
||||
type: boolean
|
||||
url:
|
||||
description: URL is the URL of the external source.
|
||||
type: string
|
||||
required:
|
||||
- url
|
||||
type: object
|
||||
secretKeyRef:
|
||||
description: The reference to a secret with release values.
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
optional:
|
||||
type: boolean
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
type: array
|
||||
wait:
|
||||
description: Wait will mark this Helm release to wait until all Pods,
|
||||
PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet,
|
||||
or ReplicaSet are in a ready state before marking the release as
|
||||
successful.
|
||||
type: boolean
|
||||
required:
|
||||
- chart
|
||||
type: object
|
||||
status:
|
||||
description: ApplicationStatus defines the observed state of Application
|
||||
properties:
|
||||
application:
|
||||
description: ChartStatus denotes the current status of the Application
|
||||
Reconciliation
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
ready:
|
||||
type: boolean
|
||||
staged:
|
||||
type: boolean
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
name:
|
||||
type: string
|
||||
subcharts:
|
||||
additionalProperties:
|
||||
description: ChartStatus denotes the current status of the Application
|
||||
Reconciliation
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
ready:
|
||||
type: boolean
|
||||
staged:
|
||||
type: boolean
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
required:
|
||||
- application
|
||||
- name
|
||||
- subcharts
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
|
|
|
@ -6,23 +6,20 @@ metadata:
|
|||
spec:
|
||||
# Namespace to which the HelmRelease object is deployed
|
||||
namespace: "default"
|
||||
# HelmRelease spec fields
|
||||
# https://docs.fluxcd.io/projects/helm-operator/en/1.0.0-rc9/references/helmrelease-custom-resource.html#helmrelease-custom-resource
|
||||
chart:
|
||||
repo: "https://charts.bitnami.com/bitnami"
|
||||
name: kafka
|
||||
version: 12.4.1
|
||||
repo: bitnami
|
||||
groupID: "dev"
|
||||
subcharts:
|
||||
# subchart ordering
|
||||
- name: zookeeper
|
||||
dependencies: []
|
||||
# Overlay Values
|
||||
values:
|
||||
global:
|
||||
imagePullSecrets: []
|
||||
zookeeper:
|
||||
enable: true
|
||||
targetNamespace: "kafka-dev-ns"
|
||||
# HelmRelease spec fields
|
||||
# https://docs.fluxcd.io/projects/helm-operator/en/1.0.0-rc9/references/helmrelease-custom-resource.html#helmrelease-custom-resource
|
||||
chart:
|
||||
repository: "https://charts.bitnami.com/bitnami"
|
||||
name: kafka
|
||||
version: 12.4.1
|
||||
overlays: "{\"global\":{\"imagePullSecrets\":[]},\"zookeeper\":{\"enable\":true}}"
|
||||
# targetNamespace: "kafka-dev-ns"
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -5,22 +5,17 @@ metadata:
|
|||
name: redis-dev
|
||||
spec:
|
||||
namespace: "default"
|
||||
repo: bitnami
|
||||
groupID: "dev"
|
||||
subcharts: []
|
||||
# HelmRelease spec fields
|
||||
# https://docs.fluxcd.io/projects/helm-operator/en/1.0.0-rc9/references/helmrelease-custom-resource.html#helmrelease-custom-resource
|
||||
chart:
|
||||
repo: "https://charts.bitnami.com/bitnami"
|
||||
repository: "https://charts.bitnami.com/bitnami"
|
||||
name: redis
|
||||
version: 12.2.3
|
||||
subcharts: []
|
||||
# Overlay Values
|
||||
values:
|
||||
global:
|
||||
imageRegistry: nil
|
||||
imagePullSecrets: []
|
||||
master:
|
||||
persistence:
|
||||
size: "4Gi"
|
||||
targetNamespace: "redis-dev-ns"
|
||||
overlays: "{\"global\":{\"imageRegistry\":\"nil\",\"imagePullSecrets\":[]},\"master\":{\"persistence\":{\"size\":\"4Gi\"}}}"
|
||||
# targetNamespace: "redis-dev-ns"
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,10 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Azure/Orkestra/pkg/configurer"
|
||||
"github.com/Azure/Orkestra/pkg/workflow"
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/record"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
@ -16,12 +19,23 @@ import (
|
|||
orkestrav1alpha1 "github.com/Azure/Orkestra/api/v1alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
appgroupNameKey = "appgroup"
|
||||
)
|
||||
|
||||
// ApplicationGroupReconciler reconciles a ApplicationGroup object
|
||||
type ApplicationGroupReconciler struct {
|
||||
client.Client
|
||||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
|
||||
// Cfg is the controller configuration that gives access to the helm registry configuration (and more as we add options to configure the controller)
|
||||
Cfg *configurer.Controller
|
||||
Engine workflow.Engine
|
||||
|
||||
// WorkflowNS is the namespace to which (generated) Argo Workflow object is deployed
|
||||
WorkflowNS string
|
||||
|
||||
// Recorder generates kubernetes events
|
||||
Recorder record.EventRecorder
|
||||
}
|
||||
|
@ -30,12 +44,42 @@ type ApplicationGroupReconciler struct {
|
|||
// +kubebuilder:rbac:groups=orkestra.azure.microsoft.com,resources=applicationgroups/status,verbs=get;update;patch
|
||||
|
||||
func (r *ApplicationGroupReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
_ = context.Background()
|
||||
_ = r.Log.WithValues("applicationgroup", req.NamespacedName)
|
||||
var requeue bool
|
||||
var err error
|
||||
var appGroup orkestrav1alpha1.ApplicationGroup
|
||||
|
||||
// your logic here
|
||||
ctx := context.Background()
|
||||
logr := r.Log.WithValues(appgroupNameKey, req.NamespacedName.Name)
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
if err := r.Get(ctx, req.NamespacedName, &appGroup); err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
logr.V(3).Info("skip reconciliation since AppGroup instance not found on the cluster")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
logr.Error(err, "unable to fetch ApplicationGroup instance")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
logr = logr.WithValues("status-ready", appGroup.Status.Ready, "status-error", appGroup.Status.Error)
|
||||
|
||||
if appGroup.Status.Ready {
|
||||
logr.V(3).Info("skip reconciling since AppGroup has already been successfully reconciled")
|
||||
return ctrl.Result{Requeue: false}, nil
|
||||
}
|
||||
|
||||
// info log if status error is not nil on reconciling
|
||||
if appGroup.Status.Error != "" {
|
||||
logr.V(3).Info("reconciling AppGroup instance previously in error state")
|
||||
}
|
||||
|
||||
requeue, err = r.reconcile(ctx, logr, r.WorkflowNS, &appGroup)
|
||||
defer r.updateStatusAndEvent(ctx, appGroup, requeue, err)
|
||||
if err != nil {
|
||||
logr.Error(err, "failed to reconcile ApplicationGroup instance")
|
||||
return ctrl.Result{Requeue: requeue}, err
|
||||
}
|
||||
|
||||
return ctrl.Result{Requeue: requeue}, nil
|
||||
}
|
||||
|
||||
func (r *ApplicationGroupReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
|
@ -50,7 +94,7 @@ func (r *ApplicationGroupReconciler) updateStatusAndEvent(ctx context.Context, g
|
|||
errStr = err.Error()
|
||||
}
|
||||
|
||||
grp.Status = orkestrav1alpha1.ApplicationGroupStatus{}
|
||||
grp.Status.Error = errStr
|
||||
|
||||
_ = r.Status().Update(ctx, &grp)
|
||||
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
orkestrav1alpha1 "github.com/Azure/Orkestra/api/v1alpha1"
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidSpec = fmt.Errorf("custom resource spec is invalid")
|
||||
// ErrRequeue describes error while requeuing
|
||||
ErrRequeue = fmt.Errorf("(transitory error) Requeue-ing resource to try again")
|
||||
)
|
||||
|
||||
func (r *ApplicationGroupReconciler) reconcile(ctx context.Context, l logr.Logger, ns string, appGroup *orkestrav1alpha1.ApplicationGroup) (bool, error) {
|
||||
l = l.WithValues(appgroupNameKey, appGroup.Name)
|
||||
l.V(3).Info("Reconciling ApplicationGroup object")
|
||||
|
||||
if len(appGroup.Spec.Applications) == 0 {
|
||||
l.Error(ErrInvalidSpec, "ApplicationGroup must list atleast one Application")
|
||||
return false, fmt.Errorf("application group must list atleast one Application : %w", ErrInvalidSpec)
|
||||
}
|
||||
|
||||
// Readiness matrix stores the status of the Application object
|
||||
// It assists in accounting for the status of all Applications in the app group
|
||||
appReadinessMatrix := populateReadinessMatrix(appGroup.Spec.Applications)
|
||||
// Cache the application objects to pass into the generate function for workflow gen
|
||||
appObjCache := make([]*orkestrav1alpha1.Application, 0, len(appGroup.Spec.Applications))
|
||||
|
||||
// Lookup each application in the App group to check if status is ready or errored
|
||||
for _, application := range appGroup.Spec.Applications {
|
||||
ll := l.WithValues("application", application.Name)
|
||||
ll.V(3).Info("Looking up Application instance")
|
||||
|
||||
obj := &orkestrav1alpha1.Application{}
|
||||
err := r.Client.Get(ctx, types.NamespacedName{Namespace: "", Name: application.Name}, obj)
|
||||
if err != nil {
|
||||
// requeue for IsNotFound error but for any other error this is unrecoverable
|
||||
// and should not be requeued
|
||||
if errors.IsNotFound(err) {
|
||||
ll.V(2).Info("object not found - requeueing")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
ll.Error(err, "unrecoverable application object GET error - will not requeue")
|
||||
return false, err
|
||||
}
|
||||
status := obj.Status
|
||||
|
||||
if !status.Application.Ready || status.Application.Error != "" {
|
||||
ll.V(1).Info("application not in Ready state or in Error state - requeueing")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
appObjCache = append(appObjCache, obj)
|
||||
appReadinessMatrix[obj.Name] = true
|
||||
|
||||
if appGroup.Status.Applications == nil {
|
||||
appGroup.Status.Applications = make([]orkestrav1alpha1.ApplicationStatus, 0)
|
||||
}
|
||||
if !entryExists(obj.Name, appGroup.Status.Applications) {
|
||||
appStatus := orkestrav1alpha1.ApplicationStatus{
|
||||
Name: obj.Name,
|
||||
Application: obj.Status.Application,
|
||||
Subcharts: obj.Status.Subcharts,
|
||||
}
|
||||
appGroup.Status.Applications = append(appGroup.Status.Applications, appStatus)
|
||||
}
|
||||
}
|
||||
|
||||
// Safety check that all applications are accounted for and that all are in READY state
|
||||
for k, v := range appReadinessMatrix {
|
||||
if v == false {
|
||||
l.V(1).Info("application not in ready state - requeueing", "application", k)
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// if target workflow namespace is unset, then set it to the default namespace explicitly
|
||||
if ns == "" {
|
||||
ns = defaultNamespace()
|
||||
}
|
||||
|
||||
// Generate the Workflow object to submit to Argo
|
||||
return r.generateWorkflow(ctx, l, ns, appGroup, appObjCache)
|
||||
}
|
||||
|
||||
func populateReadinessMatrix(apps []orkestrav1alpha1.DAG) map[string]bool {
|
||||
readyMatrix := make(map[string]bool)
|
||||
for _, app := range apps {
|
||||
readyMatrix[app.Name] = false
|
||||
}
|
||||
return readyMatrix
|
||||
}
|
||||
|
||||
func entryExists(name string, ss []orkestrav1alpha1.ApplicationStatus) bool {
|
||||
for _, v := range ss {
|
||||
if v.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *ApplicationGroupReconciler) generateWorkflow(ctx context.Context, logr logr.Logger, ns string, g *orkestrav1alpha1.ApplicationGroup, apps []*orkestrav1alpha1.Application) (requeue bool, err error) {
|
||||
err = r.Engine.Generate(ctx, logr, ns, g, apps)
|
||||
if err != nil {
|
||||
logr.Error(err, "engine failed to generate workflow")
|
||||
return false, fmt.Errorf("failed to generate workflow : %w", err)
|
||||
}
|
||||
|
||||
err = r.Engine.Submit(ctx, logr, g)
|
||||
if err != nil {
|
||||
logr.Error(err, "engine failed to submit workflow")
|
||||
return false, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func defaultNamespace() string {
|
||||
if ns, ok := os.LookupEnv("WORKFLOW_NAMESPACE"); ok {
|
||||
return ns
|
||||
}
|
||||
return v1.NamespaceDefault
|
||||
}
|
|
@ -7,6 +7,8 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/record"
|
||||
|
@ -14,6 +16,12 @@ import (
|
|||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
orkestrav1alpha1 "github.com/Azure/Orkestra/api/v1alpha1"
|
||||
"github.com/Azure/Orkestra/pkg/configurer"
|
||||
"github.com/Azure/Orkestra/pkg/registry"
|
||||
)
|
||||
|
||||
const (
|
||||
appNameKey = "appgroup"
|
||||
)
|
||||
|
||||
// ApplicationReconciler reconciles a Application object
|
||||
|
@ -22,6 +30,18 @@ type ApplicationReconciler struct {
|
|||
Log logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
|
||||
// Cfg is the controller configuration that gives access to the helm registry configuration (and more as we add options to configure the controller)
|
||||
Cfg *configurer.Controller
|
||||
|
||||
// RegistryClient interacts with the helm registries to pull and push charts
|
||||
RegistryClient *registry.Client
|
||||
|
||||
// StagingRepoName is the nickname for the repository used for staging artifacts before being deployed using the HelmRelease object
|
||||
StagingRepoName string
|
||||
|
||||
// TargetDir to stage the charts before pushing
|
||||
TargetDir string
|
||||
|
||||
// Recorder generates kubernetes events
|
||||
Recorder record.EventRecorder
|
||||
}
|
||||
|
@ -30,12 +50,42 @@ type ApplicationReconciler struct {
|
|||
// +kubebuilder:rbac:groups=orkestra.azure.microsoft.com,resources=applications/status,verbs=get;update;patch
|
||||
|
||||
func (r *ApplicationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||
_ = context.Background()
|
||||
_ = r.Log.WithValues("application", req.NamespacedName)
|
||||
var requeue bool
|
||||
var err error
|
||||
var application orkestrav1alpha1.Application
|
||||
|
||||
// your logic here
|
||||
ctx := context.Background()
|
||||
logr := r.Log.WithValues(appNameKey, req.NamespacedName.Name)
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
if err := r.Get(ctx, req.NamespacedName, &application); err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
logr.V(3).Info("skip reconciliation since Appllication instance not found on the cluster")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
logr.Error(err, "unable to fetch Application instance")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
logr = logr.WithValues("status-ready", application.Status.Application.Ready, "status-error", application.Status.Application.Error)
|
||||
|
||||
if application.Status.Application.Ready {
|
||||
logr.V(3).Info("skip reconciling since Application has already been successfully reconciled")
|
||||
return ctrl.Result{Requeue: false}, nil
|
||||
}
|
||||
|
||||
// info log if status error is not nil on reconciling
|
||||
if application.Status.Application.Error != "" {
|
||||
logr.V(3).Info("reconciling Application instance previously in error state")
|
||||
}
|
||||
|
||||
requeue, err = r.reconcile(ctx, logr, &application)
|
||||
defer r.updateStatusAndEvent(ctx, application, requeue, err)
|
||||
if err != nil {
|
||||
logr.Error(err, "failed to reconcile application instance")
|
||||
return ctrl.Result{Requeue: requeue}, err
|
||||
}
|
||||
|
||||
return ctrl.Result{Requeue: requeue}, nil
|
||||
}
|
||||
|
||||
func (r *ApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
|
@ -50,7 +100,8 @@ func (r *ApplicationReconciler) updateStatusAndEvent(ctx context.Context, app or
|
|||
errStr = err.Error()
|
||||
}
|
||||
|
||||
app.Status = orkestrav1alpha1.ApplicationStatus{}
|
||||
app.Status.Application.Ready = !requeue
|
||||
app.Status.Application.Error = errStr
|
||||
|
||||
_ = r.Status().Update(ctx, &app)
|
||||
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
orkestrav1alpha1 "github.com/Azure/Orkestra/api/v1alpha1"
|
||||
"github.com/Azure/Orkestra/pkg/registry"
|
||||
"github.com/go-logr/logr"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
)
|
||||
|
||||
func (r *ApplicationReconciler) reconcile(ctx context.Context, l logr.Logger, application *orkestrav1alpha1.Application) (bool, error) {
|
||||
ll := l.WithValues("application", application.Name, "group", application.Spec.GroupID)
|
||||
|
||||
application.Status.Name = application.Name
|
||||
|
||||
if application.Status.Subcharts == nil {
|
||||
application.Status.Subcharts = make(map[string]orkestrav1alpha1.ChartStatus)
|
||||
}
|
||||
|
||||
if application.Status.Application.Ready {
|
||||
ll.Info("application already in ready state")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
repoKey := application.Spec.ChartRepoNickname
|
||||
name := application.Spec.HelmReleaseSpec.Name
|
||||
version := application.Spec.HelmReleaseSpec.Version
|
||||
|
||||
fpath, appCh, err := r.RegistryClient.PullChart(ll, repoKey, name, version)
|
||||
defer func() {
|
||||
if r.Cfg.Cleanup {
|
||||
os.Remove(fpath)
|
||||
}
|
||||
}()
|
||||
if err != nil || appCh == nil {
|
||||
ll.Error(err, "failed to pull application chart")
|
||||
return false, fmt.Errorf("failed to pull application chart %s/%s:%s : %w", repoKey, name, version, err)
|
||||
}
|
||||
|
||||
// If Dependencies - extract subchart and push each to staging registry
|
||||
if !isDependenciesEmbedded(appCh) {
|
||||
stagingRepoName := r.StagingRepoName
|
||||
|
||||
if appCh.Dependencies() != nil {
|
||||
for _, sc := range appCh.Dependencies() {
|
||||
cs := orkestrav1alpha1.ChartStatus{}
|
||||
|
||||
if err := sc.Validate(); err != nil {
|
||||
cs.Error = err.Error()
|
||||
ll.Error(err, "failed to validate application subchart for staging registry")
|
||||
return false, fmt.Errorf("failed to validate application subchart for staging registry : %w", err)
|
||||
}
|
||||
|
||||
path, err := registry.SaveChartPackage(sc, r.TargetDir)
|
||||
if err != nil {
|
||||
cs.Error = err.Error()
|
||||
ll.Error(err, "failed to save subchart package as tgz")
|
||||
return false, fmt.Errorf("failed to save subchart package as tgz at location %s : %w", path, err)
|
||||
}
|
||||
|
||||
err = r.RegistryClient.PushChart(ll, stagingRepoName, path, sc)
|
||||
|
||||
if err != nil {
|
||||
cs.Error = err.Error()
|
||||
ll.Error(err, "failed to push application subchart to staging registry")
|
||||
return false, fmt.Errorf("failed to push application subchart to staging registry : %w", err)
|
||||
}
|
||||
|
||||
cs.Staged = true
|
||||
// TODO (nitishm) : Rather than the path we can just store the version of the chart
|
||||
cs.Version = sc.Metadata.Version
|
||||
cs.Ready = true
|
||||
cs.Error = ""
|
||||
|
||||
application.Status.Subcharts[sc.Name()] = cs
|
||||
}
|
||||
}
|
||||
|
||||
// unset dependencies
|
||||
appCh.SetDependencies()
|
||||
if err := appCh.Validate(); err != nil {
|
||||
application.Status.Application.Error = err.Error()
|
||||
ll.Error(err, "failed to validate application chart for staging registry")
|
||||
return false, fmt.Errorf("failed to validate application chart for staging registry : %w", err)
|
||||
}
|
||||
|
||||
path := r.TargetDir + "/" + appCh.Name() + "-" + appCh.Metadata.Version + ".tgz"
|
||||
err := r.RegistryClient.PushChart(ll, stagingRepoName, path, appCh)
|
||||
if err != nil {
|
||||
application.Status.Application.Error = err.Error()
|
||||
ll.Error(err, "failed to push modified application chart to staging registry")
|
||||
return false, fmt.Errorf("failed to push modified application chart to staging registry : %w", err)
|
||||
}
|
||||
|
||||
application.Status.Application.Staged = true
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func isDependenciesEmbedded(ch *chart.Chart) bool {
|
||||
for _, d := range ch.Metadata.Dependencies {
|
||||
if _, err := url.ParseRequestURI(d.Repository); err == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -3,69 +3,69 @@
|
|||
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
// import (
|
||||
// "path/filepath"
|
||||
// "testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
// . "github.com/onsi/ginkgo"
|
||||
// . "github.com/onsi/gomega"
|
||||
// "k8s.io/client-go/kubernetes/scheme"
|
||||
// "k8s.io/client-go/rest"
|
||||
// "sigs.k8s.io/controller-runtime/pkg/client"
|
||||
// "sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
// "sigs.k8s.io/controller-runtime/pkg/envtest/printer"
|
||||
// logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
// "sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
orkestrav1alpha1 "github.com/Azure/Orkestra/api/v1alpha1"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
// orkestrav1alpha1 "github.com/Azure/Orkestra/api/v1alpha1"
|
||||
// // +kubebuilder:scaffold:imports
|
||||
// )
|
||||
|
||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
// var cfg *rest.Config
|
||||
// var k8sClient client.Client
|
||||
// var testEnv *envtest.Environment
|
||||
|
||||
func TestAPIs(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
// func TestAPIs(t *testing.T) {
|
||||
// RegisterFailHandler(Fail)
|
||||
|
||||
RunSpecsWithDefaultAndCustomReporters(t,
|
||||
"Controller Suite",
|
||||
[]Reporter{printer.NewlineReporter{}})
|
||||
}
|
||||
// RunSpecsWithDefaultAndCustomReporters(t,
|
||||
// "Controller Suite",
|
||||
// []Reporter{printer.NewlineReporter{}})
|
||||
// }
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
|
||||
// var _ = BeforeSuite(func(done Done) {
|
||||
// logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
|
||||
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
|
||||
}
|
||||
// By("bootstrapping test environment")
|
||||
// testEnv = &envtest.Environment{
|
||||
// CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
|
||||
// }
|
||||
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
// var err error
|
||||
// cfg, err = testEnv.Start()
|
||||
// Expect(err).ToNot(HaveOccurred())
|
||||
// Expect(cfg).ToNot(BeNil())
|
||||
|
||||
err = orkestrav1alpha1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// err = orkestrav1alpha1.AddToScheme(scheme.Scheme)
|
||||
// Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = orkestrav1alpha1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
// err = orkestrav1alpha1.AddToScheme(scheme.Scheme)
|
||||
// Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// +kubebuilder:scaffold:scheme
|
||||
// // +kubebuilder:scaffold:scheme
|
||||
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
// k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
// Expect(err).ToNot(HaveOccurred())
|
||||
// Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
close(done)
|
||||
}, 60)
|
||||
// close(done)
|
||||
// }, 60)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
// var _ = AfterSuite(func() {
|
||||
// By("tearing down the test environment")
|
||||
// err := testEnv.Stop()
|
||||
// Expect(err).ToNot(HaveOccurred())
|
||||
// })
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
apiVersion: orkestra.azure.microsoft.com/v1alpha1
|
||||
kind: ApplicationGroup
|
||||
metadata:
|
||||
name: dev
|
||||
spec:
|
||||
applications:
|
||||
- name: kafka-dev
|
||||
dependencies:
|
||||
- redis-dev
|
||||
- name: redis-dev
|
||||
# TODO (nitishm) - define these fields using Argo Workflow spec
|
||||
# strategy:
|
||||
# rollout:
|
||||
# backout:
|
|
@ -0,0 +1,25 @@
|
|||
# Example: Kafka with Zookeeper as a dependency
|
||||
apiVersion: orkestra.azure.microsoft.com/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: kafka-dev
|
||||
spec:
|
||||
# Namespace to which the HelmRelease object is deployed
|
||||
namespace: "orkestra"
|
||||
repo: bitnami
|
||||
groupID: "dev"
|
||||
subcharts:
|
||||
# subchart ordering
|
||||
- name: zookeeper
|
||||
dependencies: []
|
||||
# HelmRelease spec fields
|
||||
# https://docs.fluxcd.io/projects/helm-operator/en/1.0.0-rc9/references/helmrelease-custom-resource.html#helmrelease-custom-resource
|
||||
chart:
|
||||
repository: "https://charts.bitnami.com/bitnami"
|
||||
name: kafka
|
||||
version: 12.4.1
|
||||
overlays: "{\"global\":{\"imagePullSecrets\":[]},\"zookeeper\":{\"enable\":true}}"
|
||||
# targetNamespace: "kafka-dev-ns"
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# Example: Kafka with Zookeeper as a dependency
|
||||
apiVersion: orkestra.azure.microsoft.com/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: redis-dev
|
||||
spec:
|
||||
namespace: "orkestra"
|
||||
repo: bitnami
|
||||
groupID: "dev"
|
||||
subcharts: []
|
||||
# HelmRelease spec fields
|
||||
# https://docs.fluxcd.io/projects/helm-operator/en/1.0.0-rc9/references/helmrelease-custom-resource.html#helmrelease-custom-resource
|
||||
chart:
|
||||
repository: "https://charts.bitnami.com/bitnami"
|
||||
name: redis
|
||||
version: 12.2.3
|
||||
overlays: "{\"global\":{\"imageRegistry\":\"nil\",\"imagePullSecrets\":[]},\"master\":{\"persistence\":{\"size\":\"4Gi\"}}}"
|
||||
# targetNamespace: "redis-dev-ns"
|
||||
|
||||
|
||||
|
60
go.mod
60
go.mod
|
@ -1,17 +1,41 @@
|
|||
module github.com/Azure/Orkestra
|
||||
|
||||
go 1.13
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/argoproj/argo v2.5.2+incompatible
|
||||
github.com/chartmuseum/helm-push v0.9.0
|
||||
github.com/fluxcd/helm-operator v1.2.0
|
||||
github.com/go-delve/delve v1.5.1 // indirect
|
||||
github.com/go-logr/logr v0.1.0
|
||||
github.com/onsi/ginkgo v1.11.0
|
||||
github.com/onsi/gomega v1.8.1
|
||||
k8s.io/apimachinery v0.17.4
|
||||
github.com/gofrs/flock v0.7.1
|
||||
github.com/golang/protobuf v1.4.3 // indirect
|
||||
github.com/google/go-cmp v0.5.2
|
||||
github.com/onsi/ginkgo v1.14.0
|
||||
github.com/onsi/gomega v1.10.1
|
||||
github.com/spf13/viper v1.7.0
|
||||
go.opencensus.io v0.22.5 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
|
||||
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 // indirect
|
||||
golang.org/x/text v0.3.4 // indirect
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb // indirect
|
||||
google.golang.org/grpc v1.33.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
helm.sh/helm/v3 v3.3.4
|
||||
k8s.io/api v0.17.5
|
||||
k8s.io/apimachinery v0.17.5
|
||||
k8s.io/client-go v11.0.0+incompatible
|
||||
sigs.k8s.io/controller-runtime v0.5.0
|
||||
k8s.io/helm v2.16.12+incompatible
|
||||
sigs.k8s.io/controller-runtime v0.4.0
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
)
|
||||
|
||||
// Hack to import helm-operator package
|
||||
// Start hack
|
||||
|
||||
replace sigs.k8s.io/controller-tools => sigs.k8s.io/controller-tools v0.2.9
|
||||
|
||||
replace (
|
||||
github.com/docker/docker => github.com/moby/moby v1.4.2-0.20200203170920-46ec8731fbce
|
||||
github.com/fluxcd/flux => github.com/fluxcd/flux v1.19.0
|
||||
|
@ -19,8 +43,34 @@ replace (
|
|||
github.com/fluxcd/helm-operator/pkg/install => github.com/fluxcd/helm-operator/pkg/install v0.0.0-20200407140510-8d71b0072a3e
|
||||
)
|
||||
|
||||
// Pin Flux to 1.18.0
|
||||
|
||||
// Force upgrade because of a transitive downgrade.
|
||||
// github.com/fluxcd/helm-operator
|
||||
// +-> github.com/fluxcd/flux@v1.17.2
|
||||
// +-> k8s.io/client-go@v11.0.0+incompatible
|
||||
replace k8s.io/client-go => k8s.io/client-go v0.17.2
|
||||
|
||||
// Force upgrade because of a transitive downgrade.
|
||||
// github.com/fluxcd/flux
|
||||
// +-> github.com/fluxcd/helm-operator@v1.0.0-rc6
|
||||
// +-> helm.sh/helm/v3@v3.1.2
|
||||
// +-> helm.sh/helm@v2.16.1
|
||||
replace (
|
||||
helm.sh/helm/v3 => helm.sh/helm/v3 v3.1.2
|
||||
k8s.io/helm => k8s.io/helm v2.16.3+incompatible
|
||||
)
|
||||
|
||||
// Force upgrade because of transitive downgrade.
|
||||
// runc >=1.0.0-RC10 patches CVE-2019-19921.
|
||||
// runc >=1.0.0-RC7 patches CVE-2019-5736.
|
||||
// github.com/fluxcd/helm-operator
|
||||
// +-> helm.sh/helm/v3@v3.1.2
|
||||
// +-> github.com/opencontainers/runc@v0.1.1
|
||||
replace github.com/opencontainers/runc => github.com/opencontainers/runc v1.0.0-rc10
|
||||
|
||||
// End hack
|
||||
|
||||
replace github.com/googleapis/gnostic => github.com/googleapis/gnostic v0.3.1
|
||||
|
||||
replace github.com/docker/distribution => github.com/docker/distribution v0.0.0-20191216044856-a8371794149d
|
||||
|
|
1175
go.sum
1175
go.sum
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
77
main.go
77
main.go
|
@ -7,6 +7,9 @@ import (
|
|||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/Azure/Orkestra/pkg/configurer"
|
||||
"github.com/Azure/Orkestra/pkg/registry"
|
||||
"github.com/Azure/Orkestra/pkg/workflow"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
||||
|
@ -15,9 +18,15 @@ import (
|
|||
|
||||
orkestrav1alpha1 "github.com/Azure/Orkestra/api/v1alpha1"
|
||||
"github.com/Azure/Orkestra/controllers"
|
||||
v1alpha12 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
|
||||
helmopv1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
const (
|
||||
stagingRepoNameEnv = "STAGING_REPO_NAME"
|
||||
)
|
||||
|
||||
var (
|
||||
scheme = runtime.NewScheme()
|
||||
setupLog = ctrl.Log.WithName("setup")
|
||||
|
@ -28,15 +37,30 @@ func init() {
|
|||
|
||||
_ = orkestrav1alpha1.AddToScheme(scheme)
|
||||
// +kubebuilder:scaffold:scheme
|
||||
|
||||
// Add Argo Workflow scheme to operator
|
||||
_ = v1alpha12.AddToScheme(scheme)
|
||||
|
||||
// Add HelmRelease scheme to operator
|
||||
_ = helmopv1.AddToScheme(scheme)
|
||||
}
|
||||
|
||||
func main() {
|
||||
var metricsAddr string
|
||||
var enableLeaderElection bool
|
||||
var configPath string
|
||||
var stagingRepoName string
|
||||
var tempChartStoreTargetDir string
|
||||
var cleanup bool
|
||||
|
||||
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
|
||||
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
|
||||
"Enable leader election for controller manager. "+
|
||||
"Enabling this will ensure there is only one active controller manager.")
|
||||
flag.StringVar(&configPath, "config", "", "The path to the controller config file")
|
||||
flag.StringVar(&stagingRepoName, "staging-repo-name", "", "The nickname for the helm registry used for staging artifacts (ENV - STAGING_REPO_URL). NOTE: Flag overrides env value")
|
||||
flag.StringVar(&tempChartStoreTargetDir, "chart-store-path", "", "The temporary storage path for the downloaded and staged chart artifacts")
|
||||
flag.BoolVar(&cleanup, "cleanup", false, "cleanup the pull/downloaded charts from the temporary storage path")
|
||||
flag.Parse()
|
||||
|
||||
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
|
||||
|
@ -53,18 +77,59 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
if stagingRepoName == "" {
|
||||
if s := os.Getenv(stagingRepoNameEnv); s != "" {
|
||||
stagingRepoName = s
|
||||
} else {
|
||||
setupLog.Error(err, "staging repo URL must be set")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
cfg, err := configurer.NewConfigurer(configPath)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to create new configurer instance", "controller", "config")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg.Ctrl.Cleanup = cleanup
|
||||
|
||||
rc, err := registry.NewClient(
|
||||
ctrl.Log.Logger, cfg.Ctrl.Registries,
|
||||
registry.TargetDir(tempChartStoreTargetDir),
|
||||
)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to create new registry client", "controller", "registry-client")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = (&controllers.ApplicationReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("Application"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("Application"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Cfg: cfg.Ctrl,
|
||||
RegistryClient: rc,
|
||||
StagingRepoName: stagingRepoName,
|
||||
TargetDir: tempChartStoreTargetDir,
|
||||
Recorder: mgr.GetEventRecorderFor("application-controller"),
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Application")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sCfg, err := cfg.Ctrl.RegistryConfig(stagingRepoName)
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to find staging repo configuration", "controller", "registry-config")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = (&controllers.ApplicationGroupReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("ApplicationGroup"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Client: mgr.GetClient(),
|
||||
Log: ctrl.Log.WithName("controllers").WithName("ApplicationGroup"),
|
||||
Scheme: mgr.GetScheme(),
|
||||
Cfg: cfg.Ctrl,
|
||||
Engine: workflow.Argo(scheme, mgr.GetClient(), sCfg.URL),
|
||||
Recorder: mgr.GetEventRecorderFor("appgroup-controller"),
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "ApplicationGroup")
|
||||
os.Exit(1)
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
package configurer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/Orkestra/pkg/registry"
|
||||
"github.com/gofrs/flock"
|
||||
"github.com/spf13/viper"
|
||||
"helm.sh/helm/v3/pkg/cli"
|
||||
"helm.sh/helm/v3/pkg/getter"
|
||||
"helm.sh/helm/v3/pkg/repo"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultConfigPath = "/etc/controller/config.yaml"
|
||||
)
|
||||
|
||||
type Configurer struct {
|
||||
v *viper.Viper
|
||||
Ctrl *Controller
|
||||
}
|
||||
|
||||
func NewConfigurer(cfgPath string) (*Configurer, error) {
|
||||
v := viper.New()
|
||||
if cfgPath == "" {
|
||||
cfgPath = defaultConfigPath
|
||||
}
|
||||
|
||||
v.SetConfigFile(cfgPath)
|
||||
err := v.ReadInConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctrlCfg := &Controller{
|
||||
Registries: make(map[string]*registry.Config),
|
||||
Cleanup: false,
|
||||
}
|
||||
|
||||
err = v.Unmarshal(ctrlCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = setupRegistryRepos(ctrlCfg.Registries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Configurer{
|
||||
v: v,
|
||||
Ctrl: ctrlCfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setupRegistryRepos(registries map[string]*registry.Config) error {
|
||||
settings := cli.New()
|
||||
repoFile := settings.RepositoryConfig
|
||||
err := os.MkdirAll(filepath.Dir(repoFile), os.ModePerm)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Acquire a file lock for process synchronization
|
||||
fileLock := flock.New(strings.Replace(repoFile, filepath.Ext(repoFile), ".lock", 1))
|
||||
lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
locked, err := fileLock.TryLockContext(lockCtx, time.Second)
|
||||
if err == nil && locked {
|
||||
defer fileLock.Unlock() //nolint:errcheck
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(repoFile)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
var f repo.File
|
||||
if err := yaml.Unmarshal(b, &f); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// XXX (nitishm) : Probably not needed. We can validate the config.yaml for duplicate entries
|
||||
// Most likely the map will be overwritten with the last named entry.
|
||||
// if o.noUpdate && f.Has(o.name) {
|
||||
// return errors.Errorf("repository name (%s) already exists, please specify a different name", o.name)
|
||||
// }
|
||||
|
||||
for name, cfg := range registries {
|
||||
c := repo.Entry{
|
||||
Name: name,
|
||||
URL: cfg.URL,
|
||||
Username: cfg.Username,
|
||||
Password: cfg.Password,
|
||||
CertFile: cfg.CertFile,
|
||||
KeyFile: cfg.KeyFile,
|
||||
CAFile: cfg.CaFile,
|
||||
}
|
||||
|
||||
r, err := repo.NewChartRepository(&c, getter.All(settings))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := r.DownloadIndexFile(); err != nil {
|
||||
return fmt.Errorf("looks like %q is not a valid chart repository or cannot be reached : %w", c.URL, err)
|
||||
}
|
||||
|
||||
f.Update(&c)
|
||||
|
||||
if err := f.WriteFile(repoFile, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package configurer
|
||||
|
||||
import (
|
||||
"github.com/Azure/Orkestra/pkg/registry"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
Registries registry.RegistryMap `yaml:"registries"`
|
||||
// Cleanup the downloaded charts after they are pushed to staging repo
|
||||
Cleanup bool
|
||||
}
|
||||
|
||||
func (c *Controller) RegistryConfig(key string) (*registry.Config, error) {
|
||||
return c.Registries.RegistryConfig(key)
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package configurer
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/Orkestra/pkg/registry"
|
||||
)
|
||||
|
||||
func TestController_RegistryConfig(t *testing.T) {
|
||||
type fields struct {
|
||||
Registries map[string]*registry.Config
|
||||
}
|
||||
type args struct {
|
||||
key string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want *registry.Config
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Controller{
|
||||
Registries: tt.fields.Registries,
|
||||
}
|
||||
got, err := c.RegistryConfig(tt.args.key)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("RegistryConfig() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("RegistryConfig() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
registries:
|
||||
primary:
|
||||
url: "https://primary-repo.acme.com/"
|
||||
username: "admin"
|
||||
password: "password"
|
||||
accessToken: "eyJpc3MiOiJodHRwczovL2V4YW1wbGUuYXV0aDAuY29tLyIsImF1ZCI6Imh0dHBzOi8vYXBpLmV4YW1wbGUuY29tL2NhbGFuZGFyL3YxLyIsInN1YiI6InVzcl8xMjMiLCJpYXQiOjE0NTg3ODU3OTYsImV4cCI6MTQ1ODg3MjE5Nn0"
|
||||
authHeader: "alternateAuthTokenHeader"
|
||||
caFile: "/path/to/ca/cert"
|
||||
certFile: "/path/to/certfile"
|
||||
keyFile: "path/to/keyfile"
|
||||
insecureSkipVerify: true
|
||||
|
||||
bitnami:
|
||||
url: "https://charts.bitnami.com/bitnami"
|
||||
insecureSkipVerify: true
|
||||
|
||||
staging:
|
||||
url: "http://localhost:8080"
|
||||
# Uncomment this when deployed in cluster
|
||||
# url: "http://releaser-chartmuseum.default:8080"
|
|
@ -0,0 +1,22 @@
|
|||
package registry
|
||||
|
||||
// Config struct captures the configuration fields as per the repoAddOptions - https://github.com/helm/helm/blob/v3.1.2/cmd/helm/repo_add.go#L39
|
||||
type Config struct {
|
||||
URL string `yaml:"url" json:"url,omitempty"`
|
||||
Username string `yaml:"username" json:"username,omitempty"`
|
||||
Password string `yaml:"password" json:"password,omitempty"`
|
||||
|
||||
AuthHeader string `yaml:"authHeader" json:"auth_header,omitempty"`
|
||||
CaFile string `yaml:"caFile" json:"ca_file,omitempty"`
|
||||
CertFile string `yaml:"certFile" json:"cert_file,omitempty"`
|
||||
KeyFile string `yaml:"keyFile" json:"key_file,omitempty"`
|
||||
InsecureSkipVerify bool `yaml:"insecureSkipVerify" json:"insecure_skip_verify,omitempty"`
|
||||
|
||||
// TODO (nitishm) : Add these fields to the config.yaml as and when the need arrives
|
||||
// See https://github.com/helm/helm/blob/v3.2.1/pkg/action/install.go#L106-L116
|
||||
// Keyring string // --keyring
|
||||
// Verify bool // --verify
|
||||
// Version string // --version
|
||||
|
||||
AccessToken string `yaml:"accessToken" json:"access_token,omitempty"`
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package registry
|
||||
|
||||
import (
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
)
|
||||
|
||||
type Option func(c *Client)
|
||||
|
||||
type PullOption func(p *action.Pull)
|
||||
|
||||
// Client Options
|
||||
|
||||
func TargetDir(d string) Option {
|
||||
return func(c *Client) {
|
||||
c.TargetDir = d
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
)
|
||||
|
||||
func (c *Client) PullChart(l logr.Logger, repoKey, chartName, version string) (string, *chart.Chart, error) {
|
||||
l.WithValues("repo-key", repoKey, "chart-name", chartName, "chart-version", version)
|
||||
|
||||
l.V(3).Info("pulling chart")
|
||||
|
||||
rCfg, err := c.registries.RegistryConfig(repoKey)
|
||||
if err != nil {
|
||||
l.Error(err, "failed to find registry with provided key in registries map")
|
||||
return "", nil, fmt.Errorf("failed to find registry with repoKey %s Name %s Version %s in registries map : %w", repoKey, chartName, version, err)
|
||||
}
|
||||
|
||||
c.cfg.pull.Username = rCfg.Username
|
||||
c.cfg.pull.Password = rCfg.Password
|
||||
c.cfg.pull.CaFile = rCfg.CaFile
|
||||
c.cfg.pull.CaFile = rCfg.CaFile
|
||||
c.cfg.pull.CertFile = rCfg.CertFile
|
||||
c.cfg.pull.KeyFile = rCfg.KeyFile
|
||||
|
||||
filePath := fmt.Sprintf("%s/%s-%s.tgz", c.TargetDir, chartName, version)
|
||||
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
l.V(3).Info("chart artifact not found in target directory - downloading")
|
||||
_, err = c.cfg.pull.Run(chartURL(rCfg.URL, chartName, version))
|
||||
if err != nil {
|
||||
l.Error(err, "failed to pull chart from repo")
|
||||
return "", nil, fmt.Errorf("failed to pull chart from repoKey %s Name %s Version %s in registries map : %w", repoKey, chartName, version, err)
|
||||
}
|
||||
} else {
|
||||
l.V(3).Info("chart artifact found in target directory - skip downloading")
|
||||
}
|
||||
|
||||
_, err = c.cfg.pull.ChartPathOptions.LocateChart(filePath, c.cfg.pull.Settings)
|
||||
if err != nil {
|
||||
l.Error(err, "failed to locate chart in filesystem")
|
||||
return "", nil, fmt.Errorf("failed to locate chart in filesystem at path %s : %w", filePath, err)
|
||||
}
|
||||
|
||||
var ch *chart.Chart
|
||||
|
||||
ch, err = loader.LoadFile(filePath)
|
||||
|
||||
if err != nil {
|
||||
l.Error(err, "failed to load chart")
|
||||
return "", nil, fmt.Errorf("failed to load chart: %w", err)
|
||||
}
|
||||
|
||||
if !(ch.Metadata.Type == "application" || ch.Metadata.Type == "") {
|
||||
return "", nil, fmt.Errorf("%s charts are not installable", ch.Metadata.Type)
|
||||
}
|
||||
|
||||
return filePath, ch, nil
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package registry
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/chartmuseum/helm-push/pkg/chartmuseum"
|
||||
"github.com/go-logr/logr"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
)
|
||||
|
||||
// PushChart pushes the chart to the repository specified by the repoKey. The repository setting is fetched from the associated registry config file
|
||||
func (c *Client) PushChart(l logr.Logger, repoKey, pkgPath string, ch *chart.Chart) error {
|
||||
chartName := ch.Name()
|
||||
version := ch.Metadata.Version
|
||||
|
||||
l.WithValues("repo-key", repoKey, "chart-name", chartName, "chart-version", version)
|
||||
l.V(3).Info("pushing chart")
|
||||
|
||||
rCfg, err := c.registries.RegistryConfig(repoKey)
|
||||
if err != nil {
|
||||
l.Error(err, "failed to find registry with provided key in registries map")
|
||||
return fmt.Errorf("failed to find registry with repoKey %s Name %s Version %s in registries map : %w", repoKey, chartName, version, err)
|
||||
}
|
||||
|
||||
c.cfg.push, err = chartmuseum.NewClient(
|
||||
chartmuseum.URL(rCfg.URL),
|
||||
chartmuseum.Username(rCfg.Username),
|
||||
chartmuseum.Password(rCfg.Password),
|
||||
chartmuseum.AccessToken(rCfg.AccessToken),
|
||||
chartmuseum.AuthHeader(rCfg.AuthHeader),
|
||||
chartmuseum.CAFile(rCfg.CaFile),
|
||||
chartmuseum.CertFile(rCfg.CertFile),
|
||||
chartmuseum.KeyFile(rCfg.KeyFile),
|
||||
chartmuseum.InsecureSkipVerify(rCfg.InsecureSkipVerify),
|
||||
)
|
||||
if err != nil {
|
||||
l.Error(err, "failed to create new helm push client")
|
||||
return fmt.Errorf("failed to create new helm push client : %w", err)
|
||||
}
|
||||
|
||||
resp, err := c.cfg.push.UploadChartPackage(pkgPath, true)
|
||||
if err != nil {
|
||||
l.Error(err, "failed to upload chart package")
|
||||
return fmt.Errorf("failed to upload chart package with repoKey %s Name %s Version %s : %w", repoKey, chartName, version, err)
|
||||
}
|
||||
|
||||
err = handlePushResponse(resp)
|
||||
if err != nil {
|
||||
l.Error(err, "failed to handle upload/push http response")
|
||||
return fmt.Errorf("failed to handle upload/push http response for chart package with repoKey %s Name %s Version %s : %w", repoKey, chartName, version, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handlePushResponse(resp *http.Response) error {
|
||||
if resp.StatusCode != 201 {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return getChartmuseumError(b, resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getChartmuseumError(b []byte, code int) error {
|
||||
var er struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
err := json.Unmarshal(b, &er)
|
||||
if err != nil || er.Error == "" {
|
||||
return fmt.Errorf("%d: could not properly parse response JSON: %s", code, string(b))
|
||||
}
|
||||
return fmt.Errorf("%d: %s", code, er.Error)
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package registry
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/chartmuseum/helm-push/pkg/chartmuseum"
|
||||
"github.com/chartmuseum/helm-push/pkg/helm"
|
||||
"github.com/go-logr/logr"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
"helm.sh/helm/v3/pkg/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTargetDir = "/etc/orkestra/charts/pull/"
|
||||
)
|
||||
|
||||
var (
|
||||
errEmptyKey = errors.New("key cannot be an empty string")
|
||||
errEmptyRegistries = errors.New("registries map cannot be nil or empty")
|
||||
errRegistryNotFound = errors.New("registry entry not found in registries map")
|
||||
)
|
||||
|
||||
// RegistryMap specifies a type alias for the registry configuration by repo key
|
||||
type RegistryMap map[string]*Config //nolint:golint
|
||||
|
||||
func (rm RegistryMap) RegistryConfig(key string) (*Config, error) {
|
||||
if key == "" {
|
||||
return nil, errEmptyKey
|
||||
}
|
||||
if rm == nil || len(rm) == 0 {
|
||||
return nil, errEmptyRegistries
|
||||
}
|
||||
|
||||
v, ok := rm[key]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("registry with key %s not found : %w", key, errRegistryNotFound)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type helmActionConfig struct {
|
||||
pull *action.Pull
|
||||
push *chartmuseum.Client
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
l logr.InfoLogger
|
||||
// cfg stores the helm pull and push configurations
|
||||
cfg helmActionConfig
|
||||
// TargetDir is the location where the downloaded chart is saved
|
||||
TargetDir string
|
||||
|
||||
registries RegistryMap
|
||||
}
|
||||
|
||||
// NewClient is the constructor for the registry client
|
||||
func NewClient(l logr.InfoLogger, registries map[string]*Config, opts ...Option) (*Client, error) {
|
||||
// TODO (nitishm) : Check if TargetDir exists.
|
||||
// If not, then create it.
|
||||
cm, err := chartmuseum.NewClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &Client{
|
||||
l: l,
|
||||
TargetDir: defaultTargetDir,
|
||||
cfg: helmActionConfig{
|
||||
pull: action.NewPull(),
|
||||
push: cm,
|
||||
},
|
||||
registries: registries,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
|
||||
err = c.init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Client) init() error {
|
||||
// If no TargetDir option was passed, set to default location
|
||||
if c.TargetDir == "" {
|
||||
c.TargetDir = defaultTargetDir
|
||||
}
|
||||
// Set destination directory where we download the chart
|
||||
c.cfg.pull.DestDir = c.TargetDir
|
||||
|
||||
// Initialize the pull and push clients
|
||||
// Pull client config
|
||||
actionCfg := new(action.Configuration)
|
||||
settings := cli.New()
|
||||
helmDriver := "memory"
|
||||
|
||||
if err := actionCfg.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, c.l.Info); err != nil {
|
||||
return fmt.Errorf("unable to initialize action configuration: %w", err)
|
||||
}
|
||||
|
||||
c.cfg.pull.Settings = settings
|
||||
|
||||
// Push Client
|
||||
// no init required
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func chartURL(repo, chart, version string) string {
|
||||
s := fmt.Sprintf("%s/%s-%s.tgz",
|
||||
strings.Trim(repo, "/"),
|
||||
strings.Trim(chart, "/"),
|
||||
version,
|
||||
)
|
||||
|
||||
// Validate the URL
|
||||
if u, err := url.ParseRequestURI(s); u != nil || err != nil {
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func SaveChartPackage(ch *chart.Chart, dir string) (string, error) {
|
||||
return helm.CreateChartPackage(&helm.Chart{V3: ch}, dir)
|
||||
}
|
|
@ -0,0 +1,458 @@
|
|||
package workflow
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
helmopv1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1"
|
||||
|
||||
"github.com/Azure/Orkestra/api/v1alpha1"
|
||||
v1alpha12 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
const (
|
||||
project = "orkestra"
|
||||
ownershipLabel = "owner"
|
||||
heritageLabel = "heritage"
|
||||
argoAPIVersion = "argoproj.io/v1alpha1"
|
||||
argoKind = "Workflow"
|
||||
entrypointTplName = "entry"
|
||||
|
||||
helmReleaseArg = "helmrelease"
|
||||
helmReleaseExecutor = "helmrelease-executor"
|
||||
|
||||
valuesKeyGlobal = "global"
|
||||
)
|
||||
|
||||
type argo struct {
|
||||
scheme *runtime.Scheme
|
||||
cli client.Client
|
||||
wf *v1alpha12.Workflow
|
||||
|
||||
stagingRepoURL string
|
||||
}
|
||||
|
||||
// Argo implements the Workflow interface for the Argo Workflow based DAG engine
|
||||
func Argo(scheme *runtime.Scheme, c client.Client, stagingRepoURL string) *argo { //nolint:golint
|
||||
return &argo{
|
||||
scheme: scheme,
|
||||
cli: c,
|
||||
stagingRepoURL: stagingRepoURL,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *argo) initWorkflowObject() {
|
||||
a.wf = &v1alpha12.Workflow{}
|
||||
|
||||
a.wf.APIVersion = argoAPIVersion
|
||||
a.wf.Kind = argoKind
|
||||
|
||||
// Entry point is the entry node into the Application Group DAG
|
||||
a.wf.Spec.Entrypoint = entrypointTplName
|
||||
|
||||
// Initialize the Templates slice
|
||||
a.wf.Spec.Templates = make([]v1alpha12.Template, 0)
|
||||
}
|
||||
|
||||
func (a *argo) Generate(ctx context.Context, l logr.Logger, ns string, g *v1alpha1.ApplicationGroup, apps []*v1alpha1.Application) error {
|
||||
if g == nil {
|
||||
l.Error(nil, "ApplicationGroup object cannot be nil")
|
||||
return fmt.Errorf("applicationGroup object cannot be nil")
|
||||
}
|
||||
|
||||
if apps == nil {
|
||||
l.Error(nil, "applications slice cannot be nil")
|
||||
return fmt.Errorf("applications slice cannot be nil")
|
||||
}
|
||||
|
||||
if len(apps) != len(g.Spec.Applications) {
|
||||
l.Error(nil, "application len mismatch")
|
||||
return fmt.Errorf("len application objects [%v] do not match len entries applicationgroup.spec.applications %v", len(apps), len(g.Spec.Applications))
|
||||
}
|
||||
|
||||
a.initWorkflowObject()
|
||||
|
||||
// Set name and namespace based on the input application group
|
||||
a.wf.Name = g.Name
|
||||
a.wf.Namespace = ns
|
||||
|
||||
sort.SliceStable(apps[:], func(i, j int) bool { //nolint:gocritic
|
||||
return apps[i].Name < apps[j].Name
|
||||
})
|
||||
|
||||
sort.SliceStable(g.Spec.Applications[:], func(i, j int) bool { //nolint:gocritic
|
||||
return g.Spec.Applications[i].Name < g.Spec.Applications[j].Name
|
||||
})
|
||||
|
||||
err := a.generateWorkflow(g, apps)
|
||||
if err != nil {
|
||||
l.Error(err, "failed to generate workflow")
|
||||
return fmt.Errorf("failed to generate argo workflow : %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *argo) Submit(ctx context.Context, l logr.Logger, g *v1alpha1.ApplicationGroup) error {
|
||||
if a.wf == nil {
|
||||
l.Error(nil, "workflow object cannot be nil")
|
||||
return fmt.Errorf("workflow object cannot be nil")
|
||||
}
|
||||
|
||||
if g == nil {
|
||||
l.Error(nil, "applicationGroup object cannot be nil")
|
||||
return fmt.Errorf("applicationGroup object cannot be nil")
|
||||
}
|
||||
|
||||
obj := &v1alpha12.Workflow{
|
||||
ObjectMeta: v1.ObjectMeta{Labels: make(map[string]string)},
|
||||
}
|
||||
|
||||
obj.Labels[ownershipLabel] = g.Name
|
||||
obj.Labels[heritageLabel] = project
|
||||
|
||||
err := a.cli.Get(ctx, types.NamespacedName{Namespace: a.wf.Namespace, Name: a.wf.Name}, obj)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) && !errors.IsAlreadyExists(err) {
|
||||
// Add OwnershipReference
|
||||
err = controllerutil.SetControllerReference(g, a.wf, a.scheme)
|
||||
if err != nil {
|
||||
l.Error(err, "unable to set ApplicationGroup as owner of Argo Workflow object")
|
||||
return fmt.Errorf("unable to set ApplicationGroup as owner of Argo Workflow: %w", err)
|
||||
}
|
||||
|
||||
// If the argo Workflow object is NotFound and not AlreadyExists on the cluster
|
||||
// create a new object and submit it to the cluster
|
||||
err = a.cli.Create(ctx, a.wf)
|
||||
if err != nil {
|
||||
l.Error(err, "failed to CREATE argo workflow object")
|
||||
return fmt.Errorf("failed to CREATE argo workflow object : %w", err)
|
||||
}
|
||||
} else {
|
||||
l.Error(err, "failed to GET workflow object with an unrecoverable error")
|
||||
return fmt.Errorf("failed to GET workflow object with an unrecoverable error : %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *argo) generateWorkflow(g *v1alpha1.ApplicationGroup, apps []*v1alpha1.Application) error {
|
||||
// Generate the Entrypoint template and Application Group DAG
|
||||
err := a.generateAppGroupTpls(g, apps)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate Application Group DAG : %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *argo) generateAppGroupTpls(g *v1alpha1.ApplicationGroup, apps []*v1alpha1.Application) error {
|
||||
if a.wf == nil {
|
||||
return fmt.Errorf("workflow cannot be nil")
|
||||
}
|
||||
|
||||
if g == nil {
|
||||
return fmt.Errorf("applicationGroup cannot be nil")
|
||||
}
|
||||
|
||||
entry := v1alpha12.Template{
|
||||
Name: entrypointTplName,
|
||||
DAG: &v1alpha12.DAGTemplate{
|
||||
Tasks: make([]v1alpha12.DAGTask, len(apps)),
|
||||
// TBD (nitishm): Do we need to failfast?
|
||||
// FailFast: true
|
||||
},
|
||||
}
|
||||
|
||||
adt, err := generateAppDAGTemplates(apps, a.stagingRepoURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate application DAG templates : %w", err)
|
||||
}
|
||||
a.updateWorkflowTemplates(adt...)
|
||||
|
||||
err = updateAppGroupDAG(g, &entry, adt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate Application Group DAG : %w", err)
|
||||
}
|
||||
a.updateWorkflowTemplates(entry)
|
||||
|
||||
// TODO: Add the executor template
|
||||
// This should eventually be configurable
|
||||
a.updateWorkflowTemplates(defaultExecutor())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateAppGroupDAG(g *v1alpha1.ApplicationGroup, entry *v1alpha12.Template, tpls []v1alpha12.Template) error {
|
||||
if entry == nil {
|
||||
return fmt.Errorf("entry template cannot be nil")
|
||||
}
|
||||
|
||||
entry.DAG = &v1alpha12.DAGTemplate{
|
||||
Tasks: make([]v1alpha12.DAGTask, len(tpls), len(tpls)),
|
||||
}
|
||||
|
||||
for i, tpl := range tpls {
|
||||
entry.DAG.Tasks[i] = v1alpha12.DAGTask{
|
||||
Name: tpl.Name,
|
||||
Template: tpl.Name,
|
||||
Dependencies: g.Spec.Applications[i].Dependencies,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateAppDAGTemplates(apps []*v1alpha1.Application, repo string) ([]v1alpha12.Template, error) {
|
||||
ts := make([]v1alpha12.Template, 0)
|
||||
|
||||
for _, app := range apps {
|
||||
// XXX (nitishm) : Workaround for https://github.com/kubernetes/kubernetes/issues/98683
|
||||
vString := app.Spec.Overlays
|
||||
if vString != "" {
|
||||
appHV := helmopv1.HelmValues{}
|
||||
err := json.Unmarshal([]byte(vString), &appHV)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal overlay values into HelmValues object")
|
||||
}
|
||||
app.Spec.Values = appHV
|
||||
}
|
||||
// END
|
||||
|
||||
var hasSubcharts bool
|
||||
|
||||
// Create Subchart DAG only when the application chart has dependencies
|
||||
if len(app.Spec.Subcharts) > 0 {
|
||||
hasSubcharts = true
|
||||
t := v1alpha12.Template{
|
||||
Name: app.Name,
|
||||
}
|
||||
|
||||
t.DAG = &v1alpha12.DAGTemplate{}
|
||||
tasks, err := generateSubchartAndAppDAGTasks(app, repo, app.Spec.HelmReleaseSpec.TargetNamespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate Application Template DAG tasks : %w", err)
|
||||
}
|
||||
|
||||
t.DAG.Tasks = tasks
|
||||
|
||||
ts = append(ts, t)
|
||||
}
|
||||
|
||||
if !hasSubcharts {
|
||||
hr := helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: app.Name,
|
||||
// FIXME (nitishm) : Deploying HelmRelease to specific namespace requires special serviceAccount, ClusterRole and ClusterRoleBinding
|
||||
// Namespace: app.Spec.TargetNamespace,
|
||||
},
|
||||
Spec: app.DeepCopy().Spec.HelmReleaseSpec,
|
||||
}
|
||||
|
||||
if app.Status.Application.Staged {
|
||||
hr.Spec.RepoURL = repo
|
||||
}
|
||||
|
||||
tApp := v1alpha12.Template{
|
||||
Name: app.Name,
|
||||
DAG: &v1alpha12.DAGTemplate{
|
||||
Tasks: []v1alpha12.DAGTask{
|
||||
{
|
||||
Name: app.Name,
|
||||
Template: helmReleaseExecutor,
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(hr)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ts = append(ts, tApp)
|
||||
}
|
||||
}
|
||||
|
||||
return ts, nil
|
||||
}
|
||||
|
||||
func generateSubchartAndAppDAGTasks(app *v1alpha1.Application, repo, targetNS string) ([]v1alpha12.DAGTask, error) {
|
||||
if repo == "" {
|
||||
return nil, fmt.Errorf("repo arg must be a valid non-empty string")
|
||||
}
|
||||
|
||||
// XXX (nitishm) Should this be set to nil if no subcharts are found??
|
||||
tasks := make([]v1alpha12.DAGTask, 0, len(app.Spec.Subcharts)+1)
|
||||
|
||||
for _, sc := range app.Spec.Subcharts {
|
||||
s, ok := app.Status.Subcharts[sc.Name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to find subchart info in applications status field")
|
||||
}
|
||||
|
||||
hr := generateSubchartHelmRelease(app.Spec.HelmReleaseSpec, sc.Name, s.Version, repo, targetNS)
|
||||
task := v1alpha12.DAGTask{
|
||||
Name: sc.Name,
|
||||
Template: helmReleaseExecutor,
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(hr)),
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: sc.Dependencies,
|
||||
}
|
||||
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
|
||||
hr := helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: app.Name,
|
||||
// FIXME (nitishm) : Deploying HelmRelease to specific namespace requires special serviceAccount, ClusterRole and ClusterRoleBinding
|
||||
// Namespace: app.Spec.HelmReleaseSpec.TargetNamespace,
|
||||
},
|
||||
Spec: app.DeepCopy().Spec.HelmReleaseSpec,
|
||||
}
|
||||
|
||||
// staging repo instead of the primary repo
|
||||
hr.Spec.RepoURL = repo
|
||||
|
||||
task := v1alpha12.DAGTask{
|
||||
Name: app.Name,
|
||||
Template: helmReleaseExecutor,
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(hr)),
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: func() (out []string) {
|
||||
for _, t := range tasks {
|
||||
out = append(out, t.Name)
|
||||
}
|
||||
return out
|
||||
}(),
|
||||
}
|
||||
|
||||
tasks = append(tasks, task)
|
||||
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
func (a *argo) updateWorkflowTemplates(tpls ...v1alpha12.Template) {
|
||||
a.wf.Spec.Templates = append(a.wf.Spec.Templates, tpls...)
|
||||
}
|
||||
|
||||
func defaultExecutor() v1alpha12.Template {
|
||||
return v1alpha12.Template{
|
||||
Name: helmReleaseExecutor,
|
||||
// FIXME (nitishm) : Hack
|
||||
// Replace with the actual service account in use
|
||||
ServiceAccountName: "orkestra",
|
||||
Inputs: v1alpha12.Inputs{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: "helmrelease",
|
||||
},
|
||||
},
|
||||
},
|
||||
Outputs: v1alpha12.Outputs{},
|
||||
Resource: &v1alpha12.ResourceTemplate{
|
||||
SetOwnerReference: true,
|
||||
Action: "create",
|
||||
Manifest: "{{inputs.parameters.helmrelease}}",
|
||||
SuccessCondition: "status.phase == Succeeded",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func strToStrPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func hrToYAML(hr helmopv1.HelmRelease) string {
|
||||
b, err := yaml.Marshal(hr)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func generateSubchartHelmRelease(a helmopv1.HelmReleaseSpec, sc, version, repo, targetNS string) helmopv1.HelmRelease {
|
||||
hr := helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: sc,
|
||||
// FIXME (nitishm) : Deploying HelmRelease to specific namespace requires special serviceAccount, ClusterRole and ClusterRoleBinding
|
||||
// Namespace: targetNS,
|
||||
},
|
||||
Spec: helmopv1.HelmReleaseSpec{
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{},
|
||||
},
|
||||
},
|
||||
// FIXME (nitishm) : Deploying HelmRelease to specific namespace requires special serviceAccount, ClusterRole and ClusterRoleBinding
|
||||
// TargetNamespace: targetNS,
|
||||
}
|
||||
|
||||
hr.Spec.ChartSource.RepoChartSource = a.DeepCopy().RepoChartSource
|
||||
hr.Spec.ChartSource.RepoChartSource.Name = sc
|
||||
hr.Spec.ChartSource.RepoChartSource.RepoURL = repo
|
||||
hr.Spec.ChartSource.RepoChartSource.Version = version
|
||||
hr.Spec.Values = subchartValues(sc, a.Values)
|
||||
|
||||
return hr
|
||||
}
|
||||
|
||||
func subchartValues(sc string, av helmopv1.HelmValues) helmopv1.HelmValues {
|
||||
v := helmopv1.HelmValues{
|
||||
Data: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
if scVals, ok := av.Data[sc]; ok {
|
||||
if vv, ok := scVals.(map[string]interface{}); ok {
|
||||
for k, val := range vv {
|
||||
v.Data[k] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if gVals, ok := av.Data[valuesKeyGlobal]; ok {
|
||||
if vv, ok := gVals.(map[string]interface{}); ok {
|
||||
v.Data[valuesKeyGlobal] = vv
|
||||
}
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
|
@ -0,0 +1,931 @@
|
|||
package workflow
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/Orkestra/api/v1alpha1"
|
||||
v1alpha12 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
|
||||
helmopv1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func helmValues(v string) map[string]interface{} {
|
||||
out := make(map[string]interface{})
|
||||
err := json.Unmarshal([]byte(v), &out)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func Test_subchartValues(t *testing.T) {
|
||||
type args struct {
|
||||
sc string
|
||||
av helmopv1.HelmValues
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want helmopv1.HelmValues
|
||||
}{
|
||||
{
|
||||
name: "withGlobalSuchart",
|
||||
args: args{
|
||||
sc: "subchart",
|
||||
av: helmopv1.HelmValues{
|
||||
Data: helmValues(`{"global": {"keyG": "valueG"},"subchart": {"keySC": "valueSC"}}`),
|
||||
},
|
||||
},
|
||||
want: helmopv1.HelmValues{
|
||||
Data: helmValues(`{"global": {"keyG": "valueG"},"keySC": "valueSC"}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "withOnlyGlobal",
|
||||
args: args{
|
||||
sc: "subchart",
|
||||
av: helmopv1.HelmValues{
|
||||
Data: helmValues(`{"global": {"keyG": "valueG"}}`),
|
||||
},
|
||||
},
|
||||
want: helmopv1.HelmValues{
|
||||
Data: helmValues(`{"global": {"keyG": "valueG"}}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "withOnlySubchart",
|
||||
args: args{
|
||||
sc: "subchart",
|
||||
av: helmopv1.HelmValues{
|
||||
Data: helmValues(`{"subchart": {"keySC": "valueSC"}}`),
|
||||
},
|
||||
},
|
||||
want: helmopv1.HelmValues{
|
||||
Data: helmValues(`{"keySC": "valueSC"}`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "withNone",
|
||||
args: args{
|
||||
sc: "subchart",
|
||||
av: helmopv1.HelmValues{
|
||||
Data: make(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
want: helmopv1.HelmValues{
|
||||
Data: make(map[string]interface{}),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := subchartValues(tt.args.sc, tt.args.av); !cmp.Equal(got, tt.want) {
|
||||
t.Errorf("subchartValues() = %v", cmp.Diff(got, tt.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_generateSubchartAndAppDAGTasks(t *testing.T) {
|
||||
type args struct {
|
||||
app *v1alpha1.Application
|
||||
targetNS string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []v1alpha12.DAGTask
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "sequential",
|
||||
args: args{
|
||||
app: &v1alpha1.Application{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "application",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Subcharts: []v1alpha1.DAG{
|
||||
{
|
||||
Name: "subchart-3",
|
||||
Dependencies: []string{"subchart-2"},
|
||||
},
|
||||
{
|
||||
Name: "subchart-2",
|
||||
Dependencies: []string{"subchart-1"},
|
||||
},
|
||||
{
|
||||
Name: "subchart-1",
|
||||
Dependencies: nil,
|
||||
},
|
||||
},
|
||||
HelmReleaseSpec: helmopv1.HelmReleaseSpec{
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
"subchart-1": map[string]interface{}{
|
||||
"subchart-1-key": "subchart-1-value",
|
||||
},
|
||||
"subchart-2": map[string]interface{}{
|
||||
"subchart-2-key": "subchart-2-value",
|
||||
},
|
||||
"subchart-3": map[string]interface{}{
|
||||
"subchart-3-key": "subchart-3-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://stagingrepo",
|
||||
Name: "appchart",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationStatus{
|
||||
Subcharts: map[string]v1alpha1.ChartStatus{
|
||||
"subchart-1": {
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"subchart-2": {
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"subchart-3": {
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []v1alpha12.DAGTask{
|
||||
{
|
||||
Name: "subchart-3",
|
||||
Template: "helmrelease-executor",
|
||||
Dependencies: []string{"subchart-2"},
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "subchart-3",
|
||||
},
|
||||
Spec: helmopv1.HelmReleaseSpec{
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://stagingrepo",
|
||||
Name: "subchart-3",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
"subchart-3-key": "subchart-3-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "subchart-2",
|
||||
Template: "helmrelease-executor",
|
||||
Dependencies: []string{"subchart-1"},
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "subchart-2",
|
||||
},
|
||||
Spec: helmopv1.HelmReleaseSpec{
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://stagingrepo",
|
||||
Name: "subchart-2",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
"subchart-2-key": "subchart-2-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "subchart-1",
|
||||
Template: "helmrelease-executor",
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "subchart-1",
|
||||
},
|
||||
Spec: helmopv1.HelmReleaseSpec{
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://stagingrepo",
|
||||
Name: "subchart-1",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
"subchart-1-key": "subchart-1-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "application",
|
||||
Template: "helmrelease-executor",
|
||||
Dependencies: []string{"subchart-3", "subchart-2", "subchart-1"},
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "application",
|
||||
},
|
||||
Spec: helmopv1.HelmReleaseSpec{
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://stagingrepo",
|
||||
Name: "appchart",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
"subchart-1": map[string]interface{}{
|
||||
"subchart-1-key": "subchart-1-value",
|
||||
},
|
||||
"subchart-2": map[string]interface{}{
|
||||
"subchart-2-key": "subchart-2-value",
|
||||
},
|
||||
"subchart-3": map[string]interface{}{
|
||||
"subchart-3-key": "subchart-3-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "parallel",
|
||||
args: args{
|
||||
app: &v1alpha1.Application{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "application",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Subcharts: []v1alpha1.DAG{
|
||||
{
|
||||
Name: "subchart-3",
|
||||
Dependencies: []string{"subchart-2", "subchart-1"},
|
||||
},
|
||||
{
|
||||
Name: "subchart-2",
|
||||
Dependencies: nil,
|
||||
},
|
||||
{
|
||||
Name: "subchart-1",
|
||||
Dependencies: nil,
|
||||
},
|
||||
},
|
||||
HelmReleaseSpec: helmopv1.HelmReleaseSpec{
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
"subchart-1": map[string]interface{}{
|
||||
"subchart-1-key": "subchart-1-value",
|
||||
},
|
||||
"subchart-2": map[string]interface{}{
|
||||
"subchart-2-key": "subchart-2-value",
|
||||
},
|
||||
"subchart-3": map[string]interface{}{
|
||||
"subchart-3-key": "subchart-3-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://stagingrepo",
|
||||
Name: "appchart",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationStatus{
|
||||
Subcharts: map[string]v1alpha1.ChartStatus{
|
||||
"subchart-1": {
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"subchart-2": {
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"subchart-3": {
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []v1alpha12.DAGTask{
|
||||
{
|
||||
Name: "subchart-3",
|
||||
Template: "helmrelease-executor",
|
||||
Dependencies: []string{"subchart-2", "subchart-1"},
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "subchart-3",
|
||||
},
|
||||
Spec: helmopv1.HelmReleaseSpec{
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://stagingrepo",
|
||||
Name: "subchart-3",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
"subchart-3-key": "subchart-3-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "subchart-2",
|
||||
Template: "helmrelease-executor",
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "subchart-2",
|
||||
},
|
||||
Spec: helmopv1.HelmReleaseSpec{
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://stagingrepo",
|
||||
Name: "subchart-2",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
"subchart-2-key": "subchart-2-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "subchart-1",
|
||||
Template: "helmrelease-executor",
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "subchart-1",
|
||||
},
|
||||
Spec: helmopv1.HelmReleaseSpec{
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://stagingrepo",
|
||||
Name: "subchart-1",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
"subchart-1-key": "subchart-1-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "application",
|
||||
Template: "helmrelease-executor",
|
||||
Dependencies: []string{"subchart-3", "subchart-2", "subchart-1"},
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "application",
|
||||
},
|
||||
Spec: helmopv1.HelmReleaseSpec{
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://stagingrepo",
|
||||
Name: "appchart",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
"subchart-1": map[string]interface{}{
|
||||
"subchart-1-key": "subchart-1-value",
|
||||
},
|
||||
"subchart-2": map[string]interface{}{
|
||||
"subchart-2-key": "subchart-2-value",
|
||||
},
|
||||
"subchart-3": map[string]interface{}{
|
||||
"subchart-3-key": "subchart-3-value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := generateSubchartAndAppDAGTasks(tt.args.app, "http://stagingrepo", tt.args.targetNS)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("generateSubchartDAGTasks() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !cmp.Equal(got, tt.want) {
|
||||
t.Errorf("generateSubchartDAGTasks() = %v", cmp.Diff(got, tt.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_generateAppDAGTemplates(t *testing.T) {
|
||||
type args struct {
|
||||
apps []*v1alpha1.Application
|
||||
repo string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []v1alpha12.Template
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "singleApplicationWithSubchartDAG",
|
||||
args: args{
|
||||
apps: []*v1alpha1.Application{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "application",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
HelmReleaseSpec: helmopv1.HelmReleaseSpec{
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
},
|
||||
},
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://primaryrepo",
|
||||
Name: "appchart",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
Subcharts: []v1alpha1.DAG{
|
||||
{
|
||||
Name: "subchart-3",
|
||||
Dependencies: []string{"subchart-2", "subchart-1"},
|
||||
},
|
||||
{
|
||||
Name: "subchart-2",
|
||||
Dependencies: nil,
|
||||
},
|
||||
{
|
||||
Name: "subchart-1",
|
||||
Dependencies: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationStatus{
|
||||
Subcharts: map[string]v1alpha1.ChartStatus{
|
||||
"subchart-1": {
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"subchart-2": {
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"subchart-3": {
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
repo: "http://stagingrepo",
|
||||
},
|
||||
want: []v1alpha12.Template{
|
||||
{
|
||||
Name: "application",
|
||||
DAG: &v1alpha12.DAGTemplate{
|
||||
Tasks: []v1alpha12.DAGTask{
|
||||
{
|
||||
Name: "subchart-3",
|
||||
Dependencies: []string{"subchart-2", "subchart-1"},
|
||||
Template: "helmrelease-executor",
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "subchart-3",
|
||||
},
|
||||
Spec: helmopv1.HelmReleaseSpec{
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://stagingrepo",
|
||||
Name: "subchart-3",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "subchart-2",
|
||||
Template: "helmrelease-executor",
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "subchart-2",
|
||||
},
|
||||
Spec: helmopv1.HelmReleaseSpec{
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://stagingrepo",
|
||||
Name: "subchart-2",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "subchart-1",
|
||||
Template: "helmrelease-executor",
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "subchart-1",
|
||||
},
|
||||
Spec: helmopv1.HelmReleaseSpec{
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://stagingrepo",
|
||||
Name: "subchart-1",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "application",
|
||||
Template: "helmrelease-executor",
|
||||
Dependencies: []string{"subchart-3", "subchart-2", "subchart-1"},
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "application",
|
||||
},
|
||||
Spec: helmopv1.HelmReleaseSpec{
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://stagingrepo",
|
||||
Name: "appchart",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "singleApplicationWithNoSubchartDAG",
|
||||
args: args{
|
||||
apps: []*v1alpha1.Application{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "application",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
HelmReleaseSpec: helmopv1.HelmReleaseSpec{
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
},
|
||||
},
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://primaryrepo",
|
||||
Name: "appchart",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationStatus{
|
||||
Subcharts: map[string]v1alpha1.ChartStatus{
|
||||
"subchart-1": {
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"subchart-2": {
|
||||
Version: "1.0.0",
|
||||
},
|
||||
"subchart-3": {
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
repo: "http://stagingrepo",
|
||||
},
|
||||
want: []v1alpha12.Template{
|
||||
{
|
||||
Name: "application",
|
||||
DAG: &v1alpha12.DAGTemplate{
|
||||
Tasks: []v1alpha12.DAGTask{
|
||||
{
|
||||
Name: "application",
|
||||
Template: "helmrelease-executor",
|
||||
Arguments: v1alpha12.Arguments{
|
||||
Parameters: []v1alpha12.Parameter{
|
||||
{
|
||||
Name: helmReleaseArg,
|
||||
Value: strToStrPtr(hrToYAML(helmopv1.HelmRelease{
|
||||
TypeMeta: v1.TypeMeta{
|
||||
Kind: "HelmRelease",
|
||||
APIVersion: "helm.fluxcd.io/v1",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "application",
|
||||
},
|
||||
Spec: helmopv1.HelmReleaseSpec{
|
||||
Values: helmopv1.HelmValues{
|
||||
Data: map[string]interface{}{
|
||||
"global": map[string]interface{}{
|
||||
"keyG": "valueG",
|
||||
},
|
||||
},
|
||||
},
|
||||
ChartSource: helmopv1.ChartSource{
|
||||
RepoChartSource: &helmopv1.RepoChartSource{
|
||||
RepoURL: "http://primaryrepo",
|
||||
Name: "appchart",
|
||||
Version: "1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := generateAppDAGTemplates(tt.args.apps, tt.args.repo)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("generateAppDAGTemplates() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !cmp.Equal(got, tt.want) {
|
||||
t.Errorf("generateAppDAGTemplates() = %v", cmp.Diff(got, tt.want))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_argo_generateAppGroupTpls(t *testing.T) {
|
||||
type fields struct {
|
||||
scheme *runtime.Scheme
|
||||
cli client.Client
|
||||
wf *v1alpha12.Workflow
|
||||
stagingRepoURL string
|
||||
}
|
||||
type args struct {
|
||||
g *v1alpha1.ApplicationGroup
|
||||
apps []*v1alpha1.Application
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &argo{
|
||||
scheme: tt.fields.scheme,
|
||||
cli: tt.fields.cli,
|
||||
wf: tt.fields.wf,
|
||||
stagingRepoURL: tt.fields.stagingRepoURL,
|
||||
}
|
||||
if err := a.generateAppGroupTpls(tt.args.g, tt.args.apps); (err != nil) != tt.wantErr {
|
||||
t.Errorf("argo.generateAppGroupTpls() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package workflow
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Azure/Orkestra/api/v1alpha1"
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
type Engine interface {
|
||||
// Generate the object required by the workflow engine
|
||||
Generate(ctx context.Context, l logr.Logger, ns string, g *v1alpha1.ApplicationGroup, apps []*v1alpha1.Application) error
|
||||
// Submit the object required by the workflow engine generated by the Generate method
|
||||
Submit(ctx context.Context, l logr.Logger, g *v1alpha1.ApplicationGroup) error
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
registries:
|
||||
bitnami:
|
||||
url: "https://charts.bitnami.com/bitnami"
|
||||
insecureSkipVerify: true
|
||||
|
||||
staging:
|
||||
url: "http://localhost:8080"
|
Загрузка…
Ссылка в новой задаче