github.com/redhat-appstudio/e2e-tests@v0.0.0-20240520140907-9709f6f59323/magefiles/installation/install.go (about) 1 package installation 2 3 import ( 4 "context" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "os" 9 "time" 10 11 "strings" 12 13 appsv1 "k8s.io/api/apps/v1" 14 15 "github.com/devfile/library/v2/pkg/util" 16 "github.com/go-git/go-git/v5" 17 "github.com/go-git/go-git/v5/config" 18 "github.com/go-git/go-git/v5/plumbing" 19 configv1client "github.com/openshift/client-go/config/clientset/versioned" 20 21 appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned" 22 kubeCl "github.com/redhat-appstudio/e2e-tests/pkg/clients/kubernetes" 23 "github.com/redhat-appstudio/e2e-tests/pkg/constants" 24 "github.com/redhat-appstudio/e2e-tests/pkg/utils" 25 corev1 "k8s.io/api/core/v1" 26 k8sErrors "k8s.io/apimachinery/pkg/api/errors" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/types" 29 30 "k8s.io/client-go/tools/clientcmd" 31 "k8s.io/klog/v2" 32 33 sigsConfig "sigs.k8s.io/controller-runtime/pkg/client/config" 34 ) 35 36 const ( 37 DEFAULT_TMP_DIR = "tmp" 38 DEFAULT_INFRA_DEPLOYMENTS_BRANCH = "main" 39 DEFAULT_INFRA_DEPLOYMENTS_GH_ORG = "redhat-appstudio" 40 DEFAULT_LOCAL_FORK_NAME = "qe" 41 DEFAULT_LOCAL_FORK_ORGANIZATION = "redhat-appstudio-qe" 42 DEFAULT_E2E_QUAY_ORG = "redhat-appstudio-qe" 43 44 enableSchedulingOnMasterNodes = "true" 45 ) 46 47 var ( 48 previewInstallArgs = []string{"preview", "--keycloak", "--toolchain"} 49 ) 50 51 type patchStringValue struct { 52 Op string `json:"op"` 53 Path string `json:"path"` 54 Value string `json:"value"` 55 } 56 57 type InstallAppStudio struct { 58 // Kubernetes Client to interact with Openshift Cluster 59 KubernetesClient *kubeCl.CustomClient 60 61 // TmpDirectory to store temporary files like git repos or some metadata 62 TmpDirectory string 63 64 // Directory where to clone https://github.com/redhat-appstudio/infra-deployments repo 65 InfraDeploymentsCloneDir string 66 67 // Branch to clone from https://github.com/redhat-appstudio/infra-deployments. By default will be main 68 InfraDeploymentsBranch string 69 70 // Github organization from where will be cloned 71 InfraDeploymentsOrganizationName string 72 73 // Desired fork name for testing 74 LocalForkName string 75 76 // Github organization to use for testing purposes in preview mode 77 LocalGithubForkOrganization string 78 79 // Namespace where build applications will be placed 80 E2EApplicationsNamespace string 81 82 // base64-encoded content of a docker/config.json file which contains a valid login credentials for quay.io 83 QuayToken string 84 85 // Default quay organization for repositories generated by Image-controller 86 DefaultImageQuayOrg string 87 88 // Oauth2 token for default quay organization 89 DefaultImageQuayOrgOAuth2Token string 90 91 // Default expiration for image tags 92 DefaultImageTagExpiration string 93 94 // If set to "true", e2e-tests installer will mark master/control plane nodes as schedulable 95 EnableSchedulingOnMasterNodes string 96 } 97 98 func NewAppStudioInstallController() (*InstallAppStudio, error) { 99 cwd, _ := os.Getwd() 100 k8sClient, err := kubeCl.NewAdminKubernetesClient() 101 102 if err != nil { 103 return nil, err 104 } 105 106 return &InstallAppStudio{ 107 KubernetesClient: k8sClient, 108 TmpDirectory: DEFAULT_TMP_DIR, 109 InfraDeploymentsCloneDir: fmt.Sprintf("%s/%s/infra-deployments", cwd, DEFAULT_TMP_DIR), 110 InfraDeploymentsBranch: utils.GetEnv("INFRA_DEPLOYMENTS_BRANCH", DEFAULT_INFRA_DEPLOYMENTS_BRANCH), 111 InfraDeploymentsOrganizationName: utils.GetEnv("INFRA_DEPLOYMENTS_ORG", DEFAULT_INFRA_DEPLOYMENTS_GH_ORG), 112 LocalForkName: DEFAULT_LOCAL_FORK_NAME, 113 LocalGithubForkOrganization: utils.GetEnv("MY_GITHUB_ORG", DEFAULT_LOCAL_FORK_ORGANIZATION), 114 QuayToken: utils.GetEnv("QUAY_TOKEN", ""), 115 DefaultImageQuayOrg: utils.GetEnv("DEFAULT_QUAY_ORG", DEFAULT_E2E_QUAY_ORG), 116 DefaultImageQuayOrgOAuth2Token: utils.GetEnv("DEFAULT_QUAY_ORG_TOKEN", ""), 117 DefaultImageTagExpiration: utils.GetEnv(constants.IMAGE_TAG_EXPIRATION_ENV, constants.DefaultImageTagExpiration), 118 EnableSchedulingOnMasterNodes: utils.GetEnv(constants.ENABLE_SCHEDULING_ON_MASTER_NODES_ENV, enableSchedulingOnMasterNodes), 119 }, nil 120 } 121 122 func NewAppStudioInstallControllerUpgrade(infraFork string, infraBranch string) (*InstallAppStudio, error) { 123 cwd, _ := os.Getwd() 124 k8sClient, err := kubeCl.NewAdminKubernetesClient() 125 126 if err != nil { 127 return nil, err 128 } 129 130 return &InstallAppStudio{ 131 KubernetesClient: k8sClient, 132 TmpDirectory: DEFAULT_TMP_DIR, 133 InfraDeploymentsCloneDir: fmt.Sprintf("%s/%s/infra-deployments", cwd, DEFAULT_TMP_DIR), 134 InfraDeploymentsBranch: infraBranch, 135 InfraDeploymentsOrganizationName: infraFork, 136 LocalForkName: DEFAULT_LOCAL_FORK_NAME, 137 LocalGithubForkOrganization: utils.GetEnv("MY_GITHUB_ORG", DEFAULT_LOCAL_FORK_ORGANIZATION), 138 QuayToken: utils.GetEnv("QUAY_TOKEN", ""), 139 DefaultImageQuayOrg: utils.GetEnv("DEFAULT_QUAY_ORG", ""), 140 DefaultImageQuayOrgOAuth2Token: utils.GetEnv("DEFAULT_QUAY_ORG_TOKEN", ""), 141 DefaultImageTagExpiration: utils.GetEnv(constants.IMAGE_TAG_EXPIRATION_ENV, constants.DefaultImageTagExpiration), 142 EnableSchedulingOnMasterNodes: utils.GetEnv(constants.ENABLE_SCHEDULING_ON_MASTER_NODES_ENV, enableSchedulingOnMasterNodes), 143 }, nil 144 } 145 146 // Start the appstudio installation in preview mode. 147 func (i *InstallAppStudio) InstallAppStudioPreviewMode() error { 148 if err := i.cloneInfraDeployments(); err != nil { 149 return fmt.Errorf("failed to clone infra-deployments repository: %+v", err) 150 } 151 i.setInstallationEnvironments() 152 153 if i.EnableSchedulingOnMasterNodes == "true" { 154 if err := i.MarkMasterNodesAsSchedulable(); err != nil { 155 return err 156 } 157 } 158 159 if err := utils.ExecuteCommandInASpecificDirectory("hack/bootstrap-cluster.sh", previewInstallArgs, i.InfraDeploymentsCloneDir); err != nil { 160 return err 161 } 162 163 i.addSPIOauthRedirectProxyUrl() 164 165 return i.createE2EQuaySecret() 166 } 167 168 // MarkMasterNodesAsSchedulable uses configv1client for updating scheduler/cluster with "spec.mastersSchedulable:true" 169 func (i *InstallAppStudio) MarkMasterNodesAsSchedulable() error { 170 klog.Infof("Configuring master/control plane nodes as schedulable") 171 172 kubeconfig, err := sigsConfig.GetConfig() 173 if err != nil { 174 return fmt.Errorf("error when getting config: %+v", err) 175 } 176 177 configv1client, err := configv1client.NewForConfig(kubeconfig) 178 if err != nil { 179 return fmt.Errorf("error when creating configv1client: %+v", err) 180 } 181 _, err = configv1client.ConfigV1().Schedulers().Patch(context.Background(), "cluster", types.MergePatchType, []byte("{\"spec\":{\"mastersSchedulable\":true}}"), metav1.PatchOptions{}) 182 if err != nil { 183 return fmt.Errorf("failed to mark master nodes as schedulable: %+v", err) 184 } 185 return nil 186 } 187 188 func (i *InstallAppStudio) setInstallationEnvironments() { 189 os.Setenv("MY_GITHUB_ORG", i.LocalGithubForkOrganization) 190 os.Setenv("MY_GITHUB_TOKEN", utils.GetEnv("GITHUB_TOKEN", "")) 191 os.Setenv("MY_GIT_FORK_REMOTE", i.LocalForkName) 192 os.Setenv("TEST_BRANCH_ID", util.GenerateRandomString(4)) 193 os.Setenv("QUAY_TOKEN", i.QuayToken) 194 os.Setenv("IMAGE_CONTROLLER_QUAY_ORG", i.DefaultImageQuayOrg) 195 os.Setenv("IMAGE_CONTROLLER_QUAY_TOKEN", i.DefaultImageQuayOrgOAuth2Token) 196 os.Setenv("BUILD_SERVICE_IMAGE_TAG_EXPIRATION", i.DefaultImageTagExpiration) 197 os.Setenv("PAC_GITHUB_APP_ID", utils.GetEnv("E2E_PAC_GITHUB_APP_ID", "")) // #nosec G104 198 os.Setenv("PAC_GITHUB_APP_PRIVATE_KEY", utils.GetEnv("E2E_PAC_GITHUB_APP_PRIVATE_KEY", "")) // #nosec G104 199 os.Setenv(constants.ENABLE_SCHEDULING_ON_MASTER_NODES_ENV, i.EnableSchedulingOnMasterNodes) 200 } 201 202 func (i *InstallAppStudio) cloneInfraDeployments() error { 203 dirInfo, err := os.Stat(i.InfraDeploymentsCloneDir) 204 205 if !os.IsNotExist(err) && dirInfo.IsDir() { 206 klog.Warningf("folder %s already exists... removing", i.InfraDeploymentsCloneDir) 207 208 err := os.RemoveAll(i.InfraDeploymentsCloneDir) 209 if err != nil { 210 return fmt.Errorf("error removing %s folder", i.InfraDeploymentsCloneDir) 211 } 212 } 213 214 url := fmt.Sprintf("https://github.com/%s/infra-deployments", i.InfraDeploymentsOrganizationName) 215 refName := fmt.Sprintf("refs/heads/%s", i.InfraDeploymentsBranch) 216 klog.Infof("cloning '%s' with git ref '%s'", url, refName) 217 repo, _ := git.PlainClone(i.InfraDeploymentsCloneDir, false, &git.CloneOptions{ 218 URL: url, 219 ReferenceName: plumbing.ReferenceName(refName), 220 Progress: os.Stdout, 221 }) 222 223 if _, err := repo.CreateRemote(&config.RemoteConfig{Name: "upstream", URLs: []string{"https://github.com/redhat-appstudio/infra-deployments.git"}}); err != nil { 224 return err 225 } 226 if _, err := repo.CreateRemote(&config.RemoteConfig{Name: i.LocalForkName, URLs: []string{fmt.Sprintf("https://github.com/%s/infra-deployments.git", i.LocalGithubForkOrganization)}}); err != nil { 227 return err 228 } 229 if err := utils.ExecuteCommandInASpecificDirectory("git", []string{"pull", "--rebase", "upstream", "main"}, i.InfraDeploymentsCloneDir); err != nil { 230 return err 231 } 232 233 return nil 234 } 235 236 func (i *InstallAppStudio) CheckOperatorsReady() (err error) { 237 apiConfig, err := clientcmd.NewDefaultClientConfigLoadingRules().Load() 238 if err != nil { 239 klog.Fatal(err) 240 } 241 config, err := clientcmd.NewDefaultClientConfig(*apiConfig, &clientcmd.ConfigOverrides{}).ClientConfig() 242 if err != nil { 243 klog.Fatal(err) 244 } 245 appClientset := appclientset.NewForConfigOrDie(config) 246 247 patchPayload := []patchStringValue{{ 248 Op: "replace", 249 Path: "/metadata/annotations/argocd.argoproj.io~1refresh", 250 Value: "hard", 251 }} 252 patchPayloadBytes, err := json.Marshal(patchPayload) 253 if err != nil { 254 klog.Fatal(err) 255 } 256 _, err = appClientset.ArgoprojV1alpha1().Applications("openshift-gitops").Patch(context.Background(), "all-application-sets", types.JSONPatchType, patchPayloadBytes, metav1.PatchOptions{}) 257 if err != nil { 258 klog.Fatal(err) 259 } 260 261 for { 262 var count = 0 263 appsListFor, err := appClientset.ArgoprojV1alpha1().Applications("openshift-gitops").List(context.Background(), metav1.ListOptions{}) 264 for _, app := range appsListFor.Items { 265 fmt.Printf("Check application: %s\n", app.Name) 266 application, err := appClientset.ArgoprojV1alpha1().Applications("openshift-gitops").Get(context.Background(), app.Name, metav1.GetOptions{}) 267 if err != nil { 268 klog.Fatal(err) 269 } 270 271 if !(application.Status.Sync.Status == "Synced" && application.Status.Health.Status == "Healthy") { 272 klog.Infof("Application %s not ready", app.Name) 273 count++ 274 } else if strings.Contains(application.String(), ("context deadline exceeded")) { 275 fmt.Printf("Refreshing Application %s\n", app.Name) 276 patchPayload := []patchStringValue{{ 277 Op: "replace", 278 Path: "/metadata/annotations/argocd.argoproj.io~1refresh", 279 Value: "soft", 280 }} 281 282 patchPayloadBytes, err := json.Marshal(patchPayload) 283 if err != nil { 284 klog.Fatal(err) 285 } 286 for _, app := range appsListFor.Items { 287 _, err = i.KubernetesClient.KubeInterface().AppsV1().Deployments("openshift-gitops").Patch(context.Background(), app.Name, types.JSONPatchType, patchPayloadBytes, metav1.PatchOptions{}) 288 if err != nil { 289 klog.Fatal(err) 290 } 291 } 292 } 293 } 294 if err != nil { 295 klog.Fatal(err) 296 } 297 298 if count == 0 { 299 klog.Info("All Application are ready\n") 300 break 301 } 302 time.Sleep(10 * time.Second) 303 } 304 return err 305 } 306 307 // Create secret in e2e-secrets which can be copied to testing namespaces 308 func (i *InstallAppStudio) createE2EQuaySecret() error { 309 quayToken := os.Getenv("QUAY_TOKEN") 310 if quayToken == "" { 311 return fmt.Errorf("failed to obtain quay token from 'QUAY_TOKEN' env; make sure the env exists") 312 } 313 314 decodedToken, err := base64.StdEncoding.DecodeString(quayToken) 315 if err != nil { 316 return fmt.Errorf("failed to decode quay token. Make sure that QUAY_TOKEN env contain a base64 token") 317 } 318 319 namespace := constants.QuayRepositorySecretNamespace 320 _, err = i.KubernetesClient.KubeInterface().CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{}) 321 if err != nil { 322 if k8sErrors.IsNotFound(err) { 323 _, err := i.KubernetesClient.KubeInterface().CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{ 324 ObjectMeta: metav1.ObjectMeta{ 325 Name: namespace, 326 }, 327 }, metav1.CreateOptions{}) 328 if err != nil { 329 return fmt.Errorf("error when creating namespace %s : %v", namespace, err) 330 } 331 } else { 332 return fmt.Errorf("error when getting namespace %s : %v", namespace, err) 333 } 334 } 335 336 secretName := constants.QuayRepositorySecretName 337 secret, err := i.KubernetesClient.KubeInterface().CoreV1().Secrets(namespace).Get(context.Background(), secretName, metav1.GetOptions{}) 338 339 if err != nil { 340 if k8sErrors.IsNotFound(err) { 341 _, err := i.KubernetesClient.KubeInterface().CoreV1().Secrets(namespace).Create(context.Background(), &corev1.Secret{ 342 ObjectMeta: metav1.ObjectMeta{ 343 Name: secretName, 344 Namespace: namespace, 345 }, 346 Type: corev1.SecretTypeDockerConfigJson, 347 Data: map[string][]byte{ 348 corev1.DockerConfigJsonKey: decodedToken, 349 }, 350 }, metav1.CreateOptions{}) 351 352 if err != nil { 353 return fmt.Errorf("error when creating secret %s : %v", secretName, err) 354 } 355 } else { 356 secret.Data = map[string][]byte{ 357 corev1.DockerConfigJsonKey: decodedToken, 358 } 359 _, err = i.KubernetesClient.KubeInterface().CoreV1().Secrets(namespace).Update(context.Background(), secret, metav1.UpdateOptions{}) 360 if err != nil { 361 return fmt.Errorf("error when updating secret '%s' namespace: %v", secretName, err) 362 } 363 } 364 } 365 366 return nil 367 } 368 369 // Update spi-oauth-service-environment-config to add OAUTH_REDIRECT_PROXY_URL property for oauth tests 370 func (i *InstallAppStudio) addSPIOauthRedirectProxyUrl() { 371 OauthRedirectProxyUrl := os.Getenv("OAUTH_REDIRECT_PROXY_URL") 372 if OauthRedirectProxyUrl == "" { 373 klog.Error("OAUTH_REDIRECT_PROXY_URL not set: not updating spi configuration") 374 return 375 } 376 377 namespace := "spi-system" 378 configMapName := "spi-oauth-service-environment-config" 379 deploymentName := "spi-oauth-service" 380 381 patchData := []byte(fmt.Sprintf(`{"data": {"OAUTH_REDIRECT_PROXY_URL": "%s"}}`, OauthRedirectProxyUrl)) 382 _, err := i.KubernetesClient.KubeInterface().CoreV1().ConfigMaps(namespace).Patch(context.Background(), configMapName, types.MergePatchType, patchData, metav1.PatchOptions{}) 383 if err != nil { 384 klog.Error(err) 385 return 386 } 387 388 namespacedName := types.NamespacedName{ 389 Name: deploymentName, 390 Namespace: namespace, 391 } 392 393 deployment := &appsv1.Deployment{} 394 err = i.KubernetesClient.KubeRest().Get(context.Background(), namespacedName, deployment) 395 if err != nil { 396 klog.Error(err) 397 return 398 } 399 400 newDeployment := deployment.DeepCopy() 401 ann := newDeployment.ObjectMeta.Annotations 402 if ann == nil { 403 ann = make(map[string]string) 404 } 405 ann["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339) 406 var replicas int32 = 0 407 newDeployment.Spec.Replicas = &replicas 408 newDeployment.SetAnnotations(ann) 409 410 _, err = i.KubernetesClient.KubeInterface().AppsV1().Deployments(namespace).Update(context.Background(), newDeployment, metav1.UpdateOptions{}) 411 if err != nil { 412 klog.Error(err) 413 return 414 } 415 416 }