Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
37223fa
API spec
manish-agrawal-ai Apr 21, 2022
2fd2b2d
timeout added
manish-agrawal-ai Apr 21, 2022
f03729e
spec update
manish-agrawal-ai Apr 21, 2022
4ba773a
fix
manish-agrawal-ai Apr 21, 2022
0abbb06
fix
manish-agrawal-ai Apr 21, 2022
4172212
wip
manish-agrawal-ai Apr 25, 2022
e0879ed
fix
manish-agrawal-ai Apr 25, 2022
89fec09
spec updated
manish-agrawal-ai Apr 26, 2022
328feeb
status fix
manish-agrawal-ai Apr 26, 2022
d72e218
helm operations
manish-agrawal-ai Apr 26, 2022
651a5c7
Merge branch 'main' into module_mvp
manish-agrawal-ai Apr 26, 2022
894a237
fix
manish-agrawal-ai Apr 26, 2022
3816db4
sql fixes
manish-agrawal-ai Apr 26, 2022
a301e12
fixes
manish-agrawal-ai Apr 26, 2022
c30bc2e
fix
manish-agrawal-ai Apr 26, 2022
63c7864
bom url change
manish-agrawal-ai Apr 27, 2022
9b27de7
canServerUpdate in get server-info api
manish-agrawal-ai Apr 27, 2022
8d78a03
bug fix
manish-agrawal-ai Apr 27, 2022
017c2fa
bug fix
manish-agrawal-ai Apr 27, 2022
5ba7983
informer fix
manish-agrawal-ai Apr 27, 2022
ce18754
fix
manish-agrawal-ai Apr 27, 2022
8a43a31
module name casing change
manish-agrawal-ai Apr 27, 2022
bb52b94
Merge branch 'main' into module_mvp
manish-agrawal-ai Apr 27, 2022
aaad5cb
Merge branch 'main' into module_mvp
manish-agrawal-ai Apr 28, 2022
404bfb8
Merge branch 'main' into module_mvp
manish-agrawal-ai May 3, 2022
6e7562d
DB file numbe change
manish-agrawal-ai May 3, 2022
09840c4
devtronBomUrl from env variable
manish-agrawal-ai May 3, 2022
a0a87f1
wire generate
manish-agrawal-ai May 3, 2022
761abec
code comments incorporate
manish-agrawal-ai May 3, 2022
d469082
fix
manish-agrawal-ai May 3, 2022
9ebc78e
dummy
manish-agrawal-ai May 3, 2022
f79031d
removed cicd hardcoding
manish-agrawal-ai May 3, 2022
5373386
log level made configurable
manish-agrawal-ai May 4, 2022
40e437b
fix : using InstallerCrdObjectExists
manish-agrawal-ai May 4, 2022
bb23f1b
buildInformerToListenOnInstallerObject fix
manish-agrawal-ai May 4, 2022
0e102b6
log removed
manish-agrawal-ai May 4, 2022
c6bc1f1
code comments incorporate
manish-agrawal-ai May 6, 2022
0792150
Merge branch 'main' into module_mvp
manish-agrawal-ai May 9, 2022
3e36e7e
sql file number changed
manish-agrawal-ai May 9, 2022
92bc019
bom url defualt value change
manish-agrawal-ai May 10, 2022
649912d
Merge branch 'main' into module_mvp
manish-agrawal-ai May 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ import (
"github.com/devtron-labs/devtron/api/deployment"
"github.com/devtron-labs/devtron/api/externalLink"
client "github.com/devtron-labs/devtron/api/helm-app"
"github.com/devtron-labs/devtron/api/module"
"github.com/devtron-labs/devtron/api/restHandler"
pipeline2 "github.com/devtron-labs/devtron/api/restHandler/app"
"github.com/devtron-labs/devtron/api/router"
"github.com/devtron-labs/devtron/api/router/pubsub"
"github.com/devtron-labs/devtron/api/server"
"github.com/devtron-labs/devtron/api/sse"
"github.com/devtron-labs/devtron/api/sso"
"github.com/devtron-labs/devtron/api/team"
Expand Down Expand Up @@ -114,6 +116,8 @@ func InitializeApp() (*App, error) {
appStoreDiscover.AppStoreDiscoverWireSet,
appStoreValues.AppStoreValuesWireSet,
appStoreDeployment.AppStoreDeploymentWireSet,
server.ServerWireSet,
module.ModuleWireSet,
// -------wireset end ----------
gitSensor.GetGitSensorConfig,
gitSensor.NewGitSensorSession,
Expand Down
170 changes: 159 additions & 11 deletions api/helm-app/HelmAppService.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,28 @@ package client

import (
"context"
"errors"
"fmt"
"github.com/devtron-labs/devtron/api/connector"
openapi "github.com/devtron-labs/devtron/api/helm-app/openapiClient"
"github.com/devtron-labs/devtron/client/k8s/application"
"github.com/devtron-labs/devtron/pkg/cluster"
serverBean "github.com/devtron-labs/devtron/pkg/server/bean"
serverEnvConfig "github.com/devtron-labs/devtron/pkg/server/config"
serverDataStore "github.com/devtron-labs/devtron/pkg/server/store"
util2 "github.com/devtron-labs/devtron/util"
"github.com/devtron-labs/devtron/util/rbac"
jsonpatch "github.com/evanphx/json-patch"
"github.com/ghodss/yaml"
"github.com/gogo/protobuf/proto"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"go.uber.org/zap"
"net/http"
"reflect"
"strconv"
"strings"
"time"
)

const DEFAULT_CLUSTER = "default_cluster"
Expand All @@ -34,26 +45,33 @@ type HelmAppService interface {
IsReleaseInstalled(ctx context.Context, app *AppIdentifier) (bool, error)
RollbackRelease(ctx context.Context, app *AppIdentifier, version int32) (bool, error)
GetClusterConf(clusterId int) (*ClusterConfig, error)
GetDevtronHelmAppIdentifier() *AppIdentifier
UpdateApplicationWithChartInfoWithExtraValues(ctx context.Context, appIdentifier *AppIdentifier, chartRepository *ChartRepository, extraValues map[string]interface{}, extraValuesYamlUrl string, useLatestChartVersion bool) (*openapi.UpdateReleaseResponse, error)
}

type HelmAppServiceImpl struct {
logger *zap.SugaredLogger
clusterService cluster.ClusterService
helmAppClient HelmAppClient
pump connector.Pump
enforcerUtil rbac.EnforcerUtilHelm
logger *zap.SugaredLogger
clusterService cluster.ClusterService
helmAppClient HelmAppClient
pump connector.Pump
enforcerUtil rbac.EnforcerUtilHelm
serverDataStore *serverDataStore.ServerDataStore
serverEnvConfig *serverEnvConfig.ServerEnvConfig
}

func NewHelmAppServiceImpl(Logger *zap.SugaredLogger,
clusterService cluster.ClusterService,
helmAppClient HelmAppClient,
pump connector.Pump, enforcerUtil rbac.EnforcerUtilHelm) *HelmAppServiceImpl {
pump connector.Pump, enforcerUtil rbac.EnforcerUtilHelm, serverDataStore *serverDataStore.ServerDataStore,
serverEnvConfig *serverEnvConfig.ServerEnvConfig) *HelmAppServiceImpl {
return &HelmAppServiceImpl{
logger: Logger,
clusterService: clusterService,
helmAppClient: helmAppClient,
pump: pump,
enforcerUtil: enforcerUtil,
logger: Logger,
clusterService: clusterService,
helmAppClient: helmAppClient,
pump: pump,
enforcerUtil: enforcerUtil,
serverDataStore: serverDataStore,
serverEnvConfig: serverEnvConfig,
}
}

Expand Down Expand Up @@ -179,6 +197,7 @@ func (impl *HelmAppServiceImpl) GetClusterConf(clusterId int) (*ClusterConfig, e
}
return config, nil
}

func (impl *HelmAppServiceImpl) GetApplicationDetail(ctx context.Context, app *AppIdentifier) (*AppDetail, error) {
config, err := impl.GetClusterConf(app.ClusterId)
if err != nil {
Expand All @@ -191,6 +210,26 @@ func (impl *HelmAppServiceImpl) GetApplicationDetail(ctx context.Context, app *A
ReleaseName: app.ReleaseName,
}
appdetail, err := impl.helmAppClient.GetAppDetail(ctx, req)
if err != nil {
impl.logger.Errorw("error in fetching app detail", "err", err)
return nil, err
}

// if application is devtron app helm release,
// then for FULL (installer object exists), then status is combination of helm app status and installer object status -
// if installer status is not applied then check for timeout and progressing
devtronHelmAppIdentifier := impl.GetDevtronHelmAppIdentifier()
if app.ClusterId == devtronHelmAppIdentifier.ClusterId && app.Namespace == devtronHelmAppIdentifier.Namespace && app.ReleaseName == devtronHelmAppIdentifier.ReleaseName &&
impl.serverDataStore.InstallerCrdObjectExists {
if impl.serverDataStore.InstallerCrdObjectStatus != serverBean.InstallerCrdObjectStatusApplied {
// if timeout
if time.Now().After(appdetail.GetLastDeployed().AsTime().Add(1 * time.Hour)) {
appdetail.ApplicationStatus = serverBean.AppHealthStatusDegraded
} else {
appdetail.ApplicationStatus = serverBean.AppHealthStatusProgressing
}
}
}
return appdetail, err

}
Expand Down Expand Up @@ -427,6 +466,115 @@ func (impl *HelmAppServiceImpl) RollbackRelease(ctx context.Context, app *AppIde
return apiResponse.Result, nil
}

func (impl *HelmAppServiceImpl) GetDevtronHelmAppIdentifier() *AppIdentifier {
return &AppIdentifier{
ClusterId: 1,
Namespace: impl.serverEnvConfig.DevtronHelmReleaseNamespace,
ReleaseName: impl.serverEnvConfig.DevtronHelmReleaseName,
}
}

func (impl *HelmAppServiceImpl) UpdateApplicationWithChartInfoWithExtraValues(ctx context.Context, appIdentifier *AppIdentifier,
chartRepository *ChartRepository, extraValues map[string]interface{}, extraValuesYamlUrl string, useLatestChartVersion bool) (*openapi.UpdateReleaseResponse, error) {

// get release info
releaseInfo, err := impl.GetValuesYaml(context.Background(), appIdentifier)
if err != nil {
impl.logger.Errorw("error in fetching helm release info", "err", err)
return nil, err
}

// initialise object with original values
jsonString := releaseInfo.MergedValues

// handle extra values
// special handling for array
if len(extraValues) > 0 {
for k, v := range extraValues {
var valueI interface{}
if reflect.TypeOf(v).Kind() == reflect.Slice {
currentValue := gjson.Get(jsonString, k).Value()
value := make([]interface{}, 0)
if currentValue != nil {
value = currentValue.([]interface{})
}
for _, singleNewVal := range v.([]interface{}) {
value = append(value, singleNewVal)
}
valueI = value
} else {
valueI = v
}
jsonString, err = sjson.Set(jsonString, k, valueI)
if err != nil {
impl.logger.Errorw("error in handing extra values", "err", err)
return nil, err
}
}
}

// convert to byte array
mergedValuesJsonByteArr := []byte(jsonString)

// handle extra values from url
if len(extraValuesYamlUrl) > 0 {
extraValuesUrlYamlByteArr, err := util2.ReadFromUrlWithRetry(extraValuesYamlUrl)
if err != nil {
impl.logger.Errorw("error in reading content", "extraValuesYamlUrl", extraValuesYamlUrl, "err", err)
return nil, err
} else if extraValuesUrlYamlByteArr == nil {
impl.logger.Errorw("response is empty from url", "extraValuesYamlUrl", extraValuesYamlUrl)
return nil, errors.New("response is empty from values url")
}

extraValuesUrlJsonByteArr, err := yaml.YAMLToJSON(extraValuesUrlYamlByteArr)
if err != nil {
impl.logger.Errorw("error in converting json to yaml", "err", err)
return nil, err
}

mergedValuesJsonByteArr, err = jsonpatch.MergePatch(mergedValuesJsonByteArr, extraValuesUrlJsonByteArr)
if err != nil {
impl.logger.Errorw("error in json patch of extra values from url", "err", err)
return nil, err
}
}

// convert JSON to yaml byte array
mergedValuesYamlByteArr, err := yaml.JSONToYAML(mergedValuesJsonByteArr)
if err != nil {
impl.logger.Errorw("error in converting json to yaml", "err", err)
return nil, err
}

// update in helm
updateReleaseRequest := &InstallReleaseRequest{
ReleaseIdentifier: &ReleaseIdentifier{
ReleaseName: appIdentifier.ReleaseName,
ReleaseNamespace: appIdentifier.Namespace,
},
ChartName: releaseInfo.DeployedAppDetail.ChartName,
ValuesYaml: string(mergedValuesYamlByteArr),
ChartRepository: chartRepository,
}
if !useLatestChartVersion {
updateReleaseRequest.ChartVersion = releaseInfo.DeployedAppDetail.ChartVersion
}

updateResponse, err := impl.UpdateApplicationWithChartInfo(ctx, appIdentifier.ClusterId, updateReleaseRequest)
if err != nil {
impl.logger.Errorw("error in upgrading release", "err", err)
return nil, err
}
// update in helm ends

response := &openapi.UpdateReleaseResponse{
Success: updateResponse.Success,
}

return response, nil
}

type AppIdentifier struct {
ClusterId int `json:"clusterId"`
Namespace string `json:"namespace"`
Expand Down
136 changes: 136 additions & 0 deletions api/module/ModuleRestHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright (c) 2020 Devtron Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package module

import (
"encoding/json"
"errors"
"github.com/devtron-labs/devtron/api/restHandler/common"
"github.com/devtron-labs/devtron/pkg/module"
"github.com/devtron-labs/devtron/pkg/user"
"github.com/devtron-labs/devtron/pkg/user/casbin"
"github.com/gorilla/mux"
"go.uber.org/zap"
"gopkg.in/go-playground/validator.v9"
"net/http"
)

type ModuleRestHandler interface {
GetModuleInfo(w http.ResponseWriter, r *http.Request)
HandleModuleAction(w http.ResponseWriter, r *http.Request)
}

type ModuleRestHandlerImpl struct {
logger *zap.SugaredLogger
moduleService module.ModuleService
userService user.UserService
enforcer casbin.Enforcer
validator *validator.Validate
}

func NewModuleRestHandlerImpl(logger *zap.SugaredLogger,
moduleService module.ModuleService,
userService user.UserService,
enforcer casbin.Enforcer,
validator *validator.Validate,
) *ModuleRestHandlerImpl {
return &ModuleRestHandlerImpl{
logger: logger,
moduleService: moduleService,
userService: userService,
enforcer: enforcer,
validator: validator,
}
}

func (impl ModuleRestHandlerImpl) GetModuleInfo(w http.ResponseWriter, r *http.Request) {
// check if user is logged in or not
userId, err := impl.userService.GetLoggedInUser(r)
if userId == 0 || err != nil {
common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
return
}

// check query param
params := mux.Vars(r)
moduleName := params["name"]
if len(moduleName) == 0 {
impl.logger.Error("module name is not supplied")
common.WriteJsonResp(w, errors.New("module name is not supplied"), nil, http.StatusBadRequest)
return
}

// service call
res, err := impl.moduleService.GetModuleInfo(moduleName)
if err != nil {
impl.logger.Errorw("service err, GetModuleInfo", "err", err)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
common.WriteJsonResp(w, err, res, http.StatusOK)
}

func (impl ModuleRestHandlerImpl) HandleModuleAction(w http.ResponseWriter, r *http.Request) {
// check if user is logged in or not
userId, err := impl.userService.GetLoggedInUser(r)
if userId == 0 || err != nil {
common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized)
return
}

// check query param
params := mux.Vars(r)
moduleName := params["name"]
if len(moduleName) == 0 {
impl.logger.Error("module name is not supplied")
common.WriteJsonResp(w, errors.New("module name is not supplied"), nil, http.StatusBadRequest)
return
}

// decode request
decoder := json.NewDecoder(r.Body)
var moduleActionRequestDto *module.ModuleActionRequestDto
err = decoder.Decode(&moduleActionRequestDto)
if err != nil {
impl.logger.Errorw("error in decoding request in HandleModuleAction", "err", err)
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
return
}
err = impl.validator.Struct(moduleActionRequestDto)
if err != nil {
impl.logger.Errorw("error in validating request in HandleModuleAction", "err", err)
common.WriteJsonResp(w, err, nil, http.StatusBadRequest)
return
}

// handle super-admin RBAC
token := r.Header.Get("token")
if ok := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*"); !ok {
common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden)
return
}

// service call
res, err := impl.moduleService.HandleModuleAction(userId, moduleName, moduleActionRequestDto)
if err != nil {
impl.logger.Errorw("service err, HandleModuleAction", "err", err)
common.WriteJsonResp(w, err, nil, http.StatusInternalServerError)
return
}
common.WriteJsonResp(w, err, res, http.StatusOK)
}
Loading