* 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:
Nitish Malhotra 2021-02-02 18:49:42 -08:00 коммит произвёл GitHub
Родитель ac115b6f54
Коммит f1c574a36b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
48 изменённых файлов: 4469 добавлений и 502 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -31,6 +31,7 @@ debug
# IntelliJ / Goland
.idea/
.vscode/
# Emacs
*~

Просмотреть файл

@ -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"]

Просмотреть файл

@ -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/

12
chart/orkestra/Chart.lock Normal file
Просмотреть файл

@ -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"

37
chart/orkestra/Chart.yaml Normal file
Просмотреть файл

@ -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

Двоичные данные
chart/orkestra/charts/argo-0.15.3.tgz Normal file

Двоичный файл не отображается.

Двоичные данные
chart/orkestra/charts/chartmuseum-2.15.0.tgz Normal file

Двоичный файл не отображается.

Двоичные данные
chart/orkestra/charts/helm-operator-1.2.0.tgz Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -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

7
config.yaml Normal file
Просмотреть файл

@ -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
Просмотреть файл

@ -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

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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"

22
pkg/registry/config.go Normal file
Просмотреть файл

@ -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"`
}

17
pkg/registry/options.go Normal file
Просмотреть файл

@ -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
}
}

63
pkg/registry/pull.go Normal file
Просмотреть файл

@ -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
}

79
pkg/registry/push.go Normal file
Просмотреть файл

@ -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)
}

134
pkg/registry/registry.go Normal file
Просмотреть файл

@ -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)
}

458
pkg/workflow/argo.go Normal file
Просмотреть файл

@ -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
}

931
pkg/workflow/argo_test.go Normal file
Просмотреть файл

@ -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)
}
})
}
}

15
pkg/workflow/workflow.go Normal file
Просмотреть файл

@ -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
}

7
testwith/config.yaml Normal file
Просмотреть файл

@ -0,0 +1,7 @@
registries:
bitnami:
url: "https://charts.bitnami.com/bitnami"
insecureSkipVerify: true
staging:
url: "http://localhost:8080"