Skip to content

Commit

Permalink
Add API for FluxCD Application (#778)
Browse files Browse the repository at this point in the history
  • Loading branch information
chengleqi authored Oct 8, 2022
1 parent 2797669 commit f8820f7
Show file tree
Hide file tree
Showing 18 changed files with 1,868 additions and 553 deletions.
1 change: 1 addition & 0 deletions cmd/apiserver/app/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func (s *ServerRunOptions) Flags() (fss cliflag.NamedFlagSets) {
s.SonarQubeOptions.AddFlags(fss.FlagSet("sonarqube"), s.SonarQubeOptions)
s.S3Options.AddFlags(fss.FlagSet("s3"), s.S3Options)
s.ArgoCDOption.AddFlags(fss.FlagSet("argocd"))
s.FluxCDOption.AddFlags(fss.FlagSet("fluxcd"))

fs = fss.FlagSet("klog")
local := flag.NewFlagSet("klog", flag.ExitOnError)
Expand Down
2 changes: 1 addition & 1 deletion pkg/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (s *APIServer) installKubeSphereAPIs() {
))
wss = append(wss, gitops.AddToContainer(s.container, &common.Options{
GenericClient: s.Client,
}, s.Config.ArgoCDOption)...)
}, s.Config.ArgoCDOption, s.Config.FluxCDOption)...)
doc.AddSwaggerService(wss, s.container)
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ type Config struct {
S3Options *s3.Options `json:"s3,omitempty" yaml:"s3,omitempty" mapstructure:"s3"`
SonarQubeOptions *sonarqube.Options `json:"sonarqube,omitempty" yaml:"sonarQube,omitempty" mapstructure:"sonarqube"`
ArgoCDOption *ArgoCDOption `json:"argocd,omitempty" yaml:"argocd,omitempty" mapstructure:"argocd"`
FluxCDOption *FluxCDOption `json:"fluxcd,omitempty" yaml:"fluxcd,omitempty" mapstructure:"fluxcd"`
AuthenticationOptions *authoptions.AuthenticationOptions `json:"authentication,omitempty" yaml:"authentication,omitempty" mapstructure:"authentication"`
AuthMode AuthMode `json:"authMode,omitempty" yaml:"authMode,omitempty" mapstructure:"authMode"`
JWTSecret string `json:"jwtSecret,omitempty" yaml:"jwtSecret,omitempty" mapstructure:"jwtSecret"`
Expand All @@ -99,6 +100,7 @@ func New() *Config {
S3Options: s3.NewS3Options(),
AuthMode: AuthModeToken,
ArgoCDOption: &ArgoCDOption{},
FluxCDOption: &FluxCDOption{},
}
}

Expand Down
28 changes: 27 additions & 1 deletion pkg/config/argocd.go → pkg/config/gitops.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,41 @@ limitations under the License.

package config

import "github.com/spf13/pflag"
import (
"github.com/spf13/pflag"
v1alpha1 "kubesphere.io/devops/pkg/api/gitops/v1alpha1"
)

// ArgoCDOption as the ArgoCD integration configuration
type ArgoCDOption struct {
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty" description:"enabled FluxCD"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty" description:"Which namespace the ArgoCD located"`
}

// AddFlags adds the flags which related to argocd
func (o *ArgoCDOption) AddFlags(fs *pflag.FlagSet) {
fs.BoolVar(&o.Enabled, "argocd-enabled", false, "Enable ArgoCD APIs")
// see also https://argo-cd.readthedocs.io/en/stable/getting_started/
fs.StringVarP(&o.Namespace, "argocd-namespace", o.Namespace, "argocd", "Which namespace the ArgoCD located")
}

// FluxCDOption as the FluxCD integration configuration
type FluxCDOption struct {
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty" description:"enabled FluxCD"`
}

// AddFlags adds the flags which related to fluxcd
func (o *FluxCDOption) AddFlags(fs *pflag.FlagSet) {
fs.BoolVar(&o.Enabled, "fluxcd-enabled", false, "Enable FluxCD APIs")
}

