Nitishm/rework/combine app into appgrp (#68)

* Application type and app reconciler deprecated

BREAKING CHANGES:
- No longer supporting Application type and application reconciler
- Application spec has been merged into ApplicationGroup API

- No changes to behavior other than revised spec and sample YAMLs

Signed-off-by: nitishm <nitishm@microsoft.com>

* Update Tiltfile to use appgroup only

Signed-off-by: nitishm <nitishm@microsoft.com>

* Rename DAG to Application

Renamed DAG struct to Application
Renamed DAGData struct to DAG

Signed-off-by: nitishm <nitishm@microsoft.com>

* Updated docs

Signed-off-by: nitishm <nitishm@microsoft.com>

* Fix lint error

Signed-off-by: nitishm <nitishm@microsoft.com>

Co-authored-by: nitishm <nitishm@microsoft.com>
This commit is contained in:
Nitish Malhotra 2021-02-22 11:12:58 -08:00 коммит произвёл GitHub
Родитель 2cbe7fcda9
Коммит 3d5603dddb
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
31 изменённых файлов: 719 добавлений и 1190 удалений

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

@ -1,9 +1,6 @@
domain: azure.microsoft.com
repo: github.com/Azure/Orkestra
resources:
- group: orkestra
kind: Application
version: v1alpha1
- group: orkestra
kind: ApplicationGroup
version: v1alpha1

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

@ -27,7 +27,7 @@ For getting started you will need,
- `helm` - Helm client
- (_optional_) `argo` - Argo workflow client (follow the instructions to install the binary at https://github.com/argoproj/argo/releases)
Install the `ApplicationGroup` and `Application` custom resource definitions (CRDs) using `make install`
Install the `ApplicationGroup` and custom resource definitions (CRDs) using `make install`
```console
/home/nitishm/go/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
@ -112,9 +112,9 @@ To solve the complex application orchestration problem Orkestra builds a [Direct
<p align="center"><img src="./assets/orkestra-core.png" width="750x" /></p>
1. Submit `Application` and `ApplicationGroup` CRs
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).
1. Submit `ApplicationGroup` CRs
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.
6. Fetch and deploy Helm charts referred to by each `HelmRelease` CR to the Kubernetes cluster.
@ -140,7 +140,7 @@ Try out the examples in [examples](./examples)
### Functional
- [ ] Handling of `Application`, `ApplicationGroup` UPDATE & DELETE reconcilation events : [#64](https://github.com/Azure/Orkestra/issues/64), [#59](https://github.com/Azure/Orkestra/issues/59)
- [ ] Handling of `ApplicationGroup` UPDATE & DELETE reconcilation events : [#64](https://github.com/Azure/Orkestra/issues/64), [#59](https://github.com/Azure/Orkestra/issues/59)
### Features

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

@ -25,4 +25,4 @@ yaml = helm(
k8s_yaml(yaml,allow_duplicates=True)
k8s_yaml(['./config/samples/dev-applicationgroup.yaml', './config/samples/kafka-dev-application.yaml', './config/samples/redis-dev-application.yaml'],allow_duplicates=True)
k8s_yaml(['./config/samples/dev-applicationgroup.yaml'],allow_duplicates=True)

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

@ -4,26 +4,72 @@
package v1alpha1
import (
helmopv1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// ApplicationSpec defines the desired state of Application
type ApplicationSpec struct {
// Namespace to which the HelmRelease object will be deployed
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) **IMPORTANT**: DO NOT USE HelmReleaseSpec.Values!!!
// ApplicationSpec.Overlays field replaces HelmReleaseSpec.Values field.
// Setting the HelmReleaseSpec.Values field will not reflect in the deployed Application object
//
// Explanation
// ===========
// HelmValues uses a map[string]interface{} structure for holding helm values Data.
// kubebuilder prunes the field value when deploying the Application resource as it considers the field to be an
// Unknown field. HelmOperator v1 being in maintenance mode, we do not expect them to merge PRs
// to add the +kubebuilder:pruning:PreserveUnknownFields
// https://github.com/fluxcd/helm-operator/issues/585
// +kubebuilder:pruning:PreserveUnknownFields
// +kubebuilder:validation:XPreserveUnknownFields
Overlays helmopv1.HelmValues `json:"overlays,omitempty"`
// RepoPath provides the subdir path to the actual chart artifact within a Helm Registry
// Artifactory for instance utilizes folders to store charts
RepoPath string `json:"repoPath,omitempty"`
helmopv1.HelmReleaseSpec `json:",inline"`
}
// ChartStatus denotes the current status of the Application Reconciliation
type ChartStatus struct {
Error string `json:"error,omitempty"`
Version string `json:"version,omitempty"`
Staged bool `json:"staged,omitempty"`
}
// ApplicationGroupSpec defines the desired state of ApplicationGroup
type ApplicationGroupSpec struct {
Applications []DAG `json:"applications,omitempty"`
Applications []Application `json:"applications,omitempty"`
}
type Application struct {
DAG `json:",inline"`
Spec ApplicationSpec `json:"spec,omitempty"`
}
type DAG struct {
Name string `json:"name,omitempty"`
Dependencies []string `json:"dependencies,omitempty"`
}
type ApplicationStatus struct {
Name string `json:"name"`
ChartStatus `json:",inline"`
Subcharts map[string]ChartStatus `json:"subcharts,omitempty"`
}
// ApplicationGroupStatus defines the observed state of ApplicationGroup
type ApplicationGroupStatus struct {
Applications []ApplicationStatus `json:"status,omitempty"`
Ready bool `json:"ready"`
Ready bool `json:"ready,omitempty"`
Error string `json:"error,omitempty"`
}

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

@ -1,84 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package v1alpha1
import (
helmopv1 "github.com/fluxcd/helm-operator/pkg/apis/helm.fluxcd.io/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// ApplicationSpec defines the desired state of Application
type ApplicationSpec struct {
// Namespace to which the HelmRelease object will be deployed
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) **IMPORTANT**: DO NOT USE HelmReleaseSpec.Values!!!
// ApplicationSpec.Overlays field replaces HelmReleaseSpec.Values field.
// Setting the HelmReleaseSpec.Values field will not reflect in the deployed Application object
//
// Explanation
// ===========
// HelmValues uses a map[string]interface{} structure for holding helm values Data.
// kubebuilder prunes the field value when deploying the Application resource as it considers the field to be an
// Unknown field. HelmOperator v1 being in maintenance mode, we do not expect them to merge PRs
// to add the +kubebuilder:pruning:PreserveUnknownFields
// https://github.com/fluxcd/helm-operator/issues/585
// +kubebuilder:pruning:PreserveUnknownFields
// +kubebuilder:validation:XPreserveUnknownFields
Overlays helmopv1.HelmValues `json:"overlays,omitempty"`
// RepoPath provides the subdir path to the actual chart artifact within a Helm Registry
// Artifactory for instance utilizes folders to store charts
RepoPath string `json:"repoPath,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 {
Name string `json:"name"`
Application ChartStatus `json:"application"`
Subcharts map[string]ChartStatus `json:"subcharts"`
}
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=applications,scope=Cluster
// +kubebuilder:subresource:status
// Application is the Schema for the applications API
type Application struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ApplicationSpec `json:"spec,omitempty"`
Status ApplicationStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// ApplicationList contains a list of Application
type ApplicationList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Application `json:"items"`
}
func init() {
SchemeBuilder.Register(&Application{}, &ApplicationList{})
}

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

