diff --git a/Wire.go b/Wire.go index 7536b6d25f..eb0edb3b85 100644 --- a/Wire.go +++ b/Wire.go @@ -82,6 +82,7 @@ import ( "github.com/devtron-labs/devtron/client/argocdServer/certificate" cluster2 "github.com/devtron-labs/devtron/client/argocdServer/cluster" "github.com/devtron-labs/devtron/client/argocdServer/connection" + "github.com/devtron-labs/devtron/client/argocdServer/repoCredsK8sClient" repocreds "github.com/devtron-labs/devtron/client/argocdServer/repocreds" repository2 "github.com/devtron-labs/devtron/client/argocdServer/repository" session2 "github.com/devtron-labs/devtron/client/argocdServer/session" @@ -118,7 +119,6 @@ import ( repository9 "github.com/devtron-labs/devtron/pkg/appStore/installedApp/repository" deployment3 "github.com/devtron-labs/devtron/pkg/appStore/installedApp/service/FullMode/deployment" "github.com/devtron-labs/devtron/pkg/appWorkflow" - "github.com/devtron-labs/devtron/pkg/argoRepositoryCreds" "github.com/devtron-labs/devtron/pkg/asyncProvider" "github.com/devtron-labs/devtron/pkg/attributes" "github.com/devtron-labs/devtron/pkg/build" @@ -950,8 +950,8 @@ func InitializeApp() (*App, error) { common.NewDeploymentConfigServiceImpl, wire.Bind(new(common.DeploymentConfigService), new(*common.DeploymentConfigServiceImpl)), - argoRepositoryCreds.NewRepositorySecret, - wire.Bind(new(argoRepositoryCreds.RepositorySecret), new(*argoRepositoryCreds.RepositorySecretImpl)), + repoCredsK8sClient.NewRepositorySecret, + wire.Bind(new(repoCredsK8sClient.RepositoryCreds), new(*repoCredsK8sClient.RepositorySecretImpl)), repocreds.NewServiceClientImpl, wire.Bind(new(repocreds.ServiceClient), new(*repocreds.ServiceClientImpl)), diff --git a/client/argocdServer/repoCredsK8sClient/bean/bean.go b/client/argocdServer/repoCredsK8sClient/bean/bean.go new file mode 100644 index 0000000000..5059562d6e --- /dev/null +++ b/client/argocdServer/repoCredsK8sClient/bean/bean.go @@ -0,0 +1,53 @@ +package bean + +const ( + REPOSITORY_SECRET_NAME_KEY = "name" + REPOSITORY_SECRET_URL_KEY = "url" + REPOSITORY_SECRET_USERNAME_KEY = "username" + REPOSITORY_SECRET_PASSWORD_KEY = "password" + REPOSITORY_SECRET_ENABLE_OCI_KEY = "enableOCI" + REPOSITORY_SECRET_TYPE_KEY = "type" + REPOSITORY_TYPE_HELM = "helm" + ARGOCD_REPOSITORY_SECRET_KEY = "argocd.argoproj.io/secret-type" + ARGOCD_REPOSITORY_SECRET_VALUE = "repository" + REPOSITORY_SECRET_INSECURE_KEY = "insecure" +) + +type ChartRepositoryAddRequest struct { + Name string + Username string + Password string + URL string + AllowInsecureConnection bool + IsPrivateChart bool +} + +type ChartRepositoryUpdateRequest struct { + PreviousName string + PreviousURL string + Name string + AuthMode string + Username string + Password string + SSHKey string + URL string + AllowInsecureConnection bool + IsPrivateChart bool +} + +type KeyDto struct { + Name string `json:"name,omitempty"` + Key string `json:"key,omitempty"` + Url string `json:"url,omitempty"` +} + +type AcdConfigMapRepositoriesDto struct { + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Url string `json:"url,omitempty"` + UsernameSecret *KeyDto `json:"usernameSecret,omitempty"` + PasswordSecret *KeyDto `json:"passwordSecret,omitempty"` + CaSecret *KeyDto `json:"caSecret,omitempty"` + CertSecret *KeyDto `json:"certSecret,omitempty"` + KeySecret *KeyDto `json:"keySecret,omitempty"` +} diff --git a/client/argocdServer/repoCredsK8sClient/repositoryCredsManager.go b/client/argocdServer/repoCredsK8sClient/repositoryCredsManager.go new file mode 100644 index 0000000000..96e02f6ea1 --- /dev/null +++ b/client/argocdServer/repoCredsK8sClient/repositoryCredsManager.go @@ -0,0 +1,660 @@ +package repoCredsK8sClient + +import ( + "encoding/json" + "fmt" + "github.com/devtron-labs/common-lib/utils/k8s" + "github.com/devtron-labs/devtron/client/argocdServer/repoCredsK8sClient/bean" + "github.com/devtron-labs/devtron/internal/sql/constants" + "github.com/devtron-labs/devtron/pkg/cluster" + util2 "github.com/devtron-labs/devtron/pkg/util" + "github.com/ghodss/yaml" + "github.com/google/uuid" + "go.uber.org/zap" + errors2 "k8s.io/apimachinery/pkg/api/errors" + "net/http" + url2 "net/url" + "path" + yaml2 "sigs.k8s.io/yaml" + "strings" +) + +type RepositoryCreds interface { + AddOrUpdateOCIRegistry(username, password string, uniqueId int, registryUrl, repo string, isPublic bool) error + DeleteOCIRegistry(registryURL, repo string, ociRegistryId int) error + AddChartRepository(request bean.ChartRepositoryAddRequest) error + UpdateChartRepository(request bean.ChartRepositoryUpdateRequest) error + DeleteChartRepository(name, url string) error +} + +type RepositorySecretImpl struct { + logger *zap.SugaredLogger + K8sService k8s.K8sService + clusterService cluster.ClusterService + acdAuthConfig *util2.ACDAuthConfig +} + +func NewRepositorySecret( + logger *zap.SugaredLogger, + K8sService k8s.K8sService, + clusterService cluster.ClusterService, + acdAuthConfig *util2.ACDAuthConfig, +) *RepositorySecretImpl { + return &RepositorySecretImpl{ + logger: logger, + K8sService: K8sService, + clusterService: clusterService, + acdAuthConfig: acdAuthConfig, + } +} + +func (impl *RepositorySecretImpl) AddOrUpdateOCIRegistry(username, password string, uniqueId int, registryUrl, repo string, isPublic bool) error { + + secretData, uniqueSecretName, err := getSecretDataAndName( + username, + password, + uniqueId, + registryUrl, + repo, + isPublic) + if err != nil { + impl.logger.Errorw("error in getting secretData and secretName", "err", err) + return err + } + + err = impl.createOrUpdateArgoRepoSecret(uniqueSecretName, secretData) + if err != nil { + impl.logger.Errorw("error in create/update k8s secret", "registryUrl", registryUrl, "repo", repo, "err", err) + return err + } + return nil + +} + +func (impl *RepositorySecretImpl) DeleteOCIRegistry(registryURL, repo string, ociRegistryId int) error { + + _, _, chartName, err := getHostAndFullRepoPathAndChartName(registryURL, repo) + if err != nil { + return err + } + + uniqueSecretName := fmt.Sprintf("%s-%d", chartName, ociRegistryId) + + err = impl.DeleteChartSecret(uniqueSecretName) + if err != nil { + impl.logger.Errorw("error in deleting oci registry secret", "secretName", uniqueSecretName, "err", err) + return err + } + return nil +} + +func (impl *RepositorySecretImpl) createOrUpdateArgoRepoSecret(uniqueSecretName string, secretData map[string]string) error { + clusterBean, err := impl.clusterService.FindOne(cluster.DEFAULT_CLUSTER) + if err != nil { + impl.logger.Errorw("error in fetching cluster bean from db", "err", err) + return err + } + cfg := clusterBean.GetClusterConfig() + if err != nil { + impl.logger.Errorw("error in getting cluster config", "err", err) + return err + } + client, err := impl.K8sService.GetCoreV1Client(cfg) + if err != nil { + impl.logger.Errorw("error in creating kubernetes client", "err", err) + return err + } + + secretLabel := make(map[string]string) + secretLabel[bean.ARGOCD_REPOSITORY_SECRET_KEY] = bean.ARGOCD_REPOSITORY_SECRET_VALUE + + err = impl.K8sService.CreateOrUpdateSecretByName( + client, + impl.acdAuthConfig.ACDConfigMapNamespace, + uniqueSecretName, + secretLabel, + secretData) + if err != nil { + impl.logger.Errorw("error in create/update argocd secret by name", "secretName", uniqueSecretName, "err", err) + return err + } + + return nil +} + +func getSecretDataAndName(username, password string, ociRegistryId int, registryUrl, repo string, isPublic bool) (map[string]string, string, error) { + + host, fullRepoPath, chartName, err := getHostAndFullRepoPathAndChartName(registryUrl, repo) + if err != nil { + return nil, "", nil + } + + uniqueSecretName := fmt.Sprintf("%s-%d", chartName, ociRegistryId) + + secretData := parseSecretDataForOCI(username, password, fullRepoPath, host, isPublic) + + return secretData, uniqueSecretName, nil +} + +func getHostAndFullRepoPathAndChartName(registryUrl string, repo string) (string, string, string, error) { + host, fullRepoPath, err := GetHostAndFullRepoPath(registryUrl, repo) + if err != nil { + return "", "", "", err + } + repoSplit := strings.Split(fullRepoPath, "/") + chartName := repoSplit[len(repoSplit)-1] + return host, fullRepoPath, chartName, nil +} + +func GetHostAndFullRepoPath(url string, repo string) (string, string, error) { + parsedUrl, err := url2.Parse(url) + if err != nil || parsedUrl.Scheme == "" { + url = fmt.Sprintf("//%s", url) + parsedUrl, err = url2.Parse(url) + if err != nil { + return "", "", err + } + } + var repoName string + if len(parsedUrl.Path) > 0 { + repoName = path.Join(strings.TrimLeft(parsedUrl.Path, "/"), repo) + } else { + repoName = repo + } + return parsedUrl.Host, repoName, nil +} + +func parseSecretDataForOCI(username, password, repoName, repoHost string, isPublic bool) map[string]string { + secretData := make(map[string]string) + secretData[bean.REPOSITORY_SECRET_NAME_KEY] = repoName // argocd will use this for passing repository credentials to application + secretData[bean.REPOSITORY_SECRET_TYPE_KEY] = bean.REPOSITORY_TYPE_HELM + secretData[bean.REPOSITORY_SECRET_URL_KEY] = repoHost + if !isPublic { + secretData[bean.REPOSITORY_SECRET_USERNAME_KEY] = username + secretData[bean.REPOSITORY_SECRET_PASSWORD_KEY] = password + } + secretData[bean.REPOSITORY_SECRET_ENABLE_OCI_KEY] = "true" + return secretData +} + +func (impl *RepositorySecretImpl) AddChartRepository(request bean.ChartRepositoryAddRequest) error { + clusterBean, err := impl.clusterService.FindOne(cluster.DEFAULT_CLUSTER) + if err != nil { + impl.logger.Errorw("error in fetching cluster bean from db", "err", err) + return err + } + cfg := clusterBean.GetClusterConfig() + client, err := impl.K8sService.GetCoreV1Client(cfg) + if err != nil { + impl.logger.Errorw("error in creating kubernetes client", "err", err) + return err + } + + updateSuccess := false + retryCount := 0 + + for !updateSuccess && retryCount < 3 { + retryCount = retryCount + 1 + + secretLabel := make(map[string]string) + secretLabel[bean.ARGOCD_REPOSITORY_SECRET_KEY] = bean.ARGOCD_REPOSITORY_SECRET_VALUE + secretData := impl.CreateSecretDataForHelmChart( + request.Name, + request.Username, + request.Password, + request.URL, + request.AllowInsecureConnection, + request.IsPrivateChart) + _, err = impl.K8sService.CreateSecret(impl.acdAuthConfig.ACDConfigMapNamespace, nil, request.Name, "", client, secretLabel, secretData) + if err != nil { + // TODO refactoring: Implement the below error handling if secret name already exists + //if statusError, ok := err.(*k8sErrors.StatusError); ok && + // statusError != nil && + // statusError.Status().Code == http.StatusConflict && + // statusError.ErrStatus.Reason == metav1.StatusReasonAlreadyExists { + // impl.logger.Errorw("secret already exists", "err", statusError.Error()) + // return nil, fmt.Errorf(statusError.Error()) + //} + continue + } + if err == nil { + updateSuccess = true + } + } + if !updateSuccess { + impl.logger.Errorw("error in creating secret for chart repository", "err", err) + return fmt.Errorf("resouce version not matched with config map attempted 3 times") + } + return nil +} + +func (impl *RepositorySecretImpl) UpdateChartRepository(request bean.ChartRepositoryUpdateRequest) error { + clusterBean, err := impl.clusterService.FindOne(cluster.DEFAULT_CLUSTER) + if err != nil { + return err + } + cfg := clusterBean.GetClusterConfig() + client, err := impl.K8sService.GetCoreV1Client(cfg) + if err != nil { + return err + } + + updateSuccess := false + retryCount := 0 + for !updateSuccess && retryCount < 3 { + retryCount = retryCount + 1 + + var isFoundInArgoCdCm bool + cm, err := impl.K8sService.GetConfigMap(impl.acdAuthConfig.ACDConfigMapNamespace, impl.acdAuthConfig.ACDConfigMapName, client) + if err != nil { + return err + } + var repositories []*bean.AcdConfigMapRepositoriesDto + if cm != nil && cm.Data != nil { + repoStr := cm.Data["repositories"] + repoByte, err := yaml2.YAMLToJSON([]byte(repoStr)) + if err != nil { + impl.logger.Errorw("error in json patch", "err", err) + return err + } + err = json.Unmarshal(repoByte, &repositories) + if err != nil { + impl.logger.Errorw("error in unmarshal", "err", err) + return err + } + for _, repo := range repositories { + if repo.Name == request.PreviousName && repo.Url == request.PreviousURL { + //chart repo is present in argocd-cm + isFoundInArgoCdCm = true + break + } + } + } + + if isFoundInArgoCdCm { + var data map[string]string + // if the repo name has been updated then, create a new repo + if cm != nil && cm.Data != nil { + data, err = impl.updateRepoData(cm.Data, + request.Name, + request.AuthMode, + request.Username, + request.Password, + request.SSHKey, + request.URL) + // if the repo name has been updated then, delete the previous repo + if err != nil { + impl.logger.Warnw(" config map update failed", "err", err) + continue + } + if request.PreviousName != request.Name { + data, err = impl.removeRepoData(cm.Data, request.PreviousName) + } + if err != nil { + impl.logger.Warnw(" config map update failed", "err", err) + continue + } + } + cm.Data = data + _, err = impl.K8sService.UpdateConfigMap(impl.acdAuthConfig.ACDConfigMapNamespace, cm, client) + } else { + secretData := impl.CreateSecretDataForHelmChart(request.Name, + request.Username, + request.Password, + request.URL, + request.AllowInsecureConnection, + request.IsPrivateChart) + secret, err := impl.K8sService.GetSecret(impl.acdAuthConfig.ACDConfigMapNamespace, request.PreviousName, client) + statusError, ok := err.(*errors2.StatusError) + if err != nil && (ok && statusError != nil && statusError.Status().Code != http.StatusNotFound) { + impl.logger.Errorw("error in fetching secret", "err", err) + continue + } + + if ok && statusError != nil && statusError.Status().Code == http.StatusNotFound { + secretLabel := make(map[string]string) + secretLabel[bean.ARGOCD_REPOSITORY_SECRET_KEY] = bean.ARGOCD_REPOSITORY_SECRET_VALUE + _, err = impl.K8sService.CreateSecret(impl.acdAuthConfig.ACDConfigMapNamespace, nil, request.PreviousName, "", client, secretLabel, secretData) + if err != nil { + impl.logger.Errorw("Error in creating secret for chart repo", "Chart Name", request.PreviousName, "err", err) + continue + } + updateSuccess = true + break + } + + if request.PreviousName != request.Name { + err = impl.DeleteChartSecret(request.PreviousName) + if err != nil { + impl.logger.Errorw("Error in deleting secret for chart repo", "Chart Name", request.Name, "err", err) + continue + } + secretLabel := make(map[string]string) + secretLabel[bean.ARGOCD_REPOSITORY_SECRET_KEY] = bean.ARGOCD_REPOSITORY_SECRET_VALUE + _, err = impl.K8sService.CreateSecret(impl.acdAuthConfig.ACDConfigMapNamespace, nil, request.Name, "", client, secretLabel, secretData) + if err != nil { + impl.logger.Errorw("Error in creating secret for chart repo", "Chart Name", request.Name, "err", err) + } + } else { + secret.StringData = secretData + _, err = impl.K8sService.UpdateSecret(impl.acdAuthConfig.ACDConfigMapNamespace, secret, client) + if err != nil { + impl.logger.Errorw("Error in creating secret for chart repo", "Chart Name", request.Name, "err", err) + } + } + if err != nil { + impl.logger.Warnw("secret update for chart repo failed", "err", err) + continue + } + } + if err != nil { + impl.logger.Warnw(" config map failed", "err", err) + continue + } + if err == nil { + impl.logger.Warnw(" config map apply succeeded", "on retryCount", retryCount) + updateSuccess = true + } + } + if !updateSuccess { + return fmt.Errorf("resouce version not matched with config map attempted 3 times") + } + return nil +} + +func (impl *RepositorySecretImpl) DeleteChartSecret(secretName string) error { + clusterBean, err := impl.clusterService.FindOne(cluster.DEFAULT_CLUSTER) + if err != nil { + return err + } + cfg := clusterBean.GetClusterConfig() + client, err := impl.K8sService.GetCoreV1Client(cfg) + if err != nil { + return err + } + err = impl.K8sService.DeleteSecret(impl.acdAuthConfig.ACDConfigMapNamespace, secretName, client) + return err +} + +func (impl *RepositorySecretImpl) removeRepoData(data map[string]string, name string) (map[string]string, error) { + helmRepoStr := data["helm.repositories"] + helmRepoByte, err := yaml.YAMLToJSON([]byte(helmRepoStr)) + if err != nil { + impl.logger.Errorw("error in json patch", "err", err) + return nil, err + } + var helmRepositories []*bean.AcdConfigMapRepositoriesDto + err = json.Unmarshal(helmRepoByte, &helmRepositories) + if err != nil { + impl.logger.Errorw("error in unmarshal", "err", err) + return nil, err + } + + rb, err := json.Marshal(helmRepositories) + if err != nil { + impl.logger.Errorw("error in marshal", "err", err) + return nil, err + } + helmRepositoriesYamlByte, err := yaml.JSONToYAML(rb) + if err != nil { + impl.logger.Errorw("error in yaml patch", "err", err) + return nil, err + } + + //SETUP for repositories + var repositories []*bean.AcdConfigMapRepositoriesDto + repoStr := data["repositories"] + repoByte, err := yaml.YAMLToJSON([]byte(repoStr)) + if err != nil { + impl.logger.Errorw("error in json patch", "err", err) + return nil, err + } + err = json.Unmarshal(repoByte, &repositories) + if err != nil { + impl.logger.Errorw("error in unmarshal", "err", err) + return nil, err + } + + found := false + for index, item := range repositories { + //if request chart repo found, then delete its values + if item.Name == name { + repositories = append(repositories[:index], repositories[index+1:]...) + found = true + break + } + } + + // if request chart repo not found, add new one + if !found { + impl.logger.Errorw("Repo not found", "err", err) + return nil, fmt.Errorf("Repo not found in config-map") + } + + rb, err = json.Marshal(repositories) + if err != nil { + impl.logger.Errorw("error in marshal", "err", err) + return nil, err + } + repositoriesYamlByte, err := yaml.JSONToYAML(rb) + if err != nil { + impl.logger.Errorw("error in yaml patch", "err", err) + return nil, err + } + + if len(helmRepositoriesYamlByte) > 0 { + data["helm.repositories"] = string(helmRepositoriesYamlByte) + } + if len(repositoriesYamlByte) > 0 { + data["repositories"] = string(repositoriesYamlByte) + } + //dex config copy as it is + dexConfigStr := data["dex.config"] + data["dex.config"] = string([]byte(dexConfigStr)) + return data, nil +} + +// updateRepoData update the request field in the argo-cm +func (impl *RepositorySecretImpl) updateRepoData(data map[string]string, name, authMode, username, password, sshKey, url string) (map[string]string, error) { + helmRepoStr := data["helm.repositories"] + helmRepoByte, err := yaml.YAMLToJSON([]byte(helmRepoStr)) + if err != nil { + impl.logger.Errorw("error in json patch", "err", err) + return nil, err + } + var helmRepositories []*bean.AcdConfigMapRepositoriesDto + err = json.Unmarshal(helmRepoByte, &helmRepositories) + if err != nil { + impl.logger.Errorw("error in unmarshal", "err", err) + return nil, err + } + + rb, err := json.Marshal(helmRepositories) + if err != nil { + impl.logger.Errorw("error in marshal", "err", err) + return nil, err + } + helmRepositoriesYamlByte, err := yaml.JSONToYAML(rb) + if err != nil { + impl.logger.Errorw("error in yaml patch", "err", err) + return nil, err + } + + //SETUP for repositories + var repositories []*bean.AcdConfigMapRepositoriesDto + repoStr := data["repositories"] + repoByte, err := yaml.YAMLToJSON([]byte(repoStr)) + if err != nil { + impl.logger.Errorw("error in json patch", "err", err) + return nil, err + } + err = json.Unmarshal(repoByte, &repositories) + if err != nil { + impl.logger.Errorw("error in unmarshal", "err", err) + return nil, err + } + + found := false + for _, item := range repositories { + //if request chart repo found, then update its values + if item.Name == name { + if authMode == string(constants.AUTH_MODE_USERNAME_PASSWORD) { + usernameSecret := &bean.KeyDto{Name: username, Key: "username"} + passwordSecret := &bean.KeyDto{Name: password, Key: "password"} + item.PasswordSecret = passwordSecret + item.UsernameSecret = usernameSecret + } else if authMode == string(constants.AUTH_MODE_ACCESS_TOKEN) { + // TODO - is it access token or ca cert nd secret + } else if authMode == string(constants.AUTH_MODE_SSH) { + keySecret := &bean.KeyDto{Name: sshKey, Key: "key"} + item.KeySecret = keySecret + } + item.Url = url + found = true + } + } + + // if request chart repo not found, add new one + if !found { + repoData := impl.createRepoElement(authMode, username, password, sshKey, url, name) + repositories = append(repositories, repoData) + } + + rb, err = json.Marshal(repositories) + if err != nil { + impl.logger.Errorw("error in marshal", "err", err) + return nil, err + } + repositoriesYamlByte, err := yaml.JSONToYAML(rb) + if err != nil { + impl.logger.Errorw("error in yaml patch", "err", err) + return nil, err + } + + if len(helmRepositoriesYamlByte) > 0 { + data["helm.repositories"] = string(helmRepositoriesYamlByte) + } + if len(repositoriesYamlByte) > 0 { + data["repositories"] = string(repositoriesYamlByte) + } + //dex config copy as it is + dexConfigStr := data["dex.config"] + data["dex.config"] = string([]byte(dexConfigStr)) + return data, nil +} + +func (impl *RepositorySecretImpl) createRepoElement(authmode, username, password, sshKey, url, name string) *bean.AcdConfigMapRepositoriesDto { + repoData := &bean.AcdConfigMapRepositoriesDto{} + if authmode == string(constants.AUTH_MODE_USERNAME_PASSWORD) { + usernameSecret := &bean.KeyDto{Name: username, Key: "username"} + passwordSecret := &bean.KeyDto{Name: password, Key: "password"} + repoData.PasswordSecret = passwordSecret + repoData.UsernameSecret = usernameSecret + } else if authmode == string(constants.AUTH_MODE_ACCESS_TOKEN) { + // TODO - is it access token or ca cert nd secret + } else if (authmode) == string(constants.AUTH_MODE_SSH) { + keySecret := &bean.KeyDto{Name: sshKey, Key: "key"} + repoData.KeySecret = keySecret + } + repoData.Url = url + repoData.Name = name + repoData.Type = "helm" + + return repoData +} + +// Private helm charts credentials are saved as secrets +func (impl *RepositorySecretImpl) CreateSecretDataForHelmChart(name, username, password, repoURL string, allowInsecureConnection, isPrivateChart bool) (secretData map[string]string) { + secretData = make(map[string]string) + secretData[bean.REPOSITORY_SECRET_NAME_KEY] = fmt.Sprintf("%s-%s", name, uuid.New().String()) // making repo name unique so that "helm repo add" command in argo-repo-server doesn't give error + secretData[bean.REPOSITORY_SECRET_TYPE_KEY] = bean.REPOSITORY_TYPE_HELM + secretData[bean.REPOSITORY_SECRET_URL_KEY] = repoURL + if isPrivateChart { + secretData[bean.REPOSITORY_SECRET_USERNAME_KEY] = username + secretData[bean.REPOSITORY_SECRET_PASSWORD_KEY] = password + } + isInsecureConnection := "true" + if !allowInsecureConnection { + isInsecureConnection = "false" + } + secretData[bean.REPOSITORY_SECRET_INSECURE_KEY] = isInsecureConnection + return secretData +} + +func (impl *RepositorySecretImpl) DeleteChartRepository(name, url string) error { + + clusterBean, err := impl.clusterService.FindOne(cluster.DEFAULT_CLUSTER) + if err != nil { + return err + } + cfg := clusterBean.GetClusterConfig() + client, err := impl.K8sService.GetCoreV1Client(cfg) + if err != nil { + return err + } + updateSuccess := false + retryCount := 0 + //request.RedirectionUrl = "" + + for !updateSuccess && retryCount < 3 { + retryCount = retryCount + 1 + + var isFoundInArgoCdCm bool + cm, err := impl.K8sService.GetConfigMap(impl.acdAuthConfig.ACDConfigMapNamespace, impl.acdAuthConfig.ACDConfigMapName, client) + if err != nil { + return err + } + var repositories []*bean.AcdConfigMapRepositoriesDto + if cm != nil && cm.Data != nil { + repoStr := cm.Data["repositories"] + repoByte, err := yaml.YAMLToJSON([]byte(repoStr)) + if err != nil { + impl.logger.Errorw("error in json patch", "err", err) + return err + } + err = json.Unmarshal(repoByte, &repositories) + if err != nil { + impl.logger.Errorw("error in unmarshal", "err", err) + return err + } + for _, repo := range repositories { + if repo.Name == name && repo.Url == url { + //chart repo is present in argocd-cm + isFoundInArgoCdCm = true + break + } + } + } + + if isFoundInArgoCdCm { + var data map[string]string + + if cm != nil && cm.Data != nil { + data, err = impl.removeRepoData(cm.Data, name) + if err != nil { + impl.logger.Warnw(" config map update failed", "err", err) + continue + } + } + cm.Data = data + _, err = impl.K8sService.UpdateConfigMap(impl.acdAuthConfig.ACDConfigMapNamespace, cm, client) + } else { + err = impl.DeleteChartSecret(name) + if err != nil { + impl.logger.Errorw("Error in deleting secret for chart repo", "Chart Name", name, "err", err) + } + } + if err != nil { + impl.logger.Warnw(" error in deleting config/secret failed", "err", err) + continue + } + if err == nil { + impl.logger.Warnw(" config map apply succeeded", "on retryCount", retryCount) + updateSuccess = true + } + } + if !updateSuccess { + return fmt.Errorf("resouce version not matched with config map attempted 3 times") + } + return nil +} diff --git a/pkg/argoRepositoryCreds/tests/ociSecret_test.go b/client/argocdServer/repoCredsK8sClient/tests/ociSecret_test.go similarity index 87% rename from pkg/argoRepositoryCreds/tests/ociSecret_test.go rename to client/argocdServer/repoCredsK8sClient/tests/ociSecret_test.go index 97f2fca13a..17fd5dbf10 100644 --- a/pkg/argoRepositoryCreds/tests/ociSecret_test.go +++ b/client/argocdServer/repoCredsK8sClient/tests/ociSecret_test.go @@ -1,7 +1,7 @@ package tests import ( - "github.com/devtron-labs/devtron/pkg/argoRepositoryCreds" + "github.com/devtron-labs/devtron/client/argocdServer/repoCredsK8sClient" "testing" ) @@ -82,7 +82,7 @@ func Test_OCIArgoSecretRepoPathAndHostParseLogic(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if host, fullRepoPath, err := argoRepositoryCreds.GetHostAndFullRepoPath(tt.args.repositoryURL, tt.args.repositoryName); err != nil || host != tt.expectedHost || fullRepoPath != tt.expectedFullRepoPath { + if host, fullRepoPath, err := repoCredsK8sClient.GetHostAndFullRepoPath(tt.args.repositoryURL, tt.args.repositoryName); err != nil || host != tt.expectedHost || fullRepoPath != tt.expectedFullRepoPath { t.Errorf("SanitizeRepoNameAndURLForOCIRepo() = repositoryURL: %v , repositoryName: %v, want %v %v", host, fullRepoPath, tt.expectedHost, tt.expectedFullRepoPath) } }) diff --git a/cmd/external-app/wire.go b/cmd/external-app/wire.go index b835100fb0..44137bf475 100644 --- a/cmd/external-app/wire.go +++ b/cmd/external-app/wire.go @@ -52,6 +52,7 @@ import ( "github.com/devtron-labs/devtron/api/team" "github.com/devtron-labs/devtron/api/terminal" webhookHelm "github.com/devtron-labs/devtron/api/webhook/helm" + "github.com/devtron-labs/devtron/client/argocdServer/repoCredsK8sClient" "github.com/devtron-labs/devtron/client/argocdServer/session" "github.com/devtron-labs/devtron/client/dashboard" "github.com/devtron-labs/devtron/client/telemetry" @@ -67,7 +68,6 @@ import ( repository4 "github.com/devtron-labs/devtron/pkg/appStore/chartGroup/repository" deployment2 "github.com/devtron-labs/devtron/pkg/appStore/installedApp/service/EAMode/deployment" "github.com/devtron-labs/devtron/pkg/appStore/installedApp/service/FullMode/deployment" - "github.com/devtron-labs/devtron/pkg/argoRepositoryCreds" "github.com/devtron-labs/devtron/pkg/attributes" "github.com/devtron-labs/devtron/pkg/build/git/gitMaterial" delete2 "github.com/devtron-labs/devtron/pkg/delete" @@ -255,8 +255,8 @@ func InitializeApp() (*App, error) { wire.Bind(new(util4.K8sService), new(*util4.K8sServiceImpl)), - argoRepositoryCreds.NewRepositorySecret, - wire.Bind(new(argoRepositoryCreds.RepositorySecret), new(*argoRepositoryCreds.RepositorySecretImpl)), + repoCredsK8sClient.NewRepositorySecret, + wire.Bind(new(repoCredsK8sClient.RepositoryCreds), new(*repoCredsK8sClient.RepositorySecretImpl)), dbMigration.NewDbMigrationServiceImpl, wire.Bind(new(dbMigration.DbMigration), new(*dbMigration.DbMigrationServiceImpl)), diff --git a/cmd/external-app/wire_gen.go b/cmd/external-app/wire_gen.go index b4d20a3efb..15b9eae6d0 100644 --- a/cmd/external-app/wire_gen.go +++ b/cmd/external-app/wire_gen.go @@ -46,6 +46,7 @@ import ( terminal2 "github.com/devtron-labs/devtron/api/terminal" webhookHelm2 "github.com/devtron-labs/devtron/api/webhook/helm" "github.com/devtron-labs/devtron/client/argocdServer" + "github.com/devtron-labs/devtron/client/argocdServer/repoCredsK8sClient" "github.com/devtron-labs/devtron/client/dashboard" "github.com/devtron-labs/devtron/client/telemetry" repository5 "github.com/devtron-labs/devtron/internal/sql/repository" @@ -73,7 +74,6 @@ import ( "github.com/devtron-labs/devtron/pkg/argoApplication" read6 "github.com/devtron-labs/devtron/pkg/argoApplication/read" config2 "github.com/devtron-labs/devtron/pkg/argoApplication/read/config" - "github.com/devtron-labs/devtron/pkg/argoRepositoryCreds" "github.com/devtron-labs/devtron/pkg/attributes" "github.com/devtron-labs/devtron/pkg/auth/authentication" "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" @@ -238,7 +238,8 @@ func InitializeApp() (*App, error) { if err != nil { return nil, err } - chartRepositoryServiceImpl := chartRepo.NewChartRepositoryServiceImpl(sugaredLogger, chartRepoRepositoryImpl, k8sServiceImpl, clusterServiceImpl, acdAuthConfig, httpClient, serverEnvConfigServerEnvConfig) + repositorySecretImpl := repoCredsK8sClient.NewRepositorySecret(sugaredLogger, k8sServiceImpl, clusterServiceImpl, acdAuthConfig) + chartRepositoryServiceImpl := chartRepo.NewChartRepositoryServiceImpl(sugaredLogger, chartRepoRepositoryImpl, k8sServiceImpl, clusterServiceImpl, acdAuthConfig, httpClient, serverEnvConfigServerEnvConfig, repositorySecretImpl) installedAppRepositoryImpl := repository6.NewInstalledAppRepositoryImpl(sugaredLogger, db) helmClientConfig, err := gRPC.GetConfig() if err != nil { @@ -266,7 +267,6 @@ func InitializeApp() (*App, error) { dockerArtifactStoreRepositoryImpl := repository7.NewDockerArtifactStoreRepositoryImpl(db) dockerRegistryIpsConfigRepositoryImpl := repository7.NewDockerRegistryIpsConfigRepositoryImpl(db) ociRegistryConfigRepositoryImpl := repository7.NewOCIRegistryConfigRepositoryImpl(db) - repositorySecretImpl := argoRepositoryCreds.NewRepositorySecret(sugaredLogger, k8sServiceImpl, clusterServiceImpl, acdAuthConfig) dockerRegistryConfigImpl := pipeline.NewDockerRegistryConfigImpl(sugaredLogger, helmAppServiceImpl, dockerArtifactStoreRepositoryImpl, dockerRegistryIpsConfigRepositoryImpl, ociRegistryConfigRepositoryImpl, repositorySecretImpl) deleteServiceImpl := delete2.NewDeleteServiceImpl(sugaredLogger, teamServiceImpl, clusterServiceImpl, environmentServiceImpl, chartRepositoryServiceImpl, installedAppRepositoryImpl, dockerRegistryConfigImpl, dockerArtifactStoreRepositoryImpl, k8sInformerFactoryImpl, k8sServiceImpl) teamRestHandlerImpl := team2.NewTeamRestHandlerImpl(sugaredLogger, teamServiceImpl, userServiceImpl, enforcerImpl, validate, userAuthServiceImpl, deleteServiceImpl) diff --git a/pkg/appStore/installedApp/service/FullMode/deployment/FullModeDeploymentService.go b/pkg/appStore/installedApp/service/FullMode/deployment/FullModeDeploymentService.go index 587a91814f..e7eb3c012a 100644 --- a/pkg/appStore/installedApp/service/FullMode/deployment/FullModeDeploymentService.go +++ b/pkg/appStore/installedApp/service/FullMode/deployment/FullModeDeploymentService.go @@ -23,9 +23,9 @@ import ( "fmt" "github.com/devtron-labs/devtron/api/helm-app/gRPC" client "github.com/devtron-labs/devtron/api/helm-app/service" + "github.com/devtron-labs/devtron/client/argocdServer/repoCredsK8sClient" "github.com/devtron-labs/devtron/internal/sql/repository/pipelineConfig/bean/timelineStatus" "github.com/devtron-labs/devtron/pkg/appStore/installedApp/service/common" - "github.com/devtron-labs/devtron/pkg/argoRepositoryCreds" repository5 "github.com/devtron-labs/devtron/pkg/cluster/environment/repository" "github.com/devtron-labs/devtron/pkg/deployment/common" commonBean "github.com/devtron-labs/devtron/pkg/deployment/gitOps/common/bean" @@ -97,7 +97,7 @@ type FullModeDeploymentServiceImpl struct { environmentRepository repository5.EnvironmentRepository deploymentConfigService common.DeploymentConfigService chartTemplateService util.ChartTemplateService - RepositorySecretService argoRepositoryCreds.RepositorySecret + RepositorySecretService repoCredsK8sClient.RepositoryCreds } func NewFullModeDeploymentServiceImpl( @@ -123,7 +123,7 @@ func NewFullModeDeploymentServiceImpl( environmentRepository repository5.EnvironmentRepository, deploymentConfigService common.DeploymentConfigService, chartTemplateService util.ChartTemplateService, - RepositorySecretService argoRepositoryCreds.RepositorySecret) *FullModeDeploymentServiceImpl { + RepositorySecretService repoCredsK8sClient.RepositoryCreds) *FullModeDeploymentServiceImpl { return &FullModeDeploymentServiceImpl{ Logger: logger, acdClient: acdClient, diff --git a/pkg/appStore/installedApp/service/FullMode/deployment/InstalledAppGitOpsService.go b/pkg/appStore/installedApp/service/FullMode/deployment/InstalledAppGitOpsService.go index 8ee5873a1b..0943420954 100644 --- a/pkg/appStore/installedApp/service/FullMode/deployment/InstalledAppGitOpsService.go +++ b/pkg/appStore/installedApp/service/FullMode/deployment/InstalledAppGitOpsService.go @@ -406,7 +406,7 @@ func (impl *FullModeDeploymentServiceImpl) CreateArgoRepoSecretIfNeeded(appStore appStore := appStoreApplicationVersion.AppStore dockerArtifactStore := appStoreApplicationVersion.AppStore.DockerArtifactStore - err = impl.RepositorySecretService.CreateArgoRepositorySecret( + err = impl.RepositorySecretService.AddOrUpdateOCIRegistry( dockerArtifactStore.Username, dockerArtifactStore.Password, dockerArtifactStore.OCIRegistryConfig[0].Id, diff --git a/pkg/argoRepositoryCreds/bean/bean.go b/pkg/argoRepositoryCreds/bean/bean.go deleted file mode 100644 index df7984b72c..0000000000 --- a/pkg/argoRepositoryCreds/bean/bean.go +++ /dev/null @@ -1,13 +0,0 @@ -package bean - -const ( - REPOSITORY_SECRET_NAME_KEY = "name" - REPOSITORY_SECRET_URL_KEY = "url" - REPOSITORY_SECRET_USERNAME_KEY = "username" - REPOSITORY_SECRET_PASSWORD_KEY = "password" - REPOSITORY_SECRET_ENABLE_OCI_KEY = "enableOCI" - REPOSITORY_SECRET_TYPE_KEY = "type" - REPOSITORY_TYPE_HELM = "helm" - ARGOCD_REPOSITORY_SECRET_KEY = "argocd.argoproj.io/secret-type" - ARGOCD_REPOSITORY_SECRET_VALUE = "repository" -) diff --git a/pkg/argoRepositoryCreds/repositorySecretCreationService.go b/pkg/argoRepositoryCreds/repositorySecretCreationService.go deleted file mode 100644 index 6fc7423c97..0000000000 --- a/pkg/argoRepositoryCreds/repositorySecretCreationService.go +++ /dev/null @@ -1,145 +0,0 @@ -package argoRepositoryCreds - -import ( - "fmt" - "github.com/devtron-labs/common-lib/utils/k8s" - "github.com/devtron-labs/devtron/pkg/argoRepositoryCreds/bean" - "github.com/devtron-labs/devtron/pkg/cluster" - util2 "github.com/devtron-labs/devtron/pkg/util" - "go.uber.org/zap" - url2 "net/url" - "path" - "strings" -) - -type RepositorySecret interface { - CreateArgoRepositorySecret(username, password string, uniqueId int, registryUrl, repo string, isPublic bool) error -} - -type RepositorySecretImpl struct { - logger *zap.SugaredLogger - K8sService k8s.K8sService - clusterService cluster.ClusterService - acdAuthConfig *util2.ACDAuthConfig -} - -func NewRepositorySecret( - logger *zap.SugaredLogger, - K8sService k8s.K8sService, - clusterService cluster.ClusterService, - acdAuthConfig *util2.ACDAuthConfig, -) *RepositorySecretImpl { - return &RepositorySecretImpl{ - logger: logger, - K8sService: K8sService, - clusterService: clusterService, - acdAuthConfig: acdAuthConfig, - } -} - -func (impl *RepositorySecretImpl) CreateArgoRepositorySecret(username, password string, uniqueId int, registryUrl, repo string, isPublic bool) error { - - secretData, uniqueSecretName, err := getSecretDataAndName( - username, - password, - uniqueId, - registryUrl, - repo, - isPublic) - if err != nil { - impl.logger.Errorw("error in getting secretData and secretName", "err", err) - return err - } - - err = impl.createOrUpdateArgoRepoSecret(uniqueSecretName, secretData) - if err != nil { - impl.logger.Errorw("error in create/update k8s secret", "registryUrl", registryUrl, "repo", repo, "err", err) - return err - } - return nil - -} - -func (impl *RepositorySecretImpl) createOrUpdateArgoRepoSecret(uniqueSecretName string, secretData map[string]string) error { - clusterBean, err := impl.clusterService.FindOne(cluster.DEFAULT_CLUSTER) - if err != nil { - impl.logger.Errorw("error in fetching cluster bean from db", "err", err) - return err - } - cfg := clusterBean.GetClusterConfig() - if err != nil { - impl.logger.Errorw("error in getting cluster config", "err", err) - return err - } - client, err := impl.K8sService.GetCoreV1Client(cfg) - if err != nil { - impl.logger.Errorw("error in creating kubernetes client", "err", err) - return err - } - - secretLabel := make(map[string]string) - secretLabel[bean.ARGOCD_REPOSITORY_SECRET_KEY] = bean.ARGOCD_REPOSITORY_SECRET_VALUE - - err = impl.K8sService.CreateOrUpdateSecretByName( - client, - impl.acdAuthConfig.ACDConfigMapNamespace, - uniqueSecretName, - secretLabel, - secretData) - if err != nil { - impl.logger.Errorw("error in create/update argocd secret by name", "secretName", uniqueSecretName, "err", err) - return err - } - - return nil -} - -func getSecretDataAndName(username, password string, ociRegistryId int, registryUrl, repo string, isPublic bool) (map[string]string, string, error) { - - url := registryUrl - - host, fullRepoPath, err := GetHostAndFullRepoPath(url, repo) - if err != nil { - return nil, "", err - } - - repoSplit := strings.Split(fullRepoPath, "/") - chartName := repoSplit[len(repoSplit)-1] - - uniqueSecretName := fmt.Sprintf("%s-%d", chartName, ociRegistryId) - - secretData := parseSecretData(username, password, fullRepoPath, host, isPublic) - - return secretData, uniqueSecretName, nil -} - -func GetHostAndFullRepoPath(url string, repo string) (string, string, error) { - parsedUrl, err := url2.Parse(url) - if err != nil || parsedUrl.Scheme == "" { - url = fmt.Sprintf("//%s", url) - parsedUrl, err = url2.Parse(url) - if err != nil { - return "", "", err - } - } - var repoName string - if len(parsedUrl.Path) > 0 { - repoName = path.Join(strings.TrimLeft(parsedUrl.Path, "/"), repo) - } else { - repoName = repo - } - return parsedUrl.Host, repoName, nil -} - -func parseSecretData(username, password, repoName, repoHost string, isPublic bool) map[string]string { - secretData := make(map[string]string) - secretData[bean.REPOSITORY_SECRET_NAME_KEY] = repoName // argocd will use this for passing repository credentials to application - secretData[bean.REPOSITORY_SECRET_TYPE_KEY] = bean.REPOSITORY_TYPE_HELM - secretData[bean.REPOSITORY_SECRET_URL_KEY] = repoHost - if !isPublic { - secretData[bean.REPOSITORY_SECRET_USERNAME_KEY] = username - secretData[bean.REPOSITORY_SECRET_PASSWORD_KEY] = password - } - secretData[bean.REPOSITORY_SECRET_ENABLE_OCI_KEY] = "true" - return secretData -} diff --git a/pkg/chartRepo/ChartRepositoryService.go b/pkg/chartRepo/ChartRepositoryService.go index 1433c31959..43078ea2bc 100644 --- a/pkg/chartRepo/ChartRepositoryService.go +++ b/pkg/chartRepo/ChartRepositoryService.go @@ -18,14 +18,13 @@ package chartRepo import ( "bytes" - "encoding/json" "errors" "fmt" util3 "github.com/devtron-labs/common-lib/utils/k8s" - "github.com/devtron-labs/devtron/internal/sql/constants" + "github.com/devtron-labs/devtron/client/argocdServer/repoCredsK8sClient" + "github.com/devtron-labs/devtron/client/argocdServer/repoCredsK8sClient/bean" "io" "io/ioutil" - errors2 "k8s.io/apimachinery/pkg/api/errors" "net/http" "net/url" "strconv" @@ -38,13 +37,11 @@ import ( serverEnvConfig "github.com/devtron-labs/devtron/pkg/server/config" "github.com/devtron-labs/devtron/pkg/sql" util2 "github.com/devtron-labs/devtron/pkg/util" - "github.com/google/uuid" "go.uber.org/zap" "k8s.io/helm/pkg/getter" "k8s.io/helm/pkg/helm/environment" "k8s.io/helm/pkg/repo" "k8s.io/helm/pkg/version" - "sigs.k8s.io/yaml" ) // secret keys @@ -76,51 +73,34 @@ type ChartRepositoryService interface { ValidateAndUpdateChartRepo(request *ChartRepoDto) (*chartRepoRepository.ChartRepo, error, *DetailedErrorHelmRepoValidation) TriggerChartSyncManual(chartProviderConfig *ChartProviderConfig) error DeleteChartRepo(request *ChartRepoDto) error - DeleteChartSecret(secretName string) error } type ChartRepositoryServiceImpl struct { - logger *zap.SugaredLogger - repoRepository chartRepoRepository.ChartRepoRepository - K8sUtil *util3.K8sServiceImpl - clusterService cluster.ClusterService - aCDAuthConfig *util2.ACDAuthConfig - client *http.Client - serverEnvConfig *serverEnvConfig.ServerEnvConfig + logger *zap.SugaredLogger + repoRepository chartRepoRepository.ChartRepoRepository + K8sUtil *util3.K8sServiceImpl + clusterService cluster.ClusterService + aCDAuthConfig *util2.ACDAuthConfig + client *http.Client + serverEnvConfig *serverEnvConfig.ServerEnvConfig + RepositoryCredsManager repoCredsK8sClient.RepositoryCreds } func NewChartRepositoryServiceImpl(logger *zap.SugaredLogger, repoRepository chartRepoRepository.ChartRepoRepository, K8sUtil *util3.K8sServiceImpl, clusterService cluster.ClusterService, - aCDAuthConfig *util2.ACDAuthConfig, client *http.Client, serverEnvConfig *serverEnvConfig.ServerEnvConfig) *ChartRepositoryServiceImpl { + aCDAuthConfig *util2.ACDAuthConfig, client *http.Client, serverEnvConfig *serverEnvConfig.ServerEnvConfig, + RepositoryCredsManager repoCredsK8sClient.RepositoryCreds) *ChartRepositoryServiceImpl { return &ChartRepositoryServiceImpl{ - logger: logger, - repoRepository: repoRepository, - K8sUtil: K8sUtil, - clusterService: clusterService, - aCDAuthConfig: aCDAuthConfig, - client: client, - serverEnvConfig: serverEnvConfig, + logger: logger, + repoRepository: repoRepository, + K8sUtil: K8sUtil, + clusterService: clusterService, + aCDAuthConfig: aCDAuthConfig, + client: client, + serverEnvConfig: serverEnvConfig, + RepositoryCredsManager: RepositoryCredsManager, } } -// Private helm charts credentials are saved as secrets -func (impl *ChartRepositoryServiceImpl) CreateSecretDataForHelmChart(request *ChartRepoDto, isPrivateChart bool) (secretData map[string]string) { - secretData = make(map[string]string) - secretData[NAME] = fmt.Sprintf("%s-%s", request.Name, uuid.New().String()) // making repo name unique so that "helm repo add" command in argo-repo-server doesn't give error - secretData[TYPE] = HELM - secretData[URL] = request.Url - if isPrivateChart { - secretData[USERNAME] = request.UserName - secretData[PASSWORD] = request.Password - } - isInsecureConnection := "true" - if !request.AllowInsecureConnection { - isInsecureConnection = "false" - } - secretData[INSECRUE] = isInsecureConnection - - return secretData -} - func (impl *ChartRepositoryServiceImpl) CreateChartRepo(request *ChartRepoDto) (*chartRepoRepository.ChartRepo, error) { //metadata.name label in Secret doesn't support uppercase hence returning if user enters uppercase letters if strings.ToLower(request.Name) != request.Name { @@ -166,51 +146,24 @@ func (impl *ChartRepositoryServiceImpl) CreateChartRepo(request *ChartRepoDto) ( return nil, err } - clusterBean, err := impl.clusterService.FindOne(cluster.DEFAULT_CLUSTER) - if err != nil { - impl.logger.Errorw("error in fetching cluster bean from db", "err", err) - return nil, err - } - cfg := clusterBean.GetClusterConfig() - client, err := impl.K8sUtil.GetCoreV1Client(cfg) - if err != nil { - impl.logger.Errorw("error in creating kubernetes client", "err", err) - return nil, err - } - - updateSuccess := false - retryCount := 0 - isPrivateChart := false if len(chartRepo.UserName) > 0 && len(chartRepo.Password) > 0 { isPrivateChart = true } - for !updateSuccess && retryCount < 3 { - retryCount = retryCount + 1 - - secretLabel := make(map[string]string) - secretLabel[LABEL] = REPOSITORY - secretData := impl.CreateSecretDataForHelmChart(request, isPrivateChart) - _, err = impl.K8sUtil.CreateSecret(impl.aCDAuthConfig.ACDConfigMapNamespace, nil, chartRepo.Name, "", client, secretLabel, secretData) - if err != nil { - // TODO refactoring: Implement the below error handling if secret name already exists - //if statusError, ok := err.(*k8sErrors.StatusError); ok && - // statusError != nil && - // statusError.Status().Code == http.StatusConflict && - // statusError.ErrStatus.Reason == metav1.StatusReasonAlreadyExists { - // impl.logger.Errorw("secret already exists", "err", statusError.Error()) - // return nil, fmt.Errorf(statusError.Error()) - //} - continue - } - if err == nil { - updateSuccess = true - } + argoServerAddRequest := bean.ChartRepositoryAddRequest{ + Name: chartRepo.Name, + Username: chartRepo.UserName, + Password: chartRepo.Password, + URL: chartRepo.Url, + AllowInsecureConnection: chartRepo.AllowInsecureConnection, + IsPrivateChart: isPrivateChart, } - if !updateSuccess { - impl.logger.Errorw("error in creating secret for chart repository", "err", err) - return nil, fmt.Errorf("resouce version not matched with config map attempted 3 times") + + err = impl.RepositoryCredsManager.AddChartRepository(argoServerAddRequest) + if err != nil { + impl.logger.Errorw("error in adding chart repository to argocd server", "name", request.Name, "err", err) + return nil, err } err = tx.Commit() if err != nil { @@ -276,130 +229,27 @@ func (impl *ChartRepositoryServiceImpl) UpdateData(request *ChartRepoDto) (*char return nil, err } - // modify configmap - clusterBean, err := impl.clusterService.FindOne(cluster.DEFAULT_CLUSTER) - if err != nil { - return nil, err - } - cfg := clusterBean.GetClusterConfig() - client, err := impl.K8sUtil.GetCoreV1Client(cfg) - if err != nil { - return nil, err - } - isPrivateChart := false if len(chartRepo.UserName) > 0 && len(chartRepo.Password) > 0 { isPrivateChart = true } - updateSuccess := false - retryCount := 0 - for !updateSuccess && retryCount < 3 { - retryCount = retryCount + 1 - - var isFoundInArgoCdCm bool - cm, err := impl.K8sUtil.GetConfigMap(impl.aCDAuthConfig.ACDConfigMapNamespace, impl.aCDAuthConfig.ACDConfigMapName, client) - if err != nil { - return nil, err - } - var repositories []*AcdConfigMapRepositoriesDto - if cm != nil && cm.Data != nil { - repoStr := cm.Data["repositories"] - repoByte, err := yaml.YAMLToJSON([]byte(repoStr)) - if err != nil { - impl.logger.Errorw("error in json patch", "err", err) - return nil, err - } - err = json.Unmarshal(repoByte, &repositories) - if err != nil { - impl.logger.Errorw("error in unmarshal", "err", err) - return nil, err - } - for _, repo := range repositories { - if repo.Name == previousName && repo.Url == previousUrl { - //chart repo is present in argocd-cm - isFoundInArgoCdCm = true - break - } - } - } - - if isFoundInArgoCdCm { - var data map[string]string - // if the repo name has been updated then, create a new repo - if cm != nil && cm.Data != nil { - data, err = impl.updateRepoData(cm.Data, request) - // if the repo name has been updated then, delete the previous repo - if err != nil { - impl.logger.Warnw(" config map update failed", "err", err) - continue - } - if previousName != request.Name { - data, err = impl.removeRepoData(cm.Data, previousName) - } - if err != nil { - impl.logger.Warnw(" config map update failed", "err", err) - continue - } - } - cm.Data = data - _, err = impl.K8sUtil.UpdateConfigMap(impl.aCDAuthConfig.ACDConfigMapNamespace, cm, client) - } else { - secretData := impl.CreateSecretDataForHelmChart(request, isPrivateChart) - secret, err := impl.K8sUtil.GetSecret(impl.aCDAuthConfig.ACDConfigMapNamespace, previousName, client) - statusError, ok := err.(*errors2.StatusError) - if err != nil && (ok && statusError != nil && statusError.Status().Code != http.StatusNotFound) { - impl.logger.Errorw("error in fetching secret", "err", err) - continue - } - - if ok && statusError != nil && statusError.Status().Code == http.StatusNotFound { - secretLabel := make(map[string]string) - secretLabel[LABEL] = REPOSITORY - _, err = impl.K8sUtil.CreateSecret(impl.aCDAuthConfig.ACDConfigMapNamespace, nil, chartRepo.Name, "", client, secretLabel, secretData) - if err != nil { - impl.logger.Errorw("Error in creating secret for chart repo", "Chart Name", chartRepo.Name, "err", err) - continue - } - updateSuccess = true - break - } - - if previousName != request.Name { - err = impl.DeleteChartSecret(previousName) - if err != nil { - impl.logger.Errorw("Error in deleting secret for chart repo", "Chart Name", chartRepo.Name, "err", err) - continue - } - secretLabel := make(map[string]string) - secretLabel[LABEL] = REPOSITORY - _, err = impl.K8sUtil.CreateSecret(impl.aCDAuthConfig.ACDConfigMapNamespace, nil, chartRepo.Name, "", client, secretLabel, secretData) - if err != nil { - impl.logger.Errorw("Error in creating secret for chart repo", "Chart Name", chartRepo.Name, "err", err) - } - } else { - secret.StringData = secretData - _, err = impl.K8sUtil.UpdateSecret(impl.aCDAuthConfig.ACDConfigMapNamespace, secret, client) - if err != nil { - impl.logger.Errorw("Error in creating secret for chart repo", "Chart Name", chartRepo.Name, "err", err) - } - } - if err != nil { - impl.logger.Warnw("secret update for chart repo failed", "err", err) - continue - } - } - if err != nil { - impl.logger.Warnw(" config map failed", "err", err) - continue - } - if err == nil { - impl.logger.Warnw(" config map apply succeeded", "on retryCount", retryCount) - updateSuccess = true - } + argoServerUpdateRequest := bean.ChartRepositoryUpdateRequest{ + PreviousName: previousName, + PreviousURL: previousUrl, + Name: chartRepo.Name, + AuthMode: string(chartRepo.AuthMode), + Username: chartRepo.UserName, + Password: chartRepo.Password, + SSHKey: chartRepo.SshKey, + URL: chartRepo.Url, + AllowInsecureConnection: chartRepo.AllowInsecureConnection, + IsPrivateChart: isPrivateChart, } - if !updateSuccess { - return nil, fmt.Errorf("resouce version not matched with config map attempted 3 times") + // modify configmap + err = impl.RepositoryCredsManager.UpdateChartRepository(argoServerUpdateRequest) + if err != nil { + return nil, err } err = tx.Commit() if err != nil { @@ -442,79 +292,11 @@ func (impl *ChartRepositoryServiceImpl) DeleteChartRepo(request *ChartRepoDto) e } // modify configmap - clusterBean, err := impl.clusterService.FindOne(cluster.DEFAULT_CLUSTER) + err = impl.RepositoryCredsManager.DeleteChartRepository(request.Name, request.Url) if err != nil { + impl.logger.Errorw("error in deleting chart repository from argocd", "name", request.Name, "err", err) return err } - cfg := clusterBean.GetClusterConfig() - client, err := impl.K8sUtil.GetCoreV1Client(cfg) - if err != nil { - return err - } - updateSuccess := false - retryCount := 0 - //request.RedirectionUrl = "" - - for !updateSuccess && retryCount < 3 { - retryCount = retryCount + 1 - - var isFoundInArgoCdCm bool - cm, err := impl.K8sUtil.GetConfigMap(impl.aCDAuthConfig.ACDConfigMapNamespace, impl.aCDAuthConfig.ACDConfigMapName, client) - if err != nil { - return err - } - var repositories []*AcdConfigMapRepositoriesDto - if cm != nil && cm.Data != nil { - repoStr := cm.Data["repositories"] - repoByte, err := yaml.YAMLToJSON([]byte(repoStr)) - if err != nil { - impl.logger.Errorw("error in json patch", "err", err) - return err - } - err = json.Unmarshal(repoByte, &repositories) - if err != nil { - impl.logger.Errorw("error in unmarshal", "err", err) - return err - } - for _, repo := range repositories { - if repo.Name == request.Name && repo.Url == request.Url { - //chart repo is present in argocd-cm - isFoundInArgoCdCm = true - break - } - } - } - - if isFoundInArgoCdCm { - var data map[string]string - - if cm != nil && cm.Data != nil { - data, err = impl.removeRepoData(cm.Data, request.Name) - if err != nil { - impl.logger.Warnw(" config map update failed", "err", err) - continue - } - } - cm.Data = data - _, err = impl.K8sUtil.UpdateConfigMap(impl.aCDAuthConfig.ACDConfigMapNamespace, cm, client) - } else { - err = impl.DeleteChartSecret(chartRepo.Name) - if err != nil { - impl.logger.Errorw("Error in deleting secret for chart repo", "Chart Name", chartRepo.Name, "err", err) - } - } - if err != nil { - impl.logger.Warnw(" error in deleting config/secret failed", "err", err) - continue - } - if err == nil { - impl.logger.Warnw(" config map apply succeeded", "on retryCount", retryCount) - updateSuccess = true - } - } - if !updateSuccess { - return fmt.Errorf("resouce version not matched with config map attempted 3 times") - } err = tx.Commit() if err != nil { impl.logger.Errorw("error in tx commit, DeleteChartRepo", "err", err) @@ -784,205 +566,3 @@ func (impl *ChartRepositoryServiceImpl) newChartRepository(cfg *repo.Entry, gett Client: client, }, nil, fmt.Sprintf("") } - -func (impl *ChartRepositoryServiceImpl) createRepoElement(request *ChartRepoDto) *AcdConfigMapRepositoriesDto { - repoData := &AcdConfigMapRepositoriesDto{} - if request.AuthMode == constants.AUTH_MODE_USERNAME_PASSWORD { - usernameSecret := &KeyDto{Name: request.UserName, Key: "username"} - passwordSecret := &KeyDto{Name: request.Password, Key: "password"} - repoData.PasswordSecret = passwordSecret - repoData.UsernameSecret = usernameSecret - } else if request.AuthMode == constants.AUTH_MODE_ACCESS_TOKEN { - // TODO - is it access token or ca cert nd secret - } else if request.AuthMode == constants.AUTH_MODE_SSH { - keySecret := &KeyDto{Name: request.SshKey, Key: "key"} - repoData.KeySecret = keySecret - } - repoData.Url = request.Url - repoData.Name = request.Name - repoData.Type = "helm" - - return repoData -} - -// updateRepoData update the request field in the argo-cm -func (impl *ChartRepositoryServiceImpl) updateRepoData(data map[string]string, request *ChartRepoDto) (map[string]string, error) { - helmRepoStr := data["helm.repositories"] - helmRepoByte, err := yaml.YAMLToJSON([]byte(helmRepoStr)) - if err != nil { - impl.logger.Errorw("error in json patch", "err", err) - return nil, err - } - var helmRepositories []*AcdConfigMapRepositoriesDto - err = json.Unmarshal(helmRepoByte, &helmRepositories) - if err != nil { - impl.logger.Errorw("error in unmarshal", "err", err) - return nil, err - } - - rb, err := json.Marshal(helmRepositories) - if err != nil { - impl.logger.Errorw("error in marshal", "err", err) - return nil, err - } - helmRepositoriesYamlByte, err := yaml.JSONToYAML(rb) - if err != nil { - impl.logger.Errorw("error in yaml patch", "err", err) - return nil, err - } - - //SETUP for repositories - var repositories []*AcdConfigMapRepositoriesDto - repoStr := data["repositories"] - repoByte, err := yaml.YAMLToJSON([]byte(repoStr)) - if err != nil { - impl.logger.Errorw("error in json patch", "err", err) - return nil, err - } - err = json.Unmarshal(repoByte, &repositories) - if err != nil { - impl.logger.Errorw("error in unmarshal", "err", err) - return nil, err - } - - found := false - for _, item := range repositories { - //if request chart repo found, then update its values - if item.Name == request.Name { - if request.AuthMode == constants.AUTH_MODE_USERNAME_PASSWORD { - usernameSecret := &KeyDto{Name: request.UserName, Key: "username"} - passwordSecret := &KeyDto{Name: request.Password, Key: "password"} - item.PasswordSecret = passwordSecret - item.UsernameSecret = usernameSecret - } else if request.AuthMode == constants.AUTH_MODE_ACCESS_TOKEN { - // TODO - is it access token or ca cert nd secret - } else if request.AuthMode == constants.AUTH_MODE_SSH { - keySecret := &KeyDto{Name: request.SshKey, Key: "key"} - item.KeySecret = keySecret - } - item.Url = request.Url - found = true - } - } - - // if request chart repo not found, add new one - if !found { - repoData := impl.createRepoElement(request) - repositories = append(repositories, repoData) - } - - rb, err = json.Marshal(repositories) - if err != nil { - impl.logger.Errorw("error in marshal", "err", err) - return nil, err - } - repositoriesYamlByte, err := yaml.JSONToYAML(rb) - if err != nil { - impl.logger.Errorw("error in yaml patch", "err", err) - return nil, err - } - - if len(helmRepositoriesYamlByte) > 0 { - data["helm.repositories"] = string(helmRepositoriesYamlByte) - } - if len(repositoriesYamlByte) > 0 { - data["repositories"] = string(repositoriesYamlByte) - } - //dex config copy as it is - dexConfigStr := data["dex.config"] - data["dex.config"] = string([]byte(dexConfigStr)) - return data, nil -} - -// removeRepoData delete the request field from the argo-cm -func (impl *ChartRepositoryServiceImpl) removeRepoData(data map[string]string, name string) (map[string]string, error) { - helmRepoStr := data["helm.repositories"] - helmRepoByte, err := yaml.YAMLToJSON([]byte(helmRepoStr)) - if err != nil { - impl.logger.Errorw("error in json patch", "err", err) - return nil, err - } - var helmRepositories []*AcdConfigMapRepositoriesDto - err = json.Unmarshal(helmRepoByte, &helmRepositories) - if err != nil { - impl.logger.Errorw("error in unmarshal", "err", err) - return nil, err - } - - rb, err := json.Marshal(helmRepositories) - if err != nil { - impl.logger.Errorw("error in marshal", "err", err) - return nil, err - } - helmRepositoriesYamlByte, err := yaml.JSONToYAML(rb) - if err != nil { - impl.logger.Errorw("error in yaml patch", "err", err) - return nil, err - } - - //SETUP for repositories - var repositories []*AcdConfigMapRepositoriesDto - repoStr := data["repositories"] - repoByte, err := yaml.YAMLToJSON([]byte(repoStr)) - if err != nil { - impl.logger.Errorw("error in json patch", "err", err) - return nil, err - } - err = json.Unmarshal(repoByte, &repositories) - if err != nil { - impl.logger.Errorw("error in unmarshal", "err", err) - return nil, err - } - - found := false - for index, item := range repositories { - //if request chart repo found, then delete its values - if item.Name == name { - repositories = append(repositories[:index], repositories[index+1:]...) - found = true - break - } - } - - // if request chart repo not found, add new one - if !found { - impl.logger.Errorw("Repo not found", "err", err) - return nil, fmt.Errorf("Repo not found in config-map") - } - - rb, err = json.Marshal(repositories) - if err != nil { - impl.logger.Errorw("error in marshal", "err", err) - return nil, err - } - repositoriesYamlByte, err := yaml.JSONToYAML(rb) - if err != nil { - impl.logger.Errorw("error in yaml patch", "err", err) - return nil, err - } - - if len(helmRepositoriesYamlByte) > 0 { - data["helm.repositories"] = string(helmRepositoriesYamlByte) - } - if len(repositoriesYamlByte) > 0 { - data["repositories"] = string(repositoriesYamlByte) - } - //dex config copy as it is - dexConfigStr := data["dex.config"] - data["dex.config"] = string([]byte(dexConfigStr)) - return data, nil -} - -func (impl *ChartRepositoryServiceImpl) DeleteChartSecret(secretName string) error { - clusterBean, err := impl.clusterService.FindOne(cluster.DEFAULT_CLUSTER) - if err != nil { - return err - } - cfg := clusterBean.GetClusterConfig() - client, err := impl.K8sUtil.GetCoreV1Client(cfg) - if err != nil { - return err - } - err = impl.K8sUtil.DeleteSecret(impl.aCDAuthConfig.ACDConfigMapNamespace, secretName, client) - return err -} diff --git a/pkg/pipeline/DockerRegistryConfig.go b/pkg/pipeline/DockerRegistryConfig.go index 187a1b7330..3111cf98e8 100644 --- a/pkg/pipeline/DockerRegistryConfig.go +++ b/pkg/pipeline/DockerRegistryConfig.go @@ -21,7 +21,7 @@ import ( "fmt" bean2 "github.com/devtron-labs/devtron/api/helm-app/gRPC" client "github.com/devtron-labs/devtron/api/helm-app/service" - "github.com/devtron-labs/devtron/pkg/argoRepositoryCreds" + "github.com/devtron-labs/devtron/client/argocdServer/repoCredsK8sClient" "github.com/devtron-labs/devtron/pkg/pipeline/types" "github.com/devtron-labs/devtron/pkg/sql" "github.com/go-pg/pg" @@ -65,12 +65,12 @@ type DockerRegistryConfigImpl struct { dockerArtifactStoreRepository repository.DockerArtifactStoreRepository dockerRegistryIpsConfigRepository repository.DockerRegistryIpsConfigRepository ociRegistryConfigRepository repository.OCIRegistryConfigRepository - RepositorySecret argoRepositoryCreds.RepositorySecret + RepositorySecret repoCredsK8sClient.RepositoryCreds } func NewDockerRegistryConfigImpl(logger *zap.SugaredLogger, helmAppService client.HelmAppService, dockerArtifactStoreRepository repository.DockerArtifactStoreRepository, dockerRegistryIpsConfigRepository repository.DockerRegistryIpsConfigRepository, ociRegistryConfigRepository repository.OCIRegistryConfigRepository, - RepositorySecret argoRepositoryCreds.RepositorySecret) *DockerRegistryConfigImpl { + RepositorySecret repoCredsK8sClient.RepositoryCreds) *DockerRegistryConfigImpl { return &DockerRegistryConfigImpl{ logger: logger, helmAppService: helmAppService, @@ -298,7 +298,7 @@ func (impl DockerRegistryConfigImpl) ConfigureOCIRegistry(bean *types.DockerArti func (impl DockerRegistryConfigImpl) CreateArgoRepositorySecretForRepositories(artifactStore *types.DockerArtifactStoreBean, ociRegistryConfig *repository.OCIRegistryConfig) error { for _, repo := range artifactStore.RepositoryList { - err := impl.RepositorySecret.CreateArgoRepositorySecret(artifactStore.Username, + err := impl.RepositorySecret.AddOrUpdateOCIRegistry(artifactStore.Username, artifactStore.Password, ociRegistryConfig.Id, artifactStore.RegistryURL, @@ -852,6 +852,18 @@ func (impl DockerRegistryConfigImpl) DeleteReg(bean *types.DockerArtifactStoreBe impl.logger.Errorw("err in deleting OCI configs for registry", "registryId", bean.Id, "err", err) return err } + if ociRegistryConfig.RepositoryType == repository.OCI_REGISRTY_REPO_TYPE_CHART { + repositoryList := strings.Split(ociRegistryConfig.RepositoryList, ",") + for _, repo := range repositoryList { + err = impl.RepositorySecret.DeleteOCIRegistry(dockerReg.RegistryURL, repo, ociRegistryConfig.Id) + if err != nil { + impl.logger.Errorw("error in deleting OCI registry secret", "err", err) + //intentionally ignoring error + } + + } + } + } } diff --git a/wire_gen.go b/wire_gen.go index 459130f142..0ac39e304f 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -76,6 +76,7 @@ import ( "github.com/devtron-labs/devtron/client/argocdServer/certificate" "github.com/devtron-labs/devtron/client/argocdServer/cluster" "github.com/devtron-labs/devtron/client/argocdServer/connection" + "github.com/devtron-labs/devtron/client/argocdServer/repoCredsK8sClient" repository26 "github.com/devtron-labs/devtron/client/argocdServer/repocreds" repository21 "github.com/devtron-labs/devtron/client/argocdServer/repository" cron2 "github.com/devtron-labs/devtron/client/cron" @@ -127,7 +128,6 @@ import ( "github.com/devtron-labs/devtron/pkg/argoApplication" read15 "github.com/devtron-labs/devtron/pkg/argoApplication/read" config2 "github.com/devtron-labs/devtron/pkg/argoApplication/read/config" - "github.com/devtron-labs/devtron/pkg/argoRepositoryCreds" "github.com/devtron-labs/devtron/pkg/asyncProvider" "github.com/devtron-labs/devtron/pkg/attributes" "github.com/devtron-labs/devtron/pkg/auth/authentication" @@ -384,7 +384,8 @@ func InitializeApp() (*App, error) { if err != nil { return nil, err } - chartRepositoryServiceImpl := chartRepo.NewChartRepositoryServiceImpl(sugaredLogger, chartRepoRepositoryImpl, k8sServiceImpl, clusterServiceImplExtended, acdAuthConfig, httpClient, serverEnvConfigServerEnvConfig) + repositorySecretImpl := repoCredsK8sClient.NewRepositorySecret(sugaredLogger, k8sServiceImpl, clusterServiceImplExtended, acdAuthConfig) + chartRepositoryServiceImpl := chartRepo.NewChartRepositoryServiceImpl(sugaredLogger, chartRepoRepositoryImpl, k8sServiceImpl, clusterServiceImplExtended, acdAuthConfig, httpClient, serverEnvConfigServerEnvConfig, repositorySecretImpl) helmClientConfig, err := gRPC.GetConfig() if err != nil { return nil, err @@ -409,7 +410,6 @@ func InitializeApp() (*App, error) { dockerArtifactStoreRepositoryImpl := repository7.NewDockerArtifactStoreRepositoryImpl(db) dockerRegistryIpsConfigRepositoryImpl := repository7.NewDockerRegistryIpsConfigRepositoryImpl(db) ociRegistryConfigRepositoryImpl := repository7.NewOCIRegistryConfigRepositoryImpl(db) - repositorySecretImpl := argoRepositoryCreds.NewRepositorySecret(sugaredLogger, k8sServiceImpl, clusterServiceImplExtended, acdAuthConfig) dockerRegistryConfigImpl := pipeline.NewDockerRegistryConfigImpl(sugaredLogger, helmAppServiceImpl, dockerArtifactStoreRepositoryImpl, dockerRegistryIpsConfigRepositoryImpl, ociRegistryConfigRepositoryImpl, repositorySecretImpl) deleteServiceExtendedImpl := delete2.NewDeleteServiceExtendedImpl(sugaredLogger, teamServiceImpl, clusterServiceImplExtended, environmentServiceImpl, appRepositoryImpl, environmentRepositoryImpl, pipelineRepositoryImpl, chartRepositoryServiceImpl, installedAppRepositoryImpl, dockerRegistryConfigImpl, dockerArtifactStoreRepositoryImpl, k8sServiceImpl, k8sInformerFactoryImpl) argoApplicationConfigServiceImpl := config2.NewArgoApplicationConfigServiceImpl(sugaredLogger, k8sServiceImpl, clusterRepositoryImpl)