// GetGitOpsEngine return gitops engine type
func GetGitOpsEngine(argoOption *ArgoCDOption, fluxOption *FluxCDOption) v1alpha1.Engine {
if argoOption.Enabled && !fluxOption.Enabled {
return v1alpha1.ArgoCD
}
if fluxOption.Enabled && !argoOption.Enabled {
return v1alpha1.FluxCD
}
return ""
}
177 changes: 43 additions & 134 deletions pkg/kapis/gitops/v1alpha1/argocd/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@ import (
"k8s.io/apiserver/pkg/authentication/user"
utilretry "k8s.io/client-go/util/retry"
"kubesphere.io/devops/pkg/api/gitops/v1alpha1"
"kubesphere.io/devops/pkg/apiserver/query"
apiserverrequest "kubesphere.io/devops/pkg/apiserver/request"
"kubesphere.io/devops/pkg/config"
"kubesphere.io/devops/pkg/kapis/common"
"kubesphere.io/devops/pkg/models/resources/v1alpha3"
"kubesphere.io/devops/pkg/utils/k8sutil"
"kubesphere.io/devops/pkg/kapis/gitops/v1alpha1/gitops"
"net/http"
"sigs.k8s.io/controller-runtime/pkg/client"
)
Expand All @@ -42,40 +40,54 @@ var invalidRequestBodyError = restful.NewError(http.StatusBadRequest,
var unauthenticatedError = restful.NewError(http.StatusUnauthorized,
"unauthenticated request")

func (h *handler) applicationList(req *restful.Request, res *restful.Response) {
func (h *handler) createApplication(req *restful.Request, res *restful.Response) {
var err error
namespace := common.GetPathParameter(req, common.NamespacePathParameter)
healthStatus := common.GetQueryParameter(req, healthStatusQueryParam)
syncStatus := common.GetQueryParameter(req, syncStatusQueryParam)

applicationList := &v1alpha1.ApplicationList{}
matchingLabels := client.MatchingLabels{}
if syncStatus != "" {
matchingLabels[v1alpha1.SyncStatusLabelKey] = syncStatus
}
if healthStatus != "" {
matchingLabels[v1alpha1.HealthStatusLabelKey] = healthStatus
}
if err := h.List(context.Background(), applicationList, client.InNamespace(namespace), matchingLabels); err != nil {
common.Response(req, res, applicationList, err)
return
application := &v1alpha1.Application{}
if err = req.ReadEntity(application); err == nil {
application.Namespace = namespace
if application.Labels == nil {
application.Labels = make(map[string]string)
}
application.Labels[v1alpha1.ArgoCDLocationLabelKey] = h.ArgoCDNamespace
err = h.Create(context.Background(), application)
}
common.Response(req, res, application, err)
}

queryParam := query.ParseQueryParameter(req)
list := v1alpha3.DefaultList(toObjects(applicationList.Items), queryParam, v1alpha3.DefaultCompare(), v1alpha3.DefaultFilter(), nil)
func (h *handler) applicationSummary(request *restful.Request, response *restful.Response) {
namespace := common.GetPathParameter(request, common.NamespacePathParameter)

common.Response(req, res, list, nil)
summary, err := h.populateApplicationSummary(namespace)
common.Response(request, response, summary, err)
}

func (h *handler) getApplication(req *restful.Request, res *restful.Response) {
namespace := common.GetPathParameter(req, common.NamespacePathParameter)
name := common.GetPathParameter(req, pathParameterApplication)
func (h *handler) populateApplicationSummary(namespace string) (*ApplicationsSummary, error) {
applicationList := &v1alpha1.ApplicationList{}
if err := h.List(context.Background(), applicationList, client.InNamespace(namespace)); err != nil {
return nil, err
}
summary := &ApplicationsSummary{
HealthStatus: map[string]int{},
SyncStatus: map[string]int{},
}
summary.Total = len(applicationList.Items)

application := &v1alpha1.Application{}
err := h.Get(context.Background(), types.NamespacedName{
Namespace: namespace,
Name: name,
}, application)
common.Response(req, res, application, err)
for i := range applicationList.Items {
app := &applicationList.Items[i]
// accumulate health status
healthStatus := app.GetLabels()[v1alpha1.HealthStatusLabelKey]
if healthStatus != "" {
summary.HealthStatus[healthStatus]++
}
// accumulate sync status
syncStatus := app.GetLabels()[v1alpha1.SyncStatusLabelKey]
if syncStatus != "" {
summary.SyncStatus[syncStatus]++
}
}
return summary, nil
}

func (h *handler) handleSyncApplication(req *restful.Request, res *restful.Response) {
Expand Down Expand Up @@ -169,75 +181,6 @@ func (h *handler) updateOperation(namespace, name string, operation *v1alpha1.Op
})
}

func (h *handler) delApplication(req *restful.Request, res *restful.Response) {
namespace := common.GetPathParameter(req, common.NamespacePathParameter)
name := common.GetPathParameter(req, pathParameterApplication)
cascade := common.GetQueryParameter(req, cascadeQueryParam)

ctx := context.Background()
application := &v1alpha1.Application{}
objectKey := types.NamespacedName{
Namespace: namespace,
Name: name,
}
err := h.Get(ctx, objectKey, application)
if err == nil {
// add the Argo CD resources finalizer if cascade is true
if cascade == "true" {
if k8sutil.AddFinalizer(&application.ObjectMeta, v1alpha1.ArgoCDResourcesFinalizer) {
if err = h.Update(ctx, application); err != nil {
common.Response(req, res, application, err)
return
}

if err = h.Get(ctx, objectKey, application); err != nil {
common.Response(req, res, application, err)
return
}
}
}
err = h.Delete(ctx, application)
}
common.Response(req, res, application, err)
}

func (h *handler) createApplication(req *restful.Request, res *restful.Response) {
var err error
namespace := common.GetPathParameter(req, common.NamespacePathParameter)

application := &v1alpha1.Application{}
if err = req.ReadEntity(application); err == nil {
application.Namespace = namespace
if application.Labels == nil {
application.Labels = make(map[string]string)
}
application.Labels[v1alpha1.ArgoCDLocationLabelKey] = h.ArgoCDNamespace
err = h.Create(context.Background(), application)
}

common.Response(req, res, application, err)
}

func (h *handler) updateApplication(req *restful.Request, res *restful.Response) {
namespace := common.GetPathParameter(req, common.NamespacePathParameter)
name := common.GetPathParameter(req, pathParameterApplication)

var err error
application := &v1alpha1.Application{}
if err = req.ReadEntity(application); err == nil {
latestApp := &v1alpha1.Application{}
err = h.Get(context.Background(), types.NamespacedName{
Namespace: namespace,
Name: name,
}, latestApp)
if err == nil {
application.ResourceVersion = latestApp.ResourceVersion
err = h.Update(context.Background(), application)
}
}
common.Response(req, res, application, err)
}

func (h *handler) getClusters(req *restful.Request, res *restful.Response) {
ctx := context.Background()

Expand Down Expand Up @@ -269,48 +212,14 @@ func (h *handler) getClusters(req *restful.Request, res *restful.Response) {
common.Response(req, res, argoClusters, err)
}

func (h *handler) applicationSummary(request *restful.Request, response *restful.Response) {
namespace := common.GetPathParameter(request, common.NamespacePathParameter)

summary, err := h.populateApplicationSummary(namespace)
common.Response(request, response, summary, err)
}

func (h *handler) populateApplicationSummary(namespace string) (*ApplicationsSummary, error) {
applicationList := &v1alpha1.ApplicationList{}
if err := h.List(context.Background(), applicationList, client.InNamespace(namespace)); err != nil {
return nil, err
}
summary := &ApplicationsSummary{
HealthStatus: map[string]int{},
SyncStatus: map[string]int{},
}
summary.Total = len(applicationList.Items)

for i := range applicationList.Items {
app := &applicationList.Items[i]
// accumulate health status
healthStatus := app.GetLabels()[v1alpha1.HealthStatusLabelKey]
if healthStatus != "" {
summary.HealthStatus[healthStatus]++
}
// accumulate sync status
syncStatus := app.GetLabels()[v1alpha1.SyncStatusLabelKey]
if syncStatus != "" {
summary.SyncStatus[syncStatus]++
}
}
return summary, nil
}

type handler struct {
client.Client
*gitops.Handler
ArgoCDNamespace string
}

func newHandler(options *common.Options, argoOption *config.ArgoCDOption) *handler {
return &handler{
Client: options.GenericClient,
Handler: gitops.NewHandler(options),
ArgoCDNamespace: argoOption.Namespace,
}
}
Loading

0 comments on commit f8820f7

Please sign in to comment.