@ -14,10 +14,8 @@ import (
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Application) DeepCopyInto(out *Application) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.DAG.DeepCopyInto(&out.DAG)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Application.
@ -30,14 +28,6 @@ func (in *Application) DeepCopy() *Application {
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Application) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationGroup) DeepCopyInto(out *ApplicationGroup) {
*out = *in
@ -102,7 +92,7 @@ func (in *ApplicationGroupSpec) DeepCopyInto(out *ApplicationGroupSpec) {
*out = *in
if in.Applications != nil {
in, out := &in.Applications, &out.Applications
*out = make([]DAG, len(*in))
*out = make([]Application, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@ -141,38 +131,6 @@ func (in *ApplicationGroupStatus) DeepCopy() *ApplicationGroupStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationList) DeepCopyInto(out *ApplicationList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Application, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationList.
func (in *ApplicationList) DeepCopy() *ApplicationList {
if in == nil {
return nil
}
out := new(ApplicationList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ApplicationList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationSpec) DeepCopyInto(out *ApplicationSpec) {
*out = *in
@ -200,7 +158,7 @@ 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
out.ChartStatus = in.ChartStatus
if in.Subcharts != nil {
in, out := &in.Subcharts, &out.Subcharts
*out = make(map[string]ChartStatus, len(*in))

Двоичные данные
assets/orkestra-core.png

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

До

Ширина:  |  Высота:  |  Размер: 282 KiB

После

Ширина:  |  Высота:  |  Размер: 276 KiB

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

@ -45,6 +45,305 @@ spec:
type: array
name:
type: string
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:
type: object
x-kubernetes-preserve-unknown-fields: true
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
repoPath:
description: RepoPath provides the subdir path to the actual
chart artifact within a Helm Registry Artifactory for
instance utilizes folders to store charts
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
type: object
type: array
type: object
@ -57,23 +356,13 @@ spec:
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
error:
type: string
name:
type: string
staged:
type: boolean
subcharts:
additionalProperties:
description: ChartStatus denotes the current status of the
@ -81,22 +370,18 @@ spec:
properties:
error:
type: string
ready:
type: boolean
staged:
type: boolean
version:
type: string
type: object
type: object
version:
type: string
required:
- application
- name
- subcharts
type: object
type: array
required:
- ready
type: object
type: object
served: true

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

@ -1,364 +0,0 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: (devel)
creationTimestamp: null
name: applications.orkestra.azure.microsoft.com
spec:
group: orkestra.azure.microsoft.com
names:
kind: Application
listKind: ApplicationList
plural: applications
singular: application
scope: Cluster
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:
type: object
x-kubernetes-preserve-unknown-fields: true
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
repoPath:
description: RepoPath provides the subdir path to the actual chart
artifact within a Helm Registry Artifactory for instance utilizes
folders to store charts
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: ""
plural: ""
conditions: []
storedVersions: []

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

@ -2,20 +2,17 @@
# since it depends on service name and namespace that are out of this kustomize package.
# It should be run by config/default
resources:
- bases/orkestra.azure.microsoft.com_applications.yaml
- bases/orkestra.azure.microsoft.com_applicationgroups.yaml
# +kubebuilder:scaffold:crdkustomizeresource
patchesStrategicMerge:
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
# patches here are for enabling the conversion webhook for each CRD
#- patches/webhook_in_applications.yaml
#- patches/webhook_in_applicationgroups.yaml
# +kubebuilder:scaffold:crdkustomizewebhookpatch
# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
# patches here are for enabling the CA injection for each CRD
#- patches/cainjection_in_applications.yaml
#- patches/cainjection_in_applicationgroups.yaml
# +kubebuilder:scaffold:crdkustomizecainjectionpatch

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

@ -1,8 +0,0 @@
# The following patch adds a directive for certmanager to inject CA into the CRD
# CRD conversion requires k8s 1.13 or later.
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
name: applications.orkestra.azure.microsoft.com

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

@ -1,17 +0,0 @@
# The following patch enables conversion webhook for CRD
# CRD conversion requires k8s 1.13 or later.
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: applications.orkestra.azure.microsoft.com
spec:
conversion:
strategy: Webhook
webhookClientConfig:
# this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
# but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
caBundle: Cg==
service:
namespace: system
name: webhook-service
path: /convert

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

@ -1,24 +0,0 @@
# permissions for end users to edit applications.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: application-editor-role
rules:
- apiGroups:
- orkestra.azure.microsoft.com
resources:
- applications
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- orkestra.azure.microsoft.com
resources:
- applications/status
verbs:
- get

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

@ -1,20 +0,0 @@
# permissions for end users to view applications.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: application-viewer-role
rules:
- apiGroups:
- orkestra.azure.microsoft.com
resources:
- applications
verbs:
- get
- list
- watch
- apiGroups:
- orkestra.azure.microsoft.com
resources:
- applications/status
verbs:
- get

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

@ -26,23 +26,3 @@ rules:
- get
- patch
- update
- apiGroups:
- orkestra.azure.microsoft.com
resources:
- applications
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- orkestra.azure.microsoft.com
resources:
- applications/status
verbs:
- get
- patch
- update

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

@ -7,7 +7,46 @@ spec:
- name: kafka-dev
dependencies:
- redis-dev
spec:
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: []
zookeerper:
enabled: true
targetNamespace: "kafka-dev-ns"
- 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:
imagePullSecrets: []
master:
persistence:
size: 4Gi
targetNamespace: "redis-dev-ns"
# TODO (nitishm) - define these fields using Argo Workflow spec
# strategy:
# rollout:

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

@ -1,29 +0,0 @@
# 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: []
zookeerper:
enabled: true
targetNamespace: "kafka-dev-ns"

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

@ -1,26 +0,0 @@
# 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:
imagePullSecrets: []
master:
persistence:
size: 4Gi
targetNamespace: "redis-dev-ns"

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

@ -6,10 +6,13 @@ package controllers
import (
"context"
"fmt"
"net/url"
"github.com/Azure/Orkestra/pkg/configurer"
"github.com/Azure/Orkestra/pkg/registry"
"github.com/Azure/Orkestra/pkg/workflow"
"github.com/go-logr/logr"
"helm.sh/helm/v3/pkg/chart"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/record"
@ -33,9 +36,18 @@ type ApplicationGroupReconciler struct {
Cfg *configurer.Controller
Engine workflow.Engine
// RegistryClient interacts with the helm registries to pull and push charts
RegistryClient *registry.Client
// WorkflowNS is the namespace to which (generated) Argo Workflow object is deployed
WorkflowNS string
// 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
}
@ -72,6 +84,19 @@ func (r *ApplicationGroupReconciler) Reconcile(req ctrl.Request) (ctrl.Result, e
logr.V(3).Info("reconciling AppGroup instance previously in error state")
}
// Initialize the Status fields if not already setup
if len(appGroup.Status.Applications) == 0 {
appGroup.Status.Applications = make([]orkestrav1alpha1.ApplicationStatus, 0, len(appGroup.Spec.Applications))
for _, app := range appGroup.Spec.Applications {
status := orkestrav1alpha1.ApplicationStatus{
Name: app.Name,
ChartStatus: orkestrav1alpha1.ChartStatus{Version: app.Spec.Version},
Subcharts: make(map[string]orkestrav1alpha1.ChartStatus),
}
appGroup.Status.Applications = append(appGroup.Status.Applications, status)
}
}
requeue, err = r.reconcile(ctx, logr, r.WorkflowNS, &appGroup)
defer r.updateStatusAndEvent(ctx, appGroup, requeue, err)
if err != nil {
@ -104,3 +129,19 @@ func (r *ApplicationGroupReconciler) updateStatusAndEvent(ctx context.Context, g
r.Recorder.Event(&grp, "Normal", "ReconcileSuccess", fmt.Sprintf("Successfully reconciled ApplicationGroup %s", grp.Name))
}
}
func isDependenciesEmbedded(ch *chart.Chart) bool {
isURI := false
for _, d := range ch.Metadata.Dependencies {
if _, err := url.ParseRequestURI(d.Repository); err == nil {
isURI = true
}
}
if !isURI {
if len(ch.Dependencies()) > 0 {
return true
}
}
return false
}

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

@ -6,11 +6,10 @@ import (
"fmt"
"k8s.io/apimachinery/pkg/api/errors"
"github.com/Azure/Orkestra/api/v1alpha1"
orkestrav1alpha1 "github.com/Azure/Orkestra/api/v1alpha1"
"github.com/Azure/Orkestra/pkg/registry"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/types"
)
var (
@ -25,92 +24,154 @@ func (r *ApplicationGroupReconciler) reconcile(ctx context.Context, l logr.Logge
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)
err := fmt.Errorf("application group must list atleast one Application : %w", ErrInvalidSpec)
appGroup.Status.Error = err.Error()
return false, err
}
// 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)
}
err := r.reconcileApplications(l, appGroup)
if err != nil {
l.Error(err, "failed to reconcile the applications")
err = fmt.Errorf("failed to reconcile the applications : %w", err)
return false, err
}
// 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)
return r.generateWorkflow(ctx, l, ns, appGroup)
}
func populateReadinessMatrix(apps []orkestrav1alpha1.DAG) map[string]bool {
readyMatrix := make(map[string]bool)
for _, app := range apps {
readyMatrix[app.Name] = false
}
return readyMatrix
}
func (r *ApplicationGroupReconciler) reconcileApplications(l logr.Logger, appGroup *v1alpha1.ApplicationGroup) error {
stagingDir := r.TargetDir + "/" + r.StagingRepoName
// Pull and conditionally stage application & dependency charts
for i, application := range appGroup.Spec.Applications {
ll := l.WithValues("application", application.Name)
ll.V(3).Info("performing chart actions")
func entryExists(name string, ss []orkestrav1alpha1.ApplicationStatus) bool {
for _, v := range ss {
if v.Name == name {
return true
repoKey := application.Spec.ChartRepoNickname
repoPath := application.Spec.RepoPath
name := application.Spec.HelmReleaseSpec.Name
version := application.Spec.HelmReleaseSpec.Version
fpath, appCh, err := r.RegistryClient.PullChart(ll, repoKey, repoPath, name, version)
defer func() {
if r.Cfg.Cleanup {
os.Remove(fpath)
}
}()
if err != nil || appCh == nil {
err = fmt.Errorf("failed to pull application chart %s/%s:%s : %w", repoKey, name, version, err)
appGroup.Status.Error = err.Error()
ll.Error(err, "failed to pull application chart")
return err
}
if appCh.Dependencies() != nil {
for _, sc := range appCh.Dependencies() {
cs := orkestrav1alpha1.ChartStatus{
Version: sc.Metadata.Version,
}
appGroup.Status.Applications[i].Subcharts[sc.Name()] = cs
}
stagingRepoName := r.StagingRepoName
// If Dependencies - extract subchart and push each to staging registry
if isDependenciesEmbedded(appCh) {
for _, sc := range appCh.Dependencies() {
cs := orkestrav1alpha1.ChartStatus{
Version: sc.Metadata.Version,
}
if err := sc.Validate(); err != nil {
ll.Error(err, "failed to validate application subchart for staging registry")
err = fmt.Errorf("failed to validate application subchart for staging registry : %w", err)
cs.Error = err.Error()
appGroup.Status.Applications[i].Subcharts[sc.Name()] = cs
appGroup.Status.Error = cs.Error
return err
}
path, err := registry.SaveChartPackage(sc, stagingDir)
if err != nil {
ll.Error(err, "failed to save subchart package as tgz")
err = fmt.Errorf("failed to save subchart package as tgz at location %s : %w", path, err)
cs.Error = err.Error()
appGroup.Status.Applications[i].Subcharts[sc.Name()] = cs
appGroup.Status.Error = cs.Error
return err
}
err = r.RegistryClient.PushChart(ll, stagingRepoName, path, sc)
if err != nil {
ll.Error(err, "failed to push application subchart to staging registry")
err = fmt.Errorf("failed to push application subchart to staging registry : %w", err)
cs.Error = err.Error()
appGroup.Status.Applications[i].Subcharts[sc.Name()] = cs
appGroup.Status.Error = cs.Error
return err
}
cs.Staged = true
cs.Version = sc.Metadata.Version
cs.Error = ""
appGroup.Status.Applications[i].Subcharts[sc.Name()] = cs
}
}
// Unset dependencies by disabling them.
// Using appCh.SetDependencies() does not cut it since some charts rely on subcharts for tpl helpers
// provided in the charts directory.
// IMPORTANT: This expects charts to follow best practices to allow enabling and disabling subcharts
// See: https://helm.sh/docs/topics/charts/ #Chart Dependencies
for _, dep := range appCh.Metadata.Dependencies {
// Disable subchart through metadata
dep.Enabled = false
// Precautionary - overwrite values with subcharts disabled
appCh.Values[dep.Name] = map[string]interface{}{
"enabled": false,
}
}
if err := appCh.Validate(); err != nil {
ll.Error(err, "failed to validate application chart for staging registry")
err = fmt.Errorf("failed to validate application chart for staging registry : %w", err)
appGroup.Status.Error = err.Error()
appGroup.Status.Applications[i].ChartStatus.Error = err.Error()
return err
}
_, err := registry.SaveChartPackage(appCh, stagingDir)
if err != nil {
ll.Error(err, "failed to save modified app chart to filesystem")
err = fmt.Errorf("failed to save modified app chart to filesystem : %w", err)
appGroup.Status.Error = err.Error()
appGroup.Status.Applications[i].ChartStatus.Error = err.Error()
return err
}
// Replace existing chart with modified chart
path := stagingDir + "/" + application.Spec.HelmReleaseSpec.Name + "-" + appCh.Metadata.Version + ".tgz"
err = r.RegistryClient.PushChart(ll, stagingRepoName, path, appCh)
if err != nil {
ll.Error(err, "failed to push modified application chart to staging registry")
err = fmt.Errorf("failed to push modified application chart to staging registry : %w", err)
appGroup.Status.Applications[i].ChartStatus.Error = err.Error()
appGroup.Status.Error = err.Error()
return err
}
appGroup.Status.Applications[i].ChartStatus.Staged = true
}
}
return false
return nil
}
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)
func (r *ApplicationGroupReconciler) generateWorkflow(ctx context.Context, logr logr.Logger, ns string, g *orkestrav1alpha1.ApplicationGroup) (requeue bool, err error) {
err = r.Engine.Generate(ctx, logr, ns, g)
if err != nil {
logr.Error(err, "engine failed to generate workflow")
return false, fmt.Errorf("failed to generate workflow : %w", err)
@ -122,6 +183,8 @@ func (r *ApplicationGroupReconciler) generateWorkflow(ctx context.Context, logr
return false, err
}
g.Status.Ready = true
return false, nil
}

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

@ -1,113 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package controllers
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"
ctrl "sigs.k8s.io/controller-runtime"
"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
type ApplicationReconciler 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
// 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
}
// +kubebuilder:rbac:groups=orkestra.azure.microsoft.com,resources=applications,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=orkestra.azure.microsoft.com,resources=applications/status,verbs=get;update;patch
func (r *ApplicationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
var requeue bool
var err error
var application orkestrav1alpha1.Application
ctx := context.Background()
logr := r.Log.WithValues(appNameKey, req.NamespacedName.Name)
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 {
return ctrl.NewControllerManagedBy(mgr).
For(&orkestrav1alpha1.Application{}).
Complete(r)
}
func (r *ApplicationReconciler) updateStatusAndEvent(ctx context.Context, app orkestrav1alpha1.Application, requeue bool, err error) {
errStr := ""
if err != nil {
errStr = err.Error()
}
app.Status.Application.Ready = !requeue
app.Status.Application.Error = errStr
_ = r.Status().Update(ctx, &app)
if errStr != "" {
r.Recorder.Event(&app, "Warning", "ReconcileError", fmt.Sprintf("Failed to reconcile Application %s with Error %s", app.Name, errStr))
} else {
r.Recorder.Event(&app, "Normal", "ReconcileSuccess", fmt.Sprintf("Successfully reconciled Application %s", app.Name))
}
}

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

@ -1,149 +0,0 @@
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) {
stagingDir := r.TargetDir + "/" + r.StagingRepoName
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
repoPath := application.Spec.RepoPath
name := application.Spec.HelmReleaseSpec.Name
version := application.Spec.HelmReleaseSpec.Version
fpath, appCh, err := r.RegistryClient.PullChart(ll, repoKey, repoPath, 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 appCh.Dependencies() != nil {
stagingRepoName := r.StagingRepoName
// If Dependencies - extract subchart and push each to staging registry
if isDependenciesEmbedded(appCh) {
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, stagingDir)
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
cs.Version = sc.Metadata.Version
cs.Ready = true
cs.Error = ""
application.Status.Subcharts[sc.Name()] = cs
application.Status.Application.Staged = true
}
} else {
for _, sc := range appCh.Dependencies() {
cs := orkestrav1alpha1.ChartStatus{
Staged: false,
Version: sc.Metadata.Version,
Ready: true,
Error: "",
}
application.Status.Subcharts[sc.Name()] = cs
application.Status.Application.Staged = false
}
}
// Unset dependencies by disabling them.
// Using appCh.SetDependencies() does not cut it since some charts rely on subcharts for tpl helpers
// provided in the charts directory.
// IMPORTANT: This expects charts to follow best practices to allow enabling and disabling subcharts
// See: https://helm.sh/docs/topics/charts/ #Chart Dependencies
for _, dep := range appCh.Metadata.Dependencies {
// Disable subchart through metadata
dep.Enabled = false
// Precautionary - overrite values with subcharts disabled:93
appCh.Values[dep.Name] = map[string]interface{}{
"enabled": false,
}
}
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)
}
_, err := registry.SaveChartPackage(appCh, stagingDir)
if err != nil {
ll.Error(err, "failed to save modified app chart to filesystem")
return false, fmt.Errorf("failed to save modified app chart to filesystem : %w", err)
}
// Replace existing chart with modified chart
path := stagingDir + "/" + application.Spec.HelmReleaseSpec.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)
}
}
return false, nil
}
func isDependenciesEmbedded(ch *chart.Chart) bool {
isURI := false
for _, d := range ch.Metadata.Dependencies {
if _, err := url.ParseRequestURI(d.Repository); err == nil {
isURI = true
}
}
if !isURI {
if len(ch.Dependencies()) > 0 {
return true
}
}
return false
}

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

@ -1,7 +1,9 @@
# > **Out of date** as of 02/22/2021 - *`Application` custom resource has been deprecated*
# Sequence Diagrams
## Submitting an Application Group to a Kubernetes Cluster
<p align="center"><img src="../assets/submit-sequence.png" width="1000x" /></p>
### Steps

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

@ -7,7 +7,46 @@ spec:
- name: kafka-dev
dependencies:
- redis-dev
spec:
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: []
zookeerper:
enabled: true
targetNamespace: "kafka-dev-ns"
- 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:
imagePullSecrets: []
master:
persistence:
size: 4Gi
targetNamespace: "redis-dev-ns"
# TODO (nitishm) - define these fields using Argo Workflow spec
# strategy:
# rollout:

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

@ -1,29 +0,0 @@
# 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: []
zookeerper:
enabled: true
targetNamespace: "kafka-dev-ns"

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

@ -1,26 +0,0 @@
# 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:
imagePullSecrets: []
master:
persistence:
size: 4Gi
targetNamespace: "redis-dev-ns"

29
main.go
Просмотреть файл

@ -103,20 +103,6 @@ func main() {
os.Exit(1)
}
if err = (&controllers.ApplicationReconciler{
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")
@ -124,12 +110,15 @@ func main() {
}
if err = (&controllers.ApplicationGroupReconciler{
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"),
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("ApplicationGroup"),
Scheme: mgr.GetScheme(),
Cfg: cfg.Ctrl,
RegistryClient: rc,
Engine: workflow.Argo(scheme, mgr.GetClient(), sCfg.URL),
StagingRepoName: stagingRepoName,
TargetDir: tempChartStoreTargetDir,
Recorder: mgr.GetEventRecorderFor("appgroup-controller"),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ApplicationGroup")
os.Exit(1)

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

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"os"
"sort"
"github.com/Azure/Orkestra/api/v1alpha1"
v1alpha12 "github.com/argoproj/argo/pkg/apis/workflow/v1alpha1"
@ -64,37 +63,19 @@ func (a *argo) initWorkflowObject() {
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 {
func (a *argo) Generate(ctx context.Context, l logr.Logger, ns string, g *v1alpha1.ApplicationGroup) 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(ctx, g, apps)
err := a.generateWorkflow(ctx, g)
if err != nil {
l.Error(err, "failed to generate workflow")
return fmt.Errorf("failed to generate argo workflow : %w", err)
@ -146,9 +127,9 @@ func (a *argo) Submit(ctx context.Context, l logr.Logger, g *v1alpha1.Applicatio
return nil
}
func (a *argo) generateWorkflow(ctx context.Context, g *v1alpha1.ApplicationGroup, apps []*v1alpha1.Application) error {
func (a *argo) generateWorkflow(ctx context.Context, g *v1alpha1.ApplicationGroup) error {
// Generate the Entrypoint template and Application Group DAG
err := a.generateAppGroupTpls(ctx, g, apps)
err := a.generateAppGroupTpls(ctx, g)
if err != nil {
return fmt.Errorf("failed to generate Application Group DAG : %w", err)
}
@ -156,7 +137,7 @@ func (a *argo) generateWorkflow(ctx context.Context, g *v1alpha1.ApplicationGrou
return nil
}
func (a *argo) generateAppGroupTpls(ctx context.Context, g *v1alpha1.ApplicationGroup, apps []*v1alpha1.Application) error {
func (a *argo) generateAppGroupTpls(ctx context.Context, g *v1alpha1.ApplicationGroup) error {
if a.wf == nil {
return fmt.Errorf("workflow cannot be nil")
}
@ -168,13 +149,13 @@ func (a *argo) generateAppGroupTpls(ctx context.Context, g *v1alpha1.Application
entry := v1alpha12.Template{
Name: entrypointTplName,
DAG: &v1alpha12.DAGTemplate{
Tasks: make([]v1alpha12.DAGTask, len(apps)),
Tasks: make([]v1alpha12.DAGTask, len(g.Spec.Applications)),
// TBD (nitishm): Do we need to failfast?
// FailFast: true
},
}
adt, err := a.generateAppDAGTemplates(ctx, apps, a.stagingRepoURL)
adt, err := a.generateAppDAGTemplates(ctx, g, a.stagingRepoURL)
if err != nil {
return fmt.Errorf("failed to generate application DAG templates : %w", err)
}
@ -213,13 +194,16 @@ func updateAppGroupDAG(g *v1alpha1.ApplicationGroup, entry *v1alpha12.Template,
return nil
}
func (a *argo) generateAppDAGTemplates(ctx context.Context, apps []*v1alpha1.Application, repo string) ([]v1alpha12.Template, error) {
func (a *argo) generateAppDAGTemplates(ctx context.Context, g *v1alpha1.ApplicationGroup, repo string) ([]v1alpha12.Template, error) {
ts := make([]v1alpha12.Template, 0)
for _, app := range apps {
for i, app := range g.Spec.Applications {
var hasSubcharts bool
app.Spec.Values = app.Spec.Overlays
appStatus := &g.Status.Applications[i].ChartStatus
scStatus := g.Status.Applications[i].Subcharts
// Create Subchart DAG only when the application chart has dependencies
if len(app.Spec.Subcharts) > 0 {
hasSubcharts = true
@ -228,7 +212,7 @@ func (a *argo) generateAppDAGTemplates(ctx context.Context, apps []*v1alpha1.App
}
t.DAG = &v1alpha12.DAGTemplate{}
tasks, err := a.generateSubchartAndAppDAGTasks(ctx, app, repo, app.Spec.HelmReleaseSpec.TargetNamespace)
tasks, err := a.generateSubchartAndAppDAGTasks(ctx, g, &app.Spec, appStatus, scStatus, repo, app.Spec.HelmReleaseSpec.TargetNamespace)
if err != nil {
return nil, fmt.Errorf("failed to generate Application Template DAG tasks : %w", err)
}
@ -251,14 +235,16 @@ func (a *argo) generateAppDAGTemplates(ctx context.Context, apps []*v1alpha1.App
// Create the namespace since helm-operator does not do this
err := a.cli.Get(ctx, types.NamespacedName{Name: ns.Name}, &ns)
if err != nil {
err = controllerutil.SetControllerReference(app, &ns, a.scheme)
if err != nil {
return nil, fmt.Errorf("failed to set OwnerReference for Namespace %s : %w", ns.Name, err)
}
if errors.IsNotFound(err) {
err = controllerutil.SetControllerReference(g, &ns, a.scheme)
if err != nil {
return nil, fmt.Errorf("failed to set OwnerReference for Namespace %s : %w", ns.Name, err)
}
err = a.cli.Create(ctx, &ns)
if err != nil {
return nil, fmt.Errorf("failed to CREATE namespace %s object for application %s : %w", ns.Name, app.Name, err)
err = a.cli.Create(ctx, &ns)
if err != nil {
return nil, fmt.Errorf("failed to CREATE namespace %s object for application %s : %w", ns.Name, app.Name, err)
}
}
}
@ -277,7 +263,7 @@ func (a *argo) generateAppDAGTemplates(ctx context.Context, apps []*v1alpha1.App
// hr.Spec.RepoChartSource.RepoURL = repo
if app.Status.Application.Staged {
if appStatus.Staged {
hr.Spec.RepoURL = repo
}
@ -307,23 +293,19 @@ func (a *argo) generateAppDAGTemplates(ctx context.Context, apps []*v1alpha1.App
return ts, nil
}
func (a *argo) generateSubchartAndAppDAGTasks(ctx context.Context, app *v1alpha1.Application, repo, targetNS string) ([]v1alpha12.DAGTask, error) {
func (a *argo) generateSubchartAndAppDAGTasks(ctx context.Context, g *v1alpha1.ApplicationGroup, app *v1alpha1.ApplicationSpec, status *v1alpha1.ChartStatus, subchartsStatus map[string]v1alpha1.ChartStatus, 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)
tasks := make([]v1alpha12.DAGTask, 0, len(app.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")
}
for _, sc := range app.Subcharts {
isStaged := subchartsStatus[sc.Name].Staged
version := subchartsStatus[sc.Name].Version
isStaged := app.Status.Application.Staged
hr := generateSubchartHelmRelease(app.Spec.HelmReleaseSpec, sc.Name, s.Version, repo, targetNS, isStaged)
hr := generateSubchartHelmRelease(app.HelmReleaseSpec, sc.Name, version, repo, targetNS, isStaged)
task := v1alpha12.DAGTask{
Name: sc.Name,
Template: helmReleaseExecutor,
@ -351,15 +333,17 @@ func (a *argo) generateSubchartAndAppDAGTasks(ctx context.Context, app *v1alpha1
// Create the namespace since helm-operator does not do this
err := a.cli.Get(ctx, types.NamespacedName{Name: ns.Name}, &ns)
if err != nil {
// Add OwnershipReference
err = controllerutil.SetControllerReference(app, &ns, a.scheme)
if err != nil {
return nil, fmt.Errorf("failed to set OwnerReference for Namespace %s : %w", ns.Name, err)
}
if errors.IsNotFound(err) {
// Add OwnershipReference
err = controllerutil.SetControllerReference(g, &ns, a.scheme)
if err != nil {
return nil, fmt.Errorf("failed to set OwnerReference for Namespace %s : %w", ns.Name, err)
}
err = a.cli.Create(ctx, &ns)
if err != nil {
return nil, fmt.Errorf("failed to CREATE namespace %s object for subchart %s : %w", ns.Name, sc.Name, err)
err = a.cli.Create(ctx, &ns)
if err != nil {
return nil, fmt.Errorf("failed to CREATE namespace %s object for subchart %s : %w", ns.Name, sc.Name, err)
}
}
}
@ -373,9 +357,9 @@ func (a *argo) generateSubchartAndAppDAGTasks(ctx context.Context, app *v1alpha1
},
ObjectMeta: v1.ObjectMeta{
Name: app.Name,
Namespace: app.Spec.HelmReleaseSpec.TargetNamespace,
Namespace: app.HelmReleaseSpec.TargetNamespace,
},
Spec: app.DeepCopy().Spec.HelmReleaseSpec,
Spec: app.DeepCopy().HelmReleaseSpec,
}
// staging repo instead of the primary repo
@ -385,8 +369,8 @@ func (a *argo) generateSubchartAndAppDAGTasks(ctx context.Context, app *v1alpha1
// to prevent duplication and possible collision of deployed resources
// Since the subchart should have been deployed in a prior DAG step,
// we must not redeploy it along with the parent application chart.
for d := range app.Status.Subcharts {
hr.Spec.Values.Data[d] = map[string]interface{}{
for _, d := range app.Subcharts {
hr.Spec.Values.Data[d.Name] = map[string]interface{}{
"enabled": false,
}
}
@ -404,15 +388,17 @@ func (a *argo) generateSubchartAndAppDAGTasks(ctx context.Context, app *v1alpha1
// Create the namespace since helm-operator does not do this
err := a.cli.Get(ctx, types.NamespacedName{Name: ns.Name}, &ns)
if err != nil {
// Add OwnershipReference
err = controllerutil.SetControllerReference(app, &ns, a.scheme)
if err != nil {
return nil, fmt.Errorf("failed to set OwnerReference for Namespace %s : %w", ns.Name, err)
}
if errors.IsNotFound(err) {
// Add OwnershipReference
err = controllerutil.SetControllerReference(g, &ns, a.scheme)
if err != nil {
return nil, fmt.Errorf("failed to set OwnerReference for Namespace %s : %w", ns.Name, err)
}
err = a.cli.Create(ctx, &ns)
if err != nil {
return nil, fmt.Errorf("failed to CREATE namespace %s object for staged application %s : %w", ns.Name, app.Name, err)
err = a.cli.Create(ctx, &ns)
if err != nil {
return nil, fmt.Errorf("failed to CREATE namespace %s object for staged application %s : %w", ns.Name, app.Name, err)
}
}
}

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

@ -1,16 +1,11 @@
package workflow
import (
"context"
"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"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func helmValues(v string) map[string]interface{} {
@ -900,36 +895,36 @@ func Test_subchartValues(t *testing.T) {
// }
// }
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(context.TODO(), tt.args.g, tt.args.apps); (err != nil) != tt.wantErr {
t.Errorf("argo.generateAppGroupTpls() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
// 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(context.TODO(), tt.args.g, tt.args.apps); (err != nil) != tt.wantErr {
// t.Errorf("argo.generateAppGroupTpls() error = %v, wantErr %v", err, tt.wantErr)
// }
// })
// }
// }

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

@ -9,7 +9,7 @@ import (
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
Generate(ctx context.Context, l logr.Logger, ns string, g *v1alpha1.ApplicationGroup) 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
}

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

@ -5,3 +5,4 @@ registries:
staging:
url: "http://localhost:8080"
path: "charts/"